Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
emit_unencrypted_log.fuzzer.cpp
Go to the documentation of this file.
2#include <cassert>
3#include <cstdint>
4
25
26using namespace bb::avm2::simulation;
27using namespace bb::avm2::tracegen;
28using namespace bb::avm2::constraining;
29using namespace bb::avm2::fuzzing;
30
31using bb::avm2::FF;
34
36
37const uint8_t default_log_fields = 16;
38// Set to slightly above the maximum size so we hit error_too_many_log_fields
40
44 uint32_t log_size = 0;
45 uint64_t selection_encoding = 0;
46 bool is_static = false;
48 false; // Since we generate log_size values in the test, we must pass a flag to modify their tag(s)
49
50 std::array<FF, default_log_fields> init_log_values{};
51
52 void print() const
53 {
54 info("contract_address: ", contract_address);
55 info("log_offset: ", log_offset);
56 info("log_size: ", log_size);
57 info("selection_encoding: ", selection_encoding);
58 info("is_static: ", is_static);
59 info("tag_mismatch: ", tag_mismatch);
60 for (size_t i = 0; i < init_log_values.size(); i++) {
61 info("init_log_value ", i, ": ", init_log_values[i]);
62 }
63 }
64
65 void to_buffer(uint8_t* buffer) const
66 {
67 size_t offset = 0;
69 offset += sizeof(contract_address);
71 offset += sizeof(log_offset);
73 offset += sizeof(log_size);
75 offset += sizeof(selection_encoding);
77 offset += sizeof(is_static);
79 offset += sizeof(tag_mismatch);
81 }
82
84 {
86 size_t offset = 0;
88 offset += sizeof(input.contract_address);
89 std::memcpy(&input.log_offset, buffer + offset, sizeof(input.log_offset));
90 offset += sizeof(input.log_offset);
91 std::memcpy(&input.log_size, buffer + offset, sizeof(input.log_size));
92 offset += sizeof(input.log_size);
94 offset += sizeof(input.selection_encoding);
95 std::memcpy(&input.is_static, buffer + offset, sizeof(input.is_static));
96 offset += sizeof(input.is_static);
97 std::memcpy(&input.tag_mismatch, buffer + offset, sizeof(input.tag_mismatch));
98 offset += sizeof(input.tag_mismatch);
99 std::memcpy(&input.init_log_values[0], buffer + offset, sizeof(FF) * input.init_log_values.size());
100
101 return input;
102 }
103
104 bool is_error() const
105 {
106 uint64_t end_log_address = static_cast<uint64_t>(log_offset) + static_cast<uint64_t>(log_size) - 1;
107 bool error_memory_out_of_bounds = end_log_address > AVM_HIGHEST_MEM_ADDRESS;
108 // TODO(MW): Since this fuzzer only emits one log for now, we just check its size, once it emits multiple
109 // we would need to check prev_emitted_log_fields + total_size:
110 bool error_too_many_log_fields = PUBLIC_LOG_HEADER_LENGTH + log_size > FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH;
111 return error_memory_out_of_bounds || error_too_many_log_fields || tag_mismatch || is_static;
112 }
113};
114
116{
117 return { .id = context->get_context_id(),
118 .pc = context->get_pc(),
119 .contract_addr = context->get_address(),
120 .is_static = context->get_is_static(),
121 .numUnencryptedLogFields =
122 context->get_side_effect_tracker().get_side_effects().get_num_unencrypted_log_fields() };
123}
124
125// TODO(MW): multiple events, std::vector<std::vector<FF>>
127{
128 std::vector<FF> log_fields;
129 auto total_log_fields_size = input.log_size + PUBLIC_LOG_HEADER_LENGTH;
130 log_fields.reserve(total_log_fields_size);
131 // Assign log length and address
132 log_fields.emplace_back(input.log_size);
133 log_fields.emplace_back(input.contract_address);
134
135 size_t max_index = std::min(static_cast<size_t>(input.log_size), input.init_log_values.size());
136 // Place initial values
137 for (size_t j = 0; j < max_index; j++) {
138 log_fields.emplace_back(input.init_log_values[j]);
139 }
140 // If size > init_log_values, fill gaps
141 for (size_t j = input.init_log_values.size(); j < input.log_size; j++) {
142 // Copied from memory.fuzzer:
143 auto entry_idx = (input.selection_encoding >> j) % log_fields.size();
144 // TODO(MW): make sure to exclude size/address fields?
145 auto entry_value = log_fields[entry_idx];
146 FF modified_value = entry_value + input.init_log_values[j % input.init_log_values.size()];
147 log_fields.emplace_back(modified_value);
148 }
149 // The first two fields are size (IS_WRITE_LOG_LENGTH) and contract address (is_write_contract_address), which are
150 // not read from memory, so we start at j = PUBLIC_LOG_HEADER_LENGTH
151 MemoryAddress addr = input.log_offset;
152 for (size_t j = PUBLIC_LOG_HEADER_LENGTH; j < total_log_fields_size; j++) {
153 mem->set(addr++, MemoryValue::from(log_fields[j]));
154 }
155
156 // Choose an index to set to an incorrect tag if we are testing a mismatch
157 if (input.tag_mismatch) {
158 size_t set_incorrect_tag_at = ((input.selection_encoding >> max_index) % log_fields.size()) + 2;
159 MemoryAddress addr = static_cast<MemoryAddress>(input.log_offset + set_incorrect_tag_at - 2);
160 MemoryTag incorrect_tag = MemoryTag::FF;
161 uint64_t incr = 0;
162 while (incorrect_tag == MemoryTag::FF) {
163 // TODO(MW): use rng here?
164 incorrect_tag =
165 static_cast<MemoryTag>((static_cast<uint64_t>(incorrect_tag) + input.selection_encoding + incr++) %
166 static_cast<uint64_t>(MemoryTag::MAX));
167 }
168 mem->set(addr, MemoryValue::from_tag_truncating(incorrect_tag, log_fields[set_incorrect_tag_at]));
169 }
170
171 return log_fields;
172}
173
174extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, size_t max_size, unsigned int seed)
175{
176 if (size < sizeof(EmitUnencryptedLogFuzzerInput)) {
177 // Initialize with default input
179 input.to_buffer(data);
180 return sizeof(EmitUnencryptedLogFuzzerInput);
181 }
182
183 std::mt19937_64 rng(seed);
184
185 // Deserialize current input
187
188 // Choose mutation case
190 int choice = dist(rng);
191 switch (choice) {
192 case 0: {
193 // Set contract address
194 std::uniform_int_distribution<uint64_t> addr_dist(0, std::numeric_limits<uint64_t>::max());
195 input.contract_address = FF(addr_dist(rng), addr_dist(rng), addr_dist(rng), addr_dist(rng));
196 break;
197 }
198 case 1: {
199 // Set log address
200 std::uniform_int_distribution<int> addr_change(-4000, 4000);
201 int new_addr = static_cast<int>(input.log_offset) + addr_change(rng);
202 input.log_offset = static_cast<uint32_t>(new_addr);
203 break;
204 }
205 case 2: {
206 // Set log size
208 input.log_size = num_fields_dist(rng);
209 break;
210 }
211 case 3: {
212 // Toggle selection encoding for a random entry, as long as this log is not empty
213 if (input.log_size != 0) {
214 std::uniform_int_distribution<size_t> entry_dist(0, input.log_size - 1);
215 size_t entry_idx = entry_dist(rng);
216 input.selection_encoding ^= (1ULL << entry_idx);
217 }
218 break;
219 }
220 case 4: {
221 // Modify a random initial value
222 std::uniform_int_distribution<size_t> index_dist(0, input.init_log_values.size() - 1);
223 size_t value_idx = index_dist(rng);
224 std::uniform_int_distribution<uint64_t> dist(0, std::numeric_limits<uint64_t>::max());
225 FF value = FF(dist(rng), dist(rng), dist(rng), dist(rng));
226 input.init_log_values[value_idx] = value;
227 break;
228 }
229 case 5: {
230 // Toggle error cases
231 // Note that memory out of bounds and too many log fields are already covered by other mutations
232 // TODO(MW): Add more? E.g. set incorrect log_size/log_offset in emit_unencrypted_log call?
234 int choice = err_dist(rng);
235 switch (choice) {
236 case 0: {
237 // Toggle is_static
238 input.is_static = !input.is_static;
239 break;
240 }
241 case 1: {
242 // Toggle tag_mismatch
243 input.tag_mismatch = !input.tag_mismatch;
244 break;
245 }
246 default:
247 break;
248 }
249 }
250 default:
251 break;
252 }
253
254 // Serialize mutated input back to buffer
255 input.to_buffer(data);
256
257 if (max_size > sizeof(EmitUnencryptedLogFuzzerInput)) {
258 return sizeof(EmitUnencryptedLogFuzzerInput);
259 }
260
261 return sizeof(EmitUnencryptedLogFuzzerInput);
262}
263
264extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
265{
267
268 if (size < sizeof(EmitUnencryptedLogFuzzerInput)) {
269 return 0;
270 }
271
273 bool error = false;
274
275 // Set up gadgets and event emitters
277
278 GadgetFuzzerContextHelper context_helper(input.contract_address, input.is_static, 1);
280 context_helper.execution_id_manager, context_helper.greater_than, emit_log_emitter);
281
282 auto context =
284
285 // TODO(MW): multiple log events
286 std::vector<FF> log_fields = generate_and_set_log_fields(input, &context->get_memory());
287
288 try {
289 emit_unencrypted_log.emit_unencrypted_log(
290 context->get_memory(), *context, input.contract_address, input.log_offset, input.log_size);
291 } catch (const EmitUnencryptedLogException& e) {
292 // TODO(MW): Ensure error is expected
293 error = true;
294 }
295
297
302 ExecutionTraceBuilder ex_builder;
304
306
307 if (!error) {
308 // TODO(MW): use below to check values:
309 // auto public_logs = side_effect_tracker.get_side_effects().public_logs;
310 trace.set(avm2::Column::public_inputs_cols_0_, pi_row, log_fields.size());
311 trace.set(avm2::Column::public_inputs_sel, pi_row, 1);
312
313 // Set public input columns
314 for (FF log_field : log_fields) {
315 pi_row++;
316 trace.set(avm2::Column::public_inputs_sel, pi_row, 1);
317 // Logs only use cols_0
318 trace.set(avm2::Column::public_inputs_cols_0_, pi_row, log_field);
319 }
320 }
321
322 // Precomputed values
326 precomputed_builder.process_misc(trace, pi_row + 1); // Need enough for public input columns
327
328 // TODO(MW): Properly set these via calls (lookup changed to perm recently)
329 // TODO(MW): Set before_context_event.prev_num_unencrypted_log_fields in multiple calls
330 ExecutionEvent ex_event = { .wire_instruction =
331 bb::avm2::testing::InstructionBuilder(WireOpCode::EMITUNENCRYPTEDLOG).build(),
332 .inputs = { MemoryValue::from<uint32_t>(input.log_size) },
333 .after_context_event = fill_context_event(context) };
334 ex_builder.process({ ex_event }, trace);
335 auto exec_log_row = trace.get_column_rows(avm2::Column::execution_sel_exec_dispatch_emit_unencrypted_log);
336 trace.set(avm2::Column::execution_rop_1_, exec_log_row - 1, input.log_offset);
337 trace.set(avm2::Column::execution_register_0_, exec_log_row - 1, input.log_size);
338 trace.set(avm2::Column::execution_sel_opcode_error, exec_log_row - 1, error ? 1 : 0);
339
340 range_check_builder.process(context_helper.range_check_emitter.dump_events(), trace);
341 field_gt_builder.process(context_helper.field_gt_emitter.dump_events(), trace);
342 gt_builder.process(context_helper.greater_than_emitter.dump_events(), trace);
343 builder.process(emit_log_emitter.dump_events(), trace);
344
345 if (getenv("AVM_DEBUG") != nullptr) {
346 info("Debugging trace:");
348 debugger.run();
349 }
350
351 check_relation<emit_log_rel>(trace);
352 check_all_interactions<EmitUnencryptedLogTraceBuilder>(trace);
353 check_interaction<ExecutionTraceBuilder, bb::avm2::perm_execution_dispatch_to_emit_unencrypted_log_settings>(trace);
354
355 return 0;
356}
#define FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH
#define PUBLIC_LOG_HEADER_LENGTH
#define AVM_HIGHEST_MEM_ADDRESS
#define AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_PUBLIC_LOGS_ROW_IDX
void run(uint32_t starting_row=0)
Definition debugger.cpp:76
static TaggedValue from(T value)
static TaggedValue from_tag_truncating(ValueTag tag, FF value)
Sets up gadgets and instance managers to provide a context for fuzzing. NOTE: rudimentary set up for ...
DeduplicatingEventEmitter< GreaterThanEvent > greater_than_emitter
std::unique_ptr< simulation::ContextInterface > make_enqueued_fuzzing_context(AztecAddress address=AztecAddress(0), AztecAddress msg_sender=AztecAddress(0), bool is_static=false, FF transaction_fee=FF(0), std::span< const FF > calldata={}, Gas gas_limit=GAS_LIMIT, Gas gas_used=GAS_USED_BY_PRIVATE, TransactionPhase phase=TransactionPhase::APP_LOGIC)
DeduplicatingEventEmitter< RangeCheckEvent > range_check_emitter
DeduplicatingEventEmitter< FieldGreaterThanEvent > field_gt_emitter
simulation::Instruction build() const
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::ExecutionEvent >::Container &ex_events, TraceContainer &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)
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
MemoryStore mem
const std::vector< MemoryValue > data
TestTraceContainer trace
const uint32_t max_log_fields
const uint8_t default_log_fields
ContextEvent fill_context_event(std::unique_ptr< ContextInterface > &context)
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)
std::vector< FF > generate_and_set_log_fields(const EmitUnencryptedLogFuzzerInput &input, MemoryInterface *mem)
ssize_t offset
Definition engine.cpp:36
uint8_t buffer[RANDOM_BUFFER_SIZE]
Definition engine.cpp:34
uint32_t MemoryAddress
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static EmitUnencryptedLogFuzzerInput from_buffer(const uint8_t *buffer)
std::array< FF, default_log_fields > init_log_values