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
15
16namespace bb::avm2::simulation {
17
18using ::testing::_;
19using ::testing::ElementsAre;
20using ::testing::Return;
21using ::testing::StrictMock;
22
24
25namespace {
26
27TEST(AvmSimulationIndexedTreeCheck, ReadExists)
28{
29 StrictMock<MockPoseidon2> poseidon2;
30 StrictMock<MockMerkleCheck> merkle_check;
31 StrictMock<MockFieldGreaterThan> field_gt;
32
33 EventEmitter<IndexedTreeCheckEvent> event_emitter;
34 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
35
36 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
38 uint64_t low_leaf_index = 30;
39 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
40 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
41
42 FF value = 42;
43
44 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
45 EXPECT_CALL(merkle_check, assert_membership(low_leaf_hash, low_leaf_index, _, snapshot.root))
46 .WillRepeatedly(Return());
47
49 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot);
50
51 IndexedTreeReadWriteEvent expect_event = {
52 .value = value,
53 .prev_snapshot = snapshot,
54 .next_snapshot = snapshot,
55 .tree_height = sibling_path.size(),
56 .low_leaf_data = low_leaf,
57 .low_leaf_hash = low_leaf_hash,
58 .low_leaf_index = low_leaf_index,
59 };
60 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
61
62 // Negative test: value does not exist
65 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot),
66 "non-membership check failed");
67}
68
69TEST(AvmSimulationIndexedTreeCheck, ReadNotExistsLowPointsToInfinity)
70{
71 StrictMock<MockPoseidon2> poseidon2;
72 StrictMock<MockMerkleCheck> merkle_check;
73 StrictMock<MockFieldGreaterThan> field_gt;
74
75 EventEmitter<IndexedTreeCheckEvent> event_emitter;
76 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
77
78 IndexedTreeLeafData low_leaf = { .value = 40, .next_value = 0, .next_index = 0 };
80 uint64_t low_leaf_index = 30;
81 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
82 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
83 FF value = 42;
84
85 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
86 EXPECT_CALL(merkle_check, assert_membership(low_leaf_hash, low_leaf_index, _, snapshot.root))
87 .WillRepeatedly(Return());
88 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
89
91 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot);
92 IndexedTreeReadWriteEvent expect_event = {
93 .value = value,
94 .prev_snapshot = snapshot,
95 .next_snapshot = snapshot,
96 .tree_height = sibling_path.size(),
97 .low_leaf_data = low_leaf,
98 .low_leaf_hash = low_leaf_hash,
99 .low_leaf_index = low_leaf_index,
100 };
101 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
102
103 // Negative test: value exists
106 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
107 "membership check failed");
108
109 // Negative test: value not greater than low leaf value
110 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(false));
113 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
114 "Low leaf value is GTE leaf value");
115}
116
117TEST(AvmSimulationIndexedTreeCheck, ReadNotExistsLowPointsToAnotherLeaf)
118{
119 StrictMock<MockPoseidon2> poseidon2;
120 StrictMock<MockMerkleCheck> merkle_check;
121 StrictMock<MockFieldGreaterThan> field_gt;
122
123 EventEmitter<IndexedTreeCheckEvent> event_emitter;
124 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
125
126 IndexedTreeLeafData low_leaf = { .value = 40, .next_value = 50, .next_index = 28 };
128 uint64_t low_leaf_index = 30;
129 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
130 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
131 FF value = 42;
132
133 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
134 EXPECT_CALL(merkle_check, assert_membership(low_leaf_hash, low_leaf_index, _, snapshot.root))
135 .WillRepeatedly(Return());
136 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
137 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillRepeatedly(Return(true));
138
140 value, /*siloing_params*/ std::nullopt, false, low_leaf, low_leaf_index, sibling_path, snapshot);
141 IndexedTreeReadWriteEvent expect_event = {
142 .value = value,
143 .prev_snapshot = snapshot,
144 .next_snapshot = snapshot,
145 .tree_height = sibling_path.size(),
146 .low_leaf_data = low_leaf,
147 .low_leaf_hash = low_leaf_hash,
148 .low_leaf_index = low_leaf_index,
149 };
150 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
151
152 // Negative test: value exists
155 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
156 "membership check failed");
157
158 // Negative test: next value not greater than value
159 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillOnce(Return(false));
162 value, /*siloing_params*/ std::nullopt, true, low_leaf, low_leaf_index, sibling_path, snapshot),
163 "Leaf value is GTE low leaf next value");
164}
165
166TEST(AvmSimulationIndexedTreeCheck, WriteExists)
167{
168 StrictMock<MockPoseidon2> poseidon2;
169 StrictMock<MockMerkleCheck> merkle_check;
170 StrictMock<MockFieldGreaterThan> field_gt;
171
172 EventEmitter<IndexedTreeCheckEvent> event_emitter;
173 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
174
175 IndexedTreeLeafData low_leaf = { .value = 42, .next_value = 0, .next_index = 0 };
177 uint64_t low_leaf_index = 30;
178 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
179 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
180
181 FF value = 42;
182
183 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
184 EXPECT_CALL(merkle_check, assert_membership(low_leaf_hash, low_leaf_index, _, snapshot.root))
185 .WillRepeatedly(Return());
186
187 AppendOnlyTreeSnapshot result_snapshot = indexed_tree_check.write(value,
188 /*siloing_params*/ std::nullopt,
189 10,
190 low_leaf,
191 low_leaf_index,
192 sibling_path,
193 snapshot,
194 /*insertion_path*/ std::nullopt);
195
196 EXPECT_EQ(result_snapshot, snapshot);
197
198 IndexedTreeReadWriteEvent expect_event = {
199 .value = value,
200 .prev_snapshot = snapshot,
201 .next_snapshot = snapshot,
202 .tree_height = sibling_path.size(),
203 .low_leaf_data = low_leaf,
204 .low_leaf_hash = low_leaf_hash,
205 .low_leaf_index = low_leaf_index,
206 .write = true,
207 .public_inputs_index = 10,
208 };
209 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
210}
211
212TEST(AvmSimulationIndexedTreeCheck, Siloing)
213{
214 StrictMock<MockPoseidon2> poseidon2;
215 StrictMock<MockMerkleCheck> merkle_check;
216 StrictMock<MockFieldGreaterThan> field_gt;
217
218 EventEmitter<IndexedTreeCheckEvent> event_emitter;
219 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
220
221 FF value = 42;
222 FF separator = 99;
224 IndexedTreeSiloingParameters siloing_params = { .address = address, .siloing_separator = separator };
225 std::vector<FF> siloed_hash_inputs = { separator, address, value };
226 FF siloed_value = RawPoseidon2::hash(siloed_hash_inputs);
227
228 IndexedTreeLeafData low_leaf = { .value = siloed_value, .next_value = 0, .next_index = 0 };
230 uint64_t low_leaf_index = 30;
231 std::vector<FF> sibling_path = { 1, 2, 3, 4, 5 };
232 AppendOnlyTreeSnapshot snapshot = { .root = 123456, .next_available_leaf_index = 128 };
233
234 EXPECT_CALL(poseidon2, hash(siloed_hash_inputs)).WillRepeatedly(Return(siloed_value));
235 EXPECT_CALL(poseidon2, hash(low_leaf.get_hash_inputs())).WillRepeatedly(Return(low_leaf_hash));
236 EXPECT_CALL(merkle_check, assert_membership(low_leaf_hash, low_leaf_index, _, snapshot.root))
237 .WillRepeatedly(Return());
238
239 indexed_tree_check.assert_read(value, siloing_params, true, low_leaf, low_leaf_index, sibling_path, snapshot);
240
241 IndexedLeafSiloingData expected_siloing_data = { .siloed_value = siloed_value, .parameters = siloing_params };
242 IndexedTreeReadWriteEvent read_event = {
243 .value = value,
244 .prev_snapshot = snapshot,
245 .next_snapshot = snapshot,
246 .tree_height = sibling_path.size(),
247 .low_leaf_data = low_leaf,
248 .low_leaf_hash = low_leaf_hash,
249 .low_leaf_index = low_leaf_index,
250 .siloing_data = expected_siloing_data,
251 };
253 siloing_params,
254 10,
255 low_leaf,
256 low_leaf_index,
257 sibling_path,
258 snapshot,
259 /*insertion_path*/ std::nullopt);
260
261 IndexedTreeReadWriteEvent write_event = {
262 .value = value,
263 .prev_snapshot = snapshot,
264 .next_snapshot = snapshot,
265 .tree_height = sibling_path.size(),
266 .low_leaf_data = low_leaf,
267 .low_leaf_hash = low_leaf_hash,
268 .low_leaf_index = low_leaf_index,
269 .write = true,
270 .siloing_data = expected_siloing_data,
271 .public_inputs_index = 10,
272 };
273 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(read_event, write_event));
274}
275
276TEST(AvmSimulationIndexedTreeCheck, WriteAppend)
277{
278 StrictMock<MockPoseidon2> poseidon2;
279 StrictMock<MockMerkleCheck> merkle_check;
280 StrictMock<MockFieldGreaterThan> field_gt;
281
282 EventEmitter<IndexedTreeCheckEvent> event_emitter;
283 IndexedTreeCheck indexed_tree_check(poseidon2, merkle_check, field_gt, event_emitter);
284
285 FF value = 100;
286 FF low_value = 40;
287
289
290 IndexedTreeLeafData low_leaf = { .value = low_value, .next_value = value + 1, .next_index = 10 };
292 uint64_t low_leaf_index = 0;
293 tree.update_element(low_leaf_index, low_leaf_hash);
294
295 AppendOnlyTreeSnapshot prev_snapshot = { .root = tree.root(), .next_available_leaf_index = 128 };
296 std::vector<FF> low_leaf_sibling_path = tree.get_sibling_path(low_leaf_index);
297
298 IndexedTreeLeafData updated_low_leaf = {
299 .value = low_leaf.value,
300 .next_value = value,
301 .next_index = prev_snapshot.next_available_leaf_index,
302 };
303 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
304 tree.update_element(low_leaf_index, updated_low_leaf_hash);
305
306 FF intermediate_root = tree.root();
307 std::vector<FF> insertion_sibling_path = tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
308
309 // The new leaf gets the old low leaf's next pointer.
310 IndexedTreeLeafData new_leaf = { .value = value,
311 .next_value = low_leaf.next_value,
312 .next_index = low_leaf.next_index };
313 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
314 tree.update_element(prev_snapshot.next_available_leaf_index, new_leaf_hash);
315
316 AppendOnlyTreeSnapshot next_snapshot = {
317 .root = tree.root(),
318 .next_available_leaf_index = prev_snapshot.next_available_leaf_index + 1,
319 };
320
321 EXPECT_CALL(poseidon2, hash(_)).WillRepeatedly([](const std::vector<FF>& input) {
322 return RawPoseidon2::hash(input);
323 });
324 EXPECT_CALL(merkle_check, write(low_leaf_hash, updated_low_leaf_hash, low_leaf_index, _, prev_snapshot.root))
325 .WillRepeatedly(Return(intermediate_root));
326 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillRepeatedly(Return(true));
327 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillRepeatedly(Return(true));
328 EXPECT_CALL(merkle_check,
329 write(FF(0), new_leaf_hash, prev_snapshot.next_available_leaf_index, _, intermediate_root))
330 .WillRepeatedly(Return(next_snapshot.root));
331
332 AppendOnlyTreeSnapshot result_snapshot = indexed_tree_check.write(value,
333 /*siloing_params*/ std::nullopt,
335 low_leaf,
336 low_leaf_index,
337 low_leaf_sibling_path,
338 prev_snapshot,
339 insertion_sibling_path);
340
341 EXPECT_EQ(next_snapshot, result_snapshot);
342
343 IndexedTreeReadWriteEvent expect_event = {
344 .value = value,
345 .prev_snapshot = prev_snapshot,
346 .next_snapshot = next_snapshot,
347 .tree_height = low_leaf_sibling_path.size(),
348 .low_leaf_data = low_leaf,
349 .low_leaf_hash = low_leaf_hash,
350 .low_leaf_index = low_leaf_index,
351 .write = true,
352 .append_data =
353 IndexedLeafAppendData{
354 .updated_low_leaf_hash = updated_low_leaf_hash,
355 .new_leaf_hash = new_leaf_hash,
356 .intermediate_root = intermediate_root,
357 },
358 };
359
360 EXPECT_THAT(event_emitter.dump_events(), ElementsAre(expect_event));
361
362 // Negative test: value already exists in tree
363 IndexedTreeLeafData matching_leaf = { .value = value,
364 .next_value = low_leaf.next_value,
365 .next_index = low_leaf.next_index };
367 /*siloing_params*/ std::nullopt,
369 matching_leaf,
370 low_leaf_index,
371 low_leaf_sibling_path,
372 prev_snapshot,
373 insertion_sibling_path),
374 "non-membership check failed");
375
376 // Negative test: value not greater than low leaf value
377 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(false));
379 /*siloing_params*/ std::nullopt,
381 low_leaf,
382 low_leaf_index,
383 low_leaf_sibling_path,
384 prev_snapshot,
385 insertion_sibling_path),
386 "Low leaf value is GTE leaf value");
387 EXPECT_CALL(field_gt, ff_gt(value, low_leaf.value)).WillOnce(Return(true));
388
389 // Negative test: next value not greater than value
390 EXPECT_CALL(field_gt, ff_gt(low_leaf.next_value, value)).WillOnce(Return(false));
392 /*siloing_params*/ std::nullopt,
394 low_leaf,
395 low_leaf_index,
396 low_leaf_sibling_path,
397 prev_snapshot,
398 insertion_sibling_path),
399 "Leaf value is GTE low leaf next value");
400}
401
402} // namespace
403
404} // namespace bb::avm2::simulation
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
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