-
Notifications
You must be signed in to change notification settings - Fork 293
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Goblin Translator Fuzzer (#4752)
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
Showing
4 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
barretenberg/cpp/src/barretenberg/proof_system/circuit_builder/goblin_translator.fuzzer.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
46 changes: 46 additions & 0 deletions
46
...rc/barretenberg/proof_system/circuit_builder/goblin_translator_circuit_builder.fuzzer.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
47 changes: 47 additions & 0 deletions
47
barretenberg/cpp/src/barretenberg/translator_vm/goblin_translator_composer.fuzzer.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |