mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge pull request #1384 from apoelstra/2024-12--simple-fuzz
simplicity: add fuzz target
This commit is contained in:
commit
e41b225107
5 changed files with 535 additions and 0 deletions
|
|
@ -219,6 +219,7 @@ endif
|
|||
if ENABLE_FUZZ_BINARY
|
||||
test_fuzz_fuzz_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES)
|
||||
test_fuzz_fuzz_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
|
||||
test_fuzz_fuzz_CFLAGS = $(AM_CFLAGS) $(PIE_FLAGS)
|
||||
test_fuzz_fuzz_LDADD = $(FUZZ_SUITE_LD_COMMON)
|
||||
test_fuzz_fuzz_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) $(RUNTIME_LDFLAGS)
|
||||
test_fuzz_fuzz_SOURCES = \
|
||||
|
|
@ -312,6 +313,9 @@ test_fuzz_fuzz_SOURCES = \
|
|||
test/fuzz/secp256k1_ecdsa_signature_parse_der_lax.cpp \
|
||||
test/fuzz/signature_checker.cpp \
|
||||
test/fuzz/signet.cpp \
|
||||
test/fuzz/simplicity_compute_amr.c \
|
||||
test/fuzz/simplicity.cpp \
|
||||
test/fuzz/simplicity_tx.cpp \
|
||||
test/fuzz/socks5.cpp \
|
||||
test/fuzz/span.cpp \
|
||||
test/fuzz/spanparsing.cpp \
|
||||
|
|
|
|||
226
src/test/fuzz/simplicity.cpp
Normal file
226
src/test/fuzz/simplicity.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <cstdio>
|
||||
#include <primitives/transaction.h>
|
||||
extern "C" {
|
||||
#include <simplicity/cmr.h>
|
||||
#include <simplicity/elements/env.h>
|
||||
#include <simplicity/elements/exec.h>
|
||||
}
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static uint256 GENESIS_HASH;
|
||||
|
||||
static CConfidentialAsset INPUT_ASSET_UNCONF{};
|
||||
static CConfidentialAsset INPUT_ASSET_CONF{};
|
||||
static CConfidentialValue INPUT_VALUE_UNCONF{};
|
||||
static CConfidentialValue INPUT_VALUE_CONF{};
|
||||
static CScript TAPROOT_SCRIPT_PUB_KEY{};
|
||||
static std::vector<unsigned char> TAPROOT_CONTROL{};
|
||||
static std::vector<unsigned char> TAPROOT_ANNEX(99, 0x50);
|
||||
//CMutableTransaction MTX_TEMPLATE{};
|
||||
|
||||
// Defined in simplicity_compute_amr.c
|
||||
extern "C" {
|
||||
bool simplicity_computeAmr( simplicity_err* error, unsigned char* amr
|
||||
, const unsigned char* program, size_t program_len
|
||||
, const unsigned char* witness, size_t witness_len);
|
||||
}
|
||||
|
||||
void initialize_simplicity()
|
||||
{
|
||||
g_con_elementsmode = true;
|
||||
|
||||
GENESIS_HASH = uint256S("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206");
|
||||
|
||||
INPUT_VALUE_UNCONF.SetToAmount(12345678);
|
||||
INPUT_VALUE_CONF.vchCommitment = {
|
||||
0x08,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
};
|
||||
|
||||
INPUT_ASSET_UNCONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment;
|
||||
INPUT_ASSET_UNCONF.vchCommitment[0] = 0x01;
|
||||
INPUT_ASSET_CONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment;
|
||||
INPUT_ASSET_CONF.vchCommitment[0] = 0x0a;
|
||||
|
||||
XOnlyPubKey intkey = XOnlyPubKey{uint256::ONE};
|
||||
XOnlyPubKey extkey = XOnlyPubKey{uint256::ONE};
|
||||
TAPROOT_SCRIPT_PUB_KEY = CScript{} << OP_1 << std::vector<unsigned char>(extkey.begin(), extkey.end());
|
||||
// TODO have control block of nontrivial path length
|
||||
TAPROOT_CONTROL.push_back(TAPROOT_LEAF_TAPSIMPLICITY | 1); // 1 is parity
|
||||
TAPROOT_CONTROL.insert(TAPROOT_CONTROL.end(), intkey.begin(), intkey.end());
|
||||
}
|
||||
|
||||
uint32_t read_u32(const unsigned char **buf) {
|
||||
uint32_t ret;
|
||||
memcpy(&ret, *buf, 4);
|
||||
*buf += 4;
|
||||
return le32toh(ret);
|
||||
}
|
||||
|
||||
#define MAX_LEN (1024 * 1024)
|
||||
|
||||
FUZZ_TARGET_INIT(simplicity, initialize_simplicity)
|
||||
{
|
||||
const unsigned char *buf = buffer.data();
|
||||
|
||||
uint32_t budget;
|
||||
uint32_t tx_data_len;
|
||||
uint32_t prog_data_len;
|
||||
uint32_t wit_data_len;
|
||||
|
||||
// 1. Sanitize and parse the buffer
|
||||
if (buffer.size() < 8) {
|
||||
return;
|
||||
}
|
||||
budget = read_u32(&buf);
|
||||
|
||||
tx_data_len = read_u32(&buf);
|
||||
if (tx_data_len > MAX_LEN || buffer.size() < tx_data_len + 12) {
|
||||
return;
|
||||
}
|
||||
const unsigned char *tx_data = buf;
|
||||
buf += tx_data_len;
|
||||
|
||||
prog_data_len = read_u32(&buf);
|
||||
if (prog_data_len > MAX_LEN || buffer.size() < tx_data_len + prog_data_len + 16) {
|
||||
return;
|
||||
}
|
||||
const unsigned char *prog_data = buf;
|
||||
buf += prog_data_len;
|
||||
|
||||
wit_data_len = read_u32(&buf);
|
||||
if (wit_data_len > MAX_LEN || buffer.size() != tx_data_len + prog_data_len + wit_data_len + 16) {
|
||||
return;
|
||||
}
|
||||
const unsigned char *wit_data = buf;
|
||||
|
||||
//printf("OK going\n");
|
||||
|
||||
// 2. Parse the transaction (the program and witness are just raw bytes)
|
||||
CMutableTransaction mtx;
|
||||
CDataStream txds{Span{tx_data, tx_data_len}, SER_NETWORK, INIT_PROTO_VERSION};
|
||||
try {
|
||||
txds >> mtx;
|
||||
mtx.witness.vtxinwit.resize(mtx.vin.size());
|
||||
mtx.witness.vtxoutwit.resize(mtx.vout.size());
|
||||
|
||||
// We use the first vin as a "random oracle" rather than reading more from
|
||||
// the fuzzer, because we want our fuzz seeds to have as simple a structure
|
||||
// as possible. This means we must reject 0-input transactions, which are
|
||||
// invalid on-chain anyway.
|
||||
if (mtx.vin.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is an assertion in the Simplicity interpreter. It is guaranteed
|
||||
// to hold for anything on the network since (even if validatepegin is off)
|
||||
// pegins are validated for well-formedness long before the script interpreter
|
||||
// is invoked. But in this code we just call the interpreter directly without
|
||||
// these checks.
|
||||
for (unsigned i = 0; i < mtx.vin.size(); i++) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (const std::ios_base::failure&) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2a. Pull the program and witness into vectors so they can be pushed onto the stack.
|
||||
std::vector<unsigned char> prog_bytes;
|
||||
std::vector<unsigned char> wit_bytes;
|
||||
prog_bytes.assign(prog_data, prog_data + prog_data_len);
|
||||
wit_bytes.assign(wit_data, wit_data + wit_data_len);
|
||||
|
||||
simplicity_err error;
|
||||
unsigned char cmr[32];
|
||||
unsigned char amr[32];
|
||||
assert(simplicity_computeAmr(&error, amr, prog_data, prog_data_len, wit_data, wit_data_len));
|
||||
assert(simplicity_computeCmr(&error, cmr, prog_data, prog_data_len));
|
||||
|
||||
// The remainder is just copy/pasted from the original fuzztest
|
||||
|
||||
// 3. Construct `nIn` and `spent_outs` array.
|
||||
//
|
||||
// Here we extract data from the first input's txid, since the fuzzer already
|
||||
// produced that as a random string which has no other meaning. So to avoid
|
||||
// complicating our seed encoding beyond "transaction then simplicity code"
|
||||
// we just use it as a random source.
|
||||
//
|
||||
// We do skip the first byte since that has pegin/issuance flag in it and
|
||||
// therefore already has semantic information.
|
||||
size_t nIn = mtx.vin[0].prevout.hash.data()[1] % mtx.vin.size();
|
||||
std::vector<CTxOut> spent_outs{};
|
||||
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
|
||||
// Null asset or value would assert in the interpreter, and are impossible
|
||||
// to hit in real transactions. Nonces are not included in the UTXO set and
|
||||
// therefore don't matter.
|
||||
CConfidentialValue value = i & 1 ? INPUT_VALUE_CONF : INPUT_VALUE_UNCONF;
|
||||
CConfidentialAsset asset = i & 2 ? INPUT_ASSET_CONF : INPUT_ASSET_UNCONF;
|
||||
CScript scriptPubKey;
|
||||
if (i != nIn) {
|
||||
// For scriptPubKeys we can use arbitrary scripts. We include the empty
|
||||
// script even though in a real transaction this would be impossible,
|
||||
// because it shouldn't break anything.
|
||||
for (unsigned int j = 0; j < i; j++) {
|
||||
scriptPubKey << OP_TRUE;
|
||||
}
|
||||
} else {
|
||||
scriptPubKey = TAPROOT_SCRIPT_PUB_KEY;
|
||||
}
|
||||
|
||||
spent_outs.push_back(CTxOut{asset, value, scriptPubKey});
|
||||
}
|
||||
assert(spent_outs.size() == mtx.vin.size());
|
||||
|
||||
// 4. Set up witness data
|
||||
mtx.witness.vtxinwit[nIn].scriptWitness.stack.clear();
|
||||
mtx.witness.vtxinwit[nIn].scriptWitness.stack.push_back(prog_bytes);
|
||||
mtx.witness.vtxinwit[nIn].scriptWitness.stack.push_back(TAPROOT_CONTROL);
|
||||
if (mtx.vin[0].prevout.hash.data()[2] & 1) {
|
||||
mtx.witness.vtxinwit[nIn].scriptWitness.stack.push_back(TAPROOT_ANNEX);
|
||||
}
|
||||
|
||||
// 5. Set up Simplicity environment and tx environment
|
||||
rawTapEnv simplicityRawTap;
|
||||
simplicityRawTap.controlBlock = TAPROOT_CONTROL.data();
|
||||
simplicityRawTap.pathLen = (TAPROOT_CONTROL.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
|
||||
simplicityRawTap.scriptCMR = cmr;
|
||||
|
||||
PrecomputedTransactionData txdata{GENESIS_HASH};
|
||||
std::vector<CTxOut> spent_outs_copy{spent_outs};
|
||||
txdata.Init(mtx, std::move(spent_outs_copy));
|
||||
assert(txdata.m_simplicity_tx_data != NULL);
|
||||
|
||||
// 4. Main test
|
||||
unsigned char imr_out[32];
|
||||
unsigned char *imr = mtx.vin[0].prevout.hash.data()[2] & 2 ? imr_out : NULL;
|
||||
|
||||
const transaction* tx = txdata.m_simplicity_tx_data;
|
||||
tapEnv* taproot = simplicity_elements_mallocTapEnv(&simplicityRawTap);
|
||||
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());
|
||||
|
||||
// 5. Secondary test -- try flipping a bunch of bits and check that this doesn't mess things up
|
||||
for (size_t j = 0; j < 8 * prog_bytes.size(); j++) {
|
||||
if (j > 32 && j % 23 != 0) continue; // skip most bits so this test doesn't overwhelm the fuzz time
|
||||
prog_bytes.data()[j / 8] ^= (1 << (j % 8));
|
||||
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());
|
||||
}
|
||||
|
||||
// 6. Cleanup
|
||||
free(taproot);
|
||||
}
|
||||
61
src/test/fuzz/simplicity_compute_amr.c
Normal file
61
src/test/fuzz/simplicity_compute_amr.c
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <simplicity/cmr.h>
|
||||
#include <simplicity/dag.h>
|
||||
#include <simplicity/deserialize.h> // simplicity_decodeMallocDag
|
||||
#include <simplicity/limitations.h> // DAG_LEN_MAX
|
||||
#include <simplicity/simplicity_alloc.h> // simplicity_free
|
||||
#include <simplicity/typeInference.h> // simplicity_mallocTypeInference
|
||||
#include <simplicity/elements/env.h>
|
||||
#include <simplicity/elements/exec.h>
|
||||
|
||||
// Copy of computeCmr used for AMR
|
||||
bool simplicity_computeAmr( simplicity_err* error, unsigned char* amr
|
||||
, const unsigned char* program, size_t program_len
|
||||
, const unsigned char* witness, size_t witness_len) {
|
||||
simplicity_assert(NULL != error);
|
||||
simplicity_assert(NULL != amr);
|
||||
simplicity_assert(NULL != program || 0 == program_len);
|
||||
simplicity_assert(NULL != witness || 0 == witness_len);
|
||||
|
||||
bitstream stream = initializeBitstream(program, program_len);
|
||||
dag_node* dag = NULL;
|
||||
combinator_counters census;
|
||||
int_fast32_t dag_len = simplicity_decodeMallocDag(&dag, &census, &stream);
|
||||
if (dag_len <= 0) {
|
||||
simplicity_assert(dag_len < 0);
|
||||
*error = (simplicity_err)dag_len;
|
||||
} else {
|
||||
simplicity_assert(NULL != dag);
|
||||
simplicity_assert((uint_fast32_t)dag_len <= DAG_LEN_MAX);
|
||||
*error = simplicity_closeBitstream(&stream);
|
||||
|
||||
type* type_dag = NULL;
|
||||
if (IS_OK(*error)) {
|
||||
*error = simplicity_mallocTypeInference(&type_dag, dag, (uint_fast32_t)dag_len, &census);
|
||||
}
|
||||
bitstream witness_stream;
|
||||
if (IS_OK(*error)) {
|
||||
witness_stream = initializeBitstream(witness, witness_len);
|
||||
*error = simplicity_fillWitnessData(dag, type_dag, (uint_fast32_t)dag_len, &witness_stream);
|
||||
}
|
||||
if (IS_OK(*error)) {
|
||||
*error = simplicity_closeBitstream(&witness_stream);
|
||||
if (SIMPLICITY_ERR_BITSTREAM_TRAILING_BYTES == *error) *error = SIMPLICITY_ERR_WITNESS_TRAILING_BYTES;
|
||||
if (SIMPLICITY_ERR_BITSTREAM_ILLEGAL_PADDING == *error) *error = SIMPLICITY_ERR_WITNESS_ILLEGAL_PADDING;
|
||||
}
|
||||
if (IS_OK(*error)) {
|
||||
analyses *analysis = (analyses*) simplicity_malloc((size_t)dag_len * sizeof(analyses));
|
||||
simplicity_assert(NULL != analysis);
|
||||
simplicity_computeAnnotatedMerkleRoot(analysis, dag, type_dag, (uint_fast32_t)dag_len);
|
||||
sha256_fromMidstate(amr, analysis[dag_len-1].annotatedMerkleRoot.s);
|
||||
simplicity_free(analysis);
|
||||
}
|
||||
simplicity_free(type_dag);
|
||||
}
|
||||
|
||||
simplicity_free(dag);
|
||||
return IS_PERMANENT(*error);
|
||||
}
|
||||
227
src/test/fuzz/simplicity_tx.cpp
Normal file
227
src/test/fuzz/simplicity_tx.cpp
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
// Copyright (c) 2020 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <cstdio>
|
||||
#include <span.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/sigcache.h>
|
||||
#include <validation.h>
|
||||
extern "C" {
|
||||
#include <simplicity/cmr.h>
|
||||
#include <simplicity/elements/env.h>
|
||||
#include <simplicity/elements/exec.h>
|
||||
}
|
||||
#include <test/fuzz/FuzzedDataProvider.h>
|
||||
#include <test/fuzz/fuzz.h>
|
||||
#include <test/fuzz/util.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static uint256 GENESIS_HASH;
|
||||
|
||||
static CConfidentialAsset INPUT_ASSET_UNCONF{};
|
||||
static CConfidentialAsset INPUT_ASSET_CONF{};
|
||||
static CConfidentialValue INPUT_VALUE_UNCONF{};
|
||||
static CConfidentialValue INPUT_VALUE_CONF{};
|
||||
|
||||
const unsigned int VERIFY_FLAGS = SCRIPT_VERIFY_NONE
|
||||
| SCRIPT_VERIFY_P2SH
|
||||
| SCRIPT_VERIFY_WITNESS
|
||||
| SCRIPT_VERIFY_DERSIG
|
||||
| SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY
|
||||
| SCRIPT_VERIFY_CHECKSEQUENCEVERIFY
|
||||
| SCRIPT_VERIFY_TAPROOT
|
||||
| SCRIPT_VERIFY_NULLDUMMY
|
||||
| SCRIPT_SIGHASH_RANGEPROOF
|
||||
| SCRIPT_VERIFY_SIMPLICITY;
|
||||
|
||||
void initialize_simplicity_tx()
|
||||
{
|
||||
g_con_elementsmode = true;
|
||||
// Copied from init.cpp AppInitMain
|
||||
InitSignatureCache();
|
||||
InitScriptExecutionCache();
|
||||
InitRangeproofCache();
|
||||
InitSurjectionproofCache();
|
||||
|
||||
GENESIS_HASH = uint256S("0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206");
|
||||
|
||||
INPUT_VALUE_UNCONF.SetToAmount(12345678);
|
||||
INPUT_VALUE_CONF.vchCommitment = {
|
||||
0x08,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
|
||||
0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
};
|
||||
|
||||
INPUT_ASSET_UNCONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment;
|
||||
INPUT_ASSET_UNCONF.vchCommitment[0] = 0x01;
|
||||
INPUT_ASSET_CONF.vchCommitment = INPUT_VALUE_CONF.vchCommitment;
|
||||
INPUT_ASSET_CONF.vchCommitment[0] = 0x0a;
|
||||
}
|
||||
|
||||
void write_u32(FILE *fh, uint32_t val) {
|
||||
unsigned char buf[4];
|
||||
|
||||
val = htole32(val);
|
||||
memcpy(buf, &val, 4);
|
||||
assert(fwrite(buf, 1, 4, fh) == 4);
|
||||
}
|
||||
|
||||
FUZZ_TARGET_INIT(simplicity_tx, initialize_simplicity_tx)
|
||||
{
|
||||
simplicity_err error;
|
||||
|
||||
// 1. (no-op) run through Rust code
|
||||
//
|
||||
// 2. Construct transaction.
|
||||
CMutableTransaction mtx;
|
||||
{
|
||||
CDataStream txds{buffer, SER_NETWORK, INIT_PROTO_VERSION};
|
||||
try {
|
||||
txds >> mtx;
|
||||
} catch (const std::ios_base::failure&) {
|
||||
return;
|
||||
}
|
||||
mtx.witness.vtxoutwit.resize(mtx.vout.size());
|
||||
|
||||
// If no inputs have witnesses, all the code below should continue to work -- we
|
||||
// should be able to call `PrecomputedTransactionData::Init` on a legacy transaction
|
||||
// without any trouble. In this case it will set txdata.m_simplicity_tx_data to
|
||||
// NULL, and we won't be able to go any further, but there should be no crashes
|
||||
// or memory issues.
|
||||
if (!mtx.witness.vtxinwit.empty()) {
|
||||
mtx.witness.vtxinwit.resize(mtx.vin.size());
|
||||
// This is an assertion in the Simplicity interpreter. It is guaranteed
|
||||
// to hold for anything on the network since (even if validatepegin is off)
|
||||
// pegins are validated for well-formedness long before the script interpreter
|
||||
// is invoked. But in this code we just call the interpreter directly without
|
||||
// these checks.
|
||||
for (unsigned i = 0; i < mtx.vin.size(); i++) {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We use the first vin as a "random oracle" rather than reading more from
|
||||
// the fuzzer, because we want our fuzz seeds to have as simple a structure
|
||||
// as possible. This means we must reject 0-input transactions, which are
|
||||
// invalid on-chain anyway.
|
||||
if (mtx.vin.size() == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto& random_bytes = mtx.vin[0].prevout.hash;
|
||||
|
||||
// 3. Construct `nIn` and `spent_outs` arrays.
|
||||
bool expect_simplicity = false;
|
||||
std::vector<CTxOut> spent_outs{};
|
||||
for (unsigned int i = 0; i < mtx.vin.size(); i++) {
|
||||
// Null asset or value would assert in the interpreter, and are impossible
|
||||
// to hit in real transactions. Nonces are not included in the UTXO set and
|
||||
// therefore don't matter.
|
||||
CConfidentialValue value = i & 1 ? INPUT_VALUE_CONF : INPUT_VALUE_UNCONF;
|
||||
CConfidentialAsset asset = i & 2 ? INPUT_ASSET_CONF : INPUT_ASSET_UNCONF;
|
||||
CScript scriptPubKey;
|
||||
if (i < random_bytes.size()) {
|
||||
if (i & 1 && random_bytes.data()[i] & 1) {
|
||||
value.vchCommitment[0] ^= 1;
|
||||
}
|
||||
if (i & 2 && random_bytes.data()[i] & 2) {
|
||||
asset.vchCommitment[0] ^= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for size 4: a Simplicity program will always have a witness, program,
|
||||
// CMR, control block and (maybe) annex, in that order. If the annex is present,
|
||||
// then checking for size 4 doesn't guarantee that a witness is present, but
|
||||
// that is ok at this point. (In fact, it is a useful thing to check.)
|
||||
if (i < mtx.witness.vtxinwit.size()) {
|
||||
auto& current = mtx.witness.vtxinwit[i].scriptWitness.stack;
|
||||
if (current.size() >= 4) {
|
||||
size_t top = current.size();
|
||||
if (!current[top - 1].empty() && current[top - 1][0] == 0x50) {
|
||||
--top;
|
||||
}
|
||||
const auto& control = current[top - 1];
|
||||
const auto& program = current[top - 3];
|
||||
|
||||
if (control.size() >= TAPROOT_CONTROL_BASE_SIZE && (control[0] & 0xfe) == 0xbe) {
|
||||
// The fuzzer won't be able to produce a valid CMR on its own, so we compute it
|
||||
// and jam it into the witness stack. But we do require the fuzzer give us a
|
||||
// place to put it, so we don't have to resize the stack (and so that actual
|
||||
// valid transactions will work with this code).
|
||||
// Compute CMR and do some sanity checks on it (and the program)
|
||||
std::vector<unsigned char> cmr(32, 0);
|
||||
assert(simplicity_computeCmr(&error, cmr.data(), program.data(), program.size()));
|
||||
const XOnlyPubKey internal{Span{control}.subspan(1, TAPROOT_CONTROL_BASE_SIZE - 1)};
|
||||
|
||||
const CScript leaf_script{cmr.begin(), cmr.end()};
|
||||
const uint256 tapleaf_hash = ComputeTapleafHash(0xbe, leaf_script);
|
||||
uint256 merkle_root = ComputeTaprootMerkleRoot(control, tapleaf_hash);
|
||||
auto ret = internal.CreateTapTweak(&merkle_root);
|
||||
if (ret.has_value()) {
|
||||
expect_simplicity = (error == SIMPLICITY_NO_ERROR);
|
||||
// Just drop the parity; it needs to match the one in the control block,
|
||||
// but we want to test that logic, so we allow them not to match.
|
||||
const XOnlyPubKey output_key = ret->first;
|
||||
// If we made it here, success (aside from parity maybe)
|
||||
current[top - 2] = std::move(cmr);
|
||||
scriptPubKey = CScript() << OP_1 << ToByteVector(output_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// For scripts that we're not using, set them to various witness programs to try to
|
||||
// trick the interpreter into treating them as taproot or simplicity outputs. It
|
||||
// should fail but shouldn't crash or anything.
|
||||
//
|
||||
// We don't cover all cases, so this may result in the empty scriptpubkey -- this is
|
||||
// impossible on-chain but it shouldn't hurt anything.
|
||||
if (scriptPubKey.empty()) {
|
||||
if (i < random_bytes.size()) {
|
||||
switch(random_bytes.data()[i] >> 6) {
|
||||
case 0:
|
||||
scriptPubKey << OP_TRUE;
|
||||
break;
|
||||
case 1:
|
||||
scriptPubKey << OP_0 << std::vector<unsigned char>(20, 0xab);
|
||||
break;
|
||||
case 2:
|
||||
scriptPubKey << OP_0 << std::vector<unsigned char>(32, 0xcd);
|
||||
break;
|
||||
case 3:
|
||||
scriptPubKey << OP_1 << std::vector<unsigned char>(32, 0xef);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spent_outs.push_back(CTxOut{asset, value, scriptPubKey});
|
||||
}
|
||||
assert(spent_outs.size() == mtx.vin.size());
|
||||
|
||||
// 4. Test via scriptcheck
|
||||
PrecomputedTransactionData txdata{GENESIS_HASH};
|
||||
std::vector<CTxOut> spent_outs_copy{spent_outs};
|
||||
txdata.Init(mtx, std::move(spent_outs_copy));
|
||||
if (expect_simplicity) {
|
||||
// The converse of this is not true -- if !expect_simplicity, it's still possible
|
||||
// that we will allocate Simplicity data. The check for whether to do this is very
|
||||
// lax: is this a 34-byte scriptPubKey that starts with OP_1 and does it have a
|
||||
// nonempty witness.
|
||||
assert(txdata.m_simplicity_tx_data != NULL);
|
||||
}
|
||||
|
||||
const CTransaction tx{mtx};
|
||||
for (unsigned i = 0; i < tx.vin.size(); i++) {
|
||||
CScriptCheck check{txdata.m_spent_outputs[i], tx, i, VERIFY_FLAGS, false /* cache */, &txdata};
|
||||
check();
|
||||
}
|
||||
}
|
||||
|
|
@ -79,7 +79,24 @@ implicit-integer-sign-change:blech32.cpp
|
|||
implicit-integer-sign-change:primitives/block.h
|
||||
implicit-integer-sign-change:primitives/confidential.cpp
|
||||
implicit-integer-sign-change:primitives/confidential.h
|
||||
shift-base:simplicity/jets.c
|
||||
unsigned-integer-overflow:simplicity/jets.c
|
||||
implicit-unsigned-integer-truncation:simplicity/jets.c
|
||||
shift-base:simplicity/sha256.c
|
||||
unsigned-integer-overflow:simplicity/sha256.c
|
||||
unsigned-integer-overflow:simplicity/secp256k1/modinv64_impl.h
|
||||
implicit-integer-sign-change:simplicity/secp256k1/modinv64_impl.h
|
||||
implicit-unsigned-integer-truncation:simplicity/secp256k1/modinv64_impl.h
|
||||
shift-base:simplicity/secp256k1/modinv64_impl.h
|
||||
shift-base:simplicity/secp256k1/field_5x52_impl.h
|
||||
unsigned-integer-overflow:simplicity/secp256k1/field_5x52_impl.h
|
||||
shift-base:simplicity/secp256k1/scalar_4x64_impl.h
|
||||
implicit-unsigned-integer-truncation:simplicity/secp256k1/scalar_4x64_impl.h
|
||||
unsigned-integer-overflow:simplicity/secp256k1/scalar_4x64_impl.h
|
||||
# This one, in secp256k1_ge_table_set_globalz, looks dangerous (len - 1 with
|
||||
# len = 0) but is ok since the rest of the function is guarded on len > 0.
|
||||
unsigned-integer-overflow:simplicity/secp256k1/group_impl.h
|
||||
# This one involves careful roconnor code
|
||||
shift-base:simplicity/frame.c
|
||||
# See comment in simplicity/primitive/elements/env.c line 303
|
||||
unsigned-integer-overflow:simplicity/primitive/elements/env.c
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue