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 static CK ck;
22 static VK vk;
23
24 static constexpr size_t log_n = 7;
25
27
28 static constexpr size_t n = 1UL << log_n;
29
30 // IPA round challenges are 127-bit limbs in production (transcript split_challenge). The mock
31 // transcript bypasses that, so mask explicitly to honor batch_two_round_fold's precondition.
32 static uint256_t random_127_bit_challenge()
33 {
35 c.data[2] = 0;
36 c.data[3] = 0;
37 c.data[1] &= 0x7FFFFFFFFFFFFFFFULL;
38 return c;
39 }
40
41 static void SetUpTestSuite()
42 {
43 ck = create_commitment_key<CK>(n);
44 vk = create_verifier_commitment_key<VK>();
45 }
46
47 struct ProofData {
49 NativeTranscript::Proof proof_data;
50 };
51
52 static ProofData generate_proof(const Polynomial& poly, const Fr& x)
53 {
54 Commitment commitment = ck.commit(poly);
55 auto eval = poly.evaluate(x);
56 auto prover_transcript = std::make_shared<NativeTranscript>();
57 PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript);
58 return { { { x, eval }, commitment }, prover_transcript->export_proof() };
59 }
60
61 static ProofData generate_random_proof() { return generate_proof(Polynomial::random(n), Fr::random_element()); }
62
63 struct ResultOfProveVerify {
64 bool result;
65 std::shared_ptr<NativeTranscript> prover_transcript;
66 std::shared_ptr<NativeTranscript> verifier_transcript;
67 };
68
69 static ResultOfProveVerify run_native_prove_verify(const Polynomial& poly, const Fr x)
70 {
71 Commitment commitment = ck.commit(poly);
72 auto eval = poly.evaluate(x);
73 // initialize empty prover transcript
74 auto prover_transcript = std::make_shared<NativeTranscript>();
75 PCS::compute_opening_proof(ck, { poly, { x, eval } }, prover_transcript);
76
77 // initialize verifier transcript from proof data
78 auto verifier_transcript = std::make_shared<NativeTranscript>(prover_transcript->export_proof());
79 // the native reduce_verify does a _complete_ IPA proof and returns whether or not the checks pass.
80 bool result = PCS::reduce_verify(vk, { { x, eval }, commitment }, verifier_transcript);
81 return { result, prover_transcript, verifier_transcript };
82 }
83};
84} // namespace
85
86#define IPA_TEST
87#include "ipa.hpp"
88
89// Opening tests, i.e., check completeness for prove-and-verify.
90//
91// poly is zero, point is random
92TEST_F(IPATest, OpenZeroPolynomial)
93{
94 Polynomial poly(n);
95 auto x = this->random_element();
96 bool result = run_native_prove_verify(poly, x).result;
97 EXPECT_TRUE(result);
98}
99
100TEST_F(IPATest, OpenManyZerosPolynomial)
101{
102 // polynomial with zero odd coefficients and random even coefficients
103 Polynomial poly_even(n);
104 // polynomial with zero even coefficients and random odd coefficients
105 Polynomial poly_odd(n);
106 for (size_t i = 0; i < n / 2; ++i) {
107 poly_even.at(2 * i) = this->random_element();
108 poly_odd.at(2 * i + 1) = this->random_element();
109 }
110 auto x = this->random_element();
111 bool result_even = run_native_prove_verify(poly_even, x).result;
112 bool result_odd = run_native_prove_verify(poly_odd, x).result;
113 EXPECT_TRUE(result_even && result_odd);
114}
115
116// poly is random, point is zero
117TEST_F(IPATest, OpenAtZero)
118{
119 // generate a random polynomial, degree needs to be a power of two
120 auto poly = Polynomial::random(n);
121 const Fr x = Fr::zero();
122 bool result = run_native_prove_verify(poly, x).result;
123 EXPECT_TRUE(result);
124}
125
126// poly and point are random
127TEST_F(IPATest, Open)
128{
129 // generate a random polynomial, degree needs to be a power of two
130 auto poly = Polynomial::random(n);
131 auto x = this->random_element();
132 auto result_of_prove_verify = run_native_prove_verify(poly, x);
133 EXPECT_TRUE(result_of_prove_verify.result);
134
135 EXPECT_EQ(result_of_prove_verify.prover_transcript->get_manifest(),
136 result_of_prove_verify.verifier_transcript->get_manifest());
137}
138
139// poly and point are random, condition on the fact that the evaluation is zero.
140TEST_F(IPATest, OpeningValueZero)
141{
142 // generate random polynomial
143 auto poly = Polynomial::random(n);
144 auto x = this->random_element();
145 auto initial_evaluation = poly.evaluate(x);
146 auto change_in_linear_coefficient = initial_evaluation / x;
147 // change linear coefficient so that poly(x) == 0.
148 poly.at(1) -= change_in_linear_coefficient;
149
150 EXPECT_EQ(poly.evaluate(x), Fr::zero());
151 bool result = run_native_prove_verify(poly, x).result;
152 EXPECT_TRUE(result);
153}
154
155// Tests that "artificially" mutate the Transcript. This uses the type `MockTranscript`.
156
157namespace bb {
158#if !defined(__wasm__)
159// This test ensures that IPA throws or aborts when a challenge is zero, since it breaks the logic of the argument
160TEST_F(IPATest, ChallengesAreZero)
161{
162 // generate a random polynomial, degree needs to be a power of two
163 auto poly = Polynomial::random(n);
164 auto [x, eval] = this->random_eval(poly);
165 auto commitment = ck.commit(poly);
166 const OpeningPair<Curve> opening_pair = { x, eval };
167 const OpeningClaim<Curve> opening_claim{ opening_pair, commitment };
168
169 // initialize an empty mock transcript
170 auto transcript = std::make_shared<MockTranscript>();
171 const size_t num_challenges = numeric::get_msb(n) + 1;
172 std::vector<uint256_t> random_vector(num_challenges);
173
174 // Generate a random element vector with challenges
175 for (size_t i = 0; i < num_challenges; i++) {
176 random_vector[i] = random_127_bit_challenge();
177 }
178
179 // Compute opening proofs several times, where each time a different challenge is equal to zero. Should cause
180 // exceptions
181 for (size_t i = 0; i < num_challenges; i++) {
182 auto new_random_vector = random_vector;
183 new_random_vector[i] = Fr::zero();
184 transcript->initialize(new_random_vector);
185 EXPECT_ANY_THROW(PCS::compute_opening_proof<MockTranscript>(ck, { poly, opening_pair }, transcript));
186 }
187 // Fill out a vector of affine elements that the verifier receives from the prover with generators (we don't care
188 // about them right now)
189 std::vector<Curve::AffineElement> lrs(num_challenges * 2);
190 for (size_t i = 0; i < num_challenges * 2; i++) {
191 lrs[i] = Curve::AffineElement::one();
192 }
193 // Verify proofs several times, where each time a different challenge is equal to zero. Should cause
194 // exceptions
195 for (size_t i = 0; i < num_challenges; i++) {
196 auto new_random_vector = random_vector;
197 new_random_vector[i] = Fr::zero();
198 transcript->initialize(new_random_vector, lrs, { uint256_t(n) });
199 EXPECT_ANY_THROW(PCS::reduce_verify(vk, opening_claim, transcript));
200 }
201}
202
203// This test checks that if the vector \vec{a_new} becomes zero after one round, it doesn't break IPA.
204TEST_F(IPATest, AIsZeroAfterOneRound)
205{
206 // initialize a mock transcript with 127-bit challenges (production challenges are 127-bit limbs;
207 // the fused SRS fold asserts that). Index 0 is the generator challenge, index 1 the first
208 // folding challenge u.
209 auto transcript = std::make_shared<MockTranscript>();
210 const size_t num_challenges = log_n + 1;
211 std::vector<uint256_t> random_vector(num_challenges);
212 for (size_t i = 0; i < num_challenges; i++) {
213 random_vector[i] = random_127_bit_challenge();
214 }
215 const Fr u = Fr(random_vector[1]);
216
217 // Build the witness so a folds to zero after round 1: with a' = u^-1 a_lo + a_hi, set
218 // a_hi = -u^-1 a_lo, giving a' = 0.
219 const Fr neg_u_inv = -u.invert();
220 auto poly = Polynomial(n);
221 for (size_t i = 0; i < n / 2; i++) {
222 poly.at(i) = Fr::random_element();
223 poly.at(i + (n / 2)) = neg_u_inv * poly[i];
224 }
225 auto [x, eval] = this->random_eval(poly);
226 auto commitment = ck.commit(poly);
227 const OpeningPair<Curve> opening_pair = { x, eval };
228 const OpeningClaim<Curve> opening_claim{ opening_pair, commitment };
229
230 // Put the challenges in the transcript
231 transcript->initialize(random_vector);
232
233 // Compute opening proof
234 PCS::compute_opening_proof<MockTranscript>(ck, { poly, opening_pair }, transcript);
235
236 // Reset indices
237 transcript->reset_indices();
238
239 // Verify
240 EXPECT_TRUE(PCS::reduce_verify(vk, opening_claim, transcript));
241}
242#endif
243} // namespace bb
244
245// Tests of batched MLPCS, where IPA is the final univariate commitment scheme.
246
247// Batch IPA verification tests
248
249TEST_F(IPATest, BatchVerifyTwoValidProofs)
250{
251 auto [claim1, proof1] = generate_random_proof();
252 auto [claim2, proof2] = generate_random_proof();
253
254 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
257
258 EXPECT_TRUE(PCS::batch_reduce_verify(vk, claims, transcripts));
259}
260
261TEST_F(IPATest, BatchVerifySingleProof)
262{
263 // Degenerate case: batch verify with N=1 should match reduce_verify
264 auto [claim, proof_data] = generate_random_proof();
265
266 EXPECT_TRUE(PCS::reduce_verify(vk, claim, std::make_shared<NativeTranscript>(proof_data)));
267 EXPECT_TRUE(PCS::batch_reduce_verify(vk, { claim }, { std::make_shared<NativeTranscript>(proof_data) }));
268}
269
270TEST_F(IPATest, BatchVerifyRejectsTamperedGZero)
271{
272 auto [claim, proof_data] = generate_random_proof();
273 auto tampered_proof = proof_data;
274
275 constexpr size_t commitment_size = FrCodec::template calc_num_fields<Commitment>();
276 constexpr size_t g_zero_offset = 2 * log_n * commitment_size;
277 static_assert(g_zero_offset + commitment_size + FrCodec::template calc_num_fields<Fr>() == 4 * log_n + 4);
278 ASSERT_LE(g_zero_offset + commitment_size, tampered_proof.size());
279
280 Commitment wrong_g_zero = Commitment::one() * Fr(7);
281 auto wrong_g_zero_fields = FrCodec::serialize_to_fields<Commitment>(wrong_g_zero);
282 std::copy(wrong_g_zero_fields.begin(),
283 wrong_g_zero_fields.end(),
284 tampered_proof.begin() + static_cast<std::ptrdiff_t>(g_zero_offset));
285
286 EXPECT_FALSE(PCS::reduce_verify(vk, claim, std::make_shared<NativeTranscript>(tampered_proof)));
287 EXPECT_FALSE(PCS::batch_reduce_verify(vk, { claim }, { std::make_shared<NativeTranscript>(tampered_proof) }));
288}
289
290TEST_F(IPATest, BatchVerifyRejectsTamperedAZero)
291{
292 auto [claim, proof_data] = generate_random_proof();
293 auto tampered_proof = proof_data;
294
295 constexpr size_t commitment_size = FrCodec::template calc_num_fields<Commitment>();
296 constexpr size_t g_zero_offset = 2 * log_n * commitment_size;
297 constexpr size_t a_zero_offset = g_zero_offset + commitment_size;
298 static_assert(a_zero_offset + FrCodec::template calc_num_fields<Fr>() == 4 * log_n + 4);
299 ASSERT_LT(a_zero_offset, tampered_proof.size());
300
301 auto wrong_a_zero_fields = FrCodec::serialize_to_fields<Fr>(Fr(7));
302 std::copy(wrong_a_zero_fields.begin(),
303 wrong_a_zero_fields.end(),
304 tampered_proof.begin() + static_cast<std::ptrdiff_t>(a_zero_offset));
305
306 EXPECT_FALSE(PCS::reduce_verify(vk, claim, std::make_shared<NativeTranscript>(tampered_proof)));
307 EXPECT_FALSE(PCS::batch_reduce_verify(vk, { claim }, { std::make_shared<NativeTranscript>(tampered_proof) }));
308}
309
310TEST_F(IPATest, BatchVerifyTamperedProof)
311{
312 auto [claim1, proof1] = generate_random_proof();
313 auto [claim2, proof2] = generate_random_proof();
314
315 // Tamper with the second claim's evaluation
316 claim2.opening_pair.evaluation += Fr::one();
317
318 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
321
322 EXPECT_FALSE(PCS::batch_reduce_verify(vk, claims, transcripts));
323}
324
325TEST_F(IPATest, BatchVerifyRejectsClaimTranscriptMismatch)
326{
327 // Batch verification must bind each claim to its own transcript. Two individually valid proofs
328 // should pass when correctly paired but fail when the transcripts are swapped, since each
329 // transcript's round messages (L_j, R_j) are coupled to its claim's commitment in C_zero.
330 auto [claim1, proof1] = generate_random_proof();
331 auto [claim2, proof2] = generate_random_proof();
332
333 std::vector<OpeningClaim<Curve>> claims = { claim1, claim2 };
334
335 // Correct pairing: passes
336 EXPECT_TRUE(PCS::batch_reduce_verify(
338
339 // Swapped pairing: fails
340 EXPECT_FALSE(PCS::batch_reduce_verify(
342}
343
344typename IPATest::CK IPATest::ck;
345typename IPATest::VK IPATest::vk;
std::vector< DataType > Proof
CommitmentKey object over a pairing group 𝔾₁.
static void SetUpTestSuite()
IPA (inner product argument) commitment scheme class.
Definition ipa.hpp:87
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
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[...
Representation of the Grumpkin Verifier Commitment Key inside a bn254 circuit.
typename Group::element Element
Definition grumpkin.hpp:63
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
void generate_proof(uint256_t inputs[])
TEST_F(IPATest, OpenZeroPolynomial)
Definition ipa.test.cpp:92
constexpr T get_msb(const T in)
Definition get_msb.hpp:50
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:160
CommitmentKey< Curve > ck
VerifierCommitmentKey< Curve > vk
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
static constexpr field one()
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
static constexpr field zero()
VectorField result