Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
emit_public_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
25
26namespace bb::avm2::constraining {
27namespace {
28
30using simulation::EmitPublicLogWriteEvent;
31using simulation::TrackedSideEffects;
32using testing::PublicInputsBuilder;
33using tracegen::EmitPublicLogTraceBuilder;
34using tracegen::MemoryTraceBuilder;
35using tracegen::PrecomputedTraceBuilder;
36using tracegen::PublicInputsTraceBuilder;
37using tracegen::TestTraceContainer;
39using C = Column;
40using emit_public_log = bb::avm2::emit_public_log<FF>;
41
42std::vector<MemoryValue> to_memory_values(const std::vector<FF>& fields)
43{
44 std::vector<MemoryValue> memory_values;
45 memory_values.reserve(fields.size());
46 for (const FF& field : fields) {
47 memory_values.push_back(MemoryValue::from<FF>(field));
48 }
49 return memory_values;
50}
51
52TEST(EmitPublicLogConstrainingTest, EmptyTrace)
53{
54 check_relation<emit_public_log>(testing::empty_trace());
55}
56
57TEST(EmitPublicLogConstrainingTest, Positive)
58{
59 AztecAddress address = 0xdeadbeef;
60 MemoryAddress log_address = 27;
61 const std::vector<FF> log_fields = { 4, 5 };
62 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
63 TrackedSideEffects side_effect_states = { .public_logs = {} };
64 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
65
66 EmitPublicLogWriteEvent event = {
67 .execution_clk = 1,
68 .contract_address = address,
69 .space_id = 57,
70 .log_address = log_address,
71 .log_size = log_size,
72 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
73 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
74 .is_static = false,
75 .values = to_memory_values(log_fields),
76 .error_memory_out_of_bounds = false,
77 .error_too_many_log_fields = false,
78 .error_tag_mismatch = false,
79 };
80
81 TestTraceContainer trace({
82 { { C::precomputed_first_row, 1 } },
83 });
84
85 EmitPublicLogTraceBuilder trace_builder;
86 trace_builder.process({ event }, trace);
87
88 check_relation<emit_public_log>(trace);
89}
90
91TEST(EmitPublicLogConstrainingTest, PositiveEmptyLog)
92{
93 // Test created to ensure we do not underflow/fail memory checks for logs with no fields (not including header)
94 AztecAddress address = 0xdeadbeef;
95 MemoryAddress log_address = 0;
96 const std::vector<FF> log_fields = {};
97 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
98 TrackedSideEffects side_effect_states = { .public_logs = {} };
99 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
100
101 EmitPublicLogWriteEvent event = {
102 .execution_clk = 1,
103 .contract_address = address,
104 .space_id = 57,
105 .log_address = log_address,
106 .log_size = log_size,
107 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
108 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
109 .is_static = false,
110 .values = to_memory_values(log_fields),
111 .error_memory_out_of_bounds = false,
112 .error_too_many_log_fields = false,
113 .error_tag_mismatch = false,
114 };
115
116 // As calculated in EmitPublicLog::emit_public_log gadget:
117 uint64_t end_log_address_upper_bound = static_cast<uint64_t>(log_address) + static_cast<uint64_t>(log_size);
118
119 simulation::GreaterThanEvent gt_event = {
120 .a = end_log_address_upper_bound,
121 .b = AVM_MEMORY_SIZE,
122 .result = end_log_address_upper_bound > AVM_MEMORY_SIZE,
123 };
124
125 TestTraceContainer trace({
126 { { C::precomputed_first_row, 1 } },
127 });
128
129 EmitPublicLogTraceBuilder trace_builder;
130 tracegen::GreaterThanTraceBuilder gt_builder;
131 gt_builder.process({ gt_event }, trace);
132 trace_builder.process({ event }, trace);
133
134 // Check tracegen fills the values correctly:
135 FF end_log_address_upper_bound_log_trace = trace.get(C::emit_public_log_end_log_address_upper_bound, 1);
136 FF end_log_address_upper_bound_gt_trace = trace.get(C::gt_input_a, 0);
137 EXPECT_EQ(end_log_address_upper_bound_log_trace, end_log_address_upper_bound_gt_trace);
138
139 check_relation<emit_public_log>(trace);
140 check_interaction<EmitPublicLogTraceBuilder, lookup_emit_public_log_check_memory_out_of_bounds_settings>(trace);
141}
142
143TEST(EmitPublicLogConstrainingTest, ErrorMemoryOutOfBounds)
144{
145 AztecAddress address = 0xdeadbeef;
147 uint32_t log_size = 2;
148 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 }, address } } } };
149 const TrackedSideEffects& side_effect_states_after = side_effect_states;
150
151 EmitPublicLogWriteEvent event = {
152 .execution_clk = 1,
153 .contract_address = address,
154 .space_id = 57,
155 .log_address = log_address,
156 .log_size = log_size,
157 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
158 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
159 .is_static = false,
160 .values = {},
161 .error_memory_out_of_bounds = true,
162 .error_too_many_log_fields = false,
163 .error_tag_mismatch = false,
164 };
165
166 TestTraceContainer trace({
167 { { C::precomputed_first_row, 1 } },
168 });
169
170 EmitPublicLogTraceBuilder trace_builder;
171 trace_builder.process({ event }, trace);
172
173 check_relation<emit_public_log>(trace);
174}
175
176TEST(EmitPublicLogConstrainingTest, ErrorTooManyLogFields)
177{
178 AztecAddress address = 0xdeadbeef;
179 MemoryAddress log_address = 27;
180 const std::vector<FF> log_fields = { 4, 5 };
181 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
182 // Minus three so header = 2 + log_size = 2 doesn't fit
183 TrackedSideEffects side_effect_states = {
184 .public_logs = PublicLogs{ { { testing::random_fields(FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH - 3), address } } }
185 };
186 const TrackedSideEffects& side_effect_states_after = side_effect_states;
187
188 EmitPublicLogWriteEvent event = {
189 .execution_clk = 1,
190 .contract_address = address,
191 .space_id = 57,
192 .log_address = log_address,
193 .log_size = log_size,
194 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
195 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
196 .is_static = false,
197 .values = to_memory_values(log_fields),
198 .error_memory_out_of_bounds = false,
199 .error_too_many_log_fields = true,
200 .error_tag_mismatch = false,
201 };
202
203 TestTraceContainer trace({
204 { { C::precomputed_first_row, 1 } },
205 });
206
207 EmitPublicLogTraceBuilder trace_builder;
208 trace_builder.process({ event }, trace);
209
210 check_relation<emit_public_log>(trace);
211}
212
213TEST(EmitPublicLogConstrainingTest, ErrorTagMismatch)
214{
215 AztecAddress address = 0xdeadbeef;
216 MemoryAddress log_address = 27;
217 std::vector<MemoryValue> log_values = { MemoryValue::from<uint32_t>(4), MemoryValue::from<uint32_t>(5) };
218 uint32_t log_size = static_cast<uint32_t>(log_values.size());
219 TrackedSideEffects side_effect_states = { .public_logs = {} };
220 // No change to side effect states due to failure.
221 const TrackedSideEffects& side_effect_states_after = side_effect_states;
222
223 EmitPublicLogWriteEvent event = {
224 .execution_clk = 1,
225 .contract_address = address,
226 .space_id = 57,
227 .log_address = log_address,
228 .log_size = log_size,
229 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
230 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
231 .is_static = false,
232 .values = log_values,
233 .error_memory_out_of_bounds = false,
234 .error_too_many_log_fields = false,
235 .error_tag_mismatch = true,
236 };
237
238 TestTraceContainer trace({
239 { { C::precomputed_first_row, 1 } },
240 });
241
242 EmitPublicLogTraceBuilder trace_builder;
243 trace_builder.process({ event }, trace);
244
245 check_relation<emit_public_log>(trace);
246}
247
248TEST(EmitPublicLogConstrainingTest, ErrorStatic)
249{
250 AztecAddress address = 0xdeadbeef;
251 MemoryAddress log_address = 27;
252 const std::vector<FF> log_fields = { 4, 5 };
253 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
254 TrackedSideEffects side_effect_states = { .public_logs = PublicLogs{ { { { 4 }, address } } } };
255 const TrackedSideEffects& side_effect_states_after = side_effect_states;
256
257 EmitPublicLogWriteEvent event = {
258 .execution_clk = 1,
259 .contract_address = address,
260 .space_id = 57,
261 .log_address = log_address,
262 .log_size = log_size,
263 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
264 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
265 .is_static = true,
266 .values = to_memory_values(log_fields),
267 .error_memory_out_of_bounds = false,
268 .error_too_many_log_fields = false,
269 .error_tag_mismatch = false,
270 };
271
272 TestTraceContainer trace({
273 { { C::precomputed_first_row, 1 } },
274 });
275
276 EmitPublicLogTraceBuilder trace_builder;
277 trace_builder.process({ event }, trace);
278
279 check_relation<emit_public_log>(trace);
280}
281
282TEST(EmitPublicLogConstrainingTest, Interactions)
283{
284 AztecAddress address = 0xdeadbeef;
285 MemoryAddress log_address = 27;
286 const std::vector<FF> log_fields = { 4, 5 };
287 uint32_t log_size = static_cast<uint32_t>(log_fields.size());
288 TrackedSideEffects side_effect_states = { .public_logs = {} };
289 TrackedSideEffects side_effect_states_after = { .public_logs = PublicLogs{ { { log_fields, address } } } };
290 AvmAccumulatedData accumulated_data = {};
291 accumulated_data.public_logs.add_log({
292 .fields = { FF(4), FF(5) },
293 .contract_address = address,
294 });
295 auto public_inputs = PublicInputsBuilder().set_accumulated_data(accumulated_data).build();
296
297 std::vector<MemoryValue> inputs = to_memory_values(log_fields);
298
299 EmitPublicLogWriteEvent event = {
300 .execution_clk = 1,
301 .contract_address = address,
302 .space_id = 57,
303 .log_address = log_address,
304 .log_size = log_size,
305 .prev_num_public_log_fields = side_effect_states.get_num_public_log_fields(),
306 .next_num_public_log_fields = side_effect_states_after.get_num_public_log_fields(),
307 .is_static = false,
308 .values = inputs,
309 .error_memory_out_of_bounds = false,
310 .error_too_many_log_fields = false,
311 .error_tag_mismatch = false,
312 };
313
314 TestTraceContainer trace = TestTraceContainer({
315 // Row 0
316 {
317 { C::precomputed_first_row, 1 },
318 // GT - check log size
319 { C::gt_sel, 1 },
320 { C::gt_input_a, side_effect_states_after.get_num_public_log_fields() },
321 { C::gt_input_b, FLAT_PUBLIC_LOGS_PAYLOAD_LENGTH },
322 { C::gt_res, 0 },
323 },
324 {
325 // Execution
326 { C::execution_sel, 1 },
327 { C::execution_sel_exec_dispatch_emit_public_log, 1 },
328 { C::execution_context_id, 57 },
329 { C::execution_rop_1_, log_address },
330 { C::execution_register_0_, log_size },
331 { C::execution_contract_address, address },
332 { C::execution_prev_num_public_log_fields, side_effect_states.get_num_public_log_fields() },
333 { C::execution_num_public_log_fields, side_effect_states_after.get_num_public_log_fields() },
334 { C::execution_is_static, false },
335 { C::execution_sel_opcode_error, 0 },
336 { C::execution_discard, 0 },
337 // GT - check memory out of bounds
338 { C::gt_sel, 1 },
339 { C::gt_input_a, log_address + log_size },
340 { C::gt_input_b, static_cast<uint64_t>(AVM_MEMORY_SIZE) },
341 { C::gt_res, 0 },
342 },
343 });
344
345 // Set up memory trace
346 for (uint32_t i = 0; i < inputs.size(); ++i) {
347 // Set memory reads
348 trace.set(C::memory_address, i + 1, log_address + i);
349 trace.set(C::memory_value, i + 1, inputs[i].as_ff());
350 trace.set(C::memory_tag, i + 1, static_cast<uint32_t>(inputs[i].get_tag()));
351 trace.set(C::memory_sel, i + 1, 1);
352 trace.set(C::memory_clk, i + 1, 1);
353 trace.set(C::memory_rw, i + 1, 0);
354 trace.set(C::memory_space_id, i + 1, 57);
355 }
356
357 PublicInputsTraceBuilder public_inputs_builder;
358 public_inputs_builder.process_public_inputs(trace, public_inputs);
359 public_inputs_builder.process_public_inputs_aux_precomputed(trace);
360
361 tracegen::PrecomputedTraceBuilder precomputed_builder;
363
364 EmitPublicLogTraceBuilder trace_builder;
365 trace_builder.process({ event }, trace);
366
367 check_relation<emit_public_log>(trace);
368 check_all_interactions<EmitPublicLogTraceBuilder>(trace);
369}
370
371TEST(EmitPublicLogConstrainingTest, NegativeStartAfterLatch)
372{
373 TestTraceContainer trace = TestTraceContainer({ {
374 { C::precomputed_first_row, 1 },
375 },
376 {
377 { C::emit_public_log_sel, 1 },
378 { C::emit_public_log_start, 1 },
379 { C::emit_public_log_end, 1 },
380 },
381 {
382 { C::emit_public_log_sel, 1 },
383 { C::emit_public_log_start, 1 },
384 } });
385
386 check_relation<emit_public_log>(trace, emit_public_log::SR_START_AFTER_LATCH);
387
388 trace.set(C::emit_public_log_end, 1, 0);
389
390 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_START_AFTER_LATCH),
391 "START_AFTER_LATCH");
392
393 trace.set(C::emit_public_log_end, 1, 1);
394 trace.set(C::precomputed_first_row, 0, 0);
395
396 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_START_AFTER_LATCH),
397 "START_AFTER_LATCH");
398}
399
400TEST(EmitPublicLogConstrainingTest, NegativeTraceContinuity)
401{
402 // Once sel drops to 0, it cannot come back to 1 (on non-first rows).
403 // Row 0: first_row=1, sel=1, start=1 (LATCH_CONDITION=1 via first_row)
404 // Row 1: sel=1, end=1 (LATCH_CONDITION=1 via end, allows sel to drop)
405 // Row 2: sel=0
406 // Row 3: sel=0 (positive) / sel=1 (negative, violates continuity)
407 TestTraceContainer trace = TestTraceContainer({ {
408 { C::precomputed_first_row, 1 },
409 { C::emit_public_log_sel, 1 },
410 { C::emit_public_log_start, 1 },
411 },
412 {
413 { C::emit_public_log_sel, 1 },
414 { C::emit_public_log_end, 1 },
415 },
416 {
417 { C::emit_public_log_sel, 0 },
418 },
419 {
420 { C::emit_public_log_sel, 0 },
421 } });
422
423 check_relation<emit_public_log>(trace, emit_public_log::SR_TRACE_CONTINUITY);
424
425 // Now make sel come back to 1 after a gap.
426 trace.set(C::emit_public_log_sel, 3, 1);
427
428 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_TRACE_CONTINUITY),
429 "TRACE_CONTINUITY");
430}
431
432TEST(EmitPublicLogConstrainingTest, NegativeComputationFinishAtEnd)
433{
434 // sel can only turn off (sel=1 -> sel'=0) when end=1.
435 // Row 0: first_row=1, sel=1, start=1 (LATCH_CONDITION=1 via first_row)
436 // Row 1: sel=1, end=0 (sel drops to 0 on next row without end=1)
437 // Row 2: sel=0 (violates TRACE_CONTINUITY at row 1)
438 TestTraceContainer trace = TestTraceContainer({ {
439 { C::precomputed_first_row, 1 },
440 { C::emit_public_log_sel, 1 },
441 { C::emit_public_log_start, 1 },
442 },
443 {
444 { C::emit_public_log_sel, 1 },
445 },
446 {
447 { C::emit_public_log_sel, 0 },
448 } });
449
450 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_TRACE_CONTINUITY),
451 "TRACE_CONTINUITY");
452
453 // Adding end=1 on row 1 should satisfy the constraint.
454 trace.set(C::emit_public_log_end, 1, 1);
455
456 check_relation<emit_public_log>(trace, emit_public_log::SR_TRACE_CONTINUITY);
457}
458
459TEST(EmitPublicLogConstrainingTest, NegativeRemainingRowsDecrement)
460{
461 TestTraceContainer trace = TestTraceContainer({ {
462 { C::emit_public_log_sel, 1 },
463 { C::emit_public_log_remaining_rows, 1 },
464 },
465 {
466 { C::emit_public_log_sel, 1 },
467 { C::emit_public_log_remaining_rows, 0 },
468 { C::emit_public_log_end, 1 },
469 } });
470
471 check_relation<emit_public_log>(trace, emit_public_log::SR_REMAINING_ROWS_DECREMENT);
472
473 trace.set(C::emit_public_log_remaining_rows, 1, 1);
474
475 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_REMAINING_ROWS_DECREMENT),
476 "REMAINING_ROWS_DECREMENT");
477}
478
479TEST(EmitPublicLogConstrainingTest, NegativeErrorOutOfBoundsConsistency)
480{
481 TestTraceContainer trace = TestTraceContainer({ {
482 { C::emit_public_log_sel, 1 },
483 { C::emit_public_log_error_out_of_bounds, 1 },
484 },
485 {
486 { C::emit_public_log_sel, 1 },
487 { C::emit_public_log_error_out_of_bounds, 1 },
488 { C::emit_public_log_end, 1 },
489 } });
490
491 check_relation<emit_public_log>(trace, emit_public_log::SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY);
492
493 trace.set(C::emit_public_log_error_out_of_bounds, 1, 0);
494
496 check_relation<emit_public_log>(trace, emit_public_log::SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY),
497 "ERROR_OUT_OF_BOUNDS_CONSISTENCY");
498}
499
500TEST(EmitPublicLogConstrainingTest, NegativeErrorTagMismatchConsistency)
501{
502 TestTraceContainer trace = TestTraceContainer({ {
503 { C::emit_public_log_sel, 1 },
504 { C::emit_public_log_error_tag_mismatch, 1 },
505 },
506 {
507 { C::emit_public_log_sel, 1 },
508 { C::emit_public_log_error_tag_mismatch, 1 },
509 { C::emit_public_log_end, 1 },
510 } });
511
512 check_relation<emit_public_log>(trace, emit_public_log::SR_ERROR_TAG_MISMATCH_CONSISTENCY);
513
514 trace.set(C::emit_public_log_error_tag_mismatch, 1, 0);
515
517 check_relation<emit_public_log>(trace, emit_public_log::SR_ERROR_TAG_MISMATCH_CONSISTENCY),
518 "ERROR_TAG_MISMATCH_CONSISTENCY");
519}
520
521TEST(EmitPublicLogConstrainingTest, NegativeWrongTagCheck)
522{
523 TestTraceContainer trace = TestTraceContainer({ {
524 { C::emit_public_log_sel, 1 },
525 { C::emit_public_log_seen_wrong_tag, 0 },
526 },
527 {
528 { C::emit_public_log_sel, 1 },
529 { C::emit_public_log_seen_wrong_tag, 1 },
530 { C::emit_public_log_correct_tag, 0 },
531 { C::emit_public_log_end, 1 },
532 } });
533
534 check_relation<emit_public_log>(trace, emit_public_log::SR_WRONG_TAG_CHECK);
535
536 trace.set(C::emit_public_log_seen_wrong_tag, 1, 0);
537
538 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_WRONG_TAG_CHECK),
539 "WRONG_TAG_CHECK");
540}
541
542TEST(EmitPublicLogConstrainingTest, NegativeSelectorShouldWriteToPublicInputsConsistency)
543{
544 TestTraceContainer trace = TestTraceContainer({ {
545 { C::emit_public_log_sel, 1 },
546 { C::emit_public_log_sel_write_to_public_inputs, 1 },
547 },
548 {
549 { C::emit_public_log_sel, 1 },
550 { C::emit_public_log_sel_write_to_public_inputs, 1 },
551 { C::emit_public_log_end, 1 },
552 } });
553
554 check_relation<emit_public_log>(trace, emit_public_log::SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY);
555
556 trace.set(C::emit_public_log_sel_write_to_public_inputs, 1, 0);
557
559 check_relation<emit_public_log>(trace, emit_public_log::SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY),
560 "SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY");
561}
562
563TEST(EmitPublicLogConstrainingTest, NegativeLogOffsetIncrement)
564{
565 TestTraceContainer trace = TestTraceContainer({ {
566 { C::emit_public_log_sel, 1 },
567 { C::emit_public_log_is_write_memory_value, 1 },
568 { C::emit_public_log_log_address, 10 },
569 },
570 {
571 { C::emit_public_log_sel, 1 },
572 { C::emit_public_log_is_write_memory_value, 1 },
573 { C::emit_public_log_log_address, 11 },
574 { C::emit_public_log_end, 1 },
575 } });
576
577 check_relation<emit_public_log>(trace, emit_public_log::SR_LOG_ADDRESS_INCREMENT);
578
579 trace.set(C::emit_public_log_log_address, 1, 9);
580
581 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_LOG_ADDRESS_INCREMENT),
582 "LOG_ADDRESS_INCREMENT");
583}
584
585TEST(EmitPublicLogConstrainingTest, NegativeExecutionClkConsistency)
586{
587 TestTraceContainer trace = TestTraceContainer({ {
588 { C::emit_public_log_sel, 1 },
589 { C::emit_public_log_execution_clk, 1 },
590 },
591 {
592 { C::emit_public_log_sel, 1 },
593 { C::emit_public_log_execution_clk, 1 },
594 { C::emit_public_log_end, 1 },
595 } });
596
597 check_relation<emit_public_log>(trace, emit_public_log::SR_EXEC_CLK_CONSISTENCY);
598
599 trace.set(C::emit_public_log_execution_clk, 1, 0);
600
601 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_EXEC_CLK_CONSISTENCY),
602 "EXEC_CLK_CONSISTENCY");
603}
604
605TEST(EmitPublicLogConstrainingTest, NegativeSpaceIdConsistency)
606{
607 TestTraceContainer trace = TestTraceContainer({ {
608 { C::emit_public_log_sel, 1 },
609 { C::emit_public_log_space_id, 17 },
610 },
611 {
612 { C::emit_public_log_sel, 1 },
613 { C::emit_public_log_space_id, 17 },
614 { C::emit_public_log_end, 1 },
615 } });
616
617 check_relation<emit_public_log>(trace, emit_public_log::SR_SPACE_ID_CONSISTENCY);
618
619 trace.set(C::emit_public_log_space_id, 1, 18);
620
621 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_SPACE_ID_CONSISTENCY),
622 "SPACE_ID_CONSISTENCY");
623}
624
625TEST(EmitPublicLogConstrainingTest, NegativeContractAddressConsistency)
626{
627 TestTraceContainer trace = TestTraceContainer({ {
628 { C::emit_public_log_sel, 1 },
629 { C::emit_public_log_contract_address, 42 },
630 },
631 {
632 { C::emit_public_log_sel, 1 },
633 { C::emit_public_log_contract_address, 42 },
634 { C::emit_public_log_end, 1 },
635 } });
636
637 check_relation<emit_public_log>(trace, emit_public_log::SR_CONTRACT_ADDRESS_CONSISTENCY);
638
639 trace.set(C::emit_public_log_contract_address, 1, 43);
640
642 "CONTRACT_ADDRESS_CONSISTENCY");
643}
644
645// =====================================================================
646// Ghost Row Injection Vulnerability Tests
647// =====================================================================
648// These tests verify that ghost rows (sel=0) cannot fire permutations.
649// This is a defensive/sanity check: even though the situation is hard to exploit,
650// we still enforce the selector gating to prevent accidental ghost reads.
651// The vulnerability: is_write_memory_value is only boolean-constrained,
652// not constrained to be 0 when sel=0. This allows ghost rows to fire
653// the #[READ_MEM] permutation via sel_read_memory.
654//
655// VULNERABILITY SUMMARY:
656// - is_write_memory_value is only boolean-constrained
657// - When sel=0, is_write_memory_value can still be set to 1
658// - This makes sel_read_memory = 1 (via derived constraint)
659// - This fires the #[READ_MEM] permutation from a ghost row
660//
661// REQUIRED FIX:
662// Gate by sel to avoid ghost rows triggering memory reads.
663// sel_read_memory = sel * is_write_memory_value * (1 - error_out_of_bounds);
664
665// This test verifies that the fix for the ghost row injection vulnerability works.
666// The constraint `is_write_memory_value * (1 - sel) = 0` should prevent ghost rows
667// from setting is_write_memory_value=1 when sel=0.
668TEST(EmitPublicLogConstrainingTest, NegativeGhostRowInjectionBlocked)
669{
670 TestTraceContainer trace;
671 MemoryTraceBuilder memory_trace_builder;
672 PrecomputedTraceBuilder precomputed_trace_builder;
673
674 uint32_t malicious_clk = 42;
675 uint16_t malicious_space_id = 1;
676 MemoryAddress malicious_log_addr = 0xDEAD;
677 FF malicious_value = 0x1337;
678 MemoryTag malicious_tag = MemoryTag::FF;
679
681 {
682 .execution_clk = malicious_clk,
684 .addr = malicious_log_addr,
685 .value = MemoryValue::from<FF>(malicious_value),
686 .space_id = malicious_space_id,
687 },
688 };
689
690 precomputed_trace_builder.process_sel_range_8(trace);
691 precomputed_trace_builder.process_sel_range_16(trace);
692 precomputed_trace_builder.process_misc(trace, 1 << 16);
693 precomputed_trace_builder.process_tag_parameters(trace);
694 memory_trace_builder.process(mem_events, trace);
695
696 uint32_t memory_row = 0;
697 for (uint32_t row = 0; row < trace.get_num_rows(); row++) {
698 if (trace.get(C::memory_sel, row) == 1) {
699 memory_row = row;
700 break;
701 }
702 }
703
704 // Attempt ghost row injection: sel=0 but is_write_memory_value=1
705 uint32_t ghost_row = 0;
706 trace.set(ghost_row,
707 std::vector<std::pair<Column, FF>>{
708 { C::precomputed_first_row, 1 },
709 { C::execution_clk, ghost_row },
710 { C::precomputed_zero, 0 },
711 { C::emit_public_log_sel, 0 },
712 { C::emit_public_log_is_write_memory_value, 1 },
713 { C::emit_public_log_error_out_of_bounds, 0 },
714 { C::emit_public_log_sel_read_memory, 1 },
715 { C::emit_public_log_execution_clk, malicious_clk },
716 { C::emit_public_log_space_id, malicious_space_id },
717 { C::emit_public_log_log_address, malicious_log_addr },
718 { C::emit_public_log_value, malicious_value },
719 { C::emit_public_log_tag, static_cast<uint8_t>(malicious_tag) },
720 { C::emit_public_log_public_inputs_value, malicious_value },
721 });
722
723 trace.set(C::memory_sel_public_log_read, memory_row, 1);
724
725 // The fix: sel_read_memory = sel * is_write_memory_value * (1 - error_out_of_bounds)
726 // Gating by sel should cause the relation check to fail
727 // because sel_read_memory=1 and sel=0 violates this constraint
728 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace),
729 "SEL_SHOULD_READ_MEMORY_IS_SEL_AND_WRITE_MEM_AND_NO_ERR");
730}
731
732TEST(EmitPublicLogConstrainingTest, NegativeSelToggledAtStartEnd)
733{
734 // Constraint: (start + end) * (1 - sel) = 0
735 // If start=1, sel must be 1.
736 TestTraceContainer trace = TestTraceContainer({ {
737 { C::emit_public_log_sel, 1 },
738 { C::emit_public_log_start, 1 },
739 } });
740
741 check_relation<emit_public_log>(trace, emit_public_log::SR_SEL_ON_START_OR_END);
742
743 trace.set(C::emit_public_log_sel, 0, 0);
744
745 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_SEL_ON_START_OR_END),
746 "SEL_ON_START_OR_END");
747}
748
749TEST(EmitPublicLogConstrainingTest, NegativeInitialSeenWrongTag)
750{
751 // Constraint: start * seen_wrong_tag = 0
752 // At start=1, seen_wrong_tag must be 0.
753 TestTraceContainer trace = TestTraceContainer({ {
754 { C::emit_public_log_sel, 1 },
755 { C::emit_public_log_start, 1 },
756 { C::emit_public_log_seen_wrong_tag, 0 },
757 } });
758
759 check_relation<emit_public_log>(trace, emit_public_log::SR_INITIAL_SEEN_WRONG_TAG);
760
761 trace.set(C::emit_public_log_seen_wrong_tag, 0, 1);
762
763 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_INITIAL_SEEN_WRONG_TAG),
764 "INITIAL_SEEN_WRONG_TAG");
765}
766
767TEST(EmitPublicLogConstrainingTest, NegativeCheckEndTagMismatch)
768{
769 // Constraint: end * (error_tag_mismatch - seen_wrong_tag) = 0
770 // At end=1, error_tag_mismatch must equal seen_wrong_tag.
771 TestTraceContainer trace = TestTraceContainer({ {
772 { C::emit_public_log_sel, 1 },
773 { C::emit_public_log_end, 1 },
774 { C::emit_public_log_error_tag_mismatch, 1 },
775 { C::emit_public_log_seen_wrong_tag, 1 },
776 } });
777
778 check_relation<emit_public_log>(trace, emit_public_log::SR_CHECK_END_TAG_MISMATCH);
779
780 trace.set(C::emit_public_log_error_tag_mismatch, 0, 0);
781
782 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_CHECK_END_TAG_MISMATCH),
783 "CHECK_END_TAG_MISMATCH");
784}
785
786TEST(EmitPublicLogConstrainingTest, NegativeWriteContractAddressAfterStart)
787{
788 // Constraint: is_write_contract_address' = start (global, not gated by sel)
789 // Row 1's is_write_contract_address must equal row 0's start.
790 TestTraceContainer trace = TestTraceContainer({ {
791 { C::emit_public_log_sel, 1 },
792 { C::emit_public_log_start, 1 },
793 },
794 {
795 { C::emit_public_log_is_write_contract_address, 1 },
796 } });
797
798 check_relation<emit_public_log>(trace, emit_public_log::SR_WRITE_CONTRACT_ADDRESS_AFTER_START);
799
800 trace.set(C::emit_public_log_is_write_contract_address, 1, 0);
801
803 check_relation<emit_public_log>(trace, emit_public_log::SR_WRITE_CONTRACT_ADDRESS_AFTER_START),
804 "WRITE_CONTRACT_ADDRESS_AFTER_START");
805}
806
807TEST(EmitPublicLogConstrainingTest, NegativeSetAndProgateValueWrite)
808{
809 // Constraint: NOT_END * (is_write_memory_value + is_write_contract_address - is_write_memory_value') = 0
810 // When sel=1 and end=0: is_write_memory_value' = is_write_memory_value + is_write_contract_address
811 TestTraceContainer trace = TestTraceContainer({ {
812 { C::emit_public_log_sel, 1 },
813 { C::emit_public_log_is_write_contract_address, 1 },
814 { C::emit_public_log_is_write_memory_value, 0 },
815 },
816 {
817 { C::emit_public_log_sel, 1 },
818 { C::emit_public_log_end, 1 },
819 { C::emit_public_log_is_write_memory_value, 1 },
820 } });
821
822 check_relation<emit_public_log>(trace, emit_public_log::SR_SET_AND_PROGATE_VALUE_WRITE);
823
824 trace.set(C::emit_public_log_is_write_memory_value, 1, 0);
825
827 "SET_AND_PROGATE_VALUE_WRITE");
828}
829
830TEST(EmitPublicLogConstrainingTest, NegativeDisabledMemReadValueZero)
831{
832 // Constraint: (sel - sel_read_memory) * value = 0
833 // When memory read is disabled (sel=1, sel_read_memory=0), value must be 0.
834 TestTraceContainer trace = TestTraceContainer({ {
835 { C::emit_public_log_sel, 1 },
836 { C::emit_public_log_sel_read_memory, 0 },
837 { C::emit_public_log_value, 0 },
838 } });
839
840 check_relation<emit_public_log>(trace, emit_public_log::SR_DISABLED_MEM_READ_VALUE_ZERO);
841
842 trace.set(C::emit_public_log_value, 0, 42);
843
845 "DISABLED_MEM_READ_VALUE_ZERO");
846}
847
848TEST(EmitPublicLogConstrainingTest, NegativeDisabledMemReadTagFF)
849{
850 // Constraint: (sel - sel_read_memory) * (MEM_TAG_FF - tag) = 0
851 // When memory read is disabled (sel=1, sel_read_memory=0), tag must be MEM_TAG_FF (0).
852 TestTraceContainer trace = TestTraceContainer({ {
853 { C::emit_public_log_sel, 1 },
854 { C::emit_public_log_sel_read_memory, 0 },
855 { C::emit_public_log_tag, 0 },
856 } });
857
858 check_relation<emit_public_log>(trace, emit_public_log::SR_DISABLED_MEM_READ_TAG_FF);
859
860 trace.set(C::emit_public_log_tag, 0, 3);
861
862 EXPECT_THROW_WITH_MESSAGE(check_relation<emit_public_log>(trace, emit_public_log::SR_DISABLED_MEM_READ_TAG_FF),
863 "DISABLED_MEM_READ_TAG_FF");
864}
865
866} // namespace
867
868} // 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_WRONG_TAG_CHECK
static constexpr size_t SR_SEL_ON_START_OR_END
static constexpr size_t SR_SPACE_ID_CONSISTENCY
static constexpr size_t SR_CHECK_END_TAG_MISMATCH
static constexpr size_t SR_LOG_ADDRESS_INCREMENT
static constexpr size_t SR_CONTRACT_ADDRESS_CONSISTENCY
static constexpr size_t SR_SEL_SHOULD_WRITE_TO_PUBLIC_INPUTS_CONSISTENCY
static constexpr size_t SR_ERROR_OUT_OF_BOUNDS_CONSISTENCY
static constexpr size_t SR_SET_AND_PROGATE_VALUE_WRITE
static constexpr size_t SR_WRITE_CONTRACT_ADDRESS_AFTER_START
static constexpr size_t SR_START_AFTER_LATCH
static constexpr size_t SR_DISABLED_MEM_READ_TAG_FF
static constexpr size_t SR_TRACE_CONTINUITY
static constexpr size_t SR_DISABLED_MEM_READ_VALUE_ZERO
static constexpr size_t SR_EXEC_CLK_CONSISTENCY
static constexpr size_t SR_REMAINING_ROWS_DECREMENT
static constexpr size_t SR_INITIAL_SEEN_WRONG_TAG
static constexpr size_t SR_ERROR_TAG_MISMATCH_CONSISTENCY
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)
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< EmitPublicLogWriteEvent, CheckPointEventType > EmitPublicLogEvent
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
std::vector< FF > random_fields(size_t n)
Definition fixtures.cpp:23
AvmFlavorSettings::FF FF
Definition field.hpp:10
uint32_t MemoryAddress
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
tracegen::PublicInputsTraceBuilder public_inputs_builder
Definition tx.test.cpp:81