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
28
29using namespace bb::avm2::simulation;
30using namespace bb::avm2::tracegen;
31using namespace bb::avm2::constraining;
32
33using bb::avm2::FF;
36
38
39namespace {
40
41struct AluFuzzerInput {
44 MemoryValue c = MemoryValue::from_tag(MemoryTag::FF, 0); // Placeholder for result
45 int op_id = 0; // For execution trace alu_op_id
46
47 // Serialize to buffer
48 void to_buffer(uint8_t* buffer) const
49 {
50 auto write_mem_value = [](uint8_t* target, MemoryValue mem_value) {
51 serialize::write(target, static_cast<uint8_t>(mem_value.get_tag()));
52 FF::serialize_to_buffer(mem_value.as_ff(), target);
53 };
54
55 write_mem_value(buffer, a);
56 buffer += sizeof(FF) + 1;
57 write_mem_value(buffer, b);
58 buffer += sizeof(FF) + 1;
59 write_mem_value(buffer, c);
60 buffer += sizeof(FF) + 1;
61 serialize::write(buffer, static_cast<uint16_t>(op_id));
62 }
63
64 static AluFuzzerInput from_buffer(const uint8_t* buffer)
65 {
66 auto read_mem_value = [](const uint8_t* src) -> MemoryValue {
67 uint8_t tag = 0;
68 serialize::read(src, tag);
70 };
71
72 AluFuzzerInput input;
73 input.a = read_mem_value(buffer);
74 buffer += sizeof(FF) + 1;
75 input.b = read_mem_value(buffer);
76 buffer += sizeof(FF) + 1;
77 input.c = read_mem_value(buffer);
78 buffer += sizeof(FF) + 1;
79 uint16_t op_id = 0;
80 serialize::read(buffer, op_id);
81 input.op_id = op_id;
82
83 return input;
84 }
85};
86
87} // namespace
88
89extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
90{
91 if (size < sizeof(AluFuzzerInput)) {
92 // Initialize with default input
93 AluFuzzerInput input;
94 input.to_buffer(data);
95 return sizeof(AluFuzzerInput);
96 }
97
98 std::mt19937_64 rng(seed);
99
100 auto op_likes_matched_tags = [](int op_id) -> bool {
101 switch (op_id) {
112 return true;
115 default:
116 return false;
117 }
118 };
119
120 auto random_mem_value_from_tag = [&rng](MemoryTag tag) -> MemoryValue {
121 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
122 // TODO(MW): Use array?
123 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
124 // Do we want the option of making "invalid tag" values, where the value is out of range for the tag?
125 // These aren't currently possible with this function since MemoryValue::from_tag will throw in that case.
127 };
128
129 auto random_mem_value = [&rng, random_mem_value_from_tag]() -> MemoryValue {
130 std::uniform_int_distribution<int> dist(0, int(MemoryTag::MAX));
131 MemoryTag tag = static_cast<MemoryTag>(dist(rng));
132 return random_mem_value_from_tag(tag);
133 };
134
135 // Deserialize current input
136 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
137
138 // Choose random ALU operation
140 input.op_id = 1 << dist(rng);
141
142 // Choose test case (TODO(MW): what else do we want here?)
144 int choice = dist(rng);
145
146 // Choose random tag and values
147 switch (choice) {
148 case 0: {
149 // Set a
150 input.a = random_mem_value();
151 break;
152 }
153 case 1: {
154 // Matching tags (if the op's happy path expects them)
155 if (op_likes_matched_tags(input.op_id)) {
156 input.b = random_mem_value_from_tag(input.a.get_tag());
157 } else {
158 // We deal with the below ops in TestOneInput
159 input.b = random_mem_value();
160 }
161 break;
162 }
163 case 2: {
164 // Mismatching tags (if the op's happy path expects a match)
165 input.b = random_mem_value();
166 if (op_likes_matched_tags(input.op_id)) {
167 while (input.b.get_tag() == input.a.get_tag()) {
168 input.b = random_mem_value();
169 }
170 }
171 break;
172 }
173 case 3: {
174 // Set a = b
175 input.a = input.b;
176 break;
177 }
178 case 4: {
179 // Swap a and b
180 std::swap(input.a, input.b);
181 break;
182 }
183 default:
184 break;
185 }
186
187 // Serialize mutated input back to buffer
188 input.to_buffer(data);
189
190 if (max_size > sizeof(AluFuzzerInput)) {
191 return sizeof(AluFuzzerInput);
192 }
193
194 return sizeof(AluFuzzerInput);
195}
196
197extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
198{
200
201 if (size < sizeof(AluFuzzerInput)) {
202 info("Input size too small");
203 return 0;
204 }
205
206 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
207 bool error = false; // For execution trace sel_err
208
209 // Set up gadgets and event emitters
214
217 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
219
220 // info("Fuzzing ALU with op_id =", input.op_id, ", a_tag =", input.a.to_string(), ", b_tag =",input.b.to_string());
221
222 // Pick and execute operation
223 try {
224 switch (input.op_id) {
226 input.c = alu.add(input.a, input.b);
227 assert(input.c == input.a + input.b);
228 break;
229 }
231 input.c = alu.sub(input.a, input.b);
232 assert(input.c == input.a - input.b);
233 break;
234 }
236 input.c = alu.mul(input.a, input.b);
237 assert(input.c == input.a * input.b);
238 break;
239 }
241 input.c = alu.div(input.a, input.b);
242 assert(input.c == input.a / input.b);
243 break;
244 }
246 input.c = alu.fdiv(input.a, input.b);
247 assert(input.c == input.a / input.b);
248 break;
249 }
251 input.c = alu.eq(input.a, input.b);
252 assert(input.c == (input.a == input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
253 : MemoryValue::from_tag(MemoryTag::U1, 0)));
254 break;
255 }
257 input.c = alu.lt(input.a, input.b);
258 assert(input.c == (input.a < input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
259 : MemoryValue::from_tag(MemoryTag::U1, 0)));
260 break;
261 }
263 input.c = alu.lte(input.a, input.b);
264 assert(input.c == (input.a <= input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
265 : MemoryValue::from_tag(MemoryTag::U1, 0)));
266 break;
267 }
269 // Reset b since if we error we need it to be zero for the trace
270 input.b = MemoryValue::from_tag(MemoryTag::FF, 0);
271 input.b = alu.op_not(input.a);
272 assert(input.b == ~input.a);
273 break;
274 }
276 input.c = alu.shr(input.a, input.b);
277 assert(input.c == (input.a >> input.b));
278 break;
279 }
281 input.c = alu.shl(input.a, input.b);
282 assert(input.c == (input.a << input.b));
283 break;
284 }
286 input.c = alu.truncate(input.a, input.b.get_tag());
287 break;
288 }
289 default:
290 return 0;
291 }
292 } catch (const AluException& e) {
293 // Expected alu exception (e.g., division by zero), but we should handle it
294 error = true;
295 }
296
297 TestTraceContainer trace = [&]() {
298 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
299 // For truncate we will test using a CAST
300 return TestTraceContainer({ {
301 { avm2::Column::execution_register_0_, input.a.as_ff() }, // = ia
302 { avm2::Column::execution_register_1_, input.c.as_ff() }, // = ic
303 { avm2::Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ic_tag
304 { avm2::Column::execution_rop_2_, static_cast<uint8_t>(input.b.get_tag()) }, // = truncate to tag
305 { avm2::Column::execution_sel_exec_dispatch_cast, 1 }, // = sel
306 { avm2::Column::execution_sel_opcode_error, 0 }, // = sel_err
307 } });
308 }
309 // Otherwise standard initialization of trace container and execution trace columns
310 return TestTraceContainer({ {
311 { avm2::Column::execution_mem_tag_reg_0_, static_cast<uint8_t>(input.a.get_tag()) }, // = ia_tag
312 { avm2::Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ib_tag
313 { avm2::Column::execution_mem_tag_reg_2_, static_cast<uint8_t>(input.c.get_tag()) }, // = ic_tag
314 { avm2::Column::execution_register_0_, input.a.as_ff() }, // = ia
315 { avm2::Column::execution_register_1_, input.b.as_ff() }, // = ib
316 { avm2::Column::execution_register_2_, input.c.as_ff() }, // = ic
317 { avm2::Column::execution_sel_exec_dispatch_alu, 1 }, // = sel
318 { avm2::Column::execution_sel_opcode_error, error ? 1 : 0 }, // = sel_err
319 { avm2::Column::execution_subtrace_operation_id, input.op_id }, // = alu_op_id
320 } });
321 }();
322
328
331 gt_builder.process(greater_than_emitter.dump_events(), trace);
332 builder.process(alu_emitter.dump_events(), trace);
333
334 // Precomputed values
338 precomputed_builder.process_misc(trace, 256); // Need enough for 8-bit range checks
339
340 if (getenv("AVM_DEBUG") != nullptr) {
341 info("Debugging trace:");
343 debugger.run();
344 }
345
346 check_relation<alu_rel>(trace);
347 check_all_interactions<AluTraceBuilder>(trace);
348
349 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
350 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_cast_settings>(trace);
351 } else {
352 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_alu_settings>(trace);
353 }
354
355 return 0;
356}
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: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
const std::vector< MemoryValue > data
TestTraceContainer trace
FF a
FF b
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
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)