| // 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 |