|
| 1 | +// DGB Phase B — per-connection Stratum coinbase assembler + split KAT. |
| 2 | +// |
| 3 | +// Locks dgb::coin::build_connection_coinbase_parts() (coin/connection_coinbase.hpp) |
| 4 | +// against the SAME ground-truth oracle vector used by the gentx assembler KAT |
| 5 | +// (gentx_coinbase_test.cpp NOSEG_BYTES — derived from frstrtr/p2pool-dgb-scrypt |
| 6 | +// util/pack.py + donation 4104ffd0...ac). A PASS proves: |
| 7 | +// |
| 8 | +// 1. the per-connection coinbase serializes byte-identical to the oracle wire |
| 9 | +// (the assembler path used == the verification SSOT), AND |
| 10 | +// 2. the Stratum split invariant holds: |
| 11 | +// coinb1 || extranonce1(4) || extranonce2(4) || coinb2 == full gentx bytes |
| 12 | +// with the 8-byte extranonce slot landing exactly on the OP_RETURN |
| 13 | +// last_txout_nonce (the submit path reconstructs the header off this). |
| 14 | +// |
| 15 | +// Pure / no tracker: the (ref_hash, last_txout_nonce) are fixed inputs, so the |
| 16 | +// vector is the oracle serializer's output — not self-generated. |
| 17 | + |
| 18 | +#include <gtest/gtest.h> |
| 19 | +#include <impl/dgb/coin/connection_coinbase.hpp> |
| 20 | + |
| 21 | +#include <optional> |
| 22 | +#include <string> |
| 23 | +#include <utility> |
| 24 | +#include <vector> |
| 25 | + |
| 26 | +namespace { |
| 27 | + |
| 28 | +std::vector<unsigned char> unhex(const std::string& h) { |
| 29 | + std::vector<unsigned char> v; v.reserve(h.size() / 2); |
| 30 | + auto nyb = [](char c) -> int { return (c <= '9') ? c - '0' : (c | 0x20) - 'a' + 10; }; |
| 31 | + for (size_t i = 0; i + 1 < h.size(); i += 2) |
| 32 | + v.push_back(static_cast<unsigned char>((nyb(h[i]) << 4) | nyb(h[i + 1]))); |
| 33 | + return v; |
| 34 | +} |
| 35 | +std::string tohex(const std::vector<unsigned char>& v) { |
| 36 | + static const char* H = "0123456789abcdef"; |
| 37 | + std::string s; s.reserve(v.size() * 2); |
| 38 | + for (unsigned char b : v) { s.push_back(H[b >> 4]); s.push_back(H[b & 0xf]); } |
| 39 | + return s; |
| 40 | +} |
| 41 | + |
| 42 | +// --- inputs shared verbatim with gentx_coinbase_test.cpp NOSEG vector -------- |
| 43 | +const std::vector<unsigned char> CB = unhex("03a1b2c3041122334455667788"); |
| 44 | +const std::vector<unsigned char> P1 = unhex(std::string("76a914") + std::string(40, '1') + "88ac"); |
| 45 | +const std::vector<unsigned char> P2 = unhex(std::string("76a914") + std::string(40, '2') + "88ac"); |
| 46 | +const std::vector<unsigned char> DON = unhex("4104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac"); |
| 47 | + |
| 48 | +const std::vector<std::pair<std::vector<unsigned char>, uint64_t>> PAYOUTS = { |
| 49 | + {P1, 5000000000ull}, |
| 50 | + {P2, 2500000000ull}, |
| 51 | +}; |
| 52 | + |
| 53 | +// ref_hash = 0xab * 32 ; last_txout_nonce LE bytes = 08 07 06 05 04 03 02 01. |
| 54 | +const uint64_t NONCE = 0x0102030405060708ull; |
| 55 | + |
| 56 | +// Oracle ground truth (identical to gentx_coinbase_test.cpp NOSEG_BYTES). |
| 57 | +const std::string NOSEG_BYTES = |
| 58 | + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0d03a1b2c3041122334455667788ffffffff0400f2052a010000001976a914111111111111111111111111111111111111111188ac00f90295000000001976a914222222222222222222222222222222222222222288ac0100000000000000434104ffd03de44a6e11b9917f3a29f9443283d9871c9d743ef30d5eddcd37094b64d1b3d8090496b53256786bf5c82932ec23c3b74d9f05a6f95a8b5529352656664bac00000000000000002a6a28abababababababababababababababababababababababababababababababab080706050403020100000000"; |
| 59 | + |
| 60 | +dgb::coin::ConnCoinbaseParts build() { |
| 61 | + dgb::coin::ConnCoinbaseInputs in; |
| 62 | + in.coinbase_script = CB; |
| 63 | + in.segwit_commitment_script = std::nullopt; |
| 64 | + in.payout_outputs = PAYOUTS; |
| 65 | + in.donation_amount = 1; // matches NOSEG donation value (1 sat) |
| 66 | + in.donation_script = DON; |
| 67 | + in.ref_hash = uint256(std::vector<unsigned char>(32, 0xab)); |
| 68 | + in.last_txout_nonce = NONCE; |
| 69 | + return dgb::coin::build_connection_coinbase_parts(in); |
| 70 | +} |
| 71 | + |
| 72 | +// (1) full assembled bytes == oracle wire. |
| 73 | +TEST(ConnCoinbase, FullBytesMatchOracle) { |
| 74 | + auto parts = build(); |
| 75 | + EXPECT_EQ(tohex(parts.gentx.bytes), NOSEG_BYTES); |
| 76 | +} |
| 77 | + |
| 78 | +// (2) the Stratum split reassembles to the exact oracle bytes with the |
| 79 | +// 8-byte extranonce slot filling the OP_RETURN nonce. |
| 80 | +TEST(ConnCoinbase, SplitReassemblesToOracle) { |
| 81 | + auto parts = build(); |
| 82 | + const std::string extranonce = "0807060504030201"; // en1(4)||en2(4), LE nonce |
| 83 | + EXPECT_EQ(parts.coinb1 + extranonce + parts.coinb2, NOSEG_BYTES); |
| 84 | +} |
| 85 | + |
| 86 | +// (3) coinb1 is the prefix up to (excl.) the nonce slot; coinb2 is the locktime. |
| 87 | +TEST(ConnCoinbase, Coinb1PrefixCoinb2Locktime) { |
| 88 | + auto parts = build(); |
| 89 | + ASSERT_GE(NOSEG_BYTES.size(), size_t{24}); |
| 90 | + // coinb1 == oracle minus the last 12 bytes (nonce 8 + locktime 4 = 24 hex) |
| 91 | + EXPECT_EQ(parts.coinb1, NOSEG_BYTES.substr(0, NOSEG_BYTES.size() - 24)); |
| 92 | + // coinb2 == final 4 bytes (locktime), all-zero |
| 93 | + EXPECT_EQ(parts.coinb2, "00000000"); |
| 94 | +} |
| 95 | + |
| 96 | +// (4) the OP_RETURN ref script is the canonical 6a28 || ref_hash || nonce(LE). |
| 97 | +TEST(ConnCoinbase, RefOpReturnLayout) { |
| 98 | + auto op = dgb::coin::build_ref_op_return( |
| 99 | + uint256(std::vector<unsigned char>(32, 0xab)), NONCE); |
| 100 | + std::string ref32; for (int i = 0; i < 32; ++i) ref32 += "ab"; |
| 101 | + EXPECT_EQ(tohex(op), std::string("6a28") + ref32 + "0807060504030201"); |
| 102 | +} |
| 103 | + |
| 104 | +} // namespace |
0 commit comments