@@ -150,4 +150,215 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo)
150150 BOOST_CHECK_EQUAL (out110_2.nChainTx , 111U );
151151}
152152
153+ BOOST_AUTO_TEST_CASE (block_malleation)
154+ {
155+ // Test utilities that calls `IsBlockMutated` and then clears the validity
156+ // cache flags on `CBlock`.
157+ auto is_mutated = [](CBlock& block, bool check_witness_root) {
158+ bool mutated{IsBlockMutated (block, check_witness_root)};
159+ block.fChecked = false ;
160+ block.m_checked_witness_commitment = false ;
161+ block.m_checked_merkle_root = false ;
162+ return mutated;
163+ };
164+ auto is_not_mutated = [&is_mutated](CBlock& block, bool check_witness_root) {
165+ return !is_mutated (block, check_witness_root);
166+ };
167+
168+ // Test utilities to create coinbase transactions and insert witness
169+ // commitments.
170+ //
171+ // Note: this will not include the witness stack by default to avoid
172+ // triggering the "no witnesses allowed for blocks that don't commit to
173+ // witnesses" rule when testing other malleation vectors.
174+ auto create_coinbase_tx = [](bool include_witness = false ) {
175+ CMutableTransaction coinbase;
176+ coinbase.vin .resize (1 );
177+ if (include_witness) {
178+ coinbase.vin [0 ].scriptWitness .stack .resize (1 );
179+ coinbase.vin [0 ].scriptWitness .stack [0 ] = std::vector<unsigned char >(32 , 0x00 );
180+ }
181+
182+ coinbase.vout .resize (1 );
183+ coinbase.vout [0 ].scriptPubKey .resize (MINIMUM_WITNESS_COMMITMENT);
184+ coinbase.vout [0 ].scriptPubKey [0 ] = OP_RETURN;
185+ coinbase.vout [0 ].scriptPubKey [1 ] = 0x24 ;
186+ coinbase.vout [0 ].scriptPubKey [2 ] = 0xaa ;
187+ coinbase.vout [0 ].scriptPubKey [3 ] = 0x21 ;
188+ coinbase.vout [0 ].scriptPubKey [4 ] = 0xa9 ;
189+ coinbase.vout [0 ].scriptPubKey [5 ] = 0xed ;
190+
191+ auto tx = MakeTransactionRef (coinbase);
192+ assert (tx->IsCoinBase ());
193+ return tx;
194+ };
195+ auto insert_witness_commitment = [](CBlock& block, uint256 commitment) {
196+ assert (!block.vtx .empty () && block.vtx [0 ]->IsCoinBase () && !block.vtx [0 ]->vout .empty ());
197+
198+ CMutableTransaction mtx{*block.vtx [0 ]};
199+ CHash256 ().Write (commitment).Write (std::vector<unsigned char >(32 , 0x00 )).Finalize (commitment);
200+ memcpy (&mtx.vout [0 ].scriptPubKey [6 ], commitment.begin (), 32 );
201+ block.vtx [0 ] = MakeTransactionRef (mtx);
202+ };
203+
204+ {
205+ CBlock block;
206+
207+ // Empty block is expected to have merkle root of 0x0.
208+ BOOST_CHECK (block.vtx .empty ());
209+ block.hashMerkleRoot = uint256{1 };
210+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
211+ block.hashMerkleRoot = uint256{};
212+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ false ));
213+
214+ // Block with a single coinbase tx is mutated if the merkle root is not
215+ // equal to the coinbase tx's hash.
216+ block.vtx .push_back (create_coinbase_tx ());
217+ BOOST_CHECK (block.vtx [0 ]->GetHash () != block.hashMerkleRoot );
218+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
219+ block.hashMerkleRoot = block.vtx [0 ]->GetHash ();
220+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ false ));
221+
222+ // Block with two transactions is mutated if the merkle root does not
223+ // match the double sha256 of the concatenation of the two transaction
224+ // hashes.
225+ block.vtx .push_back (MakeTransactionRef (CMutableTransaction{}));
226+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
227+ HashWriter hasher;
228+ hasher.write (block.vtx [0 ]->GetHash ());
229+ hasher.write (block.vtx [1 ]->GetHash ());
230+ block.hashMerkleRoot = hasher.GetHash ();
231+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ false ));
232+
233+ // Block with two transactions is mutated if any node is duplicate.
234+ {
235+ block.vtx [1 ] = block.vtx [0 ];
236+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
237+ HashWriter hasher;
238+ hasher.write (block.vtx [0 ]->GetHash ());
239+ hasher.write (block.vtx [1 ]->GetHash ());
240+ block.hashMerkleRoot = hasher.GetHash ();
241+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
242+ }
243+
244+ // Blocks with 64-byte coinbase transactions are not considered mutated
245+ block.vtx .clear ();
246+ {
247+ CMutableTransaction mtx;
248+ mtx.vin .resize (1 );
249+ mtx.vout .resize (1 );
250+ mtx.vout [0 ].scriptPubKey .resize (4 );
251+ block.vtx .push_back (MakeTransactionRef (mtx));
252+ block.hashMerkleRoot = block.vtx .back ()->GetHash ();
253+ assert (block.vtx .back ()->IsCoinBase ());
254+ assert (GetSerializeSize (TX_NO_WITNESS (block.vtx .back ())) == 64 );
255+ }
256+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ false ));
257+ }
258+
259+ {
260+ // Test merkle root malleation
261+
262+ // Pseudo code to mine transactions tx{1,2,3}:
263+ //
264+ // ```
265+ // loop {
266+ // tx1 = random_tx()
267+ // tx2 = random_tx()
268+ // tx3 = deserialize_tx(txid(tx1) || txid(tx2));
269+ // if serialized_size_without_witness(tx3) == 64 {
270+ // print(hex(tx3))
271+ // break
272+ // }
273+ // }
274+ // ```
275+ //
276+ // The `random_tx` function used to mine the txs below simply created
277+ // empty transactions with a random version field.
278+ CMutableTransaction tx1;
279+ BOOST_CHECK (DecodeHexTx (tx1, " ff204bd0000000000000" , /* try_no_witness=*/ true , /* try_witness=*/ false ));
280+ CMutableTransaction tx2;
281+ BOOST_CHECK (DecodeHexTx (tx2, " 8ae53c92000000000000" , /* try_no_witness=*/ true , /* try_witness=*/ false ));
282+ CMutableTransaction tx3;
283+ BOOST_CHECK (DecodeHexTx (tx3, " cdaf22d00002c6a7f848f8ae4d30054e61dcf3303d6fe01d282163341f06feecc10032b3160fcab87bdfe3ecfb769206ef2d991b92f8a268e423a6ef4d485f06" , /* try_no_witness=*/ true , /* try_witness=*/ false ));
284+ {
285+ // Verify that double_sha256(txid1||txid2) == txid3
286+ HashWriter hasher;
287+ hasher.write (tx1.GetHash ());
288+ hasher.write (tx2.GetHash ());
289+ assert (hasher.GetHash () == tx3.GetHash ());
290+ // Verify that tx3 is 64 bytes in size (without witness).
291+ assert (GetSerializeSize (TX_NO_WITNESS (tx3)) == 64 );
292+ }
293+
294+ CBlock block;
295+ block.vtx .push_back (MakeTransactionRef (tx1));
296+ block.vtx .push_back (MakeTransactionRef (tx2));
297+ uint256 merkle_root = block.hashMerkleRoot = BlockMerkleRoot (block);
298+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ false ));
299+
300+ // Mutate the block by replacing the two transactions with one 64-byte
301+ // transaction that serializes into the concatenation of the txids of
302+ // the transactions in the unmutated block.
303+ block.vtx .clear ();
304+ block.vtx .push_back (MakeTransactionRef (tx3));
305+ BOOST_CHECK (!block.vtx .back ()->IsCoinBase ());
306+ BOOST_CHECK (BlockMerkleRoot (block) == merkle_root);
307+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
308+ }
309+
310+ {
311+ CBlock block;
312+ block.vtx .push_back (create_coinbase_tx (/* include_witness=*/ true ));
313+ {
314+ CMutableTransaction mtx;
315+ mtx.vin .resize (1 );
316+ mtx.vin [0 ].scriptWitness .stack .resize (1 );
317+ mtx.vin [0 ].scriptWitness .stack [0 ] = {0 };
318+ block.vtx .push_back (MakeTransactionRef (mtx));
319+ }
320+ block.hashMerkleRoot = BlockMerkleRoot (block);
321+ // Block with witnesses is considered mutated if the witness commitment
322+ // is not validated.
323+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ false ));
324+ // Block with invalid witness commitment is considered mutated.
325+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ true ));
326+
327+ // Block with valid commitment is not mutated
328+ {
329+ auto commitment{BlockWitnessMerkleRoot (block)};
330+ insert_witness_commitment (block, commitment);
331+ block.hashMerkleRoot = BlockMerkleRoot (block);
332+ }
333+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ true ));
334+
335+ // Malleating witnesses should be caught by `IsBlockMutated`.
336+ {
337+ CMutableTransaction mtx{*block.vtx [1 ]};
338+ assert (!mtx.vin [0 ].scriptWitness .stack [0 ].empty ());
339+ ++mtx.vin [0 ].scriptWitness .stack [0 ][0 ];
340+ block.vtx [1 ] = MakeTransactionRef (mtx);
341+ }
342+ // Without also updating the witness commitment, the merkle root should
343+ // not change when changing one of the witnesses.
344+ BOOST_CHECK (block.hashMerkleRoot == BlockMerkleRoot (block));
345+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ true ));
346+ {
347+ auto commitment{BlockWitnessMerkleRoot (block)};
348+ insert_witness_commitment (block, commitment);
349+ block.hashMerkleRoot = BlockMerkleRoot (block);
350+ }
351+ BOOST_CHECK (is_not_mutated (block, /* check_witness_root=*/ true ));
352+
353+ // Test malleating the coinbase witness reserved value
354+ {
355+ CMutableTransaction mtx{*block.vtx [0 ]};
356+ mtx.vin [0 ].scriptWitness .stack .resize (0 );
357+ block.vtx [0 ] = MakeTransactionRef (mtx);
358+ block.hashMerkleRoot = BlockMerkleRoot (block);
359+ }
360+ BOOST_CHECK (is_mutated (block, /* check_witness_root=*/ true ));
361+ }
362+ }
363+
153364BOOST_AUTO_TEST_SUITE_END ()
0 commit comments