[qa] Add GetTransactionSigOpCost unit tests
This commit is contained in:
parent
d846e02372
commit
fdb43df23e
2 changed files with 185 additions and 0 deletions
|
@ -333,6 +333,14 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx);
|
||||||
*/
|
*/
|
||||||
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs);
|
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)
|
* Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Distributed under the MIT software license, see the accompanying
|
// Distributed under the MIT software license, see the accompanying
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "main.h"
|
||||||
#include "pubkey.h"
|
#include "pubkey.h"
|
||||||
#include "key.h"
|
#include "key.h"
|
||||||
#include "script/script.h"
|
#include "script/script.h"
|
||||||
|
@ -64,4 +65,180 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount)
|
||||||
BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(scriptSig2), 3U);
|
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<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(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<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(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<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(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<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(0));
|
||||||
|
scriptWitness.stack.push_back(vector<unsigned char>(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()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
Loading…
Reference in a new issue