Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
ipa.test.cpp
Go to the documentation of this file.
1
7using namespace bb;
8
9namespace {
11
12class IPATest : public CommitmentTest<Curve> {
13 public:
14 using Fr = typename Curve::ScalarField;
15 using GroupElement = typename Curve::Element;
16 using CK = CommitmentKey<Curve>;
19 using Commitment = typename Curve::AffineElement;
20
21 using ShplonkProver = ShplonkProver_<Curve>;
22 using GeminiProver = GeminiProver_<Curve>;
23 using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
24 using ClaimBatcher = ClaimBatcher_<Curve>;
25 using ClaimBatch = ClaimBatcher::Batch;
26
27 static CK ck;
28 static VK vk;
29
30 static constexpr size_t log_n = 7;
31
33
34 static constexpr size_t n = 1UL << log_n;
35
36 static void SetUpTestSuite()
37 {
38 ck = create_commitment_key<CK>(n);
39 vk = create_verifier_commitment_key<VK>();
40 }
41
42 struct ProofData {
44 NativeTranscript::Proof proof_data;
45 };
46
47 static ProofData generate_proof(const Polynomial& poly, const Fr& x)
48 {
49 Commitment commitment = ck.commit(poly);
50 auto eval = poly.evaluate(x);
51 auto prover_transcript = std::make_shared<NativeTranscript>();
52 PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript);
53 return { { { x, eval }, commitment }, prover_transcript->export_proof() };
54 }
55
56 static ProofData generate_random_proof() { return generate_proof(Polynomial::random(n), Fr::random_element()); }
57
58 struct ResultOfProveVerify {
59 bool result;
60 std::shared_ptr<NativeTranscript> prover_transcript;
61 std::shared_ptr<NativeTranscript> verifier_transcript;
62 };
63
64 static ResultOfProveVerify run_native_prove_verify(const Polynomial& poly, const Fr x)
65 {
66 Commitment commitment = ck.commit(poly);
67 auto eval = poly.evaluate(x);
68 // initialize empty prover transcript
69 auto prover_transcript = std::make_shared<NativeTranscript>();
70 PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript);
71
72 // initialize verifier transcript from proof data
73 auto verifier_transcript = std::make_shared<NativeTranscript>(prover_transcript->export_proof());
74 // the native reduce_verify does a _complete_ IPA proof and returns whether or not the checks pass.
75 bool result = PCS::reduce_verify(vk, { { x, eval }, commitment }, verifier_transcript);
76 return { result, prover_transcript, verifier_transcript };
77 }
78};
79} // namespace
80
81#define IPA_TEST
82#include "ipa.hpp"
83
84// Opening tests, i.e., check completeness for prove-and-verify.
85//
86// poly is zero, point is random
87TEST_F(IPATest, OpenZeroPolynomial)
88{
89 Polynomial poly(n);
90 auto x = this->random_element();
91 bool result = run_native_prove_verify(poly, x).result;
92 EXPECT_TRUE(result);
93}
94
95TEST_F(IPATest, OpenManyZerosPolynomial)
96{
97 // polynomial with zero odd coefficients and random even coefficients
98 Polynomial poly_even(n);
99 // polynomial with zero even coefficients and random odd coefficients
100 Polynomial poly_odd(n);
101 for (size_t i = 0; i < n / 2; ++i) {
102 poly_even.at(2 * i) = this->random_element();
103 poly_odd.at(2 * i + 1) = this->random_element();
104 }
105 auto x = this->random_element();
106 bool result_even = run_native_prove_verify(poly_even, x).result;
107 bool result_odd = run_native_prove_verify(poly_odd, x).result;
108 EXPECT_TRUE(result_even && result_odd);
109}
110
111// poly is random, point is zero
112TEST_F(IPATest, OpenAtZero)
113{
114 // generate a random polynomial, degree needs to be a power of two
115 auto poly = Polynomial::random(n);
116 const Fr x = Fr::zero();
117 bool result = run_native_prove_verify(poly, x).result;
118 EXPECT_TRUE(result);
119}
120
121// poly and point are random
122TEST_F(IPATest, Open)
123{
124 // generate a random polynomial, degree needs to be a power of two
125 auto poly = Polynomial::random(n);
126 auto x = this->random_element();
127 auto result_of_prove_verify = run_native_prove_verify(poly, x);
128 EXPECT_TRUE(result_of_prove_verify.result);
129
130 EXPECT_EQ(result_of_prove_verify.prover_transcript->get_manifest(),
131 result_of_prove_verify.verifier_transcript->get_manifest());
132}
133
134// poly and point are random, condition on the fact that the evaluation is zero.
135TEST_F(IPATest, OpeningValueZero)
136{
137 // generate random polynomial
138 auto poly = Polynomial::random(n);
139 auto x = this->random_element();
140 auto initial_evaluation = poly.evaluate(x);
141 auto change_in_linear_coefficient = initial_evaluation / x;
142 // change linear coefficient so that poly(x) == 0.
143 poly.at(1) -= change_in_linear_coefficient;
144
145 EXPECT_EQ(poly.evaluate(x), Fr::zero());
146 bool result = run_native_prove_verify(poly, x).result;
147 EXPECT_TRUE(result);
148}
149
150// Tests that "artificially" mutate the Transcript. This uses the type `MockTranscript`.
151
152namespace bb {
153#if !defined(__wasm__)
154// This test ensures that IPA throws or aborts when a challenge is zero, since it breaks the logic of the argument
155TEST_F(IPATest, ChallengesAreZero)
156{
157 // generate a random polynomial, degree needs to be a power of two
158 auto poly = Polynomial::random(n);
159 auto [x, eval] = this->random_eval(poly);
160 auto commitment = ck.commit(poly);
161 const OpeningPair<Curve> opening_pair = { x, eval };
162 const OpeningClaim<Curve> opening_claim{ opening_pair, commitment };
163
164 // initialize an empty mock transcript
165 auto transcript = std::make_shared<MockTranscript>();
166 const size_t num_challenges = numeric::get_msb(n) + 1;
167 std::vector<uint256_t> random_vector(num_challenges);
168
169 // Generate a random element vector with challenges
170 for (size_t i = 0; i < num_challenges; i++) {
171 random_vector[i] = Fr::random_element();
172 }
173
174 // Compute opening proofs several times, where each time a different challenge is equal to zero. Should cause
175 // exceptions
176 for (size_t i = 0; i < num_challenges; i++) {
177 auto new_random_vector = random_vector;
178 new_random_vector[i] = Fr::zero();
179 transcript->initialize(new_random_vector);
180 EXPECT_ANY_THROW(PCS::compute_opening_proof<MockTranscript>(ck, { poly, opening_pair }, transcript));
181 }
182 // Fill out a vector of affine elements that the verifier receives from the prover with generators (we don't care
183 // about them right now)
184 std::vector<Curve::AffineElement> lrs(num_challenges * 2);
185 for (size_t i = 0; i < num_challenges * 2; i++) {
186 lrs[i] = Curve::AffineElement::one();
187 }
188 // Verify proofs several times, where each time a different challenge is equal to zero. Should cause
189 // exceptions
190 for (size_t i = 0; i < num_challenges; i++) {
191 auto new_random_vector = random_vector;
192 new_random_vector[i] = Fr::zero();
193 transcript->initialize(new_random_vector, lrs, { uint256_t(n) });
194 EXPECT_ANY_THROW(PCS::reduce_verify(vk, opening_claim, transcript));
195 }
196}
197
198// This test checks that if the vector \vec{a_new} becomes zero after one round, it doesn't break IPA.
199TEST_F(IPATest, AIsZeroAfterOneRound)
200{
201 // generate a random polynomial of degree < n / 2
202 auto poly = Polynomial(n);
203 for (size_t i = 0; i < n / 2; i++) {
204 poly.at(i) = Fr::random_element();
205 poly.at(i + (n / 2)) = poly[i];
206 }
207 auto [x, eval] = this->random_eval(poly);
208 auto commitment = ck.commit(poly);
209 const OpeningPair<Curve> opening_pair = { x, eval };
210 const OpeningClaim<Curve> opening_claim{ opening_pair, commitment };
211
212 // initialize an empty mock transcript
213 auto transcript = std::make_shared<MockTranscript>();
214 const size_t num_challenges = log_n + 1;
215 std::vector<uint256_t> random_vector(num_challenges);
216
217 // Generate a random element vector with challenges
218 for (size_t i = 0; i < num_challenges; i++) {
219 random_vector[i] = Fr::random_element();
220 }
221 // Substitute the first folding challenge with -1
222 random_vector[1] = -Fr::one();
223
224 // Put the challenges in the transcript
225 transcript->initialize(random_vector);
226
227 // Compute opening proof
228 PCS::compute_opening_proof<MockTranscript>(ck, { poly, opening_pair }, transcript);
229
230 // Reset indices
231 transcript->reset_indices();
232
233 // Verify
234 EXPECT_TRUE(PCS::reduce_verify(vk, opening_claim, transcript));
235}
236#endif
237} // namespace bb
238
239// Tests of batched MLPCS, where IPA is the final univariate commitment scheme.
240
241// Shplemini + IPA. Two random polynomials, no shifts.
242TEST_F(IPATest, ShpleminiIPAWithoutShift)
243{
244 // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random
245 // point.
246 auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
247
248 MockClaimGenerator mock_claims(n,
249 /*num_polynomials*/ 2,
250 /*num_to_be_shifted*/ 0,
251 mle_opening_point,
252 ck);
253
254 auto prover_transcript = NativeTranscript::test_prover_init_empty();
255
256 // Run the full prover PCS protocol:
257 // Compute:
258 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
259 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
260 auto prover_opening_claims =
261 GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript);
262
263 const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript);
264 PCS::compute_opening_proof(ck, opening_claim, prover_transcript);
265
266 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
267
268 std::array<Fr, log_n> padding_indicator_array;
269 std::ranges::fill(padding_indicator_array, Fr{ 1 });
270
271 const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(padding_indicator_array,
272 mock_claims.claim_batcher,
273 mle_opening_point,
274 vk.get_g1_identity(),
275 verifier_transcript)
276 .batch_opening_claim;
277
278 auto result = PCS::reduce_verify_batch_opening_claim(batch_opening_claim, vk, verifier_transcript);
279
280 EXPECT_EQ(result, true);
281}
282// Shplemini + IPA. Five polynomials, one of which is shifted.
283TEST_F(IPATest, ShpleminiIPAWithShift)
284{
285 // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random
286 // point.
287 auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
288 MockClaimGenerator mock_claims(n,
289 /*num_polynomials*/ 4,
290 /*num_to_be_shifted*/ 1,
291 mle_opening_point,
292 ck);
293 auto prover_transcript = NativeTranscript::test_prover_init_empty();
294
295 // Run the full prover PCS protocol:
296
297 // Compute:
298 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
299 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
300 auto prover_opening_claims =
301 GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript);
302 const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript);
303 PCS::compute_opening_proof(ck, opening_claim, prover_transcript);
304
305 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
306
307 std::array<Fr, log_n> padding_indicator_array;
308 std::ranges::fill(padding_indicator_array, Fr{ 1 });
309
310 const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(padding_indicator_array,
311 mock_claims.claim_batcher,
312 mle_opening_point,
313 vk.get_g1_identity(),
314 verifier_transcript)
315 .batch_opening_claim;
316
317 auto result = PCS::reduce_verify_batch_opening_claim(batch_opening_claim, vk, verifier_transcript);
318 // auto result = PCS::reduce_verify(vk, shplonk_verifier_claim, verifier_transcript);
319
320 EXPECT_EQ(result, true);
321}
322// Test `ShpleminiVerifier::remove_shifted_commitments`. Four polynomials, two of which are shifted.
323TEST_F(IPATest, ShpleminiIPAShiftsRemoval)
324{
325 // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random
326 // point.
327 auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
328 MockClaimGenerator mock_claims(n,
329 /*num_polynomials*/ 4,
330 /*num_to_be_shifted*/ 2,
331 mle_opening_point,
332 ck);
333
334 auto prover_transcript = NativeTranscript::test_prover_init_empty();
335
336 // Run the full prover PCS protocol:
337
338 // Compute:
339 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
340 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
341 auto prover_opening_claims =
342 GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript);
343
344 const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript);
345 PCS::compute_opening_proof(ck, opening_claim, prover_transcript);
346
347 // the index of the first commitment to a polynomial to be shifted in the union of unshifted_commitments and
348 // shifted_commitments. in our case, it is poly2
349 const size_t to_be_shifted_commitments_start = 2;
350 // the index of the first commitment to a shifted polynomial in the union of unshifted_commitments and
351 // shifted_commitments. in our case, it is the second occurence of poly2
352 const size_t shifted_commitments_start = 4;
353 // number of shifted polynomials
354 const size_t num_shifted_commitments = 2;
355 const RepeatedCommitmentsData repeated_commitments =
356 RepeatedCommitmentsData(to_be_shifted_commitments_start, shifted_commitments_start, num_shifted_commitments);
357 // since commitments to poly2, poly3 and their shifts are the same group elements, we simply combine the scalar
358 // multipliers of commitment2 and commitment3 in one place and remove the entries of the commitments and scalars
359 // vectors corresponding to the "shifted" commitment
360 auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript);
361
362 std::array<Fr, log_n> padding_indicator_array;
363 std::ranges::fill(padding_indicator_array, Fr{ 1 });
364
365 const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(padding_indicator_array,
366 mock_claims.claim_batcher,
367 mle_opening_point,
368 vk.get_g1_identity(),
369 verifier_transcript,
370 repeated_commitments)
371 .batch_opening_claim;
372
373 auto result = PCS::reduce_verify_batch_opening_claim(batch_opening_claim, vk, verifier_transcript);
374 EXPECT_EQ(result, true);
375}
376
377// Batch IPA verification tests
378
379TEST_F(IPATest, BatchVerifyTwoValidProofs)
380{
381 auto [claim1, proof1] = generate_random_proof();
382 auto [claim2, proof2] = generate_random_proof();
383
384 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
387
388 EXPECT_TRUE(PCS::batch_reduce_verify(vk, claims, transcripts));
389}
390
391TEST_F(IPATest, BatchVerifySingleProof)
392{
393 // Degenerate case: batch verify with N=1 should match reduce_verify
394 auto [claim, proof_data] = generate_random_proof();
395
396 EXPECT_TRUE(PCS::reduce_verify(vk, claim, std::make_shared<NativeTranscript>(proof_data)));
397 EXPECT_TRUE(PCS::batch_reduce_verify(vk, { claim }, { std::make_shared<NativeTranscript>(proof_data) }));
398}
399
400TEST_F(IPATest, BatchVerifyTamperedProof)
401{
402 auto [claim1, proof1] = generate_random_proof();
403 auto [claim2, proof2] = generate_random_proof();
404
405 // Tamper with the second claim's evaluation
406 claim2.opening_pair.evaluation += Fr::one();
407
408 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
411
412 EXPECT_FALSE(PCS::batch_reduce_verify(vk, claims, transcripts));
413}
414
415TEST_F(IPATest, BatchVerifyRejectsClaimTranscriptMismatch)
416{
417 // Batch verification must bind each claim to its own transcript. Two individually valid proofs
418 // should pass when correctly paired but fail when the transcripts are swapped, since each
419 // transcript's round messages (L_j, R_j) are coupled to its claim's commitment in C_zero.
420 auto [claim1, proof1] = generate_random_proof();
421 auto [claim2, proof2] = generate_random_proof();
422
423 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
424
425 // Correct pairing: passes
426 EXPECT_TRUE(PCS::batch_reduce_verify(
428
429 // Swapped pairing: fails
430 EXPECT_FALSE(PCS::batch_reduce_verify(
432}
433
434typename IPATest::CK IPATest::ck;
435typename IPATest::VK IPATest::vk;
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...
std::vector< DataType > Proof
CommitmentKey object over a pairing group 𝔾₁.
static void SetUpTestSuite()
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:86
Unverified claim (C,r,v) for some witness polynomial p(X) such that.
Definition claim.hpp:55
Opening pair (r,v) for some witness polynomial p(X) such that p(r) = v.
Definition claim.hpp:21
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
static Polynomial random(size_t size, size_t start_index=0)
Fr evaluate(const Fr &z) const
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
Shplonk Prover.
Definition shplonk.hpp:36
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
typename Group::element Element
Definition grumpkin.hpp:64
typename Group::affine_element AffineElement
Definition grumpkin.hpp:65
void generate_proof(uint256_t inputs[])
TEST_F(IPATest, OpenZeroPolynomial)
Definition ipa.test.cpp:87
constexpr T get_msb(const T in)
Definition get_msb.hpp:49
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:155
CommitmentKey< Curve > ck
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Logic to support batching opening claims for unshifted and shifted polynomials in Shplemini.
Constructs random polynomials, computes commitments and corresponding evaluations.
static constexpr field one()
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()