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
40
41using namespace bb::avm2::simulation;
42using namespace bb::avm2::tracegen;
43using namespace bb::avm2::constraining;
44using namespace bb::avm2::fuzzing;
45
47using bb::avm2::FF;
48
51
52// We initialize it here once so it can be shared to other threads.
53// We don't use LLVMFuzzerInitialize since (IIUC) it is not thread safe and we want to run this
54// with multiple worker threads.
55static const TestTraceContainer precomputed_trace = []() {
58 // Up to 16 bits for the context id diff range check:
61 return t;
62}();
63
64// Each worker thread gets its own trace, initialized from precomputed_trace
65thread_local static TestTraceContainer trace = precomputed_trace;
66
67const int max_num_events = 20;
68const int max_calldata_fields = 20;
69const uint8_t default_calldata_fields = 16;
70
71extern "C" {
72__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_events = 1;
73}
74
76 uint8_t num_fields = default_calldata_fields; // The size of this calldata event
77 uint64_t selection_encoding = 0; // Element selection
78 uint8_t mutation = 0; // Mutation selection
79
81
82 void to_buffer(uint8_t* buffer) const
83 {
84 size_t offset = 0;
85 std::memcpy(buffer + offset, &num_fields, sizeof(num_fields));
86 offset += sizeof(num_fields);
87 std::memcpy(buffer + offset, &selection_encoding, sizeof(selection_encoding));
88 offset += sizeof(selection_encoding);
89 std::memcpy(buffer + offset, &mutation, sizeof(mutation));
90 }
91
93 {
95 size_t offset = 0;
96 std::memcpy(&input.num_fields, buffer + offset, sizeof(input.num_fields));
97 offset += sizeof(input.num_fields);
99 offset += sizeof(input.selection_encoding);
100 std::memcpy(&input.mutation, buffer + offset, sizeof(input.mutation));
101
102 return input;
103 }
104};
105
107 uint8_t num_events_input = 1; // The number of calldata events to process
108 uint16_t start_context_id = 1; // We assume that the context id is always incrementing
109
110 std::array<FF, default_calldata_fields> init_calldata_values{};
111 std::array<CalldataFuzzerInstance, max_num_events> calldata_instances{};
112
114
115 void print() const
116 {
117 info("start_context_id: ", start_context_id);
118 info("num_events_input: ", int(num_events_input));
119 for (size_t i = 0; i < init_calldata_values.size(); i++) {
120 info("init_calldata_value ", i, ": ", init_calldata_values[i]);
121 }
122 for (size_t i = 0; i < calldata_instances.size(); i++) {
123 info("calldata_instances ",
124 i,
125 ": ",
126 int(calldata_instances[i].num_fields),
127 ", ",
128 int(calldata_instances[i].selection_encoding),
129 ", ",
130 int(calldata_instances[i].mutation));
131 }
132 }
133
134 void to_buffer(uint8_t* buffer) const
135 {
136 size_t offset = 0;
138 offset += sizeof(num_events_input);
140 offset += sizeof(start_context_id);
142 offset += sizeof(FF) * init_calldata_values.size();
143 for (const auto& calldata_instance : calldata_instances) {
144 calldata_instance.to_buffer(buffer + offset);
146 }
147 }
148
150 {
152 size_t offset = 0;
154 offset += sizeof(input.num_events_input);
156 offset += sizeof(input.start_context_id);
157 std::memcpy(&input.init_calldata_values[0], buffer + offset, sizeof(FF) * input.init_calldata_values.size());
158 offset += sizeof(FF) * input.init_calldata_values.size();
159 for (auto& calldata_instance : input.calldata_instances) {
162 }
163
164 return input;
165 }
166};
167
168// Mutate a single random calldata instance
170{
171 // Modify a random calldata instance (using num_events to ensure it's used in a run)
173 size_t value_idx = index_dist(rng);
174 std::uniform_int_distribution<int> inner_mutation_dist(0, 2);
175 int inner_mutation_choice = inner_mutation_dist(rng);
176 switch (inner_mutation_choice) {
177 case 0: {
178 // Set mutation choice for calldata fields (see generate_calldata_values)
179 std::uniform_int_distribution<int> choice_dist(0, 2);
180 input.calldata_instances[value_idx].mutation = static_cast<uint8_t>(choice_dist(rng));
181 break;
182 }
183 case 1: {
184 // Set the number of fields
186 input.calldata_instances[value_idx].num_fields = num_fields_dist(rng);
187 break;
188 }
189 case 2: {
190 // Set selection encoding:
191 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
192 std::uniform_int_distribution<size_t> entry_dist(0, input.calldata_instances[value_idx].num_fields - 1);
193 size_t entry_idx = entry_dist(rng);
194 input.calldata_instances[value_idx].selection_encoding ^= (1ULL << entry_idx);
195 break;
196 }
197 default:
198 break;
199 }
200}
201
202// TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
204{
205 std::vector<std::vector<FF>> all_calldata_fields(input.num_events_input, std::vector<FF>(0));
206 for (size_t i = 0; i < input.num_events_input; i++) {
207 auto calldata_fuzzer_instance = input.calldata_instances[i];
208 all_calldata_fields[i].reserve(calldata_fuzzer_instance.num_fields);
209 size_t max_index =
210 std::min(static_cast<size_t>(calldata_fuzzer_instance.num_fields), input.init_calldata_values.size());
211 // Place initial values
212 for (size_t j = 0; j < max_index; j++) {
213 all_calldata_fields[i].emplace_back(input.init_calldata_values[j]);
214 }
215 // If size > init_calldata_values, fill gaps
216 for (size_t j = input.init_calldata_values.size(); j < calldata_fuzzer_instance.num_fields; j++) {
217 // Copied from memory.fuzzer:
218 auto entry_idx = (calldata_fuzzer_instance.selection_encoding >> j) % all_calldata_fields[i].size();
219 auto entry_value = all_calldata_fields[i].at(entry_idx);
220 FF modified_value = entry_value + input.init_calldata_values[j % input.init_calldata_values.size()];
221 all_calldata_fields[i].emplace_back(modified_value);
222 }
223 // If selected, mutate the calldata
224 switch (calldata_fuzzer_instance.mutation) {
225 case 1: {
226 // Duplicate previous calldata (or final calldata if this is the first)
227 all_calldata_fields[i] = all_calldata_fields[(i - 1) % input.num_events_input];
228 break;
229 }
230 case 2: {
231 // Set to empty calldata
232 all_calldata_fields[i] = {};
233 break;
234 }
235 case 0: // Do nothing
236 default:
237 break;
238 }
239 }
240
241 return all_calldata_fields;
242}
243
244extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
245{
246 if (size < sizeof(CalldataFuzzerInput)) {
247 // Initialize with default input
249 input.to_buffer(data);
250 return sizeof(CalldataFuzzerInput);
251 }
252
253 std::mt19937 rng(seed);
254 // Deserialize current input
256
257 // Choose random mutation
258 std::uniform_int_distribution<int> mutation_dist(0, 3);
259 int mutation_choice = mutation_dist(rng);
260
293 switch (mutation_choice) {
294 case 0: {
295 // Modify number of events
297 input.num_events_input = num_events_dist(rng);
298 break;
299 }
300 case 1: {
301 // Modify initial context id
303 0, std::numeric_limits<uint16_t>::max() - input.num_events_input - 1);
304 input.start_context_id = context_id_dist(rng);
305 break;
306 }
307 case 2: {
308 // Modify a random initial value
309 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
310 std::uniform_int_distribution<size_t> index_dist(0, input.init_calldata_values.size() - 1);
311 size_t value_idx = index_dist(rng);
312 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
313 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
314 input.init_calldata_values[value_idx] = value;
315 break;
316 }
317 case 3: {
318 // Modify a random calldata instance
319 mutate_calldata_instance(input, rng);
320 break;
321 }
322 default:
323 break;
324 }
325
326 input.to_buffer(data);
327
328 if (max_size > sizeof(CalldataFuzzerInput)) {
329 return sizeof(CalldataFuzzerInput);
330 }
331
332 return sizeof(CalldataFuzzerInput);
333}
334
335extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
336{
337 if (size < sizeof(CalldataFuzzerInput)) {
338 return 0;
339 }
340
342
343 // Set the libFuzzer extra counter from input
344 // LibFuzzer will track increases in this value as coverage progress
345 num_events = input.num_events_input;
346
348
349 // Set up gadgets and event emitters
350 EventEmitter<CalldataEvent> calldata_event_emitter;
354 uint32_t clk = 0;
359
362 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
363
366
367 // Using provider/interface to generate more hashers, so we can use different context ids over the trace:
368 CalldataHashingProvider calldata_hashing_provider(poseidon2, calldata_event_emitter);
369
370 uint32_t context_id = input.start_context_id;
371
372 // Execute operation
373 try {
374 for (size_t i = 0; i < num_events; i++) {
375 auto calldata_interface = calldata_hashing_provider.make_calldata_hasher(context_id++);
376 FF cd_hash = compute_calldata_hash(calldata_fields[i]);
377 calldata_interface->assert_calldata_hash(cd_hash, calldata_fields[i]);
378 }
379 } catch (const std::exception& e) {
380 // If any exception occurs, we cannot proceed further.
381 return 0;
382 }
383
389
390 range_check_builder.process(range_check_emitter.dump_events(), trace);
391 field_gt_builder.process(field_gt_emitter.dump_events(), trace);
392 gt_builder.process(greater_than_emitter.dump_events(), trace);
393
395
396 // We reuse the calldata events:
397 auto calldata_events = calldata_event_emitter.dump_events();
398 builder.process_retrieval(calldata_events, trace);
399 builder.process_hashing(calldata_events, trace);
400
401 if (getenv("AVM_DEBUG") != nullptr) {
402 info("Debugging trace:");
403 bb::avm2::InteractiveDebugger debugger(trace);
404 debugger.run();
405 }
406
407 check_relation<calldata_rel>(trace);
408 check_relation<calldata_hashing_rel>(trace);
409 // Individual for easily switching on/off hashing:
410 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_0_settings>(trace);
411 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_1_settings>(trace);
412 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_2_settings>(trace);
413 check_interaction<CalldataTraceBuilder, bb::avm2::perm_calldata_hashing_check_final_size_settings>(trace);
414 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_poseidon2_hash_settings>(trace);
415 // check_all_interactions<CalldataTraceBuilder>(trace);
416
417 // Reset the shared trace for the next run
418 for (uint32_t i = 1; i < trace.get_column_rows(Column::calldata_sel); i++) {
419 trace.set(i,
420 { {
421 { Column::calldata_sel, 0 },
422 { Column::calldata_context_id, 0 },
423 { Column::calldata_value, 0 },
424 { Column::calldata_index, 0 },
425 { Column::calldata_end, 0 },
426 } });
427 }
428
429 for (uint32_t i = 1; i < trace.get_column_rows(Column::calldata_hashing_sel); i++) {
430 trace.set(i,
431 { {
432 { Column::calldata_hashing_sel, 0 },
433 { Column::calldata_hashing_start, 0 },
434 { Column::calldata_hashing_sel_not_start, 0 },
435 { Column::calldata_hashing_context_id, 0 },
436 { Column::calldata_hashing_calldata_size, 0 },
437 { Column::calldata_hashing_input_len, 0 },
438 { Column::calldata_hashing_rounds_rem, 0 },
439 { Column::calldata_hashing_index_0_, 0 },
440 { Column::calldata_hashing_index_1_, 0 },
441 { Column::calldata_hashing_index_2_, 0 },
442 { Column::calldata_hashing_input_0_, 0 },
443 { Column::calldata_hashing_input_1_, 0 },
444 { Column::calldata_hashing_input_2_, 0 },
445 { Column::calldata_hashing_output_hash, 0 },
446 { Column::calldata_hashing_sel_not_padding_1, 0 },
447 { Column::calldata_hashing_sel_not_padding_2, 0 },
448 { Column::calldata_hashing_end, 0 },
449 { Column::calldata_hashing_sel_end_not_empty, 0 },
450 } });
451 }
452
453 return 0;
454}
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)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void process_sel_range_16(TraceContainer &trace)
Generate a selector column that activates the first 2^16 (65536) rows.
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:62
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
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