Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
indexed_tree_check.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
31using ::testing::NiceMock;
32using ::testing::TestWithParam;
33
34using testing::TestMemoryTree;
35
36using simulation::EventEmitter;
37using simulation::FieldGreaterThan;
38using simulation::FieldGreaterThanEvent;
39using simulation::IndexedTreeCheck;
41using simulation::IndexedTreeLeafData;
42using simulation::IndexedTreeSiloingParameters;
43using simulation::MerkleCheck;
44using simulation::MerkleCheckEvent;
45using simulation::MockExecutionIdManager;
46using simulation::MockGreaterThan;
47using simulation::MockRangeCheck;
48using simulation::NoopEventEmitter;
49using simulation::Poseidon2;
50using simulation::Poseidon2HashEvent;
51using simulation::Poseidon2PermutationEvent;
52using simulation::Poseidon2PermutationMemoryEvent;
54
55using tracegen::IndexedTreeCheckTraceBuilder;
56using tracegen::TestTraceContainer;
57
59using C = Column;
60using IndexedTreeCheckRelation = bb::avm2::indexed_tree_check<FF>;
62
63TEST(IndexedTreeCheckConstrainingTest, EmptyRow)
64{
65 check_relation<IndexedTreeCheckRelation>(testing::empty_trace());
66}
67
68struct TestParams {
70 bool exists;
71 IndexedTreeLeafData low_leaf;
72};
73
74std::vector<TestParams> positive_read_tests = {
75 // Exists = true, leaf pointers to infinity
76 TestParams{ .value = 42, .exists = true, .low_leaf = { .value = 42, .next_value = 0, .next_index = 0 } },
77 // Exists = true, leaf points to higher value
78 TestParams{ .value = 42, .exists = true, .low_leaf = { .value = 42, .next_value = 50, .next_index = 28 } },
79 // Exists = false, low leaf points to infinity
80 TestParams{ .value = 42, .exists = false, .low_leaf = { .value = 10, .next_value = 0, .next_index = 0 } },
81 // Exists = false, low leaf points to higher value
82 TestParams{ .value = 42, .exists = false, .low_leaf = { .value = 10, .next_value = 50, .next_index = 28 } }
83};
84
85class IndexedTreeReadPositiveTests : public TestWithParam<TestParams> {};
86
87TEST_P(IndexedTreeReadPositiveTests, Positive)
88{
89 const auto& param = GetParam();
90
91 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
92 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
93 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
94
95 NiceMock<MockGreaterThan> mock_gt;
96 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
98 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
99 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
100
101 NiceMock<MockRangeCheck> range_check;
102
103 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
104 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
105
106 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
107 IndexedTreeCheck indexed_tree_check_simulator(
109
110 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
111 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
112
113 FF low_leaf_hash = poseidon2.hash(param.low_leaf.get_hash_inputs());
114 uint64_t leaf_index = 30;
115 std::vector<FF> sibling_path;
116 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
117 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
118 sibling_path.emplace_back(i);
119 }
120 FF root = unconstrained_root_from_path(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, leaf_index, sibling_path);
121
122 indexed_tree_check_simulator.assert_read(param.value,
123 /*siloing_params*/ std::nullopt,
124 param.exists,
125 param.low_leaf,
126 leaf_index,
127 sibling_path,
128 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 });
129
131 EXPECT_EQ(trace.get_num_rows(), 1);
132
133 check_relation<IndexedTreeCheckRelation>(trace);
134}
135
136INSTANTIATE_TEST_SUITE_P(IndexedTreeCheckConstrainingTest,
137 IndexedTreeReadPositiveTests,
138 ::testing::ValuesIn(positive_read_tests));
139
140TEST(IndexedTreeCheckConstrainingTest, PositiveWriteAppend)
141{
142 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
143 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
144 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
145
146 NiceMock<MockGreaterThan> mock_gt;
147 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
149 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
150 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
151
152 NiceMock<MockRangeCheck> range_check;
153
154 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
155 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
156
157 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
158 IndexedTreeCheck indexed_tree_check_simulator(
160
161 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
162 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
163
164 FF value = 100;
165 FF low_value = 40;
166 TestMemoryTree<aztec::NullifierMerkleHashPolicy> tree(8, NULLIFIER_TREE_HEIGHT);
167
168 IndexedTreeLeafData low_leaf = { .value = low_value, .next_value = value + 1, .next_index = 10 };
170 uint64_t low_leaf_index = 0;
171 tree.update_element(low_leaf_index, low_leaf_hash);
172
173 AppendOnlyTreeSnapshot prev_snapshot =
174 AppendOnlyTreeSnapshot{ .root = tree.root(), .next_available_leaf_index = 128 };
175 std::vector<FF> low_leaf_sibling_path = tree.get_sibling_path(low_leaf_index);
176
177 IndexedTreeLeafData updated_low_leaf = low_leaf;
178 updated_low_leaf.next_index = prev_snapshot.next_available_leaf_index;
179 updated_low_leaf.next_value = value;
180 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
181 tree.update_element(low_leaf_index, updated_low_leaf_hash);
182
183 std::vector<FF> insertion_sibling_path = tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
184
185 IndexedTreeLeafData new_leaf = { .value = value,
186 .next_value = low_leaf.next_value,
187 .next_index = low_leaf.next_index };
188 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
189 tree.update_element(prev_snapshot.next_available_leaf_index, new_leaf_hash);
190
191 indexed_tree_check_simulator.write(value,
192 /*siloing_params*/ std::nullopt,
193 0,
194 low_leaf,
195 low_leaf_index,
196 low_leaf_sibling_path,
197 prev_snapshot,
198 insertion_sibling_path);
199
201
202 EXPECT_EQ(trace.get_num_rows(), 1);
203
204 check_relation<IndexedTreeCheckRelation>(trace);
205}
206
207TEST(IndexedTreeCheckConstrainingTest, PositiveWriteMembership)
208{
209 FF value = 42;
210 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
211
212 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
213 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
214 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
215
216 NiceMock<MockGreaterThan> mock_gt;
217 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
219 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
220 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
221
222 NiceMock<MockRangeCheck> range_check;
223
224 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
225 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
226
227 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
228 IndexedTreeCheck indexed_tree_check_simulator(
230
231 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
232 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
233
234 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
235 uint64_t leaf_index = 30;
236 std::vector<FF> sibling_path;
237 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
238 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
239 sibling_path.emplace_back(i);
240 }
241 FF root = unconstrained_root_from_path(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, leaf_index, sibling_path);
242
243 indexed_tree_check_simulator.write(value,
245 10,
246 low_leaf,
247 leaf_index,
248 sibling_path,
249 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
250 /* insertion_sibling_path */ std::nullopt);
251
253 EXPECT_EQ(trace.get_num_rows(), 1);
254
255 check_relation<IndexedTreeCheckRelation>(trace);
256}
257
258TEST(IndexedTreeCheckConstrainingTest, Siloing)
259{
260 AztecAddress contract_address = 1;
261 FF value = 42;
262 FF siloed_value = RawPoseidon2::hash({ DOM_SEP__SILOED_NULLIFIER, FF(contract_address), value });
263 IndexedTreeLeafData low_leaf = { .value = siloed_value, .next_value = 0, .next_index = 0 };
264
265 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
266 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
267 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
268
269 NiceMock<MockGreaterThan> mock_gt;
270 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
272 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
273 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
274
275 NiceMock<MockRangeCheck> range_check;
276
277 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
278 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
279
280 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
281 IndexedTreeCheck indexed_tree_check_simulator(
283
284 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
285 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
286
287 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
288 uint64_t leaf_index = 30;
289 std::vector<FF> sibling_path;
290 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
291 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
292 sibling_path.emplace_back(i);
293 }
294 FF root = unconstrained_root_from_path(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, leaf_index, sibling_path);
295
296 indexed_tree_check_simulator.write(
297 value,
298 IndexedTreeSiloingParameters{ .address = contract_address, .siloing_separator = DOM_SEP__SILOED_NULLIFIER },
299 10,
300 low_leaf,
301 leaf_index,
302 sibling_path,
303 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
304 /* insertion_sibling_path */ std::nullopt);
305
307 EXPECT_EQ(trace.get_num_rows(), 1);
308
309 check_relation<IndexedTreeCheckRelation>(trace);
310}
311
312TEST(IndexedTreeCheckConstrainingTest, NegativeExistsFlagCheck)
313{
314 // Test constraint: sel * (VALUE_LOW_LEAF_VALUE_DIFF * (exists * (1 - value_low_leaf_value_diff_inv)
315 // + value_low_leaf_value_diff_inv) - 1 + exists) = 0
316 TestTraceContainer trace({
317 { { C::indexed_tree_check_sel, 1 },
318 { C::indexed_tree_check_siloed_value, 27 },
319 { C::indexed_tree_check_low_leaf_value, 27 },
320 { C::indexed_tree_check_value_low_leaf_value_diff_inv, 0 },
321 { C::indexed_tree_check_exists, 1 } },
322 { { C::indexed_tree_check_sel, 1 },
323 { C::indexed_tree_check_siloed_value, 28 },
324 { C::indexed_tree_check_low_leaf_value, 27 },
325 { C::indexed_tree_check_value_low_leaf_value_diff_inv, FF(1).invert() },
326 { C::indexed_tree_check_exists, 0 } },
327 });
328
329 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK);
330 trace.set(C::indexed_tree_check_exists, 0, 0);
331
333 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK), "EXISTS_CHECK");
334 trace.set(C::indexed_tree_check_exists, 0, 1);
335 trace.set(C::indexed_tree_check_exists, 1, 1);
336
338 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK), "EXISTS_CHECK");
339}
340
341TEST(IndexedTreeCheckConstrainingTest, NegativeNextValueIsZero)
342{
343 // Test constraint: not_exists * (low_leaf_next_value * (NEXT_VALUE_IS_ZERO * (1 - next_value_inv)
344 // + next_value_inv) - 1 + NEXT_VALUE_IS_ZERO) = 0
345 TestTraceContainer trace({
346 {
347 { C::indexed_tree_check_not_exists, 1 },
348 { C::indexed_tree_check_low_leaf_next_value, 0 },
349 { C::indexed_tree_check_next_value_inv, 0 },
350 { C::indexed_tree_check_next_value_is_nonzero, 0 },
351 },
352 {
353 { C::indexed_tree_check_not_exists, 1 },
354 { C::indexed_tree_check_low_leaf_next_value, 1 },
355 { C::indexed_tree_check_next_value_inv, FF(1).invert() },
356 { C::indexed_tree_check_next_value_is_nonzero, 1 },
357 },
358 });
359
360 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK);
361
362 trace.set(C::indexed_tree_check_next_value_is_nonzero, 0, 1);
363
365 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK),
366 "NEXT_VALUE_IS_ZERO_CHECK");
367
368 trace.set(C::indexed_tree_check_next_value_is_nonzero, 0, 0);
369 trace.set(C::indexed_tree_check_next_value_is_nonzero, 1, 0);
370
372 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK),
373 "NEXT_VALUE_IS_ZERO_CHECK");
374}
375
376TEST(IndexedTreeCheckConstrainingTest, NegativePassthroughSiloing)
377{
378 // Test constraint: (1 - sel_silo) * (value - siloed_value) = 0
379 TestTraceContainer trace({
380 {
381 { C::indexed_tree_check_sel, 1 },
382 { C::indexed_tree_check_sel_silo, 1 },
383 { C::indexed_tree_check_value, 27 },
384 { C::indexed_tree_check_siloed_value, 42 },
385 },
386 {
387 { C::indexed_tree_check_sel, 1 },
388 { C::indexed_tree_check_sel_silo, 0 },
389 { C::indexed_tree_check_value, 27 },
390 { C::indexed_tree_check_siloed_value, 27 },
391 },
392 });
393
394 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_PASSTHROUGH_SILOING);
395
396 trace.set(C::indexed_tree_check_siloed_value, 1, 28);
397
399 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_PASSTHROUGH_SILOING),
400 "PASSTHROUGH_SILOING");
401}
402
403} // namespace
404} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define DOM_SEP__SILOED_NULLIFIER
#define NULLIFIER_TREE_HEIGHT
#define DOM_SEP__NULLIFIER_MERKLE
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
FieldGreaterThan field_gt
IndexedTreeCheckTraceBuilder indexed_tree_check_builder
MerkleCheck merkle_check
EventEmitter< simulation::IndexedTreeCheckEvent > indexed_tree_check_event_emitter
RangeCheck range_check
void process(const simulation::EventEmitterInterface< simulation::IndexedTreeCheckEvent >::Container &events, TraceContainer &trace)
Process generic indexed tree check events and populate the relevant columns in the trace.
void set(Column col, uint32_t row, const FF &value)
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
TestTraceContainer trace
IndexedTreeLeafData low_leaf
TEST_P(AvmRecursiveTestsParameterized, TwoLayerAvmRecursion)
A test of the Two Layer AVM recursive verifier.
INSTANTIATE_TEST_SUITE_P(PaddingVariants, AvmRecursiveTestsParameterized, ::testing::Values(false, true), [](const auto &info) { return info.param ? "Padded" :"Unpadded";})
TEST(AvmFixedVKTests, FixedVKCommitments)
Test that the fixed VK commitments agree with the ones computed from precomputed columns.
crypto::Poseidon2< crypto::Poseidon2Bn254ScalarFieldParams > poseidon2
std::variant< IndexedTreeReadWriteEvent, CheckPointEventType > IndexedTreeCheckEvent
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
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
AvmFlavorSettings::FF FF
Definition field.hpp:10
::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