Skip to content

Commit 3eaaafa

Browse files
dergoeggeachow101
authored andcommitted
[test] IsBlockMutated unit tests
Github-Pull: #29412 Rebased-From: d8087ad
1 parent 0667441 commit 3eaaafa

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

src/test/validation_tests.cpp

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,215 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo)
149149
BOOST_CHECK_EQUAL(out210.nChainTx, 200U);
150150
}
151151

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

0 commit comments

Comments
 (0)