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.
2
3#include <gmock/gmock.h>
4#include <gtest/gtest.h>
5
16
17namespace bb::avm2::simulation {
18
19using ::testing::_;
20using ::testing::ElementsAre;
21using ::testing::Return;
22using ::testing::StrictMock;
23
25
26namespace {
27
28TEST(AvmSimulationIndexedTreeCheck, ReadExists)
29{
30 StrictMock<MockPoseidon2> poseidon2;
31 StrictMock<MockMerkleCheck> merkle_check;
32 StrictMock<MockFieldGreaterThan> field_gt;
33
34 EventEmitter<IndexedTreeCheckEvent> event_emitter;
36
37 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
39 uint64_t low_leaf_index = 30;
40 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
41 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
42
43 FF value = 42;
44
45 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
46 EXPECT_CALL(merkle_check,
47 assert_membership(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, low_leaf_index, _, snapshot.root))
48 .WillRepeatedly(Return());
49
51 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot);
52
53 IndexedTreeReadWriteEvent expect_event = {
54 .value = value,
55 .prev_snapshot = snapshot,
56 .next_snapshot = snapshot,
57 .tree_height = sibling_path.size(),
58 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
59 .low_leaf_data = low_leaf,
60 .low_leaf_hash = low_leaf_hash,
61 .low_leaf_index = low_leaf_index,
62 };
63 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
64
65 // Negative test: value does not exist
68 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot),
69 "non-membership check failed");
70}
71
72TEST(AvmSimulationIndexedTreeCheck, ReadNotExistsLowPointsToInfinity)
73{
74 StrictMock<MockPoseidon2> poseidon2;
75 StrictMock<MockMerkleCheck> merkle_check;
76 StrictMock<MockFieldGreaterThan> field_gt;
77
78 EventEmitter<IndexedTreeCheckEvent> event_emitter;
80
81 IndexedTreeLeafData low_leaf = { .value = 40, .next_value = 0, .next_index = 0 };
83 uint64_t low_leaf_index = 30;
84 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
85 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
86 FF value = 42;
87
88 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
89 EXPECT_CALL(merkle_check,
90 assert_membership(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, low_leaf_index, _, snapshot.root))
91 .WillRepeatedly(Return());
92 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
93
95 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot);
96 IndexedTreeReadWriteEvent expect_event = {
97 .value = value,
98 .prev_snapshot = snapshot,
99 .next_snapshot = snapshot,
100 .tree_height = sibling_path.size(),
101 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
102 .low_leaf_data = low_leaf,
103 .low_leaf_hash = low_leaf_hash,
104 .low_leaf_index = low_leaf_index,
105 };
106 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
107
108 // Negative test: value exists
111 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
112 "membership check failed");
113
114 // Negative test: value not greater than low leaf value
115 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(false));
118 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
119 "Low leaf value is GTE leaf value");
120}
121
122TEST(AvmSimulationIndexedTreeCheck, ReadNotExistsLowPointsToAnotherLeaf)
123{
124 StrictMock<MockPoseidon2> poseidon2;
125 StrictMock<MockMerkleCheck> merkle_check;
126 StrictMock<MockFieldGreaterThan> field_gt;
127
128 EventEmitter<IndexedTreeCheckEvent> event_emitter;
130
131 IndexedTreeLeafData low_leaf = { .value = 40, .next_value = 50, .next_index = 28 };
133 uint64_t low_leaf_index = 30;
134 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
135 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
136 FF value = 42;
137
138 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
139 EXPECT_CALL(merkle_check,
140 assert_membership(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, low_leaf_index, _, snapshot.root))
141 .WillRepeatedly(Return());
142 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
143 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillRepeatedly(Return(true));
144
146 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot);
147 IndexedTreeReadWriteEvent expect_event = {
148 .value = value,
149 .prev_snapshot = snapshot,
150 .next_snapshot = snapshot,
151 .tree_height = sibling_path.size(),
152 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
153 .low_leaf_data = low_leaf,
154 .low_leaf_hash = low_leaf_hash,
155 .low_leaf_index = low_leaf_index,
156 };
157 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
158
159 // Negative test: value exists
162 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
163 "membership check failed");
164
165 // Negative test: next value not greater than value
166 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillOnce(Return(false));
169 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
170 "Leaf value is GTE low leaf next value");
171}
172
173TEST(AvmSimulationIndexedTreeCheck, WriteExists)
174{
175 StrictMock<MockPoseidon2> poseidon2;
176 StrictMock<MockMerkleCheck> merkle_check;
177 StrictMock<MockFieldGreaterThan> field_gt;
178
179 EventEmitter<IndexedTreeCheckEvent> event_emitter;
181
182 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
184 uint64_t low_leaf_index = 30;
185 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
186 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
187
188 FF value = 42;
189
190 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
191 EXPECT_CALL(merkle_check,
192 assert_membership(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, low_leaf_index, _, snapshot.root))
193 .WillRepeatedly(Return());
194
195 AppendOnlyTreeSnapshot result_snapshot = indexed_tree_check.write(value,
196 /*siloing_params*/ std::nullopt,
197 10,
198 low_leaf,
199 low_leaf_index,
200 sibling_path,
201 snapshot,
202 /*insertion_path*/ std::nullopt);
203
204 EXPECT_EQ(result_snapshot, snapshot);
205
206 IndexedTreeReadWriteEvent expect_event = {
207 .value = value,
208 .prev_snapshot = snapshot,
209 .next_snapshot = snapshot,
210 .tree_height = sibling_path.size(),
211 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
212 .low_leaf_data = low_leaf,
213 .low_leaf_hash = low_leaf_hash,
214 .low_leaf_index = low_leaf_index,
215 .write = true,
216 .public_inputs_index = 10,
217 };
218 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
219}
220
221TEST(AvmSimulationIndexedTreeCheck, Siloing)
222{
223 StrictMock<MockPoseidon2> poseidon2;
224 StrictMock<MockMerkleCheck> merkle_check;
225 StrictMock<MockFieldGreaterThan> field_gt;
226
227 EventEmitter<IndexedTreeCheckEvent> event_emitter;
229
230 FF value = 42;
231 FF separator = 99;
233 IndexedTreeSiloingParameters siloing_params = { .address = address, .siloing_separator = separator };
234 std::vector<FF> siloed_hash_inputs = { separator, address, value };
235 FF siloed_value = RawPoseidon2::hash(siloed_hash_inputs);
236
237 IndexedTreeLeafData low_leaf = { .value = siloed_value, .next_value = 0, .next_index = 0 };
239 uint64_t low_leaf_index = 30;
240 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
241 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
242
243 EXPECT_CALL(poseidon2, hash(siloed_hash_inputs)).WillRepeatedly(Return(siloed_value));
244 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
245 EXPECT_CALL(merkle_check,
246 assert_membership(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, low_leaf_index, _, snapshot.root))
247 .WillRepeatedly(Return());
248
249 indexed_tree_check.assert_read(value, siloing_params, true, low_leaf, low_leaf_index, sibling_path, snapshot);
250
251 IndexedLeafSiloingData expected_siloing_data = { .siloed_value = siloed_value, .parameters = siloing_params };
252 IndexedTreeReadWriteEvent read_event = {
253 .value = value,
254 .prev_snapshot = snapshot,
255 .next_snapshot = snapshot,
256 .tree_height = sibling_path.size(),
257 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
258 .low_leaf_data = low_leaf,
259 .low_leaf_hash = low_leaf_hash,
260 .low_leaf_index = low_leaf_index,
261 .siloing_data = expected_siloing_data,
262 };
264 siloing_params,
265 10,
266 low_leaf,
267 low_leaf_index,
268 sibling_path,
269 snapshot,
270 /*insertion_path*/ std::nullopt);
271
272 IndexedTreeReadWriteEvent write_event = {
273 .value = value,
274 .prev_snapshot = snapshot,
275 .next_snapshot = snapshot,
276 .tree_height = sibling_path.size(),
277 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
278 .low_leaf_data = low_leaf,
279 .low_leaf_hash = low_leaf_hash,
280 .low_leaf_index = low_leaf_index,
281 .write = true,
282 .siloing_data = expected_siloing_data,
283 .public_inputs_index = 10,
284 };
285 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(read_event, write_event));
286}
287
288TEST(AvmSimulationIndexedTreeCheck, WriteAppend)
289{
290 StrictMock<MockPoseidon2> poseidon2;
291 StrictMock<MockMerkleCheck> merkle_check;
292 StrictMock<MockFieldGreaterThan> field_gt;
293
294 EventEmitter<IndexedTreeCheckEvent> event_emitter;
296
297 FF value = 100;
298 FF low_value = 40;
299
301
302 IndexedTreeLeafData low_leaf = { .value = low_value, .next_value = value + 1, .next_index = 10 };
304 uint64_t low_leaf_index = 0;
305 tree.update_element(low_leaf_index, low_leaf_hash);
306
307 AppendOnlyTreeSnapshot prev_snapshot = { .root = tree.root(), .next_available_leaf_index = 128 };
308 std::vector<FF> low_leaf_sibling_path = tree.get_sibling_path(low_leaf_index);
309
310 IndexedTreeLeafData updated_low_leaf = {
311 .value = low_leaf.value,
312 .next_value = value,
313 .next_index = prev_snapshot.next_available_leaf_index,
314 };
315 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
316 tree.update_element(low_leaf_index, updated_low_leaf_hash);
317
318 FF intermediate_root = tree.root();
319 std::vector<FF> insertion_sibling_path = tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
320
321 // The new leaf gets the old low leaf's next pointer.
322 IndexedTreeLeafData new_leaf = { .value = value,
323 .next_value = low_leaf.next_value,
324 .next_index = low_leaf.next_index };
325 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
326 tree.update_element(prev_snapshot.next_available_leaf_index, new_leaf_hash);
327
328 AppendOnlyTreeSnapshot next_snapshot = {
329 .root = tree.root(),
330 .next_available_leaf_index = prev_snapshot.next_available_leaf_index + 1,
331 };
332
333 EXPECT_CALL(poseidon2, hash(_)).WillRepeatedly([](const std::vector<FF>& input) {
334 return RawPoseidon2::hash(input);
335 });
336 EXPECT_CALL(
337 merkle_check,
338 write(DOM_SEP__NULLIFIER_MERKLE, low_leaf_hash, updated_low_leaf_hash, low_leaf_index, _, prev_snapshot.root))
339 .WillRepeatedly(Return(intermediate_root));
340 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
341 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillRepeatedly(Return(true));
342 EXPECT_CALL(merkle_check,
344 FF(0),
345 new_leaf_hash,
346 prev_snapshot.next_available_leaf_index,
347 _,
348 intermediate_root))
349 .WillRepeatedly(Return(next_snapshot.root));
350
351 AppendOnlyTreeSnapshot result_snapshot = indexed_tree_check.write(value,
352 /*siloing_params*/ std::nullopt,
354 low_leaf,
355 low_leaf_index,
356 low_leaf_sibling_path,
357 prev_snapshot,
358 insertion_sibling_path);
359
360 EXPECT_EQ(next_snapshot, result_snapshot);
361
362 IndexedTreeReadWriteEvent expect_event = {
363 .value = value,
364 .prev_snapshot = prev_snapshot,
365 .next_snapshot = next_snapshot,
366 .tree_height = low_leaf_sibling_path.size(),
367 .merkle_hash_separator = FF(DOM_SEP__NULLIFIER_MERKLE),
368 .low_leaf_data = low_leaf,
369 .low_leaf_hash = low_leaf_hash,
370 .low_leaf_index = low_leaf_index,
371 .write = true,
372 .append_data =
373 IndexedLeafAppendData{
374 .updated_low_leaf_hash = updated_low_leaf_hash,
375 .new_leaf_hash = new_leaf_hash,
376 .intermediate_root = intermediate_root,
377 },
378 };
379
380 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
381
382 // Negative test: value already exists in tree
383 IndexedTreeLeafData matching_leaf = { .value = value,
384 .next_value = low_leaf.next_value,
385 .next_index = low_leaf.next_index };
387 /*siloing_params*/ std::nullopt,
389 matching_leaf,
390 low_leaf_index,
391 low_leaf_sibling_path,
392 prev_snapshot,
393 insertion_sibling_path),
394 "non-membership check failed");
395
396 // Negative test: value not greater than low leaf value
397 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(false));
399 /*siloing_params*/ std::nullopt,
401 low_leaf,
402 low_leaf_index,
403 low_leaf_sibling_path,
404 prev_snapshot,
405 insertion_sibling_path),
406 "Low leaf value is GTE leaf value");
407 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(true));
408
409 // Negative test: next value not greater than value
410 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillOnce(Return(false));
412 /*siloing_params*/ std::nullopt,
414 low_leaf,
415 low_leaf_index,
416 low_leaf_sibling_path,
417 prev_snapshot,
418 insertion_sibling_path),
419 "Leaf value is GTE low leaf next value");
420}
421
422} // namespace
423
424} // namespace bb::avm2::simulation
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define DOM_SEP__NULLIFIER_MERKLE
FieldGreaterThan field_gt
MerkleCheck merkle_check
IndexedTreeCheck indexed_tree_check
void assert_read(const FF &value, std::optional< IndexedTreeSiloingParameters > siloing_params, bool exists, const IndexedTreeLeafData &low_leaf_preimage, uint64_t low_leaf_index, std::span< const FF > sibling_path, const AppendOnlyTreeSnapshot &snapshot) override
Performs a membership/non-membership read check on an indexed tree.
AppendOnlyTreeSnapshot write(const FF &value, std::optional< IndexedTreeSiloingParameters > siloing_params, std::optional< uint64_t > public_inputs_index, const IndexedTreeLeafData &low_leaf_preimage, uint64_t low_leaf_index, std::span< const FF > low_leaf_sibling_path, const AppendOnlyTreeSnapshot &prev_snapshot, std::optional< std::span< const FF > > insertion_sibling_path) override
Writes a value into an indexed tree, or validates it already exists.
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
EventEmitter< DataCopyEvent > event_emitter
IndexedTreeLeafData low_leaf
AVM range check gadget for witness generation.
crypto::Poseidon2< crypto::Poseidon2Bn254ScalarFieldParams > poseidon2
AvmFlavorSettings::FF FF
Definition field.hpp:10
void write(B &buf, field2< base_field, Params > const &value)
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13