script_assets_tests.cpp raw

   1  // Copyright (c) 2011-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 <primitives/transaction.h>
   6  #include <script/interpreter.h>
   7  #include <script/script.h>
   8  #include <script/sigcache.h>
   9  #include <script/sign.h>
  10  #include <script/signingprovider.h>
  11  #include <span.h>
  12  #include <streams.h>
  13  #include <test/util/json.h>
  14  #include <util/check.h>
  15  #include <util/fs.h>
  16  #include <util/strencodings.h>
  17  
  18  #include <cstdint>
  19  #include <cstdlib>
  20  #include <fstream>
  21  #include <utility>
  22  #include <vector>
  23  
  24  #include <boost/test/unit_test.hpp>
  25  
  26  #include <univalue.h>
  27  
  28  script_verify_flags ParseScriptFlags(std::string strFlags);
  29  
  30  BOOST_AUTO_TEST_SUITE(script_assets_tests)
  31  
  32  template <typename T>
  33  CScript ToScript(const T& byte_container)
  34  {
  35      auto span{MakeUCharSpan(byte_container)};
  36      return {span.begin(), span.end()};
  37  }
  38  
  39  static CScript ScriptFromHex(const std::string& str)
  40  {
  41      return ToScript(*Assert(TryParseHex(str)));
  42  }
  43  
  44  static CMutableTransaction TxFromHex(const std::string& str)
  45  {
  46      CMutableTransaction tx;
  47      SpanReader{ParseHex(str)} >> TX_NO_WITNESS(tx);
  48      return tx;
  49  }
  50  
  51  static std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
  52  {
  53      assert(univalue.isArray());
  54      std::vector<CTxOut> prevouts;
  55      for (size_t i = 0; i < univalue.size(); ++i) {
  56          CTxOut txout;
  57          SpanReader{ParseHex(univalue[i].get_str())} >> txout;
  58          prevouts.push_back(std::move(txout));
  59      }
  60      return prevouts;
  61  }
  62  
  63  static CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
  64  {
  65      assert(univalue.isArray());
  66      CScriptWitness scriptwitness;
  67      for (size_t i = 0; i < univalue.size(); ++i) {
  68          auto bytes = ParseHex(univalue[i].get_str());
  69          scriptwitness.stack.push_back(std::move(bytes));
  70      }
  71      return scriptwitness;
  72  }
  73  
  74  static std::vector<script_verify_flags> AllConsensusFlags()
  75  {
  76      std::vector<script_verify_flags> ret;
  77  
  78      for (unsigned int i = 0; i < 128; ++i) {
  79          script_verify_flags flag = 0;
  80          if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
  81          if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
  82          if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
  83          if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
  84          if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
  85          if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
  86          if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
  87  
  88          // SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
  89          if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
  90          // SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
  91          if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
  92  
  93          ret.push_back(flag);
  94      }
  95  
  96      return ret;
  97  }
  98  
  99  /** Precomputed list of all valid combinations of consensus-relevant script validation flags. */
 100  static const std::vector<script_verify_flags> ALL_CONSENSUS_FLAGS = AllConsensusFlags();
 101  
 102  static void AssetTest(const UniValue& test, SignatureCache& signature_cache)
 103  {
 104      BOOST_CHECK(test.isObject());
 105  
 106      CMutableTransaction mtx = TxFromHex(test["tx"].get_str());
 107      const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
 108      BOOST_CHECK(prevouts.size() == mtx.vin.size());
 109      size_t idx = test["index"].getInt<int64_t>();
 110      script_verify_flags test_flags{ParseScriptFlags(test["flags"].get_str())};
 111      bool fin = test.exists("final") && test["final"].get_bool();
 112  
 113      if (test.exists("success")) {
 114          mtx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
 115          mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
 116          CTransaction tx(mtx);
 117          PrecomputedTransactionData txdata;
 118          txdata.Init(tx, std::vector<CTxOut>(prevouts));
 119          CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
 120  
 121          for (const auto flags : ALL_CONSENSUS_FLAGS) {
 122              // "final": true tests are valid for all flags. Others are only valid with flags that are
 123              // a subset of test_flags.
 124              if (fin || ((flags & test_flags) == flags)) {
 125                  bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
 126                  BOOST_CHECK(ret);
 127              }
 128          }
 129      }
 130  
 131      if (test.exists("failure")) {
 132          mtx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
 133          mtx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
 134          CTransaction tx(mtx);
 135          PrecomputedTransactionData txdata;
 136          txdata.Init(tx, std::vector<CTxOut>(prevouts));
 137          CachingTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, true, signature_cache, txdata);
 138  
 139          for (const auto flags : ALL_CONSENSUS_FLAGS) {
 140              // If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
 141              if ((flags & test_flags) == test_flags) {
 142                  bool ret = VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
 143                  BOOST_CHECK(!ret);
 144              }
 145          }
 146      }
 147  }
 148  
 149  BOOST_AUTO_TEST_CASE(script_assets_test)
 150  {
 151      // See src/test/fuzz/script_assets_test_minimizer.cpp for information on how to generate
 152      // the script_assets_test.json file used by this test.
 153      SignatureCache signature_cache{DEFAULT_SIGNATURE_CACHE_BYTES};
 154  
 155      const char* dir = std::getenv("DIR_UNIT_TEST_DATA");
 156      BOOST_WARN_MESSAGE(dir != nullptr, "Variable DIR_UNIT_TEST_DATA unset, skipping script_assets_test");
 157      if (dir == nullptr) return;
 158      auto path = fs::path(dir) / "script_assets_test.json";
 159      bool exists = fs::exists(path);
 160      BOOST_WARN_MESSAGE(exists, "File $DIR_UNIT_TEST_DATA/script_assets_test.json not found, skipping script_assets_test");
 161      if (!exists) return;
 162      std::ifstream file{path.std_path()};
 163      BOOST_CHECK(file.is_open());
 164      file.seekg(0, std::ios::end);
 165      size_t length = file.tellg();
 166      file.seekg(0, std::ios::beg);
 167      std::string data(length, '\0');
 168      file.read(data.data(), data.size());
 169      UniValue tests = read_json(data);
 170      BOOST_CHECK(tests.isArray());
 171      BOOST_CHECK(tests.size() > 0);
 172  
 173      for (size_t i = 0; i < tests.size(); i++) {
 174          AssetTest(tests[i], signature_cache);
 175      }
 176      file.close();
 177  }
 178  
 179  BOOST_AUTO_TEST_SUITE_END()
 180