Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
alu.fuzzer.cpp
Go to the documentation of this file.
1#include <cassert>
2#include <cstdint>
3#include <fuzzer/FuzzedDataProvider.h>
4
29
30using namespace bb::avm2::simulation;
31using namespace bb::avm2::tracegen;
32using namespace bb::avm2::constraining;
33using namespace bb::avm2::fuzzing;
34
35using bb::avm2::FF;
38
40
41namespace {
42
43struct AluFuzzerInput {
46 MemoryValue c = MemoryValue::from_tag(MemoryTag::FF, 0); // Placeholder for result
47 int op_id = 0; // For execution trace alu_op_id
48
49 // Serialize to buffer
50 void to_buffer(uint8_t* buffer) const
51 {
52 auto write_mem_value = [](uint8_t* target, MemoryValue mem_value) {
53 serialize::write(target, static_cast<uint8_t>(mem_value.get_tag()));
54 FF::serialize_to_buffer(mem_value.as_ff(), target);
55 };
56
57 write_mem_value(buffer, a);
58 buffer += sizeof(FF) + 1;
59 write_mem_value(buffer, b);
60 buffer += sizeof(FF) + 1;
61 write_mem_value(buffer, c);
62 buffer += sizeof(FF) + 1;
63 serialize::write(buffer, static_cast<uint16_t>(op_id));
64 }
65
66 static AluFuzzerInput from_buffer(const uint8_t* buffer)
67 {
68 auto read_mem_value = [](const uint8_t* src) -> MemoryValue {
69 uint8_t tag = 0;
70 serialize::read(src, tag);
72 };
73
74 AluFuzzerInput input;
75 input.a = read_mem_value(buffer);
76 buffer += sizeof(FF) + 1;
77 input.b = read_mem_value(buffer);
78 buffer += sizeof(FF) + 1;
79 input.c = read_mem_value(buffer);
80 buffer += sizeof(FF) + 1;
81 uint16_t op_id = 0;
82 serialize::read(buffer, op_id);
83 input.op_id = op_id;
84
85 return input;
86 }
87};
88
89} // namespace
90
91extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
92{
93 if (size < sizeof(AluFuzzerInput)) {
94 // Initialize with default input
95 AluFuzzerInput input;
96 input.to_buffer(data);
97 return sizeof(AluFuzzerInput);
98 }
99
100 std::mt19937_64 rng(seed);
101
102 auto op_likes_matched_tags = [](int op_id) -> bool {
103 switch (op_id) {
114 return true;
117 default:
118 return false;
119 }
120 };
121
122 auto random_mem_value_from_tag = [&rng](MemoryTag tag) -> MemoryValue {
123 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
124 // TODO(MW): Use array?
125 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
126 // Do we want the option of making "invalid tag" values, where the value is out of range for the tag?
127 // These aren't currently possible with this function since MemoryValue::from_tag will throw in that case.
129 };
130
131 auto random_mem_value = [&rng, random_mem_value_from_tag]() -> MemoryValue {
132 std::uniform_int_distribution<int> dist(0, int(MemoryTag::MAX));
133 MemoryTag tag = static_cast<MemoryTag>(dist(rng));
134 return random_mem_value_from_tag(tag);
135 };
136
137 // Deserialize current input
138 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
139
140 // Choose random ALU operation
142 input.op_id = 1 << dist(rng);
143
144 // Choose test case (TODO(MW): what else do we want here?)
146 int choice = dist(rng);
147
148 // Choose random tag and values
149 switch (choice) {
150 case 0: {
151 // Set a
152 input.a = random_mem_value();
153 break;
154 }
155 case 1: {
156 // Matching tags (if the op's happy path expects them)
157 if (op_likes_matched_tags(input.op_id)) {
158 input.b = random_mem_value_from_tag(input.a.get_tag());
159 } else {
160 // We deal with the below ops in TestOneInput
161 input.b = random_mem_value();
162 }
163 break;
164 }
165 case 2: {
166 // Mismatching tags (if the op's happy path expects a match)
167 input.b = random_mem_value();
168 if (op_likes_matched_tags(input.op_id)) {
169 while (input.b.get_tag() == input.a.get_tag()) {
170 input.b = random_mem_value();
171 }
172 }
173 break;
174 }
175 case 3: {
176 // Set a = b
177 input.a = input.b;
178 break;
179 }
180 case 4: {
181 // Swap a and b
182 std::swap(input.a, input.b);
183 break;
184 }
185 default:
186 break;
187 }
188
189 // Serialize mutated input back to buffer
190 input.to_buffer(data);
191
192 if (max_size > sizeof(AluFuzzerInput)) {
193 return sizeof(AluFuzzerInput);
194 }
195
196 return sizeof(AluFuzzerInput);
197}
198
199extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
200{
202
203 if (size < sizeof(AluFuzzerInput)) {
204 info("Input size too small");
205 return 0;
206 }
207
208 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
209 bool error = false; // For execution trace sel_err
210
211 // Set up gadgets and event emitters
216
219 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
221
222 // info("Fuzzing ALU with op_id =", input.op_id, ", a_tag =", input.a.to_string(), ", b_tag =",input.b.to_string());
223
224 // Pick and execute operation
225 try {
226 switch (input.op_id) {
228 input.c = alu.add(input.a, input.b);
229 assert(input.c == input.a + input.b);
230 break;
231 }
233 input.c = alu.sub(input.a, input.b);
234 assert(input.c == input.a - input.b);
235 break;
236 }
238 input.c = alu.mul(input.a, input.b);
239 assert(input.c == input.a * input.b);
240 break;
241 }
243 input.c = alu.div(input.a, input.b);
244 assert(input.c == input.a / input.b);
245 break;
246 }
248 input.c = alu.fdiv(input.a, input.b);
249 assert(input.c == input.a / input.b);
250 break;
251 }
253 input.c = alu.eq(input.a, input.b);
254 assert(input.c == (input.a == input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
255 : MemoryValue::from_tag(MemoryTag::U1, 0)));
256 break;
257 }
259 input.c = alu.lt(input.a, input.b);
260 assert(input.c == (input.a < input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
261 : MemoryValue::from_tag(MemoryTag::U1, 0)));
262 break;
263 }
265 input.c = alu.lte(input.a, input.b);
266 assert(input.c == (input.a <= input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
267 : MemoryValue::from_tag(MemoryTag::U1, 0)));
268 break;
269 }
271 // Reset b since if we error we need it to be zero for the trace
272 input.b = MemoryValue::from_tag(MemoryTag::FF, 0);
273 input.b = alu.op_not(input.a);
274 assert(input.b == ~input.a);
275 break;
276 }
278 input.c = alu.shr(input.a, input.b);
279 assert(input.c == (input.a >> input.b));
280 break;
281 }
283 input.c = alu.shl(input.a, input.b);
284 assert(input.c == (input.a << input.b));
285 break;
286 }
288 input.c = alu.truncate(input.a, input.b.get_tag());
289 break;
290 }
291 default:
292 return 0;
293 }
294 } catch (const AluException& e) {
295 // Expected alu exception (e.g., division by zero), but we should handle it
296 error = true;
297 }
298
299 TestTraceContainer trace = [&]() {
300 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
301 // For truncate we will test using a CAST
302 return TestTraceContainer({ {
303 { avm2::Column::execution_register_0_, input.a.as_ff() }, // = ia
304 { avm2::Column::execution_register_1_, input.c.as_ff() }, // = ic
305 { avm2::Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ic_tag
306 { avm2::Column::execution_rop_2_, static_cast<uint8_t>(input.b.get_tag()) }, // = truncate to tag
307 { avm2::Column::execution_sel_exec_dispatch_cast, 1 }, // = sel
308 { avm2::Column::execution_sel_opcode_error, 0 }, // = sel_err
309 } });
310 }
311 // Otherwise standard initialization of trace container and execution trace columns
312 return TestTraceContainer({ {
313 { avm2::Column::execution_mem_tag_reg_0_, static_cast<uint8_t>(input.a.get_tag()) }, // = ia_tag
314 { avm2::Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ib_tag
315 { avm2::Column::execution_mem_tag_reg_2_, static_cast<uint8_t>(input.c.get_tag()) }, // = ic_tag
316 { avm2::Column::execution_register_0_, input.a.as_ff() }, // = ia
317 { avm2::Column::execution_register_1_, input.b.as_ff() }, // = ib
318 { avm2::Column::execution_register_2_, input.c.as_ff() }, // = ic
319 { avm2::Column::execution_sel_exec_dispatch_alu, 1 }, // = sel
320 { avm2::Column::execution_sel_opcode_error, error ? 1 : 0 }, // = sel_err
321 { avm2::Column::execution_subtrace_operation_id, input.op_id }, // = alu_op_id
322 } });
323 }();
324
330
333 gt_builder.process(greater_than_emitter.dump_events(), trace);
334 builder.process(alu_emitter.dump_events(), trace);
335
336 // Precomputed values
340 precomputed_builder.process_misc(trace, 256); // Need enough for 8-bit range checks
341
342 if (getenv("AVM_DEBUG") != nullptr) {
343 info("Debugging trace:");
345 debugger.run();
346 }
347
348 check_relation<alu_rel>(trace);
349 check_all_interactions<AluTraceBuilder>(trace);
350
351 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
352 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_cast_settings>(trace);
353 } else {
354 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_alu_settings>(trace);
355 }
356
357 return 0;
358}
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)
DeduplicatingEventEmitter< FieldGreaterThanEvent > field_gt_emitter
GreaterThan greater_than
FieldGreaterThan field_gt
DeduplicatingEventEmitter< RangeCheckEvent > range_check_emitter
EventEmitter< AluEvent > alu_emitter
#define AVM_EXEC_OP_ID_ALU_TRUNCATE
#define AVM_EXEC_OP_ID_ALU_LTE
#define AVM_EXEC_OP_ID_ALU_DIV
#define AVM_EXEC_OP_ID_ALU_ADD
#define AVM_EXEC_OP_ID_ALU_SHL
#define AVM_EXEC_OP_ID_ALU_EQ
#define AVM_EXEC_OP_ID_ALU_SUB
#define AVM_EXEC_OP_ID_ALU_NOT
#define AVM_EXEC_OP_ID_ALU_MUL
#define AVM_EXEC_OP_ID_ALU_FDIV
#define AVM_EXEC_OP_ID_ALU_SHR
#define AVM_EXEC_OP_ID_ALU_LT
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
static TaggedValue from_tag(ValueTag tag, FF value)
EventEmitter< Event >::Container dump_events()
void process(const simulation::EventEmitterInterface< simulation::AluEvent >::Container &events, TraceContainer &trace)
Process the ALU events and populate the ALU relevant columns in the trace.
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_misc(TraceContainer &trace, const uint32_t num_rows=MAX_AVM_TRACE_SIZE)
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
void info(Args... args)
Definition log.hpp:75
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
const std::vector< MemoryValue > data
TestTraceContainer trace
FF a
FF b
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
MemoryValue read_mem_value(FuzzedDataProvider &fdp)
Read a MemoryValue from the fuzzed data provider.
ValueTag MemoryTag
AvmFlavorSettings::FF FF
Definition field.hpp:10
void read(auto &it, msgpack_concepts::HasMsgPack auto &obj)
Automatically derived read for any object that defines .msgpack() (implicitly defined by MSGPACK_FIEL...
void write(auto &buf, const msgpack_concepts::HasMsgPack auto &obj)
Automatically derived write for any object that defines .msgpack() (implicitly defined by MSGPACK_FIE...
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
T from_buffer(B const &buffer, size_t offset=0)
std::vector< uint8_t > to_buffer(T const &value)
static field serialize_from_buffer(const uint8_t *buffer)
static void serialize_to_buffer(const field &value, uint8_t *buffer)