diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 96a34f70c..206fa7d8f 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -95,6 +95,7 @@ add_executable(test_tapyrus sanity_tests.cpp scheduler_tests.cpp script_p2sh_tests.cpp + script_risky_combinations_tests.cpp script_standard_tests.cpp script_tests.cpp scriptnum_tests.cpp diff --git a/src/test/chainstate_tests.cpp b/src/test/chainstate_tests.cpp index fcc07644c..e5cb29daa 100644 --- a/src/test/chainstate_tests.cpp +++ b/src/test/chainstate_tests.cpp @@ -936,6 +936,167 @@ BOOST_AUTO_TEST_CASE(duplicate_nonreissuable_issuance_rejected) } } +/** + * Regression test: burning an NFT must not erase its colorId from + * g_colorid_state, so a later re-issuance attempt is still rejected. + * + * "Burn" means spending the sole live NFT UTXO to an uncolored output — + * VerifyTokenBalances allows colored input value with no matching colored + * output (the value is simply discarded). This does not touch + * g_colorid_state: CIssuedColorIds::Erase() is only ever called from + * DisconnectBlock (reorg), never from a normal spend/burn. So after a burn, + * total live supply of the colorId is zero, but g_colorid_state must still + * report it as issued forever. + * + * As in duplicate_nonreissuable_issuance_rejected, actually re-deriving the + * same colorId requires the exact defining TPC outpoint to look unspent + * again (a SHA-256 preimage collision in practice), so the re-issuance + * attempt is exercised directly against CheckColorIdentifierValidity with a + * synthetic CCoinsViewCache that presents the (long since spent) defining + * outpoint as unspent — simulating the same reorg-shaped bug the + * NON_REISSUABLE test guards against. + */ +BOOST_AUTO_TEST_CASE(duplicate_nft_issuance_after_burn_rejected) +{ + CScript payTo = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG; + + CKey key; + // Arbitrary non-coinbase key; low byte 3 is not load-bearing, just chosen + // to avoid clashing with any of TestChainSetup's pre-defined keys. + const unsigned char vchKeyBytes[32] = { + 3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + key.Set(vchKeyBytes, vchKeyBytes + 32, true); + CPubKey pubkey = key.GetPubKey(); + std::vector vchPubKey(pubkey.begin(), pubkey.end()); + std::vector pubkeyHash(20); + CHash160().Write(pubkey.data(), pubkey.size()).Finalize(pubkeyHash.data()); + + // Block: spend coinbase[1] into a plain TPC P2PKH output that will define + // the NFT's color id below. + CMutableTransaction spendTx; + spendTx.nFeatures = 1; + spendTx.vin.resize(1); + spendTx.vout.resize(1); + spendTx.vin[0].prevout.hashMalFix = m_coinbase_txns[1]->GetHashMalFix(); + spendTx.vin[0].prevout.n = 0; + spendTx.vout[0].nValue = 100 * CENT; + spendTx.vout[0].scriptPubKey = CScript() << OP_DUP << OP_HASH160 + << ToByteVector(pubkeyHash) + << OP_EQUALVERIFY << OP_CHECKSIG; + { + std::vector vchSig; + uint256 sigHash = SignatureHash( + m_coinbase_txns[1]->vout[0].scriptPubKey, spendTx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + coinbaseKey.Sign_Schnorr(sigHash, vchSig); + vchSig.push_back(SIGHASH_ALL); + spendTx.vin[0].scriptSig = CScript() << vchSig; + } + CreateAndProcessBlock({spendTx}, payTo); + + // Block: issue an NFT (nValue must be exactly 1) from the defining P2PKH UTXO. + COutPoint definingUtxo(spendTx.GetHashMalFix(), 0); + ColorIdentifier colorid(definingUtxo, TokenTypes::NFT); + CScript colorScript = CScript() << colorid.toVector() << OP_COLOR + << OP_DUP << OP_HASH160 + << ToByteVector(pubkeyHash) + << OP_EQUALVERIFY << OP_CHECKSIG; + + CMutableTransaction issueTx; + issueTx.nFeatures = 1; + issueTx.vin.resize(1); + issueTx.vout.resize(1); + issueTx.vin[0].prevout = definingUtxo; + issueTx.vout[0].nValue = 1; + issueTx.vout[0].scriptPubKey = colorScript; + { + std::vector vchSig; + uint256 sigHash = SignatureHash( + spendTx.vout[0].scriptPubKey, issueTx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + key.Sign_Schnorr(sigHash, vchSig); + vchSig.push_back(SIGHASH_ALL); + issueTx.vin[0].scriptSig = CScript() << vchSig << vchPubKey; + } + CreateAndProcessBlock({issueTx}, payTo); + + { + LOCK(cs_main); + BOOST_REQUIRE(g_colorid_state && g_colorid_state->IsIssued(colorid)); + } + + // Block: burn the NFT — spend issueTx:0 (the only live unit) plus a TPC + // coinbase input (for fee/tpcin>0) to a plain uncolored output. No output + // carries colorid, so its value is simply discarded (a genuine burn). + CMutableTransaction burnTx; + burnTx.nFeatures = 1; + burnTx.vin.resize(2); + burnTx.vout.resize(1); + burnTx.vin[0].prevout = COutPoint(issueTx.GetHashMalFix(), 0); + burnTx.vin[1].prevout.hashMalFix = m_coinbase_txns[2]->GetHashMalFix(); + burnTx.vin[1].prevout.n = 0; + burnTx.vout[0].nValue = 1; + burnTx.vout[0].scriptPubKey = CScript() << OP_DUP << OP_HASH160 + << ToByteVector(pubkeyHash) + << OP_EQUALVERIFY << OP_CHECKSIG; + { + std::vector vchSig; + uint256 sigHash = SignatureHash( + issueTx.vout[0].scriptPubKey, burnTx, 0, SIGHASH_ALL, 0, SigVersion::BASE); + key.Sign_Schnorr(sigHash, vchSig); + vchSig.push_back(SIGHASH_ALL); + burnTx.vin[0].scriptSig = CScript() << vchSig << vchPubKey; + } + { + std::vector vchSig; + uint256 sigHash = SignatureHash( + m_coinbase_txns[2]->vout[0].scriptPubKey, burnTx, 1, SIGHASH_ALL, 0, SigVersion::BASE); + coinbaseKey.Sign_Schnorr(sigHash, vchSig); + vchSig.push_back(SIGHASH_ALL); + burnTx.vin[1].scriptSig = CScript() << vchSig; + } + CreateAndProcessBlock({burnTx}, payTo); + + // The burn destroyed the only live unit of colorid, but g_colorid_state + // must still remember it was issued -- Erase() is reorg-only. + { + LOCK(cs_main); + BOOST_REQUIRE(g_colorid_state && g_colorid_state->IsIssued(colorid)); + } + + // Build a synthetic CCoinsViewCache that presents definingUtxo as unspent + // again, simulating a reorg that restored the UTXO while g_colorid_state + // retained the record. CheckColorIdentifierValidity must still reject the + // re-issuance with "bad-txns-colorid-already-issued", even though the + // colorId's supply is currently zero. + { + LOCK(cs_main); + + CCoinsView dummy; + CCoinsViewCache syntheticView(&dummy); + Coin tpcCoin; + tpcCoin.out.nValue = spendTx.vout[0].nValue; + tpcCoin.out.scriptPubKey = spendTx.vout[0].scriptPubKey; + tpcCoin.nHeight = 6; + tpcCoin.fCoinBase = false; + syntheticView.AddCoin(definingUtxo, std::move(tpcCoin), /*potential_overwrite=*/false); + + CMutableTransaction reissueTx; + reissueTx.nFeatures = 1; + reissueTx.vin.resize(1); + reissueTx.vout.resize(1); + reissueTx.vin[0].prevout = definingUtxo; + reissueTx.vout[0].nValue = 1; + reissueTx.vout[0].scriptPubKey = colorScript; + + CValidationState state; + bool valid = CheckColorIdentifierValidity( + CTransaction(reissueTx), state, syntheticView, chainActive.Tip()->nHeight); + + BOOST_CHECK(!valid); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-colorid-already-issued"); + } +} + /** * Regression test: VerifyTokenBalances must not use DoS=100 for tpcin<=0. * diff --git a/src/test/data/script_tests.json b/src/test/data/script_tests.json index 0acff8a4c..9b422c068 100644 --- a/src/test/data/script_tests.json +++ b/src/test/data/script_tests.json @@ -3089,16 +3089,35 @@ [ "0x47 0x3044022073df1d1a15d0e6fd8a14f0c1ba4409239c97d4fbc3cb113095c0929582d8447e0220271ec24bc865852e239693b20f0efbbe584d1b4cc4777948f18473ce30dc976a01 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x19 0x76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac", "0x21 0xc11863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262 COLOR HASH160 0x14 0xda8a647bba351bbae4cee0089d373c97ec240580 EQUAL", - "", + "CP2SH_COLORED", "OK", - "CP2SH(P2PKH) ECDSA" + "CP2SH(P2PKH) ECDSA, redeem script genuinely satisfied (passes with the flag on)" ], [ "0x41 0xbc4b27bef382c8159db61d673868137020c08e7cb25b9d9653c706a025b02eadba29657e2937fd58f1dcfdb582433cb63a06a15379de562fadba9877cc531d3101 0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 0x19 0x76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac", "0x21 0xc11863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262 COLOR HASH160 0x14 0xda8a647bba351bbae4cee0089d373c97ec240580 EQUAL", + "CP2SH_COLORED", + "OK", + "CP2SH(P2PKH) SCHNORR, redeem script genuinely satisfied (passes with the flag on)" +], + +["The next two show what SCRIPT_VERIFY_CP2SH_COLORED actually changes: same"], +["scriptSig/scriptPubKey (redeem script HASH160 matches; the sig inside it does"], +["NOT correspond to the pubkey), flag off vs flag on."], + +[ + "0x47 0x304402202539b3b68e870d525765d24cc08fadd7502ffd8735564eb545a9a3057f85469d02207611be2fde48142a19497f6c4cb4146c71bb4cd6bc567dbae30bb7ecd46bc73801 0x21 0x026292d4b98eecee53d2b78bea0883bbf85bcb4fcdd4479369bfc7c3ee3b46ea49 0x19 0x76a914aba705eb3d02be6c4563d3f6874a3e81004ec83988ac", + "0x21 0xc1c6a2c97c58b8e8264ebc66ffa99a1536ec324c21263722368a00b3dfb025abec COLOR HASH160 0x14 0x45601212e04228745b047c1fc4e8d78d83a762b1 EQUAL", "", "OK", - "CP2SH(P2PKH) SCHNORR" + "CP2SH(P2PKH), broken inner signature, flag OFF -- only the HASH160 preimage is checked, so this passes" +], +[ + "0x47 0x304402202539b3b68e870d525765d24cc08fadd7502ffd8735564eb545a9a3057f85469d02207611be2fde48142a19497f6c4cb4146c71bb4cd6bc567dbae30bb7ecd46bc73801 0x21 0x026292d4b98eecee53d2b78bea0883bbf85bcb4fcdd4479369bfc7c3ee3b46ea49 0x19 0x76a914aba705eb3d02be6c4563d3f6874a3e81004ec83988ac", + "0x21 0xc1c6a2c97c58b8e8264ebc66ffa99a1536ec324c21263722368a00b3dfb025abec COLOR HASH160 0x14 0x45601212e04228745b047c1fc4e8d78d83a762b1 EQUAL", + "CP2SH_COLORED", + "EVAL_FALSE", + "CP2SH(P2PKH), same script, flag ON -- the redeem script is now actually evaluated and its CHECKSIG fails" ], ["The End"] ] diff --git a/src/test/data/tx_invalid.json b/src/test/data/tx_invalid.json index 19b21b4f1..f47beaa93 100644 --- a/src/test/data/tx_invalid.json +++ b/src/test/data/tx_invalid.json @@ -887,5 +887,48 @@ ["0000000000000000000000000000000000000000000000000000000000000100", 11, "DUP HASH160 0x14 0x4c9c3dfac4207d5d8cb89df5722cb3d712385e3f EQUALVERIFY CHECKSIG", 1011]], "010000000c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000", "WITNESS"], + ["Colored coin spends (OP_COLOR): invalid CP2PKH / CP2SH(P2PKH) variants"], + ["Note: these only exercise script-level (VerifyScript) rejections. The"], + ["consensus-level 'OP_COLOR must be exactly CP2PKH or CP2SH, else"], + ["bad-txns-nonstandard-opcolor' rule (CheckColorIdentifierValidity) is not"], + ["reachable from this harness -- it is covered in txvalidation_tests.cpp."], + + ["CP2PKH invalid: signature from the wrong key (correct pubkey pushed)"], + [[["e814a0b9ffca0211cd0d607e43cae079d5ac1baac8994f15018d5c5651324d8e", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR DUP HASH160 0x14 0x5e9a45bba03b45737b421c38366b590a89be80fd EQUALVERIFY CHECKSIG"]], + "01000000018e4d3251565c8d01154f99c8aa1bacd579e0ca437e600dcd1102caffb9a014e8000000006a473044022058a1e91ce4978c8d235c61e6783094c9bb29a2d274590f1edcd920fcaa369c4502202c0dd847d5deab020ca5f02523974dd44d815715551c5295bdf97dd76f04443f012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b1ffffffff0100000000000000000000000000", ""], + + ["CP2PKH invalid: wrong pubkey pushed (does not match embedded hash)"], + [[["e814a0b9ffca0211cd0d607e43cae079d5ac1baac8994f15018d5c5651324d8e", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR DUP HASH160 0x14 0x5e9a45bba03b45737b421c38366b590a89be80fd EQUALVERIFY CHECKSIG"]], + "01000000018e4d3251565c8d01154f99c8aa1bacd579e0ca437e600dcd1102caffb9a014e8000000006a473044022058a1e91ce4978c8d235c61e6783094c9bb29a2d274590f1edcd920fcaa369c4502202c0dd847d5deab020ca5f02523974dd44d815715551c5295bdf97dd76f04443f01210233115655789d58c0b3d1660df65e44e652eb620fb5ee06939a9513249e4aa8e7ffffffff0100000000000000000000000000", ""], + + ["CP2PKH invalid: color identifier payload is all-zero (rejected by EvalScript itself, not just consensus)"], + [[["b51a255d9d383c17cb958726e520415b045e2fba73ac4194fd85f28a9c6b543f", 0, "0x21 0xc10000000000000000000000000000000000000000000000000000000000000000 COLOR DUP HASH160 0x14 0x5e9a45bba03b45737b421c38366b590a89be80fd EQUALVERIFY CHECKSIG"]], + "01000000013f546b9c8af285fd9441ac73ba2f5e045b4120e5268795cb173c389d5d251ab5000000006a4730440220231f650579cbc80e180959911b3acaa756c264f6abccfdce4ff864bcb8d01f5f02204b78cab66ab4948cd6be3f60047c0db17f9fdd2c2104d2742c12af7bf9be6241012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b1ffffffff0100000000000000000000000000", ""], + + ["CP2SH(P2PKH) invalid: redeem script does not hash to the embedded scripthash"], + [[["30be58e04d2818fabc4a7e8cffdf22e3d5ed8d421e63d64b20fa84458116972f", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR HASH160 0x14 0x21e7adb7d667d8bf4a2db3b1107fecf74269a96b EQUAL"]], + "01000000012f9716814584fa204bd6631e428dedd5e322dfff8c7e4abcfa18284de058be30000000008447304402205a07d3acd38e6f4151cbb4249e8116115fae3dd859812c94ad8edf9c1a8fa90e0220052f2620585a81c5e1954204abe0d21f646b492fdaea259d583994e69d9ff56101210233115655789d58c0b3d1660df65e44e652eb620fb5ee06939a9513249e4aa8e71976a91433b0afdd23a1c43c4337bfbaae34b3fe0db939e088acffffffff0100000000000000000000000000", ""], + + ["P2SH(CP2PKH) invalid: OP_COLOR is unexpected inside a plain (uncolored) P2SH redeem script"], + [[["d71550dad4de82aef0e3c4c594d8e066c532a7007899ff4c78ea930f9009f9fc", 0, "HASH160 0x14 0xf4eca5b0452c281be60d90d1c36c182f39551c66 EQUAL"]], + "0100000001fcf909900f93ea784cff997800a732c566e0d894c5c4e3f0ae82ded4da5015d700000000a6463043021f72838cc7bc2de3968bbdaf157c7a3da40a6585fb837469957d6218a58c8b160220673212a09c44d9bbb0c1b654ed968eebc4da72ff6be0bcdcd6d6ddae1892d7c7012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b13c21c111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113bc76a9145e9a45bba03b45737b421c38366b590a89be80fd88acffffffff0100000000000000000000000000", ""], + + ["The following two scripts are proven ACCEPTED by the raw interpreter in"], + ["script_risky_combinations_tests.cpp (risk_opcolor_stack_manipulation and"], + ["risk_opcolor_with_timelock) and rejected only by CheckColorIdentifierValidity"], + ["(consensus layer, see txvalidation_tests.cpp) -- a check this VerifyScript-only"], + ["harness cannot reach. To still exercise these exact script shapes here, the"], + ["signature is deliberately wrong, so the failure below is EVAL_FALSE (a"], + ["cryptographic signature mismatch), NOT proof that OP_COLOR's template"], + ["restriction is enforced at this layer -- it is not."], + + ["SWAP-derived OP_COLOR, wrong signature"], + [[["6b3fa0499cd96b34ae40da7343cef8ff2ea61e1d382b2557c748fcd1658502c7", 0, "0x21 0xc22d62163d58906df7daa4aa14bbd74ef624333c074f922faa437ddaa538869ef1 SWAP COLOR DROP DUP HASH160 0x14 0xbe803c9776fc0302352fcff912c3e6de07d8deee EQUALVERIFY CHECKSIG"]], + "0100000001c7028565d1fc48c757252b381d1ea62efff8ce4373da40ae346bd99c49a03f6b000000008c473044022015138b446d133d86bc05f5365e2895de59d73413334ac670c8e553cb1f5dcb480220276597f1cd19fb4e7c8c7ee4809e95b1f25ef2cf38da83d1239520ad8c937eaf012102e219573f570f3f61f8be3bbaeb1408b3691cfaedf489b5499177553419528b0921c1107242fc3c7a88276516ae8987cfe9d39e204179b15e826dc511799418ba2015ffffffff0100000000000000000000000000", ""], + + ["OP_COLOR + CHECKLOCKTIMEVERIFY, wrong signature (locktime itself is satisfied)"], + [[["decb42c916d02a34fdba9a8e9b295aacf0423c1deee3babc69613a5ff4acb679", 0, "0x21 0xc13101b19114d1456c945f627fc6ca90a87cd25ff557c8b23ce981492f7ef23037 COLOR 0x03 0x20a107 CHECKLOCKTIMEVERIFY DROP DUP HASH160 0x14 0xbe803c9776fc0302352fcff912c3e6de07d8deee EQUALVERIFY CHECKSIG"]], + "010000000179b6acf45f3a6169bcbae3ee1d3c42f0ac5a299b8e9abafd342ad016c942cbde000000006a473044022076f7020243dcc33793f95a9cd4b0c1aed30e393e7aad88f0928db2e32735225002201cfec9c67df0359229231085d62a0d3373ebf0e04fca833561543152f2ba1252012102e219573f570f3f61f8be3bbaeb1408b3691cfaedf489b5499177553419528b09000000000100000000000000000020a10700", ""], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] \ No newline at end of file diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 08e555e23..0c6f91846 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -447,6 +447,26 @@ [[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], "010000000111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", ""], + ["Colored coin spends (OP_COLOR): CP2PKH and CP2SH(P2PKH), ECDSA and Schnorr"], + ["CP2SH cases use flags=\"CP2SH_COLORED\" so the redeem script's own CHECKSIG"], + ["is genuinely evaluated, not just the OP_HASH160(redeemScript) preimage check."], + + ["CP2PKH valid ECDSA"], + [[["e814a0b9ffca0211cd0d607e43cae079d5ac1baac8994f15018d5c5651324d8e", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR DUP HASH160 0x14 0x5e9a45bba03b45737b421c38366b590a89be80fd EQUALVERIFY CHECKSIG"]], + "01000000018e4d3251565c8d01154f99c8aa1bacd579e0ca437e600dcd1102caffb9a014e8000000006a47304402204ff979d5770f4ad6a07be099e2748760540b27a430ff529fad4a9254696dc20b022048c565d237518686e47d8d7d35bbf000fb73eec8fec0445b3dc6fa923717ab05012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b1ffffffff0100000000000000000000000000", ""], + + ["CP2PKH valid SCHNORR"], + [[["e814a0b9ffca0211cd0d607e43cae079d5ac1baac8994f15018d5c5651324d8e", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR DUP HASH160 0x14 0x5e9a45bba03b45737b421c38366b590a89be80fd EQUALVERIFY CHECKSIG"]], + "01000000018e4d3251565c8d01154f99c8aa1bacd579e0ca437e600dcd1102caffb9a014e800000000644102efa7e8cb06e4ad2b8c36f71efb521b271377a4e4e26cb91b5f6e0794dd35ba25ce09c676c2bd91ea407ec696aaf4c79e367c9cbd205bb0c36a08d74ff441b7012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b1ffffffff0100000000000000000000000000", ""], + + ["CP2SH(P2PKH) valid ECDSA"], + [[["30be58e04d2818fabc4a7e8cffdf22e3d5ed8d421e63d64b20fa84458116972f", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR HASH160 0x14 0x21e7adb7d667d8bf4a2db3b1107fecf74269a96b EQUAL"]], + "01000000012f9716814584fa204bd6631e428dedd5e322dfff8c7e4abcfa18284de058be30000000008447304402202410a4885f8d131e6e8ceddc56e69d120278ce32cc743eb29cdff4ab7681c7cd022021bd0f85cb4382befc2fb114e2b2da51173977e50c3917729599bb4fa9f30106012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b11976a9145e9a45bba03b45737b421c38366b590a89be80fd88acffffffff0100000000000000000000000000", "CP2SH_COLORED"], + + ["CP2SH(P2PKH) valid SCHNORR"], + [[["30be58e04d2818fabc4a7e8cffdf22e3d5ed8d421e63d64b20fa84458116972f", 0, "0x21 0xc111bc115c0226329c9a35a7fee1d29c1f5248e0f7841d57829b50278263149113 COLOR HASH160 0x14 0x21e7adb7d667d8bf4a2db3b1107fecf74269a96b EQUAL"]], + "01000000012f9716814584fa204bd6631e428dedd5e322dfff8c7e4abcfa18284de058be30000000007e414dba95fc8d1db75137a3d7b658e359da1492d0fcd2521870bb7bc51a6ff440cf38f3ceb62cb130f52c5366d1d02ef67dab2f59d4fe03ec7136062302bf8b75e6012102fc4672572cb02c5ebb43623c3cc5b213d16bfcee45aab502aaa2d92afd2775b11976a9145e9a45bba03b45737b421c38366b590a89be80fd88acffffffff0100000000000000000000000000", "CP2SH_COLORED"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] \ No newline at end of file diff --git a/src/test/script_risky_combinations_tests.cpp b/src/test/script_risky_combinations_tests.cpp new file mode 100644 index 000000000..763007b02 --- /dev/null +++ b/src/test/script_risky_combinations_tests.cpp @@ -0,0 +1,446 @@ +// Copyright (c) 2019-2020 Chaintope Inc. +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Verifies, with runnable proofs, the "risky but permitted" operator +// combinations documented for the Tapyrus script interpreter: +// +// 1. Zero-of-N CHECKMULTISIG is a trivial anyone-can-spend pass. +// 2. OP_COLOR accepts a color id assembled by prior stack manipulation, +// not just an immediate push -- closed off only at the consensus layer +// (CheckColorIdentifierValidity), not by the interpreter itself. +// 3. OP_CHECKDATASIG(VERIFY) has no built-in binding to the spending +// transaction, so a (signature, message) pair replays across unrelated +// transactions -- unlike OP_CHECKSIG. +// 4. OP_CHECKSIG calls made independently (outside OP_CHECKMULTISIG) can +// mix ECDSA and Schnorr freely; only CHECKMULTISIG enforces uniformity. +// 5. OP_CHECKLOCKTIMEVERIFY can be combined directly with OP_COLOR in a +// single scriptPubKey at the interpreter level, even though the design +// intends CLTV/CSV to live only inside a CP2SH redeem script. +// +// Each finding is exercised with multiple cases: the risky behavior itself, +// plus a control/contrast case showing where the behavior does NOT apply, +// so a future change that narrows (or accidentally widens) these behaviors +// will be caught here. + +#include +#include +#include +#include