Skip to content

Commit 95969bc

Browse files
committed
test: added fuzz coverage to consensus/merkle.cpp
This adds a new fuzz target merkle which adds fuzz coverage to consensus/merkle.cpp
1 parent 65dcbec commit 95969bc

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

src/test/fuzz/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ add_executable(fuzz
5959
kitchen_sink.cpp
6060
load_external_block_file.cpp
6161
locale.cpp
62+
merkle.cpp
6263
merkleblock.cpp
6364
message.cpp
6465
miniscript.cpp

src/test/fuzz/merkle.cpp

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// Copyright (c) 2025 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <consensus/merkle.h>
6+
#include <test/fuzz/fuzz.h>
7+
#include <test/fuzz/FuzzedDataProvider.h>
8+
#include <test/fuzz/util.h>
9+
#include <test/util/str.h>
10+
#include <util/strencodings.h>
11+
#include <hash.h>
12+
13+
#include <cassert>
14+
#include <cstdint>
15+
#include <vector>
16+
17+
uint256 ComputeMerkleRootFromPath(const CBlock& block, uint32_t position, const std::vector<uint256>& merkle_path) {
18+
if (position >= block.vtx.size()) {
19+
throw std::out_of_range("Position out of range");
20+
}
21+
22+
uint256 current_hash = block.vtx[position]->GetHash();
23+
24+
for (const uint256& sibling : merkle_path) {
25+
if (position % 2 == 0) {
26+
current_hash = Hash(current_hash, sibling);
27+
} else {
28+
current_hash = Hash(sibling, current_hash);
29+
}
30+
position = position / 2;
31+
}
32+
33+
return current_hash;
34+
}
35+
36+
FUZZ_TARGET(merkle)
37+
{
38+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
39+
40+
const bool with_witness = fuzzed_data_provider.ConsumeBool();
41+
std::optional<CBlock> block {ConsumeDeserializable<CBlock>(fuzzed_data_provider, with_witness ? TX_WITH_WITNESS : TX_NO_WITNESS)};
42+
if (!block){
43+
return;
44+
}
45+
const size_t num_txs = block->vtx.size();
46+
std::vector<uint256> tx_hashes;
47+
tx_hashes.reserve(num_txs);
48+
49+
for (size_t i = 0; i < num_txs; ++i) {
50+
tx_hashes.push_back(block->vtx[i]->GetHash());
51+
}
52+
53+
// Test ComputeMerkleRoot
54+
bool mutated = fuzzed_data_provider.ConsumeBool();
55+
const uint256 merkle_root = ComputeMerkleRoot(tx_hashes, &mutated);
56+
57+
// Basic sanity checks for ComputeMerkleRoot
58+
if (tx_hashes.size() == 1) {
59+
assert(merkle_root == tx_hashes[0]);
60+
}
61+
62+
63+
const uint256 block_merkle_root = BlockMerkleRoot(*block, &mutated);
64+
if (tx_hashes.size() == 1) {
65+
assert(block_merkle_root == tx_hashes[0]);
66+
}
67+
68+
if (!block->vtx.empty()){
69+
const uint256 block_witness_merkle_root = BlockWitnessMerkleRoot(*block, &mutated);
70+
if (tx_hashes.size() == 1) {
71+
assert(block_witness_merkle_root == uint256());
72+
}
73+
}
74+
75+
// Test TransactionMerklePath
76+
const uint32_t position = fuzzed_data_provider.ConsumeIntegralInRange<uint32_t>(0, num_txs > 0 ? num_txs - 1 : 0);
77+
std::vector<uint256> merkle_path = TransactionMerklePath(*block, position);
78+
79+
// Check that the root we compute from TransactionMerklePath equals the same merkle_root and block_merkle_root
80+
if (tx_hashes.size() > 1) {
81+
uint256 merkle_root_from_merkle_path = ComputeMerkleRootFromPath(*block, position, merkle_path);
82+
assert(merkle_root_from_merkle_path == merkle_root);
83+
assert(merkle_root_from_merkle_path == block_merkle_root);
84+
}
85+
86+
// Basic sanity checks for TransactionMerklePath
87+
assert(merkle_path.size() <= 32); // Maximum depth of a Merkle tree with 2^32 leaves
88+
if (num_txs == 1 || num_txs == 0) {
89+
assert(merkle_path.empty()); // Single transaction has no path
90+
}
91+
92+
}

0 commit comments

Comments
 (0)