/ src / bench / verify_script.cpp
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);