2
2
// Distributed under the MIT software license, see the accompanying
3
3
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
4
5
+ #include < cstdio>
5
6
#include < primitives/transaction.h>
6
7
extern " C" {
8
+ #include < simplicity/cmr.h>
7
9
#include < simplicity/elements/env.h>
8
10
#include < simplicity/elements/exec.h>
9
11
}
@@ -16,49 +18,209 @@ extern "C" {
16
18
#include < string>
17
19
#include < vector>
18
20
19
- FUZZ_TARGET (simplicity)
21
+ uint256 GENESIS_HASH;
22
+
23
+ CConfidentialAsset INPUT_ASSET_UNCONF{};
24
+ CConfidentialAsset INPUT_ASSET_CONF{};
25
+ CConfidentialValue INPUT_VALUE_UNCONF{};
26
+ CConfidentialValue INPUT_VALUE_CONF{};
27
+ CScript TAPROOT_SCRIPT_PUB_KEY{};
28
+ std::vector<unsigned char > TAPROOT_CONTROL{};
29
+ std::vector<unsigned char > TAPROOT_ANNEX{99 , 0x50 };
30
+ // CMutableTransaction MTX_TEMPLATE{};
31
+
32
+ // Defined in simplicity_compute_amr.c
33
+ extern " C" {
34
+ bool simplicity_computeAmr ( simplicity_err* error, unsigned char * amr
35
+ , const unsigned char * program, size_t program_len
36
+ , const unsigned char * witness, size_t witness_len);
37
+ }
38
+
39
+ void initialize_simplicity ()
20
40
{
21
- FuzzedDataProvider fuzzed_data_provider (buffer.data (), buffer.size ());
22
- {
23
- const std::optional<CMutableTransaction> mtx_precomputed = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider);
24
- if (mtx_precomputed) {
25
- const CTransaction tx_precomputed{*mtx_precomputed};
26
- const PrecomputedTransactionData precomputed_transaction_data{tx_precomputed};
27
- const transaction* tx = precomputed_transaction_data.m_simplicity_tx_data ;
28
-
29
- const uint256 genesisBlockHash = precomputed_transaction_data.m_hash_genesis_block ;
30
-
31
- std::vector<unsigned char > imr = fuzzed_data_provider.ConsumeBytes <unsigned char >(32 );
32
-
33
- const uint_fast32_t ix = fuzzed_data_provider.ConsumeIntegral <uint_fast32_t >();
34
-
35
- const std::vector<unsigned char > control = ConsumeRandomLengthByteVector (fuzzed_data_provider);
36
- // control block invariant: 33 + pathLen*32
37
- // https://github.com/ElementsProject/elements/blob/174c46baecd/src/script/interpreter.cpp#L3285
38
- if (
39
- control.size () >= TAPROOT_CONTROL_BASE_SIZE &&
40
- control.size () <= TAPROOT_CONTROL_MAX_SIZE &&
41
- ((control.size () - TAPROOT_CONTROL_BASE_SIZE) % TAPROOT_CONTROL_NODE_SIZE) == 0
42
- ) {
43
- const std::vector<unsigned char > script_bytes = fuzzed_data_provider.ConsumeBytes <unsigned char >(32 );
44
- // script_bytes invariant: 32 bytes
45
- // https://github.com/ElementsProject/elements/blob/174c46baecd/src/script/interpreter.cpp#L3300
46
- if (script_bytes.size () == 32 ) {
47
- rawTapEnv simplicityRawTap;
48
- simplicityRawTap.controlBlock = control.data ();
49
- simplicityRawTap.pathLen = (control.size () - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
50
- simplicityRawTap.scriptCMR = script_bytes.data ();
51
- tapEnv* taproot = simplicity_elements_mallocTapEnv (&simplicityRawTap);
52
-
53
- const int64_t budget = fuzzed_data_provider.ConsumeIntegral <int64_t >();
54
- const std::vector<unsigned char > amr = fuzzed_data_provider.ConsumeBytes <unsigned char >(32 );
55
- const std::vector<unsigned char > program = ConsumeRandomLengthByteVector (fuzzed_data_provider);
56
-
57
- simplicity_err error;
58
- simplicity_elements_execSimplicity (&error, imr.data (), tx, ix, taproot, genesisBlockHash.data (), budget, amr.data (), program.data (), program.size ());
59
- free (taproot);
60
- }
41
+ g_con_elementsmode = true ;
42
+
43
+ GENESIS_HASH = uint256S (" 0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" );
44
+
45
+ INPUT_VALUE_UNCONF.SetToAmount (12345678 );
46
+ INPUT_VALUE_CONF.vchCommitment = {
47
+ 0x08 ,
48
+ 0x01 , 0x02 , 0x03 , 0x04 , 0x05 , 0x06 , 0x07 , 0x08 ,
49
+ 0x11 , 0x12 , 0x13 , 0x14 , 0x15 , 0x16 , 0x17 , 0x18 ,
50
+ 0x21 , 0x22 , 0x23 , 0x24 , 0x25 , 0x26 , 0x27 , 0x28 ,
51
+ 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x38 ,
52
+ };
53
+
54
+ INPUT_ASSET_UNCONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment ;
55
+ INPUT_ASSET_UNCONF.vchCommitment [0 ] = 0x01 ;
56
+ INPUT_ASSET_CONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment ;
57
+ INPUT_ASSET_CONF.vchCommitment [0 ] = 0x0a ;
58
+
59
+ XOnlyPubKey intkey = XOnlyPubKey{uint256::ONE};
60
+ XOnlyPubKey extkey = XOnlyPubKey{uint256::ONE};
61
+ TAPROOT_SCRIPT_PUB_KEY = CScript{} << OP_1 << std::vector<unsigned char >(extkey.begin (), extkey.end ());
62
+ // TODO have control block of nontrivial path length
63
+ TAPROOT_CONTROL.push_back (TAPROOT_LEAF_TAPSIMPLICITY | 1 ); // 1 is parity
64
+ TAPROOT_CONTROL.insert (TAPROOT_CONTROL.end (), intkey.begin (), intkey.end ());
65
+ }
66
+
67
+ uint32_t read_u32 (const unsigned char **buf) {
68
+ uint32_t ret;
69
+ memcpy (&ret, *buf, 4 );
70
+ *buf += 4 ;
71
+ return le32toh (ret);
72
+ }
73
+
74
+ #define MAX_LEN (1024 * 1024 )
75
+
76
+ FUZZ_TARGET_INIT (simplicity, initialize_simplicity)
77
+ {
78
+ const unsigned char *buf = buffer.data ();
79
+
80
+ uint32_t budget;
81
+ uint32_t tx_data_len;
82
+ uint32_t prog_data_len;
83
+ uint32_t wit_data_len;
84
+
85
+ // 1. Sanitize and parse the buffer
86
+ if (buffer.size () < 8 ) {
87
+ return ;
88
+ }
89
+ budget = read_u32 (&buf);
90
+
91
+ tx_data_len = read_u32 (&buf);
92
+ if (tx_data_len > MAX_LEN || buffer.size () < tx_data_len + 12 ) {
93
+ return ;
94
+ }
95
+ const unsigned char *tx_data = buf;
96
+ buf += tx_data_len;
97
+
98
+ prog_data_len = read_u32 (&buf);
99
+ if (prog_data_len > MAX_LEN || buffer.size () < tx_data_len + prog_data_len + 16 ) {
100
+ return ;
101
+ }
102
+ const unsigned char *prog_data = buf;
103
+ buf += prog_data_len;
104
+
105
+ wit_data_len = read_u32 (&buf);
106
+ if (wit_data_len > MAX_LEN || buffer.size () != tx_data_len + prog_data_len + wit_data_len + 16 ) {
107
+ return ;
108
+ }
109
+ const unsigned char *wit_data = buf;
110
+
111
+ // printf("OK going\n");
112
+
113
+ // 2. Parse the transaction (the program and witness are just raw bytes)
114
+ CMutableTransaction mtx;
115
+ CDataStream txds{Span{tx_data, tx_data_len}, SER_NETWORK, INIT_PROTO_VERSION};
116
+ try {
117
+ txds >> mtx;
118
+ mtx.witness .vtxinwit .resize (mtx.vin .size ());
119
+ mtx.witness .vtxoutwit .resize (mtx.vout .size ());
120
+
121
+ // We use the first vin as a "random oracle" rather than reading more from
122
+ // the fuzzer, because we want our fuzz seeds to have as simple a structure
123
+ // as possible. This means we must reject 0-input transactions, which are
124
+ // invalid on-chain anyway.
125
+ if (mtx.vin .size () == 0 ) {
126
+ return ;
127
+ }
128
+
129
+ // This is an assertion in the Simplicity interpreter. It is guaranteed
130
+ // to hold for anything on the network since (even if validatepegin is off)
131
+ // pegins are validated for well-formedness long before the script interpreter
132
+ // is invoked. But in this code we just call the interpreter directly without
133
+ // these checks.
134
+ for (unsigned i = 0 ; i < mtx.vin .size (); i++) {
135
+ if (mtx.vin [i].m_is_pegin && (mtx.witness .vtxinwit [i].m_pegin_witness .stack .size () < 4 || mtx.witness .vtxinwit [i].m_pegin_witness .stack [2 ].size () != 32 )) {
136
+ return ;
61
137
}
62
138
}
139
+ } catch (const std::ios_base::failure&) {
140
+ return ;
63
141
}
142
+
143
+ // 2a. Pull the program and witness into vectors so they can be pushed onto the stack.
144
+ std::vector<unsigned char > prog_bytes;
145
+ std::vector<unsigned char > wit_bytes;
146
+ prog_bytes.assign (prog_data, prog_data + prog_data_len);
147
+ wit_bytes.assign (wit_data, wit_data + wit_data_len);
148
+
149
+ simplicity_err error;
150
+ unsigned char cmr[32 ];
151
+ unsigned char amr[32 ];
152
+ assert (simplicity_computeAmr (&error, amr, prog_data, prog_data_len, wit_data, wit_data_len));
153
+ assert (simplicity_computeCmr (&error, cmr, prog_data, prog_data_len));
154
+
155
+ // The remainder is just copy/pasted from the original fuzztest
156
+
157
+ // 3. Construct `nIn` and `spent_outs` array.
158
+ //
159
+ // Here we extract data from the first input's txid, since the fuzzer already
160
+ // produced that as a random string which has no other meaning. So to avoid
161
+ // complicating our seed encoding beyond "transaction then simplicity code"
162
+ // we just use it as a random source.
163
+ //
164
+ // We do skip the first byte since that has pegin/issuance flag in it and
165
+ // therefore already has semantic information.
166
+ size_t nIn = mtx.vin [0 ].prevout .hash .data ()[1 ] % mtx.vin .size ();
167
+ std::vector<CTxOut> spent_outs{};
168
+ for (unsigned int i = 0 ; i < mtx.vin .size (); i++) {
169
+ // Null asset or value would assert in the interpreter, and are impossible
170
+ // to hit in real transactions. Nonces are not included in the UTXO set and
171
+ // therefore don't matter.
172
+ CConfidentialValue value = i & 1 ? INPUT_VALUE_CONF : INPUT_VALUE_UNCONF;
173
+ CConfidentialAsset asset = i & 2 ? INPUT_ASSET_CONF : INPUT_ASSET_UNCONF;
174
+ CScript scriptPubKey;
175
+ if (i != nIn) {
176
+ // For scriptPubKeys we can use arbitrary scripts. We include the empty
177
+ // script even though in a real transaction this would be impossible,
178
+ // because it shouldn't break anything.
179
+ for (unsigned int j = 0 ; j < i; j++) {
180
+ scriptPubKey << OP_TRUE;
181
+ }
182
+ } else {
183
+ scriptPubKey = TAPROOT_SCRIPT_PUB_KEY;
184
+ }
185
+
186
+ spent_outs.push_back (CTxOut{asset, value, scriptPubKey});
187
+ }
188
+ assert (spent_outs.size () == mtx.vin .size ());
189
+
190
+ // 4. Set up witness data
191
+ mtx.witness .vtxinwit [nIn].scriptWitness .stack .clear ();
192
+ mtx.witness .vtxinwit [nIn].scriptWitness .stack .push_back (prog_bytes);
193
+ mtx.witness .vtxinwit [nIn].scriptWitness .stack .push_back (TAPROOT_CONTROL);
194
+ if (mtx.vin [0 ].prevout .hash .data ()[2 ] & 1 ) {
195
+ mtx.witness .vtxinwit [nIn].scriptWitness .stack .push_back (TAPROOT_ANNEX);
196
+ }
197
+
198
+ // 5. Set up Simplicity environment and tx environment
199
+ rawTapEnv simplicityRawTap;
200
+ simplicityRawTap.controlBlock = TAPROOT_CONTROL.data ();
201
+ simplicityRawTap.pathLen = (TAPROOT_CONTROL.size () - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
202
+ simplicityRawTap.scriptCMR = cmr;
203
+
204
+ PrecomputedTransactionData txdata{GENESIS_HASH};
205
+ std::vector<CTxOut> spent_outs_copy{spent_outs};
206
+ txdata.Init (mtx, std::move (spent_outs_copy));
207
+ assert (txdata.m_simplicity_tx_data != NULL );
208
+
209
+ // 4. Main test
210
+ unsigned char imr_out[32 ];
211
+ unsigned char *imr = mtx.vin [0 ].prevout .hash .data ()[2 ] & 2 ? imr_out : NULL ;
212
+
213
+ const transaction* tx = txdata.m_simplicity_tx_data ;
214
+ tapEnv* taproot = simplicity_elements_mallocTapEnv (&simplicityRawTap);
215
+ simplicity_elements_execSimplicity (&error, imr, tx, nIn, taproot, GENESIS_HASH.data (), budget, amr, prog_bytes.data (), prog_bytes.size (), wit_bytes.data (), wit_bytes.size ());
216
+
217
+ // 5. Secondary test -- try flipping a bunch of bits and check that this doesn't mess things up
218
+ for (size_t j = 0 ; j < 8 * prog_bytes.size (); j++) {
219
+ if (j > 32 && j % 23 != 0 ) continue ; // skip most bits so this test doesn't overwhelm the fuzz time
220
+ prog_bytes.data ()[j / 8 ] ^= (1 << (j % 8 ));
221
+ simplicity_elements_execSimplicity (&error, imr, tx, nIn, taproot, GENESIS_HASH.data (), budget, amr, prog_bytes.data (), prog_bytes.size (), wit_bytes.data (), wit_bytes.size ());
222
+ }
223
+
224
+ // 6. Cleanup
225
+ free (taproot);
64
226
}
0 commit comments