Bitcoin Core  27.99.0
P2P Digital Currency
script_assets_test_minimizer.cpp
Go to the documentation of this file.
1 // Copyright (c) 2020-2022 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 <test/fuzz/fuzz.h>
6 
8 #include <pubkey.h>
9 #include <script/interpreter.h>
10 #include <serialize.h>
11 #include <streams.h>
12 #include <univalue.h>
13 #include <util/strencodings.h>
14 #include <util/string.h>
15 
16 #include <cstdint>
17 #include <string>
18 #include <vector>
19 
20 using util::SplitString;
21 
22 // This fuzz "test" can be used to minimize test cases for script_assets_test in
23 // src/test/script_tests.cpp. While it written as a fuzz test, and can be used as such,
24 // fuzzing the inputs is unlikely to construct useful test cases.
25 //
26 // Instead, it is primarily intended to be run on a test set that was generated
27 // externally, for example using test/functional/feature_taproot.py's --dumptests mode.
28 // The minimized set can then be concatenated together, surrounded by '[' and ']',
29 // and used as the script_assets_test.json input to the script_assets_test unit test:
30 //
31 // (normal build)
32 // $ mkdir dump
33 // $ for N in $(seq 1 10); do TEST_DUMP_DIR=dump test/functional/feature_taproot.py --dumptests; done
34 // $ ...
35 //
36 // (libFuzzer build)
37 // $ mkdir dump-min
38 // $ FUZZ=script_assets_test_minimizer ./src/test/fuzz/fuzz -merge=1 -use_value_profile=1 dump-min/ dump/
39 // $ (echo -en '[\n'; cat dump-min/* | head -c -2; echo -en '\n]') >script_assets_test.json
40 
41 namespace {
42 
43 std::vector<unsigned char> CheckedParseHex(const std::string& str)
44 {
45  if (str.size() && !IsHex(str)) throw std::runtime_error("Non-hex input '" + str + "'");
46  return ParseHex(str);
47 }
48 
49 CScript ScriptFromHex(const std::string& str)
50 {
51  std::vector<unsigned char> data = CheckedParseHex(str);
52  return CScript(data.begin(), data.end());
53 }
54 
55 CMutableTransaction TxFromHex(const std::string& str)
56 {
58  try {
59  SpanReader{CheckedParseHex(str)} >> TX_NO_WITNESS(tx);
60  } catch (const std::ios_base::failure&) {
61  throw std::runtime_error("Tx deserialization failure");
62  }
63  return tx;
64 }
65 
66 std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
67 {
68  if (!univalue.isArray()) throw std::runtime_error("Prevouts must be array");
69  std::vector<CTxOut> prevouts;
70  for (size_t i = 0; i < univalue.size(); ++i) {
71  CTxOut txout;
72  try {
73  SpanReader{CheckedParseHex(univalue[i].get_str())} >> txout;
74  } catch (const std::ios_base::failure&) {
75  throw std::runtime_error("Prevout invalid format");
76  }
77  prevouts.push_back(std::move(txout));
78  }
79  return prevouts;
80 }
81 
83 {
84  if (!univalue.isArray()) throw std::runtime_error("Script witness is not array");
85  CScriptWitness scriptwitness;
86  for (size_t i = 0; i < univalue.size(); ++i) {
87  auto bytes = CheckedParseHex(univalue[i].get_str());
88  scriptwitness.stack.push_back(std::move(bytes));
89  }
90  return scriptwitness;
91 }
92 
93 const std::map<std::string, unsigned int> FLAG_NAMES = {
94  {std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH},
95  {std::string("DERSIG"), (unsigned int)SCRIPT_VERIFY_DERSIG},
96  {std::string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY},
97  {std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY},
98  {std::string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY},
99  {std::string("WITNESS"), (unsigned int)SCRIPT_VERIFY_WITNESS},
100  {std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
101 };
102 
103 std::vector<unsigned int> AllFlags()
104 {
105  std::vector<unsigned int> ret;
106 
107  for (unsigned int i = 0; i < 128; ++i) {
108  unsigned int flag = 0;
109  if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
110  if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
111  if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
112  if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
113  if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
114  if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
115  if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
116 
117  // SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
118  if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
119  // SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
120  if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
121 
122  ret.push_back(flag);
123  }
124 
125  return ret;
126 }
127 
128 const std::vector<unsigned int> ALL_FLAGS = AllFlags();
129 
130 unsigned int ParseScriptFlags(const std::string& str)
131 {
132  if (str.empty()) return 0;
133 
134  unsigned int flags = 0;
135  std::vector<std::string> words = SplitString(str, ',');
136 
137  for (const std::string& word : words) {
138  auto it = FLAG_NAMES.find(word);
139  if (it == FLAG_NAMES.end()) throw std::runtime_error("Unknown verification flag " + word);
140  flags |= it->second;
141  }
142 
143  return flags;
144 }
145 
146 void Test(const std::string& str)
147 {
148  UniValue test;
149  if (!test.read(str) || !test.isObject()) throw std::runtime_error("Non-object test input");
150 
151  CMutableTransaction tx = TxFromHex(test["tx"].get_str());
152  const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
153  if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts");
154  size_t idx = test["index"].getInt<int64_t>();
155  if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index");
156  unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
157  bool final = test.exists("final") && test["final"].get_bool();
158 
159  if (test.exists("success")) {
160  tx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
161  tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
163  txdata.Init(tx, std::vector<CTxOut>(prevouts));
164  MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
165  for (const auto flags : ALL_FLAGS) {
166  // "final": true tests are valid for all flags. Others are only valid with flags that are
167  // a subset of test_flags.
168  if (final || ((flags & test_flags) == flags)) {
169  (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
170  }
171  }
172  }
173 
174  if (test.exists("failure")) {
175  tx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
176  tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
178  txdata.Init(tx, std::vector<CTxOut>(prevouts));
179  MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
180  for (const auto flags : ALL_FLAGS) {
181  // If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
182  if ((flags & test_flags) == test_flags) {
183  (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
184  }
185  }
186  }
187 }
188 
189 void test_init() {}
190 
191 FUZZ_TARGET(script_assets_test_minimizer, .init = test_init, .hidden = true)
192 {
193  if (buffer.size() < 2 || buffer.back() != '\n' || buffer[buffer.size() - 2] != ',') return;
194  const std::string str((const char*)buffer.data(), buffer.size() - 2);
195  try {
196  Test(str);
197  } catch (const std::runtime_error&) {
198  }
199 }
200 
201 } // namespace
int ret
int flags
Definition: bitcoin-tx.cpp:533
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:414
An output of a transaction.
Definition: transaction.h:150
Minimal stream for reading from an existing byte array by Span.
Definition: streams.h:101
bool isArray() const
Definition: univalue.h:85
size_t size() const
Definition: univalue.h:71
bool read(std::string_view raw)
Int getInt() const
Definition: univalue.h:138
bool exists(const std::string &key) const
Definition: univalue.h:77
bool get_bool() const
bool isObject() const
Definition: univalue.h:86
#define FUZZ_TARGET(...)
Definition: fuzz.h:35
bool VerifyScript(const CScript &scriptSig, const CScript &scriptPubKey, const CScriptWitness *witness, unsigned int flags, const BaseSignatureChecker &checker, ScriptError *serror)
@ SCRIPT_VERIFY_NULLDUMMY
Definition: interpreter.h:64
@ SCRIPT_VERIFY_P2SH
Definition: interpreter.h:49
@ SCRIPT_VERIFY_WITNESS
Definition: interpreter.h:108
@ SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY
Definition: interpreter.h:99
@ SCRIPT_VERIFY_TAPROOT
Definition: interpreter.h:134
@ SCRIPT_VERIFY_DERSIG
Definition: interpreter.h:57
@ SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
Definition: interpreter.h:104
@ ASSERT_FAIL
Abort execution through assertion failure (for consensus code)
std::vector< std::string > SplitString(std::string_view str, char sep)
Definition: string.h:59
static constexpr TransactionSerParams TX_NO_WITNESS
Definition: transaction.h:196
static CScript ScriptFromHex(const std::string &str)
static CMutableTransaction TxFromHex(const std::string &str)
unsigned int ParseScriptFlags(std::string strFlags)
static CScriptWitness ScriptWitnessFromJSON(const UniValue &univalue)
static std::vector< CTxOut > TxOutsFromJSON(const UniValue &univalue)
std::vector< Byte > ParseHex(std::string_view hex_str)
Like TryParseHex, but returns an empty vector on invalid input.
Definition: strencodings.h:66
A mutable version of CTransaction.
Definition: transaction.h:378
std::vector< CTxIn > vin
Definition: transaction.h:379
std::vector< std::vector< unsigned char > > stack
Definition: script.h:569
void Init(const T &tx, std::vector< CTxOut > &&spent_outputs, bool force=false)
Initialize this PrecomputedTransactionData with transaction data.
bool IsHex(std::string_view str)