Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
public_data_tree.test.cpp
Go to the documentation of this file.
1#include <gmock/gmock.h>
2#include <gtest/gtest.h>
3
4#include <cmath>
5#include <cstdint>
6
39
40namespace bb::avm2::constraining {
41namespace {
42
43using ::testing::NiceMock;
44
45using testing::TestMemoryTree;
46
47using simulation::DeduplicatingEventEmitter;
48using simulation::EventEmitter;
49using simulation::ExecutionIdManager;
50using simulation::FieldGreaterThan;
51using simulation::FieldGreaterThanEvent;
52using simulation::MerkleCheck;
53using simulation::MerkleCheckEvent;
54using simulation::MockGreaterThan;
55using simulation::Poseidon2;
56using simulation::Poseidon2HashEvent;
57using simulation::Poseidon2PermutationEvent;
58using simulation::Poseidon2PermutationMemoryEvent;
59using simulation::PublicDataTreeCheck;
62using simulation::RangeCheck;
63using simulation::RangeCheckEvent;
66
67using tracegen::ExecutionTraceBuilder;
68using tracegen::FieldGreaterThanTraceBuilder;
69using tracegen::MerkleCheckTraceBuilder;
70using tracegen::Poseidon2TraceBuilder;
71using tracegen::PrecomputedTraceBuilder;
72using tracegen::PublicDataTreeTraceBuilder;
73using tracegen::PublicInputsTraceBuilder;
74using tracegen::RangeCheckTraceBuilder;
75using tracegen::TestTraceContainer;
76
78using C = Column;
79using public_data_check = bb::avm2::public_data_check<FF>;
80using public_data_squash = bb::avm2::public_data_squash<FF>;
82
83AztecAddress contract_address = 1;
84
85class PublicDataTreeCheckConstrainingTest : public ::testing::Test {
86 protected:
87 PublicDataTreeCheckConstrainingTest()
89
90 EventEmitter<Poseidon2HashEvent> hash_event_emitter;
91 EventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
92 EventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
93
94 ExecutionIdManager execution_id_manager;
95 NiceMock<MockGreaterThan> mock_gt;
97 Poseidon2(execution_id_manager, mock_gt, hash_event_emitter, perm_event_emitter, perm_mem_event_emitter);
98};
99
100struct TestParams {
104};
105
106std::vector<TestParams> positive_tests = {
107 // Exists = true, leaf pointers to infinity
108 TestParams{ .slot = 42,
109 .value = 27,
110 .low_leaf = PublicDataTreeLeafPreimage(
111 PublicDataLeafValue(unconstrained_compute_leaf_slot(contract_address, 42), 27), 0, 0) },
112 // Exists = true, leaf points to higher value
114 .slot = 42,
115 .value = 27,
116 .low_leaf = PublicDataTreeLeafPreimage(
117 PublicDataLeafValue(unconstrained_compute_leaf_slot(contract_address, 42), 27), 28, FF::neg_one()) },
118 // Exists = false, low leaf points to infinity
119 TestParams{ .slot = 42, .value = 0, .low_leaf = PublicDataTreeLeafPreimage(PublicDataLeafValue(10, 0), 0, 0) },
120 // Exists = false, low leaf points to higher value
122 .slot = 42, .value = 0, .low_leaf = PublicDataTreeLeafPreimage(PublicDataLeafValue(10, 0), 28, FF::neg_one()) }
123};
124
125class PublicDataReadPositiveTests : public PublicDataTreeCheckConstrainingTest,
126 public ::testing::WithParamInterface<TestParams> {};
127
128TEST_P(PublicDataReadPositiveTests, Positive)
129{
130 const auto& param = GetParam();
131
132 auto test_public_inputs = testing::PublicInputsBuilder().build();
133
134 EventEmitter<MerkleCheckEvent> merkle_event_emitter;
135 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
136
137 EventEmitter<RangeCheckEvent> range_check_emitter;
139
140 DeduplicatingEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
141 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
142
143 EventEmitter<PublicDataTreeCheckEvent> public_data_tree_check_event_emitter;
144 PublicDataTreeCheck public_data_tree_check_simulator(
145 poseidon2, merkle_check, field_gt, execution_id_manager, public_data_tree_check_event_emitter);
146
147 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
148 RangeCheckTraceBuilder range_check_builder;
149 Poseidon2TraceBuilder poseidon2_builder;
150 MerkleCheckTraceBuilder merkle_check_builder;
151 FieldGreaterThanTraceBuilder field_gt_builder;
152 PrecomputedTraceBuilder precomputed_builder;
153 PublicInputsTraceBuilder public_inputs_builder;
154 PublicDataTreeTraceBuilder public_data_tree_read_builder;
155
156 FF low_leaf_hash = poseidon2.hash(param.low_leaf.get_hash_inputs());
157 uint64_t leaf_index = 30;
158 std::vector<FF> sibling_path;
159 sibling_path.reserve(PUBLIC_DATA_TREE_HEIGHT);
160 for (size_t i = 0; i < PUBLIC_DATA_TREE_HEIGHT; ++i) {
161 sibling_path.emplace_back(i);
162 }
163 FF root = unconstrained_root_from_path(DOM_SEP__PUBLIC_DATA_MERKLE, low_leaf_hash, leaf_index, sibling_path);
164
165 public_data_tree_check_simulator.assert_read(param.slot,
166 contract_address,
167 param.value,
168 param.low_leaf,
169 leaf_index,
170 sibling_path,
171 AppendOnlyTreeSnapshot{
172 .root = root,
173 .next_available_leaf_index = 128,
174 });
175
176 precomputed_builder.process_misc(trace, 1 << 16);
178 public_inputs_builder.process_public_inputs(trace, test_public_inputs);
179 public_inputs_builder.process_public_inputs_aux_precomputed(trace);
180 range_check_builder.process(range_check_emitter.dump_events(), trace);
182 merkle_check_builder.process(merkle_event_emitter.dump_events(), trace);
183 field_gt_builder.process(field_gt_event_emitter.dump_events(), trace);
184 public_data_tree_read_builder.process(public_data_tree_check_event_emitter.dump_events(), trace);
185
186 check_all_interactions<PublicDataTreeTraceBuilder>(trace);
187
188 check_relation<public_data_check>(trace);
189 check_relation<public_data_squash>(trace);
190}
191
192INSTANTIATE_TEST_SUITE_P(PublicDataTreeConstrainingTest,
193 PublicDataReadPositiveTests,
194 ::testing::ValuesIn(positive_tests));
195
196TEST(PublicDataTreeConstrainingTest, NegativeStartCondition)
197{
198 // Test constraint: sel' * (1 - sel) * (1 - precomputed.first_row) = 0
199 TestTraceContainer trace({ {
200 { C::public_data_check_sel, 0 },
201 { C::precomputed_first_row, 1 },
202 },
203 {
204 { C::public_data_check_sel, 1 },
205 },
206 {
207 { C::public_data_check_sel, 1 },
208 } });
209
210 check_relation<public_data_check>(trace, public_data_check::SR_TRACE_CONTINUITY);
211
212 // Invalid: sel can't be activated if prev is not the first row
213 trace.set(C::precomputed_first_row, 0, 0);
214
215 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_TRACE_CONTINUITY),
217}
218
219TEST(PublicDataTreeConstrainingTest, NegativeExistsFlagCheck)
220{
221 // Test constraint: sel * (LEAF_SLOT_LOW_LEAF_SLOT_DIFF * (LEAF_EXISTS * (1 - leaf_slot_low_leaf_slot_diff_inv) +
222 // leaf_slot_low_leaf_slot_diff_inv) - 1 + LEAF_EXISTS) = 0
223 TestTraceContainer trace({
224 { { C::public_data_check_sel, 1 },
225 { C::public_data_check_leaf_slot, 27 },
226 { C::public_data_check_low_leaf_slot, 27 },
227 { C::public_data_check_leaf_slot_low_leaf_slot_diff_inv, 0 },
228 { C::public_data_check_leaf_not_exists, 0 } },
229 { { C::public_data_check_sel, 1 },
230 { C::public_data_check_leaf_slot, 28 },
231 { C::public_data_check_low_leaf_slot, 27 },
232 { C::public_data_check_leaf_slot_low_leaf_slot_diff_inv, FF(1).invert() },
233 { C::public_data_check_leaf_not_exists, 1 } },
234 });
235
236 check_relation<public_data_check>(trace, public_data_check::SR_EXISTS_FLAG_CHECK);
237
238 trace.set(C::public_data_check_leaf_not_exists, 0, 1);
239
240 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_EXISTS_FLAG_CHECK),
242
243 trace.set(C::public_data_check_leaf_not_exists, 0, 0);
244 trace.set(C::public_data_check_leaf_not_exists, 1, 0);
245
246 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_EXISTS_FLAG_CHECK),
248}
249
250TEST(PublicDataTreeConstrainingTest, NegativeNextSlotIsZero)
251{
252 // Test constraint: leaf_not_exists * (low_leaf_next_slot * (NEXT_SLOT_IS_ZERO * (1 - next_slot_inv) +
253 // next_slot_inv) - 1 + NEXT_SLOT_IS_ZERO) = 0
254 TestTraceContainer trace({
255 {
256 { C::public_data_check_leaf_not_exists, 1 },
257 { C::public_data_check_low_leaf_next_slot, 0 },
258 { C::public_data_check_next_slot_inv, 0 },
259 { C::public_data_check_next_slot_is_nonzero, 0 },
260 },
261 {
262 { C::public_data_check_leaf_not_exists, 1 },
263 { C::public_data_check_low_leaf_next_slot, 1 },
264 { C::public_data_check_next_slot_inv, FF(1).invert() },
265 { C::public_data_check_next_slot_is_nonzero, 1 },
266 },
267 });
268
269 check_relation<public_data_check>(trace, public_data_check::SR_NEXT_SLOT_IS_ZERO_CHECK);
270
271 trace.set(C::public_data_check_next_slot_is_nonzero, 0, 1);
272
273 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_NEXT_SLOT_IS_ZERO_CHECK),
275
276 trace.set(C::public_data_check_next_slot_is_nonzero, 0, 0);
277 trace.set(C::public_data_check_next_slot_is_nonzero, 1, 0);
278
279 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_NEXT_SLOT_IS_ZERO_CHECK),
281}
282
283TEST(PublicDataTreeConstrainingTest, NegativeValueIsCorrect)
284{
285 // Test constraint: leaf_not_exists * (low_leaf_next_slot * (NEXT_SLOT_IS_ZERO * (1 - next_slot_inv) +
286 // next_slot_inv) - 1 + NEXT_SLOT_IS_ZERO) = 0
287 TestTraceContainer trace({
288 {
289 { C::public_data_check_low_leaf_value, 27 },
290 { C::public_data_check_leaf_not_exists, 0 },
291 { C::public_data_check_value, 27 },
292 },
293 {
294 { C::public_data_check_low_leaf_value, 27 },
295 { C::public_data_check_leaf_not_exists, 1 },
296 { C::public_data_check_value, 0 },
297 },
298 });
299
300 check_relation<public_data_check>(trace, public_data_check::SR_VALUE_IS_CORRECT);
301
302 // Invalid, if leaf exists, the value should be the low leaf value
303 trace.set(C::public_data_check_value, 0, 0);
304
305 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_VALUE_IS_CORRECT),
307
308 trace.set(C::public_data_check_value, 0, 27);
309 // Invalid, if leaf does not exists, the value should be zero
310 trace.set(C::public_data_check_value, 1, 27);
311
312 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_VALUE_IS_CORRECT),
314}
315
316TEST_F(PublicDataTreeCheckConstrainingTest, PositiveWriteExists)
317{
318 FF slot = 40;
319 FF leaf_slot = unconstrained_compute_leaf_slot(contract_address, slot);
320 FF new_value = 27;
321 TestMemoryTree<aztec::PublicDataMerkleHashPolicy> public_data_tree(8, PUBLIC_DATA_TREE_HEIGHT);
322
323 AvmAccumulatedData accumulated_data = {};
324 accumulated_data.public_data_writes[0] = PublicDataWrite{
325 .leaf_slot = leaf_slot,
326 .value = new_value,
327 };
328
329 auto test_public_inputs = testing::PublicInputsBuilder()
330 .set_accumulated_data(accumulated_data)
331 .set_accumulated_data_array_lengths({ .public_data_writes = 1 })
332 .build();
333
334 EventEmitter<MerkleCheckEvent> merkle_event_emitter;
335 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
336
337 EventEmitter<RangeCheckEvent> range_check_emitter;
339
340 DeduplicatingEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
341 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
342
343 EventEmitter<PublicDataTreeCheckEvent> public_data_tree_check_event_emitter;
344 PublicDataTreeCheck public_data_tree_check_simulator(
345 poseidon2, merkle_check, field_gt, execution_id_manager, public_data_tree_check_event_emitter);
346
347 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
348 RangeCheckTraceBuilder range_check_builder;
349 Poseidon2TraceBuilder poseidon2_builder;
350 MerkleCheckTraceBuilder merkle_check_builder;
351 FieldGreaterThanTraceBuilder field_gt_builder;
352 PrecomputedTraceBuilder precomputed_builder;
353 PublicInputsTraceBuilder public_inputs_builder;
354 PublicDataTreeTraceBuilder public_data_tree_builder;
355
357 FF low_leaf_hash = UnconstrainedPoseidon2::hash(low_leaf.get_hash_inputs());
358 uint64_t low_leaf_index = 30;
359 public_data_tree.update_element(low_leaf_index, low_leaf_hash);
360
361 AppendOnlyTreeSnapshot prev_snapshot =
362 AppendOnlyTreeSnapshot{ .root = public_data_tree.root(), .next_available_leaf_index = 128 };
363 std::vector<FF> low_leaf_sibling_path = public_data_tree.get_sibling_path(low_leaf_index);
364
365 PublicDataTreeLeafPreimage updated_low_leaf = low_leaf;
366 updated_low_leaf.leaf.value = new_value;
367 FF updated_low_leaf_hash = UnconstrainedPoseidon2::hash(updated_low_leaf.get_hash_inputs());
368 public_data_tree.update_element(low_leaf_index, updated_low_leaf_hash);
369
370 FF intermediate_root = public_data_tree.root();
371 std::vector<FF> insertion_sibling_path = public_data_tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
372
373 // No insertion happens
374 AppendOnlyTreeSnapshot next_snapshot =
375 AppendOnlyTreeSnapshot{ .root = intermediate_root,
376 .next_available_leaf_index = prev_snapshot.next_available_leaf_index };
377
378 AppendOnlyTreeSnapshot result_snapshot = public_data_tree_check_simulator.write(slot,
379 contract_address,
380 new_value,
381 low_leaf,
382 low_leaf_index,
383 low_leaf_sibling_path,
384 prev_snapshot,
385 insertion_sibling_path,
386 false);
387 EXPECT_EQ(next_snapshot, result_snapshot);
388
389 precomputed_builder.process_misc(trace, 1 << 16);
391 public_inputs_builder.process_public_inputs(trace, test_public_inputs);
392 public_inputs_builder.process_public_inputs_aux_precomputed(trace);
393 range_check_builder.process(range_check_emitter.dump_events(), trace);
395 merkle_check_builder.process(merkle_event_emitter.dump_events(), trace);
396 field_gt_builder.process(field_gt_event_emitter.dump_events(), trace);
397 public_data_tree_builder.process(public_data_tree_check_event_emitter.dump_events(), trace);
398
399 check_relation<public_data_check>(trace);
400 check_relation<public_data_squash>(trace);
401
402 check_all_interactions<PublicDataTreeTraceBuilder>(trace);
403}
404
405TEST_F(PublicDataTreeCheckConstrainingTest, PositiveSquashing)
406{
407 // This test will write
408 // 1. slot with value 27
409 // 2. (dummy write to check ordering) dummy_slot with value 0
410 // 3. slot with value 28
411 // If squashing is correct, we should get (slot, 28), (dummy_slot, 0)
412
413 // Compute hashes for two candidate slot values
414 FF slot_a = 42;
415 FF slot_b = 51;
416 FF leaf_slot_a = unconstrained_compute_leaf_slot(contract_address, slot_a);
417 FF leaf_slot_b = unconstrained_compute_leaf_slot(contract_address, slot_b);
418
419 // We need leaf_slot < dummy_leaf_slot for the tree ordering.
420 // Since hash outputs are pseudo-random, we pick the smaller hash as leaf_slot.
421 FF slot;
422 FF dummy_slot;
423 FF leaf_slot;
424 FF dummy_leaf_slot;
425 if (static_cast<uint256_t>(leaf_slot_a) < static_cast<uint256_t>(leaf_slot_b)) {
426 slot = slot_a;
427 dummy_slot = slot_b;
428 leaf_slot = leaf_slot_a;
429 dummy_leaf_slot = leaf_slot_b;
430 } else {
431 slot = slot_b;
432 dummy_slot = slot_a;
433 leaf_slot = leaf_slot_b;
434 dummy_leaf_slot = leaf_slot_a;
435 }
436
437 FF new_value = 27; // Will get squashed
438 FF updated_value = 28;
439 FF dummy_leaf_value = 0;
440
441 // The expected tree order is low_leaf_slot := (leaf_slot - 2) < leaf_slot < second_low_leaf_slot < dummy_leaf_slot
442 // We set second_low_leaf_slot == leaf_slot + 1. (We do not need to know the preimage for second_low_leaf_slot)
443 ASSERT_GT(dummy_leaf_slot, leaf_slot + 1);
444
445 FF low_leaf_slot = 40;
446 TestMemoryTree<aztec::PublicDataMerkleHashPolicy> public_data_tree(8, PUBLIC_DATA_TREE_HEIGHT);
447
448 AvmAccumulatedData accumulated_data = {};
449 accumulated_data.public_data_writes[0] = PublicDataWrite{
450 .leaf_slot = leaf_slot,
451 .value = updated_value,
452 };
453
454 accumulated_data.public_data_writes[1] = PublicDataWrite{
455 .leaf_slot = dummy_leaf_slot,
456 .value = dummy_leaf_value,
457 };
458
459 auto test_public_inputs = testing::PublicInputsBuilder()
460 .set_accumulated_data(accumulated_data)
461 .set_accumulated_data_array_lengths({ .public_data_writes = 2 })
462 .build();
463
464 EventEmitter<MerkleCheckEvent> merkle_event_emitter;
465 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
466
467 EventEmitter<RangeCheckEvent> range_check_emitter;
469
470 DeduplicatingEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
471 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
472
473 EventEmitter<PublicDataTreeCheckEvent> public_data_tree_check_event_emitter;
474 PublicDataTreeCheck public_data_tree_check_simulator(
475 poseidon2, merkle_check, field_gt, execution_id_manager, public_data_tree_check_event_emitter);
476
477 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
478 RangeCheckTraceBuilder range_check_builder;
479 Poseidon2TraceBuilder poseidon2_builder;
480 MerkleCheckTraceBuilder merkle_check_builder;
481 FieldGreaterThanTraceBuilder field_gt_builder;
482 PrecomputedTraceBuilder precomputed_builder;
483 PublicInputsTraceBuilder public_inputs_builder;
484 PublicDataTreeTraceBuilder public_data_tree_read_builder;
485
486 // Insert leaves which are already present in the tree (test preparation)
488 FF low_leaf_hash = UnconstrainedPoseidon2::hash(low_leaf.get_hash_inputs());
489 uint64_t low_leaf_index = 30;
490 public_data_tree.update_element(low_leaf_index, low_leaf_hash);
491
492 uint64_t second_low_leaf_index = 31;
493 FF second_low_leaf_slot = leaf_slot + 1;
494
495 PublicDataTreeLeafPreimage second_low_leaf =
496 PublicDataTreeLeafPreimage(PublicDataLeafValue(second_low_leaf_slot, 1), 0, 0);
497 FF second_low_leaf_hash = UnconstrainedPoseidon2::hash(second_low_leaf.get_hash_inputs());
498 public_data_tree.update_element(second_low_leaf_index, second_low_leaf_hash);
499
500 AppendOnlyTreeSnapshot prev_snapshot =
501 AppendOnlyTreeSnapshot{ .root = public_data_tree.root(), .next_available_leaf_index = 128 };
502 std::vector<FF> low_leaf_sibling_path = public_data_tree.get_sibling_path(low_leaf_index);
503
504 // Insertion section
505 PublicDataTreeLeafPreimage updated_low_leaf = low_leaf;
506 updated_low_leaf.nextIndex = prev_snapshot.next_available_leaf_index;
507 updated_low_leaf.nextKey = leaf_slot;
508 FF updated_low_leaf_hash = UnconstrainedPoseidon2::hash(updated_low_leaf.get_hash_inputs());
509 public_data_tree.update_element(low_leaf_index, updated_low_leaf_hash);
510
511 std::vector<FF> insertion_sibling_path = public_data_tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
512
514 PublicDataTreeLeafPreimage(PublicDataLeafValue(leaf_slot, new_value), low_leaf.nextIndex, low_leaf.nextKey);
515 FF new_leaf_hash = UnconstrainedPoseidon2::hash(new_leaf.get_hash_inputs());
516
517 uint64_t value_to_be_updated_leaf_index = prev_snapshot.next_available_leaf_index;
518 public_data_tree.update_element(value_to_be_updated_leaf_index, new_leaf_hash);
519
520 AppendOnlyTreeSnapshot next_snapshot =
521 AppendOnlyTreeSnapshot{ .root = public_data_tree.root(),
522 .next_available_leaf_index = prev_snapshot.next_available_leaf_index + 1 };
523
524 AppendOnlyTreeSnapshot snapshot_after_insertion = public_data_tree_check_simulator.write(slot,
525 contract_address,
526 new_value,
527 low_leaf,
528 low_leaf_index,
529 low_leaf_sibling_path,
530 prev_snapshot,
531 insertion_sibling_path,
532 false);
533 EXPECT_EQ(next_snapshot, snapshot_after_insertion);
534
535 // Dummy insertion section
536
537 prev_snapshot = snapshot_after_insertion;
538
539 low_leaf = second_low_leaf;
540 low_leaf_hash = second_low_leaf_hash;
541 low_leaf_index = second_low_leaf_index;
542 low_leaf_sibling_path = public_data_tree.get_sibling_path(low_leaf_index);
543
544 updated_low_leaf = low_leaf;
545 updated_low_leaf.nextIndex = prev_snapshot.next_available_leaf_index;
546 updated_low_leaf.nextKey = dummy_leaf_slot;
547 updated_low_leaf_hash = UnconstrainedPoseidon2::hash(updated_low_leaf.get_hash_inputs());
548 public_data_tree.update_element(low_leaf_index, updated_low_leaf_hash);
549 insertion_sibling_path = public_data_tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
550
552 PublicDataLeafValue(dummy_leaf_slot, dummy_leaf_value), low_leaf.nextIndex, low_leaf.nextKey);
553 new_leaf_hash = UnconstrainedPoseidon2::hash(new_leaf.get_hash_inputs());
554
555 uint64_t dummy_leaf_index = prev_snapshot.next_available_leaf_index;
556 public_data_tree.update_element(dummy_leaf_index, new_leaf_hash);
557
558 next_snapshot = AppendOnlyTreeSnapshot{ .root = public_data_tree.root(),
559 .next_available_leaf_index = prev_snapshot.next_available_leaf_index + 1 };
560
561 AppendOnlyTreeSnapshot snapshot_after_dummy_insertion =
562 public_data_tree_check_simulator.write(dummy_slot,
563 contract_address,
564 dummy_leaf_value,
565 low_leaf,
566 low_leaf_index,
567 low_leaf_sibling_path,
568 prev_snapshot,
569 insertion_sibling_path,
570 false);
571 EXPECT_EQ(next_snapshot, snapshot_after_dummy_insertion);
572
573 // Update section
574
575 low_leaf_index = value_to_be_updated_leaf_index;
576 prev_snapshot = snapshot_after_dummy_insertion;
577
578 low_leaf = PublicDataTreeLeafPreimage(PublicDataLeafValue(leaf_slot, new_value), 0, 0);
579 low_leaf_sibling_path = public_data_tree.get_sibling_path(low_leaf_index);
580
581 updated_low_leaf = low_leaf;
582 updated_low_leaf.leaf.value = updated_value;
583 updated_low_leaf_hash = UnconstrainedPoseidon2::hash(updated_low_leaf.get_hash_inputs());
584 public_data_tree.update_element(low_leaf_index, updated_low_leaf_hash);
585 insertion_sibling_path = public_data_tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
586
587 // No insertion happens
588 next_snapshot = AppendOnlyTreeSnapshot{ .root = public_data_tree.root(),
589 .next_available_leaf_index = prev_snapshot.next_available_leaf_index };
590 AppendOnlyTreeSnapshot snapshot_after_update = public_data_tree_check_simulator.write(slot,
591 contract_address,
592 updated_value,
593 low_leaf,
594 low_leaf_index,
595 low_leaf_sibling_path,
596 prev_snapshot,
597 insertion_sibling_path,
598 true);
599 EXPECT_EQ(next_snapshot, snapshot_after_update);
600
601 ASSERT_LE(test_public_inputs.accumulated_data_array_lengths.public_data_writes,
602 test_public_inputs.accumulated_data.public_data_writes.size());
603
604 std::vector<FF> written_slots;
605 std::ranges::transform(test_public_inputs.accumulated_data.public_data_writes,
606 std::back_inserter(written_slots),
607 [](const PublicDataWrite& write) { return write.leaf_slot; });
608
609 public_data_tree_check_simulator.generate_ff_gt_events_for_squashing(written_slots);
610
611 precomputed_builder.process_misc(trace, 1 << 16);
613 public_inputs_builder.process_public_inputs(trace, test_public_inputs);
614 public_inputs_builder.process_public_inputs_aux_precomputed(trace);
615 range_check_builder.process(range_check_emitter.dump_events(), trace);
617 merkle_check_builder.process(merkle_event_emitter.dump_events(), trace);
618 field_gt_builder.process(field_gt_event_emitter.dump_events(), trace);
619 public_data_tree_read_builder.process(public_data_tree_check_event_emitter.dump_events(), trace);
620
621 check_relation<public_data_check>(trace);
622 check_relation<public_data_squash>(trace);
623
624 check_all_interactions<PublicDataTreeTraceBuilder>(trace);
625}
626
627TEST(PublicDataTreeConstrainingTest, NegativeLowLeafValueUpdate)
628{
629 // Test constraint: write * ((low_leaf_value - value) * leaf_not_exists + value - updated_low_leaf_value) = 0
630 TestTraceContainer trace({
631 {
632 { C::public_data_check_write, 1 },
633 { C::public_data_check_leaf_not_exists, 0 },
634 { C::public_data_check_low_leaf_value, 27 },
635 { C::public_data_check_value, 28 },
636 { C::public_data_check_updated_low_leaf_value, 28 },
637 },
638 {
639 { C::public_data_check_write, 1 },
640 { C::public_data_check_leaf_not_exists, 1 },
641 { C::public_data_check_low_leaf_value, 27 },
642 { C::public_data_check_value, 28 },
643 { C::public_data_check_updated_low_leaf_value, 27 },
644 },
645 });
646
647 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_VALUE_UPDATE);
648
649 // Invalid, if leaf exists, updated_low_leaf_value should be the value to write
650 trace.set(C::public_data_check_leaf_not_exists, 0, 1);
651
652 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_VALUE_UPDATE),
654
655 trace.set(C::public_data_check_leaf_not_exists, 0, 0);
656 // Invalid, if leaf does not exist, updated_low_leaf_value should be the low leaf value
657 trace.set(C::public_data_check_leaf_not_exists, 1, 0);
658
659 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_VALUE_UPDATE),
661}
662
663TEST(PublicDataTreeConstrainingTest, NegativeLowLeafNextIndexUpdate)
664{
665 // Test constraint: write * ((tree_size_before_write - low_leaf_next_index) * leaf_not_exists + low_leaf_next_index
666 // - updated_low_leaf_next_index) = 0
667 TestTraceContainer trace({
668 {
669 { C::public_data_check_write, 1 },
670 { C::public_data_check_leaf_not_exists, 0 },
671 { C::public_data_check_low_leaf_next_index, 27 },
672 { C::public_data_check_tree_size_before_write, 128 },
673 { C::public_data_check_updated_low_leaf_next_index, 27 },
674 },
675 {
676 { C::public_data_check_write, 1 },
677 { C::public_data_check_leaf_not_exists, 1 },
678 { C::public_data_check_low_leaf_next_index, 27 },
679 { C::public_data_check_tree_size_before_write, 128 },
680 { C::public_data_check_updated_low_leaf_next_index, 128 },
681 },
682 });
683
684 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_INDEX_UPDATE);
685
686 // Invalid, if leaf not exists, the updated_low_leaf_next_index should be the newly inserted leaf
687 trace.set(C::public_data_check_leaf_not_exists, 0, 1);
688
690 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_INDEX_UPDATE),
692
693 trace.set(C::public_data_check_leaf_not_exists, 0, 0);
694 // Invalid, if leaf exists, the updated_low_leaf_next_index should be untouched
695 trace.set(C::public_data_check_leaf_not_exists, 1, 0);
696
698 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_INDEX_UPDATE),
700}
701
702TEST(PublicDataTreeConstrainingTest, NegativeLowLeafNextSlotUpdate)
703{
704 // Test constraint: write * ((leaf_slot - low_leaf_next_slot) * leaf_not_exists + low_leaf_next_slot -
705 // updated_low_leaf_next_slot) = 0
706 TestTraceContainer trace({
707 {
708 { C::public_data_check_write, 1 },
709 { C::public_data_check_leaf_not_exists, 0 },
710 { C::public_data_check_low_leaf_next_slot, 27 },
711 { C::public_data_check_leaf_slot, 28 },
712 { C::public_data_check_updated_low_leaf_next_slot, 27 },
713 },
714 {
715 { C::public_data_check_write, 1 },
716 { C::public_data_check_leaf_not_exists, 1 },
717 { C::public_data_check_low_leaf_next_slot, 27 },
718 { C::public_data_check_leaf_slot, 28 },
719 { C::public_data_check_updated_low_leaf_next_slot, 28 },
720 },
721 });
722
723 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_SLOT_UPDATE);
724
725 // Invalid, if leaf not exists, the updated_low_leaf_next_slot should be the newly inserted leaf slot
726 trace.set(C::public_data_check_leaf_not_exists, 0, 1);
727
729 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_SLOT_UPDATE),
731
732 trace.set(C::public_data_check_leaf_not_exists, 0, 0);
733 // Invalid, if leaf exists, the updated_low_leaf_next_slot should be untouched
734 trace.set(C::public_data_check_leaf_not_exists, 1, 0);
735
737 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_SLOT_UPDATE),
739}
740
741TEST(PublicDataTreeConstrainingTest, NegativeUpdateRootValidation)
742{
743 // Test constraint: (1 - leaf_not_exists) * write * (write_root - intermediate_root) = 0
744 TestTraceContainer trace({
745 {
746 { C::public_data_check_write, 1 },
747 { C::public_data_check_leaf_not_exists, 0 },
748 { C::public_data_check_intermediate_root, 28 },
749 { C::public_data_check_write_root, 28 },
750 },
751 {
752 { C::public_data_check_write, 1 },
753 { C::public_data_check_leaf_not_exists, 1 },
754 { C::public_data_check_intermediate_root, 28 },
755 { C::public_data_check_write_root, 30 },
756 },
757 });
758
759 check_relation<public_data_check>(trace, public_data_check::SR_LOW_LEAF_NEXT_SLOT_UPDATE);
760
761 // Invalid, if leaf exists, the write root should be the intermediate root
762 trace.set(C::public_data_check_write_root, 0, 30);
763
764 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_UPDATE_ROOT_VALIDATION),
766}
767
768TEST(PublicDataTreeConstrainingTest, NegativeSetProtocolWrite)
769{
770 // Test constraint: protocol_write + non_protocol_write = write
771 TestTraceContainer trace({ {
772 { C::public_data_check_sel, 1 },
773 { C::public_data_check_write, 1 },
774 { C::public_data_check_protocol_write, 1 },
775 } });
776
777 check_relation<public_data_check>(trace, public_data_check::SR_PROTOCOL_WRITE_CHECK);
778
779 // Invalid, must set either protocol or non protocol write
780 trace.set(C::public_data_check_protocol_write, 0, 0);
781
782 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_PROTOCOL_WRITE_CHECK),
784
785 trace.set(C::public_data_check_non_protocol_write, 0, 1);
786 check_relation<public_data_check>(trace, public_data_check::SR_PROTOCOL_WRITE_CHECK);
787
788 // Invalid, cannot both be a protocol and non protocol write
789 trace.set(C::public_data_check_protocol_write, 0, 1);
790 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_PROTOCOL_WRITE_CHECK),
792}
793
794// A read interaction (e.g. sload -> public_data_check.sel) must hit a destination row
795// where public_data_check.write = 0. Mutating write to 1 on the matching row breaks the lookup.
796TEST(PublicDataTreeConstrainingTest, NegativeReadInteractionRequiresWriteZero)
797{
798 FF slot = 42;
799 FF address = 1;
800 FF value = 27;
801 FF root = 100;
802
803 TestTraceContainer trace({
804 // Source row: sload caller.
805 {
806 { C::execution_sel_execute_sload, 1 },
807 { C::execution_register_0_, slot },
808 { C::execution_register_1_, address },
809 { C::execution_register_2_, value },
810 { C::execution_prev_public_data_tree_root, root },
811 },
812 // Destination row: matching read row in public_data_check.
813 {
814 { C::public_data_check_sel, 1 },
815 { C::public_data_check_slot, slot },
816 { C::public_data_check_address, address },
817 { C::public_data_check_value, value },
818 { C::public_data_check_root, root },
819 { C::public_data_check_write, 0 },
820 },
821 });
822
823 check_interaction<ExecutionTraceBuilder, lookup_sload_storage_read_settings>(trace);
824
825 // Mutate the destination row to write=1 and the lookup must fail to find a match.
826 trace.set(C::public_data_check_write, 1, 1);
827
828 EXPECT_THROW_WITH_MESSAGE((check_interaction<ExecutionTraceBuilder, lookup_sload_storage_read_settings>(trace)),
829 "Failed.*LOOKUP_SLOAD_STORAGE_READ. Could not find tuple in destination.");
830}
831
832// A write interaction (sstore -> public_data_check.non_protocol_write, via the multi-permutation
833// keyed by public_data_check.write) must hit a destination row where public_data_check.write = 1.
834// Mutating write to 0 removes the row from the multi-permutation candidate set.
835TEST(PublicDataTreeConstrainingTest, NegativeWriteInteractionRequiresWriteOne)
836{
837 FF slot = 42;
838 FF address = 1;
839 FF value = 27;
840 FF root_before = 100;
841 FF root_after = 200;
842 FF size_before = 128;
843 FF size_after = 129;
844 FF clk = 5;
845
846 TestTraceContainer trace({
847 // Source row: sstore caller.
848 {
849 { C::execution_sel_write_public_data, 1 },
850 { C::execution_register_0_, value },
851 { C::execution_contract_address, address },
852 { C::execution_register_1_, slot },
853 { C::execution_discard, 0 },
854 { C::execution_prev_public_data_tree_root, root_before },
855 { C::execution_public_data_tree_root, root_after },
856 { C::execution_prev_public_data_tree_size, size_before },
857 { C::execution_public_data_tree_size, size_after },
858 { C::execution_clk, clk },
859 },
860 // Destination row: matching non_protocol_write row in public_data_check.
861 {
862 { C::public_data_check_sel, 1 },
863 { C::public_data_check_write, 1 },
864 { C::public_data_check_non_protocol_write, 1 },
865 { C::public_data_check_value, value },
866 { C::public_data_check_address, address },
867 { C::public_data_check_slot, slot },
868 { C::public_data_check_discard, 0 },
869 { C::public_data_check_root, root_before },
870 { C::public_data_check_write_root, root_after },
871 { C::public_data_check_tree_size_before_write, size_before },
872 { C::public_data_check_tree_size_after_write, size_after },
873 { C::public_data_check_clk, clk },
874 },
875 });
876
877 check_multipermutation_interaction<PublicDataTreeTraceBuilder,
880
881 // Mutate the destination row to write=0: the multi-permutation indexes only rows where
882 // public_data_check.write = 1, so the source can no longer find its match.
883 trace.set(C::public_data_check_write, 1, 0);
884
888 "Failed setting selectors for PERM_SSTORE_STORAGE_WRITE");
889}
890
891TEST(PublicDataTreeConstrainingTest, NegativeWriteIdxInitialValue)
892{
893 // Test constraint: (1 - sel) * sel' * (constants.AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_PUBLIC_DATA_WRITES_ROW_IDX
894 // - write_idx') = 0
895 TestTraceContainer trace(
896 { {
897 { C::public_data_check_sel, 0 },
898 },
899 {
900 { C::public_data_check_sel, 1 },
901 { C::public_data_check_write_idx, AVM_PUBLIC_INPUTS_AVM_ACCUMULATED_DATA_PUBLIC_DATA_WRITES_ROW_IDX },
902 } });
903
904 check_relation<public_data_check>(trace, public_data_check::SR_WRITE_IDX_INITIAL_VALUE);
905
906 // Invalid, if sel goes from 0 to 1, the write_idx should be the initial value
907 trace.set(C::public_data_check_write_idx, 1, 27);
908
909 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_WRITE_IDX_INITIAL_VALUE),
911}
912
913TEST(PublicDataTreeConstrainingTest, NegativeWriteIdxIncrement)
914{
915 // Test constraint: not_end * (write_idx + sel_write_to_public_inputs - write_idx') = 0
916 TestTraceContainer trace({
917 {
918 { C::public_data_check_not_end, 1 },
919 { C::public_data_check_write_idx, 5 },
920 { C::public_data_check_sel_write_to_public_inputs, 1 },
921 },
922 {
923 { C::public_data_check_not_end, 1 },
924 { C::public_data_check_write_idx, 6 },
925 { C::public_data_check_sel_write_to_public_inputs, 0 },
926 },
927 {
928 { C::public_data_check_write_idx, 6 },
929 },
930 });
931
932 check_relation<public_data_check>(trace, public_data_check::SR_WRITE_IDX_INCREMENT);
933
934 // Invalid, if sel_write_to_public_inputs is 0, the write_idx should not increment
935 trace.set(C::public_data_check_sel_write_to_public_inputs, 0, 0);
936
937 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_WRITE_IDX_INCREMENT),
939
940 // Invalid, if sel_write_to_public_inputs is 1, the write_idx should increment
941 trace.set(C::public_data_check_sel_write_to_public_inputs, 0, 1);
942 trace.set(C::public_data_check_sel_write_to_public_inputs, 1, 1);
943
944 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_WRITE_IDX_INCREMENT),
946}
947
948// Negative clock diff decompostion
949TEST(PublicDataTreeConstrainingTest, NegativeClockDiffDecomposition)
950{
951 // Test constraint: CLK_DIFF = clk_diff_lo + 2**16 * clk_diff_hi;
952 TestTraceContainer trace({
953 {
954 { C::public_data_check_not_end, 1 },
955 { C::public_data_check_clk, 12 << 28 },
956 { C::public_data_check_clk_diff_lo, 234 },
957 { C::public_data_check_clk_diff_hi, 1 << 12 },
958 },
959 {
960 { C::public_data_check_clk, (13 << 28) + 234 },
961 },
962 });
963
964 check_relation<public_data_check>(trace, public_data_check::SR_CLK_DIFF_DECOMP);
965
966 // Mutate wrongly clk_diff_lo
967 trace.set(C::public_data_check_clk_diff_lo, 0, trace.get(C::public_data_check_clk_diff_lo, 0) + 1);
968
969 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_CLK_DIFF_DECOMP),
971
972 // Reset
973 trace.set(C::public_data_check_clk_diff_lo, 0, trace.get(C::public_data_check_clk_diff_lo, 0) - 1);
974
975 // Mutate wrongly clk_diff_hi
976 trace.set(C::public_data_check_clk_diff_hi, 0, trace.get(C::public_data_check_clk_diff_hi, 0) + 1);
977
978 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_check>(trace, public_data_check::SR_CLK_DIFF_DECOMP),
980}
981
982// Out of range clock diff
983TEST(PublicDataTreeConstrainingTest, NegativeOutOfRangeClockDiff)
984{
985 TestTraceContainer trace({
986 {
987 { C::public_data_check_not_end, 1 },
988 { C::public_data_check_clk_diff_lo, UINT16_MAX },
989 { C::public_data_check_clk_diff_hi, UINT16_MAX },
990 },
991 });
992
993 PrecomputedTraceBuilder precomputed_trace_builder;
994 precomputed_trace_builder.process_sel_range_16(trace);
995 precomputed_trace_builder.process_misc(trace, 1 << 16);
996
997 check_interaction<PublicDataTreeTraceBuilder,
1000
1001 // Mutate wrongly clk_diff_lo
1002 trace.set(C::public_data_check_clk_diff_lo, 0, UINT16_MAX + 1);
1003
1005 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_check_clk_diff_range_lo_settings>(trace)),
1006 "Failed.*LOOKUP_PUBLIC_DATA_CHECK_CLK_DIFF_RANGE_LO. Could not find tuple in destination.");
1007
1008 // Mutate wrongly clk_diff_hi
1009 trace.set(C::public_data_check_clk_diff_hi, 0, UINT16_MAX + 1);
1010
1012 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_check_clk_diff_range_hi_settings>(trace)),
1013 "Failed.*LOOKUP_PUBLIC_DATA_CHECK_CLK_DIFF_RANGE_HI. Could not find tuple in destination.");
1014}
1015
1016// Squashing subtrace
1017
1018TEST(PublicDataTreeConstrainingTest, SquashingNegativeStartCondition)
1019{
1020 // Test constraint: sel' * (1 - sel) * (1 - precomputed.first_row) = 0
1021 TestTraceContainer trace({ {
1022 { C::public_data_squash_sel, 0 },
1023 { C::precomputed_first_row, 1 },
1024 },
1025 {
1026 { C::public_data_squash_sel, 1 },
1027 },
1028 {
1029 { C::public_data_squash_sel, 1 },
1030 } });
1031
1032 check_relation<public_data_squash>(trace, public_data_squash::SR_TRACE_CONTINUITY);
1033
1034 // Invalid: sel can't be activated if prev is not the first row
1035 trace.set(C::precomputed_first_row, 0, 0);
1036
1037 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_TRACE_CONTINUITY),
1039}
1040
1041TEST(PublicDataTreeConstrainingTest, SquashingNegativeCheckClockCondition)
1042{
1043 // Test constraint: check_clock = NOT_END * (1 - leaf_slot_increase)
1044 // where NOT_END = sel * sel'
1045 // So check_clock = sel * sel' * (1 - leaf_slot_increase)
1046
1047 // Valid: check_clock=1 when sel=1, sel'=1, leaf_slot_increase=0
1048 TestTraceContainer trace({ {
1049 { C::public_data_squash_sel, 1 },
1050 { C::public_data_squash_check_clock, 1 },
1051 { C::public_data_squash_leaf_slot_increase, 0 },
1052 },
1053 {
1054 { C::public_data_squash_sel, 1 },
1055 { C::public_data_squash_check_clock, 0 },
1056 { C::public_data_squash_leaf_slot_increase, 0 },
1057 } });
1058
1059 check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_CLOCK_CONDITION);
1060
1061 // Invalid: check_clock=0 but NOT_END=1 and leaf_slot_increase=0 (should be 1)
1062 trace.set(C::public_data_squash_check_clock, 0, 0);
1063
1064 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_CLOCK_CONDITION),
1066
1067 trace.set(C::public_data_squash_check_clock, 0, 1);
1068
1069 // Invalid: check_clock=1 but leaf_slot_increase=1 (should be 0)
1070 trace.set(C::public_data_squash_leaf_slot_increase, 0, 1);
1071
1072 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_CLOCK_CONDITION),
1074
1075 trace.set(C::public_data_squash_leaf_slot_increase, 0, 0);
1076
1077 // Invalid: check_clock=1 but sel'=0 (NOT_END=0, should be 0)
1078 trace.set(C::public_data_squash_sel, 1, 0);
1079
1080 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_CLOCK_CONDITION),
1082}
1083
1084TEST(PublicDataTreeConstrainingTest, SquashingNegativeCheckSameLeafSlot)
1085{
1086 // Test constraint: check_clock * (leaf_slot - leaf_slot') = 0
1087 TestTraceContainer trace({ {
1088 { C::public_data_squash_check_clock, 0 },
1089 { C::public_data_squash_leaf_slot, 27 },
1090 },
1091 {
1092 { C::public_data_squash_check_clock, 0 },
1093 { C::public_data_squash_leaf_slot, 40 },
1094 } });
1095
1096 check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_SAME_LEAF_SLOT);
1097
1098 // Invalid: if leaf_slot_increase is 0, the leaf_slot should not be different from the previous leaf_slot
1099 trace.set(C::public_data_squash_check_clock, 0, 1);
1100
1101 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CHECK_SAME_LEAF_SLOT),
1103}
1104
1105TEST(PublicDataTreeConstrainingTest, SquashingNegativeFinalValuePropagation)
1106{
1107 // Test constraint: check_clock * (final_value - final_value') = 0;
1108 TestTraceContainer trace({ {
1109 { C::public_data_squash_sel, 1 },
1110 { C::public_data_squash_check_clock, 1 },
1111 { C::public_data_squash_final_value, 27 },
1112 },
1113 {
1114 { C::public_data_squash_sel, 1 },
1115 { C::public_data_squash_check_clock, 0 },
1116 { C::public_data_squash_final_value, 27 },
1117 } });
1118
1119 check_relation<public_data_squash>(trace, public_data_squash::SR_FINAL_VALUE_PROPAGATION);
1120
1121 // Invalid: if final value changes, check_clk must be 0
1122 trace.set(C::public_data_squash_final_value, 1, 28);
1123
1125 check_relation<public_data_squash>(trace, public_data_squash::SR_FINAL_VALUE_PROPAGATION),
1127}
1128
1129TEST(PublicDataTreeConstrainingTest, SquashingNegativeFinalValueCheck)
1130{
1131 // Test constraint:
1132 // LEAF_SLOT_END * (final_value - value) = 0;
1133 TestTraceContainer trace({ {
1134 { C::public_data_squash_sel, 1 },
1135 { C::public_data_squash_final_value, 27 },
1136 { C::public_data_squash_value, 99 },
1137 },
1138 {
1139 { C::public_data_squash_sel, 1 },
1140 { C::public_data_squash_final_value, 27 },
1141 { C::public_data_squash_leaf_slot_increase, 1 },
1142 { C::public_data_squash_value, 27 },
1143 },
1144 {
1145 { C::public_data_squash_sel, 1 },
1146 { C::public_data_squash_final_value, 42 },
1147 { C::public_data_squash_value, 42 },
1148 } });
1149
1150 check_relation<public_data_squash>(trace, public_data_squash::SR_FINAL_VALUE_CHECK);
1151
1152 // Negative test: if END, value == final_value
1153 trace.set(C::public_data_squash_value, 2, 99);
1154
1155 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_FINAL_VALUE_CHECK),
1157
1158 trace.set(C::public_data_squash_value, 2, 42);
1159
1160 // Negative test: if leaf_slot_increase, value == final_value
1161 trace.set(C::public_data_squash_value, 1, 99);
1162
1163 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_FINAL_VALUE_CHECK),
1165 trace.set(C::public_data_squash_value, 1, 27);
1166}
1167
1168TEST(PublicDataTreeConstrainingTest, SquashingNegativeLeafSlotIncrease)
1169{
1170 // Test constraint: leaf_slot_increase { leaf_slot', leaf_slot, sel } in ff_gt.sel_gt { ff_gt.a, ff_gt.b,
1171 // ff_gt.result }
1172 TestTraceContainer trace({ {
1173 { C::public_data_squash_leaf_slot_increase, 1 },
1174 { C::public_data_squash_leaf_slot, FF::modulus_minus_two },
1175 { C::public_data_squash_sel, 1 },
1176 },
1177 {
1178 { C::public_data_squash_leaf_slot_increase, 0 },
1179 { C::public_data_squash_leaf_slot, FF::modulus - 1 },
1180 { C::public_data_squash_sel, 1 },
1181 } });
1182
1183 // Corresponding ff_gt values. For this trace we keep the correct result.
1184 trace.set(0,
1185 { {
1186 { C::ff_gt_sel_gt, 1 },
1187 { C::ff_gt_a, FF::modulus - 1 },
1188 { C::ff_gt_b, FF::modulus_minus_two },
1189 { C::ff_gt_result, 1 },
1190 } });
1191
1192 trace.set(1,
1193 { {
1194 { C::ff_gt_sel_gt, 1 },
1195 { C::ff_gt_a, FF::modulus_minus_two },
1196 { C::ff_gt_b, FF::modulus_minus_two },
1197 { C::ff_gt_result, 0 },
1198 } });
1199
1200 trace.set(2,
1201 { {
1202 { C::ff_gt_sel_gt, 1 },
1203 { C::ff_gt_a, FF::modulus_minus_two },
1204 { C::ff_gt_b, FF::modulus - 3 },
1205 { C::ff_gt_result, 0 },
1206 } });
1207
1208 check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_squash_leaf_slot_increase_ff_gt_settings>(trace);
1209
1210 // Mutate the second row to be equal to the first row
1211 trace.set(C::public_data_squash_leaf_slot, 1, FF::modulus_minus_two);
1212
1214 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_squash_leaf_slot_increase_ff_gt_settings>(
1215 trace)),
1216 "Failed.*LOOKUP_PUBLIC_DATA_SQUASH_LEAF_SLOT_INCREASE_FF_GT. Could not find tuple in destination.");
1217
1218 // Mutate the second row to be smaller than the first row
1219 trace.set(C::public_data_squash_leaf_slot, 1, FF::modulus - 3);
1220
1222 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_squash_leaf_slot_increase_ff_gt_settings>(
1223 trace)),
1224 "Failed.*LOOKUP_PUBLIC_DATA_SQUASH_LEAF_SLOT_INCREASE_FF_GT. Could not find tuple in destination.");
1225}
1226
1227TEST(PublicDataTreeConstrainingTest, SquashingNegativeClockDecomposition)
1228{
1229 // Test constraint: CLK_DIFF = clk_diff_lo + 2**16 * clk_diff_hi;
1230 TestTraceContainer trace({
1231 {
1232 { C::public_data_squash_sel, 1 },
1233 { C::public_data_squash_check_clock, 1 },
1234 { C::public_data_squash_clk, 1 << 25 },
1235 { C::public_data_squash_clk_diff_lo, 37 },
1236 { C::public_data_squash_clk_diff_hi, 12 },
1237 },
1238 {
1239 { C::public_data_squash_sel, 1 },
1240 { C::public_data_squash_clk, (1 << 25) + (12 << 16) + 38 },
1241 },
1242 });
1243
1244 check_relation<public_data_squash>(trace, public_data_squash::SR_CLK_DIFF_DECOMP);
1245
1246 // Mutate wrongly clk_diff_lo
1247 trace.set(C::public_data_squash_clk_diff_lo, 0, trace.get(C::public_data_squash_clk_diff_lo, 0) + 1);
1248
1249 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CLK_DIFF_DECOMP),
1251
1252 // Reset
1253 trace.set(C::public_data_squash_clk_diff_lo, 0, trace.get(C::public_data_squash_clk_diff_lo, 0) - 1);
1254
1255 // Mutate wrongly clk_diff_hi
1256 trace.set(C::public_data_squash_clk_diff_hi, 0, trace.get(C::public_data_squash_clk_diff_hi, 0) + 1);
1257
1258 EXPECT_THROW_WITH_MESSAGE(check_relation<public_data_squash>(trace, public_data_squash::SR_CLK_DIFF_DECOMP),
1260}
1261
1262// Out of range clk diff
1263TEST(PublicDataTreeConstrainingTest, SquashingNegativeOutOfRangeClockDiff)
1264{
1265 TestTraceContainer trace({
1266 {
1267 { C::public_data_squash_sel, 1 },
1268 { C::public_data_squash_check_clock, 1 },
1269 { C::public_data_squash_clk, 1 << 25 },
1270 { C::public_data_squash_clk_diff_lo, UINT16_MAX },
1271 { C::public_data_squash_clk_diff_hi, UINT16_MAX },
1272 },
1273 });
1274
1275 PrecomputedTraceBuilder precomputed_trace_builder;
1276 precomputed_trace_builder.process_sel_range_16(trace);
1277 precomputed_trace_builder.process_misc(trace, 1 << 16);
1278
1279 check_interaction<PublicDataTreeTraceBuilder,
1282
1283 // Mutate wrongly clk_diff_lo
1284 trace.set(C::public_data_squash_clk_diff_lo, 0, UINT16_MAX + 1);
1285
1287 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_squash_clk_diff_range_lo_settings>(trace)),
1288 "Failed.*LOOKUP_PUBLIC_DATA_SQUASH_CLK_DIFF_RANGE_LO. Could not find tuple in destination.");
1289
1290 // Mutate wrongly clk_diff_hi
1291 trace.set(C::public_data_squash_clk_diff_hi, 0, UINT16_MAX + 1);
1292
1294 (check_interaction<PublicDataTreeTraceBuilder, lookup_public_data_squash_clk_diff_range_hi_settings>(trace)),
1295 "Failed.*LOOKUP_PUBLIC_DATA_SQUASH_CLK_DIFF_RANGE_HI. Could not find tuple in destination.");
1296}
1297
1298} // namespace
1299} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
Poseidon2TraceBuilder poseidon2_builder
FieldGreaterThan field_gt
MerkleCheck merkle_check
EventEmitter< simulation::RangeCheckEvent > range_check_emitter
RangeCheck range_check
INSTANTIATE_TEST_SUITE_P(All, KernelIOTamperingTests, testing::Values(KernelIOField::PAIRING_INPUTS, KernelIOField::ACCUMULATOR_HASH, KernelIOField::KERNEL_RETURN_DATA, KernelIOField::APP_RETURN_DATA, KernelIOField::ECC_OP_HASH), [](const testing::TestParamInfo< KernelIOField > &info) { switch(info.param) { case KernelIOField::PAIRING_INPUTS:return "PairingInputs";case KernelIOField::ACCUMULATOR_HASH:return "AccumulatorHash";case KernelIOField::KERNEL_RETURN_DATA:return "KernelReturnData";case KernelIOField::APP_RETURN_DATA:return "AppReturnData";case KernelIOField::ECC_OP_HASH:return "EccOpHash";} return "Unknown";})
TEST_P(KernelIOTamperingTests, CausesVerificationFailure)
static constexpr size_t SR_CLK_DIFF_DECOMP
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_LOW_LEAF_NEXT_INDEX_UPDATE
static constexpr size_t SR_VALUE_IS_CORRECT
static constexpr size_t SR_EXISTS_FLAG_CHECK
static constexpr size_t SR_NEXT_SLOT_IS_ZERO_CHECK
static constexpr size_t SR_TRACE_CONTINUITY
static constexpr size_t SR_LOW_LEAF_VALUE_UPDATE
static constexpr size_t SR_PROTOCOL_WRITE_CHECK
static constexpr size_t SR_WRITE_IDX_INITIAL_VALUE
static constexpr size_t SR_UPDATE_ROOT_VALIDATION
static constexpr size_t SR_WRITE_IDX_INCREMENT
static constexpr size_t SR_LOW_LEAF_NEXT_SLOT_UPDATE
static std::string get_subrelation_label(size_t index)
static constexpr size_t SR_CLK_DIFF_DECOMP
static constexpr size_t SR_CHECK_CLOCK_CONDITION
static constexpr size_t SR_TRACE_CONTINUITY
static constexpr size_t SR_FINAL_VALUE_CHECK
static constexpr size_t SR_CHECK_SAME_LEAF_SLOT
static constexpr size_t SR_FINAL_VALUE_PROPAGATION
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_hash(const simulation::EventEmitterInterface< simulation::Poseidon2HashEvent >::Container &hash_events, TraceContainer &trace)
Processes the hash events for the Poseidon2 hash function. It populates the columns for the poseidon2...
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_sel_range_16(TraceContainer &trace)
Generate a selector column that activates the first 2^16 (65536) rows.
void process(const simulation::EventEmitterInterface< simulation::RangeCheckEvent >::Container &events, TraceContainer &trace)
Processes range check events and populates the trace with decomposed value columns.
const FF & get(Column col, uint32_t row) const
void set(Column col, uint32_t row, const FF &value, bool use_atomic_limbs=false)
Native Poseidon2 hash function implementation.
Definition poseidon2.hpp:22
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
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
ExecutionIdManager execution_id_manager
TestTraceContainer trace
IndexedTreeLeafData low_leaf
TEST_F(AvmRecursiveTests, TwoLayerAvmRecursion)
A test of the Two Layer AVM recursive verifier.
void check_multipermutation_interaction(tracegen::TestTraceContainer &trace)
void check_interaction(tracegen::TestTraceContainer &trace)
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
std::variant< PublicDataTreeReadWriteEvent, CheckPointEventType > PublicDataTreeCheckEvent
IndexedLeaf< PublicDataLeafValue > PublicDataTreeLeafPreimage
::bb::crypto::merkle_tree::PublicDataLeafValue PublicDataLeafValue
Definition db.hpp:38
FF unconstrained_root_from_path(uint64_t domain_separator, const FF &leaf_value, const uint64_t leaf_index, std::span< const FF > path)
Definition merkle.cpp:12
FF unconstrained_compute_leaf_slot(const AztecAddress &contract_address, const FF &slot)
Definition merkle.cpp:30
lookup_settings< lookup_public_data_check_clk_diff_range_lo_settings_ > lookup_public_data_check_clk_diff_range_lo_settings
permutation_settings< perm_tx_balance_update_settings_ > perm_tx_balance_update_settings
Definition perms_tx.hpp:200
lookup_settings< lookup_public_data_squash_clk_diff_range_lo_settings_ > lookup_public_data_squash_clk_diff_range_lo_settings
AvmFlavorSettings::FF FF
Definition field.hpp:10
permutation_settings< perm_sstore_storage_write_settings_ > perm_sstore_storage_write_settings
lookup_settings< lookup_public_data_squash_clk_diff_range_hi_settings_ > lookup_public_data_squash_clk_diff_range_hi_settings
lookup_settings< lookup_public_data_check_clk_diff_range_hi_settings_ > lookup_public_data_check_clk_diff_range_hi_settings
void write(B &buf, field2< base_field, Params > const &value)
::testing::Types< TestParam< curve::BN254, 9 >, TestParam< curve::BN254, CHONK_MAX_NUM_CIRCUITS >, TestParam< stdlib::bn254< MegaCircuitBuilder >, 9 >, TestParam< stdlib::bn254< MegaCircuitBuilder >, CHONK_MAX_NUM_CIRCUITS > > TestParams
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
NoopEventEmitter< FieldGreaterThanEvent > field_gt_event_emitter
static constexpr uint256_t modulus
static constexpr uint256_t modulus_minus_two
tracegen::PublicInputsTraceBuilder public_inputs_builder
Definition tx.test.cpp:84