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