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
34using bb::avm2::FF;
37
39
40namespace {
41
42struct AluFuzzerInput {
45 MemoryValue c = MemoryValue::from_tag(MemoryTag::FF, 0); // Placeholder for result
46 uint16_t op_id = 0; // For execution trace alu_op_id
47 // We serialise MemoryValues as FF + 1 byte for tag to save 31 bytes per value:
48 static const size_t size = (3 * (sizeof(FF) + 1)) + sizeof(uint16_t);
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;
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 < AluFuzzerInput::size) {
94 // Initialize with default input
95 AluFuzzerInput input;
96 input.to_buffer(data);
97 return AluFuzzerInput::size;
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 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
125 // Do we want the option of making "invalid tag" values, where the value is out of range for the tag?
126 // These aren't currently possible with this function since MemoryValue::from_tag will throw in that case.
128 };
129
130 auto random_mem_value = [&rng, random_mem_value_from_tag]() -> MemoryValue {
131 std::uniform_int_distribution<int> dist(0, int(MemoryTag::MAX));
132 MemoryTag tag = static_cast<MemoryTag>(dist(rng));
133 return random_mem_value_from_tag(tag);
134 };
135
136 // Deserialize current input
137 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
138
139 // Choose random ALU operation (11 possible operations with op_id = 2^index)
141 input.op_id = static_cast<uint16_t>(1 << dist(rng));
142
143 // Choose test case (TODO(MW): what else do we want here?)
145 int choice = dist(rng);
146
147 // Choose random tag and values
148 switch (choice) {
149 case 0: {
150 // Set a
151 input.a = random_mem_value();
152 break;
153 }
154 case 1: {
155 // Matching tags (if the op's happy path expects them)
156 if (op_likes_matched_tags(input.op_id)) {
157 input.b = random_mem_value_from_tag(input.a.get_tag());
158 } else {
159 // We deal with the below ops in TestOneInput
160 input.b = random_mem_value();
161 }
162 break;
163 }
164 case 2: {
165 // Mismatching tags (if the op's happy path expects a match)
166 input.b = random_mem_value();
167 if (op_likes_matched_tags(input.op_id)) {
168 while (input.b.get_tag() == input.a.get_tag()) {
169 input.b = random_mem_value();
170 }
171 }
172 break;
173 }
174 case 3: {
175 // Set a = b
176 input.a = input.b;
177 break;
178 }
179 case 4: {
180 // Swap a and b
181 std::swap(input.a, input.b);
182 break;
183 }
184 default:
185 break;
186 }
187
188 // Serialize mutated input back to buffer
189 input.to_buffer(data);
190
191 if (max_size > AluFuzzerInput::size) {
192 return AluFuzzerInput::size;
193 }
194
195 return AluFuzzerInput::size;
196}
197
198extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
199{
201
202 if (size < AluFuzzerInput::size) {
203 info("Input size too small");
204 return 0;
205 }
206
207 AluFuzzerInput input = AluFuzzerInput::from_buffer(data);
208 bool error = false; // For execution trace sel_err
209
210 // Set up gadgets and event emitters
215
218 GreaterThan greater_than(field_gt, range_check, greater_than_emitter);
220
221 // info("Fuzzing ALU with op_id =", input.op_id, ", a_tag =", input.a.to_string(), ", b_tag =",input.b.to_string());
222
223 // Pick and execute operation
224 try {
225 switch (input.op_id) {
227 input.c = alu.add(input.a, input.b);
228 assert(input.c == input.a + input.b);
229 break;
230 }
232 input.c = alu.sub(input.a, input.b);
233 assert(input.c == input.a - input.b);
234 break;
235 }
237 input.c = alu.mul(input.a, input.b);
238 assert(input.c == input.a * input.b);
239 break;
240 }
242 input.c = alu.div(input.a, input.b);
243 assert(input.c == input.a / input.b);
244 break;
245 }
247 input.c = alu.fdiv(input.a, input.b);
248 assert(input.c == input.a / input.b);
249 break;
250 }
252 input.c = alu.eq(input.a, input.b);
253 assert(input.c == (input.a == input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
254 : MemoryValue::from_tag(MemoryTag::U1, 0)));
255 break;
256 }
258 input.c = alu.lt(input.a, input.b);
259 assert(input.c == (input.a < input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
260 : MemoryValue::from_tag(MemoryTag::U1, 0)));
261 break;
262 }
264 input.c = alu.lte(input.a, input.b);
265 assert(input.c == (input.a <= input.b ? MemoryValue::from_tag(MemoryTag::U1, 1)
266 : MemoryValue::from_tag(MemoryTag::U1, 0)));
267 break;
268 }
270 // Reset b since if we error we need it to be zero for the trace
271 input.b = MemoryValue::from_tag(MemoryTag::FF, 0);
272 input.b = alu.op_not(input.a);
273 assert(input.b == ~input.a);
274 break;
275 }
277 input.c = alu.shr(input.a, input.b);
278 assert(input.c == (input.a >> input.b));
279 break;
280 }
282 input.c = alu.shl(input.a, input.b);
283 assert(input.c == (input.a << input.b));
284 break;
285 }
287 input.c = alu.truncate(input.a, input.b.get_tag());
288 break;
289 }
290 default:
291 return 0;
292 }
293 } catch (const AluException& e) {
294 // Expected alu exception (e.g., division by zero), but we should handle it
295 error = true;
296 }
297
298 TestTraceContainer trace = [&]() {
299 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
300 // For truncate we will test using a CAST
301 return TestTraceContainer({ {
302 { Column::execution_register_0_, input.a.as_ff() }, // = ia
303 { Column::execution_register_1_, input.c.as_ff() }, // = ic
304 { Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ic_tag
305 { Column::execution_rop_2_, static_cast<uint8_t>(input.b.get_tag()) }, // = truncate to tag
306 { Column::execution_sel_exec_dispatch_cast, 1 }, // = sel
307 { Column::execution_sel_opcode_error, 0 }, // = sel_err
308 } });
309 }
310 // Otherwise standard initialization of trace container and execution trace columns
311 return TestTraceContainer({ {
312 { Column::execution_mem_tag_reg_0_, static_cast<uint8_t>(input.a.get_tag()) }, // = ia_tag
313 { Column::execution_mem_tag_reg_1_, static_cast<uint8_t>(input.b.get_tag()) }, // = ib_tag
314 { Column::execution_mem_tag_reg_2_, static_cast<uint8_t>(input.c.get_tag()) }, // = ic_tag
315 { Column::execution_register_0_, input.a.as_ff() }, // = ia
316 { Column::execution_register_1_, input.b.as_ff() }, // = ib
317 { Column::execution_register_2_, input.c.as_ff() }, // = ic
318 { Column::execution_sel_exec_dispatch_alu, 1 }, // = sel
319 { Column::execution_sel_opcode_error, error ? 1 : 0 }, // = sel_err
320 { Column::execution_subtrace_operation_id, input.op_id }, // = alu_op_id
321 } });
322 }();
323
329
332 gt_builder.process(greater_than_emitter.dump_events(), trace);
333 builder.process(alu_emitter.dump_events(), trace);
334
335 // Precomputed values
339 precomputed_builder.process_misc(trace, 256); // Need enough for 8-bit range checks
340
341 if (getenv("AVM_DEBUG") != nullptr) {
342 info("Debugging trace:");
344 debugger.run();
345 }
346
347 check_relation<alu_rel>(trace);
348 check_all_interactions<AluTraceBuilder>(trace);
349
350 if (input.op_id == AVM_EXEC_OP_ID_ALU_TRUNCATE) {
351 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_cast_settings>(trace);
352 } else {
353 check_interaction<ExecutionTraceBuilder, bb::avm2::lookup_execution_dispatch_to_alu_settings>(trace);
354 }
355
356 return 0;
357}
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)
Populate miscellaneous precomputed columns: first_row selector and idx (row index).
void process_power_of_2(TraceContainer &trace)
Generate a column where row i holds 2^i, for i in [0, 255], for the values of 254 and 255 the values ...
void process_tag_parameters(TraceContainer &trace)
Populate the memory tag parameters table (byte length, max bits, max value per tag).
void process_sel_range_8(TraceContainer &trace)
Generate a selector column that activates the first 2^8 (256) rows.
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:60
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 SERIALIZATIO...
void write(auto &buf, const msgpack_concepts::HasMsgPack auto &obj)
Automatically derived write for any object that defines .msgpack() (implicitly defined by SERIALIZATI...
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)