88#include " share_messages.hpp"
99#include " share_types.hpp"
1010
11+ #include < impl/dgb/coin/gentx_coinbase.hpp>
12+
1113#include < core/coin_params.hpp>
1214#include < core/hash.hpp>
1315#include < core/pack.hpp>
@@ -931,7 +933,7 @@ inline std::vector<unsigned char> get_share_script(const auto* obj)
931933// Reference: frstrtr/p2pool-merged-v36 p2pool/data.py generate_transaction()
932934// ============================================================================
933935template <typename ShareT, typename TrackerT>
934- uint256 generate_share_transaction (const ShareT& share, TrackerT& tracker, const core::CoinParams& params, bool dump_diag = false , bool v36_active = false )
936+ uint256 generate_share_transaction (const ShareT& share, TrackerT& tracker, const core::CoinParams& params, bool dump_diag = false , bool v36_active = false , dgb::coin::GentxCoinbase* out_gentx = nullptr )
935937{
936938 auto gst_t0 = std::chrono::steady_clock::now ();
937939 constexpr int64_t ver = ShareT::version;
@@ -1133,81 +1135,22 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
11331135 payout_outputs.erase (payout_outputs.begin (), payout_outputs.end () - MAX_OUTPUTS );
11341136
11351137 // --- 4. Serialise the coinbase transaction ---
1136- // Non-witness serialization (for txid computation):
1137- // version(4) + vin_count(varint) + vin + vout_count(varint) + vouts + locktime(4)
1138- PackStream tx;
1139-
1140- // tx version = 1
1141- uint32_t tx_version = 1 ;
1142- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&tx_version), 4 ));
1143-
1144- // vin count = 1
1145- {
1146- unsigned char one = 1 ;
1147- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&one), 1 ));
1148- }
1149-
1150- // vin[0]: prev_output = 0...0:ffffffff, script = coinbase, sequence = 0
1151- {
1152- // prev_hash (32 zero bytes)
1153- uint256 zero_hash;
1154- tx << zero_hash;
1155- // prev_index (0xffffffff)
1156- uint32_t prev_idx = 0xffffffff ;
1157- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&prev_idx), 4 ));
1158- // script (VarStr)
1159- tx << share.m_coinbase ;
1160- // sequence (0xffffffff — standard coinbase sequence, matches Python)
1161- uint32_t seq = 0xffffffff ;
1162- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&seq), 4 ));
1163- }
1164-
1165- // Count total outputs
1166- size_t n_outs = payout_outputs.size () + 1 /* donation */ + 1 /* OP_RETURN commitment */ ;
1167- // Segwit commitment output (if applicable)
1168- bool has_segwit = false ;
1138+ // Wire layout (version|vin|vouts|locktime) and txid are produced by the
1139+ // SSOT assembler dgb::coin::assemble_gentx_coinbase() so that emission and
1140+ // verification can never diverge on a byte. This function only builds the
1141+ // per-share inputs (coinbase script, optional segwit commitment, payout
1142+ // outputs, donation, OP_RETURN ref commitment).
1143+
1144+ // Segwit witness-commitment output (value=0) — present iff this share
1145+ // carries segwit data on a segwit-active version. The script is
1146+ // 0x6a24aa21a9ed + SHA256d(wtxid_merkle_root || P2POOL_WITNESS_NONCE).
1147+ std::optional<std::vector<unsigned char >> segwit_commitment_script;
11691148 if constexpr (ver >= dgb::SEGWIT_ACTIVATION_VERSION )
11701149 {
11711150 if constexpr (requires { share.m_segwit_data ; })
11721151 {
11731152 if (share.m_segwit_data .has_value ())
11741153 {
1175- has_segwit = true ;
1176- n_outs += 1 ;
1177- }
1178- }
1179- }
1180-
1181- // vout count (varint — for < 253 outputs, it's a single byte)
1182- if (n_outs < 253 )
1183- {
1184- uint8_t cnt = static_cast <uint8_t >(n_outs);
1185- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&cnt), 1 ));
1186- }
1187- else
1188- {
1189- uint8_t marker = 0xfd ;
1190- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&marker), 1 ));
1191- uint16_t cnt = static_cast <uint16_t >(n_outs);
1192- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&cnt), 2 ));
1193- }
1194-
1195- // Helper to write a single tx_out: value(8LE) + script(VarStr)
1196- auto write_txout = [&](uint64_t value, const std::vector<unsigned char >& script) {
1197- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&value), 8 ));
1198- BaseScript bs;
1199- bs.m_data = script;
1200- tx << bs;
1201- };
1202-
1203- // Segwit commitment output (value=0, script=OP_RETURN + witness_commitment)
1204- if (has_segwit)
1205- {
1206- if constexpr (requires { share.m_segwit_data ; })
1207- {
1208- if (share.m_segwit_data .has_value ())
1209- {
1210- // witness commitment: 0x6a24aa21a9ed + SHA256d(wtxid_merkle_root || '[P2Pool]'*4)
12111154 std::vector<unsigned char > wscript;
12121155 wscript.push_back (0x6a ); // OP_RETURN
12131156 wscript.push_back (0x24 ); // PUSH 36
@@ -1219,7 +1162,7 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
12191162 uint256 commitment = compute_p2pool_witness_commitment (sd.m_wtxid_merkle_root );
12201163 auto commitment_bytes = commitment.GetChars ();
12211164 wscript.insert (wscript.end (), commitment_bytes.begin (), commitment_bytes.end ());
1222- write_txout ( 0 , wscript);
1165+ segwit_commitment_script = std::move ( wscript);
12231166 if (dump_diag) {
12241167 LOG_INFO << " [WC-GST] wtxid_root=" << sd.m_wtxid_merkle_root .GetHex ()
12251168 << " commitment=" << commitment.GetHex ();
@@ -1228,17 +1171,13 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
12281171 }
12291172 }
12301173
1231- // PPLNS payout outputs
1232- for (auto & [script, amount] : payout_outputs)
1233- write_txout (amount, script);
1234-
12351174 // Donation output — V35 shares always use P2PK DONATION_SCRIPT,
12361175 // V36 shares always use P2SH COMBINED_DONATION_SCRIPT.
12371176 // Each share was created with the donation script matching its version.
12381177 auto donation_script = params.donation_script_func (ver);
1239- write_txout (donation_amount, donation_script);
12401178
12411179 // OP_RETURN commitment: value=0, script = 0x6a28 + ref_hash(32) + last_txout_nonce(8)
1180+ std::vector<unsigned char > op_return_script;
12421181 {
12431182 // We need the ref_hash — recompute it from the share the same way share_init_verify does
12441183 PackStream ref_stream;
@@ -1350,7 +1289,6 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
13501289 uint256 ref_hash = check_merkle_link (hash_ref, share.m_ref_merkle_link );
13511290
13521291 // Build OP_RETURN script: 0x6a 0x28 + ref_hash(32) + last_txout_nonce(8)
1353- std::vector<unsigned char > op_return_script;
13541292 op_return_script.push_back (0x6a ); // OP_RETURN
13551293 op_return_script.push_back (0x28 ); // PUSH 40 bytes
13561294 op_return_script.insert (op_return_script.end (), ref_hash.data (), ref_hash.data () + 32 );
@@ -1359,19 +1297,23 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
13591297 auto * p = reinterpret_cast <const unsigned char *>(&nonce);
13601298 op_return_script.insert (op_return_script.end (), p, p + 8 );
13611299 }
1362- write_txout (0 , op_return_script);
13631300 }
13641301
1365- // lock_time = 0
1366- {
1367- uint32_t locktime = 0 ;
1368- tx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&locktime), 4 ));
1369- }
1302+ // --- 5. Assemble + compute txid via the SSOT (double-SHA256 of
1303+ // non-witness serialization). Reconstruct the PackStream tx so the
1304+ // downstream V36 hash_link diagnostics keep working unchanged.
1305+ auto _gc = dgb::coin::assemble_gentx_coinbase (
1306+ share.m_coinbase .m_data , segwit_commitment_script, payout_outputs,
1307+ donation_amount, donation_script, op_return_script);
1308+ PackStream tx;
1309+ tx.write (std::span<const std::byte>(
1310+ reinterpret_cast <const std::byte*>(_gc.bytes .data ()), _gc.bytes .size ()));
1311+ auto txid = _gc.txid ;
13701312
1371- // --- 5. Compute txid (double-SHA256 of non-witness serialization) ---
1372- auto tx_span = std::span< const unsigned char >(
1373- reinterpret_cast < const unsigned char *>(tx. data ()), tx. size ());
1374- auto txid = Hash (tx_span) ;
1313+ // Expose the SSOT gentx ( non-witness bytes + txid) for the won-block
1314+ // reconstructor (#82): block tx[0] is this coinbase and gentx_hash is
1315+ // its merkle leaf. Default nullptr => zero change for verification callers.
1316+ if (out_gentx) *out_gentx = _gc ;
13751317
13761318 // V36 hash_link cross-check: compute prefix hash_link from our coinbase
13771319 // and compare with the share's stored hash_link. If states differ,
@@ -1452,8 +1394,8 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
14521394 LOG_WARNING << " [GENTX-DIAG] coinbase_hex=" << to_hex (cp, tx.size ());
14531395 LOG_WARNING << " [GENTX-DIAG] pplns_outputs=" << payout_outputs.size ()
14541396 << " donation_amount=" << donation_amount
1455- << " n_outs=" << n_outs
1456- << " has_segwit=" << has_segwit ;
1397+ << " n_outs=" << (payout_outputs. size () + 2 + (segwit_commitment_script. has_value () ? 1u : 0u ))
1398+ << " has_segwit=" << segwit_commitment_script. has_value () ;
14571399 LOG_WARNING << " [GENTX-DIAG] total_weight=" << total_weight.GetHex ()
14581400 << " total_don_weight=" << total_donation_weight.GetHex ();
14591401 for (size_t i = 0 ; i < payout_outputs.size (); ++i) {
@@ -2729,7 +2671,8 @@ uint256 create_local_share(
27292671 share.m_last_txout_nonce = static_cast <uint64_t >(std::time (nullptr )) ^
27302672 (static_cast <uint64_t >(min_header.m_nonce ) << 32 );
27312673
2732- // --- Build the p2pool coinbase in the same format as generate_share_transaction ---
2674+ // --- Build the p2pool coinbase via the SSOT assemble_gentx_coinbase() ---
2675+ // (identical wire layout to generate_share_transaction; one byte path, not a twin)
27332676 // This is needed to compute hash_link and to verify the share locally.
27342677 // The coinbase format is: version(4) + vin(1 input) + vout(outputs...) + locktime(4)
27352678 //
@@ -2942,41 +2885,29 @@ uint256 create_local_share(
29422885 if (payout_outputs.size () > 4000 )
29432886 payout_outputs.erase (payout_outputs.begin (), payout_outputs.end () - 4000 );
29442887
2945- PackStream gentx;
2946- { uint32_t v = 1 ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&v), 4 )); }
2947- { unsigned char one = 1 ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&one), 1 )); }
2948- { uint256 z; gentx << z; }
2949- { uint32_t idx = 0xffffffff ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&idx), 4 )); }
2950- gentx << share.m_coinbase ;
2951- { uint32_t seq = 0xffffffff ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&seq), 4 )); }
2952- size_t n_outs = payout_outputs.size () + 1 + 1 ;
2953- bool has_segwit_fb = share.m_segwit_data .has_value ();
2954- if (has_segwit_fb) n_outs += 1 ;
2955- if (n_outs < 253 ) { uint8_t cnt = (uint8_t )n_outs; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&cnt), 1 )); }
2956- else { uint8_t m = 0xfd ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&m), 1 )); uint16_t cnt = (uint16_t )n_outs; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&cnt), 2 )); }
2957- auto write_txout = [&](uint64_t value, const std::vector<unsigned char >& script) {
2958- gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(&value), 8 ));
2959- BaseScript bs; bs.m_data = script; gentx << bs;
2960- };
2961- if (has_segwit_fb) {
2888+ // Build the p2pool coinbase via the SSOT assembler
2889+ // dgb::coin::assemble_gentx_coinbase() — identical wire layout to
2890+ // generate_share_transaction(). This is no longer a hand-rolled twin:
2891+ // both emission and verification route through the one byte path.
2892+ std::optional<std::vector<unsigned char >> segwit_opt;
2893+ if (share.m_segwit_data .has_value ()) {
29622894 auto & sd = share.m_segwit_data .value ();
29632895 std::vector<unsigned char > wscript = {0x6a , 0x24 , 0xaa , 0x21 , 0xa9 , 0xed };
29642896 uint256 commitment = compute_p2pool_witness_commitment (sd.m_wtxid_merkle_root );
29652897 auto cb = commitment.GetChars ();
29662898 wscript.insert (wscript.end (), cb.begin (), cb.end ());
2967- write_txout (0 , wscript);
2968- }
2969- for (auto & [script, amount] : payout_outputs) write_txout (amount, script);
2970- write_txout (donation_amount, params.donation_script_func (int64_t (36 )));
2971- { std::vector<unsigned char > op; op.push_back (0x6a ); op.push_back (0x28 );
2972- op.insert (op.end (), ref_hash.data (), ref_hash.data () + 32 );
2973- uint64_t n = share.m_last_txout_nonce ; auto * p = reinterpret_cast <const unsigned char *>(&n);
2974- op.insert (op.end (), p, p + 8 ); write_txout (0 , op); }
2975- { uint32_t lt = 0 ; gentx.write (std::span<const std::byte>(reinterpret_cast <const std::byte*>(<), 4 )); }
2976-
2977- coinbase_bytes_for_hashlink.assign (
2978- reinterpret_cast <const unsigned char *>(gentx.data ()),
2979- reinterpret_cast <const unsigned char *>(gentx.data ()) + gentx.size ());
2899+ segwit_opt = std::move (wscript);
2900+ }
2901+ auto donation_script = params.donation_script_func (int64_t (36 ));
2902+ std::vector<unsigned char > op_return_vec;
2903+ op_return_vec.push_back (0x6a ); op_return_vec.push_back (0x28 );
2904+ op_return_vec.insert (op_return_vec.end (), ref_hash.data (), ref_hash.data () + 32 );
2905+ { uint64_t n = share.m_last_txout_nonce ; auto * p = reinterpret_cast <const unsigned char *>(&n);
2906+ op_return_vec.insert (op_return_vec.end (), p, p + 8 ); }
2907+ auto _gc = dgb::coin::assemble_gentx_coinbase (
2908+ share.m_coinbase .m_data , segwit_opt, payout_outputs,
2909+ donation_amount, donation_script, op_return_vec);
2910+ coinbase_bytes_for_hashlink = _gc.bytes ;
29802911 }
29812912
29822913 // The split point: everything before ref_hash + last_txout_nonce + locktime
0 commit comments