diff --git a/barretenberg/cpp/CMakePresets.json b/barretenberg/cpp/CMakePresets.json index 002a19d456c..85e8c0c9490 100644 --- a/barretenberg/cpp/CMakePresets.json +++ b/barretenberg/cpp/CMakePresets.json @@ -131,6 +131,18 @@ "FUZZING": "ON" } }, + { + "name": "fuzzing-asan", + "displayName": "Build with fuzzing", + "description": "Build default preset but with fuzzing enabled", + "inherits": "clang16-dbg", + "binaryDir": "build-fuzzing", + "cacheVariables": { + "FUZZING": "ON", + "ENABLE_ASAN": "ON", + "DISABLE_ASM": "ON" + } + }, { "name": "smt-verification", "displayName": "Build with smt verificaiton", @@ -377,6 +389,11 @@ "inherits": "clang16", "configurePreset": "fuzzing" }, +{ + "name": "fuzzing-asan", + "inherits": "clang16-dbg", + "configurePreset": "fuzzing-asan" + }, { "name": "gperftools", "inherits": "clang16", @@ -501,6 +518,11 @@ "inherits": "default", "configurePreset": "fuzzing" }, +{ + "name": "fuzzing-asan", + "inherits": "clang16-dbg", + "configurePreset": "fuzzing-asan" + }, { "name": "smt-verification", "inherits": "clang16", diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp new file mode 100644 index 00000000000..57d54288c16 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp @@ -0,0 +1,84 @@ +/** + * @file goblin_translator.fuzzer.hpp + * @author Rumata888 + * @brief Contains common procedures used by the circuit builder fuzzer and the composer fuzzer + * @date 2024-02-25 + * + */ +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "goblin_translator_circuit_builder.hpp" + +using namespace bb; + +using Fr = curve::BN254::ScalarField; +using Fq = curve::BN254::BaseField; +using G1 = curve::BN254::AffineElement; + +/** + * @brief Parse raw operations for ECCOpQueue from the data stream + * + * @param data pointer to data + * @param size size in bytes + * @return std::vector + */ +std::vector parse_operations(const unsigned char* data, size_t size) +{ + std::vector raw_ops; + + size_t size_left = size; + // Just iterate and parse until there's no data left + while (size_left >= sizeof(ECCOpQueue::ECCVMOperation)) { + raw_ops.emplace_back((ECCOpQueue::ECCVMOperation*)(data + (size - size_left))); + size_left -= sizeof(ECCOpQueue::ECCVMOperation); + } + return raw_ops; +} + +/** + * @brief Try to parse out the batching and evaluating challenges and then the ECCOpQUeue from the data + * + * @param data pointer to the buffer + * @param size size of the buffer + * @return std::optional>> + */ +std::optional>> parse_and_construct_opqueue(const unsigned char* data, + size_t size) +{ + std::vector raw_ops; + + // Try to parse batching challenge + size_t size_left = size; + if (size_left < sizeof(uint256_t)) { + return {}; + } + const auto batching_challenge = Fq(*((uint256_t*)data)); + + // Try to parse evaluation challenge + size_left -= sizeof(uint256_t); + if (size_left < sizeof(uint256_t)) { + return {}; + } + const auto x = Fq(*((uint256_t*)data)); + if (x.is_zero()) { + return {}; + } + size_left -= sizeof(uint256_t); + + // Try to parse operations + raw_ops = parse_operations(data + (size - size_left), size_left); + if (raw_ops.empty()) { + return {}; + } + + // Add a padding element to avoid non-zero commitments + const auto p_x = uint256_t(0xd3c208c16d87cfd3, 0xd97816a916871ca8, 0x9b85045b68181585, 0x30644e72e131a02); + const auto p_y = uint256_t(0x3ce1cc9c7e645a83, 0x2edac647851e3ac5, 0xd0cbe61fced2bc53, 0x1a76dae6d3272396); + auto padding_element = G1(p_x, p_y); + auto padding_scalar = -Fr::one(); + auto ecc_op_queue = std::make_shared(); + ecc_op_queue->raw_ops = raw_ops; + ecc_op_queue->mul_accumulate(padding_element, padding_scalar); + + // Create circuit builder and feed the queue inside + return std::make_tuple(batching_challenge, x, ecc_op_queue); +} diff --git a/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.fuzzer.cpp b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.fuzzer.cpp new file mode 100644 index 00000000000..216eea10ca1 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.fuzzer.cpp @@ -0,0 +1,46 @@ +#include "barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp" +/** + * @brief A very primitive fuzzing harness, no interesting mutations, just parse and throw at the circuit builder + * + */ +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + // Parse the queue and challenges + // TODO(Rumata888): composer generates the initial challenge through FS, so we have to do that, too + auto parsing_result = parse_and_construct_opqueue(data, size); + if (!parsing_result.has_value()) { + return 0; + } + auto [batching_challenge, x, op_queue] = parsing_result.value(); + // Construct the circuit + auto circuit_builder = GoblinTranslatorCircuitBuilder(batching_challenge, x, op_queue); + + Fq x_inv = x.invert(); + auto op_accumulator = Fq(0); + auto p_x_accumulator = Fq(0); + auto p_y_accumulator = Fq(0); + auto z_1_accumulator = Fq(0); + auto z_2_accumulator = Fq(0); + // Compute the batched evaluation of polynomials (multiplying by inverse to go from lower to higher) + for (auto& ecc_op : op_queue->raw_ops) { + op_accumulator = op_accumulator * x_inv + ecc_op.get_opcode_value(); + p_x_accumulator = p_x_accumulator * x_inv + ecc_op.base_point.x; + p_y_accumulator = p_y_accumulator * x_inv + ecc_op.base_point.y; + z_1_accumulator = z_1_accumulator * x_inv + ecc_op.z1; + z_2_accumulator = z_2_accumulator * x_inv + ecc_op.z2; + } + Fq x_pow = x.pow(op_queue->raw_ops.size() - 1); + + // Multiply by an appropriate power of x to get rid of the inverses + Fq result = ((((z_2_accumulator * batching_challenge + z_1_accumulator) * batching_challenge + p_y_accumulator) * + batching_challenge + + p_x_accumulator) * + batching_challenge + + op_accumulator) * + x_pow; + + // The data is malformed, so just call check_circuit, but ignore the output + circuit_builder.check_circuit(); + (void)result; + return 0; +} \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/goblin_translator_composer.fuzzer.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/goblin_translator_composer.fuzzer.cpp new file mode 100644 index 00000000000..4e2ad508063 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/translator_vm/goblin_translator_composer.fuzzer.cpp @@ -0,0 +1,47 @@ +#include "barretenberg/translator_vm/goblin_translator_composer.hpp" +#include "barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp" +#include "barretenberg/translator_vm/goblin_translator_prover.hpp" +extern "C" void LLVMFuzzerInitialize(int*, char***) +{ + srs::init_crs_factory("../srs_db/ignition"); +} +/** + * @brief A very primitive fuzzer for the composer + * + * @details Super-slow. Shouldn't be run on its own. First you need to run the circuit builder fuzzer, then minimize the + * corpus and then just use that corpus + * + */ +extern "C" int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size) +{ + // Parse challenges and opqueue from data + auto parsing_result = parse_and_construct_opqueue(data, size); + if (!parsing_result.has_value()) { + return 0; + } + auto [batching_challenge_init, x, op_queue] = parsing_result.value(); + auto prover_transcript = std::make_shared(); + prover_transcript->send_to_verifier("init", batching_challenge_init); + prover_transcript->export_proof(); + Fq translation_batching_challenge = prover_transcript->template get_challenge("Translation:batching_challenge"); + + // Construct circuit + auto circuit_builder = GoblinTranslatorCircuitBuilder(translation_batching_challenge, x, op_queue); + + // Check that the circuit passes + bool checked = circuit_builder.check_circuit(); + + // Construct proof + auto composer = bb::GoblinTranslatorComposer(); + auto prover = composer.create_prover(circuit_builder, prover_transcript); + auto proof = prover.construct_proof(); + + // Verify proof + auto verifier_transcript = std::make_shared(prover_transcript->proof_data); + verifier_transcript->template receive_from_prover("init"); + auto verifier = composer.create_verifier(circuit_builder, verifier_transcript); + bool verified = verifier.verify_proof(proof); + (void)checked; + (void)verified; + return 0; +} \ No newline at end of file