Skip to content

Commit e41b225

Browse files
authored
Merge pull request #1384 from apoelstra/2024-12--simple-fuzz
simplicity: add fuzz target
2 parents a259bc3 + bd852ba commit e41b225

5 files changed

Lines changed: 535 additions & 0 deletions

File tree

src/Makefile.test.include

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ endif
219219
if ENABLE_FUZZ_BINARY
220220
test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
221221
test_fuzz_fuzz_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
222+
test_fuzz_fuzz_CFLAGS = $(AM_CFLAGS) $(PIE_FLAGS)
222223
test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON)
223224
test_fuzz_fuzz_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) $(RUNTIME_LDFLAGS)
224225
test_fuzz_fuzz_SOURCES = \
@@ -312,6 +313,9 @@ test_fuzz_fuzz_SOURCES = \
312313
test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp \
313314
test/fuzz/signature_checker.cpp \
314315
test/fuzz/signet.cpp \
316+
test/fuzz/simplicity_compute_amr.c \
317+
test/fuzz/simplicity.cpp \
318+
test/fuzz/simplicity_tx.cpp \
315319
test/fuzz/socks5.cpp \
316320
test/fuzz/span.cpp \
317321
test/fuzz/spanparsing.cpp \

src/test/fuzz/simplicity.cpp

Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
// Copyright (c) 2020 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 <cstdio>
6+
#include <primitives/transaction.h>
7+
extern "C" {
8+
#include <simplicity/cmr.h>
9+
#include <simplicity/elements/env.h>
10+
#include <simplicity/elements/exec.h>
11+
}
12+
#include <test/fuzz/FuzzedDataProvider.h>
13+
#include <test/fuzz/fuzz.h>
14+
#include <test/fuzz/util.h>
15+
16+
#include <cstdint>
17+
#include <optional>
18+
#include <string>
19+
#include <vector>
20+
21+
static uint256 GENESIS_HASH;
22+
23+
static CConfidentialAsset INPUT_ASSET_UNCONF{};
24+
static CConfidentialAsset INPUT_ASSET_CONF{};
25+
static CConfidentialValue INPUT_VALUE_UNCONF{};
26+
static CConfidentialValue INPUT_VALUE_CONF{};
27+
static CScript TAPROOT_SCRIPT_PUB_KEY{};
28+
static std::vector<unsigned char> TAPROOT_CONTROL{};
29+
static 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()
40+
{
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;
137+
}
138+
}
139+
} catch (const std::ios_base::failure&) {
140+
return;
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);
226+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2020 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 <simplicity/cmr.h>
6+
#include <simplicity/dag.h>
7+
#include <simplicity/deserialize.h> // simplicity_decodeMallocDag
8+
#include <simplicity/limitations.h> // DAG_LEN_MAX
9+
#include <simplicity/simplicity_alloc.h> // simplicity_free
10+
#include <simplicity/typeInference.h> // simplicity_mallocTypeInference
11+
#include <simplicity/elements/env.h>
12+
#include <simplicity/elements/exec.h>
13+
14+
// Copy of computeCmr used for AMR
15+
bool simplicity_computeAmr( simplicity_err* error, unsigned char* amr
16+
, const unsigned char* program, size_t program_len
17+
, const unsigned char* witness, size_t witness_len) {
18+
simplicity_assert(NULL != error);
19+
simplicity_assert(NULL != amr);
20+
simplicity_assert(NULL != program || 0 == program_len);
21+
simplicity_assert(NULL != witness || 0 == witness_len);
22+
23+
bitstream stream = initializeBitstream(program, program_len);
24+
dag_node* dag = NULL;
25+
combinator_counters census;
26+
int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, &census, &stream);
27+
if (dag_len <= 0) {
28+
simplicity_assert(dag_len < 0);
29+
*error = (simplicity_err)dag_len;
30+
} else {
31+
simplicity_assert(NULL != dag);
32+
simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX);
33+
*error = simplicity_closeBitstream(&stream);
34+
35+
type* type_dag = NULL;
36+
if (IS_OK(*error)) {
37+
*error = simplicity_mallocTypeInference(&type_dag, dag, (uint_fast32_t)dag_len, &census);
38+
}
39+
bitstream witness_stream;
40+
if (IS_OK(*error)) {
41+
witness_stream = initializeBitstream(witness, witness_len);
42+
*error = simplicity_fillWitnessData(dag, type_dag, (uint_fast32_t)dag_len, &witness_stream);
43+
}
44+
if (IS_OK(*error)) {
45+
*error = simplicity_closeBitstream(&witness_stream);
46+
if (SIMPLICITY_ERR_BITSTREAM_TRAILING_BYTES == *error) *error = SIMPLICITY_ERR_WITNESS_TRAILING_BYTES;
47+
if (SIMPLICITY_ERR_BITSTREAM_ILLEGAL_PADDING == *error) *error = SIMPLICITY_ERR_WITNESS_ILLEGAL_PADDING;
48+
}
49+
if (IS_OK(*error)) {
50+
analyses *analysis = (analyses*) simplicity_malloc((size_t)dag_len * sizeof(analyses));
51+
simplicity_assert(NULL != analysis);
52+
simplicity_computeAnnotatedMerkleRoot(analysis, dag, type_dag, (uint_fast32_t)dag_len);
53+
sha256_fromMidstate(amr, analysis[dag_len-1].annotatedMerkleRoot.s);
54+
simplicity_free(analysis);
55+
}
56+
simplicity_free(type_dag);
57+
}
58+
59+
simplicity_free(dag);
60+
return IS_PERMANENT(*error);
61+
}

0 commit comments

Comments
 (0)