Skip to content

Commit 379aafa

Browse files
authored
feat: Gemini PolynomialBatcher (#11398)
Introduces new class in Gemini called `PolynomialBatcher` that is responsible for storing references to the polynomials to be batched and actually computing the various batched polynomials required in Gemini. This serves two purposes: (1) it separates the polynomial batching logic from the "gemini" logic (i.e. constructing univariate claims from a multilinear poly via gemini-style "folding"), and (2) it facilitates the input of different types of polynomial sets into Gemini. This latter point is needed for the new merge protocol which will add yet another type of polynomial into the mix: `to_be_k_shifted_polynomials`, which the ever-expanding gemini interface with defaulted inputs would not easily support. Note: Currently the `PolynomialBatcher` only handles the `unshifted` and `to_be_shifted` polynomials. It would be natural to include the concatenation polynomials as well. I held off for now due to expected changes to the way concatenations are handled. Note 2: Upcoming follow ons will introduce a similar mechanism for the analogous verifier logic.
1 parent cd990af commit 379aafa

File tree

15 files changed

+286
-146
lines changed

15 files changed

+286
-146
lines changed

barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp

+122-4
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,133 @@ template <typename Curve> class GeminiProver_ {
100100
using Claim = ProverOpeningClaim<Curve>;
101101

102102
public:
103+
/**
104+
* @brief Class responsible for computation of the batched multilinear polynomials required by the Gemini protocol
105+
* @details Opening multivariate polynomials using Gemini requires the computation of three batched polynomials. The
106+
* first, here denoted A₀, is a linear combination of all polynomials to be opened. If we denote the linear
107+
* combinations (based on challenge rho) of the unshifted and the to-be-shited-by-1 polynomials by F and G,
108+
* respectively, then A₀ = F + G/X. This polynomial is "folded" in Gemini to produce d-1 univariate polynomials
109+
* Fold_i, i = 1, ..., d-1. The second and third are the partially evaluated batched polynomials A₀₊ = F + G/r, and
110+
* A₀₋ = F - G/r. These are required in order to prove the opening of shifted polynomials G_i/X from the commitments
111+
* to their unshifted counterparts G_i.
112+
* @note TODO(https://github.com/AztecProtocol/barretenberg/issues/1223): There are certain operations herein that
113+
* could be made more efficient by e.g. reusing already initialized polynomials, possibly at the expense of clarity.
114+
*/
115+
class PolynomialBatcher {
116+
117+
size_t full_batched_size = 0; // size of the full batched polynomial (generally the circuit size)
118+
bool batched_unshifted_initialized = false;
119+
120+
Polynomial random_polynomial; // random polynomial used for ZK
121+
bool has_random_polynomial = false;
122+
123+
RefVector<Polynomial> unshifted; // set of unshifted polynomials
124+
RefVector<Polynomial> to_be_shifted_by_one; // set of polynomials to be left shifted by 1
125+
126+
Polynomial batched_unshifted; // linear combination of unshifted polynomials
127+
Polynomial batched_to_be_shifted_by_one; // linear combination of to-be-shifted polynomials
128+
129+
public:
130+
PolynomialBatcher(const size_t full_batched_size)
131+
: full_batched_size(full_batched_size)
132+
, batched_unshifted(full_batched_size)
133+
, batched_to_be_shifted_by_one(Polynomial::shiftable(full_batched_size))
134+
{}
135+
136+
bool has_unshifted() const { return unshifted.size() > 0; }
137+
bool has_to_be_shifted_by_one() const { return to_be_shifted_by_one.size() > 0; }
138+
139+
// Set references to the polynomials to be batched
140+
void set_unshifted(RefVector<Polynomial> polynomials) { unshifted = polynomials; }
141+
void set_to_be_shifted_by_one(RefVector<Polynomial> polynomials) { to_be_shifted_by_one = polynomials; }
142+
143+
// Initialize the random polynomial used to add randomness to the batched polynomials for ZK
144+
void set_random_polynomial(Polynomial&& random)
145+
{
146+
has_random_polynomial = true;
147+
random_polynomial = random;
148+
}
149+
150+
/**
151+
* @brief Compute batched polynomial A₀ = F + G/X as the linear combination of all polynomials to be opened
152+
* @details If the random polynomial is set, it is added to the batched polynomial for ZK
153+
*
154+
* @param challenge batching challenge
155+
* @param running_scalar power of the batching challenge
156+
* @return Polynomial A₀
157+
*/
158+
Polynomial compute_batched(const Fr& challenge, Fr& running_scalar)
159+
{
160+
// lambda for batching polynomials; updates the running scalar in place
161+
auto batch = [&](Polynomial& batched, const RefVector<Polynomial>& polynomials_to_batch) {
162+
for (auto& poly : polynomials_to_batch) {
163+
batched.add_scaled(poly, running_scalar);
164+
running_scalar *= challenge;
165+
}
166+
};
167+
168+
Polynomial full_batched(full_batched_size);
169+
170+
// if necessary, add randomness to the full batched polynomial for ZK
171+
if (has_random_polynomial) {
172+
full_batched += random_polynomial;
173+
}
174+
175+
// compute the linear combination F of the unshifted polynomials
176+
if (has_unshifted()) {
177+
batch(batched_unshifted, unshifted);
178+
full_batched += batched_unshifted; // A₀ = F
179+
}
180+
181+
// compute the linear combination G of the to-be-shifted polynomials
182+
if (has_to_be_shifted_by_one()) {
183+
batch(batched_to_be_shifted_by_one, to_be_shifted_by_one);
184+
full_batched += batched_to_be_shifted_by_one.shifted(); // A₀ = F + G/X
185+
}
186+
187+
return full_batched;
188+
}
189+
190+
/**
191+
* @brief Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - G/r
192+
* @details If the random polynomial is set, it is added to each batched polynomial for ZK
193+
*
194+
* @param r_challenge partial evaluation challenge
195+
* @return std::pair<Polynomial, Polynomial> {A₀₊, A₀₋}
196+
*/
197+
std::pair<Polynomial, Polynomial> compute_partially_evaluated_batch_polynomials(const Fr& r_challenge)
198+
{
199+
// Initialize A₀₊ and compute A₀₊ += Random and A₀₊ += F as necessary
200+
Polynomial A_0_pos(full_batched_size); // A₀₊
201+
202+
if (has_random_polynomial) {
203+
A_0_pos += random_polynomial; // A₀₊ += random
204+
}
205+
if (has_unshifted()) {
206+
A_0_pos += batched_unshifted; // A₀₊ += F
207+
}
208+
209+
Polynomial A_0_neg = A_0_pos;
210+
211+
if (has_to_be_shifted_by_one()) {
212+
Fr r_inv = r_challenge.invert(); // r⁻¹
213+
batched_to_be_shifted_by_one *= r_inv; // G = G/r
214+
215+
A_0_pos += batched_to_be_shifted_by_one; // A₀₊ = F + G/r
216+
A_0_neg -= batched_to_be_shifted_by_one; // A₀₋ = F - G/r
217+
}
218+
219+
return { A_0_pos, A_0_neg };
220+
};
221+
};
222+
103223
static std::vector<Polynomial> compute_fold_polynomials(const size_t log_n,
104224
std::span<const Fr> multilinear_challenge,
105225
const Polynomial& A_0);
106226

107227
static std::pair<Polynomial, Polynomial> compute_partially_evaluated_batch_polynomials(
108228
const size_t log_n,
109-
Polynomial&& batched_F,
110-
Polynomial&& batched_G,
229+
PolynomialBatcher& polynomial_batcher,
111230
const Fr& r_challenge,
112231
const std::vector<Polynomial>& batched_groups_to_be_concatenated = {});
113232

@@ -119,8 +238,7 @@ template <typename Curve> class GeminiProver_ {
119238

120239
template <typename Transcript>
121240
static std::vector<Claim> prove(const Fr circuit_size,
122-
RefSpan<Polynomial> f_polynomials,
123-
RefSpan<Polynomial> g_polynomials,
241+
PolynomialBatcher& polynomial_batcher,
124242
std::span<Fr> multilinear_challenge,
125243
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
126244
const std::shared_ptr<Transcript>& transcript,

barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp

+21-13
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using namespace bb;
77

88
template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
99
using GeminiProver = GeminiProver_<Curve>;
10+
using PolynomialBatcher = GeminiProver::PolynomialBatcher;
1011
using GeminiVerifier = GeminiVerifier_<Curve>;
1112
using Fr = typename Curve::ScalarField;
1213
using Commitment = typename Curve::AffineElement;
@@ -15,22 +16,24 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
1516
void execute_gemini_and_verify_claims(std::vector<Fr>& multilinear_evaluation_point,
1617
RefVector<Fr> multilinear_evaluations_unshifted,
1718
RefVector<Fr> multilinear_evaluations_shifted,
18-
RefSpan<Polynomial<Fr>> multilinear_polynomials,
19-
RefSpan<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
19+
RefVector<Polynomial<Fr>> multilinear_polynomials,
20+
RefVector<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
2021
RefVector<Commitment> multilinear_commitments,
2122
RefVector<Commitment> multilinear_commitments_to_be_shifted)
2223
{
2324
auto prover_transcript = NativeTranscript::prover_init_empty();
2425

26+
size_t circuit_size = 1 << multilinear_evaluation_point.size();
27+
28+
PolynomialBatcher polynomial_batcher(circuit_size);
29+
polynomial_batcher.set_unshifted(multilinear_polynomials);
30+
polynomial_batcher.set_to_be_shifted_by_one(multilinear_polynomials_to_be_shifted);
31+
2532
// Compute:
2633
// - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
2734
// - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
28-
auto prover_output = GeminiProver::prove(1 << multilinear_evaluation_point.size(),
29-
multilinear_polynomials,
30-
multilinear_polynomials_to_be_shifted,
31-
multilinear_evaluation_point,
32-
this->commitment_key,
33-
prover_transcript);
35+
auto prover_output = GeminiProver::prove(
36+
circuit_size, polynomial_batcher, multilinear_evaluation_point, this->commitment_key, prover_transcript);
3437

3538
// Check that the Fold polynomials have been evaluated correctly in the prover
3639
this->verify_batch_opening_pair(prover_output);
@@ -59,8 +62,8 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
5962
std::vector<Fr>& multilinear_evaluation_point,
6063
RefVector<Fr> multilinear_evaluations_unshifted,
6164
RefVector<Fr> multilinear_evaluations_shifted,
62-
RefSpan<Polynomial<Fr>> multilinear_polynomials,
63-
RefSpan<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
65+
RefVector<Polynomial<Fr>> multilinear_polynomials,
66+
RefVector<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
6467
RefVector<Commitment> multilinear_commitments,
6568
RefVector<Commitment> multilinear_commitments_to_be_shifted,
6669
RefSpan<Polynomial<Fr>> concatenated_polynomials = {},
@@ -71,12 +74,17 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
7174
{
7275
auto prover_transcript = NativeTranscript::prover_init_empty();
7376

77+
size_t circuit_size = 1 << multilinear_evaluation_point.size();
78+
79+
PolynomialBatcher polynomial_batcher(circuit_size);
80+
polynomial_batcher.set_unshifted(multilinear_polynomials);
81+
polynomial_batcher.set_to_be_shifted_by_one(multilinear_polynomials_to_be_shifted);
82+
7483
// Compute:
7584
// - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
7685
// - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
77-
auto prover_output = GeminiProver::prove(1 << multilinear_evaluation_point.size(),
78-
multilinear_polynomials,
79-
multilinear_polynomials_to_be_shifted,
86+
auto prover_output = GeminiProver::prove(circuit_size,
87+
polynomial_batcher,
8088
multilinear_evaluation_point,
8189
this->commitment_key,
8290
prover_transcript,

barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp

+17-45
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,7 @@ template <typename Curve>
4343
template <typename Transcript>
4444
std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
4545
Fr circuit_size,
46-
RefSpan<Polynomial> f_polynomials, // unshifted
47-
RefSpan<Polynomial> g_polynomials, // to-be-shifted
46+
PolynomialBatcher& polynomial_batcher,
4847
std::span<Fr> multilinear_challenge,
4948
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
5049
const std::shared_ptr<Transcript>& transcript,
@@ -57,37 +56,24 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
5756

5857
const bool has_concatenations = concatenated_polynomials.size() > 0;
5958

60-
// Compute batched polynomials
61-
Polynomial batched_unshifted(n);
62-
Polynomial batched_to_be_shifted = Polynomial::shiftable(n);
63-
6459
// To achieve ZK, we mask the batched polynomial by a random polynomial of the same size
6560
if (has_zk) {
66-
batched_unshifted = Polynomial::random(n);
67-
transcript->send_to_verifier("Gemini:masking_poly_comm", commitment_key->commit(batched_unshifted));
61+
Polynomial random_polynomial(n);
62+
transcript->send_to_verifier("Gemini:masking_poly_comm", commitment_key->commit(random_polynomial));
6863
// In the provers, the size of multilinear_challenge is CONST_PROOF_SIZE_LOG_N, but we need to evaluate the
6964
// hiding polynomial as multilinear in log_n variables
7065
transcript->send_to_verifier("Gemini:masking_poly_eval",
71-
batched_unshifted.evaluate_mle(multilinear_challenge.subspan(0, log_n)));
66+
random_polynomial.evaluate_mle(multilinear_challenge.subspan(0, log_n)));
67+
// Initialize batched unshifted poly with the random masking poly so that the full batched poly is masked
68+
polynomial_batcher.set_random_polynomial(std::move(random_polynomial));
7269
}
7370

7471
// Get the batching challenge
7572
const Fr rho = transcript->template get_challenge<Fr>("rho");
7673

77-
Fr rho_challenge{ 1 };
78-
if (has_zk) {
79-
// ρ⁰ is used to batch the hiding polynomial
80-
rho_challenge *= rho;
81-
}
74+
Fr running_scalar = has_zk ? rho : 1; // ρ⁰ is used to batch the hiding polynomial
8275

83-
for (size_t i = 0; i < f_polynomials.size(); i++) {
84-
batched_unshifted.add_scaled(f_polynomials[i], rho_challenge);
85-
rho_challenge *= rho;
86-
}
87-
for (size_t i = 0; i < g_polynomials.size(); i++) {
88-
batched_to_be_shifted.add_scaled(g_polynomials[i], rho_challenge);
89-
rho_challenge *= rho;
90-
}
76+
Polynomial A_0 = polynomial_batcher.compute_batched(rho, running_scalar);
9177

9278
size_t num_groups = groups_to_be_concatenated.size();
9379
size_t num_chunks_per_group = groups_to_be_concatenated.empty() ? 0 : groups_to_be_concatenated[0].size();
@@ -102,18 +88,13 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
10288
}
10389

10490
for (size_t i = 0; i < num_groups; ++i) {
105-
batched_concatenated.add_scaled(concatenated_polynomials[i], rho_challenge);
91+
batched_concatenated.add_scaled(concatenated_polynomials[i], running_scalar);
10692
for (size_t j = 0; j < num_chunks_per_group; ++j) {
107-
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], rho_challenge);
93+
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], running_scalar);
10894
}
109-
rho_challenge *= rho;
95+
running_scalar *= rho;
11096
}
111-
}
112-
113-
// Construct the batched polynomial A₀(X) = F(X) + G↺(X) = F(X) + G(X)/X
114-
Polynomial A_0 = batched_unshifted;
115-
A_0 += batched_to_be_shifted.shifted();
116-
if (has_concatenations) { // If proving for translator, add contribution of the batched concatenation polynomials
97+
// If proving for translator, add contribution of the batched concatenation polynomials
11798
A_0 += batched_concatenated;
11899
}
119100

@@ -141,8 +122,8 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
141122
}
142123

143124
// Compute polynomials A₀₊(X) = F(X) + G(X)/r and A₀₋(X) = F(X) - G(X)/r
144-
auto [A_0_pos, A_0_neg] = compute_partially_evaluated_batch_polynomials(
145-
log_n, std::move(batched_unshifted), std::move(batched_to_be_shifted), r_challenge, batched_group);
125+
auto [A_0_pos, A_0_neg] =
126+
compute_partially_evaluated_batch_polynomials(log_n, polynomial_batcher, r_challenge, batched_group);
146127

147128
// Construct claims for the d + 1 univariate evaluations A₀₊(r), A₀₋(-r), and Foldₗ(−r^{2ˡ}), l = 1, ..., d-1
148129
std::vector<Claim> claims = construct_univariate_opening_claims(
@@ -237,20 +218,11 @@ std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::com
237218
template <typename Curve>
238219
std::pair<typename GeminiProver_<Curve>::Polynomial, typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::
239220
compute_partially_evaluated_batch_polynomials(const size_t log_n,
240-
Polynomial&& batched_F,
241-
Polynomial&& batched_G,
221+
PolynomialBatcher& polynomial_batcher,
242222
const Fr& r_challenge,
243223
const std::vector<Polynomial>& batched_groups_to_be_concatenated)
244224
{
245-
Polynomial& A_0_pos = batched_F; // A₀₊ = F
246-
Polynomial A_0_neg = batched_F; // A₀₋ = F
247-
248-
// Compute G/r
249-
Fr r_inv = r_challenge.invert();
250-
batched_G *= r_inv;
251-
252-
A_0_pos += batched_G; // A₀₊ = F + G/r
253-
A_0_neg -= batched_G; // A₀₋ = F - G/r
225+
auto [A_0_pos, A_0_neg] = polynomial_batcher.compute_partially_evaluated_batch_polynomials(r_challenge);
254226

255227
// Reconstruct the batched concatenated polynomial from the batched groups, partially evaluated at r and -r and add
256228
// the result to A₀₊(X) and A₀₋(X). Explanation (for simplification assume a single concatenated polynomial):
@@ -279,7 +251,7 @@ std::pair<typename GeminiProver_<Curve>::Polynomial, typename GeminiProver_<Curv
279251
}
280252
}
281253

282-
return { std::move(A_0_pos), std::move(A_0_neg) };
254+
return { A_0_pos, A_0_neg };
283255
};
284256

285257
/**

0 commit comments

Comments
 (0)