diff --git a/src/test/fuzz/CMakeLists.txt b/src/test/fuzz/CMakeLists.txt index 64fbe69dc00..00e501c60d9 100644 --- a/src/test/fuzz/CMakeLists.txt +++ b/src/test/fuzz/CMakeLists.txt @@ -135,6 +135,7 @@ add_executable(fuzz validation_load_mempool.cpp vecdeque.cpp versionbits.cpp + merkle.cpp ) target_link_libraries(fuzz core_interface diff --git a/src/test/fuzz/merkle.cpp b/src/test/fuzz/merkle.cpp new file mode 100644 index 00000000000..a77273649b7 --- /dev/null +++ b/src/test/fuzz/merkle.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include +#include +#include +#include +#include + +#include +#include +#include + +FUZZ_TARGET(merkle) +{ + FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); + + // Generate a random number of transactions (0-1000 to keep it reasonable) + const size_t num_txs = fuzzed_data_provider.ConsumeIntegralInRange(0, 1000); + std::vector tx_hashes; + tx_hashes.reserve(num_txs); + + // Create a CBlock with fuzzed transactions + CBlock block; + block.vtx.resize(num_txs); + + for (size_t i = 0; i < num_txs; ++i) { + CMutableTransaction mtx; + // Add minimal valid content to make it a transaction + mtx.version = fuzzed_data_provider.ConsumeIntegral(); + mtx.nLockTime = fuzzed_data_provider.ConsumeIntegral(); + + // Add at least one input with a properly constructed Txid + CTxIn txin; + std::vector prevout_hash_bytes = fuzzed_data_provider.ConsumeBytes(32); + if (prevout_hash_bytes.size() < 32) { + prevout_hash_bytes.resize(32, 0); // Pad with zeros if needed + } + uint256 prevout_hash; + memcpy(prevout_hash.begin(), prevout_hash_bytes.data(), 32); + txin.prevout = COutPoint(Txid::FromUint256(prevout_hash), fuzzed_data_provider.ConsumeIntegral()); + std::vector script_sig_bytes = fuzzed_data_provider.ConsumeBytes(MAX_SCRIPT_SIZE); + txin.scriptSig = CScript(script_sig_bytes.begin(), script_sig_bytes.end()); + txin.nSequence = fuzzed_data_provider.ConsumeIntegral(); + mtx.vin.push_back(txin); + + CTxOut txout; + txout.nValue = fuzzed_data_provider.ConsumeIntegral(); + std::vector script_pk_bytes = fuzzed_data_provider.ConsumeBytes(MAX_SCRIPT_SIZE); + txout.scriptPubKey = CScript(script_pk_bytes.begin(), script_pk_bytes.end()); + mtx.vout.push_back(txout); + + // Compute the hash and store it + block.vtx[i] = MakeTransactionRef(std::move(mtx)); + tx_hashes.push_back(block.vtx[i]->GetHash()); + } + + // Test ComputeMerkleRoot + bool mutated = fuzzed_data_provider.ConsumeBool(); + //bool mutated = false; + const uint256 merkle_root = ComputeMerkleRoot(tx_hashes, &mutated); + + // Basic sanity checks for ComputeMerkleRoot + if (tx_hashes.size() == 1) { + assert(merkle_root == tx_hashes[0]); + } + + + const uint256 block_merkle_root = BlockMerkleRoot(block, &mutated); + if (tx_hashes.size() == 1) { + assert(block_merkle_root == tx_hashes[0]); + } + + if (!block.vtx.empty()){ + const uint256 block_witness_merkle_root = BlockWitnessMerkleRoot(block, &mutated); + if (tx_hashes.size() == 1) { + assert(block_witness_merkle_root == uint256()); + } + } + + // Test TransactionMerklePath + const uint32_t position = fuzzed_data_provider.ConsumeIntegralInRange(0, num_txs); + std::vector merkle_path = TransactionMerklePath(block, position); + + // Basic sanity checks for TransactionMerklePath + assert(merkle_path.size() <= 32); // Maximum depth of a Merkle tree with 2^32 leaves + if (num_txs == 1 || num_txs == 0) { + assert(merkle_path.empty()); // Single transaction has no path + } +}