Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 148 additions & 101 deletions src/test/sigopcount_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include <boost/test/unit_test.hpp>

static constexpr script_verify_flags STANDARD_SCRIPT_VERIFY_FLAGS{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH};

// Helpers:
static std::vector<unsigned char>
Serialize(const CScript& s)
Expand All @@ -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)
Expand Down Expand Up @@ -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()
Loading