Skip to content

Commit

Permalink
feat: Goblin Translator Fuzzer (#4752)
Browse files Browse the repository at this point in the history
A small and dumb fuzzer for Translator circuit builder and composer,
which helped find the finicky test issue.
Also adds a preset to compile with fuzzing+debug+asan
  • Loading branch information
Rumata888 authored Feb 26, 2024
1 parent d9a1853 commit 7402517
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 0 deletions.
22 changes: 22 additions & 0 deletions barretenberg/cpp/CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -377,6 +389,11 @@
"inherits": "clang16",
"configurePreset": "fuzzing"
},
{
"name": "fuzzing-asan",
"inherits": "clang16-dbg",
"configurePreset": "fuzzing-asan"
},
{
"name": "gperftools",
"inherits": "clang16",
Expand Down Expand Up @@ -501,6 +518,11 @@
"inherits": "default",
"configurePreset": "fuzzing"
},
{
"name": "fuzzing-asan",
"inherits": "clang16-dbg",
"configurePreset": "fuzzing-asan"
},
{
"name": "smt-verification",
"inherits": "clang16",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ECCOpQueue::ECCVMOperation>
*/
std::vector<ECCOpQueue::ECCVMOperation> parse_operations(const unsigned char* data, size_t size)
{
std::vector<ECCOpQueue::ECCVMOperation> 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::tuple<Fq, Fq, std::shared_ptr<ECCOpQueue>>>
*/
std::optional<std::tuple<Fq, Fq, std::shared_ptr<ECCOpQueue>>> parse_and_construct_opqueue(const unsigned char* data,
size_t size)
{
std::vector<ECCOpQueue::ECCVMOperation> 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<ECCOpQueue>();
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);
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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<bb::GoblinTranslatorFlavor::Transcript>();
prover_transcript->send_to_verifier("init", batching_challenge_init);
prover_transcript->export_proof();
Fq translation_batching_challenge = prover_transcript->template get_challenge<Fq>("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<bb::GoblinTranslatorFlavor::Transcript>(prover_transcript->proof_data);
verifier_transcript->template receive_from_prover<Fq>("init");
auto verifier = composer.create_verifier(circuit_builder, verifier_transcript);
bool verified = verifier.verify_proof(proof);
(void)checked;
(void)verified;
return 0;
}

0 comments on commit 7402517

Please sign in to comment.