Skip to content

Commit 059d4d1

Browse files
jonasnicksipa
authored andcommitted
Add GetTransactionSigOpCost unit tests
1 parent f16067f commit 059d4d1

File tree

2 files changed

+184
-0
lines changed

2 files changed

+184
-0
lines changed

src/main.h

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

353+
/**
354+
* Compute total signature operation cost of a transaction.
355+
* @param[in] tx Transaction for which we are computing the cost
356+
* @param[in] inputs Map of previous transactions that have outputs we're spending
357+
* @param[out] flags Script verification flags
358+
* @return Total signature operation cost of tx
359+
*/
360+
int64_t GetTransactionSigOpCost(const CTransaction& tx, const CCoinsViewCache& inputs, int flags);
353361

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

src/test/sigopcount_tests.cpp

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

0 commit comments

Comments
 (0)