|
4 | 4 |
|
5 | 5 | #include <chainparams.h>
|
6 | 6 | #include <consensus/amount.h>
|
| 7 | +#include <consensus/merkle.h> |
| 8 | +#include <core_io.h> |
| 9 | +#include <hash.h> |
7 | 10 | #include <net.h>
|
8 | 11 | #include <signet.h>
|
9 | 12 | #include <uint256.h>
|
10 | 13 | #include <util/chaintype.h>
|
11 | 14 | #include <validation.h>
|
12 | 15 |
|
| 16 | +#include <string> |
| 17 | + |
13 | 18 | #include <test/util/setup_common.h>
|
14 | 19 |
|
15 | 20 | #include <boost/test/unit_test.hpp>
|
@@ -145,4 +150,215 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo)
|
145 | 150 | BOOST_CHECK_EQUAL(out110_2.nChainTx, 111U);
|
146 | 151 | }
|
147 | 152 |
|
| 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 | + |
148 | 364 | BOOST_AUTO_TEST_SUITE_END()
|
0 commit comments