|
| 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 | +unsigned int 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<unsigned int> AllConsensusFlags() |
| 75 | +{ |
| 76 | + std::vector<unsigned int> ret; |
| 77 | + |
| 78 | + for (unsigned int i = 0; i < 128; ++i) { |
| 79 | + unsigned int 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<unsigned int> 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 | + uint32_t 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}; |
| 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() |
0 commit comments