Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
emit_unencrypted_log.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
4#include <cstdint>
5
27
28namespace bb::avm2::constraining {
29namespace {
30
32using simulation::EmitUnencryptedLogWriteEvent;
33using simulation::TrackedSideEffects;
34using testing::PublicInputsBuilder;
35using tracegen::EmitUnencryptedLogTraceBuilder;
36using tracegen::MemoryTraceBuilder;
37using tracegen::PrecomputedTraceBuilder;
38using tracegen::PublicInputsTraceBuilder;
39using tracegen::TestTraceContainer;
41using C = Column;
42using emit_unencrypted_log = bb::avm2::emit_unencrypted_log<FF>;
43
44std::vector<MemoryValue> to_memory_values(const std::vector<FF>& fields)
45{
46 std::vector<MemoryValue> memory_values;
47 memory_values.reserve(fields.size());
48 for (const FF& field : fields) {
49 memory_values.push_back(MemoryValue::from<FF>(field));
50 }
51 return memory_values;
52}
53
54TEST(EmitUnencryptedLogConstrainingTest, EmptyTrace)
55{
56 check_relation<emit_unencrypted_log>(testing::empty_trace());
57}
58
59TEST(EmitUnencryptedLogConstrainingTest, Positive)
60{
61 AztecAddress address = 0xdeadbeef;
62 MemoryAddress log_address = 27;
63 const std::vector<FF> log_fields = { 4, 5 };
64 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
65 TrackedSideEffects side_effect_states = { .public_logs = {} };
66 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
67
68 EmitUnencryptedLogWriteEvent event = {
69 .execution_clk = 1,
70 .contract_address = address,
71 .space_id = 57,
72 .log_address = log_address,
73 .log_size = log_size,
74 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
75 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
76 .is_static = false,
77 .values = to_memory_values(log_fields),
78 .error_memory_out_of_bounds = false,
79 .error_too_many_log_fields = false,
80 .error_tag_mismatch = false,
81 };
82
83 TestTraceContainer trace({
84 { { C::precomputed_first_row, 1 } },
85 });
86
87 EmitUnencryptedLogTraceBuilder trace_builder;
88 trace_builder.process({ event }, trace);
89
90 check_relation<emit_unencrypted_log>(trace);
91}
92
93TEST(EmitUnencryptedLogConstrainingTest, PositiveEmptyLog)
94{
95 // Test created to ensure we do not underflow/fail memory checks for logs with no fields (not including header)
96 AztecAddress address = 0xdeadbeef;
97 MemoryAddress log_address = 0;
98 const std::vector<FF> log_fields = {};
99 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
100 TrackedSideEffects side_effect_states = { .public_logs = {} };
101 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
102
103 EmitUnencryptedLogWriteEvent event = {
104 .execution_clk = 1,
105 .contract_address = address,
106 .space_id = 57,
107 .log_address = log_address,
108 .log_size = log_size,
109 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
110 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
111 .is_static = false,
112 .values = to_memory_values(log_fields),
113 .error_memory_out_of_bounds = false,
114 .error_too_many_log_fields = false,
115 .error_tag_mismatch = false,
116 };
117
118 // As calculated in EmitUnencryptedLog::emit_unencrypted_log gadget:
119 uint64_t end_log_address_upper_bound = static_cast<uint64_t>(log_address) + static_cast<uint64_t>(log_size);
120
121 simulation::GreaterThanEvent gt_event = {
122 .a = end_log_address_upper_bound,
123 .b = AVM_MEMORY_SIZE,
124 .result = end_log_address_upper_bound > AVM_MEMORY_SIZE,
125 };
126
127 TestTraceContainer trace({
128 { { C::precomputed_first_row, 1 } },
129 });
130
131 EmitUnencryptedLogTraceBuilder trace_builder;
132 tracegen::GreaterThanTraceBuilder gt_builder;
133 gt_builder.process({ gt_event }, trace);
134 trace_builder.process({ event }, trace);
135
136 // Check tracegen fills the values correctly:
137 FF end_log_address_upper_bound_log_trace = trace.get(C::emit_unencrypted_log_end_log_address_upper_bound, 1);
138 FF end_log_address_upper_bound_gt_trace = trace.get(C::gt_input_a, 0);
139 EXPECT_EQ(end_log_address_upper_bound_log_trace, end_log_address_upper_bound_gt_trace);
140
141 check_relation<emit_unencrypted_log>(trace);
142 check_interaction<EmitUnencryptedLogTraceBuilder, lookup_emit_unencrypted_log_check_memory_out_of_bounds_settings>(
143 trace);
144}
145
146TEST(EmitUnencryptedLogConstrainingTest, ErrorMemoryOutOfBounds)
147{
148 AztecAddress address = 0xdeadbeef;
150 uint32_t log_size = 2;
151 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 }, address } } } };
152 const TrackedSideEffects& side_effect_states_after = side_effect_states;
153
154 EmitUnencryptedLogWriteEvent event = {
155 .execution_clk = 1,
156 .contract_address = address,
157 .space_id = 57,
158 .log_address = log_address,
159 .log_size = log_size,
160 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
161 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
162 .is_static = false,
163 .values = {},
164 .error_memory_out_of_bounds = true,
165 .error_too_many_log_fields = false,
166 .error_tag_mismatch = false,
167 };
168
169 TestTraceContainer trace({
170 { { C::precomputed_first_row, 1 } },
171 });
172
173 EmitUnencryptedLogTraceBuilder trace_builder;
174 trace_builder.process({ event }, trace);
175
176 check_relation<emit_unencrypted_log>(trace);
177}
178
179TEST(EmitUnencryptedLogConstrainingTest, ErrorTooManyLogFields)
180{
181 AztecAddress address = 0xdeadbeef;
182 MemoryAddress log_address = 27;
183 const std::vector<FF> log_fields = { 4, 5 };
184 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
185 // Minus three so header = 2 + log_size = 2 doesn't fit
186 TrackedSideEffects side_effect_states = {
187 .public_logs = PublicLogs{ { { testing::random_fields(FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH - 3), address } } }
188 };
189 const TrackedSideEffects& side_effect_states_after = side_effect_states;
190
191 EmitUnencryptedLogWriteEvent event = {
192 .execution_clk = 1,
193 .contract_address = address,
194 .space_id = 57,
195 .log_address = log_address,
196 .log_size = log_size,
197 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
198 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
199 .is_static = false,
200 .values = to_memory_values(log_fields),
201 .error_memory_out_of_bounds = false,
202 .error_too_many_log_fields = true,
203 .error_tag_mismatch = false,
204 };
205
206 TestTraceContainer trace({
207 { { C::precomputed_first_row, 1 } },
208 });
209
210 EmitUnencryptedLogTraceBuilder trace_builder;
211 trace_builder.process({ event }, trace);
212
213 check_relation<emit_unencrypted_log>(trace);
214}
215
216TEST(EmitUnencryptedLogConstrainingTest, ErrorTagMismatch)
217{
218 AztecAddress address = 0xdeadbeef;
219 MemoryAddress log_address = 27;
220 std::vector<MemoryValue> log_values = { MemoryValue::from<uint32_t>(4), MemoryValue::from<uint32_t>(5) };
221 uint32_t log_size = static_cast<uint32_t>(log_values.size());
222 TrackedSideEffects side_effect_states = { .public_logs = {} };
223 // No change to side effect states due to failure.
224 const TrackedSideEffects& side_effect_states_after = side_effect_states;
225
226 EmitUnencryptedLogWriteEvent event = {
227 .execution_clk = 1,
228 .contract_address = address,
229 .space_id = 57,
230 .log_address = log_address,
231 .log_size = log_size,
232 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
233 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
234 .is_static = false,
235 .values = log_values,
236 .error_memory_out_of_bounds = false,
237 .error_too_many_log_fields = false,
238 .error_tag_mismatch = true,
239 };
240
241 TestTraceContainer trace({
242 { { C::precomputed_first_row, 1 } },
243 });
244
245 EmitUnencryptedLogTraceBuilder trace_builder;
246 trace_builder.process({ event }, trace);
247
248 check_relation<emit_unencrypted_log>(trace);
249}
250
251TEST(EmitUnencryptedLogConstrainingTest, ErrorStatic)
252{
253 AztecAddress address = 0xdeadbeef;
254 MemoryAddress log_address = 27;
255 const std::vector<FF> log_fields = { 4, 5 };
256 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
257 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 }, address } } } };
258 const TrackedSideEffects& side_effect_states_after = side_effect_states;
259
260 EmitUnencryptedLogWriteEvent event = {
261 .execution_clk = 1,
262 .contract_address = address,
263 .space_id = 57,
264 .log_address = log_address,
265 .log_size = log_size,
266 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
267 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
268 .is_static = true,
269 .values = to_memory_values(log_fields),
270 .error_memory_out_of_bounds = false,
271 .error_too_many_log_fields = false,
272 .error_tag_mismatch = false,
273 };
274
275 TestTraceContainer trace({
276 { { C::precomputed_first_row, 1 } },
277 });
278
279 EmitUnencryptedLogTraceBuilder trace_builder;
280 trace_builder.process({ event }, trace);
281}
282
283TEST(EmitUnencryptedLogConstrainingTest, Interactions)
284{
285 AztecAddress address = 0xdeadbeef;
286 MemoryAddress log_address = 27;
287 const std::vector<FF> log_fields = { 4, 5 };
288 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
289 TrackedSideEffects side_effect_states = { .public_logs = {} };
290 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
291 AvmAccumulatedData accumulated_data = {};
292 accumulated_data.public_logs.add_log({
293 .fields = { FF(4), FF(5) },
294 .contract_address = address,
295 });
296 auto public_inputs = PublicInputsBuilder().set_accumulated_data(accumulated_data).build();
297
298 std::vector<MemoryValue> inputs = to_memory_values(log_fields);
299
300 EmitUnencryptedLogWriteEvent event = {
301 .execution_clk = 1,
302 .contract_address = address,
303 .space_id = 57,
304 .log_address = log_address,
305 .log_size = log_size,
306 .prev_num_unencrypted_log_fields = side_effect_states.get_num_unencrypted_log_fields(),
307 .next_num_unencrypted_log_fields = side_effect_states_after.get_num_unencrypted_log_fields(),
308 .is_static = false,
309 .values = inputs,
310 .error_memory_out_of_bounds = false,
311 .error_too_many_log_fields = false,
312 .error_tag_mismatch = false,
313 };
314
315 TestTraceContainer trace = TestTraceContainer({
316 // Row 0
317 {
318 { C::precomputed_first_row, 1 },
319 // GT - check log size
320 { C::gt_sel, 1 },
321 { C::gt_input_a, side_effect_states_after.get_num_unencrypted_log_fields() },
322 { C::gt_input_b, FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH },
323 { C::gt_res, 0 },
324 },
325 {
326 // Execution
327 { C::execution_sel, 1 },
328 { C::execution_sel_exec_dispatch_emit_unencrypted_log, 1 },
329 { C::execution_context_id, 57 },
330 { C::execution_rop_1_, log_address },
331 { C::execution_register_0_, log_size },
332 { C::execution_contract_address, address },
333 { C::execution_prev_num_unencrypted_log_fields, side_effect_states.get_num_unencrypted_log_fields() },
334 { C::execution_num_unencrypted_log_fields, side_effect_states_after.get_num_unencrypted_log_fields() },
335 { C::execution_is_static, false },
336 { C::execution_sel_opcode_error, 0 },
337 { C::execution_discard, 0 },
338 // GT - check memory out of bounds
339 { C::gt_sel, 1 },
340 { C::gt_input_a, log_address + log_size },
341 { C::gt_input_b, static_cast<uint64_t>(AVM_MEMORY_SIZE) },
342 { C::gt_res, 0 },
343 },
344 });
345
346 // Set up memory trace
347 for (uint32_t i = 0; i < inputs.size(); ++i) {
348 // Set memory reads
349 trace.set(C::memory_address, i + 1, log_address + i);
350 trace.set(C::memory_value, i + 1, inputs[i].as_ff());
351 trace.set(C::memory_tag, i + 1, static_cast<uint32_t>(inputs[i].get_tag()));
352 trace.set(C::memory_sel, i + 1, 1);
353 trace.set(C::memory_clk, i + 1, 1);
354 trace.set(C::memory_rw, i + 1, 0);
355 trace.set(C::memory_space_id, i + 1, 57);
356 }
357
358 PublicInputsTraceBuilder public_inputs_builder;
359 public_inputs_builder.process_public_inputs(trace, public_inputs);
360 public_inputs_builder.process_public_inputs_aux_precomputed(trace);
361
362 tracegen::PrecomputedTraceBuilder precomputed_builder;
364
365 EmitUnencryptedLogTraceBuilder trace_builder;
366 trace_builder.process({ event }, trace);
367
368 check_relation<emit_unencrypted_log>(trace);
369 check_all_interactions<EmitUnencryptedLogTraceBuilder>(trace);
370}
371
372TEST(EmitUnencryptedLogConstrainingTest, NegativeStartAfterLatch)
373{
374 TestTraceContainer trace = TestTraceContainer({ {
375 { C::precomputed_first_row, 1 },
376 },
377 {
378 { C::emit_unencrypted_log_sel, 1 },
379 { C::emit_unencrypted_log_start, 1 },
380 { C::emit_unencrypted_log_end, 1 },
381 },
382 {
383 { C::emit_unencrypted_log_sel, 1 },
384 { C::emit_unencrypted_log_start, 1 },
385 } });
386
387 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_START_AFTER_LATCH);
388
389 trace.set(C::emit_unencrypted_log_end, 1, 0);
390
391 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_START_AFTER_LATCH),
392 "START_AFTER_LATCH");
393
394 trace.set(C::emit_unencrypted_log_end, 1, 1);
395 trace.set(C::precomputed_first_row, 0, 0);
396
397 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_START_AFTER_LATCH),
398 "START_AFTER_LATCH");
399}
400
401TEST(EmitUnencryptedLogConstrainingTest, NegativeSelectorOnStart)
402{
403 TestTraceContainer trace = TestTraceContainer({ {
404 { C::emit_unencrypted_log_sel, 1 },
405 { C::emit_unencrypted_log_start, 1 },
406 } });
407
408 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_ON_START);
409
410 trace.set(C::emit_unencrypted_log_sel, 0, 0);
411
412 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_ON_START),
413 "SELECTOR_ON_START");
414}
415
416TEST(EmitUnencryptedLogConstrainingTest, NegativeSelectorConsistency)
417{
418 TestTraceContainer trace = TestTraceContainer({ {
419 { C::precomputed_first_row, 1 },
420 },
421 {
422 { C::emit_unencrypted_log_sel, 1 },
423 { C::emit_unencrypted_log_start, 1 },
424 { C::emit_unencrypted_log_end, 1 },
425 },
426 {
427 { C::emit_unencrypted_log_sel, 0 },
428 } });
429
430 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_CONSISTENCY);
431
432 trace.set(C::emit_unencrypted_log_end, 1, 0);
433
435 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_CONSISTENCY),
436 "SELECTOR_CONSISTENCY");
437}
438
439TEST(EmitUnencryptedLogConstrainingTest, NegativeSelectorOnEnd)
440{
441 TestTraceContainer trace = TestTraceContainer({ {
442 { C::emit_unencrypted_log_sel, 1 },
443 { C::emit_unencrypted_log_end, 1 },
444 } });
445
446 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_ON_END);
447
448 trace.set(C::emit_unencrypted_log_sel, 0, 0);
449
450 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SELECTOR_ON_END),
451 "SELECTOR_ON_END");
452}
453
454TEST(EmitUnencryptedLogConstrainingTest, NegativeRemainingRowsDecrement)
455{
456 TestTraceContainer trace = TestTraceContainer({ {
457 { C::emit_unencrypted_log_sel, 1 },
458 { C::emit_unencrypted_log_remaining_rows, 1 },
459 },
460 {
461 { C::emit_unencrypted_log_sel, 1 },
462 { C::emit_unencrypted_log_remaining_rows, 0 },
463 { C::emit_unencrypted_log_end, 1 },
464 } });
465
466 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_REMAINING_ROWS_DECREMENT);
467
468 trace.set(C::emit_unencrypted_log_remaining_rows, 1, 1);
469
471 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_REMAINING_ROWS_DECREMENT),
472 "REMAINING_ROWS_DECREMENT");
473}
474
475TEST(EmitUnencryptedLogConstrainingTest, NegativeErrorOutOfBoundsConsistency)
476{
477 TestTraceContainer trace = TestTraceContainer({ {
478 { C::emit_unencrypted_log_sel, 1 },
479 { C::emit_unencrypted_log_error_out_of_bounds, 1 },
480 },
481 {
482 { C::emit_unencrypted_log_sel, 1 },
483 { C::emit_unencrypted_log_error_out_of_bounds, 1 },
484 { C::emit_unencrypted_log_end, 1 },
485 } });
486
487 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY);
488
489 trace.set(C::emit_unencrypted_log_error_out_of_bounds, 1, 0);
490
492 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY),
493 "ERROR_OUT_OF_BOUNDS_CONSISTENCY");
494}
495
496TEST(EmitUnencryptedLogConstrainingTest, NegativeErrorTagMismatchConsistency)
497{
498 TestTraceContainer trace = TestTraceContainer({ {
499 { C::emit_unencrypted_log_sel, 1 },
500 { C::emit_unencrypted_log_error_tag_mismatch, 1 },
501 },
502 {
503 { C::emit_unencrypted_log_sel, 1 },
504 { C::emit_unencrypted_log_error_tag_mismatch, 1 },
505 { C::emit_unencrypted_log_end, 1 },
506 } });
507
508 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_ERROR_TAG_MISMATCH_CONSISTENCY);
509
510 trace.set(C::emit_unencrypted_log_error_tag_mismatch, 1, 0);
511
513 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_ERROR_TAG_MISMATCH_CONSISTENCY),
514 "ERROR_TAG_MISMATCH_CONSISTENCY");
515}
516
517TEST(EmitUnencryptedLogConstrainingTest, NegativeWrongTagCheck)
518{
519 TestTraceContainer trace = TestTraceContainer({ {
520 { C::emit_unencrypted_log_sel, 1 },
521 { C::emit_unencrypted_log_seen_wrong_tag, 0 },
522 },
523 {
524 { C::emit_unencrypted_log_sel, 1 },
525 { C::emit_unencrypted_log_seen_wrong_tag, 1 },
526 { C::emit_unencrypted_log_correct_tag, 0 },
527 { C::emit_unencrypted_log_end, 1 },
528 } });
529
530 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_WRONG_TAG_CHECK);
531
532 trace.set(C::emit_unencrypted_log_seen_wrong_tag, 1, 0);
533
534 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_WRONG_TAG_CHECK),
535 "WRONG_TAG_CHECK");
536}
537
538TEST(EmitUnencryptedLogConstrainingTest, NegativeSelectorShouldWriteToPublicInputsConsistency)
539{
540 TestTraceContainer trace =
541 TestTraceContainer({ {
542 { C::emit_unencrypted_log_sel, 1 },
543 { C::emit_unencrypted_log_sel_should_write_to_public_inputs, 1 },
544 },
545 {
546 { C::emit_unencrypted_log_sel, 1 },
547 { C::emit_unencrypted_log_sel_should_write_to_public_inputs, 1 },
548 { C::emit_unencrypted_log_end, 1 },
549 } });
550
551 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY);
552
553 trace.set(C::emit_unencrypted_log_sel_should_write_to_public_inputs, 1, 0);
554
555 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(
557 "SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY");
558}
559
560TEST(EmitUnencryptedLogConstrainingTest, NegativeLogOffsetIncrement)
561{
562 TestTraceContainer trace = TestTraceContainer({ {
563 { C::emit_unencrypted_log_sel, 1 },
564 { C::emit_unencrypted_log_is_write_memory_value, 1 },
565 { C::emit_unencrypted_log_log_address, 10 },
566 },
567 {
568 { C::emit_unencrypted_log_sel, 1 },
569 { C::emit_unencrypted_log_is_write_memory_value, 1 },
570 { C::emit_unencrypted_log_log_address, 11 },
571 { C::emit_unencrypted_log_end, 1 },
572 } });
573
574 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_LOG_ADDRESS_INCREMENT);
575
576 trace.set(C::emit_unencrypted_log_log_address, 1, 9);
577
579 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_LOG_ADDRESS_INCREMENT),
580 "LOG_ADDRESS_INCREMENT");
581}
582
583TEST(EmitUnencryptedLogConstrainingTest, NegativeExecutionClkConsistency)
584{
585 TestTraceContainer trace = TestTraceContainer({ {
586 { C::emit_unencrypted_log_sel, 1 },
587 { C::emit_unencrypted_log_execution_clk, 1 },
588 },
589 {
590 { C::emit_unencrypted_log_sel, 1 },
591 { C::emit_unencrypted_log_execution_clk, 1 },
592 { C::emit_unencrypted_log_end, 1 },
593 } });
594
595 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_EXEC_CLK_CONSISTENCY);
596
597 trace.set(C::emit_unencrypted_log_execution_clk, 1, 0);
598
600 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_EXEC_CLK_CONSISTENCY),
601 "EXEC_CLK_CONSISTENCY");
602}
603
604TEST(EmitUnencryptedLogConstrainingTest, NegativeSpaceIdConsistency)
605{
606 TestTraceContainer trace = TestTraceContainer({ {
607 { C::emit_unencrypted_log_sel, 1 },
608 { C::emit_unencrypted_log_space_id, 17 },
609 },
610 {
611 { C::emit_unencrypted_log_sel, 1 },
612 { C::emit_unencrypted_log_space_id, 17 },
613 { C::emit_unencrypted_log_end, 1 },
614 } });
615
616 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SPACE_ID_CONSISTENCY);
617
618 trace.set(C::emit_unencrypted_log_space_id, 1, 18);
619
621 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_SPACE_ID_CONSISTENCY),
622 "SPACE_ID_CONSISTENCY");
623}
624
625TEST(EmitUnencryptedLogConstrainingTest, NegativeContractAddressConsistency)
626{
627 TestTraceContainer trace = TestTraceContainer({ {
628 { C::emit_unencrypted_log_sel, 1 },
629 { C::emit_unencrypted_log_contract_address, 42 },
630 },
631 {
632 { C::emit_unencrypted_log_sel, 1 },
633 { C::emit_unencrypted_log_contract_address, 42 },
634 { C::emit_unencrypted_log_end, 1 },
635 } });
636
637 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_CONTRACT_ADDRESS_CONSISTENCY);
638
639 trace.set(C::emit_unencrypted_log_contract_address, 1, 43);
640
642 check_relation<emit_unencrypted_log>(trace, emit_unencrypted_log::SR_CONTRACT_ADDRESS_CONSISTENCY),
643 "CONTRACT_ADDRESS_CONSISTENCY");
644}
645
646// =====================================================================
647// Ghost Row Injection Vulnerability Tests
648// =====================================================================
649// These tests verify that ghost rows (sel=0) cannot fire permutations.
650// This is a defensive/sanity check: even though the situation is hard to exploit,
651// we still enforce the selector gating to prevent accidental ghost reads.
652// The vulnerability: is_write_memory_value is only boolean-constrained,
653// not constrained to be 0 when sel=0. This allows ghost rows to fire
654// the #[READ_MEM] permutation via sel_should_read_memory.
655//
656// VULNERABILITY SUMMARY:
657// - is_write_memory_value is only boolean-constrained
658// - When sel=0, is_write_memory_value can still be set to 1
659// - This makes sel_should_read_memory = 1 (via derived constraint)
660// - This fires the #[READ_MEM] permutation from a ghost row
661//
662// REQUIRED FIX:
663// Gate by sel to avoid ghost rows triggering memory reads.
664// sel_should_read_memory = sel * is_write_memory_value * (1 - error_out_of_bounds);
665
666// This test verifies that the fix for the ghost row injection vulnerability works.
667// The constraint `is_write_memory_value * (1 - sel) = 0` should prevent ghost rows
668// from setting is_write_memory_value=1 when sel=0.
669TEST(EmitUnencryptedLogConstrainingTest, NegativeGhostRowInjectionBlocked)
670{
671 TestTraceContainer trace;
672 MemoryTraceBuilder memory_trace_builder;
673 PrecomputedTraceBuilder precomputed_trace_builder;
674
675 uint32_t malicious_clk = 42;
676 uint16_t malicious_space_id = 1;
677 MemoryAddress malicious_log_addr = 0xDEAD;
678 FF malicious_value = 0x1337;
679 MemoryTag malicious_tag = MemoryTag::FF;
680
682 {
683 .execution_clk = malicious_clk,
685 .addr = malicious_log_addr,
686 .value = MemoryValue::from<FF>(malicious_value),
687 .space_id = malicious_space_id,
688 },
689 };
690
691 precomputed_trace_builder.process_sel_range_8(trace);
692 precomputed_trace_builder.process_sel_range_16(trace);
693 precomputed_trace_builder.process_misc(trace, 1 << 16);
694 precomputed_trace_builder.process_tag_parameters(trace);
695 memory_trace_builder.process(mem_events, trace);
696
697 uint32_t memory_row = 0;
698 for (uint32_t row = 0; row < trace.get_num_rows(); row++) {
699 if (trace.get(C::memory_sel, row) == 1) {
700 memory_row = row;
701 break;
702 }
703 }
704
705 // Attempt ghost row injection: sel=0 but is_write_memory_value=1
706 uint32_t ghost_row = 0;
707 trace.set(ghost_row,
708 std::vector<std::pair<Column, FF>>{
709 { C::precomputed_first_row, 1 },
710 { C::precomputed_clk, ghost_row },
711 { C::precomputed_zero, 0 },
712 { C::emit_unencrypted_log_sel, 0 },
713 { C::emit_unencrypted_log_is_write_memory_value, 1 },
714 { C::emit_unencrypted_log_error_out_of_bounds, 0 },
715 { C::emit_unencrypted_log_sel_should_read_memory, 1 },
716 { C::emit_unencrypted_log_execution_clk, malicious_clk },
717 { C::emit_unencrypted_log_space_id, malicious_space_id },
718 { C::emit_unencrypted_log_log_address, malicious_log_addr },
719 { C::emit_unencrypted_log_value, malicious_value },
720 { C::emit_unencrypted_log_tag, static_cast<uint8_t>(malicious_tag) },
721 { C::emit_unencrypted_log_public_inputs_value, malicious_value },
722 });
723
724 trace.set(C::memory_sel_unencrypted_log_read, memory_row, 1);
725
726 // The fix: sel_should_read_memory = sel * is_write_memory_value * (1 - error_out_of_bounds)
727 // Gating by sel should cause the relation check to fail
728 // because sel_should_read_memory=1 and sel=0 violates this constraint
729 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_unencrypted_log>(trace),
730 "SEL_SHOULD_READ_MEMORY_IS_SEL_AND_WRITE_MEM_AND_NO_ERR");
731}
732
733} // namespace
734
735} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH
#define AVM_MEMORY_SIZE
#define AVM_HIGHEST_MEM_ADDRESS
static constexpr size_t SR_SPACE_ID_CONSISTENCY
static constexpr size_t SR_SELECTOR_ON_END
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_LOG_ADDRESS_INCREMENT
static constexpr size_t SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY
static constexpr size_t SR_ERROR_TAG_MISMATCH_CONSISTENCY
static constexpr size_t SR_SELECTOR_ON_START
static constexpr size_t SR_WRONG_TAG_CHECK
static constexpr size_t SR_REMAINING_ROWS_DECREMENT
static constexpr size_t SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY
static constexpr size_t SR_EXEC_CLK_CONSISTENCY
static constexpr size_t SR_SELECTOR_CONSISTENCY
static constexpr size_t SR_CONTRACT_ADDRESS_CONSISTENCY
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)
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value)
PrecomputedTraceBuilder precomputed_builder
Definition alu.test.cpp:120
GreaterThanTraceBuilder gt_builder
Definition alu.test.cpp:123
TestTraceContainer trace
AvmProvingInputs inputs
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
std::variant< EmitUnencryptedLogWriteEvent, CheckPointEventType > EmitUnencryptedLogEvent
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:23
uint32_t MemoryAddress
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
tracegen::PublicInputsTraceBuilder public_inputs_builder
Definition tx.test.cpp:81