Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
gemini.test.cpp
Go to the documentation of this file.
2#include "gemini_impl.hpp"
3
4#include "../commitment_key.test.hpp"
5
6using namespace bb;
7
8template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
11 using Fr = typename Curve::ScalarField;
15
16 public:
17 static constexpr size_t log_n = 4;
18 static constexpr size_t n = 1UL << log_n;
19
20 static constexpr size_t virtual_log_n = 6;
21
24
25 static CK ck;
26 static VK vk;
27
28 static void SetUpTestSuite()
29 {
30 ck = create_commitment_key<CK>(n);
31 vk = create_verifier_commitment_key<VK>();
32 }
33
34 void execute_gemini_and_verify_claims(std::vector<Fr>& multilinear_evaluation_point,
35 MockClaimGenerator<Curve> mock_claims)
36 {
37 auto prover_transcript = NativeTranscript::prover_init_empty();
38
39 // Compute:
40 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
41 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
42 auto prover_output = GeminiProver::prove(
43 this->n, mock_claims.polynomial_batcher, multilinear_evaluation_point, ck, prover_transcript);
44
45 // The prover output needs to be completed by adding the "positive" Fold claims, i.e. evaluations of Fold^(i) at
46 // r^{2^i} for i=1, ..., d-1. Although here we are copying polynomials, it is not the case when GeminiProver is
47 // combined with ShplonkProver.
48 std::vector<ProverOpeningClaim<Curve>> prover_claims_with_pos_evals;
49 // `prover_output` consists of d+1 opening claims, we add another d-1 claims for each positive evaluation
50 // Fold^i(r^{2^i}) for i = 1, ..., d-1
51 const size_t total_num_claims = 2 * log_n;
52 prover_claims_with_pos_evals.reserve(total_num_claims);
53
54 for (auto& claim : prover_output) {
55 if (claim.gemini_fold) {
56 if (claim.gemini_fold) {
57 // "positive" evaluation challenge r^{2^i} for i = 1, ..., d-1
58 const Fr evaluation_challenge = -claim.opening_pair.challenge;
59 // Fold^(i) at r^{2^i} for i=1, ..., d-1
60 const Fr pos_evaluation = claim.polynomial.evaluate(evaluation_challenge);
61 // Add the positive Fold claims to the vector of claims
62 ProverOpeningClaim<Curve> pos_fold_claim = { .polynomial = claim.polynomial,
63 .opening_pair = { .challenge = evaluation_challenge,
64 .evaluation = pos_evaluation } };
65 prover_claims_with_pos_evals.emplace_back(pos_fold_claim);
66 }
67 }
68 prover_claims_with_pos_evals.emplace_back(claim);
69 }
70
71 // Check that the Fold polynomials have been evaluated correctly in the prover
72 this->verify_batch_opening_pair(prover_claims_with_pos_evals);
73
74 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
75
76 // Compute:
77 // - d opening pairs: {r^{2^i}, \hat{a}_i} for i = 0, ..., d-1
78 // - 2 partially evaluated Fold polynomial commitments [Fold_{r}^(0)] and [Fold_{-r}^(0)]
79 // Aggregate: 2d opening pairs and 2d Fold poly commitments into verifier claim
80 auto verifier_claims = GeminiVerifier::reduce_verification(
81 multilinear_evaluation_point, mock_claims.claim_batcher, verifier_transcript);
82
83 // Check equality of the opening pairs computed by prover and verifier
84 for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
85 this->verify_opening_claim(verifier_claim, prover_claim.polynomial, ck);
86 ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
87 }
88 }
89
91 {
92 auto prover_transcript = NativeTranscript::prover_init_empty();
93
94 auto u = this->random_evaluation_point(virtual_log_n);
95
96 Polynomial<Fr> poly((1UL << log_n));
97
98 poly.at(0) = 1;
99 poly.at(1) = 2;
100 poly.at(2) = 3;
101
102 typename GeminiProver::PolynomialBatcher poly_batcher(1UL << log_n);
103 poly_batcher.set_unshifted(RefVector(poly));
104
105 // As we are opening `poly` extended by zero from `log_n` dimensions to `virtual_log_n` dimensions, it needs to
106 // be multiplied by appropriate scalars.
107 Fr eval = poly.evaluate_mle(std::span(u).subspan(0, log_n)) * (Fr(1) - u[virtual_log_n - 1]) *
108 (Fr(1) - u[virtual_log_n - 2]);
109 auto comm = ck.commit(poly);
110 auto claim_batcher = ClaimBatcher{ .unshifted = ClaimBatch{ RefVector(comm), RefVector(eval) } };
111
112 // Compute:
113 // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
114 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
115 auto prover_output = GeminiProver::prove(1UL << log_n, poly_batcher, u, ck, prover_transcript);
116
117 // The prover output needs to be completed by adding the "positive" Fold claims, i.e. evaluations of
118 // Fold^(i) at r^{2^i} for i=1, ..., d-1. Although here we are copying polynomials, it is not the case when
119 // GeminiProver is combined with ShplonkProver.
120 std::vector<ProverOpeningClaim<Curve>> prover_claims_with_pos_evals;
121 // `prover_output` consists of d+1 opening claims, we add another d-1 claims for each positive evaluation
122 // Fold^i(r^{2^i}) for i = 1, ..., d-1
123 const size_t total_num_claims = 2 * virtual_log_n;
124 prover_claims_with_pos_evals.reserve(total_num_claims);
125
126 for (auto& claim : prover_output) {
127 if (claim.gemini_fold) {
128 if (claim.gemini_fold) {
129 // "positive" evaluation challenge r^{2^i} for i = 1, ..., d-1
130 const Fr evaluation_challenge = -claim.opening_pair.challenge;
131 // Fold^(i) at r^{2^i} for i=1, ..., d-1
132 const Fr pos_evaluation = claim.polynomial.evaluate(evaluation_challenge);
133
134 // Add the positive Fold claims to the vector of claims
135 ProverOpeningClaim<Curve> pos_fold_claim = { .polynomial = claim.polynomial,
136 .opening_pair = { .challenge = evaluation_challenge,
137 .evaluation = pos_evaluation } };
138 prover_claims_with_pos_evals.emplace_back(pos_fold_claim);
139 }
140 }
141 prover_claims_with_pos_evals.emplace_back(claim);
142 }
143
144 // Check that the Fold polynomials have been evaluated correctly in the prover
145 this->verify_batch_opening_pair(prover_claims_with_pos_evals);
146
147 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
148
149 // Compute:
150 // - d opening pairs: {r^{2^i}, \hat{a}_i} for i = 0, ..., d-1
151 // - 2 partially evaluated Fold polynomial commitments [Fold_{r}^(0)] and [Fold_{-r}^(0)]
152 // Aggregate: 2d opening pairs and 2d Fold poly commitments into verifier claim
153 auto verifier_claims = GeminiVerifier::reduce_verification(u, claim_batcher, verifier_transcript);
154 // Check equality of the opening pairs computed by prover and verifier
155 for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
156 this->verify_opening_claim(verifier_claim, prover_claim.polynomial, ck);
157 ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
158 }
159 }
160}
161
162;
163
164using ParamsTypes = ::testing::Types<curve::BN254, curve::Grumpkin>;
166
168{
169 auto u = this->random_evaluation_point(this->log_n);
170 MockClaimGenerator mock_claims(
171 this->n, /*num_polynomials*/ 1, /*num_to_be_shifted*/ 0, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
172
173 this->execute_gemini_and_verify_claims(u, mock_claims);
174}
175
177{
178 auto u = this->random_evaluation_point(this->log_n);
179
180 MockClaimGenerator mock_claims(
181 this->n, /*num_polynomials*/ 1, /*num_to_be_shifted*/ 1, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
182
183 this->execute_gemini_and_verify_claims(u, mock_claims);
184}
185
187{
188
189 auto u = this->random_evaluation_point(this->log_n);
190
191 MockClaimGenerator mock_claims(
192 this->n, /*num_polynomials*/ 2, /*num_to_be_shifted*/ 0, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
193
194 this->execute_gemini_and_verify_claims(u, mock_claims);
195}
196
197TYPED_TEST(GeminiTest, DoubleWithShift)
198{
199
200 auto u = this->random_evaluation_point(this->log_n);
201
202 MockClaimGenerator mock_claims(
203 this->n, /*num_polynomials*/ 2, /*num_to_be_shifted*/ 1, /*num_to_be_right_shifted_by_k*/ 0, u, this->ck);
204
205 this->execute_gemini_and_verify_claims(u, mock_claims);
206}
207
208TYPED_TEST(GeminiTest, DoubleWithShiftAndInterleaving)
209{
210 auto u = this->random_evaluation_point(this->log_n);
211
212 MockClaimGenerator mock_claims(this->n,
213 /*num_polynomials*/ 2,
214 /*num_to_be_shifted*/ 0,
215 /*num_to_be_right_shifted_by_k*/ 0,
216 u,
217 this->ck,
218 /*num_interleaved*/ 3,
219 /*num_to_be_interleaved*/ 2);
220
221 this->execute_gemini_and_verify_claims(u, mock_claims);
222}
223
224TYPED_TEST(GeminiTest, OpenExtensionByZero)
225{
226 TestFixture::open_extension_by_zero();
227}
232TYPED_TEST(GeminiTest, SoundnessRegression)
233{
234 using ClaimBatcher = ClaimBatcher_<TypeParam>;
235 using ClaimBatch = ClaimBatcher::Batch;
236 using Claim = ProverOpeningClaim<TypeParam>;
237 using Commitment = TypeParam::AffineElement;
238 using Fr = TypeParam::ScalarField;
239 const size_t log_n = 3;
240 const size_t n = 8;
241
242 auto prover_transcript = NativeTranscript::prover_init_empty();
243 const Fr rho = prover_transcript->template get_challenge<Fr>("rho");
244 std::vector<Polynomial<Fr>> fold_polynomials;
245 fold_polynomials.reserve(log_n);
246
247 Polynomial<Fr> fold_0(n);
248 Polynomial<Fr> fold_1(n / 2);
249 Polynomial<Fr> fold_2(n / 4);
250
251 auto u = this->random_evaluation_point(log_n);
252
253 // Generate a random evaluation v, the prover claims that `zero_polynomial`(u) = v
254 Fr claimed_multilinear_eval = Fr::random_element();
255
256 // Go through the Gemini Prover steps: compute fold polynomials and their evaluations
257 std::vector<Fr> fold_evals;
258 fold_evals.reserve(log_n);
259
260 // By defining the coefficients of fold polynomials as below, a malicious prover can make sure that the values
261 // fold₁(r²) = 0, and hence fold₀(r), computed by the verifier, are 0. At the same time, the prover can open
262 // fold₁(-r²) and fold₂(-r⁴)`to their honest value.
263
264 // fold₂[0] = claimed_multilinear_eval ⋅ u₁² ⋅ [(1 - u₂) ⋅ u₁² - u₂ ⋅ (1 - u₁)²]⁻¹
265 fold_2.at(0) =
266 claimed_multilinear_eval * u[1].sqr() * ((Fr(1) - u[2]) * u[1].sqr() - u[2] * (Fr(1) - u[1]).sqr()).invert();
267 // fold₂[1] = - (1 - u₁)² ⋅ fold₂[0] ⋅ u₁⁻²
268 fold_2.at(1) = -(Fr(1) - u[1]).sqr() * fold_2.at(0) * (u[1].sqr()).invert();
269
270 // The coefficients of fold_1 are determined by the constant term of fold_2.
271 fold_1.at(0) = Fr(0);
272 fold_1.at(1) = Fr(2) * fold_2.at(0) * u[1].invert(); // fold₁[1] = 2 ⋅ fold₂[0] / u₁
273 fold_1.at(2) = -(Fr(1) - u[1]) * fold_1.at(1) * u[1].invert(); // fold₁[2] = -(1 - u₁) ⋅ fold₁[1] / u₁
274 fold_1.at(3) = Fr(0);
275
276 prover_transcript->send_to_verifier("Gemini:FOLD_1", this->ck.commit(fold_1));
277 prover_transcript->send_to_verifier("Gemini:FOLD_2", this->ck.commit(fold_2));
278
279 // Get Gemini evaluation challenge
280 const Fr gemini_r = prover_transcript->template get_challenge<Fr>("Gemini:r");
281
282 // Place honest eval of fold₀(-r) to the vector of evals
283 fold_evals.emplace_back(fold_0.evaluate(-gemini_r));
284
285 // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, 2
286 std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(gemini_r, log_n);
287
288 // Compute honest evaluations fold₁(-r²) and fold₂(-r⁴)
289 fold_evals.emplace_back(fold_1.evaluate(-r_squares[1]));
290 fold_evals.emplace_back(fold_2.evaluate(-r_squares[2]));
291 prover_transcript->send_to_verifier("Gemini:a_1", fold_evals[0]);
292 prover_transcript->send_to_verifier("Gemini:a_2", fold_evals[1]);
293 prover_transcript->send_to_verifier("Gemini:a_3", fold_evals[2]);
294
295 // Compute the powers of r used by the verifier. It is an artifact of the const proof size logic.
296 const std::vector<Fr> gemini_eval_challenge_powers = gemini::powers_of_evaluation_challenge(gemini_r, log_n);
297
298 std::vector<Claim> prover_opening_claims;
299 prover_opening_claims.reserve(2 * log_n);
300
301 prover_opening_claims.emplace_back(Claim{ fold_0, { gemini_r, Fr{ 0 } } });
302 prover_opening_claims.emplace_back(Claim{ fold_0, { -gemini_r, Fr{ 0 } } });
303 prover_opening_claims.emplace_back(Claim{ fold_1, { r_squares[1], fold_1.evaluate(r_squares[1]) } });
304 prover_opening_claims.emplace_back(Claim{ fold_1, { -r_squares[1], fold_evals[1] } });
305 prover_opening_claims.emplace_back(Claim{ fold_2, { r_squares[2], fold_2.evaluate(r_squares[2]) } });
306 prover_opening_claims.emplace_back(Claim{ fold_2, { -r_squares[2], fold_evals[2] } });
307
308 // Check that the Fold polynomials have been evaluated correctly in the prover
309 this->verify_batch_opening_pair(prover_opening_claims);
310
311 auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
312
313 std::vector<Commitment> unshifted_commitments = { this->ck.commit(fold_0) };
314 std::vector<Fr> unshifted_evals = { claimed_multilinear_eval * rho.pow(0) };
315
316 ClaimBatcher claim_batcher{ .unshifted =
317 ClaimBatch{ RefVector(unshifted_commitments), RefVector(unshifted_evals) } };
318
319 auto verifier_claims = GeminiVerifier_<TypeParam>::reduce_verification(u, claim_batcher, verifier_transcript);
320
321 // Malicious prover could honestly prove all "negative" evaluations and several "positive evaluations". In
322 // particular, the evaluation of `fold_0` at r.
323 std::vector<size_t> matching_claim_indices{ 0, 1, 3, 4, 5 };
324 // However, the evaluation of `fold_1` at r^2 that the verifier computes assuming that fold has been performed
325 // correctly, does not match the actual evaluation of tampered fold_1 that the prover can open.
326 std::vector<size_t> mismatching_claim_indices = { 2 };
327 for (auto idx : matching_claim_indices) {
328 EXPECT_TRUE(prover_opening_claims[idx].opening_pair == verifier_claims[idx].opening_pair);
329 }
330
331 // The mismatch in claims below leads to Gemini and Shplemini Verifier rejecting the tampered proof and confirms
332 // the necessity of opening `fold_i` at r^{2^i} for i = 1, ..., log_n - 1.
333 for (auto idx : mismatching_claim_indices) {
334 EXPECT_FALSE(prover_opening_claims[idx].opening_pair == verifier_claims[idx].opening_pair);
335 }
336}
337
338template <class Curve> typename GeminiTest<Curve>::CK GeminiTest<Curve>::ck;
339template <class Curve> typename GeminiTest<Curve>::VK GeminiTest<Curve>::vk;
static VK vk
typename Curve::AffineElement Commitment
static void SetUpTestSuite()
void execute_gemini_and_verify_claims(std::vector< Fr > &multilinear_evaluation_point, MockClaimGenerator< Curve > mock_claims)
void open_extension_by_zero()
static constexpr size_t log_n
static constexpr size_t n
static CK ck
static constexpr size_t virtual_log_n
typename Curve::ScalarField Fr
static std::shared_ptr< BaseTranscript > verifier_init_empty(const std::shared_ptr< BaseTranscript > &transcript)
For testing: initializes transcript based on proof data then receives junk data produced by BaseTrans...
static std::shared_ptr< BaseTranscript > prover_init_empty()
For testing: initializes transcript with some arbitrary data so that a challenge can be generated aft...
CommitmentKey object over a pairing group 𝔾₁.
Commitment commit(PolynomialSpan< const Fr > polynomial) const
Uses the ProverSRS to create a commitment to p(X)
void verify_batch_opening_pair(std::vector< ProverOpeningClaim< Curve > > opening_claims)
Ensures that a set of opening pairs is correct by checking that evaluations are correct by recomputin...
std::vector< Fr > random_evaluation_point(const size_t num_variables)
void verify_opening_claim(const OpeningClaim< Curve > &claim, const Polynomial &witness, CommitmentKey< Curve > ck=CommitmentKey< Curve >())
Class responsible for computation of the batched multilinear polynomials required by the Gemini proto...
Definition gemini.hpp:123
void set_unshifted(RefVector< Polynomial > polynomials)
Definition gemini.hpp:160
static std::vector< Claim > prove(const Fr circuit_size, PolynomialBatcher &polynomial_batcher, std::span< Fr > multilinear_challenge, const CommitmentKey< Curve > &commitment_key, const std::shared_ptr< Transcript > &transcript, bool has_zk=false)
static std::vector< OpeningClaim< Curve > > reduce_verification(std::span< Fr > multilinear_challenge, ClaimBatcher &claim_batcher, auto &transcript)
Returns univariate opening claims for the Fold polynomials to be checked later.
Definition gemini.hpp:364
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
Fr evaluate(const Fr &z, size_t target_size) const
Fr evaluate_mle(std::span< const Fr > evaluation_points, bool shift=false) const
evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = (u_0,...
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
Polynomial p and an opening pair (r,v) such that p(r) = v.
Definition claim.hpp:34
Polynomial polynomial
Definition claim.hpp:39
A template class for a reference vector. Behaves as if std::vector<T&> was possible.
typename Group::affine_element AffineElement
Definition grumpkin.hpp:56
::testing::Types< curve::BN254, curve::Grumpkin > ParamsTypes
TYPED_TEST(GeminiTest, Single)
TYPED_TEST_SUITE(GeminiTest, ParamsTypes)
std::vector< Fr > powers_of_evaluation_challenge(const Fr r, const size_t num_squares)
Compute squares of folding challenge r.
Definition gemini.hpp:92
Entry point for Barretenberg command-line interface.
CommitmentKey< Curve > ck
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
Curve::ScalarField Fr
Logic to support batching opening claims for unshifted and shifted polynomials in Shplemini.
std::optional< Batch > unshifted
Constructs random polynomials, computes commitments and corresponding evaluations.
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
constexpr field invert() const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept