Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
eccvm_relation_corruption.test.cpp
Go to the documentation of this file.
1
13#include <gtest/gtest.h>
14
15using namespace bb;
16
17namespace {
18
19using Flavor = ECCVMFlavor;
20using FF = typename Flavor::FF;
21using G1 = bb::g1;
22using Fr = typename G1::Fr;
23using Polynomial = typename Flavor::Polynomial;
26
28
34std::vector<Polynomial*> get_msm_polynomials(ProverPolynomials& polys)
35{
36 return {
37 // From WireNonShiftedEntities (columns 21-44)
38 &polys.msm_size_of_msm,
39 &polys.msm_add2,
40 &polys.msm_add3,
41 &polys.msm_add4,
42 &polys.msm_x1,
43 &polys.msm_y1,
44 &polys.msm_x2,
45 &polys.msm_y2,
46 &polys.msm_x3,
47 &polys.msm_y3,
48 &polys.msm_x4,
49 &polys.msm_y4,
50 &polys.msm_collision_x1,
51 &polys.msm_collision_x2,
52 &polys.msm_collision_x3,
53 &polys.msm_collision_x4,
54 &polys.msm_lambda1,
55 &polys.msm_lambda2,
56 &polys.msm_lambda3,
57 &polys.msm_lambda4,
58 &polys.msm_slice1,
59 &polys.msm_slice2,
60 &polys.msm_slice3,
61 &polys.msm_slice4,
62 // From WireToBeShiftedWithoutAccumulatorsEntities (columns 68-77)
63 &polys.msm_transition,
64 &polys.msm_add,
65 &polys.msm_double,
66 &polys.msm_skew,
67 &polys.msm_accumulator_x,
68 &polys.msm_accumulator_y,
69 &polys.msm_count,
70 &polys.msm_round,
71 &polys.msm_add1,
72 &polys.msm_pc,
73 };
74}
75
79ProverPolynomials build_valid_eccvm_msm_state()
80{
81 auto generators = G1::derive_generators("test generators", 3);
82 auto a = generators[0];
83 auto b = generators[1];
86
87 auto op_queue = std::make_shared<ECCOpQueue>();
88 op_queue->mul_accumulate(a, x);
89 op_queue->mul_accumulate(b, y);
90 op_queue->eq_and_reset();
91 op_queue->merge();
92 add_hiding_op_for_test(op_queue);
93
94 ECCVMCircuitBuilder builder{ op_queue };
96}
97
102RelationParameters<FF> compute_full_relation_params(ProverPolynomials& polynomials)
103{
104 const FF beta = FF::random_element(&engine);
105 const FF gamma = FF::random_element(&engine);
106 const FF beta_sqr = beta.sqr();
107 const FF beta_cube = beta_sqr * beta;
108 auto eccvm_set_permutation_delta =
109 gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr);
110 eccvm_set_permutation_delta = eccvm_set_permutation_delta.invert();
111
113 .eta = 0,
114 .beta = beta,
115 .gamma = gamma,
116 .public_input_delta = 0,
117 .beta_sqr = beta_sqr,
118 .beta_cube = beta_cube,
119 .eccvm_set_permutation_delta = eccvm_set_permutation_delta,
120 };
121
122 const size_t num_rows = polynomials.get_polynomial_size();
123 const size_t unmasked_witness_size = num_rows - NUM_DISABLED_ROWS_IN_SUMCHECK;
124 compute_logderivative_inverse<FF, ECCVMLookupRelation<FF>>(polynomials, params, unmasked_witness_size);
125 compute_grand_product<Flavor, ECCVMSetRelation<FF>>(polynomials, params, unmasked_witness_size);
126 polynomials.z_perm_shift = Polynomial(polynomials.z_perm.shifted());
127
128 return params;
129}
130
134size_t find_transcript_noop_row(const ProverPolynomials& polynomials)
135{
136 const size_t num_rows = polynomials.get_polynomial_size();
137 for (size_t i = 2; i < num_rows - 1; i++) {
138 if (polynomials.transcript_add[i] == FF(0) && polynomials.transcript_mul[i] == FF(0) &&
139 polynomials.transcript_eq[i] == FF(0) && polynomials.transcript_reset_accumulator[i] == FF(0) &&
140 polynomials.lagrange_first[i] == FF(0) && polynomials.lagrange_last[i] == FF(0)) {
141 return i;
142 }
143 }
144 return 0;
145}
146
147} // anonymous namespace
148
149class ECCVMRelationCorruptionTests : public ::testing::Test {
150 protected:
152};
153
163TEST_F(ECCVMRelationCorruptionTests, MSMAccumulatorCorruptionAtTransitionRowIsHarmless)
164{
165 auto polynomials = build_valid_eccvm_msm_state();
166 RelationParameters<FF> params{};
167
168 auto baseline = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
169 EXPECT_TRUE(baseline.empty()) << "Baseline MSM relation should pass";
170
171 // Confirm row 1 is indeed the transition row
172 ASSERT_EQ(polynomials.msm_add[1], FF(1)) << "Row 1 should be an active MSM add row";
173 ASSERT_EQ(polynomials.msm_transition[1], FF(1)) << "Row 1 should have msm_transition=1";
174
175 // Corrupt the accumulator at the transition row
176 polynomials.msm_accumulator_x.at(1) = FF::random_element(&engine);
177 polynomials.msm_accumulator_y.at(1) = FF::random_element(&engine);
178 polynomials.set_shifted();
179
180 auto failures = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
181 EXPECT_TRUE(failures.empty()) << "MSM relation should STILL PASS — acc is unused when msm_transition=1";
182}
183
194TEST_F(ECCVMRelationCorruptionTests, MSMAccumulatorCorruptionAtInteriorAndNoOpRows)
195{
196 RelationParameters<FF> params{};
197
198 // --- Part 1: corrupt the accumulator at an interior active MSM row (q_add=1, msm_transition=0) ---
199 {
200 auto polynomials = build_valid_eccvm_msm_state();
201
202 auto baseline = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
203 EXPECT_TRUE(baseline.empty()) << "Baseline MSM relation should pass";
204
205 // Find an interior addition row: q_add=1, msm_transition=0
206 const size_t num_rows = polynomials.get_polynomial_size();
207 size_t active_row = 0;
208 for (size_t i = 1; i < num_rows - 1; i++) {
209 if (polynomials.msm_add[i] == FF(1) && polynomials.msm_transition[i] == FF(0)) {
210 active_row = i;
211 break;
212 }
213 }
214 ASSERT_NE(active_row, 0) << "Should find an interior active MSM add row";
215
216 polynomials.msm_accumulator_x.at(active_row) = FF::random_element(&engine);
217 polynomials.msm_accumulator_y.at(active_row) = FF::random_element(&engine);
218 polynomials.set_shifted();
219
220 auto failures = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
221 EXPECT_FALSE(failures.empty()) << "MSM relation should fail after active-row accumulator corruption";
222 }
223
224 // --- Part 2: corrupt the accumulator at a trailing no-op row ---
225 {
226 auto polynomials = build_valid_eccvm_msm_state();
227
228 // Find the first no-op row (all MSM selectors zero, not lagrange_first)
229 const size_t num_rows = polynomials.get_polynomial_size();
230 size_t no_op_row = 0;
231 for (size_t i = 2; i < num_rows - 1; i++) {
232 if (polynomials.msm_add[i] == FF(0) && polynomials.msm_double[i] == FF(0) &&
233 polynomials.msm_skew[i] == FF(0) && polynomials.msm_transition[i] == FF(0) &&
234 polynomials.lagrange_first[i] == FF(0)) {
235 no_op_row = i;
236 break;
237 }
238 }
239 ASSERT_NE(no_op_row, 0) << "Should find a no-op row in the MSM table";
240
241 polynomials.msm_accumulator_x.at(no_op_row) = FF::random_element(&engine);
242 polynomials.msm_accumulator_y.at(no_op_row) = FF::random_element(&engine);
243 polynomials.set_shifted();
244
245 auto failures = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
246 EXPECT_FALSE(failures.empty()) << "MSM relation should fail after no-op accumulator corruption";
247
248 // The failure should be in subrelations 45 or 46 (the no-op accumulator preservation constraints)
249 bool found_noop_subrelation_failure = failures.contains(45) || failures.contains(46);
250 EXPECT_TRUE(found_noop_subrelation_failure)
251 << "Failure should be detected by subrelations 45/46 (no-op accumulator preservation)";
252 }
253}
254
270TEST_F(ECCVMRelationCorruptionTests, MSMRelationFailsOnShiftedMSMTable)
271{
272 auto polynomials = build_valid_eccvm_msm_state();
273 RelationParameters<FF> params{};
274
275 // Baseline: MSM relation passes on clean data
276 auto baseline = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
277 EXPECT_TRUE(baseline.empty()) << "Baseline MSM relation should pass";
278
279 const size_t num_rows = polynomials.get_polynomial_size();
280 auto msm_polys = get_msm_polynomials(polynomials);
281
282 // Shift every MSM column down by 1: p[k] = p[k-1] for k = num_rows-1 down to 2, then p[1] = 0
283 for (auto* poly : msm_polys) {
284 for (size_t k = num_rows - 1; k >= 2; k--) {
285 poly->at(k) = (*poly)[k - 1];
286 }
287 poly->at(1) = FF(0);
288 }
289
290 // Subrelation 27 enforces: is_not_first_row * msm_transition_shift * (msm_size + pc_shift - pc) = 0
291 // After shifting, row 1 has msm_transition_shift = msm_transition[2] = 1 (old row 1's transition),
292 // but pc[1] = 0 and pc[2] = pc_original[1] (nonzero), with msm_size[1] = 0.
293 // Patch msm_size_of_msm[1] so the pc-continuity constraint is satisfied at the injected row.
294 polynomials.msm_size_of_msm.at(1) = polynomials.msm_pc[1] - polynomials.msm_pc[2];
295
296 // Refresh shifted views
297 polynomials.set_shifted();
298
299 auto failures = RelationChecker<void>::check<ECCVMMSMRelation<FF>>(polynomials, params, "ECCVMMSMRelation");
300 EXPECT_FALSE(failures.empty()) << "MSM relation should fail after shifting MSM table by one row";
301
302 // Log all failing subrelations for visibility
303 for (const auto& [subrelation_idx, row_idx] : failures) {
304 info("Shifted MSM table: subrelation ", subrelation_idx, " first failed at row ", row_idx);
305 }
306
307 // Only subrelations 45 and 46 (no-op accumulator preservation) should fail
308 EXPECT_EQ(failures.size(), 2U) << "Exactly two subrelations should fail (45 and 46)";
309 EXPECT_TRUE(failures.contains(45)) << "Subrelation 45 (no-op acc_x preservation) should fail";
310 EXPECT_TRUE(failures.contains(46)) << "Subrelation 46 (no-op acc_y preservation) should fail";
311
312 // Verify that all other ECCVM relations still pass after the shift.
313 // We compute random Fiat-Shamir challenges and derived polynomials (logderivative inverse, grand product)
314 // so we can also check ECCVMSetRelation and ECCVMLookupRelation.
315 auto full_params = compute_full_relation_params(polynomials);
316
317 // Relations that don't touch MSM columns should be completely unaffected.
318 auto transcript_failures =
319 RelationChecker<void>::check<ECCVMTranscriptRelation<FF>>(polynomials, full_params, "ECCVMTranscriptRelation");
320 EXPECT_TRUE(transcript_failures.empty()) << "ECCVMTranscriptRelation should still pass";
321
322 auto point_table_failures =
323 RelationChecker<void>::check<ECCVMPointTableRelation<FF>>(polynomials, full_params, "ECCVMPointTableRelation");
324 EXPECT_TRUE(point_table_failures.empty()) << "ECCVMPointTableRelation should still pass";
325
326 auto wnaf_failures =
327 RelationChecker<void>::check<ECCVMWnafRelation<FF>>(polynomials, full_params, "ECCVMWnafRelation");
328 EXPECT_TRUE(wnaf_failures.empty()) << "ECCVMWnafRelation should still pass";
329
330 auto bools_failures =
331 RelationChecker<void>::check<ECCVMBoolsRelation<FF>>(polynomials, full_params, "ECCVMBoolsRelation");
332 EXPECT_TRUE(bools_failures.empty()) << "ECCVMBoolsRelation should still pass";
333
334 // The Set relation enforces a multiset equality between MSM output tuples (pc, acc_x, acc_y, msm_size)
335 // and the transcript. Shifting the MSM columns corrupts these tuples, so the grand product (computed
336 // post-shift) reflects mismatched reads/writes and the relation correctly fails. It is possible that with more
337 // care, we could make this also pass.
338 auto set_failures =
339 RelationChecker<void>::check<ECCVMSetRelation<FF>>(polynomials, full_params, "ECCVMSetRelation");
340 EXPECT_FALSE(set_failures.empty()) << "ECCVMSetRelation should also fail (MSM output tuples are shifted)";
341
342 // The Lookup relation's logderivative inverse is computed post-shift, so it adapts to the
343 // shifted column values. The per-row subrelation passes, and the sum-over-trace (linearly
344 // dependent) subrelation also vanishes since the inverse was derived from the current data.
345 auto lookup_failures = RelationChecker<void>::check<ECCVMLookupRelation<FF>, /*has_linearly_dependent=*/true>(
346 polynomials, full_params, "ECCVMLookupRelation");
347 EXPECT_TRUE(lookup_failures.empty()) << "ECCVMLookupRelation should still pass (inverse computed post-shift)";
348}
349
357TEST_F(ECCVMRelationCorruptionTests, TranscriptNoOpRowRejectsAccumulatorNotEmpty)
358{
359 auto polynomials = build_valid_eccvm_msm_state();
360 RelationParameters<FF> params{};
361
362 auto baseline =
363 RelationChecker<void>::check<ECCVMTranscriptRelation<FF>>(polynomials, params, "ECCVMTranscriptRelation");
364 EXPECT_TRUE(baseline.empty()) << "Baseline transcript relation should pass";
365
366 size_t noop_row = find_transcript_noop_row(polynomials);
367 ASSERT_NE(noop_row, 0) << "Should find a transcript no-op row";
368
369 // The no-op constraint at row `noop_row` constrains is_accumulator_empty_shift,
370 // which reads from accumulator_not_empty at row `noop_row + 1`.
371 polynomials.transcript_accumulator_not_empty.at(noop_row + 1) = FF(1);
372 polynomials.set_shifted();
373
374 auto failures =
375 RelationChecker<void>::check<ECCVMTranscriptRelation<FF>>(polynomials, params, "ECCVMTranscriptRelation");
376 EXPECT_FALSE(failures.empty()) << "Transcript relation should fail after corrupting accumulator_not_empty on "
377 "the row following a no-op";
378 EXPECT_TRUE(failures.contains(22)) << "Subrelation 22 (accumulator_infinity) should catch the corruption";
379}
A container for the prover polynomials.
typename Curve::ScalarField FF
bb::Polynomial< FF > Polynomial
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
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...
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
numeric::RNG & engine
void add_hiding_op_for_test(const std::shared_ptr< ECCOpQueue > &op_queue)
Set a hiding op on the op_queue for testing.
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:217
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
group< fq, fr, Bn254G1Params > g1
Definition g1.hpp:35
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::AffineElement G1
Container for parameters used by the grand product (permutation, lookup) Honk relations.
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept