From 616ee6fe74e1b9715620fd3d538b79e1a8651dcb Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Thu, 9 Apr 2026 01:57:51 +0200 Subject: [PATCH 1/2] bench: add script verification benchmark for P2TR script-path spends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To reflect the likely most common real-world scenario, a single OP_CHECKSIG is contained in the Tapscript leaf. While touching this benchmark, also set the operation unit to "script" and do some minor refactorings to deduplicate code and improve readability. Co-authored-by: David Gumberg Co-authored-by: Lőrinc --- src/bench/verify_script.cpp | 41 +++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index b0ef4988ce1..ac636ce86ad 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 coins; - coins[txSpend.vin[0].prevout] = Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false); + const std::map coins{ + {txSpend.vin[0].prevout, Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false)} + }; std::map 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); From fbffe8a64a96ebd46111157fa5381d7c54fe9aea Mon Sep 17 00:00:00 2001 From: David Gumberg Date: Mon, 13 Apr 2026 21:41:15 +0200 Subject: [PATCH 2/2] bench: improve `VerifyNestedIfScript` benchmark precision (make stack clearing untimed) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit on `master`: | ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 19,890.59 | 50,275.02 | 1.3% | 673,992.65 | 85,238.74 | 7.907 | 143,404.89 | 0.0% | 0.01 | `VerifyNestedIfScript` vs this commit: | ns/script | script/s | err% | ins/script | cyc/script | IPC | bra/script | miss% | total | benchmark |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- | 12,089.00 | 82,719.83 | 0.4% | 375,703.00 | 51,987.00 | 7.227 | 69,249.00 | 0.2% | 0.00 | `VerifyNestedIfScript` Co-authored-by: Lőrinc --- src/bench/verify_script.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/bench/verify_script.cpp b/src/bench/verify_script.cpp index ac636ce86ad..af254ae7783 100644 --- a/src/bench/verify_script.cpp +++ b/src/bench/verify_script.cpp @@ -115,12 +115,13 @@ static void VerifyNestedIfScript(benchmark::Bench& bench) for (int i = 0; i < 100; ++i) { script << OP_ENDIF; } - bench.run([&] { - auto stack_copy = stack; - ScriptError error; - bool ret = EvalScript(stack_copy, script, 0, BaseSignatureChecker(), SigVersion::BASE, &error); - assert(ret); - }); + bench.unit("script").epochIterations(1) + .setup([&] { stack.clear(); }) + .run([&] { + ScriptError error; + const bool ret{EvalScript(stack, script, /*flags=*/0, BaseSignatureChecker(), SigVersion::BASE, &error)}; + assert(ret && error == SCRIPT_ERR_OK); + }); } BENCHMARK(VerifyScriptP2WPKH);