From fdb43df23eb507d1c79b7ae9cc2e61c3e52c8988 Mon Sep 17 00:00:00 2001 From: Jonas Nick Date: Sat, 28 May 2016 19:22:13 +0200 Subject: [PATCH] [qa] Add GetTransactionSigOpCost unit tests --- src/main.h | 8 ++ src/test/sigopcount_tests.cpp | 177 ++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+) diff --git a/src/main.h b/src/main.h index 84a6044bc..2ffe5770d 100644 --- a/src/main.h +++ b/src/main.h @@ -333,6 +333,14 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx); */ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs); +/** + * Compute total signature operation cost of a transaction. + * @param[in] tx Transaction for which we are computing the cost + * @param[in] inputs Map of previous transactions that have outputs we're spending + * @param[out] flags Script verification flags + * @return Total signature operation cost of tx + */ +int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, int flags); /** * Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts) diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index a207fd921..8dea38833 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "main.h" #include "pubkey.h" #include "key.h" #include "script/script.h" @@ -64,4 +65,180 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount) BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(scriptSig2), 3U); } +/** + * Verifies script execution of the zeroth scriptPubKey of tx output and + * zeroth scriptSig and witness of tx input. + */ +ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction& input, int flags) +{ + ScriptError error; + CTransaction inputi(input); + bool ret = VerifyScript(inputi.vin[0].scriptSig, output.vout[0].scriptPubKey, inputi.wit.vtxinwit.size() > 0 ? &inputi.wit.vtxinwit[0].scriptWitness : NULL, flags, TransactionSignatureChecker(&inputi, 0, output.vout[0].nValue), &error); + BOOST_CHECK((ret == true) == (error == SCRIPT_ERR_OK)); + + return error; +} + +/** + * Builds a creationTx from scriptPubKey and a spendingTx from scriptSig + * and witness such that spendingTx spends output zero of creationTx. + * Also inserts creationTx's output into the coins view. + */ +void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableTransaction& creationTx, const CScript& scriptPubKey, const CScript& scriptSig, const CTxinWitness& witness) +{ + creationTx.nVersion = 1; + creationTx.vin.resize(1); + creationTx.vin[0].prevout.SetNull(); + creationTx.vin[0].scriptSig = CScript(); + creationTx.wit.vtxinwit.resize(1); + creationTx.vout.resize(1); + creationTx.vout[0].nValue = 1; + creationTx.vout[0].scriptPubKey = scriptPubKey; + + spendingTx.nVersion = 1; + spendingTx.vin.resize(1); + spendingTx.vin[0].prevout.hash = creationTx.GetHash(); + spendingTx.vin[0].prevout.n = 0; + spendingTx.vin[0].scriptSig = scriptSig; + spendingTx.wit.vtxinwit.resize(1); + spendingTx.wit.vtxinwit[0] = witness; + spendingTx.vout.resize(1); + spendingTx.vout[0].nValue = 1; + spendingTx.vout[0].scriptPubKey = CScript(); + + coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost) +{ + // Transaction creates outputs + 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; + key.MakeNewKey(true); + CPubKey pubkey = key.GetPubKey(); + // Default flags + int 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, CTxinWitness()); + // 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(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } + + // Multisig nested in P2SH + { + CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); + CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CTxinWitness()); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } + + // P2WPKH witness program + { + CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; + CScript scriptPubKey = GetScriptForWitness(p2pk); + CScript scriptSig = CScript(); + CTxinWitness witness; + CScriptWitness scriptWitness; + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(0)); + witness.scriptWitness = scriptWitness; + + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness); + 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(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, witness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + scriptPubKey[0] = 0x00; + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness); + + // The witness of a coinbase transaction is not taken into account. + spendingTx.vin[0].prevout.SetNull(); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + } + + // P2WPKH nested in P2SH + { + CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG; + CScript scriptSig = GetScriptForWitness(p2pk); + CScript scriptPubKey = GetScriptForDestination(CScriptID(scriptSig)); + scriptSig = CScript() << ToByteVector(scriptSig); + CTxinWitness witness; + CScriptWitness scriptWitness; + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(0)); + witness.scriptWitness = scriptWitness; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + } + + // P2WSH witness program + { + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForWitness(witnessScript); + CScript scriptSig = CScript(); + CTxinWitness witness; + CScriptWitness scriptWitness; + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(witnessScript.begin(), witnessScript.end())); + witness.scriptWitness = scriptWitness; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } + + // P2WSH nested in P2SH + { + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript redeemScript = GetScriptForWitness(witnessScript); + CScript scriptPubKey = GetScriptForDestination(CScriptID(redeemScript)); + CScript scriptSig = CScript() << ToByteVector(redeemScript); + CTxinWitness witness; + CScriptWitness scriptWitness; + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(0)); + scriptWitness.stack.push_back(vector(witnessScript.begin(), witnessScript.end())); + witness.scriptWitness = scriptWitness; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); + assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + } +} + BOOST_AUTO_TEST_SUITE_END()