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
32
33using namespace bb::avm2::simulation;
34using namespace bb::avm2::tracegen;
35using namespace bb::avm2::constraining;
36using namespace bb::avm2::fuzzing;
37
38using bb::avm2::FF;
39
42
43// We initialize it here once so it can be shared to other threads.
44// We don't use LLVMFuzzerInitialize since (IIUC) it is not thread safe and we want to run this
45// with multiple worker threads.
46static const TestTraceContainer precomputed_trace = []() {
49 // Up to 16 bits for the context id diff range check:
52 return t;
53}();
54
55// Each worker thread gets its own trace, initialized from precomputed_trace
56thread_local static TestTraceContainer trace = precomputed_trace;
57
58const int max_num_events = 20;
59const int max_calldata_fields = 20;
60const uint8_t default_calldata_fields = 16;
61
62extern "C" {
63__attribute__((section("__libfuzzer_extra_counters"))) uint8_t num_events = 1;
64}
65
67 uint8_t num_fields = default_calldata_fields; // The size of this calldata event
68 uint64_t selection_encoding = 0; // Element selection
69 uint8_t mutation = 0; // Mutation selection
70
72
73 void to_buffer(uint8_t* buffer) const
74 {
75 size_t offset = 0;
76 std::memcpy(buffer + offset, &num_fields, sizeof(num_fields));
77 offset += sizeof(num_fields);
78 std::memcpy(buffer + offset, &selection_encoding, sizeof(selection_encoding));
79 offset += sizeof(selection_encoding);
80 std::memcpy(buffer + offset, &mutation, sizeof(mutation));
81 }
82
84 {
86 size_t offset = 0;
87 std::memcpy(&input.num_fields, buffer + offset, sizeof(input.num_fields));
88 offset += sizeof(input.num_fields);
90 offset += sizeof(input.selection_encoding);
91 std::memcpy(&input.mutation, buffer + offset, sizeof(input.mutation));
92
93 return input;
94 }
95};
96
98 uint8_t num_events_input = 1; // The number of calldata events to process
99 uint16_t start_context_id = 1; // We assume that the context id is always incrementing
100
101 std::array<FF, default_calldata_fields> init_calldata_values{};
102 std::array<CalldataFuzzerInstance, max_num_events> calldata_instances{};
103
105
106 void print() const
107 {
108 info("start_context_id: ", start_context_id);
109 info("num_events_input: ", int(num_events_input));
110 for (size_t i = 0; i < init_calldata_values.size(); i++) {
111 info("init_calldata_value ", i, ": ", init_calldata_values[i]);
112 }
113 for (size_t i = 0; i < calldata_instances.size(); i++) {
114 info("calldata_instances ",
115 i,
116 ": ",
117 int(calldata_instances[i].num_fields),
118 ", ",
119 int(calldata_instances[i].selection_encoding),
120 ", ",
121 int(calldata_instances[i].mutation));
122 }
123 }
124
125 void to_buffer(uint8_t* buffer) const
126 {
127 size_t offset = 0;
129 offset += sizeof(num_events_input);
131 offset += sizeof(start_context_id);
133 offset += sizeof(FF) * init_calldata_values.size();
134 for (const auto& calldata_instance : calldata_instances) {
135 calldata_instance.to_buffer(buffer + offset);
137 }
138 }
139
141 {
143 size_t offset = 0;
145 offset += sizeof(input.num_events_input);
147 offset += sizeof(input.start_context_id);
148 std::memcpy(&input.init_calldata_values[0], buffer + offset, sizeof(FF) * input.init_calldata_values.size());
149 offset += sizeof(FF) * input.init_calldata_values.size();
150 for (auto& calldata_instance : input.calldata_instances) {
153 }
154
155 return input;
156 }
157};
158
159// Mutate a single random calldata instance
161{
162 // Modify a random calldata instance (using num_events to ensure it's used in a run)
164 size_t value_idx = index_dist(rng);
165 std::uniform_int_distribution<int> inner_mutation_dist(0, 2);
166 int inner_mutation_choice = inner_mutation_dist(rng);
167 switch (inner_mutation_choice) {
168 case 0: {
169 // Set mutation choice for calldata fields (see generate_calldata_values)
170 std::uniform_int_distribution<int> choice_dist(0, 2);
171 input.calldata_instances[value_idx].mutation = static_cast<uint8_t>(choice_dist(rng));
172 break;
173 }
174 case 1: {
175 // Set the number of fields
177 input.calldata_instances[value_idx].num_fields = num_fields_dist(rng);
178 break;
179 }
180 case 2: {
181 // Set selection encoding:
182 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
183 std::uniform_int_distribution<size_t> entry_dist(0, input.calldata_instances[value_idx].num_fields - 1);
184 size_t entry_idx = entry_dist(rng);
185 input.calldata_instances[value_idx].selection_encoding ^= (1ULL << entry_idx);
186 break;
187 }
188 default:
189 break;
190 }
191}
192
193// TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
195{
196 std::vector<std::vector<FF>> all_calldata_fields(input.num_events_input, std::vector<FF>(0));
197 for (size_t i = 0; i < input.num_events_input; i++) {
198 auto calldata_fuzzer_instance = input.calldata_instances[i];
199 all_calldata_fields[i].reserve(calldata_fuzzer_instance.num_fields);
200 size_t max_index =
201 std::min(static_cast<size_t>(calldata_fuzzer_instance.num_fields), input.init_calldata_values.size());
202 // Place initial values
203 for (size_t j = 0; j < max_index; j++) {
204 all_calldata_fields[i].emplace_back(input.init_calldata_values[j]);
205 }
206 // If size > init_calldata_values, fill gaps
207 for (size_t j = input.init_calldata_values.size(); j < calldata_fuzzer_instance.num_fields; j++) {
208 // Copied from memory.fuzzer:
209 auto entry_idx = (calldata_fuzzer_instance.selection_encoding >> j) % all_calldata_fields[i].size();
210 auto entry_value = all_calldata_fields[i].at(entry_idx);
211 FF modified_value = entry_value + input.init_calldata_values[j % input.init_calldata_values.size()];
212 all_calldata_fields[i].emplace_back(modified_value);
213 }
214 // If selected, mutate the calldata
215 switch (calldata_fuzzer_instance.mutation) {
216 case 1: {
217 // Duplicate previous calldata (or final calldata if this is the first)
218 all_calldata_fields[i] = all_calldata_fields[(i - 1) % input.num_events_input];
219 break;
220 }
221 case 2: {
222 // Set to empty calldata
223 all_calldata_fields[i] = {};
224 break;
225 }
226 case 0: // Do nothing
227 default:
228 break;
229 }
230 }
231
232 return all_calldata_fields;
233}
234
235extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
236{
237 if (size < sizeof(CalldataFuzzerInput)) {
238 // Initialize with default input
240 input.to_buffer(data);
241 return sizeof(CalldataFuzzerInput);
242 }
243
244 std::mt19937 rng(seed);
245 // Deserialize current input
247
248 // Choose random mutation
249 std::uniform_int_distribution<int> mutation_dist(0, 3);
250 int mutation_choice = mutation_dist(rng);
251
284 switch (mutation_choice) {
285 case 0: {
286 // Modify number of events
288 input.num_events_input = num_events_dist(rng);
289 break;
290 }
291 case 1: {
292 // Modify initial context id
294 0, std::numeric_limits<uint16_t>::max() - input.num_events_input - 1);
295 input.start_context_id = context_id_dist(rng);
296 break;
297 }
298 case 2: {
299 // Modify a random initial value
300 // TODO(MW): Use mutate_calldata_vec (modify BASIC_VEC_MUTATION_CONFIGURATION for this fuzzer?)
301 std::uniform_int_distribution<size_t> index_dist(0, input.init_calldata_values.size() - 1);
302 size_t value_idx = index_dist(rng);
303 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
304 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
305 input.init_calldata_values[value_idx] = value;
306 break;
307 }
308 case 3: {
309 // Modify a random calldata instance
310 mutate_calldata_instance(input, rng);
311 break;
312 }
313 default:
314 break;
315 }
316
317 input.to_buffer(data);
318
319 if (max_size > sizeof(CalldataFuzzerInput)) {
320 return sizeof(CalldataFuzzerInput);
321 }
322
323 return sizeof(CalldataFuzzerInput);
324}
325
326extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
327{
328 if (size < sizeof(CalldataFuzzerInput)) {
329 return 0;
330 }
331
333
334 // Set the libFuzzer extra counter from input
335 // LibFuzzer will track increases in this value as coverage progress
336 num_events = input.num_events_input;
337
339
340 // Set up gadgets and event emitters
341 EventEmitter<CalldataEvent> calldata_event_emitter;
345 uint32_t clk = 0;
350
353 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
354
357
358 // Using provider/interface to generate more hashers, so we can use different context ids over the trace:
359 CalldataHashingProvider calldata_hashing_provider(poseidon2, calldata_event_emitter);
360
361 uint32_t context_id = input.start_context_id;
362
363 // Execute operation
364 try {
365 for (size_t i = 0; i < num_events; i++) {
366 auto calldata_interface = calldata_hashing_provider.make_calldata_hasher(context_id++);
367 FF cd_hash = compute_calldata_hash(calldata_fields[i]);
368 calldata_interface->assert_calldata_hash(cd_hash, calldata_fields[i]);
369 }
370 } catch (const std::exception& e) {
371 // If any exception occurs, we cannot proceed further.
372 return 0;
373 }
374
380
381 range_check_builder.process(range_check_emitter.dump_events(), trace);
382 field_gt_builder.process(field_gt_emitter.dump_events(), trace);
383 gt_builder.process(greater_than_emitter.dump_events(), trace);
384
386
387 // We reuse the calldata events:
388 auto calldata_events = calldata_event_emitter.dump_events();
389 builder.process_retrieval(calldata_events, trace);
390 builder.process_hashing(calldata_events, trace);
391
392 if (getenv("AVM_DEBUG") != nullptr) {
393 info("Debugging trace:");
394 bb::avm2::InteractiveDebugger debugger(trace);
395 debugger.run();
396 }
397
398 check_relation<calldata_rel>(trace);
399 check_relation<calldata_hashing_rel>(trace);
400 // Individual for easily switching on/off hashing:
401 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_range_check_context_id_diff_settings>(trace);
402 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_0_settings>(trace);
403 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_1_settings>(trace);
404 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_get_calldata_field_2_settings>(trace);
405 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_check_final_size_settings>(trace);
406 check_interaction<CalldataTraceBuilder, bb::avm2::lookup_calldata_hashing_poseidon2_hash_settings>(trace);
407 // check_all_interactions<CalldataTraceBuilder>(trace);
408
409 // Reset the shared trace for the next run
410 for (uint32_t i = 1; i < trace.get_column_rows(avm2::Column::calldata_sel); i++) {
411 trace.set(i,
412 { {
413 { avm2::Column::calldata_sel, 0 },
414 { avm2::Column::calldata_context_id, 0 },
415 { avm2::Column::calldata_value, 0 },
416 { avm2::Column::calldata_index, 0 },
417 { avm2::Column::calldata_latch, 0 },
418 { avm2::Column::calldata_diff_context_id, 0 },
419 } });
420 }
421
422 for (uint32_t i = 1; i < trace.get_column_rows(avm2::Column::calldata_hashing_sel); i++) {
423 trace.set(i,
424 { {
425 { avm2::Column::calldata_hashing_sel, 0 },
426 { avm2::Column::calldata_hashing_start, 0 },
427 { avm2::Column::calldata_hashing_sel_not_start, 0 },
428 { avm2::Column::calldata_hashing_context_id, 0 },
429 { avm2::Column::calldata_hashing_calldata_size, 0 },
430 { avm2::Column::calldata_hashing_input_len, 0 },
431 { avm2::Column::calldata_hashing_rounds_rem, 0 },
432 { avm2::Column::calldata_hashing_index_0_, 0 },
433 { avm2::Column::calldata_hashing_index_1_, 0 },
434 { avm2::Column::calldata_hashing_index_2_, 0 },
435 { avm2::Column::calldata_hashing_input_0_, 0 },
436 { avm2::Column::calldata_hashing_input_1_, 0 },
437 { avm2::Column::calldata_hashing_input_2_, 0 },
438 { avm2::Column::calldata_hashing_output_hash, 0 },
439 { avm2::Column::calldata_hashing_sel_not_padding_1, 0 },
440 { avm2::Column::calldata_hashing_sel_not_padding_2, 0 },
441 { avm2::Column::calldata_hashing_latch, 0 },
442 } });
443 }
444
445 return 0;
446}
DeduplicatingEventEmitter< FieldGreaterThanEvent > field_gt_emitter
GreaterThan greater_than
FieldGreaterThan field_gt
DeduplicatingEventEmitter< RangeCheckEvent > range_check_emitter
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
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)
void process(const simulation::EventEmitterInterface< simulation::GreaterThanEvent >::Container &events, TraceContainer &trace)
Definition gt_trace.cpp:11
void process_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
void process_misc(TraceContainer &trace, const uint32_t num_rows=MAX_AVM_TRACE_SIZE)
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
uint32_t get_column_rows(Column col) const
void set(Column col, uint32_t row, const FF &value)
void info(Args... args)
Definition log.hpp:89
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:36
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
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