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