1+ // SPDX-License-Identifier: AGPL-3.0-or-later
2+ // BTC (#744 reconstructor slice 2/7) — SSOT non-witness coinbase (gentx) assembler KAT.
3+ //
4+ // Locks btc::coin::assemble_gentx_coinbase() (coin/gentx_coinbase.hpp) — the
5+ // single coinbase wire-layout extracted from generate_share_transaction()
6+ // (share_check.hpp:965, the verification SSOT) — against GROUND-TRUTH vectors
7+ // derived from the canonical oracle frstrtr/p2pool-dgb-scrypt
8+ // (util/pack.py byte logic + bitcoin/data.py tx_id_type; donation script
9+ // 4104ffd0...ac). The vectors are NOT self-generated from this builder: they
10+ // are the oracle serializer's output, so a PASS proves emission==oracle, not
11+ // merely self-consistency.
12+ //
13+ // Asserts, for both the no-segwit and witness-commitment-first layouts:
14+ // build -> non-witness serialize == oracle BYTES
15+ // double-SHA256(bytes) (GetHex display) == oracle TXID
16+ // Per-coin isolation: the only BTC<->DGB divergence is the segwit predicate at
17+ // serialize time (witness vout present-or-absent), exercised by both cases.
18+
19+ #include < gtest/gtest.h>
20+ #include < impl/btc/coin/gentx_coinbase.hpp>
21+
22+ #include < optional>
23+ #include < string>
24+ #include < utility>
25+ #include < vector>
26+
27+ namespace {
28+
29+ std::vector<unsigned char > unhex (const std::string& h) {
30+ std::vector<unsigned char > v; v.reserve (h.size () / 2 );
31+ auto nyb = [](char c) -> int { return (c <= ' 9' ) ? c - ' 0' : (c | 0x20 ) - ' a' + 10 ; };
32+ for (size_t i = 0 ; i + 1 < h.size (); i += 2 )
33+ v.push_back (static_cast <unsigned char >((nyb (h[i]) << 4 ) | nyb (h[i + 1 ])));
34+ return v;
35+ }
36+ std::string tohex (const std::vector<unsigned char >& v) {
37+ static const char * H = " 0123456789abcdef" ;
38+ std::string s; s.reserve (v.size () * 2 );
39+ for (unsigned char b : v) { s.push_back (H[b >> 4 ]); s.push_back (H[b & 0xf ]); }
40+ return s;
41+ }
42+
43+ // --- fixed inputs shared verbatim with the oracle generator ----------------
44+ const std::vector<unsigned char > CB = unhex(" 03a1b2c3041122334455667788" );
45+ const std::vector<unsigned char > P1 = unhex(std::string(" 76a914" ) + std::string(40 , ' 1' ) + " 88ac" );
46+ const std::vector<unsigned char > P2 = unhex(std::string(" 76a914" ) + std::string(40 , ' 2' ) + " 88ac" );
47+ const std::vector<unsigned char > DON = unhex(" 4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac" );
48+
49+ const std::vector<std::pair<std::vector<unsigned char >, uint64_t >> PAYOUTS = {
50+ {P1 , 5000000000ull },
51+ {P2 , 2500000000ull },
52+ };
53+
54+ // Ground truth from the oracle serializer (see file header).
55+ const std::string NOSEG_BYTES =
56+ " 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff0400f2052a010000001976a914111111111111111111111111111111111111111188ac00f90295000000001976a914222222222222222222222222222222222222222288ac0100000000000000434104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac00000000000000002a6a28abababababababababababababababababababababababababababababababab080706050403020100000000" ;
57+ const std::string NOSEG_TXID =
58+ " c5734775c1521b216e0e1bca506e4d15755cf55125caf56b7e0728a6d54a9b59" ;
59+ const std::string SEG_BYTES =
60+ " 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff050000000000000000266a24aa21a9edcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd00f2052a010000001976a914111111111111111111111111111111111111111188ac00f90295000000001976a914222222222222222222222222222222222222222288ac0100000000000000434104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac00000000000000002a6a28abababababababababababababababababababababababababababababababab080706050403020100000000" ;
61+ const std::string SEG_TXID =
62+ " 4b66aabff52ca1336b52688815cece123075856880aaf6e77cb44f0d477b6162" ;
63+
64+ } // namespace
65+
66+ // op_return / wc literals: build them explicitly to avoid std::string surprises.
67+ static std::vector<unsigned char > make_opret () {
68+ auto v = unhex (" 6a28" );
69+ for (int i = 0 ; i < 32 ; ++i) v.push_back (0xab );
70+ const unsigned char nonce[8 ] = {0x08 , 0x07 , 0x06 , 0x05 , 0x04 , 0x03 , 0x02 , 0x01 };
71+ for (unsigned char b : nonce) v.push_back (b);
72+ return v;
73+ }
74+ static std::vector<unsigned char > make_wc () {
75+ auto v = unhex (" 6a24aa21a9ed" );
76+ for (int i = 0 ; i < 32 ; ++i) v.push_back (0xcd );
77+ return v;
78+ }
79+
80+ TEST (BTC_gentx_coinbase, NoSegwitOracleVector) {
81+ auto opret = make_opret ();
82+ auto g = btc::coin::assemble_gentx_coinbase (
83+ CB , std::nullopt , PAYOUTS , /* donation_amount=*/ 1 , DON , opret);
84+ EXPECT_EQ (tohex (g.bytes ), NOSEG_BYTES );
85+ EXPECT_EQ (g.txid .GetHex (), NOSEG_TXID );
86+ }
87+
88+ TEST (BTC_gentx_coinbase, SegwitCommitmentFirstOracleVector) {
89+ auto opret = make_opret ();
90+ auto wc = make_wc ();
91+ auto g = btc::coin::assemble_gentx_coinbase (
92+ CB , std::optional<std::vector<unsigned char >>(wc), PAYOUTS ,
93+ /* donation_amount=*/ 1 , DON , opret);
94+ EXPECT_EQ (tohex (g.bytes ), SEG_BYTES );
95+ EXPECT_EQ (g.txid .GetHex (), SEG_TXID );
96+ }
97+
98+ // Guard the per-coin isolation invariant: toggling only the segwit predicate
99+ // changes vout-count + prepends exactly one 0-value commitment vout, nothing
100+ // else — the divergence is gated, not a forked framer.
101+ TEST (BTC_gentx_coinbase, SegwitPredicateIsTheOnlyDivergence) {
102+ EXPECT_NE (NOSEG_BYTES , SEG_BYTES );
103+ // both share the identical coinbase-script vin prefix and donation/op_return tail
104+ EXPECT_EQ (NOSEG_BYTES .substr (0 , 90 ), SEG_BYTES .substr (0 , 90 ));
105+ }
0 commit comments