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>
@@ -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,18 @@ 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);
1363- }
1364-
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 ));
13691300 }
13701301
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);
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 ;
13751312
13761313 // V36 hash_link cross-check: compute prefix hash_link from our coinbase
13771314 // and compare with the share's stored hash_link. If states differ,
@@ -1452,8 +1389,8 @@ uint256 generate_share_transaction(const ShareT& share, TrackerT& tracker, const
14521389 LOG_WARNING << " [GENTX-DIAG] coinbase_hex=" << to_hex (cp, tx.size ());
14531390 LOG_WARNING << " [GENTX-DIAG] pplns_outputs=" << payout_outputs.size ()
14541391 << " donation_amount=" << donation_amount
1455- << " n_outs=" << n_outs
1456- << " has_segwit=" << has_segwit ;
1392+ << " n_outs=" << (payout_outputs. size () + 2 + (segwit_commitment_script. has_value () ? 1u : 0u ))
1393+ << " has_segwit=" << segwit_commitment_script. has_value () ;
14571394 LOG_WARNING << " [GENTX-DIAG] total_weight=" << total_weight.GetHex ()
14581395 << " total_don_weight=" << total_donation_weight.GetHex ();
14591396 for (size_t i = 0 ; i < payout_outputs.size (); ++i) {
@@ -2729,7 +2666,8 @@ uint256 create_local_share(
27292666 share.m_last_txout_nonce = static_cast <uint64_t >(std::time (nullptr )) ^
27302667 (static_cast <uint64_t >(min_header.m_nonce ) << 32 );
27312668
2732- // --- Build the p2pool coinbase in the same format as generate_share_transaction ---
2669+ // --- Build the p2pool coinbase via the SSOT assemble_gentx_coinbase() ---
2670+ // (identical wire layout to generate_share_transaction; one byte path, not a twin)
27332671 // This is needed to compute hash_link and to verify the share locally.
27342672 // The coinbase format is: version(4) + vin(1 input) + vout(outputs...) + locktime(4)
27352673 //
@@ -2942,41 +2880,29 @@ uint256 create_local_share(
29422880 if (payout_outputs.size () > 4000 )
29432881 payout_outputs.erase (payout_outputs.begin (), payout_outputs.end () - 4000 );
29442882
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) {
2883+ // Build the p2pool coinbase via the SSOT assembler
2884+ // dgb::coin::assemble_gentx_coinbase() — identical wire layout to
2885+ // generate_share_transaction(). This is no longer a hand-rolled twin:
2886+ // both emission and verification route through the one byte path.
2887+ std::optional<std::vector<unsigned char >> segwit_opt;
2888+ if (share.m_segwit_data .has_value ()) {
29622889 auto & sd = share.m_segwit_data .value ();
29632890 std::vector<unsigned char > wscript = {0x6a , 0x24 , 0xaa , 0x21 , 0xa9 , 0xed };
29642891 uint256 commitment = compute_p2pool_witness_commitment (sd.m_wtxid_merkle_root );
29652892 auto cb = commitment.GetChars ();
29662893 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 ());
2894+ segwit_opt = std::move (wscript);
2895+ }
2896+ auto donation_script = params.donation_script_func (int64_t (36 ));
2897+ std::vector<unsigned char > op_return_vec;
2898+ op_return_vec.push_back (0x6a ); op_return_vec.push_back (0x28 );
2899+ op_return_vec.insert (op_return_vec.end (), ref_hash.data (), ref_hash.data () + 32 );
2900+ { uint64_t n = share.m_last_txout_nonce ; auto * p = reinterpret_cast <const unsigned char *>(&n);
2901+ op_return_vec.insert (op_return_vec.end (), p, p + 8 ); }
2902+ auto _gc = dgb::coin::assemble_gentx_coinbase (
2903+ share.m_coinbase .m_data , segwit_opt, payout_outputs,
2904+ donation_amount, donation_script, op_return_vec);
2905+ coinbase_bytes_for_hashlink = _gc.bytes ;
29802906 }
29812907
29822908 // The split point: everything before ref_hash + last_txout_nonce + locktime
0 commit comments