// Copyright 2025 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "merkle_tree.h"

#include <cassert>
#include <algorithm>
#include <optional>

#include <openssl/mem.h>
#include <openssl/span.h>
#include <openssl/sha2.h>

BSSL_NAMESPACE_BEGIN

namespace {

std::optional<TreeHashConstSpan> NextProofHash(Span<const uint8_t> *proof) {
  if (proof->size() < SHA256_DIGEST_LENGTH) {
    return std::nullopt;
  }
  auto ret = proof->first<SHA256_DIGEST_LENGTH>();
  *proof = proof->subspan(SHA256_DIGEST_LENGTH);
  return ret;
}

// TODO(crbug.com/404286922): Use std::countr_one when we have C++20.
size_t TrailingOnes(uint64_t n) {
  size_t count = 0;
  while (n & 1) {
    n >>= 1;
    count++;
  }
  return count;
}

}  // namespace

// Computes HASH(0x01 || left || right) and saves the result to |out|.
void HashNode(TreeHashConstSpan left, TreeHashConstSpan right,
              TreeHashSpan out) {
  static const uint8_t header = 0x01;
  SHA256_CTX ctx;
  SHA256_Init(&ctx);
  SHA256_Update(&ctx, &header, 1);
  SHA256_Update(&ctx, left.data(), left.size());
  SHA256_Update(&ctx, right.data(), right.size());
  SHA256_Final(out.data(), &ctx);
}

void HashLeaf(Span<const uint8_t> entry, TreeHashSpan out) {
  static const uint8_t header = 0x00;
  SHA256_CTX ctx;
  SHA256_Init(&ctx);
  SHA256_Update(&ctx, &header, 1);
  SHA256_Update(&ctx, entry.data(), entry.size());
  SHA256_Final(out.data(), &ctx);
}

std::optional<TreeHash> EvaluateMerkleSubtreeConsistencyProof(
    uint64_t n, const Subtree &subtree, Span<const uint8_t> proof,
    TreeHashConstSpan node_hash) {
  // For more detail on how subtree consistency proofs work, see appendix B
  // of draft-davidben-tls-merkle-tree-certs-08.

  // Check that inputs are valid. (Step 1)
  if (!subtree.IsValid() || n < subtree.end) {
    return std::nullopt;
  }

  // Initialize fn (first number), sn (second number), and tn (third number).
  // Each number is the path from the root of the tree to 1) the leftmost child
  // of the subtree, 2) the rightmost child of the subtree, and 3) the rightmost
  // child of the full tree. (Step 2)
  uint64_t fn = subtree.start;
  uint64_t sn = subtree.end - 1;
  uint64_t tn = n - 1;

  // The bit patterns of these numbers indicates whether the path goes left or
  // right (or in some cases on the rightmost edge of a (sub)tree, that a level
  // of the tree is skipped).
  //
  // When consuming the proof, we work up the tree from the bottom level (level
  // 0) up to the root, and moving up one level in the tree corresponds to
  // consuming the least significant bit from each of fn, sn, and tn. The proof
  // can start at a higher level than level 0 if the node on the right edge of
  // the subtree at that level is also a node of the overall tree.
  //
  // Remove bits equally from the right of fn, sn, and tn to skip to the level
  // of the tree where the proof starts. (Steps 3 and 4)
  if (sn == tn) {
    // If sn == tn, then the rightmost child of the subtree and the rightmost
    // child of the full tree are the same node, meaning that the subtree is
    // directly contained in the full tree. The proof starts at the same level
    // as the top of the subtree. That level is identified by advancing
    // bit-by-bit through fn and sn until they are the same, meaning that we've
    // moved up the levels of the tree to where the left and right edge of the
    // subtree reach the same point (the root of the subtree).
    //
    // (Step 3: right shift until fn is sn)
    while (fn != sn) {
      fn >>= 1;
      sn >>= 1;
      tn >>= 1;
    }
  } else {
    // Find the largest full (rather than partial) subtree that the rightmost
    // edge of the input subtree is still the rightmost edge of, without going
    // beyond the bounds of the input subtree. Any such full subtree is directly
    // contained in the tree of hash operations for the full tree, meaning that
    // the proof can start at that level rather than level 0.
    //
    // As long as the path to the rightmost edge of the input subtree is
    // indicating it's on the right side of its parent node (its LSB is 1), it's
    // still on the rightmost edge of a full (rather than partial) subtree.
    // Iteration stops when it's no longer on the rightmost edge of a full
    // subtree (LSB(sn) is not set) or when we reach the top of the input
    // subtree (fn is sn).
    //
    // (Step 4: right-shift until fn is sn or LSB(sn) is not set)
    while (fn != sn && (sn & 1) == 1) {
      fn >>= 1;
      sn >>= 1;
      tn >>= 1;
    }
  }

  // The proof array starts with the highest node from the subtree's right edge
  // that is also in the overall tree, and subsequent values from the proof
  // array are hashed in to compute the values that should be node_hash and
  // root_hash if the proof is valid. As an optimization, if node_hash is the
  // hash of the highest node from the subtree's right edge (i.e. the whole
  // subtree is directly contained in the overall tree), that value is omitted
  // from the proof.
  //
  // In this code, computed_node_hash and computed_root_hash are the values fr
  // and sr from draft-davidben-tls-merkle-tree-certs-08.
  // (Steps 5 and 6)
  TreeHash computed_node_hash, computed_root_hash;
  if (fn == sn) {
    // The optimization mentioned above. (Step 5)
    std::copy(node_hash.begin(), node_hash.end(), computed_node_hash.data());
    std::copy(node_hash.begin(), node_hash.end(), computed_root_hash.data());
  } else {
    // The hashes start from the first value of the proof (Step 6)
    std::optional<TreeHashConstSpan> first_hash = NextProofHash(&proof);
    if (!first_hash) {
      return std::nullopt;
    }
    std::copy(first_hash->begin(), first_hash->end(),
              computed_node_hash.data());
    std::copy(first_hash->begin(), first_hash->end(),
              computed_root_hash.data());
  }

  // Iterate over the (remaining) elements of the proof array and traverse up
  // the fn/sn/tn paths until we reach the root of the tree. Each step should
  // consume one element from the proof array and move one level up the tree,
  // and if the proof is valid both iterators end at the same time. (Step 7)
  while (!proof.empty()) {
    auto p = NextProofHash(&proof);
    if (!p) {
      return std::nullopt;
    }
    // (Step 7.1)
    if (tn == 0) {
      // We reached the root of the tree before running out of elements in the
      // proof.
      return std::nullopt;
    }
    // Update the computed root_hash, and if applicable the computed node_hash.
    // We stop updating computed_node_hash when we've reached the level of the
    // root of the subtree, which occurs when the paths to the leftmost and
    // rightmost nodes of the subtree are the same, i.e. fn == sn.
    //
    // (Step 7.2)
    if ((sn & 1) == 1 || sn == tn) {
      // (Step 7.2.1)
      if (fn < sn) {
        HashNode(*p, computed_node_hash, computed_node_hash);
      }
      // (Step 7.2.2)
      HashNode(*p, computed_root_hash, computed_root_hash);
      // Until LSB(sn) is set, right-shift fn, sn, and tn equally.
      // (Step 7.2.3)
      while ((sn & 1) == 0) {
        fn >>= 1;
        sn >>= 1;
        tn >>= 1;
      }
    } else {
      // (Step 7.3.1)
      HashNode(computed_root_hash, *p, computed_root_hash);
    }
    // Advance the iterators: (Step 7.4)
    fn >>= 1;
    sn >>= 1;
    tn >>= 1;
  }

  // Check that the iterators ended together: (Step 8)
  if (tn != 0) {
    return std::nullopt;
  }

  // Check that the computed values match the expected values: (Step 8)
  if (CRYPTO_memcmp(computed_node_hash.data(), node_hash.data(),
                    computed_node_hash.size()) != 0) {
    return std::nullopt;
  }
  // Return the computed root_hash for the caller to compare:
  return computed_root_hash;
}

std::optional<TreeHash> EvaluateMerkleSubtreeInclusionProof(
    Span<const uint8_t> inclusion_proof, uint64_t index,
    TreeHashConstSpan entry_hash, const Subtree &subtree) {
  if (!subtree.IsValid() || !subtree.Contains(index)) {
    return std::nullopt;
  }
  // Re-root |index| inside of |subtree|.
  index -= subtree.start;
  return EvaluateMerkleSubtreeConsistencyProof(
      subtree.Size(), {index, index + 1}, inclusion_proof, entry_hash);
}

TreeHash MerkleTree::SubtreeHash(const Subtree &subtree) const {
  BSSL_CHECK(subtree.IsValid());
  BSSL_CHECK(subtree.end <= Size());

  // Start at the largest complete subtree on the right edge.
  uint64_t start = subtree.start, last = subtree.end - 1;
  size_t level = TrailingOnes(last - start);
  start = start >> level;
  last = last >> level;
  TreeHash ret = GetNode(level, last);

  // Invariants:
  // * `start <= last`
  // * `subtree.start` is `start << level` (The subtree invariant implies we
  //   only shift away zero bits from `start`.)
  // * `ret` is `SubtreeHash({last << level, end})`.
  while (start < last) {
    if (last & 1) {
      // Hash in the left neighbor.
      HashNode(GetNode(level, last - 1), ret, ret);
    }
    level++;
    start >>= 1;
    last >>= 1;
  }

  return ret;
}

std::vector<uint8_t> MerkleTree::SubtreeInclusionProof(
    uint64_t index, const Subtree &subtree) const {
  BSSL_CHECK(subtree.IsValid());
  BSSL_CHECK(subtree.end <= Size());
  BSSL_CHECK(subtree.Contains(index));

  // The inclusion proof consists of the neighbors of `index` at each level.
  std::vector<uint8_t> proof;
  uint64_t start = subtree.start, last = subtree.end - 1;
  size_t level = 0;
  // Invariant: `proof` contains the inclusion proof of the original `index`
  // parameter in `Subtree{index << level, min((index + 1) << level, end)}}`.
  while (start < last) {
    // Append the neighbor node, if it exists.
    uint64_t neighbor = index ^ 1;
    if (neighbor < last) {
      // The neighbor is complete, so we can look it up directly.
      const TreeHash &h = GetNode(level, neighbor);
      proof.insert(proof.end(), h.begin(), h.end());
    } else if (neighbor == last) {
      // The neighbor is on the right edge and may not be complete.
      TreeHash h = SubtreeHash({last << level, subtree.end});
      proof.insert(proof.end(), h.begin(), h.end());
    }
    level++;
    start >>= 1;
    index >>= 1;
    last >>= 1;
  }
  return proof;
}

MerkleTreeInMemory::MerkleTreeInMemory() { levels_.emplace_back(); }

MerkleTreeInMemory::MerkleTreeInMemory(
    Span<const std::vector<uint8_t>> entries) {
  // Compute the leaf nodes.
  std::vector<TreeHash> level;
  level.resize(entries.size());
  for (size_t i = 0; i < entries.size(); i++) {
    HashLeaf(entries[i], level[i]);
  }
  levels_.push_back(std::move(level));

  UpdateLevels();
}

uint64_t MerkleTreeInMemory::Size() const { return levels_[0].size(); }

TreeHash MerkleTreeInMemory::GetNode(size_t level, uint64_t index) const {
  BSSL_CHECK(level < levels_.size());
  BSSL_CHECK(index < levels_[level].size());
  return levels_[level][index];
}

void MerkleTreeInMemory::Append(Span<const uint8_t> entry) {
  TreeHash h;
  HashLeaf(entry, h);
  levels_[0].push_back(h);
  UpdateLevels();
}

void MerkleTreeInMemory::UpdateLevels() {
  assert(!levels_.empty());
  for (size_t level = 1, n = Size() / 2; n != 0; level++, n /= 2) {
    // Ensure level `level` exists and has `n` elements.
    if (level == levels_.size()) {
      levels_.emplace_back();
    }
    levels_[level].reserve(n);
    while (levels_[level].size() < n) {
      size_t i = levels_[level].size();
      TreeHash h;
      HashNode(levels_[level - 1][2 * i], levels_[level - 1][2 * i + 1], h);
      levels_[level].push_back(h);
    }
  }
}

BSSL_NAMESPACE_END
