Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/impl/btc/coin/template_builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ inline std::vector<uint256> stratum_merkle_siblings(const std::vector<uint256>&
return siblings;
}

/// Compute the BIP141 segwit witness-commitment merkle root.
///
/// The coinbase transaction's own wtxid is DEFINED as 32 zero bytes and MUST
/// occupy leaf 0 of the witness merkle tree; the remaining leaves are the
/// other transactions' wtxids IN BLOCK ORDER. This helper is the single source
/// of truth for that leaf-0 placeholder contract -- the witness-tree analogue
/// of the txid-merkle coinbase leaf-0 fix (PR #570). Keeping it in one place
/// means a populated segwit block cannot silently drop the first tx's wtxid
/// (which bitcoind rejects as bad-witness-merkle-match).
///
/// other_wtxids: wtxids of every tx EXCEPT the coinbase, in block order.
inline uint256 witness_merkle_root(const std::vector<uint256>& other_wtxids) {
std::vector<uint256> leaves;
leaves.reserve(1 + other_wtxids.size());
leaves.push_back(uint256::ZERO); // coinbase wtxid placeholder (BIP141)
leaves.insert(leaves.end(), other_wtxids.begin(), other_wtxids.end());
return compute_merkle_root(std::move(leaves));
}

// ─── CoinNodeInterface ────────────────────────────────────────────────────────

/// Abstract interface for obtaining work and submitting blocks.
Expand Down
25 changes: 8 additions & 17 deletions src/impl/btc/stratum/work_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -525,33 +525,24 @@ core::stratum::CoinbaseResult BTCWorkSource::build_connection_coinbase(
std::vector<uint8_t> witness_commitment_script; // empty if segwit not active
uint256 witness_root_uint;
if (segwit_active) {
std::vector<uint256> wtxids;
wtxids.reserve(1 + (wd->m_txs.size()));
wtxids.push_back(uint256::ZERO); // coinbase wtxid placeholder
// Collect the OTHER txs' wtxids (template "hash" field); the coinbase
// wtxid placeholder (BIP141 = 32 zero bytes) at leaf 0 is prepended by
// the shared witness_merkle_root() SSOT helper, so the leaf-0 contract
// lives in ONE place (mirrors the txid-merkle leaf-0 fix, PR #570).
std::vector<uint256> other_wtxids;
other_wtxids.reserve(wd->m_txs.size());
if (auto txs_field = wd->m_data.find("transactions");
txs_field != wd->m_data.end() && txs_field->is_array())
{
for (const auto& t : *txs_field) {
if (!t.is_object()) continue;
if (auto h = t.find("hash"); h != t.end() && h->is_string()) {
uint256 wt; wt.SetHex(h->get<std::string>().c_str());
wtxids.push_back(wt);
other_wtxids.push_back(wt);
}
}
}
// Bitcoin Core merkle: pad odd levels by duplicating last.
std::vector<uint256> level = std::move(wtxids);
while (level.size() > 1) {
std::vector<uint256> next;
next.reserve((level.size() + 1) / 2);
for (size_t i = 0; i < level.size(); i += 2) {
const uint256& l = level[i];
const uint256& r = (i + 1 < level.size()) ? level[i + 1] : level[i];
next.push_back(btc::coin::merkle_hash_pair(l, r));
}
level = std::move(next);
}
witness_root_uint = level.empty() ? uint256::ZERO : level[0];
witness_root_uint = btc::coin::witness_merkle_root(other_wtxids);

// commitment_hash = SHA256d(witness_root || witness_reserved_value)
std::array<uint8_t, 64> commit_in;
Expand Down
2 changes: 1 addition & 1 deletion src/impl/btc/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
if (BUILD_TESTING AND GTest_FOUND)
# btc twin of ltc share_test — uniquely named to avoid the CMP0002 target
# collision with src/impl/ltc/test (both subdirs build in the same tree).
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp)
add_executable(btc_share_test share_test.cpp f11_donation_invariance_test.cpp f10b_version_punish_removal_test.cpp g01_share_format_parity_test.cpp auto_ratchet_sim_test.cpp block_broadcast_guard_test.cpp block_broadcast_connect_test.cpp regtest_sharechain_isolation_test.cpp stratum_merkle_branch_test.cpp witness_commitment_merkle_test.cpp)
target_link_libraries(btc_share_test PRIVATE
GTest::gtest_main GTest::gtest
core btc
Expand Down
66 changes: 66 additions & 0 deletions src/impl/btc/test/witness_commitment_merkle_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// KAT: BIP141 witness-commitment merkle-root leaf-0 contract.
//
// The coinbase transaction's wtxid is DEFINED as 32 zero bytes and MUST occupy
// leaf 0 of the witness merkle tree; the block's other tx wtxids follow IN
// ORDER. A populated segwit block that drops the first tx's wtxid (or omits the
// coinbase placeholder) computes the wrong commitment and bitcoind rejects it
// `bad-witness-merkle-match` -- the witness-tree analogue of the stratum
// txid-merkle leaf-0 bug fixed in PR #570. This pins the contract via the
// shared btc::coin::witness_merkle_root() SSOT helper that work_source.cpp uses.

#include <gtest/gtest.h>

#include <cstring>
#include <vector>

#include <impl/btc/coin/template_builder.hpp>

using btc::coin::compute_merkle_root;
using btc::coin::witness_merkle_root;

namespace {

// A wtxid filled with the given byte (distinct, deterministic test vectors).
uint256 wt(uint8_t b) {
uint256 h;
std::memset(h.data(), b, 32);
return h;
}

} // namespace

// Coinbase-only block: the only leaf is the coinbase ZERO placeholder, so the
// witness root is that single leaf (compute_merkle_root of one element).
TEST(BTC_witness_commitment, CoinbaseOnlyRootIsPlaceholder) {
EXPECT_EQ(witness_merkle_root({}).GetHex(), uint256::ZERO.GetHex());
}

// Leaf 0 is the coinbase ZERO placeholder, NOT the first real tx: the root must
// equal the merkle over [ZERO, a, b] (3 leaves -> odd level duplicates last).
TEST(BTC_witness_commitment, Leaf0IsCoinbasePlaceholder) {
uint256 a = wt(0x11), b = wt(0x22);
std::vector<uint256> expect_leaves = {uint256::ZERO, a, b};
EXPECT_EQ(witness_merkle_root({a, b}).GetHex(),
compute_merkle_root(expect_leaves).GetHex());
}

// flip-RED guard: dropping the FIRST real tx's wtxid changes the root, and a
// tree built WITHOUT the coinbase placeholder also diverges. Either regression
// (the #570 bug class on the witness tree) would flip these to RED.
TEST(BTC_witness_commitment, DropFirstTxOrMissingPlaceholderDiverges) {
uint256 a = wt(0x11), b = wt(0x22);
const std::string correct = witness_merkle_root({a, b}).GetHex();

// Bug 1: first real tx (a) silently lost.
EXPECT_NE(correct, witness_merkle_root({b}).GetHex());

// Bug 2: coinbase leaf-0 placeholder omitted entirely.
EXPECT_NE(correct, compute_merkle_root({a, b}).GetHex());
}

// Order matters: swapping tx order changes the witness root (no accidental sort).
TEST(BTC_witness_commitment, OrderSensitive) {
uint256 a = wt(0x11), b = wt(0x22);
EXPECT_NE(witness_merkle_root({a, b}).GetHex(),
witness_merkle_root({b, a}).GetHex());
}
Loading