Line data Source code
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 :
7 : #include <primitives/transaction.h>
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 : // 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 0 : std::vector<unsigned char> CheckedParseHex(const std::string& str)
42 : {
43 0 : if (str.size() && !IsHex(str)) throw std::runtime_error("Non-hex input '" + str + "'");
44 0 : return ParseHex(str);
45 0 : }
46 :
47 0 : CScript ScriptFromHex(const std::string& str)
48 : {
49 0 : std::vector<unsigned char> data = CheckedParseHex(str);
50 0 : return CScript(data.begin(), data.end());
51 0 : }
52 :
53 0 : CMutableTransaction TxFromHex(const std::string& str)
54 : {
55 0 : CMutableTransaction tx;
56 : try {
57 0 : SpanReader{SER_DISK, SERIALIZE_TRANSACTION_NO_WITNESS, CheckedParseHex(str)} >> tx;
58 0 : } catch (const std::ios_base::failure&) {
59 0 : throw std::runtime_error("Tx deserialization failure");
60 0 : }
61 0 : return tx;
62 0 : }
63 :
64 0 : std::vector<CTxOut> TxOutsFromJSON(const UniValue& univalue)
65 : {
66 0 : if (!univalue.isArray()) throw std::runtime_error("Prevouts must be array");
67 0 : std::vector<CTxOut> prevouts;
68 0 : for (size_t i = 0; i < univalue.size(); ++i) {
69 0 : CTxOut txout;
70 : try {
71 0 : SpanReader{SER_DISK, 0, CheckedParseHex(univalue[i].get_str())} >> txout;
72 0 : } catch (const std::ios_base::failure&) {
73 0 : throw std::runtime_error("Prevout invalid format");
74 0 : }
75 0 : prevouts.push_back(std::move(txout));
76 0 : }
77 0 : return prevouts;
78 0 : }
79 :
80 0 : CScriptWitness ScriptWitnessFromJSON(const UniValue& univalue)
81 : {
82 0 : if (!univalue.isArray()) throw std::runtime_error("Script witness is not array");
83 0 : CScriptWitness scriptwitness;
84 0 : for (size_t i = 0; i < univalue.size(); ++i) {
85 0 : auto bytes = CheckedParseHex(univalue[i].get_str());
86 0 : scriptwitness.stack.push_back(std::move(bytes));
87 0 : }
88 0 : return scriptwitness;
89 0 : }
90 :
91 16 : const std::map<std::string, unsigned int> FLAG_NAMES = {
92 2 : {std::string("P2SH"), (unsigned int)SCRIPT_VERIFY_P2SH},
93 2 : {std::string("DERSIG"), (unsigned int)SCRIPT_VERIFY_DERSIG},
94 2 : {std::string("NULLDUMMY"), (unsigned int)SCRIPT_VERIFY_NULLDUMMY},
95 2 : {std::string("CHECKLOCKTIMEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY},
96 2 : {std::string("CHECKSEQUENCEVERIFY"), (unsigned int)SCRIPT_VERIFY_CHECKSEQUENCEVERIFY},
97 2 : {std::string("WITNESS"), (unsigned int)SCRIPT_VERIFY_WITNESS},
98 2 : {std::string("TAPROOT"), (unsigned int)SCRIPT_VERIFY_TAPROOT},
99 : };
100 :
101 2 : std::vector<unsigned int> AllFlags()
102 : {
103 2 : std::vector<unsigned int> ret;
104 :
105 258 : for (unsigned int i = 0; i < 128; ++i) {
106 256 : unsigned int flag = 0;
107 256 : if (i & 1) flag |= SCRIPT_VERIFY_P2SH;
108 256 : if (i & 2) flag |= SCRIPT_VERIFY_DERSIG;
109 256 : if (i & 4) flag |= SCRIPT_VERIFY_NULLDUMMY;
110 256 : if (i & 8) flag |= SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY;
111 256 : if (i & 16) flag |= SCRIPT_VERIFY_CHECKSEQUENCEVERIFY;
112 256 : if (i & 32) flag |= SCRIPT_VERIFY_WITNESS;
113 256 : if (i & 64) flag |= SCRIPT_VERIFY_TAPROOT;
114 :
115 : // SCRIPT_VERIFY_WITNESS requires SCRIPT_VERIFY_P2SH
116 256 : if (flag & SCRIPT_VERIFY_WITNESS && !(flag & SCRIPT_VERIFY_P2SH)) continue;
117 : // SCRIPT_VERIFY_TAPROOT requires SCRIPT_VERIFY_WITNESS
118 192 : if (flag & SCRIPT_VERIFY_TAPROOT && !(flag & SCRIPT_VERIFY_WITNESS)) continue;
119 :
120 128 : ret.push_back(flag);
121 128 : }
122 :
123 2 : return ret;
124 2 : }
125 :
126 2 : const std::vector<unsigned int> ALL_FLAGS = AllFlags();
127 :
128 0 : unsigned int ParseScriptFlags(const std::string& str)
129 : {
130 0 : if (str.empty()) return 0;
131 :
132 0 : unsigned int flags = 0;
133 0 : std::vector<std::string> words = SplitString(str, ',');
134 :
135 0 : for (const std::string& word : words) {
136 0 : auto it = FLAG_NAMES.find(word);
137 0 : if (it == FLAG_NAMES.end()) throw std::runtime_error("Unknown verification flag " + word);
138 0 : flags |= it->second;
139 : }
140 :
141 0 : return flags;
142 0 : }
143 :
144 0 : void Test(const std::string& str)
145 : {
146 0 : UniValue test;
147 0 : if (!test.read(str) || !test.isObject()) throw std::runtime_error("Non-object test input");
148 :
149 0 : CMutableTransaction tx = TxFromHex(test["tx"].get_str());
150 0 : const std::vector<CTxOut> prevouts = TxOutsFromJSON(test["prevouts"]);
151 0 : if (prevouts.size() != tx.vin.size()) throw std::runtime_error("Incorrect number of prevouts");
152 0 : size_t idx = test["index"].getInt<int64_t>();
153 0 : if (idx >= tx.vin.size()) throw std::runtime_error("Invalid index");
154 0 : unsigned int test_flags = ParseScriptFlags(test["flags"].get_str());
155 0 : bool final = test.exists("final") && test["final"].get_bool();
156 :
157 0 : if (test.exists("success")) {
158 0 : tx.vin[idx].scriptSig = ScriptFromHex(test["success"]["scriptSig"].get_str());
159 0 : tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["success"]["witness"]);
160 0 : PrecomputedTransactionData txdata;
161 0 : txdata.Init(tx, std::vector<CTxOut>(prevouts));
162 0 : MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
163 0 : for (const auto flags : ALL_FLAGS) {
164 : // "final": true tests are valid for all flags. Others are only valid with flags that are
165 : // a subset of test_flags.
166 0 : if (final || ((flags & test_flags) == flags)) {
167 0 : (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
168 0 : }
169 : }
170 0 : }
171 :
172 0 : if (test.exists("failure")) {
173 0 : tx.vin[idx].scriptSig = ScriptFromHex(test["failure"]["scriptSig"].get_str());
174 0 : tx.vin[idx].scriptWitness = ScriptWitnessFromJSON(test["failure"]["witness"]);
175 0 : PrecomputedTransactionData txdata;
176 0 : txdata.Init(tx, std::vector<CTxOut>(prevouts));
177 0 : MutableTransactionSignatureChecker txcheck(&tx, idx, prevouts[idx].nValue, txdata, MissingDataBehavior::ASSERT_FAIL);
178 0 : for (const auto flags : ALL_FLAGS) {
179 : // If a test is supposed to fail with test_flags, it should also fail with any superset thereof.
180 0 : if ((flags & test_flags) == test_flags) {
181 0 : (void)VerifyScript(tx.vin[idx].scriptSig, prevouts[idx].scriptPubKey, &tx.vin[idx].scriptWitness, flags, txcheck, nullptr);
182 0 : }
183 : }
184 0 : }
185 0 : }
186 :
187 0 : void test_init() {}
188 :
189 4 : FUZZ_TARGET(script_assets_test_minimizer, .init = test_init, .hidden = true)
190 : {
191 0 : if (buffer.size() < 2 || buffer.back() != '\n' || buffer[buffer.size() - 2] != ',') return;
192 0 : const std::string str((const char*)buffer.data(), buffer.size() - 2);
193 : try {
194 0 : Test(str);
195 0 : } catch (const std::runtime_error&) {
196 0 : }
197 0 : }
198 :
199 : } // namespace
|