diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index d3fec0f9357d..a36f8fe07c86 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -18,6 +18,8 @@ #include +static constexpr script_verify_flags STANDARD_SCRIPT_VERIFY_FLAGS{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + // Helpers: static std::vector Serialize(const CScript& s) @@ -26,6 +28,13 @@ Serialize(const CScript& s) return sSerialized; } +static CTransaction MakeCoinBase(const CMutableTransaction& tx) +{ + CMutableTransaction coinbase{tx}; + coinbase.vin[0].prevout.SetNull(); + return CTransaction{coinbase}; +} + BOOST_FIXTURE_TEST_SUITE(sigopcount_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(GetSigOpCount) @@ -107,128 +116,166 @@ static void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CM AddCoins(coins, CTransaction(creationTx), 0); } -BOOST_AUTO_TEST_CASE(GetTxSigOpCost) +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_Multisig) { - // Transaction creates outputs + // Multisig script (legacy counting) CMutableTransaction creationTx; - // Transaction that spends outputs and whose - // sig op cost is going to be tested CMutableTransaction spendingTx; - // Create utxo set CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); - // Create key CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - // Default flags - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; - // Multisig script (legacy counting) - { - CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - // Do not use a valid signature to avoid using wallet operations. - CScript scriptSig = CScript() << OP_0 << OP_0; - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); - // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys - // of a transaction and does not take the actual executed sig operations into account. - // spendingTx in itself does not contain a signature operation. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - // creationTx contains two signature operations in its scriptPubKey, but legacy counting - // is not accurate. - assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); - // Sanity check: script verification fails because of an invalid signature. - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + // Do not use a valid signature to avoid using wallet operations. + CScript scriptSig = CScript() << OP_0 << OP_0; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); + // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys + // of a transaction and does not take the actual executed sig operations into account. + // spendingTx in itself does not contain a signature operation. + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); + // Even though creationTx is a coinbase, its legacy sigops in scriptPubKey are counted + // (and legacy counting is not accurate). + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(creationTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); + // Sanity check: script verification fails because of an invalid signature. + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); +} +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_MultisigP2SH) +{ // Multisig nested in P2SH - { - CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); - CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + CMutableTransaction creationTx; + CMutableTransaction spendingTx; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); - // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0) == 0); - } + CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); + CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2 * WITNESS_SCALE_FACTOR); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); + + // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0), 0); + + // For coinbase transactions, GetTransactionSigOpCost only includes legacy sigops (no P2SH/witness). + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(MakeCoinBase(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) +{ // P2WPKH witness program - { - CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey)); - CScript scriptSig = CScript(); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - // No signature operations if we don't verify the witness. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); - - // The sig op cost for witness version != 0 is zero. - assert(scriptPubKey[0] == 0x00); - scriptPubKey[0] = 0x51; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - scriptPubKey[0] = 0x00; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - - // The witness of a coinbase transaction is not taken into account. - spendingTx.vin[0].prevout.SetNull(); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + + CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey)); + CScript scriptSig = CScript(); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 1); + // No signature operations if we don't verify the witness. + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_WITNESS), 0); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_EQUALVERIFY); + + // The sig op cost for witness version != 0 is zero. + BOOST_REQUIRE_EQUAL(scriptPubKey[0], OP_0); + scriptPubKey[0] = OP_1; + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); + scriptPubKey[0] = OP_0; + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + + // For coinbase transactions, GetTransactionSigOpCost only includes legacy sigops (no P2SH/witness). + BOOST_REQUIRE(!spendingTx.vin[0].scriptWitness.IsNull()); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(MakeCoinBase(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH) +{ // P2WPKH nested in P2SH - { - CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey)); - CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig)); - scriptSig = CScript() << ToByteVector(scriptSig); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey)); + CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig)); + scriptSig = CScript() << ToByteVector(scriptSig); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 1); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_EQUALVERIFY); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH) +{ // P2WSH witness program - { - CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); - CScript scriptSig = CScript(); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); + CScript scriptSig = CScript(); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_WITNESS), 0); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH_P2SH) +{ // P2WSH nested in P2SH - { - CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); - CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); - CScript scriptSig = CScript() << ToByteVector(redeemScript); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); + CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); + CScript scriptSig = CScript() << ToByteVector(redeemScript); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); } BOOST_AUTO_TEST_SUITE_END()