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(param.nullifier,
128 /*contract_address*/ std::nullopt,
129 param.exists,
130 param.low_leaf,
131 leaf_index,
132 sibling_path,
133 AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 });
134
135 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
136 EXPECT_EQ(trace.get_num_rows(), 1);
137
138 check_relation<nullifier_check>(trace);
139}
140
141INSTANTIATE_TEST_SUITE_P(NullifierTreeCheckConstrainingTest,
142 NullifierReadPositiveTests,
143 ::testing::ValuesIn(positive_read_tests));
144
145TEST(NullifierTreeCheckConstrainingTest, PositiveWriteAppend)
146{
147 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
148 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
149 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
150
151 NiceMock<MockGreaterThan> mock_gt;
152 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
154 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
155 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
156
157 NiceMock<MockRangeCheck> range_check;
158
159 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
160 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
161
162 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
163 NullifierTreeCheck nullifier_tree_check_simulator(
164 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
165
166 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
167 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
168
169 FF nullifier = 100;
170 FF low_nullifier = 40;
171 TestMemoryTree<Poseidon2HashPolicy> nullifier_tree(8, NULLIFIER_TREE_HEIGHT);
172
176 uint64_t low_leaf_index = 0;
177 nullifier_tree.update_element(low_leaf_index, low_leaf_hash);
178
179 AppendOnlyTreeSnapshot prev_snapshot =
180 AppendOnlyTreeSnapshot{ .root = nullifier_tree.root(), .nextAvailableLeafIndex = 128 };
181 std::vector<FF> low_leaf_sibling_path = nullifier_tree.get_sibling_path(low_leaf_index);
182
183 NullifierTreeLeafPreimage updated_low_leaf = low_leaf;
184 updated_low_leaf.nextIndex = prev_snapshot.nextAvailableLeafIndex;
185 updated_low_leaf.nextKey = nullifier;
186 FF updated_low_leaf_hash = RawPoseidon2::hash(updated_low_leaf.get_hash_inputs());
187 nullifier_tree.update_element(low_leaf_index, updated_low_leaf_hash);
188
189 std::vector<FF> insertion_sibling_path = nullifier_tree.get_sibling_path(prev_snapshot.nextAvailableLeafIndex);
190
193 FF new_leaf_hash = RawPoseidon2::hash(new_leaf.get_hash_inputs());
194 nullifier_tree.update_element(prev_snapshot.nextAvailableLeafIndex, new_leaf_hash);
195
196 nullifier_tree_check_simulator.write(nullifier,
197 /*contract_address*/ std::nullopt,
198 0,
199 low_leaf,
200 low_leaf_index,
201 low_leaf_sibling_path,
202 prev_snapshot,
203 insertion_sibling_path);
204
205 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
206
207 EXPECT_EQ(trace.get_num_rows(), 1);
208
209 check_relation<nullifier_check>(trace);
210}
211
212TEST(NullifierTreeCheckConstrainingTest, PositiveWriteMembership)
213{
214 FF nullifier = 42;
216 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
217 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
218 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
219
220 NiceMock<MockGreaterThan> mock_gt;
221 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
223 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
224 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
225
226 NiceMock<MockRangeCheck> range_check;
227
228 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
229 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
230
231 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
232 NullifierTreeCheck nullifier_tree_check_simulator(
233 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
234
235 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
236 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
237
238 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
239 uint64_t leaf_index = 30;
240 std::vector<FF> sibling_path;
241 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
242 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
243 sibling_path.emplace_back(i);
244 }
245 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
246
247 nullifier_tree_check_simulator.write(nullifier,
249 10,
250 low_leaf,
251 leaf_index,
252 sibling_path,
253 AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 },
254 /* insertion_sibling_path */ std::nullopt);
255
256 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
257 EXPECT_EQ(trace.get_num_rows(), 1);
258
259 check_relation<nullifier_check>(trace);
260}
261
262TEST(NullifierTreeCheckConstrainingTest, Siloing)
263{
265 FF nullifier = 42;
267 auto low_leaf = NullifierTreeLeafPreimage(NullifierLeafValue(siloed_nullifier), 0, 0);
268 NoopEventEmitter<Poseidon2HashEvent> hash_event_emitter;
269 NoopEventEmitter<Poseidon2PermutationEvent> perm_event_emitter;
270 NoopEventEmitter<Poseidon2PermutationMemoryEvent> perm_mem_event_emitter;
271
272 NiceMock<MockGreaterThan> mock_gt;
273 NiceMock<MockExecutionIdManager> mock_exec_id_manager;
275 NoopEventEmitter<MerkleCheckEvent> merkle_event_emitter;
276 MerkleCheck merkle_check(poseidon2, merkle_event_emitter);
277
278 NiceMock<MockRangeCheck> range_check;
279
280 NoopEventEmitter<FieldGreaterThanEvent> field_gt_event_emitter;
281 FieldGreaterThan field_gt(range_check, field_gt_event_emitter);
282
283 EventEmitter<NullifierTreeCheckEvent> nullifier_tree_check_event_emitter;
284 NullifierTreeCheck nullifier_tree_check_simulator(
285 poseidon2, merkle_check, field_gt, nullifier_tree_check_event_emitter);
286
287 TestTraceContainer trace({ { { C::precomputed_first_row, 1 } } });
288 NullifierTreeCheckTraceBuilder nullifier_tree_check_builder;
289
290 FF low_leaf_hash = poseidon2.hash(low_leaf.get_hash_inputs());
291 uint64_t leaf_index = 30;
292 std::vector<FF> sibling_path;
293 sibling_path.reserve(NULLIFIER_TREE_HEIGHT);
294 for (size_t i = 0; i < NULLIFIER_TREE_HEIGHT; ++i) {
295 sibling_path.emplace_back(i);
296 }
297 FF root = unconstrained_root_from_path(low_leaf_hash, leaf_index, sibling_path);
298
299 nullifier_tree_check_simulator.write(nullifier,
301 10,
302 low_leaf,
303 leaf_index,
304 sibling_path,
305 AppendOnlyTreeSnapshot{ .root = root, .nextAvailableLeafIndex = 128 },
306 /* insertion_sibling_path */ std::nullopt);
307
308 nullifier_tree_check_builder.process(nullifier_tree_check_event_emitter.dump_events(), trace);
309 EXPECT_EQ(trace.get_num_rows(), 1);
310
311 check_relation<nullifier_check>(trace);
312}
313
314TEST(NullifierTreeCheckConstrainingTest, NegativeExistsFlagCheck)
315{
316 // Test constraint: sel * (NULLIFIER_LOW_LEAF_NULLIFIER_DIFF * (exists * (1 - nullifier_low_leaf_nullifier_diff_inv)
317 // + nullifier_low_leaf_nullifier_diff_inv) - 1 + exists) = 0
318 TestTraceContainer trace({
319 { { C::nullifier_check_sel, 1 },
320 { C::nullifier_check_siloed_nullifier, 27 },
321 { C::nullifier_check_low_leaf_nullifier, 27 },
322 { C::nullifier_check_nullifier_low_leaf_nullifier_diff_inv, 0 },
323 { C::nullifier_check_exists, 1 } },
324 { { C::nullifier_check_sel, 1 },
325 { C::nullifier_check_siloed_nullifier, 28 },
326 { C::nullifier_check_low_leaf_nullifier, 27 },
327 { C::nullifier_check_nullifier_low_leaf_nullifier_diff_inv, FF(1).invert() },
328 { C::nullifier_check_exists, 0 } },
329 });
330
331 check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK);
332 trace.set(C::nullifier_check_exists, 0, 0);
333
334 EXPECT_THROW_WITH_MESSAGE(check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK), "EXISTS_CHECK");
335 trace.set(C::nullifier_check_exists, 0, 1);
336 trace.set(C::nullifier_check_exists, 1, 1);
337
338 EXPECT_THROW_WITH_MESSAGE(check_relation<nullifier_check>(trace, nullifier_check::SR_EXISTS_CHECK), "EXISTS_CHECK");
339}
340
341TEST(NullifierTreeCheckConstrainingTest, NegativeNextSlotIsZero)
342{
343 // Test constraint: leaf_not_exists * (low_leaf_next_nullifier * (NEXT_NULLIFIER_IS_ZERO * (1 - next_nullifier_inv)
344 // + next_nullifier_inv) - 1 + NEXT_NULLIFIER_IS_ZERO) = 0
345 TestTraceContainer trace({
346 {
347 { C::nullifier_check_leaf_not_exists, 1 },
348 { C::nullifier_check_low_leaf_next_nullifier, 0 },
349 { C::nullifier_check_next_nullifier_inv, 0 },
350 { C::nullifier_check_next_nullifier_is_nonzero, 0 },
351 },
352 {
353 { C::nullifier_check_leaf_not_exists, 1 },
354 { C::nullifier_check_low_leaf_next_nullifier, 1 },
355 { C::nullifier_check_next_nullifier_inv, FF(1).invert() },
356 { C::nullifier_check_next_nullifier_is_nonzero, 1 },
357 },
358 });
359
360 check_relation<nullifier_check>(trace, nullifier_check::SR_NEXT_NULLIFIER_IS_ZERO_CHECK);
361
362 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 0, 1);
363
365 "NEXT_NULLIFIER_IS_ZERO_CHECK");
366
367 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 0, 0);
368 trace.set(C::nullifier_check_next_nullifier_is_nonzero, 1, 0);
369
371 "NEXT_NULLIFIER_IS_ZERO_CHECK");
372}
373
374TEST(NullifierTreeCheckConstrainingTest, NegativePassthrougSiloing)
375{
376 // Test constraint: sel * (1 - should_silo) * (nullifier - siloed_nullifier) = 0;
377 TestTraceContainer trace({
378 {
379 { C::nullifier_check_sel, 1 },
380 { C::nullifier_check_should_silo, 1 },
381 { C::nullifier_check_nullifier, 27 },
382 { C::nullifier_check_siloed_nullifier, 42 },
383 },
384 {
385 { C::nullifier_check_sel, 1 },
386 { C::nullifier_check_should_silo, 0 },
387 { C::nullifier_check_nullifier, 27 },
388 { C::nullifier_check_siloed_nullifier, 27 },
389 },
390 });
391
392 check_relation<nullifier_check>(trace, nullifier_check::SR_PASSTHROUGH_SILOING);
393
394 trace.set(C::nullifier_check_siloed_nullifier, 1, 28);
395
397 "PASSTHROUGH_SILOING");
398}
399
400} // namespace
401} // 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)
#define NULLIFIER_TREE_HEIGHT
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
NoopEventEmitter< Poseidon2PermutationEvent > perm_event_emitter
NoopEventEmitter< Poseidon2PermutationMemoryEvent > perm_mem_event_emitter
NiceMock< MockGreaterThan > mock_gt
EventEmitter< Poseidon2HashEvent > hash_event_emitter
NullifierTreeLeafPreimage low_leaf
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessage)
Definition macros.hpp:7
AvmFlavorSettings::FF FF
TEST(TxExecutionConstrainingTest, WriteTreeValue)
Definition tx.test.cpp:508
crypto::Poseidon2< crypto::Poseidon2Bn254ScalarFieldParams > poseidon2
::bb::crypto::merkle_tree::NullifierLeafValue NullifierLeafValue
FF unconstrained_root_from_path(const FF &leaf_value, const uint64_t leaf_index, std::span< const FF > path)
Definition merkle.cpp:12
IndexedLeaf< NullifierLeafValue > NullifierTreeLeafPreimage
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
typename Flavor::FF FF
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::vector< fr > get_hash_inputs() const
NiceMock< MockFieldGreaterThan > field_gt