diff --git a/src/impl/dgb/coin/connection_coinbase.hpp b/src/impl/dgb/coin/connection_coinbase.hpp index 481a097d8..e6bb89666 100644 --- a/src/impl/dgb/coin/connection_coinbase.hpp +++ b/src/impl/dgb/coin/connection_coinbase.hpp @@ -133,6 +133,17 @@ struct ConnCoinbasePplnsInputs std::vector donation_script; uint256 ref_hash; // p2pool ref_hash (32B) uint64_t last_txout_nonce{0}; // OP_RETURN nonce (extranonce slot) + + // DGB-as-DOGE-parent merged-mining commitment (-DAUX_DOGE=ON path only). + // The pre-built 44-byte AuxPoW MM tag (magic fabe6d6d || aux_merkle_root32 BE + // || size4 LE || nonce4 LE) produced by dgb::coin::build_aux_mm_commitment + // (the #475 SSOT). When set, it is appended to the coinbase scriptSig at + // mint so a won DGB block carries the DOGE merged-mining commitment the aux + // verifier decodes. DEFAULT nullopt -> coinbase_script is emitted unchanged, + // so the assembled coinbase is BYTE-IDENTICAL to the standalone-parent build. + // The producer (main_dgb seam) only ever populates this under #ifdef AUX_DOGE; + // sourcing a LIVE aux merkle root is the gated DC routing slice, not this one. + std::optional> aux_mm_commitment; }; inline ConnCoinbaseParts build_connection_coinbase_from_pplns(const ConnCoinbasePplnsInputs& in) @@ -142,6 +153,13 @@ inline ConnCoinbaseParts build_connection_coinbase_from_pplns(const ConnCoinbase ConnCoinbaseInputs ci; ci.coinbase_script = in.coinbase_script; + // Embed-at-mint: append the DGB-as-DOGE-parent MM commitment to the + // coinbase scriptSig when present. nullopt (default / standalone parent) + // leaves coinbase_script byte-identical -- the no-op is structural. + if (in.aux_mm_commitment) + ci.coinbase_script.insert(ci.coinbase_script.end(), + in.aux_mm_commitment->begin(), + in.aux_mm_commitment->end()); ci.segwit_commitment_script = in.segwit_commitment_script; ci.payout_outputs = std::move(split.payout_outputs); ci.donation_amount = split.donation_amount; diff --git a/src/impl/dgb/test/connection_coinbase_test.cpp b/src/impl/dgb/test/connection_coinbase_test.cpp index 030116ad5..506b01de2 100644 --- a/src/impl/dgb/test/connection_coinbase_test.cpp +++ b/src/impl/dgb/test/connection_coinbase_test.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -207,6 +208,75 @@ TEST(ConnCoinbasePplns, ValueAnchorAscendingPayoutOrder) { } +// ============================================================================ +// DGB+DOGE phase DB -- embed-at-mint: the optional aux_mm_commitment field on +// ConnCoinbasePplnsInputs appends the #475 build_aux_mm_commitment blob to the +// coinbase scriptSig at mint (the -DAUX_DOGE=ON DGB-as-DOGE-parent path). +// Two invariants, both proven non-circularly off the same fixed inputs: +// (E1) DEFAULT (nullopt) is BYTE-IDENTICAL to the standalone-parent coinbase +// -- the no-op is structural, so the standalone build cannot regress. +// (E2) When set, the assembled scriptSig is the baseline scriptSig with the +// 44-byte MM tag appended verbatim (magic..root..size..nonce), and the +// gentx grows by EXACTLY 44 bytes -- nothing else in the coinbase moves. +// ============================================================================ + +// (E1) nullopt leaves the gentx byte-identical to the no-aux build. +TEST(ConnCoinbaseEmbedAtMint, NulloptIsByteIdentical) { + std::map w{{P1, uint288(3)}, {P2, uint288(1)}}; + uint256 ref(std::vector(32, 0xab)); + // baseline: aux_mm_commitment defaults to nullopt inside wired_from_pplns. + auto baseline = wired_from_pplns(w, uint288(4), 10000, true, {}, std::nullopt, ref, NONCE); + + dgb::coin::ConnCoinbasePplnsInputs in; + in.coinbase_script = CB; + in.segwit_commitment_script = std::nullopt; + in.weights = w; in.total_weight = uint288(4); in.subsidy = 10000; + in.use_v36_pplns = true; in.donation_script = DON; + in.ref_hash = ref; in.last_txout_nonce = NONCE; + in.aux_mm_commitment = std::nullopt; // explicit default + auto with_field = dgb::coin::build_connection_coinbase_from_pplns(in); + + EXPECT_EQ(tohex(baseline.gentx.bytes), tohex(with_field.gentx.bytes)); + EXPECT_EQ(baseline.gentx.txid, with_field.gentx.txid); +} + +// (E2) a populated commitment appends the 44-byte MM tag to the scriptSig and +// grows the gentx by exactly 44 bytes; the tag bytes match the #475 SSOT. +TEST(ConnCoinbaseEmbedAtMint, AppendsMmTagToScriptSig) { + std::map w{{P1, uint288(3)}, {P2, uint288(1)}}; + uint256 ref(std::vector(32, 0xab)); + auto baseline = wired_from_pplns(w, uint288(4), 10000, true, {}, std::nullopt, ref, NONCE); + + // aux merkle root = 0x11..0x11 (internal LE); build the canonical 44-byte tag. + uint256 aux_root(std::vector(32, 0x11)); + auto tag = dgb::coin::build_aux_mm_commitment(aux_root, /*size=*/1, /*nonce=*/0); + ASSERT_EQ(tag.size(), dgb::coin::AUX_MM_COMMITMENT_SIZE); + + dgb::coin::ConnCoinbasePplnsInputs in; + in.coinbase_script = CB; + in.segwit_commitment_script = std::nullopt; + in.weights = w; in.total_weight = uint288(4); in.subsidy = 10000; + in.use_v36_pplns = true; in.donation_script = DON; + in.ref_hash = ref; in.last_txout_nonce = NONCE; + in.aux_mm_commitment = tag; + auto embedded = dgb::coin::build_connection_coinbase_from_pplns(in); + + // gentx grows by EXACTLY the 44-byte tag -- nothing else shifts. + EXPECT_EQ(embedded.gentx.bytes.size(), baseline.gentx.bytes.size() + tag.size()); + + // the embedded scriptSig is the baseline scriptSig (CB) followed by the tag. + std::vector expect_script = CB; + expect_script.insert(expect_script.end(), tag.begin(), tag.end()); + // the scriptSig sits right after the 42-byte coinbase txin prologue + // (version4 + vin_count1 + prevout36 + script_len1); locate it by content. + const std::string g = tohex(embedded.gentx.bytes); + EXPECT_NE(g.find(tohex(expect_script)), std::string::npos); + // and the bare tag is present where the baseline had none. + EXPECT_NE(g.find(tohex(tag)), std::string::npos); + EXPECT_EQ(tohex(baseline.gentx.bytes).find(tohex(tag)), std::string::npos); +} + + // ============================================================================ // last_txout_nonce SSOT (coin/last_txout_nonce.hpp) -- oracle-faithful uniform // 64-bit draw. Replaced three drifted std::time()-XOR formulas in