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