Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
nullifier_tree_check.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
30
31namespace bb::avm2::constraining {
32namespace {
33
34using ::testing::NiceMock;
35using ::testing::TestWithParam;
36
37using testing::TestMemoryTree;
38
39using simulation::EventEmitter;
40using simulation::ExecutionIdManager;
41using simulation::FieldGreaterThan;
42using simulation::FieldGreaterThanEvent;
43using simulation::MerkleCheck;
44using simulation::MerkleCheckEvent;
45using simulation::MockExecutionIdManager;
46using simulation::MockGreaterThan;
47using simulation::MockRangeCheck;
48using simulation::NoopEventEmitter;
49using simulation::NullifierTreeCheck;
52using simulation::Poseidon2;
53using simulation::Poseidon2HashEvent;
54using simulation::Poseidon2PermutationEvent;
55using simulation::Poseidon2PermutationMemoryEvent;
57
58using tracegen::NullifierTreeCheckTraceBuilder;
59using tracegen::TestTraceContainer;
60
62using C = Column;
63using nullifier_check = bb::avm2::nullifier_check<FF>;
65
66TEST(NullifierTreeCheckConstrainingTest, EmptyRow)
67{
68 check_relation<nullifier_check>(testing::empty_trace());
69}
70
71struct TestParams {
73 bool exists;
75};
76
77std::vector<TestParams> positive_read_tests = {
78 // Exists = true, leaf pointers to infinity
79 TestParams{ .nullifier = 42, .exists = true, .low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(42), 0, 0) },
80 // Exists = true, leaf points to higher value
81 TestParams{
82 .nullifier = 42, .exists = true, .low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(42), 28, 50) },
83 // Exists = false, low leaf points to infinity
84 TestParams{ .nullifier = 42, .exists = false, .low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(10), 0, 0) },
85 // Exists = false, low leaf points to higher value
86 TestParams{
87 .nullifier = 42, .exists = false, .low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(10), 28, 50) }
88};
89
90class NullifierReadPositiveTests : public TestWithParam<TestParams> {};
91
92TEST_P(NullifierReadPositiveTests, Positive)
93{
94 const auto& param = GetParam();
95
96 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
97 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
98 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
99
100 NiceMock<MockGreaterThan> mock_gt;
101 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
103 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
104 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
105
106 NiceMock<MockRangeCheck> range_check;
107
108 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
109 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
110
111 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
112 NullifierTreeCheck nullifier_tree_check_simulator(
113 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
114
115 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
116 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
117
118 FF low_leaf_hash = poseidon2.hash(param.low_leaf.get_hash_inputs());
119 uint64_t leaf_index = 30;
120 std::vector<FF> sibling_path;
121 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
122 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
123 sibling_path.emplace_back(i);
124 }
125 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
126
127 nullifier_tree_check_simulator.assert_read(
128 param.nullifier,
129 /*contract_address*/ std::nullopt,
130 param.exists,
131 param.low_leaf,
132 leaf_index,
133 sibling_path,
134 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 });
135
136 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
137 EXPECT_EQ(trace.get_num_rows(), 1);
138
139 check_relation<nullifier_check>(trace);
140}
141
142INSTANTIATE_TEST_SUITE_P(NullifierTreeCheckConstrainingTest,
143 NullifierReadPositiveTests,
144 ::testing::ValuesIn(positive_read_tests));
145
146TEST(NullifierTreeCheckConstrainingTest, PositiveWriteAppend)
147{
148 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
149 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
150 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
151
152 NiceMock<MockGreaterThan> mock_gt;
153 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
155 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
156 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
157
158 NiceMock<MockRangeCheck> range_check;
159
160 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
161 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
162
163 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
164 NullifierTreeCheck nullifier_tree_check_simulator(
165 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
166
167 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
168 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
169
170 FF nullifier = 100;
171 FF low_nullifier = 40;
172 TestMemoryTree<Poseidon2HashPolicy> nullifier_tree(8, NULLIFIER_TREE_HEIGHT);
173
177 uint64_t low_leaf_index = 0;
178 nullifier_tree.update_element(low_leaf_index, low_leaf_hash);
179
180 AppendOnlyTreeSnapshot prev_snapshot =
181 AppendOnlyTreeSnapshot{ .root = nullifier_tree.root(), .next_available_leaf_index = 128 };
182 std::vector<FF> low_leaf_sibling_path = nullifier_tree.get_sibling_path(low_leaf_index);
183
184 NullifierTreeLeafPreimage updated_low_leaf = low_leaf;
185 updated_low_leaf.nextIndex = prev_snapshot.next_available_leaf_index;
186 updated_low_leaf.nextKey = nullifier;
187 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
188 nullifier_tree.update_element(low_leaf_index, updated_low_leaf_hash);
189
190 std::vector<FF> insertion_sibling_path = nullifier_tree.get_sibling_path(prev_snapshot.next_available_leaf_index);
191
194 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
195 nullifier_tree.update_element(prev_snapshot.next_available_leaf_index, new_leaf_hash);
196
197 nullifier_tree_check_simulator.write(nullifier,
198 /*contract_address*/ std::nullopt,
199 0,
200 low_leaf,
201 low_leaf_index,
202 low_leaf_sibling_path,
203 prev_snapshot,
204 insertion_sibling_path);
205
206 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
207
208 EXPECT_EQ(trace.get_num_rows(), 1);
209
210 check_relation<nullifier_check>(trace);
211}
212
213TEST(NullifierTreeCheckConstrainingTest, PositiveWriteMembership)
214{
215 FF nullifier = 42;
217 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
218 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
219 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
220
221 NiceMock<MockGreaterThan> mock_gt;
222 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
224 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
225 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
226
227 NiceMock<MockRangeCheck> range_check;
228
229 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
230 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
231
232 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
233 NullifierTreeCheck nullifier_tree_check_simulator(
234 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
235
236 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
237 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
238
239 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
240 uint64_t leaf_index = 30;
241 std::vector<FF> sibling_path;
242 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
243 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
244 sibling_path.emplace_back(i);
245 }
246 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
247
248 nullifier_tree_check_simulator.write(nullifier,
250 10,
251 low_leaf,
252 leaf_index,
253 sibling_path,
254 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
255 /* insertion_sibling_path */ std::nullopt);
256
257 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
258 EXPECT_EQ(trace.get_num_rows(), 1);
259
260 check_relation<nullifier_check>(trace);
261}
262
263TEST(NullifierTreeCheckConstrainingTest, Siloing)
264{
266 FF nullifier = 42;
268 auto low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(siloed_nullifier), 0, 0);
269 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
270 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
271 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
272
273 NiceMock<MockGreaterThan> mock_gt;
274 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
276 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
277 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
278
279 NiceMock<MockRangeCheck> range_check;
280
281 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
282 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
283
284 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
285 NullifierTreeCheck nullifier_tree_check_simulator(
286 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
287
288 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
289 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
290
291 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
292 uint64_t leaf_index = 30;
293 std::vector<FF> sibling_path;
294 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
295 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
296 sibling_path.emplace_back(i);
297 }
298 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
299
300 nullifier_tree_check_simulator.write(nullifier,
302 10,
303 low_leaf,
304 leaf_index,
305 sibling_path,
306 AppendOnlyTreeSnapshot{ .root = root, .next_available_leaf_index = 128 },
307 /* insertion_sibling_path */ std::nullopt);
308
309 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
310 EXPECT_EQ(trace.get_num_rows(), 1);
311
312 check_relation<nullifier_check>(trace);
313}
314
315TEST(NullifierTreeCheckConstrainingTest, NegativeExistsFlagCheck)
316{
317 // Test constraint: sel * (NULLIFIER_LOW_LEAF_NULLIFIER_DIFF * (exists * (1 - nullifier_low_leaf_nullifier_diff_inv)
318 // + nullifier_low_leaf_nullifier_diff_inv) - 1 + exists) = 0
319 TestTraceContainer trace({
320 { { C::nullifier_check_sel, 1 },
321 { C::nullifier_check_siloed_nullifier, 27 },
322 { C::nullifier_check_low_leaf_nullifier, 27 },
323 { C::nullifier_check_nullifier_low_leaf_nullifier_diff_inv, 0 },
324 { C::nullifier_check_exists, 1 } },
325 { { C::nullifier_check_sel, 1 },
326 { C::nullifier_check_siloed_nullifier, 28 },
327 { C::nullifier_check_low_leaf_nullifier, 27 },
328 { C::nullifier_check_nullifier_low_leaf_nullifier_diff_inv, FF(1).invert() },
329 { C::nullifier_check_exists, 0 } },
330 });
331
332 check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK);
333 trace.set(C::nullifier_check_exists, 0, 0);
334
335 EXPECT_THROW_WITH_MESSAGE(check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK), "EXISTS_CHECK");
336 trace.set(C::nullifier_check_exists, 0, 1);
337 trace.set(C::nullifier_check_exists, 1, 1);
338
339 EXPECT_THROW_WITH_MESSAGE(check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK), "EXISTS_CHECK");
340}
341
342TEST(NullifierTreeCheckConstrainingTest, NegativeNextSlotIsZero)
343{
344 // Test constraint: leaf_not_exists * (low_leaf_next_nullifier * (NEXT_NULLIFIER_IS_ZERO * (1 - next_nullifier_inv)
345 // + next_nullifier_inv) - 1 + NEXT_NULLIFIER_IS_ZERO) = 0
346 TestTraceContainer trace({
347 {
348 { C::nullifier_check_leaf_not_exists, 1 },
349 { C::nullifier_check_low_leaf_next_nullifier, 0 },
350 { C::nullifier_check_next_nullifier_inv, 0 },
351 { C::nullifier_check_next_nullifier_is_nonzero, 0 },
352 },
353 {
354 { C::nullifier_check_leaf_not_exists, 1 },
355 { C::nullifier_check_low_leaf_next_nullifier, 1 },
356 { C::nullifier_check_next_nullifier_inv, FF(1).invert() },
357 { C::nullifier_check_next_nullifier_is_nonzero, 1 },
358 },
359 });
360
361 check_relation<nullifier_check>(trace, nullifier_check::SR_NEXT_NULLIFIER_IS_ZERO_CHECK);
362
363 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 0, 1);
364
366 "NEXT_NULLIFIER_IS_ZERO_CHECK");
367
368 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 0, 0);
369 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 1, 0);
370
372 "NEXT_NULLIFIER_IS_ZERO_CHECK");
373}
374
375TEST(NullifierTreeCheckConstrainingTest, NegativePassthrougSiloing)
376{
377 // Test constraint: sel * (1 - should_silo) * (nullifier - siloed_nullifier) = 0;
378 TestTraceContainer trace({
379 {
380 { C::nullifier_check_sel, 1 },
381 { C::nullifier_check_should_silo, 1 },
382 { C::nullifier_check_nullifier, 27 },
383 { C::nullifier_check_siloed_nullifier, 42 },
384 },
385 {
386 { C::nullifier_check_sel, 1 },
387 { C::nullifier_check_should_silo, 0 },
388 { C::nullifier_check_nullifier, 27 },
389 { C::nullifier_check_siloed_nullifier, 27 },
390 },
391 });
392
393 check_relation<nullifier_check>(trace, nullifier_check::SR_PASSTHROUGH_SILOING);
394
395 trace.set(C::nullifier_check_siloed_nullifier, 1, 28);
396
397 EXPECT_THROW_WITH_MESSAGE(check_relation<nullifier_check>(trace, nullifier_check::SR_PASSTHROUGH_SILOING),
398 "PASSTHROUGH_SILOING");
399}
400
401} // namespace
402} // namespace bb::avm2::constraining
INSTANTIATE_TEST_SUITE_P(AcirTests, AcirIntegrationSingleTest, testing::Values("a_1327_concrete_in_generic", "a_1_mul", "a_2_div", "a_3_add", "a_4_sub", "a_5_over", "a_6", "a_6_array", "a_7", "a_7_function", "aes128_encrypt", "arithmetic_binary_operations", "array_dynamic", "array_dynamic_blackbox_input", "array_dynamic_main_output", "array_dynamic_nested_blackbox_input", "array_eq", "array_if_cond_simple", "array_len", "array_neq", "array_sort", "array_to_slice", "array_to_slice_constant_length", "assert", "assert_statement", "assign_ex", "bigint", "bit_and", "bit_not", "bit_shifts_comptime", "bit_shifts_runtime", "blake3", "bool_not", "bool_or", "break_and_continue", "brillig_acir_as_brillig", "brillig_array_eq", "brillig_array_to_slice", "brillig_arrays", "brillig_assert", "brillig_bit_shifts_runtime", "brillig_blake2s", "brillig_blake3", "brillig_calls", "brillig_calls_array", "brillig_calls_conditionals", "brillig_conditional", "brillig_cow", "brillig_cow_assign", "brillig_cow_regression", "brillig_ecdsa_secp256k1", "brillig_ecdsa_secp256r1", "brillig_embedded_curve", "brillig_fns_as_values", "brillig_hash_to_field", "brillig_identity_function", "brillig_keccak", "brillig_loop", "brillig_nested_arrays", "brillig_not", "brillig_oracle", "brillig_pedersen", "brillig_recursion", "brillig_references", "brillig_schnorr", "brillig_sha256", "brillig_signed_cmp", "brillig_signed_div", "brillig_slices", "brillig_to_be_bytes", "brillig_to_bits", "brillig_to_bytes_integration", "brillig_to_le_bytes", "brillig_top_level", "brillig_uninitialized_arrays", "brillig_wrapping", "cast_bool", "closures_mut_ref", "conditional_1", "conditional_2", "conditional_regression_421", "conditional_regression_547", "conditional_regression_661", "conditional_regression_short_circuit", "conditional_regression_underflow", "custom_entry", "databus", "debug_logs", "diamond_deps_0", "double_verify_nested_proof", "double_verify_proof", "ecdsa_secp256k1", "ecdsa_secp256r1", "ecdsa_secp256r1_3x", "eddsa", "embedded_curve_ops", "field_attribute", "generics", "global_consts", "hash_to_field", "hashmap", "higher_order_functions", "if_else_chain", "import", "inline_never_basic", "integer_array_indexing", "keccak256", "main_bool_arg", "main_return", "merkle_insert", "missing_closure_env", "modules", "modules_more", "modulus", "nested_array_dynamic", "nested_array_dynamic_simple", "nested_array_in_slice", "nested_arrays_from_brillig", "no_predicates_basic", "no_predicates_brillig", "no_predicates_numeric_generic_poseidon", "operator_overloading", "pedersen_check", "pedersen_commitment", "pedersen_hash", "poseidon_bn254_hash", "poseidonsponge_x5_254", "pred_eq", "prelude", "references", "regression", "regression_2660", "regression_3051", "regression_3394", "regression_3607", "regression_3889", "regression_4088", "regression_4124", "regression_4202", "regression_4449", "regression_4709", "regression_5045", "regression_capacity_tracker", "regression_mem_op_predicate", "regression_method_cannot_be_found", "regression_struct_array_conditional", "schnorr", "sha256", "sha2_byte", "side_effects_constrain_array", "signed_arithmetic", "signed_comparison", "signed_division", "simple_2d_array", "simple_add_and_ret_arr", "simple_array_param", "simple_bitwise", "simple_comparison", "simple_mut", "simple_not", "simple_print", "simple_program_addition", "simple_radix", "simple_shield", "simple_shift_left_right", "slice_coercion", "slice_dynamic_index", "slice_loop", "slices", "strings", "struct", "struct_array_inputs", "struct_fields_ordering", "struct_inputs", "submodules", "to_be_bytes", "to_bytes_consistent", "to_bytes_integration", "to_le_bytes", "trait_as_return_type", "trait_impl_base_type", "traits_in_crates_1", "traits_in_crates_2", "tuple_inputs", "tuples", "type_aliases", "u128", "u16_support", "unconstrained_empty", "unit_value", "unsafe_range_constraint", "witness_compression", "xor"))
TEST_P(AcirIntegrationSingleTest, DISABLED_ProveAndVerifyProgram)
FieldGreaterThan field_gt
#define NULLIFIER_TREE_HEIGHT
StrictMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
EventEmitter< Poseidon2PermutationEvent > perm_event_emitter
EventEmitter< Poseidon2HashEvent > hash_event_emitter
static constexpr size_t SR_NEXT_NULLIFIER_IS_ZERO_CHECK
static constexpr size_t SR_PASSTHROUGH_SILOING
static constexpr size_t SR_EXISTS_CHECK
void set(Column col, uint32_t row, const FF &value)
static FF hash(const std::vector< FF > &input)
Hashes a vector of field elements.
Implements a parallelized batch insertion indexed tree Accepts template argument of the type of store...
RangeCheck range_check
TestTraceContainer trace
NullifierTreeLeafPreimage low_leaf
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessage)
Definition macros.hpp:7
TEST(TxExecutionConstrainingTest, WriteTreeValue)
Definition tx.test.cpp:441
crypto::merkle_tree::IndexedLeaf< crypto::merkle_tree::NullifierLeafValue > NullifierTreeLeafPreimage
Definition db_types.hpp:11
crypto::Poseidon2< crypto::Poseidon2Bn254ScalarFieldParams > poseidon2
::bb::crypto::merkle_tree::NullifierLeafValue NullifierLeafValue
Definition db.hpp:39
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< NullifierTreeReadWriteEvent, CheckPointEventType > NullifierTreeCheckEvent
FF unconstrained_silo_nullifier(const AztecAddress &contract_address, const FF &nullifier)
Definition merkle.cpp:31
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
std::vector< fr > get_hash_inputs() const