Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
shplemini.test.cpp
Go to the documentation of this file.
1
2#include "shplemini.hpp"
3#include "../gemini/gemini.hpp"
4#include "../kzg/kzg.hpp"
5#include "../pcs_test_utils.hpp"
6#include "../shplonk/shplonk.hpp"
12
13#include <gtest/gtest.h>
14#include <vector>
15
16namespace bb {
17
18template <class Flavor> class ShpleminiTest : public CommitmentTest<typename Flavor::Curve> {
19 public:
20 // Size of the test polynomials
21 static constexpr size_t log_n = 9;
22 static constexpr size_t n = 1UL << log_n;
23 // Total number of random polynomials in each test
24 static constexpr size_t num_polynomials = 7;
25 // Number of shiftable polynomials
26 static constexpr size_t num_shiftable = 2;
27
28 // The length of the mock sumcheck univariates.
29 static constexpr size_t sumcheck_univariate_length = 24;
30
31 using Fr = typename Flavor::Curve::ScalarField;
32 using GroupElement = typename Flavor::Curve::Element;
33 using Commitment = typename Flavor::Curve::AffineElement;
34 using CK = typename Flavor::CommitmentKey;
35
36 // Witness polynomial slots returned by SmallSubgroupIPAProver::get_witness_polynomials(): {G, A, Q}.
37 enum class TamperedPolynomial : size_t { None = SIZE_MAX, Concatenated = 0, GrandSum = 1, Quotient = 2 };
38
39 // libra_commitments array: [0]=Concatenated, [1]=GrandSum, [2]=Quotient
40 enum class TamperedCommitment : size_t { None = SIZE_MAX, Concatenated = 0, GrandSum = 1, Quotient = 2 };
41
43 CK& ck,
44 ZKSumcheckData<Flavor>& zk_sumcheck_data,
45 std::vector<Fr>& mle_opening_point,
47 const Fr& honest_inner_product);
48};
49
74template <class Flavor>
76 const std::shared_ptr<typename Flavor::Transcript>& prover_transcript,
77 typename Flavor::CommitmentKey& ck,
78 ZKSumcheckData<Flavor>& zk_sumcheck_data,
81 const typename Flavor::Curve::ScalarField& honest_inner_product)
82{
83 using Curve = typename Flavor::Curve;
84 using Fr = typename Curve::ScalarField;
85 using ShpleminiProver = ShpleminiProver_<Curve>;
86
87 static constexpr size_t SUBGROUP_SIZE = Flavor::SUBGROUP_SIZE;
90
91 // ---- Forging perturbation: pick delta != 0, then derive (c, delta_s, forged_s). ------------
92 Fr delta = Fr::random_element();
93 while (delta == Fr(0)) {
94 delta = Fr::random_element();
95 }
96 const Fr c = -delta / (g - Fr(1));
97 const Fr delta_s = c;
98 const Fr forged_inner_product = honest_inner_product + delta_s;
99
100 // Send forged s_f to the transcript before any prover commitments — the verifier will read this back.
101 prover_transcript->send_to_verifier("Libra:claimed_evaluation", forged_inner_product);
102
103 // Drive the small-IPA prover's component methods with the HONEST inner product so the resulting (A_h, Q_h)
104 // satisfy the identity for s_h. We bypass prove() to avoid sending honest commitments — we'll commit to the
105 // forged polynomials ourselves below.
107 zk_sumcheck_data, mle_opening_point, honest_inner_product, prover_transcript, ck);
108 ipa_prover.compute_grand_sum_polynomial();
111
112 auto honest_polys = ipa_prover.get_witness_polynomials();
113 const Polynomial<Fr>& A_honest = honest_polys[1];
114 const Polynomial<Fr>& Q_honest = honest_polys[2];
115
116 // ---- Build delta_A in monomial form via Lagrange interpolation on H. -----------------------
118 H_domain[0] = Fr(1);
119 for (size_t i = 1; i < SUBGROUP_SIZE; ++i) {
120 H_domain[i] = H_domain[i - 1] * g;
121 }
122 std::vector<Fr> delta_A_lagrange(SUBGROUP_SIZE, c);
123 delta_A_lagrange[0] = delta;
124 Polynomial<Fr> delta_A(
125 std::span<const Fr>(H_domain.data(), SUBGROUP_SIZE), std::span<const Fr>(delta_A_lagrange), SUBGROUP_SIZE);
126
127 // L_1, L_{|H|} in monomial form.
128 std::vector<Fr> L_1_lag(SUBGROUP_SIZE, Fr(0));
129 L_1_lag[0] = Fr(1);
130 Polynomial<Fr> L_1(
131 std::span<const Fr>(H_domain.data(), SUBGROUP_SIZE), std::span<const Fr>(L_1_lag), SUBGROUP_SIZE);
132 std::vector<Fr> L_H_lag(SUBGROUP_SIZE, Fr(0));
133 L_H_lag[SUBGROUP_SIZE - 1] = Fr(1);
134 Polynomial<Fr> L_H(
135 std::span<const Fr>(H_domain.data(), SUBGROUP_SIZE), std::span<const Fr>(L_H_lag), SUBGROUP_SIZE);
136
137 // delta_A(gX) — coefficient i scaled by g^i.
138 std::vector<Fr> delta_A_shifted(SUBGROUP_SIZE);
139 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
140 delta_A_shifted[i] = delta_A.at(i) * H_domain[i];
141 }
142
143 // delta_C(X) = L_1*delta_A + (X - g^{-1})*(delta_A(gX) - delta_A) + L_{|H|}*(delta_A - delta_s).
144 std::vector<Fr> delta_C(2 * SUBGROUP_SIZE, Fr(0));
145 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
146 for (size_t j = 0; j < SUBGROUP_SIZE; ++j) {
147 delta_C[i + j] += L_1.at(i) * delta_A.at(j);
148 }
149 }
150 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
151 for (size_t j = 0; j < SUBGROUP_SIZE; ++j) {
152 delta_C[i + j] += L_H.at(i) * delta_A.at(j);
153 }
154 delta_C[i] -= L_H.at(i) * delta_s;
155 }
156 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
157 const Fr u_i = delta_A_shifted[i] - delta_A.at(i);
158 delta_C[i + 1] += u_i;
159 delta_C[i] -= g_inv * u_i;
160 }
161
162 // delta_Q = delta_C / Z_H (exact division by construction; the loop below also clears the remainder buffer).
163 std::vector<Fr> delta_Q_coeffs(SUBGROUP_SIZE, Fr(0));
164 for (size_t i = 2 * SUBGROUP_SIZE; i-- > SUBGROUP_SIZE;) {
165 delta_Q_coeffs[i - SUBGROUP_SIZE] = delta_C[i];
166 delta_C[i - SUBGROUP_SIZE] += delta_C[i];
167 delta_C[i] = Fr(0);
168 }
169 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
170 BB_ASSERT_EQ(delta_C[i], Fr(0), "delta_C is not divisible by Z_H — perturbation is malformed.");
171 }
172 Polynomial<Fr> delta_Q(std::span<const Fr>(delta_Q_coeffs), SUBGROUP_SIZE);
173
174 // ---- A_forged = A_honest + delta_A; Q_forged = Q_honest + delta_Q. -------------------------
175 Polynomial<Fr> A_forged = A_honest;
176 Polynomial<Fr> Q_forged = Q_honest;
177 for (size_t i = 0; i < SUBGROUP_SIZE; ++i) {
178 A_forged.at(i) += delta_A.at(i);
179 Q_forged.at(i) += delta_Q.at(i);
180 }
181
182 // Forged commitments — these are what the verifier will see for [A] and [Q].
183 prover_transcript->send_to_verifier("Libra:grand_sum_commitment", ck.commit(A_forged));
184 prover_transcript->send_to_verifier("Libra:quotient_commitment", ck.commit(Q_forged));
185
186 // Witness layout passed to Shplemini: 3-array {G, A_f, Q_f} matching
187 // SmallSubgroupIPAProver::get_witness_polynomials().
188 std::array<Polynomial<Fr>, NUM_SMALL_IPA_COMMITMENTS> forged_witness = { honest_polys[0], A_forged, Q_forged };
189
190 // A protocol-following prover aborts inside factor_roots when constructing the (A, 1, 0) opening, because
191 // A_forged(1) != 0 makes the division non-exact. Demote asserts to warnings to simulate a malicious prover
192 // that ignores this fail-fast precondition; the resulting proof is the (incorrect) one a real attacker would
193 // submit, and the verifier must still reject it.
195 const auto opening_claim = ShpleminiProver::prove(
196 this->n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript, forged_witness);
197 KZG<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
198
199 return forged_inner_product;
200}
201
202// Shplemini's multilinear opening is a production flow only over BN254 (KZG) — UltraHonk/MegaHonk/Translator/AVM.
203// The Grumpkin (IPA) instantiation exercised Shplemini -> IPA, which ECCVM replaced with the TripleIPA; ECCVM
204// only uses Shplemini's `compute_sumcheck_round_claims` helper, covered by the eccvm integration tests.
205using TestSettings = ::testing::Types<BN254Settings>;
206
208
209// Non-template test fixture for KZG-specific tests
210class ShpleminiKZGTest : public CommitmentTest<curve::BN254> {
211 public:
212 static constexpr size_t log_n = 9;
213 static constexpr size_t n = 1UL << log_n;
214};
215
216// This test checks that batch_multivariate_opening_claims method operates correctly
217TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
218{
219 using Curve = typename TypeParam::Curve;
220 using Fr = typename Curve::ScalarField;
221 using GroupElement = typename Curve::Element;
222 using Commitment = typename Curve::AffineElement;
223 using CK = typename TypeParam::CommitmentKey;
224
225 CK ck = create_commitment_key<CK>(this->n);
226
227 // Generate mock challenges
228 Fr rho = Fr::random_element();
229 Fr gemini_eval_challenge = Fr::random_element();
230 Fr shplonk_batching_challenge = Fr::random_element();
231 Fr shplonk_eval_challenge = Fr::random_element();
232
233 // Generate multilinear polynomials and compute their commitments
234 auto mle_opening_point = this->random_evaluation_point(this->log_n);
235
236 MockClaimGenerator<Curve> mock_claims(this->n,
237 /*num_polynomials*/ this->num_polynomials,
238 /*num_to_be_shifted*/ this->num_shiftable,
239 mle_opening_point,
240 ck);
241
242 // Collect multilinear evaluations
243 std::vector<Fr> rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable);
244
245 // Lambda to compute batched multivariate evaluation
246 auto update_batched_eval = [&](Fr& batched_eval, const std::vector<Fr>& evaluations, Fr& rho_power) {
247 for (auto& eval : evaluations) {
248 batched_eval += eval * rho_power;
249 rho_power *= rho;
250 }
251 };
252
253 Fr rho_power(1);
254 Fr batched_evaluation(0);
255 update_batched_eval(batched_evaluation, mock_claims.unshifted.evals, rho_power);
256 update_batched_eval(batched_evaluation, mock_claims.to_be_shifted.evals, rho_power);
257
258 // Lambda to compute batched commitment
259 auto compute_batched_commitment = [&](const std::vector<Commitment>& commitments, Fr& rho_power) {
260 GroupElement batched = GroupElement::zero();
261 for (auto& comm : commitments) {
262 batched += comm * rho_power;
263 rho_power *= rho;
264 }
265 return batched;
266 };
267
268 // Compute batched commitments manually
269 rho_power = Fr(1);
270 GroupElement batched_commitment_unshifted =
271 compute_batched_commitment(mock_claims.unshifted.commitments, rho_power);
272 GroupElement batched_commitment_to_be_shifted =
273 compute_batched_commitment(mock_claims.to_be_shifted.commitments, rho_power);
274
275 // Compute expected result manually
276 GroupElement to_be_shifted_contribution = batched_commitment_to_be_shifted * gemini_eval_challenge.invert();
277
278 GroupElement commitment_to_univariate_pos = batched_commitment_unshifted + to_be_shifted_contribution;
279
280 GroupElement commitment_to_univariate_neg = batched_commitment_unshifted - to_be_shifted_contribution;
281
282 GroupElement expected_result =
283 commitment_to_univariate_pos * (shplonk_eval_challenge - gemini_eval_challenge).invert() +
284 commitment_to_univariate_neg *
285 (shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert());
286
287 // Run the ShepliminiVerifier batching method
288 std::vector<Commitment> commitments;
289 std::vector<Fr> scalars;
290 Fr verifier_batched_evaluation{ 0 };
291
292 Fr inverted_vanishing_eval_pos = (shplonk_eval_challenge - gemini_eval_challenge).invert();
293 Fr inverted_vanishing_eval_neg = (shplonk_eval_challenge + gemini_eval_challenge).invert();
294
295 std::vector<Fr> inverted_vanishing_evals = { inverted_vanishing_eval_pos, inverted_vanishing_eval_neg };
296
298 inverted_vanishing_evals, shplonk_batching_challenge, gemini_eval_challenge);
299
301 commitments, scalars, verifier_batched_evaluation, rho);
302
303 // Final pairing check
304 GroupElement shplemini_result = GroupElement::batch_mul(commitments, scalars);
305
306 EXPECT_EQ(commitments.size(),
307 mock_claims.unshifted.commitments.size() + mock_claims.to_be_shifted.commitments.size());
308 EXPECT_EQ(batched_evaluation, verifier_batched_evaluation);
309 EXPECT_EQ(-expected_result, shplemini_result);
310}
311TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching)
312{
313 using Curve = TypeParam::Curve;
314 using GeminiProver = GeminiProver_<Curve>;
315 using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
316 using ShplonkVerifier = ShplonkVerifier_<Curve>;
317 using Fr = typename Curve::ScalarField;
318 using GroupElement = typename Curve::Element;
319 using Commitment = typename Curve::AffineElement;
320 using Polynomial = typename bb::Polynomial<Fr>;
321 using CK = typename TypeParam::CommitmentKey;
322
323 CK ck = create_commitment_key<CK>(this->n);
324
325 // Generate mock challenges
326 Fr rho = Fr::random_element();
327 Fr gemini_eval_challenge = Fr::random_element();
328 Fr shplonk_batching_challenge = Fr::random_element();
329
330 std::vector<Fr> shplonk_batching_challenge_powers =
331 compute_shplonk_batching_challenge_powers(shplonk_batching_challenge, this->log_n);
332
333 Fr shplonk_eval_challenge = Fr::random_element();
334
335 std::vector<Fr> mle_opening_point = this->random_evaluation_point(this->log_n);
336
337 MockClaimGenerator<Curve> mock_claims(this->n,
338 /*num_polynomials*/ this->num_polynomials,
339 /*num_to_be_shifted*/ this->num_shiftable,
340 mle_opening_point,
341 ck);
342
343 // Collect multilinear evaluations
344 std::vector<Fr> rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable);
345
346 Polynomial batched = mock_claims.polynomial_batcher.compute_batched(rho);
347
348 // Compute:
349 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
350 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
351 auto fold_polynomials = GeminiProver::compute_fold_polynomials(this->log_n, mle_opening_point, batched);
352
353 std::vector<Commitment> prover_commitments;
354 for (size_t l = 0; l < this->log_n - 1; ++l) {
355 auto commitment = ck.commit(fold_polynomials[l]);
356 prover_commitments.emplace_back(commitment);
357 }
358
359 auto [A_0_pos, A_0_neg] =
360 mock_claims.polynomial_batcher.compute_partially_evaluated_batch_polynomials(gemini_eval_challenge);
361
362 const auto opening_claims = GeminiProver::construct_univariate_opening_claims(
363 this->log_n, std::move(A_0_pos), std::move(A_0_neg), std::move(fold_polynomials), gemini_eval_challenge);
364
365 std::vector<Fr> prover_evaluations;
366 for (size_t l = 0; l < this->log_n; ++l) {
367 const auto& evaluation = opening_claims[l + 1].opening_pair.evaluation;
368 prover_evaluations.emplace_back(evaluation);
369 }
370
371 std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, this->log_n);
372
373 GroupElement expected_result = GroupElement::zero();
374 std::vector<Fr> expected_inverse_vanishing_evals;
375 expected_inverse_vanishing_evals.reserve(2 * this->log_n);
376 // Compute expected inverses
377 for (size_t idx = 0; idx < this->log_n; idx++) {
378 expected_inverse_vanishing_evals.emplace_back((shplonk_eval_challenge - r_squares[idx]).invert());
379 expected_inverse_vanishing_evals.emplace_back((shplonk_eval_challenge + r_squares[idx]).invert());
380 }
381
382 Fr current_challenge{ shplonk_batching_challenge * shplonk_batching_challenge };
383 for (size_t idx = 0; idx < prover_commitments.size(); ++idx) {
384 expected_result -= prover_commitments[idx] * current_challenge * expected_inverse_vanishing_evals[2 * idx + 2];
385 current_challenge *= shplonk_batching_challenge;
386 expected_result -= prover_commitments[idx] * current_challenge * expected_inverse_vanishing_evals[2 * idx + 3];
387 current_challenge *= shplonk_batching_challenge;
388 }
389
390 // Run the ShepliminiVerifier batching method
391 std::vector<Fr> inverse_vanishing_evals =
392 ShplonkVerifier::compute_inverted_gemini_denominators(shplonk_eval_challenge, r_squares);
393
394 Fr expected_constant_term_accumulator{ 0 };
395
396 std::vector<Fr> gemini_fold_pos_evaluations = GeminiVerifier_<Curve>::compute_fold_pos_evaluations(
397 expected_constant_term_accumulator, mle_opening_point, r_squares, prover_evaluations);
398 std::vector<Commitment> commitments;
399 std::vector<Fr> scalars;
400
401 ShpleminiVerifier::batch_gemini_claims_received_from_prover(prover_commitments,
402 prover_evaluations,
403 gemini_fold_pos_evaluations,
404 inverse_vanishing_evals,
405 shplonk_batching_challenge_powers,
406 commitments,
407 scalars,
408 expected_constant_term_accumulator);
409
410 // Compute the group element using the output of Shplemini method
411 GroupElement shplemini_result = GroupElement::batch_mul(commitments, scalars);
412
413 EXPECT_EQ(shplemini_result, expected_result);
414}
415
421TYPED_TEST(ShpleminiTest, ShpleminiZKNoSumcheckOpenings)
422{
423 using ZKData = ZKSumcheckData<TypeParam>;
424 using Curve = TypeParam::Curve;
425 using ShpleminiProver = ShpleminiProver_<Curve>;
426 constexpr bool HasZK = true;
427 using ShpleminiVerifier = ShpleminiVerifier_<Curve, HasZK>;
428 using Fr = typename Curve::ScalarField;
429 using Commitment = typename Curve::AffineElement;
430 using CK = typename TypeParam::CommitmentKey;
431
432 // Initialize transcript and commitment key
433 auto prover_transcript = TypeParam::Transcript::test_prover_init_empty();
434
435 // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck.
436 static constexpr size_t log_subgroup_size = static_cast<size_t>(numeric::get_msb(Curve::SUBGROUP_SIZE));
437 CK ck = create_commitment_key<CK>(std::max<size_t>(this->n, 1ULL << (log_subgroup_size + 1)));
438
439 // Generate Libra polynomials, compute masked concatenated Libra polynomial, commit to it
440 ZKData zk_sumcheck_data(this->log_n, prover_transcript, ck);
441
442 // Generate multivariate challenge
443 std::vector<Fr> mle_opening_point = this->random_evaluation_point(this->log_n);
444
445 // Generate random prover polynomials, compute their evaluations and commitments
446 MockClaimGenerator<Curve> mock_claims(this->n,
447 /*num_polynomials*/ this->num_polynomials,
448 /*num_to_be_shifted*/ this->num_shiftable,
449 mle_opening_point,
450 ck);
451
452 // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges
454 zk_sumcheck_data, mle_opening_point, this->log_n);
455
456 prover_transcript->send_to_verifier("Libra:claimed_evaluation", claimed_inner_product);
457
458 // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials
459 SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
460 zk_sumcheck_data, mle_opening_point, claimed_inner_product, prover_transcript, ck);
461 small_subgroup_ipa_prover.prove();
462
463 // Reduce to KZG or IPA based on the curve used in the test Flavor
464 const auto opening_claim = ShpleminiProver::prove(this->n,
465 mock_claims.polynomial_batcher,
466 mle_opening_point,
467 ck,
468 prover_transcript,
469 small_subgroup_ipa_prover.get_witness_polynomials());
470
471 KZG<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
472
473 // Initialize verifier's transcript
474 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
475
476 // Start populating Verifier's array of Libra commitments
477 std::array<Commitment, NUM_SMALL_IPA_COMMITMENTS> libra_commitments = {};
478 libra_commitments[0] =
479 verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");
480
481 // Place Libra data to the transcript
482 const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
483 const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
484 const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");
485
486 // Check that transcript is consistent
487 EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum);
488 EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge);
489 EXPECT_EQ(libra_evaluation, claimed_inner_product);
490
491 // Finalize the array of Libra/SmallSubgroupIpa commitments
492 libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:grand_sum_commitment");
493 libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");
494
495 // Run Shplemini
496 auto [batch_opening_claim, consistency_checked] =
497 ShpleminiVerifier::compute_batch_opening_claim(mock_claims.claim_batcher,
498 mle_opening_point,
499 this->vk().get_g1_identity(),
500 verifier_transcript,
501 {},
502 libra_commitments,
503 libra_evaluation);
504 // Verify claim using KZG
505 const auto pairing_points =
506 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
507 // Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2)
508 EXPECT_EQ(pairing_points.check(), true);
509 EXPECT_EQ(consistency_checked, true);
510}
511
519TYPED_TEST(ShpleminiTest, ShpleminiZKWithSumcheckOpenings)
520{
521 using Curve = TypeParam::Curve;
522 using Fr = typename Curve::ScalarField;
523 using Commitment = typename Curve::AffineElement;
524 using CK = typename TypeParam::CommitmentKey;
525
526 using ShpleminiProver = ShpleminiProver_<Curve>;
527 constexpr bool HasZK = true;
528 using ShpleminiVerifier = ShpleminiVerifier_<Curve, HasZK>;
529
530 CK ck = create_commitment_key<CK>(4096);
531
532 // Generate Sumcheck challenge
533 std::vector<Fr> challenge = this->random_evaluation_point(this->log_n);
534
535 auto prover_transcript = TypeParam::Transcript::test_prover_init_empty();
536
537 // Generate masking polynomials for Sumcheck Round Univariates
538 ZKSumcheckData<TypeParam> zk_sumcheck_data(this->log_n, prover_transcript, ck);
539 // Generate mock witness
540 MockClaimGenerator<Curve> mock_claims(this->n, 1);
541
542 // Generate valid sumcheck polynomials of given length
543 mock_claims.template compute_sumcheck_opening_data<TypeParam>(
544 this->log_n, this->sumcheck_univariate_length, challenge, ck);
545
546 // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges
547 const Fr claimed_inner_product =
548 SmallSubgroupIPAProver<TypeParam>::compute_claimed_inner_product(zk_sumcheck_data, challenge, this->log_n);
549
550 prover_transcript->send_to_verifier("Libra:claimed_evaluation", claimed_inner_product);
551
552 // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials
553 SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
554 zk_sumcheck_data, challenge, claimed_inner_product, prover_transcript, ck);
555 small_subgroup_ipa_prover.prove();
556
557 // Reduce proving to a single claimed fed to KZG or IPA
558 const auto opening_claim = ShpleminiProver::prove(this->n,
559 mock_claims.polynomial_batcher,
560 challenge,
561 ck,
562 prover_transcript,
563 small_subgroup_ipa_prover.get_witness_polynomials(),
564 mock_claims.round_univariates,
565 mock_claims.sumcheck_evaluations);
566
567 KZG<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
568
569 // Initialize verifier's transcript
570 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
571
572 std::array<Commitment, NUM_SMALL_IPA_COMMITMENTS> libra_commitments = {};
573 libra_commitments[0] =
574 verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");
575
576 // Place Libra data to the transcript
577 const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
578 const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
579 const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");
580
581 // Check that transcript is consistent
582 EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum);
583 EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge);
584 EXPECT_EQ(libra_evaluation, claimed_inner_product);
585
586 // Finalize the array of Libra/SmallSubgroupIpa commitments
587 libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:grand_sum_commitment");
588 libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");
589
590 // Run Shplemini
591 auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(mock_claims.claim_batcher,
592 challenge,
593 this->vk().get_g1_identity(),
594 verifier_transcript,
595 {},
596 libra_commitments,
597 libra_evaluation,
598 mock_claims.sumcheck_commitments,
599 mock_claims.sumcheck_evaluations)
600 .batch_opening_claim;
601 // Verify claim using KZG
602 const auto pairing_points =
603 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
604 // Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2)
605 EXPECT_EQ(pairing_points.check(), true);
606}
607
614TYPED_TEST(ShpleminiTest, HighDegreeAttackAccept)
615{
616 // In debug builds, the coarse-form field assertion can intermittently fire during intermediate
617 // arithmetic when processing deliberately oversized polynomials. Suppress assertions to warnings.
619
620 using Curve = typename TypeParam::Curve;
621 using Fr = typename Curve::ScalarField;
622 using CK = typename TypeParam::CommitmentKey;
623 using ShpleminiProver = ShpleminiProver_<Curve>;
624 using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
626
627 // Use the fixture's n (1 << 9 = 512) as the polynomial size
628 // small_log_n = 3 means we fold to a constant after 3 rounds
629 static constexpr size_t small_log_n = 3;
630 CK ck = create_commitment_key<CK>(this->n);
631
632 // Sample public opening point (u_0, u_1, u_2)
633 auto u = this->random_evaluation_point(small_log_n);
634
635 // Choose a claimed eval at `u`
636 Fr claimed_multilinear_eval = Fr::random_element();
637
638 // poly is of high degrees (up to n), as the SRS allows for it
639 Polynomial poly(this->n);
640
641 // Define poly to be of a specific form such that after small_log_n folds with u, it becomes a constant equal to
642 // claimed_multilinear_eval. The non-zero coefficients are at indices that fold correctly.
643 // For n = 512, small_log_n = 3: indices 4, 504, 508 work (instead of 4, 4088, 4092 for n = 4096)
644 const Fr tail = ((Fr(1) - u[0]) * (Fr(1) - u[1])).invert();
645 poly.at(4) = claimed_multilinear_eval * tail / u[2];
646 poly.at(this->n - 8) = tail; // 504 for n=512
647 poly.at(this->n - 4) = -tail * (Fr(1) - u[2]) / u[2]; // 508 for n=512
648
649 MockClaimGenerator<Curve> mock_claims(
650 this->n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, ck);
651
652 auto prover_transcript = NativeTranscript::test_prover_init_empty();
653
654 // Run Shplemini prover
655 const auto opening_claim =
656 ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, u, ck, prover_transcript);
657
658 // Run KZG prover
659 KZG<Curve>::compute_opening_proof(ck, opening_claim, prover_transcript);
660
661 // Verifier side
662 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
663
664 auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(
665 mock_claims.claim_batcher, u, this->vk().get_g1_identity(), verifier_transcript)
666 .batch_opening_claim;
667
668 // Verify claim - should succeed because the polynomial was crafted to fold correctly
669 const auto pairing_points =
670 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
671 EXPECT_EQ(pairing_points.check(), true);
672}
673
679TYPED_TEST(ShpleminiTest, HighDegreeAttackReject)
680{
681 // In debug builds, the coarse-form field assertion can intermittently fire during intermediate
682 // arithmetic when processing deliberately oversized polynomials. Suppress assertions to warnings
683 // for this adversarial test so the test can complete and verify the pairing check fails.
685
686 using Curve = typename TypeParam::Curve;
687 using Fr = typename Curve::ScalarField;
688 using CK = typename TypeParam::CommitmentKey;
689 using ShpleminiProver = ShpleminiProver_<Curve>;
690 using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
692
693 // Use a larger SRS size to allow committing to high degree polynomials
694 static constexpr size_t big_n = 1UL << 12;
695 static constexpr size_t small_log_n = 3;
696 static constexpr size_t big_ck_size = 1 << 14;
697 CK ck = create_commitment_key<CK>(big_ck_size);
698
699 // Random high degree polynomial
700 Polynomial poly = Polynomial::random(big_n);
701
702 // Sample public opening point (u_0, u_1, u_2)
703 auto u = this->random_evaluation_point(small_log_n);
704
705 // Choose a random claimed eval at `u` (likely wrong)
706 Fr claimed_multilinear_eval = Fr::random_element();
707
708 MockClaimGenerator<Curve> mock_claims(
709 big_n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, ck);
710
711 auto prover_transcript = NativeTranscript::test_prover_init_empty();
712
713 // Run Shplemini prover
714 const auto opening_claim = ShpleminiProver::prove(big_n, mock_claims.polynomial_batcher, u, ck, prover_transcript);
715
716 // Run KZG prover
717 KZG<Curve>::compute_opening_proof(ck, opening_claim, prover_transcript);
718
719 // Verifier side
720 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
721
722 auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(
723 mock_claims.claim_batcher, u, this->vk().get_g1_identity(), verifier_transcript)
724 .batch_opening_claim;
725
726 // Verify claim - should fail because the random polynomial doesn't fold correctly
727 const auto pairing_points =
728 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
729 EXPECT_EQ(pairing_points.check(), false);
730}
731
750TYPED_TEST(ShpleminiTest, ToBeShiftedNonZeroConstantTermRejected)
751{
752 using Curve = typename TypeParam::Curve;
753 using Fr = typename Curve::ScalarField;
754 using GroupElement = typename Curve::Element;
755 using Commitment = typename Curve::AffineElement;
756 using CK = typename TypeParam::CommitmentKey;
757 using ShpleminiProver = ShpleminiProver_<Curve>;
758 using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
759
760 CK ck = create_commitment_key<CK>(this->n);
761
762 auto mle_opening_point = this->random_evaluation_point(this->log_n);
763
764 MockClaimGenerator<Curve> mock_claims(this->n,
765 /*num_polynomials*/ this->num_polynomials,
766 /*num_to_be_shifted*/ this->num_shiftable,
767 mle_opening_point,
768 ck);
769
770 auto prover_transcript = NativeTranscript::test_prover_init_empty();
771
772 const auto opening_claim =
773 ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript);
774
775 // KZG never binds the claim into Fiat-Shamir, so the verifier can be handed a tampered claim
776 // later without affecting the prover transcript.
777 KZG<Curve>::compute_opening_proof(ck, opening_claim, prover_transcript);
778
779 // Simulate adversary: replace the first to-be-shifted commitment with com(p + c * delta_0),
780 // i.e. add c * [1]_1 to it. The shifted MLE evaluation is unchanged (shifting drops the [0]
781 // coefficient). The unshifted MLE evaluation of p + c * delta_0 differs from the unshifted MLE
782 // evaluation of p by c * prod_i (1 - u_i); update the unshifted counterpart claim accordingly
783 // so that any rejection cannot be attributed to a stale unshifted-side mismatch.
784 const Fr c = Fr::random_element();
785 const Commitment g1_identity = this->vk().get_g1_identity();
786 const auto tampered = Commitment(GroupElement(mock_claims.to_be_shifted.commitments[0]) + g1_identity * c);
787
788 Fr lagrange0_at_u = Fr(1);
789 for (const auto& u_i : mle_opening_point) {
790 lagrange0_at_u *= (Fr(1) - u_i);
791 }
792 const size_t unshifted_idx = this->num_polynomials - this->num_shiftable; // first to-be-shifted in unshifted batch
793 mock_claims.to_be_shifted.commitments[0] = tampered;
794 mock_claims.unshifted.commitments[unshifted_idx] = tampered;
795 mock_claims.unshifted.evals[unshifted_idx] += c * lagrange0_at_u;
796
797 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
798
799 auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(
800 mock_claims.claim_batcher, mle_opening_point, g1_identity, verifier_transcript)
801 .batch_opening_claim;
802
803 const auto pairing_points =
804 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
805 EXPECT_EQ(pairing_points.check(), false);
806
807 // Confirm the rejection is not an artifact of transcript divergence: a fresh challenge with
808 // the same label drawn from both transcripts must agree. If this check fails, the rejection
809 // above could be attributed to the prover and verifier consuming different challenges rather
810 // than the PCS check itself.
811 EXPECT_EQ(prover_transcript->template get_challenge<Fr>("transcript_sync_check"),
812 verifier_transcript->template get_challenge<Fr>("transcript_sync_check"));
813}
814
820TYPED_TEST(ShpleminiTest, LibraConsistencyCheckFailsOnCorruptedEvaluation)
821{
822 using ZKData = ZKSumcheckData<TypeParam>;
823 using Curve = typename TypeParam::Curve;
824 using ShpleminiProver = ShpleminiProver_<Curve>;
825 constexpr bool HasZK = true;
826 using ShpleminiVerifier = ShpleminiVerifier_<Curve, HasZK>;
827 using Fr = typename Curve::ScalarField;
828 using Commitment = typename Curve::AffineElement;
829 using CK = typename TypeParam::CommitmentKey;
830
831 // Initialize transcript and commitment key
832 auto prover_transcript = TypeParam::Transcript::test_prover_init_empty();
833
834 // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck.
835 static constexpr size_t log_subgroup_size = static_cast<size_t>(numeric::get_msb(Curve::SUBGROUP_SIZE));
836 CK ck = create_commitment_key<CK>(std::max<size_t>(this->n, 1ULL << (log_subgroup_size + 1)));
837
838 // Generate Libra polynomials, compute masked concatenated Libra polynomial, commit to it
839 ZKData zk_sumcheck_data(this->log_n, prover_transcript, ck);
840
841 // Generate multivariate challenge
842 std::vector<Fr> mle_opening_point = this->random_evaluation_point(this->log_n);
843
844 // Generate random prover polynomials, compute their evaluations and commitments
845 MockClaimGenerator<Curve> mock_claims(this->n,
846 /*num_polynomials*/ this->num_polynomials,
847 /*num_to_be_shifted*/ this->num_shiftable,
848 mle_opening_point,
849 ck);
850
851 // Compute the correct sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges
853 zk_sumcheck_data, mle_opening_point, this->log_n);
854
855 // CORRUPT: Malicious prover sends a corrupted evaluation via the transcript
856 const Fr corrupted_inner_product = claimed_inner_product + Fr::random_element();
857 prover_transcript->send_to_verifier("Libra:claimed_evaluation", corrupted_inner_product);
858
859 // Instantiate SmallSubgroupIPAProver with the CORRECT value (prover's internal state is correct,
860 // but the value sent to verifier is corrupted - simulating a cheating prover)
861 SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
862 zk_sumcheck_data, mle_opening_point, corrupted_inner_product, prover_transcript, ck);
863 small_subgroup_ipa_prover.prove();
864
865 // Reduce to KZG or IPA based on the curve used in the test Flavor
866 const auto opening_claim = ShpleminiProver::prove(this->n,
867 mock_claims.polynomial_batcher,
868 mle_opening_point,
869 ck,
870 prover_transcript,
871 small_subgroup_ipa_prover.get_witness_polynomials());
872
873 KZG<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
874
875 // Initialize verifier's transcript
876 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
877
878 // Start populating Verifier's array of Libra commitments
879 std::array<Commitment, NUM_SMALL_IPA_COMMITMENTS> libra_commitments = {};
880 libra_commitments[0] =
881 verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");
882
883 // Place Libra data to the transcript
884 [[maybe_unused]] const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
885 [[maybe_unused]] const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
886 // Verifier receives the CORRUPTED evaluation from the transcript
887 const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");
888
889 // Finalize the array of Libra/SmallSubgroupIpa commitments
890 libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:grand_sum_commitment");
891 libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");
892
893 // Run Shplemini - verifier uses the corrupted evaluation received from the transcript
894 auto shplemini_output = ShpleminiVerifier::compute_batch_opening_claim(mock_claims.claim_batcher,
895 mle_opening_point,
896 this->vk().get_g1_identity(),
897 verifier_transcript,
898 {},
899 libra_commitments,
900 libra_evaluation);
901
902 // Verify that consistency_checked is false due to corrupted Libra evaluation
903 EXPECT_FALSE(shplemini_output.consistency_checked);
904}
905
913template <typename TypeParam>
915 typename ShpleminiTest<TypeParam>::TamperedPolynomial tamper_polynomial,
916 typename ShpleminiTest<TypeParam>::TamperedCommitment tamper_commitment,
917 bool expected_consistency_checked)
918{
919 using TamperedPolynomial = typename ShpleminiTest<TypeParam>::TamperedPolynomial;
920 using TamperedCommitment = typename ShpleminiTest<TypeParam>::TamperedCommitment;
921 using ZKData = ZKSumcheckData<TypeParam>;
922 using Curve = typename TypeParam::Curve;
923 using ShpleminiProver = ShpleminiProver_<Curve>;
924 constexpr bool HasZK = true;
925 using ShpleminiVerifier = ShpleminiVerifier_<Curve, HasZK>;
926 using Fr = typename Curve::ScalarField;
927 using Commitment = typename Curve::AffineElement;
928 using CK = typename TypeParam::CommitmentKey;
929
930 auto prover_transcript = TypeParam::Transcript::test_prover_init_empty();
931
932 static constexpr size_t log_subgroup_size = static_cast<size_t>(numeric::get_msb(Curve::SUBGROUP_SIZE));
933 CK ck = create_commitment_key<CK>(std::max<size_t>(test->n, 1ULL << (log_subgroup_size + 1)));
934
935 ZKData zk_sumcheck_data(test->log_n, prover_transcript, ck);
936 std::vector<Fr> mle_opening_point = test->random_evaluation_point(test->log_n);
937
938 MockClaimGenerator<Curve> mock_claims(test->n, test->num_polynomials, test->num_shiftable, mle_opening_point, ck);
939
941 zk_sumcheck_data, mle_opening_point, test->log_n);
942
943 prover_transcript->send_to_verifier("Libra:claimed_evaluation", claimed_inner_product);
944
945 SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
946 zk_sumcheck_data, mle_opening_point, claimed_inner_product, prover_transcript, ck);
947 small_subgroup_ipa_prover.prove();
948
949 auto witness_polynomials = small_subgroup_ipa_prover.get_witness_polynomials();
950
951 // Optionally tamper with a witness polynomial
952 if (tamper_polynomial != TamperedPolynomial::None) {
953 witness_polynomials[static_cast<size_t>(tamper_polynomial)].at(0) += Fr::random_element();
954 }
955
956 // Generate opening proof material for the possibly tampered witness polynomials.
957 const auto opening_claim = ShpleminiProver::prove(
958 test->n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript, witness_polynomials);
959
960 KZG<Curve>::compute_opening_proof(test->ck(), opening_claim, prover_transcript);
961
962 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
963
964 std::array<Commitment, NUM_SMALL_IPA_COMMITMENTS> libra_commitments = {};
965 libra_commitments[0] =
966 verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");
967
968 [[maybe_unused]] const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
969 [[maybe_unused]] const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
970 const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");
971
972 libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:grand_sum_commitment");
973 libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");
974
975 // Optionally tamper with a commitment
976 if (tamper_commitment != TamperedCommitment::None) {
977 auto idx = static_cast<size_t>(tamper_commitment);
978 libra_commitments[idx] = libra_commitments[idx] + Commitment::one();
979 }
980
981 auto [batch_opening_claim, consistency_checked] =
982 ShpleminiVerifier::compute_batch_opening_claim(mock_claims.claim_batcher,
983 mle_opening_point,
984 test->vk().get_g1_identity(),
985 verifier_transcript,
986 {},
987 libra_commitments,
988 libra_evaluation);
989
990 EXPECT_EQ(consistency_checked, expected_consistency_checked);
991
992 // PCS verification should always fail when tampering occurred
993 const auto pairing_points =
994 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
995 EXPECT_FALSE(pairing_points.check());
996}
997
1001TYPED_TEST(ShpleminiTest, LibraQuotientPolynomialTamperingCausesVerificationFailure)
1002{
1003 using TamperedPolynomial = typename TestFixture::TamperedPolynomial;
1004 using TamperedCommitment = typename TestFixture::TamperedCommitment;
1005 // Consistency check fails because Q(r) is wrong
1007 this, TamperedPolynomial::Quotient, TamperedCommitment::None, /*expected_consistency_checked=*/false);
1008}
1009
1013TYPED_TEST(ShpleminiTest, LibraQuotientCommitmentTamperingCausesVerificationFailure)
1014{
1015 using TamperedPolynomial = typename TestFixture::TamperedPolynomial;
1016 using TamperedCommitment = typename TestFixture::TamperedCommitment;
1017 // Consistency check passes because evaluations are honest
1019 this, TamperedPolynomial::None, TamperedCommitment::Quotient, /*expected_consistency_checked=*/true);
1020}
1021
1025TYPED_TEST(ShpleminiTest, LibraGrandSumCommitmentTamperingCausesVerificationFailure)
1026{
1027 using TamperedPolynomial = typename TestFixture::TamperedPolynomial;
1028 using TamperedCommitment = typename TestFixture::TamperedCommitment;
1029 // Consistency check passes because evaluations are honest
1031 this, TamperedPolynomial::None, TamperedCommitment::GrandSum, /*expected_consistency_checked=*/true);
1032}
1033
1037TYPED_TEST(ShpleminiTest, LibraConcatenatedPolynomialTamperingCausesVerificationFailure)
1038{
1039 using TamperedPolynomial = typename TestFixture::TamperedPolynomial;
1040 using TamperedCommitment = typename TestFixture::TamperedCommitment;
1041 // Consistency check fails because G(r) is wrong
1043 this, TamperedPolynomial::Concatenated, TamperedCommitment::None, /*expected_consistency_checked=*/false);
1044}
1045
1049TYPED_TEST(ShpleminiTest, LibraConcatenatedCommitmentTamperingCausesVerificationFailure)
1050{
1051 using TamperedPolynomial = typename TestFixture::TamperedPolynomial;
1052 using TamperedCommitment = typename TestFixture::TamperedCommitment;
1053 // Consistency check passes because evaluations are honest
1055 this, TamperedPolynomial::None, TamperedCommitment::Concatenated, /*expected_consistency_checked=*/true);
1056}
1057
1067TYPED_TEST(ShpleminiTest, SmallSubgroupIPABoundaryOpeningRejectsForgedInnerProduct)
1068{
1069 using ZKData = ZKSumcheckData<TypeParam>;
1070 using Curve = typename TypeParam::Curve;
1071 constexpr bool HasZK = true;
1072 using ShpleminiVerifier = ShpleminiVerifier_<Curve, HasZK>;
1073 using Fr = typename Curve::ScalarField;
1074 using Commitment = typename Curve::AffineElement;
1075 using CK = typename TypeParam::CommitmentKey;
1076
1077 static constexpr size_t SUBGROUP_SIZE = TypeParam::SUBGROUP_SIZE;
1078 auto prover_transcript = TypeParam::Transcript::test_prover_init_empty();
1079
1080 static constexpr size_t log_subgroup_size = static_cast<size_t>(numeric::get_msb(SUBGROUP_SIZE));
1081 CK ck = create_commitment_key<CK>(std::max<size_t>(this->n, 1ULL << (log_subgroup_size + 1)));
1082
1083 ZKData zk_sumcheck_data(this->log_n, prover_transcript, ck);
1084 std::vector<Fr> mle_opening_point = this->random_evaluation_point(this->log_n);
1085 MockClaimGenerator<Curve> mock_claims(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck);
1086
1088 zk_sumcheck_data, mle_opening_point, this->log_n);
1089
1090 const Fr forged_inner_product = this->run_forged_small_ipa_prover(
1091 prover_transcript, ck, zk_sumcheck_data, mle_opening_point, mock_claims, honest_inner_product);
1092
1093 // ---- Verifier ------------------------------------------------------------------------------
1094 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
1095
1096 std::array<Commitment, NUM_SMALL_IPA_COMMITMENTS> libra_commitments = {};
1097 libra_commitments[0] =
1098 verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");
1099 [[maybe_unused]] const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
1100 [[maybe_unused]] const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
1101 const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");
1102 libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:grand_sum_commitment");
1103 libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");
1104
1105 EXPECT_EQ(libra_evaluation, forged_inner_product);
1106 EXPECT_NE(libra_evaluation, honest_inner_product);
1107
1108 auto [batch_opening_claim, consistency_checked] =
1109 ShpleminiVerifier::compute_batch_opening_claim(mock_claims.claim_batcher,
1110 mle_opening_point,
1111 this->vk().get_g1_identity(),
1112 verifier_transcript,
1113 {},
1114 libra_commitments,
1115 libra_evaluation);
1116
1117 // The algebraic identity at r holds for (A_forged, Q_forged, forged_inner_product) by construction.
1118 EXPECT_TRUE(consistency_checked);
1119
1120 // The Shplemini batched opening MUST reject because the committed [A_forged] does not actually evaluate to 0
1121 // at X = 1 — it evaluates to delta != 0 — contradicting the verifier's hardcoded boundary claim.
1122 const auto pairing_points =
1123 KZG<Curve>::reduce_verify_batch_opening_claim(std::move(batch_opening_claim), verifier_transcript);
1124 EXPECT_FALSE(pairing_points.check());
1125}
1126
1127} // namespace bb
#define BB_ASSERT_EQ(actual, expected,...)
Definition assert.hpp:83
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
static std::shared_ptr< BaseTranscript > test_prover_init_empty()
For testing: initializes transcript with some arbitrary data so that a challenge can be generated aft...
static std::shared_ptr< BaseTranscript > test_verifier_init_empty(const std::shared_ptr< BaseTranscript > &transcript)
For testing: initializes transcript based on proof data then receives junk data produced by BaseTrans...
CommitmentKey object over a pairing group 𝔾₁.
std::vector< Fr > random_evaluation_point(const size_t num_variables)
curve::Grumpkin Curve
bb::CommitmentKey< Curve > CommitmentKey
Polynomial compute_batched(const Fr &challenge)
Compute batched polynomial A₀ = F + G/X as the linear combination of all polynomials to be opened,...
Definition gemini.hpp:163
std::pair< Polynomial, Polynomial > compute_partially_evaluated_batch_polynomials(const Fr &r_challenge)
Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - G/r.
Definition gemini.hpp:206
static std::vector< Fr > compute_fold_pos_evaluations(const Fr &batched_evaluation, std::span< const Fr > evaluation_point, std::span< const Fr > challenge_powers, std::span< const Fr > fold_neg_evals)
Compute .
Definition gemini.hpp:318
static PairingPointsType reduce_verify_batch_opening_claim(BatchOpeningClaim< Curve > batch_opening_claim, const std::shared_ptr< Transcript > &transcript, const size_t expected_final_msm_size=0)
Computes the input points for the pairing check needed to verify a KZG opening claim obtained from a ...
Definition kzg.hpp:132
static void compute_opening_proof(const CK &ck, const ProverOpeningClaim< Curve > &opening_claim, const std::shared_ptr< Transcript > &prover_trancript)
Computes the KZG commitment to an opening proof polynomial at a single evaluation point.
Definition kzg.hpp:44
static Polynomial random(size_t size, size_t start_index=0)
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
static constexpr size_t n
static constexpr size_t log_n
static constexpr size_t n
Fr run_forged_small_ipa_prover(const std::shared_ptr< typename Flavor::Transcript > &prover_transcript, CK &ck, ZKSumcheckData< Flavor > &zk_sumcheck_data, std::vector< Fr > &mle_opening_point, MockClaimGenerator< typename Flavor::Curve > &mock_claims, const Fr &honest_inner_product)
Simulated malicious prover for the Shplemini + SmallSubgroupIPA soundness regression.
typename Flavor::Curve::ScalarField Fr
static constexpr size_t num_polynomials
typename Flavor::CommitmentKey CK
typename Flavor::Curve::AffineElement Commitment
static constexpr size_t log_n
static constexpr size_t sumcheck_univariate_length
static constexpr size_t num_shiftable
typename Flavor::Curve::Element GroupElement
Shplonk Verifier.
Definition shplonk.hpp:367
A Curve-agnostic ZK protocol to prove inner products of small vectors.
std::array< bb::Polynomial< FF >, NUM_SMALL_IPA_COMMITMENTS > get_witness_polynomials() const
void compute_grand_sum_polynomial()
Computes the grand sum polynomial .
void compute_grand_sum_identity_quotient()
Efficiently compute the quotient of the grand sum identity polynomial by .
static FF compute_claimed_inner_product(ZKSumcheckData< Flavor > &zk_sumcheck_data, const std::vector< FF > &multivariate_challenge, const size_t &log_circuit_size)
For test purposes: Compute the sum of the Libra constant term and Libra univariates evaluated at Sumc...
void compute_grand_sum_identity_polynomial()
Compute , where is the fixed generator of .
void prove()
Compute the derived witnesses and and commit to them.
typename Group::element Element
Definition grumpkin.hpp:63
static constexpr size_t SUBGROUP_SIZE
Definition grumpkin.hpp:74
static constexpr ScalarField subgroup_generator_inverse
Definition grumpkin.hpp:81
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
static constexpr ScalarField subgroup_generator
Definition grumpkin.hpp:79
bool expected_result
std::vector< Fr > powers_of_evaluation_challenge(const Fr &r, const size_t num_squares)
Compute squares of folding challenge r.
Definition gemini.hpp:96
std::vector< Fr > powers_of_rho(const Fr &rho, const size_t num_powers)
Compute powers of challenge ρ
Definition gemini.hpp:76
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
constexpr size_t NUM_SMALL_IPA_COMMITMENTS
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
void run_libra_tampering_test(ShpleminiTest< TypeParam > *test, typename ShpleminiTest< TypeParam >::TamperedPolynomial tamper_polynomial, typename ShpleminiTest< TypeParam >::TamperedCommitment tamper_commitment, bool expected_consistency_checked)
Helper to run a Libra tampering test with configurable tampering options.
CommitmentKey< Curve > ck
::testing::Types< BN254Settings > TestSettings
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
void update_batch_mul_inputs_and_batched_evaluation(std::vector< Commitment > &commitments, std::vector< Fr > &scalars, Fr &batched_evaluation, const Fr &rho)
Append the commitments and scalars from each batch of claims to the Shplemini vectors which subsequen...
void compute_scalars_for_each_batch(std::span< const Fr > inverted_vanishing_evals, const Fr &nu_challenge, const Fr &r_challenge)
Compute scalars used to batch each set of claims, excluding contribution from batching challenge \rho...
Constructs random polynomials, computes commitments and corresponding evaluations.
std::vector< bb::Polynomial< Fr > > round_univariates
std::vector< Commitment > sumcheck_commitments
std::vector< std::array< Fr, 3 > > sumcheck_evaluations
This structure is created to contain various polynomials and constants required by ZK Sumcheck.
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_VF_LOAD_LIMBS * this