verify_script.cpp
1 // Copyright (c) 2016-present The Bitcoin Core developers 2 // Distributed under the MIT software license, see the accompanying 3 // file COPYING or http://www.opensource.org/licenses/mit-license.php. 4 5 #include <addresstype.h> 6 #include <bench/bench.h> 7 #include <key.h> 8 #include <policy/policy.h> 9 #include <primitives/transaction.h> 10 #include <pubkey.h> 11 #include <script/interpreter.h> 12 #include <script/script.h> 13 #include <span.h> 14 #include <test/util/transaction_utils.h> 15 #include <uint256.h> 16 #include <util/translation.h> 17 18 #include <array> 19 #include <cassert> 20 #include <cstdint> 21 #include <vector> 22 23 enum class ScriptType { 24 P2WPKH, // segwitv0, witness-pubkey-hash (ECDSA 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) 27 }; 28 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 39 // Microbenchmark for verification of standard scripts. 40 static void VerifyScriptBench(benchmark::Bench& bench, ScriptType script_type) 41 { 42 ECC_Context ecc_context{}; 43 44 // Create deterministic key material needed for output script creation / signing 45 CKey privkey; 46 privkey.Set(uint256::ONE.begin(), uint256::ONE.end(), /*fCompressedIn=*/true); 47 CPubKey pubkey = privkey.GetPubKey(); 48 XOnlyPubKey xonly_pubkey{pubkey}; 49 CKeyID key_id = pubkey.GetID(); 50 51 FlatSigningProvider keystore; 52 keystore.keys.emplace(key_id, privkey); 53 keystore.pubkeys.emplace(key_id, pubkey); 54 55 // Create crediting and spending transactions with provided input type 56 const auto dest{[&]() -> CTxDestination { 57 switch (script_type) { 58 case ScriptType::P2WPKH: return WitnessV0KeyHash(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; 67 } // no default case, so the compiler can warn about missing cases 68 assert(false); 69 }()}; 70 const CMutableTransaction& txCredit = BuildCreditingTransaction(GetScriptForDestination(dest), 1); 71 CMutableTransaction txSpend = BuildSpendingTransaction(/*scriptSig=*/{}, /*scriptWitness=*/{}, CTransaction(txCredit)); 72 73 // Sign spending transaction, precompute transaction data 74 PrecomputedTransactionData txdata; 75 { 76 const std::map<COutPoint, Coin> coins{ 77 {txSpend.vin[0].prevout, Coin(txCredit.vout[0], /*nHeightIn=*/100, /*fCoinBaseIn=*/false)} 78 }; 79 std::map<int, bilingual_str> input_errors; 80 bool complete = SignTransaction(txSpend, &keystore, coins, {.sighash_type = SIGHASH_ALL}, input_errors); 81 assert(complete); 82 // Weak sanity check on witness data to ensure we produced the intended spending type 83 assert(txSpend.vin[0].scriptWitness.stack.size() == ExpectedWitnessStackSize(script_type)); 84 txdata.Init(txSpend, /*spent_outputs=*/{txCredit.vout[0]}); 85 } 86 87 // Benchmark. 88 bench.unit("script").run([&] { 89 ScriptError err; 90 bool success = VerifyScript( 91 txSpend.vin[0].scriptSig, 92 txCredit.vout[0].scriptPubKey, 93 &txSpend.vin[0].scriptWitness, 94 STANDARD_SCRIPT_VERIFY_FLAGS, 95 MutableTransactionSignatureChecker(&txSpend, 0, txCredit.vout[0].nValue, txdata, MissingDataBehavior::ASSERT_FAIL), 96 &err); 97 assert(err == SCRIPT_ERR_OK); 98 assert(success); 99 }); 100 } 101 102 static void VerifyScriptP2WPKH(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2WPKH); } 103 static void VerifyScriptP2TR_KeyPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_KeyPath); } 104 static void VerifyScriptP2TR_ScriptPath(benchmark::Bench& bench) { VerifyScriptBench(bench, ScriptType::P2TR_ScriptPath); } 105 106 static void VerifyNestedIfScript(benchmark::Bench& bench) 107 { 108 std::vector<std::vector<unsigned char>> stack; 109 CScript script; 110 for (int i = 0; i < 100; ++i) { 111 script << OP_1 << OP_IF; 112 } 113 for (int i = 0; i < 1000; ++i) { 114 script << OP_1; 115 } 116 for (int i = 0; i < 100; ++i) { 117 script << OP_ENDIF; 118 } 119 bench.unit("script") 120 .setup([&] { stack.clear(); }) 121 .run([&] { 122 ScriptError error; 123 const bool ret{EvalScript(stack, script, /*flags=*/0, BaseSignatureChecker(), SigVersion::BASE, &error)}; 124 assert(ret && error == SCRIPT_ERR_OK); 125 }); 126 } 127 128 BENCHMARK(VerifyScriptP2WPKH); 129 BENCHMARK(VerifyScriptP2TR_KeyPath); 130 BENCHMARK(VerifyScriptP2TR_ScriptPath); 131 BENCHMARK(VerifyNestedIfScript);