Skip to content

Commit f1465f9

Browse files
committed
Merge bitcoin/bitcoin#35038: bench: add script verification benchmark for P2TR script-path spends
fbffe8a64a96ebd46111157fa5381d7c54fe9aea bench: improve `VerifyNestedIfScript` benchmark precision (make stack clearing untimed) (David Gumberg) 616ee6fe74e1b9715620fd3d538b79e1a8651dcb bench: add script verification benchmark for P2TR script-path spends (Sebastian Falbesoner) Pull request description: Similarly as #34472 already did for key-path spends, this PR adds a benchmark for P2TR script-path spends. So far we don't have a benchmark on master yet that exercises the verification of taproot commitments ([`VerifyTaprootCommitment`](https://github.com/bitcoin/bitcoin/blob/141fbe4d530b51345e62dee1348e82d8a0406ffc/src/script/interpreter.cpp#L1903)). Note that the tapscript leaf intentionally includes a single OP_CHECKSIG as it likely reflects the real world best. Spending tapscript leafs without any signature checks don't make much sense (they could be trivially tampered with and thus stolen by miners), and doing more than one signature check seems the exception rather than the rule. The primary motivation for this PR is to evaluate how potential secp256k1 changes in pubkey tweaking (e.g. [#1843](bitcoin-core/secp256k1#1843)) may impact script verification performance. ACKs for top commit: davidgumberg: reACK fbffe8a l0rinc: ACK fbffe8a64a96ebd46111157fa5381d7c54fe9aea sedited: ACK fbffe8a64a96ebd46111157fa5381d7c54fe9aea Tree-SHA512: 6fa1f2c336d6332b4f2d22173279ee29ad3ec5e5431109913a6978fef32e22a34d3247729ac9092bfdbbcd8f9dfcad8da50bc4ede2cb7efc1f93d6a744ddf41b
2 parents f4db98f + 21bbe6b commit f1465f9

1 file changed

Lines changed: 39 additions & 15 deletions

File tree

src/bench/verify_script.cpp

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,20 @@
2222

2323
enum class ScriptType {
2424
P2WPKH, // segwitv0, witness-pubkey-hash (ECDSA signature)
25-
P2TR, // segwitv1, taproot key-path spend (Schnorr signature)
25+
P2TR_KeyPath, // segwitv1, taproot key-path spend (Schnorr signature)
26+
P2TR_ScriptPath, // segwitv1, taproot script-path spend (Tapscript leaf with a single OP_CHECKSIG)
2627
};
2728

29+
static size_t ExpectedWitnessStackSize(ScriptType script_type)
30+
{
31+
switch (script_type) {
32+
case ScriptType::P2WPKH: return 2; // [pubkey, signature]
33+
case ScriptType::P2TR_KeyPath: return 1; // [signature]
34+
case ScriptType::P2TR_ScriptPath: return 3; // [signature, tapscript, control block]
35+
} // no default case, so the compiler can warn about missing cases
36+
assert(false);
37+
}
38+
2839
// Microbenchmark for verification of standard scripts.
2940
static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
3041
{
@@ -34,6 +45,7 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
3445
CKey privkey;
3546
privkey.Set(uint256::ONE.begin(), uint256::ONE.end(), /*fCompressedIn=*/true);
3647
CPubKey pubkey = privkey.GetPubKey();
48+
XOnlyPubKey xonly_pubkey{pubkey};
3749
CKeyID key_id = pubkey.GetID();
3850

3951
FlatSigningProvider keystore;
@@ -44,7 +56,14 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
4456
const auto dest{[&]() -> CTxDestination {
4557
switch (script_type) {
4658
case ScriptType::P2WPKH: return WitnessV0KeyHash(pubkey);
47-
case ScriptType::P2TR: return WitnessV1Taproot(XOnlyPubKey{pubkey});
59+
case ScriptType::P2TR_KeyPath: return WitnessV1Taproot(xonly_pubkey);
60+
case ScriptType::P2TR_ScriptPath:
61+
TaprootBuilder builder;
62+
builder.Add(0, CScript() << ToByteVector(xonly_pubkey) << OP_CHECKSIG, TAPROOT_LEAF_TAPSCRIPT);
63+
builder.Finalize(XOnlyPubKey::NUMS_H); // effectively unspendable key-path
64+
const auto output{builder.GetOutput()};
65+
keystore.tr_trees.emplace(output, builder);
66+
return output;
4867
} // no default case, so the compiler can warn about missing cases
4968
assert(false);
5069
}()};
@@ -54,16 +73,18 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
5473
// Sign spending transaction, precompute transaction data
5574
PrecomputedTransactionData txdata;
5675
{
57-
std::map<COutPoint, Coin> coins;
58-
coins[txSpend.vin[0].prevout] = Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false);
76+
const std::map<COutPoint, Coin> coins{
77+
{txSpend.vin[0].prevout, Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false)}
78+
};
5979
std::map<int, bilingual_str> input_errors;
60-
bool complete = SignTransaction(txSpend, &keystore, coins, SIGHASH_ALL, input_errors);
61-
assert(complete);
80+
assert(SignTransaction(txSpend, &keystore, coins, SIGHASH_ALL, input_errors));
81+
// Weak sanity check on witness data to ensure we produced the intended spending type
82+
assert(txSpend.vin[0].scriptWitness.stack.size() == ExpectedWitnessStackSize(script_type));
6283
txdata.Init(txSpend, /*spent_outputs=*/{txCredit.vout[0]});
6384
}
6485

6586
// Benchmark.
66-
bench.run([&] {
87+
bench.unit("script").run([&] {
6788
ScriptError err;
6889
bool success = VerifyScript(
6990
txSpend.vin[0].scriptSig,
@@ -78,7 +99,8 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
7899
}
79100

80101
static void VerifyScriptP2WPKH(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2WPKH); }
81-
static void VerifyScriptP2TR(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR); }
102+
static void VerifyScriptP2TR_KeyPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_KeyPath); }
103+
static void VerifyScriptP2TR_ScriptPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_ScriptPath); }
82104

83105
static void VerifyNestedIfScript(benchmark::Bench& bench)
84106
{
@@ -93,14 +115,16 @@ static void VerifyNestedIfScript(benchmark::Bench& bench)
93115
for (int i = 0; i < 100; ++i) {
94116
script << OP_ENDIF;
95117
}
96-
bench.run([&] {
97-
auto stack_copy = stack;
98-
ScriptError error;
99-
bool ret = EvalScript(stack_copy, script, 0, BaseSignatureChecker(), SigVersion::BASE, &error);
100-
assert(ret);
101-
});
118+
bench.unit("script").epochIterations(1)
119+
.setup([&] { stack.clear(); })
120+
.run([&] {
121+
ScriptError error;
122+
const bool ret{EvalScript(stack, script, /*flags=*/0, BaseSignatureChecker(), SigVersion::BASE, &error)};
123+
assert(ret && error == SCRIPT_ERR_OK);
124+
});
102125
}
103126

104127
BENCHMARK(VerifyScriptP2WPKH);
105-
BENCHMARK(VerifyScriptP2TR);
128+
BENCHMARK(VerifyScriptP2TR_KeyPath);
129+
BENCHMARK(VerifyScriptP2TR_ScriptPath);
106130
BENCHMARK(VerifyNestedIfScript);

0 commit comments

Comments
 (0)