Skip to content

Commit fdb43df

Browse files
jonasnicksipa
authored andcommitted
[qa] Add GetTransactionSigOpCost unit tests
1 parent d846e02 commit fdb43df

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

src/main.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ unsigned int GetLegacySigOpCount(const CTransaction& tx);
333333
*/
334334
unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& mapInputs);
335335

336+
/**
337+
* Compute total signature operation cost of a transaction.
338+
* @param[in] tx Transaction for which we are computing the cost
339+
* @param[in] inputs Map of previous transactions that have outputs we're spending
340+
* @param[out] flags Script verification flags
341+
* @return Total signature operation cost of tx
342+
*/
343+
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, int flags);
336344

337345
/**
338346
* Check whether all inputs of this transaction are valid (no double spends, scripts & sigs, amounts)

src/test/sigopcount_tests.cpp

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include "main.h"
56
#include "pubkey.h"
67
#include "key.h"
78
#include "script/script.h"
@@ -64,4 +65,180 @@ BOOST_AUTO_TEST_CASE(GetSigOpCount)
6465
BOOST_CHECK_EQUAL(p2sh.GetSigOpCount(scriptSig2), 3U);
6566
}
6667

68+
/**
69+
* Verifies script execution of the zeroth scriptPubKey of tx output and
70+
* zeroth scriptSig and witness of tx input.
71+
*/
72+
ScriptError VerifyWithFlag(const CTransaction& output, const CMutableTransaction& input, int flags)
73+
{
74+
ScriptError error;
75+
CTransaction inputi(input);
76+
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);
77+
BOOST_CHECK((ret == true) == (error == SCRIPT_ERR_OK));
78+
79+
return error;
80+
}
81+
82+
/**
83+
* Builds a creationTx from scriptPubKey and a spendingTx from scriptSig
84+
* and witness such that spendingTx spends output zero of creationTx.
85+
* Also inserts creationTx's output into the coins view.
86+
*/
87+
void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CMutableTransaction& creationTx, const CScript& scriptPubKey, const CScript& scriptSig, const CTxinWitness& witness)
88+
{
89+
creationTx.nVersion = 1;
90+
creationTx.vin.resize(1);
91+
creationTx.vin[0].prevout.SetNull();
92+
creationTx.vin[0].scriptSig = CScript();
93+
creationTx.wit.vtxinwit.resize(1);
94+
creationTx.vout.resize(1);
95+
creationTx.vout[0].nValue = 1;
96+
creationTx.vout[0].scriptPubKey = scriptPubKey;
97+
98+
spendingTx.nVersion = 1;
99+
spendingTx.vin.resize(1);
100+
spendingTx.vin[0].prevout.hash = creationTx.GetHash();
101+
spendingTx.vin[0].prevout.n = 0;
102+
spendingTx.vin[0].scriptSig = scriptSig;
103+
spendingTx.wit.vtxinwit.resize(1);
104+
spendingTx.wit.vtxinwit[0] = witness;
105+
spendingTx.vout.resize(1);
106+
spendingTx.vout[0].nValue = 1;
107+
spendingTx.vout[0].scriptPubKey = CScript();
108+
109+
coins.ModifyCoins(creationTx.GetHash())->FromTx(creationTx, 0);
110+
}
111+
112+
BOOST_AUTO_TEST_CASE(GetTxSigOpCost)
113+
{
114+
// Transaction creates outputs
115+
CMutableTransaction creationTx;
116+
// Transaction that spends outputs and whose
117+
// sig op cost is going to be tested
118+
CMutableTransaction spendingTx;
119+
120+
// Create utxo set
121+
CCoinsView coinsDummy;
122+
CCoinsViewCache coins(&coinsDummy);
123+
// Create key
124+
CKey key;
125+
key.MakeNewKey(true);
126+
CPubKey pubkey = key.GetPubKey();
127+
// Default flags
128+
int flags = SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH;
129+
130+
// Multisig script (legacy counting)
131+
{
132+
CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
133+
// Do not use a valid signature to avoid using wallet operations.
134+
CScript scriptSig = CScript() << OP_0 << OP_0;
135+
136+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CTxinWitness());
137+
// Legacy counting only includes signature operations in scriptSigs and scriptPubKeys
138+
// of a transaction and does not take the actual executed sig operations into account.
139+
// spendingTx in itself does not contain a signature operation.
140+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
141+
// creationTx contains two signature operations in its scriptPubKey, but legacy counting
142+
// is not accurate.
143+
assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR);
144+
// Sanity check: script verification fails because of an invalid signature.
145+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
146+
}
147+
148+
// Multisig nested in P2SH
149+
{
150+
CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
151+
CScript scriptPubKey = GetScriptForDestination(CScriptID(redeemScript));
152+
CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript);
153+
154+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CTxinWitness());
155+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR);
156+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
157+
}
158+
159+
// P2WPKH witness program
160+
{
161+
CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
162+
CScript scriptPubKey = GetScriptForWitness(p2pk);
163+
CScript scriptSig = CScript();
164+
CTxinWitness witness;
165+
CScriptWitness scriptWitness;
166+
scriptWitness.stack.push_back(vector<unsigned char>(0));
167+
scriptWitness.stack.push_back(vector<unsigned char>(0));
168+
witness.scriptWitness = scriptWitness;
169+
170+
171+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
172+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1);
173+
// No signature operations if we don't verify the witness.
174+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
175+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY);
176+
177+
// The sig op cost for witness version != 0 is zero.
178+
assert(scriptPubKey[0] == 0x00);
179+
scriptPubKey[0] = 0x51;
180+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
181+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
182+
scriptPubKey[0] = 0x00;
183+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
184+
185+
// The witness of a coinbase transaction is not taken into account.
186+
spendingTx.vin[0].prevout.SetNull();
187+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0);
188+
}
189+
190+
// P2WPKH nested in P2SH
191+
{
192+
CScript p2pk = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
193+
CScript scriptSig = GetScriptForWitness(p2pk);
194+
CScript scriptPubKey = GetScriptForDestination(CScriptID(scriptSig));
195+
scriptSig = CScript() << ToByteVector(scriptSig);
196+
CTxinWitness witness;
197+
CScriptWitness scriptWitness;
198+
scriptWitness.stack.push_back(vector<unsigned char>(0));
199+
scriptWitness.stack.push_back(vector<unsigned char>(0));
200+
witness.scriptWitness = scriptWitness;
201+
202+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
203+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1);
204+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY);
205+
}
206+
207+
// P2WSH witness program
208+
{
209+
CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
210+
CScript scriptPubKey = GetScriptForWitness(witnessScript);
211+
CScript scriptSig = CScript();
212+
CTxinWitness witness;
213+
CScriptWitness scriptWitness;
214+
scriptWitness.stack.push_back(vector<unsigned char>(0));
215+
scriptWitness.stack.push_back(vector<unsigned char>(0));
216+
scriptWitness.stack.push_back(vector<unsigned char>(witnessScript.begin(), witnessScript.end()));
217+
witness.scriptWitness = scriptWitness;
218+
219+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
220+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2);
221+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0);
222+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
223+
}
224+
225+
// P2WSH nested in P2SH
226+
{
227+
CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY;
228+
CScript redeemScript = GetScriptForWitness(witnessScript);
229+
CScript scriptPubKey = GetScriptForDestination(CScriptID(redeemScript));
230+
CScript scriptSig = CScript() << ToByteVector(redeemScript);
231+
CTxinWitness witness;
232+
CScriptWitness scriptWitness;
233+
scriptWitness.stack.push_back(vector<unsigned char>(0));
234+
scriptWitness.stack.push_back(vector<unsigned char>(0));
235+
scriptWitness.stack.push_back(vector<unsigned char>(witnessScript.begin(), witnessScript.end()));
236+
witness.scriptWitness = scriptWitness;
237+
238+
BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, witness);
239+
assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2);
240+
assert(VerifyWithFlag(creationTx, spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY);
241+
}
242+
}
243+
67244
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)