Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
permutation.test.cpp
Go to the documentation of this file.
4#include "ultra_honk.test.hpp"
5
6using namespace bb;
7
8#ifdef STARKNET_GARAGA_FLAVORS
9using FlavorTypes = testing::Types<UltraFlavor,
13 UltraStarknetFlavor,
14 UltraStarknetZKFlavor>;
15#else
16using FlavorTypes = testing::Types<UltraFlavor, UltraZKFlavor, UltraKeccakFlavor, UltraKeccakZKFlavor>;
17#endif
18template <typename T> using PermutationTests = UltraHonkTests<T>;
20using NonZKFlavorTypes = testing::Types<UltraFlavor, UltraKeccakFlavor>;
21template <typename T> using PermutationNonZKTests = UltraHonkTests<T>;
23
37TYPED_TEST(PermutationTests, NonTrivialTagPermutation)
38{
40
41 // Create two distinct values
43 fr y = -x;
44
45 // first multiset {x, y}
46 auto x1_idx = builder.add_variable(x);
47 auto y1_idx = builder.add_variable(y);
48
49 // second multiset {y, x}
50 auto y2_idx = builder.add_variable(y);
51 auto x2_idx = builder.add_variable(x);
52
53 // Dummy gates to include variables in the trace
54 builder.create_add_gate({ x1_idx, y1_idx, builder.zero_idx(), 1, 1, 0, 0 });
55 builder.create_add_gate({ y2_idx, x2_idx, builder.zero_idx(), 1, 1, 0, 0 });
56
57 // Set up tag transposition: first_tag <-> second_tag
58 auto first_tag = builder.get_new_tag();
59 auto second_tag = builder.get_new_tag();
60 builder.set_tau_transposition(first_tag, second_tag);
61
62 // Assign tags: first_tag -> {x, y}, second_tag -> {y, x}
63 builder.assign_tag(x1_idx, first_tag);
64 builder.assign_tag(y1_idx, first_tag);
65 builder.assign_tag(y2_idx, second_tag);
66 builder.assign_tag(x2_idx, second_tag);
67
68 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
69 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
70}
71
83TYPED_TEST(PermutationTests, NonTrivialGeneralizedPerm)
84{
86
88 fr y = -x;
89
90 // Helper to create a pair of equal variables (linked by copy constraint)
91 auto add_equal_pair = [&](fr value) {
92 auto idx1 = builder.add_variable(value);
93 auto idx2 = builder.add_variable(value);
94 builder.assert_equal(idx1, idx2);
95 return std::make_pair(idx1, idx2);
96 };
97
98 // Create pairs of equal variables
99 auto [x1_idx, x1_copy_idx] = add_equal_pair(x);
100 auto [y1_idx, y1_copy_idx] = add_equal_pair(y);
101 auto [x2_idx, x2_copy_idx] = add_equal_pair(x);
102 auto [y2_idx, y2_copy_idx] = add_equal_pair(y);
103
104 // Set up tag transposition for multiset-equality check
105 auto first_tag = builder.get_new_tag();
106 auto second_tag = builder.get_new_tag();
107 builder.set_tau_transposition(first_tag, second_tag);
108
109 // first_tag -> {x, y}, second_tag -> {x, y} (same multisets)
110 builder.assign_tag(x1_idx, first_tag);
111 builder.assign_tag(y1_idx, first_tag);
112 builder.assign_tag(x2_idx, second_tag);
113 builder.assign_tag(y2_idx, second_tag);
114
115 // Dummy gates using copy variables (z1 - z2 = 0)
116 builder.create_add_gate({ x1_copy_idx, x1_idx, builder.zero_idx(), 1, -1, 0, 0 });
117 builder.create_add_gate({ y1_idx, y2_idx, builder.zero_idx(), 1, -1, 0, 0 });
118 builder.create_add_gate({ x2_idx, x2_copy_idx, builder.zero_idx(), 1, -1, 0, 0 });
119
120 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
121 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
122}
123
135TYPED_TEST(PermutationTests, BadTagPermutation)
136{
137 // With tags: multisets {x, y} vs {y, x+1} are NOT equal, so verification should FAIL
138 {
140
142 fr y = -x;
143
144 // multiset: {x, y}
145 auto x1_idx = builder.add_variable(x);
146 auto y1_idx = builder.add_variable(y);
147
148 // multiset: {y, x+1}
149 auto y2_idx = builder.add_variable(y);
150 auto x_plus_1_idx = builder.add_variable(x + 1);
151
152 // Dummy gates: x + y = 0, y + (x+1) = 1
153 builder.create_add_gate({ x1_idx, y1_idx, builder.zero_idx(), 1, 1, 0, 0 });
154 builder.create_add_gate({ y2_idx, x_plus_1_idx, builder.zero_idx(), 1, 1, 0, -1 });
155
156 auto first_tag = builder.get_new_tag();
157 auto second_tag = builder.get_new_tag();
158 builder.set_tau_transposition(first_tag, second_tag);
159
160 builder.assign_tag(x1_idx, first_tag);
161 builder.assign_tag(y1_idx, first_tag);
162 builder.assign_tag(y2_idx, second_tag);
163 builder.assign_tag(x_plus_1_idx, second_tag);
164
165 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
166 TestFixture::prove_and_verify(builder, /*expected_result=*/false);
167 }
168 // Without tags: same circuit passes, confirming failure above is due to tag mismatch
169 {
171
173 fr y = -x;
174
175 auto x1_idx = builder.add_variable(x);
176 auto y1_idx = builder.add_variable(y);
177 auto y2_idx = builder.add_variable(y);
178 auto x_plus_1_idx = builder.add_variable(x + 1);
179
180 builder.create_add_gate({ x1_idx, y1_idx, builder.zero_idx(), 1, 1, 0, 0 });
181 builder.create_add_gate({ y2_idx, x_plus_1_idx, builder.zero_idx(), 1, 1, 0, -1 });
182
183 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
184 TestFixture::prove_and_verify(builder, /*expected_result=*/true);
185 }
186}
187
205TYPED_TEST(PermutationNonZKTests, ZPermZeroedOutFailure)
206{
207 using Flavor = TypeParam;
208 using Builder = typename Flavor::CircuitBuilder;
209
212
213 using Prover = TestFixture::Prover;
214
216
217 auto a = fr::random_element();
218 auto b = fr::random_element();
219 auto c = a + b;
220
221 uint32_t a_idx = builder.add_variable(a);
222 uint32_t a_copy_idx = builder.add_variable(a);
223 uint32_t b_idx = builder.add_variable(b);
224 uint32_t c_idx = builder.add_variable(c);
225
226 builder.create_add_gate({ a_idx, b_idx, c_idx, 1, 1, -1, 0 });
227 builder.create_add_gate({ a_copy_idx, b_idx, c_idx, 1, 1, -1, 0 });
228 builder.assert_equal(a_copy_idx, a_idx);
229
230 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
231
232 auto prover_instance = std::make_shared<ProverInstance>(builder);
233 auto verification_key = std::make_shared<VerificationKey>(prover_instance->get_precomputed());
234
235 Prover prover(prover_instance, verification_key);
236 auto proof = prover.construct_proof();
237 auto& z_perm = prover_instance->polynomials.z_perm;
238
239 // First verify that the Permutation relation holds.
240 auto permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
241 prover_instance->polynomials, prover_instance->relation_parameters, "UltraPermutation - Before Tampering");
242 EXPECT_TRUE(permutation_relation_failures.empty());
243
244 // Tamper: zero-out z_perm
245 for (size_t i = z_perm.start_index(); i < z_perm.end_index(); ++i) {
246 z_perm.at(i) = fr(0);
247 }
248 prover_instance->polynomials.set_shifted();
249 auto tampered_permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
250 prover_instance->polynomials,
251 prover_instance->relation_parameters,
252 "UltraPermutation - After zeroing out z_perm");
253 // Verify that the Permutation relation now fails
254 EXPECT_FALSE(tampered_permutation_relation_failures.empty());
255}
256
271TYPED_TEST(PermutationNonZKTests, ZPermShiftNotZeroAtLagrangeLastFailure)
272{
273 using Flavor = TypeParam;
274 using Builder = typename Flavor::CircuitBuilder;
275
278
279 using Prover = TestFixture::Prover;
280
282
283 auto a = fr::random_element();
284 auto b = fr::random_element();
285 auto c = a + b;
286
287 uint32_t a_idx = builder.add_variable(a);
288 uint32_t a_copy_idx = builder.add_variable(a);
289 uint32_t b_idx = builder.add_variable(b);
290 uint32_t c_idx = builder.add_variable(c);
291
292 builder.create_add_gate({ a_idx, b_idx, c_idx, 1, 1, -1, 0 });
293 builder.create_add_gate({ a_copy_idx, b_idx, c_idx, 1, 1, -1, 0 });
294 builder.assert_equal(a_copy_idx, a_idx);
295
296 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
297
298 auto prover_instance = std::make_shared<ProverInstance>(builder);
299 auto verification_key = std::make_shared<VerificationKey>(prover_instance->get_precomputed());
300
301 Prover prover(prover_instance, verification_key);
302 auto proof = prover.construct_proof();
303
304 // first verify that the Permutation relation holds.
305 auto permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
306 prover_instance->polynomials, prover_instance->relation_parameters, "UltraPermutation - Before Tampering");
307 EXPECT_TRUE(permutation_relation_failures.empty());
308 // we make z_perm and z_perm_shift full polynomials to tamper with values that are outside the usual allocated
309 // range. This allows us to failure test for the subrelation `z_perm_shift * lagrange_last == 0`.
310 auto& z_perm = prover_instance->polynomials.z_perm;
311 auto last_valid_index = z_perm.end_index();
312 auto& z_perm_shift = prover_instance->polynomials.z_perm_shift;
313 // make the polynomial full to tamper with a last value.
314 prover_instance->polynomials.z_perm = z_perm.full();
315 prover_instance->polynomials.z_perm_shift = z_perm_shift.full();
316
317 ASSERT_EQ(prover_instance->polynomials.lagrange_last.at(last_valid_index - 1), fr(1));
318 ASSERT_EQ(prover_instance->polynomials.z_perm.at(last_valid_index), fr(0));
319 ASSERT_EQ(prover_instance->polynomials.z_perm_shift.at(last_valid_index - 1), fr(0));
320 // Tamper: change `z_perm_shift` to something non-zero when `lagrange_last == 1`.
321 prover_instance->polynomials.z_perm_shift.at(last_valid_index - 1) += fr(1);
322 // Note that `z_perm_shift` and `z_perm` are no longer inextricably linked because we have replaced them by their
323 // full incarnations. Therefore, we still `z_perm.at(last_valid_index) == 0`. This does not effect the test we
324 // wish to check.
325
326 // Verify that the Permutation relation now fails.
327 auto tampered_permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
328 prover_instance->polynomials,
329 prover_instance->relation_parameters,
330 "UltraPermutation - After incrementing z_perm_shift where lagrange_last is 1");
331 EXPECT_FALSE(tampered_permutation_relation_failures.empty());
332 // the first subrelation first fails at `row_idx == last_valid_index - 1`.
333 ASSERT_EQ(tampered_permutation_relation_failures[1], last_valid_index - 1);
334}
335
347TYPED_TEST(PermutationNonZKTests, SigmaCorruptionFailure)
348{
349 using Flavor = TypeParam;
352 using Prover = typename TestFixture::Prover;
353
355
356 // Create variables with a copy constraint
357 auto a = fr::random_element();
358 auto b = fr::random_element();
359 auto c = a + b;
360
361 uint32_t a_idx = builder.add_variable(a);
362 uint32_t a_copy_idx = builder.add_variable(a);
363 uint32_t b_idx = builder.add_variable(b);
364 uint32_t c_idx = builder.add_variable(c);
365
366 // Gates using a_idx and a_copy_idx (which should be equal)
367 builder.create_add_gate({ a_idx, b_idx, c_idx, 1, 1, -1, 0 });
368 builder.create_add_gate({ a_copy_idx, b_idx, c_idx, 1, 1, -1, 0 });
369 // copy cycle we'll break
370 builder.assert_equal(a_copy_idx, a_idx);
371
372 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
373
374 auto prover_instance = std::make_shared<ProverInstance>(builder);
375 auto verification_key = std::make_shared<VerificationKey>(prover_instance->get_precomputed());
376
377 // Construct proof to compute z_perm
378 Prover prover(prover_instance, verification_key);
379 auto proof = prover.construct_proof();
380
381 // The Permutation relation holds before tampering
382 auto permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
383 prover_instance->polynomials, prover_instance->relation_parameters, "Permutation Relation - Before Tampering");
384 ASSERT_TRUE(permutation_relation_failures.empty());
385
386 // TAMPER
387
388 // Corrupt sigma_1 at a row that's part of the copy cycle.
389 auto& sigma_1 = prover_instance->polynomials.sigma_1;
390 auto& id_1 = prover_instance->polynomials.id_1;
391
392 // Find the first row that's part of a non-trivial cycle (sigma != id)
393 size_t row_to_corrupt = 0;
394 for (size_t row = 1; row < sigma_1.size(); ++row) {
395 if (sigma_1.at(row) != id_1.at(row)) {
396 row_to_corrupt = row;
397 vinfo("Found copy cycle at row ", row, "; will corrupt this one");
398 break;
399 }
400 }
401 ASSERT_NE(row_to_corrupt, 0) << "No copy cycle found in sigma_1!";
402
403 fr original_value = sigma_1.at(row_to_corrupt);
404 sigma_1.at(row_to_corrupt) = original_value + fr(1); // Break the cycle by pointing elsewhere
405 // We perform two distinct tests.
406 //
407 // Failure test 1: make sure that the subrelation fails when when we change sigma_1 without updating z_perm.
408 {
409 auto failures_of_tampered_instance = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
410 prover_instance->polynomials,
411 prover_instance->relation_parameters,
412 "Permutation Relation - After corrupting sigma_1");
413
414 ASSERT_TRUE(failures_of_tampered_instance.at(0));
415 }
416 // Failure test 2: make sure the subrelation fails when we DO update z_perm
417 {
418 // Sanity check: if we are recompute z_perm, the first different value will be at `row_to_corrupt + 1`. Store
419 // this to ensure that this value indeed changes.
420 auto& z_perm = prover_instance->polynomials.z_perm;
421 auto z_perm_before = z_perm.at(row_to_corrupt + 1);
422
423 // Recompute z_perm with the corrupted sigma.
424 size_t real_circuit_size = prover_instance->get_final_active_wire_idx() + 1;
425 compute_grand_product<Flavor, UltraPermutationRelation<fr>>(
426 prover_instance->polynomials, prover_instance->relation_parameters, real_circuit_size);
427 prover_instance->polynomials.set_shifted(); // Refresh z_perm_shift
428
429 // Verify z_perm actually changed after recomputation with corrupted sigma
430 auto z_perm_after = z_perm.at(row_to_corrupt + 1);
431 ASSERT_NE(z_perm_before, z_perm_after) << "z_perm should change after recomputing with corrupted sigma";
432
433 // After recomputing z_perm, we expect a failure at the very last active wire.
434 auto failures_of_tampered_instance = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
435 prover_instance->polynomials,
436 prover_instance->relation_parameters,
437 "Permutation Relation - After corrupting sigma_1 and recomputing z_perm");
438
439 ASSERT_EQ(failures_of_tampered_instance.at(0), real_circuit_size - 1)
440 << "Expected failure at row " << (real_circuit_size - 1) << " (the recomputation boundary)";
441 }
442}
443
454TYPED_TEST(PermutationNonZKTests, PublicInputDeltaMismatch)
455{
456 using Flavor = TypeParam;
459 using Prover = typename TestFixture::Prover;
460
462
463 // Add a public input
464 fr public_value = fr(314159);
465 auto pub_var = builder.add_public_variable(public_value);
466
467 // Use the public input in a simple constraint so it appears in the trace
468 auto private_val = fr::random_element();
469 auto private_var = builder.add_variable(private_val);
470 auto result_var = builder.add_variable(public_value + private_val);
471 builder.create_add_gate({ pub_var, private_var, result_var, 1, 1, -1, 0 });
472
473 TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(builder);
474
475 auto prover_instance = std::make_shared<ProverInstance>(builder);
476 auto verification_key = std::make_shared<VerificationKey>(prover_instance->get_precomputed());
477
478 // Construct proof to compute z_perm (with correct public_input_delta)
479 Prover prover(prover_instance, verification_key);
480 auto proof = prover.construct_proof();
481
482 // Verify the permutation relation holds before tampering
483 auto permutation_relation_failures = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
484 prover_instance->polynomials, prover_instance->relation_parameters, "Permutation Relation - Before Tampering");
485 ASSERT_TRUE(permutation_relation_failures.empty());
486
487 // Store the original public_input_delta
488 fr original_delta = prover_instance->relation_parameters.public_input_delta;
489
490 // TAMPER
491
492 // Recompute public_input_delta with a different public input value
493 // The wire polynomials still contain the original value (314159), but we compute delta as if it were 99999
494 fr tampered_public_val = fr(99999);
495 std::vector<fr> tampered_public_inputs = { tampered_public_val };
496 fr tampered_delta = compute_public_input_delta<Flavor>(tampered_public_inputs,
497 prover_instance->relation_parameters.beta,
498 prover_instance->relation_parameters.gamma,
499 prover_instance->pub_inputs_offset());
500
501 // Sanity check: the tampered delta should differ from the original
502 ASSERT_NE(original_delta, tampered_delta) << "Tampered delta should differ from original";
503
504 // Apply the tampered delta
505 prover_instance->relation_parameters.public_input_delta = tampered_delta;
506
507 // Verify the permutation relation now fails
508 auto failures_of_tampered_instance = RelationChecker<Flavor>::template check<UltraPermutationRelation<fr>>(
509 prover_instance->polynomials,
510 prover_instance->relation_parameters,
511 "Permutation Relation - After tampering with public_input_delta");
512 // The failure should be in subrelation 0 (the recurrence relation) since z_perm was computed with
513 // a different public_input_delta than what we're now using in the relation check
514 ASSERT_TRUE(failures_of_tampered_instance.contains(0)) << "Expected subrelation 0 to fail";
515 size_t final_active_wire_idx = prover_instance->get_final_active_wire_idx();
516 ASSERT_TRUE(failures_of_tampered_instance.at(0) == final_active_wire_idx);
517}
ECCVMCircuitBuilder CircuitBuilder
FixedVKAndHash_< PrecomputedEntities< Commitment >, BF, ECCVMHardcodedVKAndHash > VerificationKey
The verification key stores commitments to the precomputed polynomials used by the verifier.
Base Native verification key class.
Definition flavor.hpp:172
A ProverInstance is normally constructed from a finalized circuit and it contains all the information...
A debugging utility for checking whether a set of polynomials satisfies the relations for a given Fla...
Child class of UltraFlavor that runs with ZK Sumcheck.
#define vinfo(...)
Definition log.hpp:94
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
testing::Types< UltraFlavor, UltraKeccakFlavor, MegaFlavor > FlavorTypes
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:174
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
testing::Types< UltraFlavor, UltraKeccakFlavor > NonZKFlavorTypes
static field random_element(numeric::RNG *engine=nullptr) noexcept