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 uint16_t op_id = 0; // For execution trace alu_op_id
46 // We serialise MemoryValues as FF + 1 byte for tag to save 31 bytes per value:
47 static const size_t size = (3 * (sizeof(FF) + 1)) + sizeof(uint16_t);
48 // Serialize to buffer
49 void to_buffer(uint8_t* buffer) const
50 {
51 auto write_mem_value = [](uint8_t* target, MemoryValue mem_value) {
52 serialize::write(target, static_cast<uint8_t>(mem_value.get_tag()));
53 FF::serialize_to_buffer(mem_value.as_ff(), target);
54 };
55
56 write_mem_value(buffer, a);
57 buffer += sizeof(FF) + 1;
58 write_mem_value(buffer, b);
59 buffer += sizeof(FF) + 1;
60 write_mem_value(buffer, c);
61 buffer += sizeof(FF) + 1;
63 }
64
65 static AluFuzzerInput from_buffer(const uint8_t* buffer)
66 {
67 auto read_mem_value = [](const uint8_t* src) -> MemoryValue {
68 uint8_t tag = 0;
69 serialize::read(src, tag);
71 };
72
73 AluFuzzerInput input;
74 input.a = read_mem_value(buffer);
75 buffer += sizeof(FF) + 1;
76 input.b = read_mem_value(buffer);
77 buffer += sizeof(FF) + 1;
78 input.c = read_mem_value(buffer);
79 buffer += sizeof(FF) + 1;
80 uint16_t op_id = 0;
81 serialize::read(buffer, op_id);
82 input.op_id = op_id;
83
84 return input;
85 }
86};
87
88} // namespace
89
90extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
91{
92 if (size < AluFuzzerInput::size) {
93 // Initialize with default input
94 AluFuzzerInput input;
95 input.to_buffer(data);
96 return AluFuzzerInput::size;
97 }
98
99 std::mt19937_64 rng(seed);
100
101 auto op_likes_matched_tags = [](int op_id) -> bool {
102 switch (op_id) {
113 return true;
116 default:
117 return false;
118 }
119 };
120
121 auto random_mem_value_from_tag = [&rng](MemoryTag tag) -> MemoryValue {
122 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
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 (11 possible operations with op_id = 2^index)
140 input.op_id = static_cast<uint16_t>(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 > AluFuzzerInput::size) {
191 return AluFuzzerInput::size;
192 }
193
194 return AluFuzzerInput::size;
195}
196
197extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
198{
200
201 if (size < AluFuzzerInput::size) {
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)
GreaterThan greater_than
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
FieldGreaterThan field_gt
EventEmitter< simulation::FieldGreaterThanEvent > field_gt_emitter
EventEmitter< simulation::RangeCheckEvent > range_check_emitter
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)
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_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.
#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
const std::vector< MemoryValue > data
TestTraceContainer trace
FF a
FF b
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:50
AVM range check gadget for witness generation.
AvmFlavorSettings::FF FF
Definition field.hpp:10
ValueTag MemoryTag
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)