Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
calldata.fuzzer.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <array>
3#include <cassert>
4#include <cstddef>
5#include <cstdint>
6#include <fuzzer/FuzzedDataProvider.h>
7#include <memory>
8#include <utility>
9#include <vector>
10
38
39using namespace bb::avm2::simulation;
40using namespace bb::avm2::tracegen;
41using namespace bb::avm2::constraining;
42using namespace bb::avm2::fuzzing;
43
44using bb::avm2::FF;
45
48
49// We initialize it here once so it can be shared to other threads.
50// We don't use LLVMFuzzerInitialize since (IIUC) it is not thread safe and we want to run this
51// with multiple worker threads.
52static const TestTraceContainer precomputed_trace = []() {
55 // Up to 16 bits for the context id diff range check:
58 return t;
59}();
60
61// Each worker thread gets its own trace, initialized from precomputed_trace
62thread_local static TestTraceContainer trace = precomputed_trace;
63
64const int max_num_events = 20;
65const int max_calldata_fields = 20;
66const uint8_t default_calldata_fields = 16;
67
68extern "C" {
69__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_events = 1;
70}
71
73 uint8_t num_fields = default_calldata_fields; // The size of this calldata event
74 uint64_t selection_encoding = 0; // Element selection
75 uint8_t mutation = 0; // Mutation selection
76
78
79 void to_buffer(uint8_t* buffer) const
80 {
81 size_t offset = 0;
82 std::memcpy(buffer + offset, &num_fields, sizeof(num_fields));
83 offset += sizeof(num_fields);
84 std::memcpy(buffer + offset, &selection_encoding, sizeof(selection_encoding));
85 offset += sizeof(selection_encoding);
86 std::memcpy(buffer + offset, &mutation, sizeof(mutation));
87 }
88
90 {
92 size_t offset = 0;
93 std::memcpy(&input.num_fields, buffer + offset, sizeof(input.num_fields));
94 offset += sizeof(input.num_fields);
96 offset += sizeof(input.selection_encoding);
97 std::memcpy(&input.mutation, buffer + offset, sizeof(input.mutation));
98
99 return input;
100 }
101};
102
104 uint8_t num_events_input = 1; // The number of calldata events to process
105 uint16_t start_context_id = 1; // We assume that the context id is always incrementing
106
107 std::array<FF, default_calldata_fields> init_calldata_values{};
108 std::array<CalldataFuzzerInstance, max_num_events> calldata_instances{};
109
111
112 void print() const
113 {
114 info("start_context_id: ", start_context_id);
115 info("num_events_input: ", int(num_events_input));
116 for (size_t i = 0; i < init_calldata_values.size(); i++) {
117 info("init_calldata_value ", i, ": ", init_calldata_values[i]);
118 }
119 for (size_t i = 0; i < calldata_instances.size(); i++) {
120 info("calldata_instances ",
121 i,
122 ": ",
123 int(calldata_instances[i].num_fields),
124 ", ",
125 int(calldata_instances[i].selection_encoding),
126 ", ",
127 int(calldata_instances[i].mutation));
128 }
129 }
130
131 void to_buffer(uint8_t* buffer) const
132 {
133 size_t offset = 0;
135 offset += sizeof(num_events_input);
137 offset += sizeof(start_context_id);
139 offset += sizeof(FF) * init_calldata_values.size();
140 for (const auto& calldata_instance : calldata_instances) {
141 calldata_instance.to_buffer(buffer + offset);
143 }
144 }
145
147 {
149 size_t offset = 0;
151 offset += sizeof(input.num_events_input);
153 offset += sizeof(input.start_context_id);
154 std::memcpy(&input.init_calldata_values[0], buffer + offset, sizeof(FF) * input.init_calldata_values.size());
155 offset += sizeof(FF) * input.init_calldata_values.size();
156 for (auto& calldata_instance : input.calldata_instances) {
159 }
160
161 return input;
162 }
163};
164
165// Mutate a single random calldata instance
167{
168 // Modify a random calldata instance (using num_events to ensure it's used in a run)
170 size_t value_idx = index_dist(rng);
171 std::uniform_int_distribution<int> inner_mutation_dist(0, 2);
172 int inner_mutation_choice = inner_mutation_dist(rng);
173 switch (inner_mutation_choice) {
174 case 0: {
175 // Set mutation choice for calldata fields (see generate_calldata_values)
176 std::uniform_int_distribution<int> choice_dist(0, 2);
177 input.calldata_instances[value_idx].mutation = static_cast<uint8_t>(choice_dist(rng));
178 break;
179 }
180 case 1: {
181 // Set the number of fields
183 input.calldata_instances[value_idx].num_fields = num_fields_dist(rng);
184 break;
185 }
186 case 2: {
187 // Set selection encoding:
188 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
189 std::uniform_int_distribution<size_t> entry_dist(0, input.calldata_instances[value_idx].num_fields - 1);
190 size_t entry_idx = entry_dist(rng);
191 input.calldata_instances[value_idx].selection_encoding ^= (1ULL << entry_idx);
192 break;
193 }
194 default:
195 break;
196 }
197}
198
199// TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
201{
202 std::vector<std::vector<FF>> all_calldata_fields(input.num_events_input, std::vector<FF>(0));
203 for (size_t i = 0; i < input.num_events_input; i++) {
204 auto calldata_fuzzer_instance = input.calldata_instances[i];
205 all_calldata_fields[i].reserve(calldata_fuzzer_instance.num_fields);
206 size_t max_index =
207 std::min(static_cast<size_t>(calldata_fuzzer_instance.num_fields), input.init_calldata_values.size());
208 // Place initial values
209 for (size_t j = 0; j < max_index; j++) {
210 all_calldata_fields[i].emplace_back(input.init_calldata_values[j]);
211 }
212 // If size > init_calldata_values, fill gaps
213 for (size_t j = input.init_calldata_values.size(); j < calldata_fuzzer_instance.num_fields; j++) {
214 // Copied from memory.fuzzer:
215 auto entry_idx = (calldata_fuzzer_instance.selection_encoding >> j) % all_calldata_fields[i].size();
216 auto entry_value = all_calldata_fields[i].at(entry_idx);
217 FF modified_value = entry_value + input.init_calldata_values[j % input.init_calldata_values.size()];
218 all_calldata_fields[i].emplace_back(modified_value);
219 }
220 // If selected, mutate the calldata
221 switch (calldata_fuzzer_instance.mutation) {
222 case 1: {
223 // Duplicate previous calldata (or final calldata if this is the first)
224 all_calldata_fields[i] = all_calldata_fields[(i - 1) % input.num_events_input];
225 break;
226 }
227 case 2: {
228 // Set to empty calldata
229 all_calldata_fields[i] = {};
230 break;
231 }
232 case 0: // Do nothing
233 default:
234 break;
235 }
236 }
237
238 return all_calldata_fields;
239}
240
241extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
242{
243 if (size < sizeof(CalldataFuzzerInput)) {
244 // Initialize with default input
246 input.to_buffer(data);
247 return sizeof(CalldataFuzzerInput);
248 }
249
250 std::mt19937 rng(seed);
251 // Deserialize current input
253
254 // Choose random mutation
255 std::uniform_int_distribution<int> mutation_dist(0, 3);
256 int mutation_choice = mutation_dist(rng);
257
290 switch (mutation_choice) {
291 case 0: {
292 // Modify number of events
294 input.num_events_input = num_events_dist(rng);
295 break;
296 }
297 case 1: {
298 // Modify initial context id
300 0, std::numeric_limits<uint16_t>::max() - input.num_events_input - 1);
301 input.start_context_id = context_id_dist(rng);
302 break;
303 }
304 case 2: {
305 // Modify a random initial value
306 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
307 std::uniform_int_distribution<size_t> index_dist(0, input.init_calldata_values.size() - 1);
308 size_t value_idx = index_dist(rng);
309 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
310 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
311 input.init_calldata_values[value_idx] = value;
312 break;
313 }
314 case 3: {
315 // Modify a random calldata instance
316 mutate_calldata_instance(input, rng);
317 break;
318 }
319 default:
320 break;
321 }
322
323 input.to_buffer(data);
324
325 if (max_size > sizeof(CalldataFuzzerInput)) {
326 return sizeof(CalldataFuzzerInput);
327 }
328
329 return sizeof(CalldataFuzzerInput);
330}
331
332extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
333{
334 if (size < sizeof(CalldataFuzzerInput)) {
335 return 0;
336 }
337
339
340 // Set the libFuzzer extra counter from input
341 // LibFuzzer will track increases in this value as coverage progress
342 num_events = input.num_events_input;
343
345
346 // Set up gadgets and event emitters
347 EventEmitter<CalldataEvent> calldata_event_emitter;
351 uint32_t clk = 0;
356
359 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
360
363
364 // Using provider/interface to generate more hashers, so we can use different context ids over the trace:
365 CalldataHashingProvider calldata_hashing_provider(poseidon2, calldata_event_emitter);
366
367 uint32_t context_id = input.start_context_id;
368
369 // Execute operation
370 try {
371 for (size_t i = 0; i < num_events; i++) {
372 auto calldata_interface = calldata_hashing_provider.make_calldata_hasher(context_id++);
373 FF cd_hash = compute_calldata_hash(calldata_fields[i]);
374 calldata_interface->assert_calldata_hash(cd_hash, calldata_fields[i]);
375 }
376 } catch (const std::exception& e) {
377 // If any exception occurs, we cannot proceed further.
378 return 0;
379 }
380
386
387 range_check_builder.process(range_check_emitter.dump_events(), trace);
388 field_gt_builder.process(field_gt_emitter.dump_events(), trace);
389 gt_builder.process(greater_than_emitter.dump_events(), trace);
390
392
393 // We reuse the calldata events:
394 auto calldata_events = calldata_event_emitter.dump_events();
395 builder.process_retrieval(calldata_events, trace);
396 builder.process_hashing(calldata_events, trace);
397
398 if (getenv("AVM_DEBUG") != nullptr) {
399 info("Debugging trace:");
400 bb::avm2::InteractiveDebugger debugger(trace);
401 debugger.run();
402 }
403
404 check_relation<calldata_rel>(trace);
405 check_relation<calldata_hashing_rel>(trace);
406 // Individual for easily switching on/off hashing:
407 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_range_check_context_id_diff_settings>(trace);
408 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_0_settings>(trace);
409 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_1_settings>(trace);
410 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_2_settings>(trace);
411 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_check_final_size_settings>(trace);
412 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_poseidon2_hash_settings>(trace);
413 // check_all_interactions<CalldataTraceBuilder>(trace);
414
415 // Reset the shared trace for the next run
416 for (uint32_t i = 1; i < trace.get_column_rows(avm2::Column::calldata_sel); i++) {
417 trace.set(i,
418 { {
419 { avm2::Column::calldata_sel, 0 },
420 { avm2::Column::calldata_context_id, 0 },
421 { avm2::Column::calldata_value, 0 },
422 { avm2::Column::calldata_index, 0 },
423 { avm2::Column::calldata_latch, 0 },
424 { avm2::Column::calldata_diff_context_id, 0 },
425 } });
426 }
427
428 for (uint32_t i = 1; i < trace.get_column_rows(avm2::Column::calldata_hashing_sel); i++) {
429 trace.set(i,
430 { {
431 { avm2::Column::calldata_hashing_sel, 0 },
432 { avm2::Column::calldata_hashing_start, 0 },
433 { avm2::Column::calldata_hashing_sel_not_start, 0 },
434 { avm2::Column::calldata_hashing_context_id, 0 },
435 { avm2::Column::calldata_hashing_calldata_size, 0 },
436 { avm2::Column::calldata_hashing_input_len, 0 },
437 { avm2::Column::calldata_hashing_rounds_rem, 0 },
438 { avm2::Column::calldata_hashing_index_0_, 0 },
439 { avm2::Column::calldata_hashing_index_1_, 0 },
440 { avm2::Column::calldata_hashing_index_2_, 0 },
441 { avm2::Column::calldata_hashing_input_0_, 0 },
442 { avm2::Column::calldata_hashing_input_1_, 0 },
443 { avm2::Column::calldata_hashing_input_2_, 0 },
444 { avm2::Column::calldata_hashing_output_hash, 0 },
445 { avm2::Column::calldata_hashing_sel_not_padding_1, 0 },
446 { avm2::Column::calldata_hashing_sel_not_padding_2, 0 },
447 { avm2::Column::calldata_hashing_latch, 0 },
448 } });
449 }
450
451 return 0;
452}
GreaterThan greater_than
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
FieldGreaterThan field_gt
EventEmitter< simulation::FieldGreaterThanEvent > field_gt_emitter
EventEmitter< simulation::RangeCheckEvent > range_check_emitter
const int max_calldata_fields
__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_events
void mutate_calldata_instance(CalldataFuzzerInput &input, std::mt19937 rng)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
size_t LLVMFuzzerCustomMutator(uint8_t *data, size_t size, size_t max_size, unsigned int seed)
const uint8_t default_calldata_fields
const int max_num_events
std::vector< std::vector< FF > > generate_calldata_values(const CalldataFuzzerInput &input)
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
std::unique_ptr< CalldataHashingInterface > make_calldata_hasher(uint32_t context_id) override
EventEmitter< Event >::Container dump_events()
void process(const simulation::EventEmitterInterface< simulation::FieldGreaterThanEvent >::Container &events, TraceContainer &trace)
Processes FieldGreaterThanEvent events and generates trace rows for the ff_gt gadget.
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Process the greater-than events and populate the relevant columns in the trace.
Definition gt_trace.cpp:20
void process_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
Processes the hash events for the Poseidon2 hash function. It populates the columns for the poseidon2...
void process_misc(TraceContainer &trace, const uint32_t num_rows=PRECOMPUTED_TRACE_SIZE)
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
Processes range check events and populates the trace with decomposed value columns.
uint32_t get_column_rows(Column col) const
void set(Column col, uint32_t row, const FF &value)
Native Poseidon2 hash function implementation.
Definition poseidon2.hpp:22
#define info(...)
Definition log.hpp:93
RangeCheckTraceBuilder range_check_builder
Definition alu.test.cpp:121
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
FieldGreaterThanTraceBuilder field_gt_builder
Definition alu.test.cpp:122
AluTraceBuilder builder
Definition alu.test.cpp:124
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
ExecutionIdManager execution_id_manager
const std::vector< MemoryValue > data
ssize_t offset
Definition engine.cpp:52
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:50
AVM range check gadget for witness generation.
crypto::Poseidon2< crypto::Poseidon2Bn254ScalarFieldParams > poseidon2
FF compute_calldata_hash(std::span< const FF > calldata)
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
uint32_t context_id
EventEmitter< CalldataEvent > calldata_events
CalldataFuzzerInput()=default
std::array< FF, default_calldata_fields > init_calldata_values
void to_buffer(uint8_t *buffer) const
static CalldataFuzzerInput from_buffer(const uint8_t *buffer)
std::array< CalldataFuzzerInstance, max_num_events > calldata_instances
CalldataFuzzerInstance()=default
static CalldataFuzzerInstance from_buffer(const uint8_t *buffer)
void to_buffer(uint8_t *buffer) const