blob: 47386499877fdd91daf7d17c44b5517cd31c7242 [file] [edit]
// 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 <cstdint>
#include <limits>
#include <sstream>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <openssl/digest.h>
#include <openssl/sha2.h>
#include "../crypto/test/file_test.h"
#include "../crypto/test/test_util.h"
BSSL_NAMESPACE_BEGIN
namespace {
// Returns a subtree consistency proof for `subtree` in the first `n` elements
// of `tree`. This is currently implemented recursively, matching the
// specification. If we ever need to expose it, we can implement it more
// efficiently.
std::vector<uint8_t> SubtreeConsistencyProof(const MerkleTree &mt,
Subtree subtree,
const Subtree &tree,
bool known_hash = true) {
BSSL_CHECK(subtree.IsValid());
BSSL_CHECK(tree.IsValid());
BSSL_CHECK(tree.Contains(subtree));
if (subtree.Size() == 0) {
return {};
}
if (subtree == tree) {
if (known_hash) {
return {};
}
TreeHash h = mt.SubtreeHash(subtree);
return std::vector(h.begin(), h.end());
}
uint64_t k = tree.Split();
Subtree subproof_tree, mth_tree;
if (subtree.end <= k) {
subproof_tree = tree.Left();
mth_tree = tree.Right();
} else if (subtree.start >= k) {
mth_tree = tree.Left();
subproof_tree = tree.Right();
} else {
subtree.start = k;
mth_tree = tree.Left();
subproof_tree = tree.Right();
known_hash = false;
}
std::vector<uint8_t> subproof =
SubtreeConsistencyProof(mt, subtree, subproof_tree, known_hash);
TreeHash mth = mt.SubtreeHash(mth_tree);
subproof.insert(subproof.end(), mth.begin(), mth.end());
return subproof;
}
std::vector<std::vector<uint8_t>> MakeTestEntries(std::string_view label,
size_t n) {
std::vector<std::vector<uint8_t>> entries;
entries.reserve(n);
for (size_t i = 0; i < n; i++) {
std::vector<uint8_t> entry(label.begin(), label.end());
for (size_t j = 0; j < 8; j++) {
entry.push_back(static_cast<uint8_t>(i >> (j * 8)));
}
entries.push_back(std::move(entry));
}
return entries;
}
// Generates test entries compatible with the "accumulated" tests described
// in appendix C of draft-ietf-plants-merkle-tree-certs.
// This works for `n` up to 255.
std::vector<std::vector<uint8_t>> MakeAccumulatedTestEntries(size_t n) {
std::vector<std::vector<uint8_t>> entries;
entries.reserve(n);
for (size_t i = 0; i < n; i++) {
entries.push_back({static_cast<uint8_t>(i)});
}
return entries;
}
std::vector<uint8_t> ConcatProof(const std::vector<TreeHash> &proof) {
std::vector<uint8_t> out;
for (const auto &p : proof) {
out.insert(out.end(), p.begin(), p.end());
}
return out;
}
TEST(MerkleTreeTest, SubtreeIsValid) {
// An empty subtree is valid.
EXPECT_TRUE((Subtree{0, 0}.IsValid()));
// But if the end is before start, it's invalid.
EXPECT_FALSE((Subtree{1, 0}.IsValid()));
// A subtree of the maximum expressible size is valid.
EXPECT_TRUE((Subtree{0, std::numeric_limits<uint64_t>::max()}.IsValid()));
// Subtrees don't have to start at 0.
EXPECT_TRUE((Subtree{4, 8}.IsValid()));
// But if they don't start at 0, there's a limit to how big they can be.
EXPECT_FALSE((Subtree{4, 9}.IsValid()));
// Subtrees can have a ragged right edge.
EXPECT_TRUE((Subtree{4, 6}.IsValid()));
EXPECT_TRUE((Subtree{0, 6}.IsValid()));
}
TEST(MerkleTreeTest, SubtreeSplit) {
// Empty subtree.
EXPECT_EQ((Subtree{24601, 24601}).Split(), 24601ul);
// Single-item subtree.
EXPECT_EQ((Subtree{1336, 1337}).Split(), 1337ul);
// Two items in subtree.
EXPECT_EQ((Subtree{42, 44}).Split(), 43ul);
// Subtree size is 1 less than a power of 2.
EXPECT_EQ((Subtree{0, 31}).Split(), 16ul);
// Subtree size is a power of 2.
EXPECT_EQ((Subtree{64, 128}).Split(), 96ul);
/// Subtree size is 1 more than a power of 2.
EXPECT_EQ((Subtree{0, 257}).Split(), 256ul);
static const uint64_t u64_max = std::numeric_limits<uint64_t>::max();
// Maximum size tree.
EXPECT_EQ((Subtree{0, u64_max}).Split(), 1ull << 63);
// Small tree, with end at maximum value.
EXPECT_EQ((Subtree{u64_max - 3, u64_max}).Split(), u64_max - 1);
}
// This executes the "accumulated" Subtree Hashes test from appendix C.1 of
// draft-ietf-plants-merkle-tree-certs.
TEST(MerkleTreeTest, AccumulatedSubtreeHashes) {
auto entries = MakeAccumulatedTestEntries(256);
MerkleTreeInMemory tree(entries);
ScopedEVP_MD_CTX ctx;
EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr);
for (uint64_t end = 0; end < 131; ++end) {
for (uint64_t start = 0; start < end + 1; ++start) {
Subtree subtree{start, end};
if (!subtree.IsValid()) {
continue;
}
std::stringstream ss;
ss << "[" << std::to_string(start) << ", " << std::to_string(end) << ") "
<< EncodeHex(tree.SubtreeHash(subtree)) << "\n";
std::string str = ss.str();
EVP_DigestUpdate(ctx.get(), str.data(), str.size());
}
}
std::vector<uint8_t> final(EVP_MAX_MD_SIZE);
unsigned final_size;
EVP_DigestFinal_ex(ctx.get(), final.data(), &final_size);
final.resize(final_size);
const uint8_t kExpected[] = {
0xb8, 0x28, 0x06, 0xad, 0x42, 0x65, 0xbb, 0x15, 0x1c, 0x11, 0x19,
0xc0, 0xf4, 0xdb, 0x43, 0x7b, 0xb4, 0xd1, 0xa1, 0xf8, 0x87, 0xb3,
0xa7, 0xfb, 0xa1, 0xcd, 0x4e, 0xbf, 0x55, 0x2e, 0x3e, 0x81,
};
EXPECT_EQ(Bytes(final), Bytes(kExpected));
}
// This executes the "accumulated" Subtree Inclusion Proofs test from appendix
// C.2 of draft-ietf-plants-merkle-tree-certs.
TEST(MerkleTreeTest, AccumulatedSubtreeInclusionProofs) {
auto entries = MakeAccumulatedTestEntries(256);
MerkleTreeInMemory tree(entries);
ScopedEVP_MD_CTX ctx;
EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr);
for (uint64_t end = 0; end < 131; ++end) {
for (uint64_t start = 0; start < end + 1; ++start) {
Subtree subtree{start, end};
if (!subtree.IsValid()) {
continue;
}
for (uint64_t index = start; index < end; ++index) {
std::stringstream ss;
ss << std::to_string(index) << " [" << std::to_string(start) << ", "
<< std::to_string(end) << ")";
auto proof = tree.SubtreeInclusionProof(index, subtree);
for (size_t i = 0; i < proof.size(); i += SHA256_DIGEST_LENGTH) {
ss << " " << EncodeHex(Span(proof).subspan(i, SHA256_DIGEST_LENGTH));
}
ss << "\n";
std::string str = ss.str();
EVP_DigestUpdate(ctx.get(), str.data(), str.size());
}
}
}
std::vector<uint8_t> final(EVP_MAX_MD_SIZE);
unsigned final_size;
EVP_DigestFinal_ex(ctx.get(), final.data(), &final_size);
final.resize(final_size);
const uint8_t kExpected[] = {
0xac, 0x2a, 0x8f, 0x98, 0x9e, 0x44, 0xd9, 0x9e, 0x39, 0x9d, 0xb4,
0x48, 0x05, 0x0f, 0xf5, 0xf1, 0x97, 0x57, 0xdf, 0x53, 0xcf, 0xb7,
0x16, 0xaa, 0x81, 0x01, 0x5d, 0x39, 0x55, 0xd8, 0x16, 0x3f,
};
EXPECT_EQ(Bytes(final), Bytes(kExpected));
}
// This executes the "accumulated" Subtree Consistency Proofs test from appendix
// C.3 of draft-ietf-plants-merkle-tree-certs.
TEST(MerkleTreeTest, AccumulatedSubtreeConsistencyProofs) {
auto entries = MakeAccumulatedTestEntries(256);
MerkleTreeInMemory tree(entries);
ScopedEVP_MD_CTX ctx;
EVP_DigestInit_ex(ctx.get(), EVP_sha256(), nullptr);
for (size_t n = 0; n < 131; ++n) {
Subtree final_tree{0, n};
for (uint64_t end = 0; end < n + 1; ++end) {
for (uint64_t start = 0; start < end + 1; ++start) {
Subtree subtree{start, end};
if (!subtree.IsValid()) {
continue;
}
std::stringstream ss;
ss << "[" << std::to_string(start) << ", " << std::to_string(end)
<< ") " << std::to_string(n);
auto proof = SubtreeConsistencyProof(tree, subtree, final_tree);
for (size_t i = 0; i < proof.size(); i += SHA256_DIGEST_LENGTH) {
ss << " " << EncodeHex(Span(proof).subspan(i, SHA256_DIGEST_LENGTH));
}
ss << "\n";
std::string str = ss.str();
EVP_DigestUpdate(ctx.get(), str.data(), str.size());
}
}
}
std::vector<uint8_t> final(EVP_MAX_MD_SIZE);
unsigned final_size;
EVP_DigestFinal_ex(ctx.get(), final.data(), &final_size);
final.resize(final_size);
const uint8_t kExpected[] = {
0x10, 0xfa, 0x99, 0xb3, 0x7b, 0xf9, 0xbf, 0x9f, 0xfa, 0x26, 0xb4,
0x12, 0xfb, 0xd9, 0x8b, 0xd7, 0x53, 0x63, 0x25, 0x6d, 0x0b, 0x75,
0xd6, 0x1b, 0xc4, 0x53, 0x8b, 0x9c, 0x9c, 0x5a, 0x0a, 0x74,
};
EXPECT_EQ(Bytes(final), Bytes(kExpected));
}
TEST(MerkleTreeTest, VerifySubtreeInclusionProof) {
auto entries = MakeTestEntries("label", 847);
MerkleTreeInMemory tree(entries);
uint64_t index = 0;
auto node_hash = tree.SubtreeHash({index, index + 1});
Subtree subtree{0, 16};
auto proof = tree.SubtreeInclusionProof(index, subtree);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(
subtree.end, {index, index + 1}, proof, node_hash,
tree.SubtreeHash(subtree)));
// Check again with EvaluateMerkleSubtreeInclusionProof
auto root_hash =
EvaluateMerkleSubtreeInclusionProof(proof, index, node_hash, subtree);
ASSERT_TRUE(root_hash.has_value());
EXPECT_EQ(root_hash, tree.SubtreeHash(subtree));
// Build and verify a proof from a subtree with start != 0
index = 845;
node_hash = tree.SubtreeHash({index, index + 1});
subtree = {840, 847};
proof = tree.SubtreeInclusionProof(index, subtree);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(
subtree.Size(), {index - subtree.start, index - subtree.start + 1}, proof,
node_hash, tree.SubtreeHash(subtree)));
// Check again with EvaluateMerkleSubtreeInclusionProof
root_hash =
EvaluateMerkleSubtreeInclusionProof(proof, index, node_hash, subtree);
ASSERT_TRUE(root_hash.has_value());
EXPECT_EQ(root_hash, tree.SubtreeHash(subtree));
}
TEST(MerkleTreeTest, SubtreeInclusionProofInvalidArgs) {
auto entries = MakeTestEntries("label", 847);
MerkleTreeInMemory tree(entries);
uint64_t index = 845;
auto node_hash = tree.SubtreeHash({index, index + 1});
Subtree subtree = {840, 847};
auto proof = tree.SubtreeInclusionProof(index, subtree);
// If the wrong node hash is passed in, the function will still compute a
// root hash, but it won't match the expected value.
auto wrong_node_hash = tree.SubtreeHash({index + 1, index + 2});
auto root_hash = EvaluateMerkleSubtreeInclusionProof(
proof, index, wrong_node_hash, subtree);
ASSERT_TRUE(root_hash.has_value());
EXPECT_NE(root_hash, tree.SubtreeHash(subtree));
// If the subtree isn't valid, the function will fail.
ASSERT_FALSE(
EvaluateMerkleSubtreeInclusionProof(proof, index, node_hash, {840, 849}));
// If the index isn't contained within the subtree, the function will fail.
ASSERT_FALSE(
EvaluateMerkleSubtreeInclusionProof(proof, 848, node_hash, {840, 847}));
}
// Test that the computed consistency proofs match the examples given in RFC
// 9162 section 2.1.5.
TEST(MerkleTreeTest, SubtreeConsistencyProofRFC9162) {
auto entries = MakeTestEntries("label", 7);
MerkleTreeInMemory tree(entries);
// The example from section 2.1.5 has a final tree with 7 leaves.
Subtree final_tree{0, 7};
// The examples refer to letters representing the hashes of various subtrees
// within that tree. a and e aren't used in any of the examples.
auto b = tree.SubtreeHash({1, 2});
auto c = tree.SubtreeHash({2, 3});
auto d = tree.SubtreeHash({3, 4});
auto f = tree.SubtreeHash({5, 6});
auto g = tree.SubtreeHash({0, 2});
auto h = tree.SubtreeHash({2, 4});
auto i = tree.SubtreeHash({4, 6});
auto j = tree.SubtreeHash({6, 7});
auto k = tree.SubtreeHash({0, 4});
auto l = tree.SubtreeHash({4, 7});
// Inclusion proofs:
// Section 2.1.5: "The inclusion proof for `d0` is `[b, h, l]`."
auto d0_proof = tree.SubtreeInclusionProof(0, final_tree);
EXPECT_EQ(d0_proof, ConcatProof({b, h, l}));
// Section 2.1.5: "The inclusion proof for `d3` is `[c, g, l]`."
auto d3_proof = tree.SubtreeInclusionProof(3, final_tree);
EXPECT_EQ(d3_proof, ConcatProof({c, g, l}));
// Section 2.1.5: "The inclusion proof for `d4` is `[f, j, k]`."
auto d4_proof = tree.SubtreeInclusionProof(4, final_tree);
EXPECT_EQ(d4_proof, ConcatProof({f, j, k}));
// Section 2.1.5: "The inclusion proof for `d6` is `[i, k]`."
auto d6_proof = tree.SubtreeInclusionProof(6, final_tree);
EXPECT_EQ(d6_proof, ConcatProof({i, k}));
// Consistency proofs:
// The consistency proofs refer to the lettered hashes above, as well as some
// hashes of the tree as it was incrementally built.
Subtree hash0_subtree = {0, 3};
Subtree hash1_subtree = {0, 4};
auto hash1 = tree.SubtreeHash(hash1_subtree);
ASSERT_EQ(hash1, k);
Subtree hash2_subtree = {0, 6};
// "The consistency proof between hash0 and hash is [c, d, g, l]."
auto hash0_proof = SubtreeConsistencyProof(tree, hash0_subtree, final_tree);
EXPECT_EQ(hash0_proof, ConcatProof({c, d, g, l}));
// "The consistency proof between hash1 and hash is [l]."
auto hash1_proof = SubtreeConsistencyProof(tree, hash1_subtree, final_tree);
EXPECT_EQ(hash1_proof, ConcatProof({l}));
// "The consistency proof between hash2 and hash is [i, j, k]."
auto hash2_proof = SubtreeConsistencyProof(tree, hash2_subtree, final_tree);
EXPECT_EQ(hash2_proof, ConcatProof({i, j, k}));
}
TEST(MerkleTreeTest, ValidProofsTest) {
uint64_t n = 4, start = 0, end = 3;
Subtree full_tree{0, n};
auto entries = MakeTestEntries("label", n);
MerkleTreeInMemory tree(entries);
auto tree_hash = tree.SubtreeHash(full_tree);
Subtree subtree{start, end};
ASSERT_TRUE(subtree.IsValid());
auto subtree_hash = tree.SubtreeHash(subtree);
auto proof = SubtreeConsistencyProof(tree, subtree, full_tree);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(n, subtree, proof,
subtree_hash, tree_hash));
}
TEST(MerkleTreeTest, ValidProofs) {
// As of the time of writing this test, a run was performed with limit=257 and
// the test passed. This value is set to 129 to balance how much of the space
// to explore with test execution time. In particular, in an unoptimized
// build, limit=257 takes about 4 seconds.
for (bool incremental : {false, true}) {
SCOPED_TRACE(incremental);
uint64_t limit = 129;
auto entries = MakeTestEntries("label", limit);
MerkleTreeInMemory tree;
if (incremental) {
for (const auto &entry : entries) {
tree.Append(entry);
}
} else {
tree = MerkleTreeInMemory(entries);
}
// Exhaustively test subtree consistency proofs.
for (uint64_t n = 0; n < limit; n++) {
Subtree full_tree{0, n};
auto tree_hash = tree.SubtreeHash(full_tree);
for (uint64_t end = 0; end <= n; end++) {
for (uint64_t start = 0; start < end; start++) {
Subtree subtree{start, end};
if (!subtree.IsValid()) {
continue;
}
SCOPED_TRACE(testing::Message() << "Tree n=" << n << ", start: "
<< start << ", end: " << end);
auto subtree_hash = tree.SubtreeHash(subtree);
auto proof = SubtreeConsistencyProof(tree, subtree, full_tree);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(
n, subtree, proof, subtree_hash, tree_hash));
}
}
}
// Exhaustively test subtree inclusion proofs.
for (uint64_t end = 0; end <= limit; end++) {
for (uint64_t start = 0; start < end; start++) {
Subtree subtree{start, end};
if (!subtree.IsValid()) {
continue;
}
for (uint64_t index = start; index < end; index++) {
SCOPED_TRACE(testing::Message() << "index: " << index << ", start: "
<< start << ", end: " << end);
auto subtree_hash = tree.SubtreeHash(subtree);
auto entry_hash = tree.SubtreeHash({index, index + 1});
auto proof = tree.SubtreeInclusionProof(index, subtree);
auto computed_hash = EvaluateMerkleSubtreeInclusionProof(
proof, index, entry_hash, subtree);
EXPECT_EQ(computed_hash, subtree_hash);
// A subtree inclusion proof is a special case of a consistency proof.
auto proof2 =
SubtreeConsistencyProof(tree, {index, index + 1}, subtree);
EXPECT_EQ(proof, proof2);
}
}
}
}
}
class MerkleTreeLarge : public MerkleTree {
public:
// Constructs a Merkle Tree over 2^64 - 1 copies of `entry`.
explicit MerkleTreeLarge(Span<const uint8_t> entry) {
HashLeaf(entry, levels_[0]);
for (size_t i = 1; i < levels_.size(); i++) {
HashNode(levels_[i - 1], levels_[i - 1], levels_[i]);
}
}
uint64_t Size() const override {
return std::numeric_limits<uint64_t>::max();
}
TreeHash GetNode(size_t level, uint64_t index) const override {
BSSL_CHECK(level < 64);
return levels_[level];
}
private:
std::array<TreeHash, 64> levels_;
};
TEST(MerkleTreeTest, VeryLargeProofs) {
MerkleTreeLarge tree(StringAsBytes("entry"));
Subtree fullest_tree = {0, std::numeric_limits<uint64_t>::max()};
auto root_hash = tree.SubtreeHash(fullest_tree);
Subtree test_subtrees[] = {
fullest_tree,
{0, 1},
{0, 1ull << 63},
{1ull << 63, std::numeric_limits<uint64_t>::max()},
{std::numeric_limits<uint64_t>::max() - 1,
std::numeric_limits<uint64_t>::max()},
};
for (auto subtree : test_subtrees) {
SCOPED_TRACE(testing::Message() << "Subtree start: " << subtree.start
<< ", end: " << subtree.end);
auto proof = SubtreeConsistencyProof(tree, subtree, fullest_tree);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(
fullest_tree.end, subtree, proof, tree.SubtreeHash(subtree),
root_hash));
}
}
void InclusionProofFileTest(FileTest *t) {
uint64_t index, start, end;
ASSERT_TRUE(t->GetUint64(&index, "Index"));
ASSERT_TRUE(t->GetUint64(&start, "Start"));
ASSERT_TRUE(t->GetUint64(&end, "End"));
std::vector<uint8_t> entry_hash, subtree_hash, proof;
ASSERT_TRUE(t->GetBase64(&entry_hash, "EntryHash"));
ASSERT_TRUE(t->GetBase64(&subtree_hash, "SubtreeHash"));
ASSERT_TRUE(t->GetBase64(&proof, "Proof"));
const Subtree subtree{start, end};
TreeHashConstSpan entry_hash_span(entry_hash);
TreeHashConstSpan subtree_hash_span(subtree_hash);
auto computed_hash = EvaluateMerkleSubtreeInclusionProof(
proof, index, entry_hash_span, subtree);
EXPECT_TRUE(computed_hash.has_value());
EXPECT_EQ(*computed_hash, subtree_hash_span);
// Truncated inclusion proofs don't work.
const size_t original_proof_size = proof.size();
EXPECT_FALSE(EvaluateMerkleSubtreeInclusionProof(
Span(proof).subspan(original_proof_size - 1), index, entry_hash_span,
subtree));
EXPECT_FALSE(EvaluateMerkleSubtreeInclusionProof(
Span(proof).subspan(original_proof_size - EVP_MD_size(EVP_sha256())),
index, entry_hash_span, subtree));
// Extended inclusion proofs don't work.
proof.resize(original_proof_size + EVP_MD_size(EVP_sha256()));
EXPECT_FALSE(EvaluateMerkleSubtreeInclusionProof(
Span(proof).subspan(original_proof_size + 1), index, entry_hash_span,
subtree));
EXPECT_FALSE(EvaluateMerkleSubtreeInclusionProof(proof, index,
entry_hash_span, subtree));
// Bitflipped inclusion proof should produce a wrong subtree hash.
proof.resize(original_proof_size);
proof[0] ^= 1;
computed_hash = EvaluateMerkleSubtreeInclusionProof(proof, index,
entry_hash_span, subtree);
EXPECT_TRUE(computed_hash.has_value());
EXPECT_NE(*computed_hash, subtree_hash_span);
}
TEST(MerkleTreeTest, LargeInclusionProofs) {
FileTestGTest(
"crypto/x509/test/mtc/large_merkle_tree_inclusion_proof_tests.txt",
InclusionProofFileTest);
}
void ConsistencyProofFileTest(FileTest *t) {
uint64_t start, end, tree_size;
ASSERT_TRUE(t->GetUint64(&start, "Start"));
ASSERT_TRUE(t->GetUint64(&end, "End"));
ASSERT_TRUE(t->GetUint64(&tree_size, "TreeSize"));
std::vector<uint8_t> subtree_hash, tree_hash, proof;
ASSERT_TRUE(t->GetBase64(&subtree_hash, "SubtreeHash"));
ASSERT_TRUE(t->GetBase64(&tree_hash, "TreeHash"));
if (!t->HasAttribute("ProofEmpty")) {
ASSERT_TRUE(t->GetBase64(&proof, "Proof"));
}
const Subtree subtree{start, end};
TreeHashConstSpan subtree_hash_span(subtree_hash);
TreeHashConstSpan tree_hash_span(tree_hash);
EXPECT_TRUE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree, proof, subtree_hash_span, tree_hash_span));
// Truncated consistency proofs don't work.
const size_t original_proof_size = proof.size();
if (original_proof_size > 0) {
EXPECT_FALSE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree, Span(proof).subspan(original_proof_size - 1),
subtree_hash_span, tree_hash_span));
EXPECT_FALSE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree,
Span(proof).subspan(original_proof_size - EVP_MD_size(EVP_sha256())),
subtree_hash_span, tree_hash_span));
}
// Extended consistency proofs don't work.
proof.resize(original_proof_size + EVP_MD_size(EVP_sha256()));
EXPECT_FALSE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree, Span(proof).subspan(original_proof_size + 1),
subtree_hash_span, tree_hash_span));
EXPECT_FALSE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree, proof, subtree_hash_span, tree_hash_span));
// Bitflipped input subtree hash should either fail verification.
proof.resize(original_proof_size);
subtree_hash[0] ^= 1;
EXPECT_FALSE(VerifyMerkleSubtreeConsistencyProof(
tree_size, subtree, proof, subtree_hash_span, tree_hash_span));
}
TEST(MerkleTreeTest, LargeConsistencyProofs) {
FileTestGTest(
"crypto/x509/test/mtc/large_merkle_tree_consistency_proof_tests.txt",
ConsistencyProofFileTest);
}
} // namespace
BSSL_NAMESPACE_END