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
25
26namespace bb::avm2::constraining {
27namespace {
28
29using ::testing::NiceMock;
30using ::testing::TestWithParam;
31
32using testing::TestMemoryTree;
33
34using simulation::EventEmitter;
35using simulation::FieldGreaterThan;
36using simulation::FieldGreaterThanEvent;
37using simulation::IndexedTreeCheck;
39using simulation::IndexedTreeLeafData;
40using simulation::IndexedTreeSiloingParameters;
41using simulation::MerkleCheck;
42using simulation::MerkleCheckEvent;
43using simulation::MockExecutionIdManager;
44using simulation::MockGreaterThan;
45using simulation::MockRangeCheck;
46using simulation::NoopEventEmitter;
47using simulation::Poseidon2;
48using simulation::Poseidon2HashEvent;
49using simulation::Poseidon2PermutationEvent;
50using simulation::Poseidon2PermutationMemoryEvent;
52
53using tracegen::IndexedTreeCheckTraceBuilder;
54using tracegen::TestTraceContainer;
55
57using C = Column;
58using IndexedTreeCheckRelation = bb::avm2::indexed_tree_check<FF>;
60
61TEST(IndexedTreeCheckConstrainingTest, EmptyRow)
62{
63 check_relation<IndexedTreeCheckRelation>(testing::empty_trace());
64}
65
66struct TestParams {
68 bool exists;
69 IndexedTreeLeafData low_leaf;
70};
71
72std::vector<TestParams> positive_read_tests = {
73 // Exists = true, leaf pointers to infinity
74 TestParams{ .value = 42, .exists = true, .low_leaf = { .value = 42, .next_value = 0, .next_index = 0 } },
75 // Exists = true, leaf points to higher value
76 TestParams{ .value = 42, .exists = true, .low_leaf = { .value = 42, .next_value = 50, .next_index = 28 } },
77 // Exists = false, low leaf points to infinity
78 TestParams{ .value = 42, .exists = false, .low_leaf = { .value = 10, .next_value = 0, .next_index = 0 } },
79 // Exists = false, low leaf points to higher value
80 TestParams{ .value = 42, .exists = false, .low_leaf = { .value = 10, .next_value = 50, .next_index = 28 } }
81};
82
83class IndexedTreeReadPositiveTests : public TestWithParam<TestParams> {};
84
85TEST_P(IndexedTreeReadPositiveTests, Positive)
86{
87 const auto& param = GetParam();
88
89 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
90 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
91 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
92
93 NiceMock<MockGreaterThan> mock_gt;
94 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
96 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
97 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
98
99 NiceMock<MockRangeCheck> range_check;
100
101 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
102 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
103
104 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
105 IndexedTreeCheck indexed_tree_check_simulator(poseidon2, merkle_check, field_gt, indexed_tree_check_event_emitter);
106
107 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
108 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
109
110 FF low_leaf_hash = poseidon2.hash(param.low_leaf.get_hash_inputs());
111 uint64_t leaf_index = 30;
112 std::vector<FF> sibling_path;
113 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
114 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
115 sibling_path.emplace_back(i);
116 }
117 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
118
119 indexed_tree_check_simulator.assert_read(param.value,
120 /*siloing_params*/ std::nullopt,
121 param.exists,
122 param.low_leaf,
123 leaf_index,
124 sibling_path,
125 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 });
126
128 EXPECT_EQ(trace.get_num_rows(), 1);
129
130 check_relation<IndexedTreeCheckRelation>(trace);
131}
132
133INSTANTIATE_TEST_SUITE_P(IndexedTreeCheckConstrainingTest,
134 IndexedTreeReadPositiveTests,
135 ::testing::ValuesIn(positive_read_tests));
136
137TEST(IndexedTreeCheckConstrainingTest, PositiveWriteAppend)
138{
139 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
140 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
141 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
142
143 NiceMock<MockGreaterThan> mock_gt;
144 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
146 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
147 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
148
149 NiceMock<MockRangeCheck> range_check;
150
151 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
152 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
153
154 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
155 IndexedTreeCheck indexed_tree_check_simulator(poseidon2, merkle_check, field_gt, indexed_tree_check_event_emitter);
156
157 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
158 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
159
160 FF value = 100;
161 FF low_value = 40;
162 TestMemoryTree<Poseidon2HashPolicy> tree(8, NULLIFIER_TREE_HEIGHT);
163
164 IndexedTreeLeafData low_leaf = { .value = low_value, .next_value = value + 1, .next_index = 10 };
166 uint64_t low_leaf_index = 0;
167 tree.update_element(low_leaf_index, low_leaf_hash);
168
169 AppendOnlyTreeSnapshot prev_snapshot =
170 AppendOnlyTreeSnapshot{ .root = tree.root(), .next_available_leaf_index = 128 };
171 std::vector<FF> low_leaf_sibling_path = tree.get_sibling_path(low_leaf_index);
172
173 IndexedTreeLeafData updated_low_leaf = low_leaf;
174 updated_low_leaf.next_index = prev_snapshot.next_available_leaf_index;
175 updated_low_leaf.next_value = value;
176 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
177 tree.update_element(low_leaf_index, updated_low_leaf_hash);
178
179 std::vector<FF> insertion_sibling_path = tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
180
181 IndexedTreeLeafData new_leaf = { .value = value,
182 .next_value = low_leaf.next_value,
183 .next_index = low_leaf.next_index };
184 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
185 tree.update_element(prev_snapshot.next_available_leaf_index, new_leaf_hash);
186
187 indexed_tree_check_simulator.write(value,
188 /*siloing_params*/ std::nullopt,
189 0,
190 low_leaf,
191 low_leaf_index,
192 low_leaf_sibling_path,
193 prev_snapshot,
194 insertion_sibling_path);
195
197
198 EXPECT_EQ(trace.get_num_rows(), 1);
199
200 check_relation<IndexedTreeCheckRelation>(trace);
201}
202
203TEST(IndexedTreeCheckConstrainingTest, PositiveWriteMembership)
204{
205 FF value = 42;
206 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
207
208 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
209 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
210 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
211
212 NiceMock<MockGreaterThan> mock_gt;
213 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
215 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
216 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
217
218 NiceMock<MockRangeCheck> range_check;
219
220 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
221 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
222
223 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
224 IndexedTreeCheck indexed_tree_check_simulator(poseidon2, merkle_check, field_gt, indexed_tree_check_event_emitter);
225
226 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
227 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
228
229 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
230 uint64_t leaf_index = 30;
231 std::vector<FF> sibling_path;
232 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
233 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
234 sibling_path.emplace_back(i);
235 }
236 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
237
238 indexed_tree_check_simulator.write(value,
240 10,
241 low_leaf,
242 leaf_index,
243 sibling_path,
244 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
245 /* insertion_sibling_path */ std::nullopt);
246
248 EXPECT_EQ(trace.get_num_rows(), 1);
249
250 check_relation<IndexedTreeCheckRelation>(trace);
251}
252
253TEST(IndexedTreeCheckConstrainingTest, Siloing)
254{
255 AztecAddress contract_address = 1;
256 FF value = 42;
257 FF siloed_value = RawPoseidon2::hash({ DOM_SEP__SILOED_NULLIFIER, FF(contract_address), value });
258 IndexedTreeLeafData low_leaf = { .value = siloed_value, .next_value = 0, .next_index = 0 };
259
260 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
261 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
262 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
263
264 NiceMock<MockGreaterThan> mock_gt;
265 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
267 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
268 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
269
270 NiceMock<MockRangeCheck> range_check;
271
272 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
273 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
274
275 EventEmitter<IndexedTreeCheckEvent> indexed_tree_check_event_emitter;
276 IndexedTreeCheck indexed_tree_check_simulator(poseidon2, merkle_check, field_gt, indexed_tree_check_event_emitter);
277
278 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
279 IndexedTreeCheckTraceBuilder indexed_tree_check_builder;
280
281 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
282 uint64_t leaf_index = 30;
283 std::vector<FF> sibling_path;
284 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
285 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
286 sibling_path.emplace_back(i);
287 }
288 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
289
290 indexed_tree_check_simulator.write(
291 value,
292 IndexedTreeSiloingParameters{ .address = contract_address, .siloing_separator = DOM_SEP__SILOED_NULLIFIER },
293 10,
294 low_leaf,
295 leaf_index,
296 sibling_path,
297 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
298 /* insertion_sibling_path */ std::nullopt);
299
301 EXPECT_EQ(trace.get_num_rows(), 1);
302
303 check_relation<IndexedTreeCheckRelation>(trace);
304}
305
306TEST(IndexedTreeCheckConstrainingTest, NegativeExistsFlagCheck)
307{
308 // Test constraint: sel * (VALUE_LOW_LEAF_VALUE_DIFF * (exists * (1 - value_low_leaf_value_diff_inv)
309 // + value_low_leaf_value_diff_inv) - 1 + exists) = 0
310 TestTraceContainer trace({
311 { { C::indexed_tree_check_sel, 1 },
312 { C::indexed_tree_check_siloed_value, 27 },
313 { C::indexed_tree_check_low_leaf_value, 27 },
314 { C::indexed_tree_check_value_low_leaf_value_diff_inv, 0 },
315 { C::indexed_tree_check_exists, 1 } },
316 { { C::indexed_tree_check_sel, 1 },
317 { C::indexed_tree_check_siloed_value, 28 },
318 { C::indexed_tree_check_low_leaf_value, 27 },
319 { C::indexed_tree_check_value_low_leaf_value_diff_inv, FF(1).invert() },
320 { C::indexed_tree_check_exists, 0 } },
321 });
322
323 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK);
324 trace.set(C::indexed_tree_check_exists, 0, 0);
325
327 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK), "EXISTS_CHECK");
328 trace.set(C::indexed_tree_check_exists, 0, 1);
329 trace.set(C::indexed_tree_check_exists, 1, 1);
330
332 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_EXISTS_CHECK), "EXISTS_CHECK");
333}
334
335TEST(IndexedTreeCheckConstrainingTest, NegativeNextValueIsZero)
336{
337 // Test constraint: not_exists * (low_leaf_next_value * (NEXT_VALUE_IS_ZERO * (1 - next_value_inv)
338 // + next_value_inv) - 1 + NEXT_VALUE_IS_ZERO) = 0
339 TestTraceContainer trace({
340 {
341 { C::indexed_tree_check_not_exists, 1 },
342 { C::indexed_tree_check_low_leaf_next_value, 0 },
343 { C::indexed_tree_check_next_value_inv, 0 },
344 { C::indexed_tree_check_next_value_is_nonzero, 0 },
345 },
346 {
347 { C::indexed_tree_check_not_exists, 1 },
348 { C::indexed_tree_check_low_leaf_next_value, 1 },
349 { C::indexed_tree_check_next_value_inv, FF(1).invert() },
350 { C::indexed_tree_check_next_value_is_nonzero, 1 },
351 },
352 });
353
354 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK);
355
356 trace.set(C::indexed_tree_check_next_value_is_nonzero, 0, 1);
357
359 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK),
360 "NEXT_VALUE_IS_ZERO_CHECK");
361
362 trace.set(C::indexed_tree_check_next_value_is_nonzero, 0, 0);
363 trace.set(C::indexed_tree_check_next_value_is_nonzero, 1, 0);
364
366 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_NEXT_VALUE_IS_ZERO_CHECK),
367 "NEXT_VALUE_IS_ZERO_CHECK");
368}
369
370TEST(IndexedTreeCheckConstrainingTest, NegativePassthroughSiloing)
371{
372 // Test constraint: (1 - sel_silo) * (value - siloed_value) = 0
373 TestTraceContainer trace({
374 {
375 { C::indexed_tree_check_sel, 1 },
376 { C::indexed_tree_check_sel_silo, 1 },
377 { C::indexed_tree_check_value, 27 },
378 { C::indexed_tree_check_siloed_value, 42 },
379 },
380 {
381 { C::indexed_tree_check_sel, 1 },
382 { C::indexed_tree_check_sel_silo, 0 },
383 { C::indexed_tree_check_value, 27 },
384 { C::indexed_tree_check_siloed_value, 27 },
385 },
386 });
387
388 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_PASSTHROUGH_SILOING);
389
390 trace.set(C::indexed_tree_check_siloed_value, 1, 28);
391
393 check_relation<IndexedTreeCheckRelation>(trace, IndexedTreeCheckRelation::SR_PASSTHROUGH_SILOING),
394 "PASSTHROUGH_SILOING");
395}
396
397} // namespace
398} // namespace bb::avm2::constraining
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define DOM_SEP__SILOED_NULLIFIER
#define NULLIFIER_TREE_HEIGHT
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
FF unconstrained_root_from_path(const FF &leaf_value, const uint64_t leaf_index, std::span< const FF > path)
Definition merkle.cpp:12
std::variant< IndexedTreeReadWriteEvent, CheckPointEventType > IndexedTreeCheckEvent
TestTraceContainer empty_trace()
Definition fixtures.cpp:153
AvmFlavorSettings::FF FF
Definition field.hpp:10
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
NoopEventEmitter< FieldGreaterThanEvent > field_gt_event_emitter