Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
relation_failure.test.cpp
Go to the documentation of this file.
1
45#include <gtest/gtest.h>
46
47using namespace bb;
48
49namespace {
50
52using FF = typename Flavor::FF;
54
58struct ValidTranslatorState {
61};
62
70ValidTranslatorState build_valid_translator_state()
71{
72 const size_t full_circuit_size = Flavor::MINI_CIRCUIT_SIZE * Flavor::CONCATENATION_GROUP_SIZE;
74
77 ProverPolynomials& pp = key.proving_key->polynomials;
78
79 // Fill group wire polynomials with random 14-bit values in circuit region, random FF in masking rows
80 for (const auto& group : pp.get_groups_to_be_concatenated()) {
81 for (auto& poly : group) {
82 if (poly.is_empty()) {
83 continue;
84 }
85 for (size_t i = poly.start_index(); i < poly.end_index() - NUM_DISABLED_ROWS_IN_SUMCHECK; i++) {
86 poly.at(i) = engine.get_random_uint16() & ((1 << Flavor::MICRO_LIMB_BITS) - 1);
87 }
88 for (size_t i = poly.end_index() - NUM_DISABLED_ROWS_IN_SUMCHECK; i < poly.end_index(); i++) {
89 poly.at(i) = FF::random_element();
90 }
91 }
92 }
93
94 // Reallocate lagrange polynomials to full circuit size and compute them
95 pp.lagrange_first = typename Flavor::Polynomial(full_circuit_size);
96 pp.lagrange_last = typename Flavor::Polynomial(full_circuit_size);
97 pp.lagrange_real_last = typename Flavor::Polynomial(full_circuit_size);
98 pp.lagrange_masking = typename Flavor::Polynomial(full_circuit_size);
99
100 key.compute_lagrange_polynomials();
101 key.compute_extra_range_constraint_numerator();
102 key.compute_concatenated_polynomials();
103 key.compute_translator_range_constraint_ordered_polynomials();
104
105 // Compute grand product
107 compute_grand_product<Flavor, TranslatorPermutationRelation<FF>>(pp, params);
108
109 return { std::move(key), params };
110}
111
120ValidTranslatorState build_valid_accumulator_transfer_state()
121{
122 using BF = typename Flavor::BF;
123 using GroupElement = typename Flavor::GroupElement;
124
126
127 auto op_queue = std::make_shared<ECCOpQueue>();
128 op_queue->construct_zk_columns();
129
130 // Add mixed ops, merge, more mixed ops, random end ops, final merge
131 for (size_t i = 0; i < 50; i++) {
132 op_queue->add_accumulate(GroupElement::random_element(&engine));
133 op_queue->mul_accumulate(GroupElement::random_element(&engine), FF::random_element(&engine));
134 }
135 op_queue->eq_and_reset();
136 op_queue->merge();
137 for (size_t i = 0; i < 50; i++) {
138 op_queue->add_accumulate(GroupElement::random_element(&engine));
139 op_queue->mul_accumulate(GroupElement::random_element(&engine), FF::random_element(&engine));
140 }
141 op_queue->eq_and_reset();
142 for (size_t i = 0; i < Flavor::CircuitBuilder::NUM_RANDOM_OPS_END; i++) {
143 op_queue->random_op_ultra_only();
144 }
145 op_queue->merge_fixed_append(op_queue->get_append_offset());
146
147 const auto batching_challenge_v = BF::random_element(&engine);
148 const auto evaluation_input_x = BF::random_element(&engine);
149
150 auto circuit_builder = Flavor::CircuitBuilder(batching_challenge_v, evaluation_input_x, op_queue);
151 TranslatorProvingKey key(circuit_builder);
152
153 // Read accumulated_result from the witness (same as the prover does)
154 auto& pp = key.proving_key->polynomials;
156 params.accumulated_result = { pp.accumulators_binary_limbs_0[Flavor::RESULT_ROW],
157 pp.accumulators_binary_limbs_1[Flavor::RESULT_ROW],
158 pp.accumulators_binary_limbs_2[Flavor::RESULT_ROW],
159 pp.accumulators_binary_limbs_3[Flavor::RESULT_ROW] };
160
161 // Populate evaluation_input_x and batching_challenge_v limbs + native values
162 // (needed by TranslatorNonNativeFieldRelation; harmless for other relations)
163 static constexpr size_t NUM_LIMB_BITS = Flavor::CircuitBuilder::NUM_LIMB_BITS;
164 auto uint_input_x = uint256_t(evaluation_input_x);
165 params.evaluation_input_x = { uint_input_x.slice(0, NUM_LIMB_BITS),
166 uint_input_x.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2),
167 uint_input_x.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3),
168 uint_input_x.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4),
169 uint_input_x };
170 auto v_power = BF::one();
171 for (size_t i = 0; i < 4; i++) {
172 v_power *= batching_challenge_v;
173 auto uint_v_power = uint256_t(v_power);
174 params.batching_challenge_v.at(i) = { uint_v_power.slice(0, NUM_LIMB_BITS),
175 uint_v_power.slice(NUM_LIMB_BITS, NUM_LIMB_BITS * 2),
176 uint_v_power.slice(NUM_LIMB_BITS * 2, NUM_LIMB_BITS * 3),
177 uint_v_power.slice(NUM_LIMB_BITS * 3, NUM_LIMB_BITS * 4),
178 uint_v_power };
179 }
180
181 return { std::move(key), params };
182}
183
184} // anonymous namespace
185
186class TranslatorRelationFailureTests : public ::testing::Test {
187 protected:
189};
190
195TEST_F(TranslatorRelationFailureTests, PermutationFailsOnConcatenatedCorruption)
196{
197 auto [key, params] = build_valid_translator_state();
198 auto& pp = key.proving_key->polynomials;
199
200 // Baseline: permutation relation passes
201 auto baseline =
202 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
203 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
204
205 // Corrupt a non-masking position in block 1 of concatenated_range_constraints_0
206 // Block 1 starts at MINI_CIRCUIT_SIZE, position 1 within it is non-masking (start_index=1)
207 const size_t corrupt_pos = Flavor::MINI_CIRCUIT_SIZE + 1;
208 pp.concatenated_range_constraints_0.at(corrupt_pos) = FF::random_element();
209
210 // Re-compute grand product with corrupted data
211 compute_grand_product<Flavor, TranslatorPermutationRelation<FF>>(pp, params);
212
213 auto failures =
214 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
215 EXPECT_FALSE(failures.empty()) << "Permutation should fail after concatenated corruption";
216}
217
222TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnMaxValueCorruption)
223{
224 auto [key, params] = build_valid_translator_state();
225 auto& pp = key.proving_key->polynomials;
226
227 const size_t full_circuit_size = Flavor::MINI_CIRCUIT_SIZE * Flavor::CONCATENATION_GROUP_SIZE;
228
229 // Baseline: delta range passes
231 pp, params, "TranslatorDeltaRangeConstraintRelation");
232 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
233
234 // The real_last position must hold exactly 2^14 - 1. Corrupt it to something else.
235 const size_t real_last_pos = full_circuit_size - Flavor::MAX_RANDOM_VALUES_PER_ORDERED - 1;
236 pp.ordered_range_constraints_0.at(real_last_pos) = FF(42);
237
239 pp, params, "TranslatorDeltaRangeConstraintRelation");
240 EXPECT_FALSE(failures.empty()) << "Delta range should fail when real_last position != 2^14 - 1";
241}
242
246TEST_F(TranslatorRelationFailureTests, PermutationFailsOnZPermCorruption)
247{
248 auto [key, params] = build_valid_translator_state();
249 auto& pp = key.proving_key->polynomials;
250
251 // Baseline: permutation relation passes
252 auto baseline =
253 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
254 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
255
256 // Corrupt z_perm at a position in the middle of the circuit
257 const size_t corrupt_pos = (Flavor::MINI_CIRCUIT_SIZE * 2) + 500;
258 pp.z_perm.at(corrupt_pos) = FF::random_element();
259 // Must also update the shifted view
260 pp.set_shifted();
261
262 auto failures =
263 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
264 EXPECT_FALSE(failures.empty()) << "Permutation should fail after z_perm corruption";
265}
266
277TEST_F(TranslatorRelationFailureTests, PermutationFailsOnZPermNonZeroAtFirstRow)
278{
279 auto [key, params] = build_valid_translator_state();
280 auto& pp = key.proving_key->polynomials;
281
282 // Baseline: permutation relation passes
283 auto baseline =
284 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
285 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
286
287 // Derive expected lagrange_first position from z_perm shiftable structure
288 ASSERT_TRUE(pp.z_perm.is_shiftable());
289 size_t structural_first_row = pp.z_perm.start_index() - 1;
290
291 // Independently scan lagrange_first for its non-zero entry
292 const auto& lagrange_first = pp.lagrange_first;
293 size_t scanned_first_row = 0;
294 bool found = false;
295 for (size_t i = lagrange_first.start_index(); i < lagrange_first.end_index(); ++i) {
296 if (lagrange_first[i] != FF(0)) {
297 scanned_first_row = i;
298 found = true;
299 break;
300 }
301 }
302 ASSERT_TRUE(found) << "lagrange_first has no non-zero entry";
303 ASSERT_EQ(structural_first_row, scanned_first_row)
304 << "lagrange_first position doesn't match z_perm shiftable structure";
305
306 const size_t first_row = scanned_first_row;
307
308 // Expand to full polynomials so we can write at the zero row
309 pp.z_perm = pp.z_perm.full();
310 pp.z_perm_shift = pp.z_perm_shift.full();
311
312 ASSERT_EQ(pp.z_perm[first_row], FF(0));
313
314 // Tamper: set z_perm to non-zero where lagrange_first is active
315 pp.z_perm.at(first_row) = FF(1);
316
318 pp, params, "TranslatorPermutationRelation - After setting z_perm != 0 at lagrange_first");
319 EXPECT_FALSE(failures.empty()) << "Permutation should fail after z_perm init corruption";
320 // Sub-relation 2 (lagrange_first * z_perm = 0) should catch this
321 EXPECT_TRUE(failures.contains(2)) << "Sub-relation 2 (z_perm init) should catch the corruption";
322 EXPECT_EQ(failures.at(2), static_cast<uint32_t>(first_row)) << "Failure should be at lagrange_first row";
323}
324
333TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnOrderedMaskingBoundary)
334{
335 auto [key, params] = build_valid_translator_state();
336 auto& pp = key.proving_key->polynomials;
337
338 const size_t full_circuit_size = Flavor::MINI_CIRCUIT_SIZE * Flavor::CONCATENATION_GROUP_SIZE;
339
340 // Baseline: delta range passes
342 pp, params, "TranslatorDeltaRangeConstraintRelation");
343 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
344
345 // Position circuit_size - MAX_RANDOM - 2 is the last row with delta enforcement active.
346 // The next row (real_last) holds 2^14 - 1. Setting this to 0 creates delta = 16383.
347 const size_t boundary_pos = full_circuit_size - Flavor::MAX_RANDOM_VALUES_PER_ORDERED - 2;
348 pp.ordered_range_constraints_0.at(boundary_pos) = FF(0);
349
351 pp, params, "TranslatorDeltaRangeConstraintRelation");
352 EXPECT_FALSE(failures.empty()) << "Delta range should fail at the masking boundary";
353}
354
359TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnNegativeDelta)
360{
361 auto [key, params] = build_valid_translator_state();
362 auto& pp = key.proving_key->polynomials;
363
364 // Baseline: delta range passes
366 pp, params, "TranslatorDeltaRangeConstraintRelation");
367 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
368
369 // Set ordered[pos] to be larger than ordered[pos+1], creating a negative delta at row pos.
370 // D = ordered[pos+1] - ordered[pos] becomes a huge field element (negative in the integers),
371 // which is not in {0,1,2,3}.
372 const size_t pos = 5000;
373 pp.ordered_range_constraints_0.at(pos) = pp.ordered_range_constraints_0[pos + 1] + FF(1);
374
376 pp, params, "TranslatorDeltaRangeConstraintRelation");
377 EXPECT_FALSE(failures.empty()) << "Delta range should fail on descending (negative delta) pair";
378}
379
384TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnDeltaFour)
385{
386 auto [key, params] = build_valid_translator_state();
387 auto& pp = key.proving_key->polynomials;
388
389 // Baseline: delta range passes
391 pp, params, "TranslatorDeltaRangeConstraintRelation");
392 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
393
394 // Pick a mid-range position and set it so the delta to the next row is exactly 4
395 const size_t pos = 3000;
396 FF next_val = pp.ordered_range_constraints_0[pos + 1];
397 pp.ordered_range_constraints_0.at(pos) = next_val - FF(4);
398
400 pp, params, "TranslatorDeltaRangeConstraintRelation");
401 EXPECT_FALSE(failures.empty()) << "Delta range should fail when delta is exactly 4";
402}
403
408TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnFirstSortedValueTooLarge)
409{
410 auto [key, params] = build_valid_translator_state();
411 auto& pp = key.proving_key->polynomials;
412
413 // Baseline: delta range passes
415 pp, params, "TranslatorDeltaRangeConstraintRelation");
416 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
417
418 // Position 0 is virtual zero (always 0). Position 1 is the first sorted value.
419 // Setting it to 100 creates delta = 100 from row 0, which violates D ∈ {0,1,2,3}.
420 pp.ordered_range_constraints_0.at(1) = FF(100);
421
423 pp, params, "TranslatorDeltaRangeConstraintRelation");
424 EXPECT_FALSE(failures.empty()) << "Delta range should fail when first sorted value > 3";
425}
426
431TEST_F(TranslatorRelationFailureTests, DeltaRangeFailsOnFifthOrderedPolyCorruption)
432{
433 auto [key, params] = build_valid_translator_state();
434 auto& pp = key.proving_key->polynomials;
435
436 // Baseline: delta range passes
438 pp, params, "TranslatorDeltaRangeConstraintRelation");
439 EXPECT_TRUE(baseline.empty()) << "Baseline delta range should pass";
440
441 // Corrupt ordered_range_constraints_4 at a mid position
442 const size_t pos = 2000;
443 pp.ordered_range_constraints_4.at(pos) = pp.ordered_range_constraints_4[pos - 1] + FF(100);
444
446 pp, params, "TranslatorDeltaRangeConstraintRelation");
447 EXPECT_FALSE(failures.empty()) << "Delta range should fail on 5th ordered poly corruption";
448}
449
454TEST_F(TranslatorRelationFailureTests, PermutationFailsOnOrderedCorruption)
455{
456 auto [key, params] = build_valid_translator_state();
457 auto& pp = key.proving_key->polynomials;
458
459 // Baseline: permutation relation passes
460 auto baseline =
461 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
462 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
463
464 // Corrupt a non-masking position in ordered_range_constraints_0 without recomputing z_perm
465 const size_t corrupt_pos = 500;
466 pp.ordered_range_constraints_0.at(corrupt_pos) = FF::random_element();
467
468 auto failures =
469 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
470 EXPECT_FALSE(failures.empty()) << "Permutation should fail after ordered poly corruption";
471}
472
481TEST_F(TranslatorRelationFailureTests, InRangeValueInMaskingFlowsToOrderedTail)
482{
483 const size_t full_circuit_size = Flavor::MINI_CIRCUIT_SIZE * Flavor::CONCATENATION_GROUP_SIZE;
485
488 ProverPolynomials& pp = key.proving_key->polynomials;
489
490 // Fill wire polynomials with random 14-bit values (circuit) and random FF (masking)
491 for (const auto& group : pp.get_groups_to_be_concatenated()) {
492 for (auto& poly : group) {
493 if (poly.is_empty()) {
494 continue;
495 }
496 for (size_t i = poly.start_index(); i < poly.end_index() - NUM_DISABLED_ROWS_IN_SUMCHECK; i++) {
497 poly.at(i) = engine.get_random_uint16() & ((1 << Flavor::MICRO_LIMB_BITS) - 1);
498 }
499 for (size_t i = poly.end_index() - NUM_DISABLED_ROWS_IN_SUMCHECK; i < poly.end_index(); i++) {
500 poly.at(i) = FF::random_element();
501 }
502 }
503 }
504
505 // Place a known small in-range value at the first masking position of group[0][0]
506 // (p_x_low_limbs_range_constraint_0, block 0)
507 const FF sentinel(42);
508 auto groups = pp.get_groups_to_be_concatenated();
509 auto& target_wire = groups[0][0];
510 const size_t wire_masking_start = target_wire.end_index() - NUM_DISABLED_ROWS_IN_SUMCHECK;
511 target_wire.at(wire_masking_start) = sentinel;
512
513 // Reallocate lagrange polynomials
514 pp.lagrange_first = typename Flavor::Polynomial(full_circuit_size);
515 pp.lagrange_last = typename Flavor::Polynomial(full_circuit_size);
516 pp.lagrange_real_last = typename Flavor::Polynomial(full_circuit_size);
517 pp.lagrange_masking = typename Flavor::Polynomial(full_circuit_size);
518
519 key.compute_lagrange_polynomials();
520 key.compute_extra_range_constraint_numerator();
521 key.compute_concatenated_polynomials();
522
523 // After concatenation: group[0][0] maps to block 0 of concatenated_range_constraints_0
524 // Masking position in concatenated poly = 0 * MINI + wire_masking_start
525 const size_t concat_masking_pos = wire_masking_start; // block 0, so offset is 0
526 EXPECT_EQ(pp.concatenated_range_constraints_0[concat_masking_pos], sentinel)
527 << "Sentinel should appear at the correct concatenated position";
528
529 key.compute_translator_range_constraint_ordered_polynomials();
530
531 // The sentinel should now be in the contiguous masking tail of one of the ordered polys.
532 // split_concatenated_random_coefficients_to_ordered extracts from concat[0..3] in order:
533 // concat 0, block 0, rows [MINI-4, MINI) → first 4 values in random_values[]
534 // Our sentinel is random_values[0] (first extracted value from concat 0, block 0, first masking row).
535 //
536 // Distribution: 256 total values across 5 ordered polys.
537 // ordered[0] gets 52 values (256/5=51, remainder 1 → first poly gets +1).
538 // random_values[0] → ordered[0] at position circuit_size - 52.
539 bool found = false;
540 for (const auto& ord_poly : pp.get_ordered_range_constraints()) {
541 for (size_t pos = full_circuit_size - Flavor::MAX_RANDOM_VALUES_PER_ORDERED; pos < full_circuit_size; pos++) {
542 if (ord_poly[pos] == sentinel) {
543 found = true;
544 break;
545 }
546 }
547 if (found) {
548 break;
549 }
550 }
551 EXPECT_TRUE(found) << "Sentinel value 42 should appear in the ordered poly masking tail";
552
553 // Verify all relations still pass with an in-range value in the masking position
555 compute_grand_product<Flavor, TranslatorPermutationRelation<FF>>(pp, params);
556
557 auto perm_failures =
558 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
559 EXPECT_TRUE(perm_failures.empty()) << "Permutation should pass with in-range masking value";
560
562 pp, params, "TranslatorDeltaRangeConstraintRelation");
563 EXPECT_TRUE(delta_failures.empty()) << "Delta range should pass with in-range masking value";
564}
565
570TEST_F(TranslatorRelationFailureTests, PermutationFailsOnExtraNumeratorCorruption)
571{
572 auto [key, params] = build_valid_translator_state();
573 auto& pp = key.proving_key->polynomials;
574
575 // Baseline: permutation relation passes
576 auto baseline =
577 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
578 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
579
580 // Corrupt a value in the extra range constraint numerator without recomputing z_perm
581 const size_t corrupt_pos = 5;
582 pp.ordered_extra_range_constraints_numerator.at(corrupt_pos) = FF::random_element();
583
584 auto failures =
585 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
586 EXPECT_FALSE(failures.empty()) << "Permutation should fail after extra numerator corruption";
587}
588
589// ======================== Accumulator Transfer Relation ========================
590
595TEST_F(TranslatorRelationFailureTests, AccumulatorTransferFailsOnOddRowCorruption)
596{
597 auto [key, params] = build_valid_accumulator_transfer_state();
598 auto& pp = key.proving_key->polynomials;
599
600 // Baseline: accumulator transfer passes with real Horner-scheme accumulator values
602 pp, params, "TranslatorAccumulatorTransferRelation");
603 EXPECT_TRUE(baseline.empty()) << "Baseline accumulator transfer should pass";
604
605 // Corrupt accumulators_binary_limbs_0 at an interior odd row.
606 // Transfer checks acc[101] == acc[102]. Corrupting acc[101] breaks this.
607 pp.accumulators_binary_limbs_0.at(101) = FF::random_element();
608
610 pp, params, "TranslatorAccumulatorTransferRelation");
611 EXPECT_FALSE(failures.empty()) << "Accumulator transfer should fail after odd row corruption";
612}
613
618TEST_F(TranslatorRelationFailureTests, AccumulatorTransferFailsOnZeroInitCorruption)
619{
620 auto [key, params] = build_valid_accumulator_transfer_state();
621 auto& pp = key.proving_key->polynomials;
622
624 pp, params, "TranslatorAccumulatorTransferRelation");
625 EXPECT_TRUE(baseline.empty()) << "Baseline accumulator transfer should pass";
626
627 // Corrupt: set non-zero accumulator at the last minicircuit row (zero-init position 8187)
628 const size_t last_in_minicircuit = Flavor::MINI_CIRCUIT_SIZE - Flavor::NUM_MASKED_ROWS_END - 1;
629 pp.accumulators_binary_limbs_0.at(last_in_minicircuit) = FF(1);
630
632 pp, params, "TranslatorAccumulatorTransferRelation");
633 EXPECT_FALSE(failures.empty()) << "Accumulator transfer should fail when zero-init position is non-zero";
634}
635
640TEST_F(TranslatorRelationFailureTests, AccumulatorTransferFailsOnResultMismatch)
641{
642 auto [key, params] = build_valid_accumulator_transfer_state();
643 auto& pp = key.proving_key->polynomials;
644
646 pp, params, "TranslatorAccumulatorTransferRelation");
647 EXPECT_TRUE(baseline.empty()) << "Baseline accumulator transfer should pass";
648
649 // Perturb accumulated_result so it no longer matches the witness at RESULT_ROW
650 params.accumulated_result[0] += FF(1);
651
653 pp, params, "TranslatorAccumulatorTransferRelation");
654 EXPECT_FALSE(failures.empty()) << "Accumulator transfer should fail on result mismatch";
655}
656
667TEST_F(TranslatorRelationFailureTests, AccumulatorTransferPassesWithMaskingRegionValues)
668{
669 auto [key, params] = build_valid_accumulator_transfer_state();
670 auto& pp = key.proving_key->polynomials;
671
672 // Place non-zero values at start masking positions [RANDOMNESS_START, RESULT_ROW) = [2, 8)
673 for (size_t i = Flavor::RANDOMNESS_START; i < Flavor::RESULT_ROW; i++) {
674 pp.accumulators_binary_limbs_0.at(i) = FF::random_element();
675 pp.accumulators_binary_limbs_1.at(i) = FF::random_element();
676 pp.accumulators_binary_limbs_2.at(i) = FF::random_element();
677 pp.accumulators_binary_limbs_3.at(i) = FF::random_element();
678 }
679
680 // Place non-zero values at end masking positions [MINI - NUM_MASKED, MINI) = [8188, 8192)
681 const size_t end_mask_start = Flavor::MINI_CIRCUIT_SIZE - Flavor::NUM_MASKED_ROWS_END;
682 for (size_t i = end_mask_start; i < Flavor::MINI_CIRCUIT_SIZE; i++) {
683 pp.accumulators_binary_limbs_0.at(i) = FF::random_element();
684 pp.accumulators_binary_limbs_1.at(i) = FF::random_element();
685 pp.accumulators_binary_limbs_2.at(i) = FF::random_element();
686 pp.accumulators_binary_limbs_3.at(i) = FF::random_element();
687 }
688
689 // Relation should still pass — masking regions are excluded by selectors
691 pp, params, "TranslatorAccumulatorTransferRelation");
692 EXPECT_TRUE(failures.empty()) << "Accumulator transfer should pass even with arbitrary masking region values";
693}
694
699TEST_F(TranslatorRelationFailureTests, AccumulatorTransferFailsAtFirstTransferRow)
700{
701 auto [key, params] = build_valid_accumulator_transfer_state();
702 auto& pp = key.proving_key->polynomials;
703
705 pp, params, "TranslatorAccumulatorTransferRelation");
706 EXPECT_TRUE(baseline.empty()) << "Baseline accumulator transfer should pass";
707
708 // Row 9 is the first odd row where lagrange_odd_in_minicircuit = 1.
709 // Transfer checks acc[9] == acc[10]. Corrupting acc[9] breaks this.
710 const size_t first_transfer_row = Flavor::RESULT_ROW + 1;
711 pp.accumulators_binary_limbs_0.at(first_transfer_row) = FF::random_element();
712
714 pp, params, "TranslatorAccumulatorTransferRelation");
715 EXPECT_FALSE(failures.empty()) << "Accumulator transfer should fail at first transfer row";
716}
717
723TEST_F(TranslatorRelationFailureTests, AccumulatorTransferFailsAtLastTransferRow)
724{
725 auto [key, params] = build_valid_accumulator_transfer_state();
726 auto& pp = key.proving_key->polynomials;
727
729 pp, params, "TranslatorAccumulatorTransferRelation");
730 EXPECT_TRUE(baseline.empty()) << "Baseline accumulator transfer should pass";
731
732 // Row MINI - NUM_MASKED - 3 = 8185 is the last odd row before 8187 where transfer is enforced
733 const size_t last_transfer_row = Flavor::MINI_CIRCUIT_SIZE - Flavor::NUM_MASKED_ROWS_END - 3;
734 pp.accumulators_binary_limbs_0.at(last_transfer_row) = FF::random_element();
735
737 pp, params, "TranslatorAccumulatorTransferRelation");
738 EXPECT_FALSE(failures.empty()) << "Accumulator transfer should fail at last transfer row";
739}
740
746TEST_F(TranslatorRelationFailureTests, PermutationFailsOnConcatenatedBlockBoundaryCorruption)
747{
748 auto [key, params] = build_valid_translator_state();
749 auto& pp = key.proving_key->polynomials;
750
751 // Baseline: permutation relation passes
752 auto baseline =
753 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
754 EXPECT_TRUE(baseline.empty()) << "Baseline permutation should pass";
755
756 // Position 0 of block 5 in concatenated_range_constraints_0.
757 // This is at index 5 * MINI_CIRCUIT_SIZE, which should be zero (below start_index of that block's wire).
758 const size_t block_boundary_pos = 5 * Flavor::MINI_CIRCUIT_SIZE;
759 EXPECT_EQ(pp.concatenated_range_constraints_0[block_boundary_pos], FF(0))
760 << "Block boundary should initially be zero";
761
762 pp.concatenated_range_constraints_0.at(block_boundary_pos) = FF(999);
763
764 // Re-compute grand product with corrupted data
765 compute_grand_product<Flavor, TranslatorPermutationRelation<FF>>(pp, params);
766
767 auto failures =
768 RelationChecker<Flavor>::check<TranslatorPermutationRelation<FF>>(pp, params, "TranslatorPermutationRelation");
769 EXPECT_FALSE(failures.empty()) << "Permutation should fail after block boundary corruption";
770}
771
772// ======================== Non-Native Field Relation: Accumulator Alias ========================
773
785TEST_F(TranslatorRelationFailureTests, NonNativeFieldRejectsAccumulatorAlias)
786{
787 using BF = typename Flavor::BF;
788 static constexpr size_t NUM_LIMB_BITS = Flavor::CircuitBuilder::NUM_LIMB_BITS;
789
790 auto [key, params] = build_valid_accumulator_transfer_state();
791 auto& pp = key.proving_key->polynomials;
792
793 // Baseline: all three NonNativeField subrelations pass
795 pp, params, "TranslatorNonNativeFieldRelation");
796 EXPECT_TRUE(baseline.empty()) << "Baseline non-native field should pass";
797
798 constexpr size_t ROW = Flavor::RESULT_ROW; // 8
799
800 // --- Read old accumulator and quotient at RESULT_ROW ---
801 // Accumulator limbs are at the even row (current acc = row ROW, previous acc = row ROW+1 via shift)
802 auto read_limbs = [](const auto& l0, const auto& l1, const auto& l2, const auto& l3, size_t row) {
803 return uint256_t(l0[row]) | (uint256_t(l1[row]) << NUM_LIMB_BITS) |
804 (uint256_t(l2[row]) << (2 * NUM_LIMB_BITS)) | (uint256_t(l3[row]) << (3 * NUM_LIMB_BITS));
805 };
806
807 uint256_t old_acc = read_limbs(pp.accumulators_binary_limbs_0,
808 pp.accumulators_binary_limbs_1,
809 pp.accumulators_binary_limbs_2,
810 pp.accumulators_binary_limbs_3,
811 ROW);
812
813 // Quotient: limbs 0,1 from quotient_low at rows ROW, ROW+1; limbs 2,3 from quotient_high at rows ROW, ROW+1
814 uint256_t old_quot = uint256_t(pp.quotient_low_binary_limbs[ROW]) |
815 (uint256_t(pp.quotient_low_binary_limbs[ROW + 1]) << NUM_LIMB_BITS) |
816 (uint256_t(pp.quotient_high_binary_limbs[ROW]) << (2 * NUM_LIMB_BITS)) |
817 (uint256_t(pp.quotient_high_binary_limbs[ROW + 1]) << (3 * NUM_LIMB_BITS));
818
819 // --- Apply the alias mutation: acc += p, quotient -= 1 ---
820 const uint256_t p_mod = BF::modulus;
821 uint256_t new_acc = old_acc + p_mod;
822 uint256_t new_quot = old_quot - uint256_t(1);
823
824 auto split = [](const uint256_t& val) -> std::array<FF, 4> {
825 return { FF(val.slice(0, NUM_LIMB_BITS)),
826 FF(val.slice(NUM_LIMB_BITS, 2 * NUM_LIMB_BITS)),
827 FF(val.slice(2 * NUM_LIMB_BITS, 3 * NUM_LIMB_BITS)),
828 FF(val.slice(3 * NUM_LIMB_BITS, 4 * NUM_LIMB_BITS)) };
829 };
830
831 auto new_acc_limbs = split(new_acc);
832 auto new_quot_limbs = split(new_quot);
833
834 // Write mutated accumulator limbs
835 pp.accumulators_binary_limbs_0.at(ROW) = new_acc_limbs[0];
836 pp.accumulators_binary_limbs_1.at(ROW) = new_acc_limbs[1];
837 pp.accumulators_binary_limbs_2.at(ROW) = new_acc_limbs[2];
838 pp.accumulators_binary_limbs_3.at(ROW) = new_acc_limbs[3];
839
840 // Write mutated quotient limbs
841 pp.quotient_low_binary_limbs.at(ROW) = new_quot_limbs[0];
842 pp.quotient_low_binary_limbs.at(ROW + 1) = new_quot_limbs[1];
843 pp.quotient_high_binary_limbs.at(ROW) = new_quot_limbs[2];
844 pp.quotient_high_binary_limbs.at(ROW + 1) = new_quot_limbs[3];
845
846 // Deliberately do NOT update relation_wide_limbs (carry witnesses) — the stale carries
847 // should cause the higher mod-2^136 check to fail.
848
850 pp, params, "TranslatorNonNativeFieldRelation");
851
852 // The higher carry check (subrelation 1) must fail at RESULT_ROW.
853 EXPECT_TRUE(failures.contains(1)) << "Subrelation 1 (higher carry check) should reject the alias";
854 EXPECT_EQ(failures.at(1), static_cast<uint32_t>(ROW)) << "Failure should be at RESULT_ROW";
855
856 // The native-field check (subrelation 2) should still pass — the mod-r projection is preserved.
857 EXPECT_FALSE(failures.contains(2)) << "Subrelation 2 (native check) should pass under the alias mutation";
858}
A container for the prover polynomials.
typename Curve::ScalarField FF
ECCVMCircuitBuilder CircuitBuilder
typename Curve::BaseField BF
bb::Polynomial< FF > Polynomial
typename G1::element GroupElement
A debugging utility for checking whether a set of polynomials satisfies the relations for a given Fla...
A wrapper for Relations to expose methods used by the Sumcheck prover or verifier to add the contribu...
group class. Represents an elliptic curve group element. Group is parametrised by Fq and Fr
Definition group.hpp:38
virtual uint16_t get_random_uint16()=0
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
numeric::RNG & engine
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Container for parameters used by the grand product (permutation, lookup) Honk relations.
std::array< std::array< T, NUM_BINARY_LIMBS_IN_GOBLIN_TRANSLATOR+NUM_NATIVE_LIMBS_IN_GOBLIN_TRANSLATOR >, NUM_CHALLENGE_POWERS_IN_GOBLIN_TRANSLATOR > batching_challenge_v
std::array< T, NUM_BINARY_LIMBS_IN_GOBLIN_TRANSLATOR > accumulated_result
std::array< T, NUM_BINARY_LIMBS_IN_GOBLIN_TRANSLATOR+NUM_NATIVE_LIMBS_IN_GOBLIN_TRANSLATOR > evaluation_input_x
static field random_element(numeric::RNG *engine=nullptr) noexcept