bench: add script verification benchmark for P2TR script-path spends
What changed, and why it matters
This commit only adds a new performance benchmark to Bitcoin Core. It measures how long it takes to verify a specific kind of Taproot transaction (a P2TR script-path spend) and makes minor cleanups to existing benchmark code. It does not change any production wallet, consensus, or networking code, so it cannot directly affect user funds or network security.
No security action required. Treat as a normal test/ benchmark addition.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change is confined to src/bench/verify_script.cpp. It extends the script-verification benchmark to cover P2TR script-path spends (a Tapscript leaf containing a single OP_CHECKSIG), renames the existing P2TR benchmark to P2TR_KeyPath for clarity, adds a helper for expected witness stack sizes, sets the benchmark unit to “script”, and refactors some local variable declarations. No consensus, validation, wallet, or P2P logic is modified.
Changed components
src/bench/verify_script.cppInspect captured patch +32 / −9
diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp
index b0ef4988..ac636ce8 100644
--- a/src/bench/verify_script.cpp
+++ b/src/bench/verify_script.cpp
@@ -22,9 +22,20 @@
enum class ScriptType {
P2WPKH, // segwitv0, witness-pubkey-hash (ECDSA signature)
- P2TR, // segwitv1, taproot key-path spend (Schnorr signature)
+ P2TR_KeyPath, // segwitv1, taproot key-path spend (Schnorr signature)
+ P2TR_ScriptPath, // segwitv1, taproot script-path spend (Tapscript leaf with a single OP_CHECKSIG)
};
+static size_t ExpectedWitnessStackSize(ScriptType script_type)
+{
+ switch (script_type) {
+ case ScriptType::P2WPKH: return 2; // [pubkey, signature]
+ case ScriptType::P2TR_KeyPath: return 1; // [signature]
+ case ScriptType::P2TR_ScriptPath: return 3; // [signature, tapscript, control block]
+ } // no default case, so the compiler can warn about missing cases
+ assert(false);
+}
+
// Microbenchmark for verification of standard scripts.
static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
{
@@ -34,6 +45,7 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
CKey privkey;
privkey.Set(uint256::ONE.begin(), uint256::ONE.end(), /*fCompressedIn=*/true);
CPubKey pubkey = privkey.GetPubKey();
+ XOnlyPubKey xonly_pubkey{pubkey};
CKeyID key_id = pubkey.GetID();
FlatSigningProvider keystore;
@@ -44,7 +56,14 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
const auto dest{[&]() -> CTxDestination {
switch (script_type) {
case ScriptType::P2WPKH: return WitnessV0KeyHash(pubkey);
- case ScriptType::P2TR: return WitnessV1Taproot(XOnlyPubKey{pubkey});
+ case ScriptType::P2TR_KeyPath: return WitnessV1Taproot(xonly_pubkey);
+ case ScriptType::P2TR_ScriptPath:
+ TaprootBuilder builder;
+ builder.Add(0, CScript() << ToByteVector(xonly_pubkey) << OP_CHECKSIG, TAPROOT_LEAF_TAPSCRIPT);
+ builder.Finalize(XOnlyPubKey::NUMS_H); // effectively unspendable key-path
+ const auto output{builder.GetOutput()};
+ keystore.tr_trees.emplace(output, builder);
+ return output;
} // no default case, so the compiler can warn about missing cases
assert(false);
}()};
@@ -54,16 +73,18 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
// Sign spending transaction, precompute transaction data
PrecomputedTransactionData txdata;
{
- std::map<COutPoint, Coin> coins;
- coins[txSpend.vin[0].prevout] = Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false);
+ const std::map<COutPoint, Coin> coins{
+ {txSpend.vin[0].prevout, Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false)}
+ };
std::map<int, bilingual_str> input_errors;
- bool complete = SignTransaction(txSpend, &keystore, coins, SIGHASH_ALL, input_errors);
- assert(complete);
+ assert(SignTransaction(txSpend, &keystore, coins, SIGHASH_ALL, input_errors));
+ // Weak sanity check on witness data to ensure we produced the intended spending type
+ assert(txSpend.vin[0].scriptWitness.stack.size() == ExpectedWitnessStackSize(script_type));
txdata.Init(txSpend, /*spent_outputs=*/{txCredit.vout[0]});
}
// Benchmark.
- bench.run([&] {
+ bench.unit("script").run([&] {
ScriptError err;
bool success = VerifyScript(
txSpend.vin[0].scriptSig,
@@ -78,7 +99,8 @@ static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type)
}
static void VerifyScriptP2WPKH(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2WPKH); }
-static void VerifyScriptP2TR(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR); }
+static void VerifyScriptP2TR_KeyPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_KeyPath); }
+static void VerifyScriptP2TR_ScriptPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_ScriptPath); }
static void VerifyNestedIfScript(benchmark::Bench& bench)
{
@@ -102,5 +124,6 @@ static void VerifyNestedIfScript(benchmark::Bench& bench)
}
BENCHMARK(VerifyScriptP2WPKH);
-BENCHMARK(VerifyScriptP2TR);
+BENCHMARK(VerifyScriptP2TR_KeyPath);
+BENCHMARK(VerifyScriptP2TR_ScriptPath);
BENCHMARK(VerifyNestedIfScript);
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.