Skip to content

Commit ec77233

Browse files
authored
feat: add fuzzer for Poseidon2 and Pedersen hash functions (#16604)
Created a fuzzer for poseidon and pedersen
2 parents 83b4439 + a193d96 commit ec77233

File tree

4 files changed

+244
-1
lines changed

4 files changed

+244
-1
lines changed

barretenberg/cpp/cmake/module.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ function(barretenberg_module MODULE_NAME)
198198
${MODULE_NAME}_${FUZZER_NAME_STEM}_fuzzer
199199
PRIVATE
200200
${MODULE_LINK_NAME}
201+
${ARGN}
201202
)
202203
endforeach()
203204
endif()

barretenberg/cpp/src/barretenberg/stdlib/hash/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ add_subdirectory(blake3s)
33
add_subdirectory(pedersen)
44
add_subdirectory(sha256)
55
add_subdirectory(keccak)
6-
add_subdirectory(poseidon2)
6+
add_subdirectory(poseidon2)
7+
add_subdirectory(pfuzzer)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
if (FUZZING)
2+
barretenberg_module(
3+
stdlib_pfuzzer
4+
stdlib_poseidon2
5+
stdlib_pedersen_hash)
6+
endif()
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// === AUDIT STATUS ===
2+
// internal: { status: not started, auditors: [], date: YYYY-MM-DD }
3+
// external_1: { status: not started, auditors: [], date: YYYY-MM-DD }
4+
// external_2: { status: not started, auditors: [], date: YYYY-MM-DD }
5+
// =====================
6+
7+
/**
8+
* @file poseidon2_pedersen.fuzzer.cpp
9+
* @brief Fuzzer for testing Poseidon2 and Pedersen hash Ultra circuits against native implementations
10+
*
11+
* @details This fuzzer implements differential testing of both Poseidon2 and Pedersen hash function
12+
* circuits by comparing Ultra circuit outputs with native implementation results. The fuzzer:
13+
*
14+
* 1. **Dual Algorithm Testing**: Tests both Poseidon2 and Pedersen hash functions in a single fuzzer
15+
* 2. **Algorithm Selection**: Uses the first bit of input data to select between Poseidon2 (0) and Pedersen (1)
16+
* 3. **Structured Input Format**: Uses FieldVM data for deterministic field element generation
17+
* 4. **Ultra Circuit Testing**: Focuses on UltraCircuitBuilder for comprehensive circuit testing
18+
* 5. **Differential Testing**: Compares circuit output with trusted native implementation
19+
* 6. **Variable Input Lengths**: Tests inputs of any length (controlled by fuzzer configuration)
20+
* 7. **Edge Cases**: Tests zero inputs, repeated inputs, and boundary conditions
21+
* 8. **Circuit Verification**: Validates circuit correctness using CircuitChecker
22+
*
23+
*
24+
*/
25+
26+
#include "barretenberg/circuit_checker/circuit_checker.hpp"
27+
#include "barretenberg/crypto/pedersen_hash/pedersen.hpp"
28+
#include "barretenberg/crypto/poseidon2/poseidon2.hpp"
29+
#include "barretenberg/ecc/fields/field.fuzzer.hpp"
30+
#include "barretenberg/numeric/random/engine.hpp"
31+
#include "barretenberg/numeric/uint256/uint256.hpp"
32+
#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp"
33+
#include "barretenberg/stdlib/hash/poseidon2/poseidon2.hpp"
34+
#include "barretenberg/stdlib/primitives/curves/bn254.hpp"
35+
#include <cassert>
36+
#include <cstdint>
37+
#include <cstring>
38+
#include <iostream>
39+
#include <vector>
40+
41+
using namespace bb;
42+
using Fr = fr;
43+
using native_poseidon2 = crypto::Poseidon2<crypto::Poseidon2Bn254ScalarFieldParams>;
44+
using native_pedersen = crypto::pedersen_hash;
45+
46+
// Input structure constants
47+
static constexpr size_t SINGLE_CHUNK_SIZE = 129; // 1 byte for selection of element + 128 bytes for FieldVM data
48+
49+
/**
50+
* @brief Parse input structure and generate field elements using FieldVM
51+
* @param data Raw input data
52+
* @param size Data size
53+
* @return Vector of field elements for Poseidon2
54+
*/
55+
std::vector<Fr> parse_input_and_generate_elements(const uint8_t* data, size_t size)
56+
{
57+
std::vector<Fr> elements;
58+
59+
// Need at least header size
60+
if (size < SINGLE_CHUNK_SIZE) {
61+
return elements;
62+
}
63+
64+
// Parse header: first byte is number of elements (0-128)
65+
size_t num_elements = size / SINGLE_CHUNK_SIZE;
66+
67+
// Create FieldVM instance for field element generation
68+
FieldVM<Fr> field_vm(false, 65536); // Disable debug, max 65536 steps
69+
70+
// Disable heavy operations for better performance
71+
field_vm.settings.enable_inv = false; // Disable inversion
72+
field_vm.settings.enable_sqrt = false; // Disable square root
73+
field_vm.settings.enable_batch_invert = false; // Disable batch inversion
74+
field_vm.settings.enable_pow = false; // Disable power operation
75+
field_vm.settings.enable_div = false; // Disable division
76+
field_vm.settings.enable_div_assign = false; // Disable division assignment
77+
78+
// Run FieldVM with data after header (bytes 129+)
79+
size_t fieldvm_data_size = size - num_elements;
80+
if (fieldvm_data_size > 0) {
81+
field_vm.run(data, fieldvm_data_size);
82+
}
83+
84+
// Extract elements based on indices in header
85+
elements.reserve(num_elements);
86+
for (size_t i = 0; i < num_elements; ++i) {
87+
uint8_t index_byte = data[fieldvm_data_size + i]; // Bytes 1-128 contain indices
88+
89+
size_t field_index = index_byte % 32; // Wrap around if needed
90+
91+
// Get element from FieldVM state
92+
Fr element = field_vm.field_internal_state[field_index];
93+
elements.emplace_back(element);
94+
}
95+
96+
return elements;
97+
}
98+
99+
/**
100+
* @brief Test Poseidon2 circuit with specified builder type
101+
* @tparam Builder Circuit builder type
102+
* @param inputs Vector of field elements to hash
103+
* @return true if test passes, false otherwise
104+
*/
105+
template <typename Builder> bool test_poseidon2_circuit(const std::vector<Fr>& inputs)
106+
{
107+
try {
108+
using field_ct = stdlib::field_t<Builder>;
109+
using witness_ct = stdlib::witness_t<Builder>;
110+
111+
Builder builder;
112+
std::vector<field_ct> circuit_inputs;
113+
circuit_inputs.reserve(inputs.size());
114+
115+
// Convert native field elements to circuit witnesses
116+
for (const auto& input : inputs) {
117+
circuit_inputs.emplace_back(field_ct(witness_ct(&builder, input)));
118+
}
119+
120+
// Compute hash using circuit
121+
auto circuit_result = stdlib::poseidon2<Builder>::hash(builder, circuit_inputs);
122+
123+
// Compute hash using native implementation
124+
auto native_result = native_poseidon2::hash(inputs);
125+
126+
// Compare results
127+
if (circuit_result.get_value() != native_result) {
128+
std::cerr << "Poseidon2 circuit mismatch detected!" << std::endl;
129+
std::cerr << "Input length: " << inputs.size() << std::endl;
130+
std::cerr << "Circuit result: " << circuit_result.get_value() << std::endl;
131+
std::cerr << "Native result: " << native_result << std::endl;
132+
return false;
133+
}
134+
135+
// Verify circuit correctness
136+
bool circuit_check = CircuitChecker::check(builder);
137+
if (!circuit_check) {
138+
std::cerr << "Poseidon2 circuit check failed!" << std::endl;
139+
std::cerr << "Input length: " << inputs.size() << std::endl;
140+
return false;
141+
}
142+
143+
return true;
144+
145+
} catch (const std::exception& e) {
146+
std::cerr << "Exception in Poseidon2 circuit test: " << e.what() << std::endl;
147+
std::cerr << "Input length: " << inputs.size() << std::endl;
148+
return false;
149+
}
150+
}
151+
/**
152+
* @brief Test Pedersen circuit with specified builder type
153+
* @tparam Builder Circuit builder type
154+
* @param inputs Vector of field elements to hash
155+
* @return true if test passes, false otherwise
156+
*/
157+
template <typename Builder> bool test_pedersen_circuit(const std::vector<Fr>& inputs)
158+
{
159+
try {
160+
using field_ct = stdlib::field_t<Builder>;
161+
using witness_ct = stdlib::witness_t<Builder>;
162+
163+
Builder builder;
164+
std::vector<field_ct> circuit_inputs;
165+
circuit_inputs.reserve(inputs.size());
166+
167+
// Convert native field elements to circuit witnesses
168+
for (const auto& input : inputs) {
169+
circuit_inputs.emplace_back(field_ct(witness_ct(&builder, input)));
170+
}
171+
172+
// Compute hash using circuit
173+
auto circuit_result = stdlib::pedersen_hash<Builder>::hash(circuit_inputs);
174+
175+
// Compute hash using native implementation
176+
auto native_result = native_pedersen::hash(inputs);
177+
178+
// Compare results
179+
if (circuit_result.get_value() != native_result) {
180+
std::cerr << "Pedersen circuit mismatch detected!" << std::endl;
181+
std::cerr << "Input length: " << inputs.size() << std::endl;
182+
std::cerr << "Circuit result: " << circuit_result.get_value() << std::endl;
183+
std::cerr << "Native result: " << native_result << std::endl;
184+
return false;
185+
}
186+
187+
// Verify circuit correctness
188+
bool circuit_check = CircuitChecker::check(builder);
189+
if (!circuit_check) {
190+
std::cerr << "Pedersen circuit check failed!" << std::endl;
191+
std::cerr << "Input length: " << inputs.size() << std::endl;
192+
return false;
193+
}
194+
195+
return true;
196+
197+
} catch (const std::exception& e) {
198+
std::cerr << "Exception in Pedersen circuit test: " << e.what() << std::endl;
199+
std::cerr << "Input length: " << inputs.size() << std::endl;
200+
return false;
201+
}
202+
}
203+
204+
/**
205+
* @brief Main fuzzer entry point
206+
* @param Data Input data from libfuzzer
207+
* @param Size Size of input data
208+
* @return 0 for success, non-zero for failure
209+
*/
210+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size)
211+
{
212+
// Security check: Ensure minimum input size
213+
if (Size == 0) {
214+
return 0; // No input data
215+
}
216+
bool is_poseidon2 = Data[0] & 0x01;
217+
218+
// Parse input structure and generate field elements using FieldVM
219+
auto field_elements = parse_input_and_generate_elements(Data + 1, Size - 1);
220+
221+
// Security check: Ensure we have valid elements
222+
if (field_elements.empty()) {
223+
return 0; // No valid field elements generated
224+
}
225+
226+
// Test with Ultra circuit builder only
227+
bool test_result = is_poseidon2 ? test_poseidon2_circuit<UltraCircuitBuilder>(field_elements)
228+
: test_pedersen_circuit<UltraCircuitBuilder>(field_elements);
229+
230+
if (!test_result) {
231+
abort(); // Circuit test failed
232+
}
233+
234+
return 0; // Success
235+
}

0 commit comments

Comments
 (0)