Skip to content

Commit

Permalink
feat: Gemini PolynomialBatcher (#11398)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ledwards2225 authored Jan 29, 2025
1 parent cd990af commit 379aafa
Show file tree
Hide file tree
Showing 15 changed files with 286 additions and 146 deletions.
126 changes: 122 additions & 4 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,133 @@ template <typename Curve> class GeminiProver_ {
using Claim = ProverOpeningClaim<Curve>;

public:
/**
* @brief Class responsible for computation of the batched multilinear polynomials required by the Gemini protocol
* @details Opening multivariate polynomials using Gemini requires the computation of three batched polynomials. The
* first, here denoted A₀, is a linear combination of all polynomials to be opened. If we denote the linear
* combinations (based on challenge rho) of the unshifted and the to-be-shited-by-1 polynomials by F and G,
* respectively, then A₀ = F + G/X. This polynomial is "folded" in Gemini to produce d-1 univariate polynomials
* Fold_i, i = 1, ..., d-1. The second and third are the partially evaluated batched polynomials A₀₊ = F + G/r, and
* A₀₋ = F - G/r. These are required in order to prove the opening of shifted polynomials G_i/X from the commitments
* to their unshifted counterparts G_i.
* @note TODO(https://github.com/AztecProtocol/barretenberg/issues/1223): There are certain operations herein that
* could be made more efficient by e.g. reusing already initialized polynomials, possibly at the expense of clarity.
*/
class PolynomialBatcher {

size_t full_batched_size = 0; // size of the full batched polynomial (generally the circuit size)
bool batched_unshifted_initialized = false;

Polynomial random_polynomial; // random polynomial used for ZK
bool has_random_polynomial = false;

RefVector<Polynomial> unshifted; // set of unshifted polynomials
RefVector<Polynomial> to_be_shifted_by_one; // set of polynomials to be left shifted by 1

Polynomial batched_unshifted; // linear combination of unshifted polynomials
Polynomial batched_to_be_shifted_by_one; // linear combination of to-be-shifted polynomials

public:
PolynomialBatcher(const size_t full_batched_size)
: full_batched_size(full_batched_size)
, batched_unshifted(full_batched_size)
, batched_to_be_shifted_by_one(Polynomial::shiftable(full_batched_size))
{}

bool has_unshifted() const { return unshifted.size() > 0; }
bool has_to_be_shifted_by_one() const { return to_be_shifted_by_one.size() > 0; }

// Set references to the polynomials to be batched
void set_unshifted(RefVector<Polynomial> polynomials) { unshifted = polynomials; }
void set_to_be_shifted_by_one(RefVector<Polynomial> polynomials) { to_be_shifted_by_one = polynomials; }

// Initialize the random polynomial used to add randomness to the batched polynomials for ZK
void set_random_polynomial(Polynomial&& random)
{
has_random_polynomial = true;
random_polynomial = random;
}

/**
* @brief Compute batched polynomial A₀ = F + G/X as the linear combination of all polynomials to be opened
* @details If the random polynomial is set, it is added to the batched polynomial for ZK
*
* @param challenge batching challenge
* @param running_scalar power of the batching challenge
* @return Polynomial A₀
*/
Polynomial compute_batched(const Fr& challenge, Fr& running_scalar)
{
// lambda for batching polynomials; updates the running scalar in place
auto batch = [&](Polynomial& batched, const RefVector<Polynomial>& polynomials_to_batch) {
for (auto& poly : polynomials_to_batch) {
batched.add_scaled(poly, running_scalar);
running_scalar *= challenge;
}
};

Polynomial full_batched(full_batched_size);

// if necessary, add randomness to the full batched polynomial for ZK
if (has_random_polynomial) {
full_batched += random_polynomial;
}

// compute the linear combination F of the unshifted polynomials
if (has_unshifted()) {
batch(batched_unshifted, unshifted);
full_batched += batched_unshifted; // A₀ = F
}

// compute the linear combination G of the to-be-shifted polynomials
if (has_to_be_shifted_by_one()) {
batch(batched_to_be_shifted_by_one, to_be_shifted_by_one);
full_batched += batched_to_be_shifted_by_one.shifted(); // A₀ = F + G/X
}

return full_batched;
}

/**
* @brief Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - G/r
* @details If the random polynomial is set, it is added to each batched polynomial for ZK
*
* @param r_challenge partial evaluation challenge
* @return std::pair<Polynomial, Polynomial> {A₀₊, A₀₋}
*/
std::pair<Polynomial, Polynomial> compute_partially_evaluated_batch_polynomials(const Fr& r_challenge)
{
// Initialize A₀₊ and compute A₀₊ += Random and A₀₊ += F as necessary
Polynomial A_0_pos(full_batched_size); // A₀₊

if (has_random_polynomial) {
A_0_pos += random_polynomial; // A₀₊ += random
}
if (has_unshifted()) {
A_0_pos += batched_unshifted; // A₀₊ += F
}

Polynomial A_0_neg = A_0_pos;

if (has_to_be_shifted_by_one()) {
Fr r_inv = r_challenge.invert(); // r⁻¹
batched_to_be_shifted_by_one *= r_inv; // G = G/r

A_0_pos += batched_to_be_shifted_by_one; // A₀₊ = F + G/r
A_0_neg -= batched_to_be_shifted_by_one; // A₀₋ = F - G/r
}

return { A_0_pos, A_0_neg };
};
};

static std::vector<Polynomial> compute_fold_polynomials(const size_t log_n,
std::span<const Fr> multilinear_challenge,
const Polynomial& A_0);

static std::pair<Polynomial, Polynomial> compute_partially_evaluated_batch_polynomials(
const size_t log_n,
Polynomial&& batched_F,
Polynomial&& batched_G,
PolynomialBatcher& polynomial_batcher,
const Fr& r_challenge,
const std::vector<Polynomial>& batched_groups_to_be_concatenated = {});

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

template <typename Transcript>
static std::vector<Claim> prove(const Fr circuit_size,
RefSpan<Polynomial> f_polynomials,
RefSpan<Polynomial> g_polynomials,
PolynomialBatcher& polynomial_batcher,
std::span<Fr> multilinear_challenge,
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
const std::shared_ptr<Transcript>& transcript,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ using namespace bb;

template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
using GeminiProver = GeminiProver_<Curve>;
using PolynomialBatcher = GeminiProver::PolynomialBatcher;
using GeminiVerifier = GeminiVerifier_<Curve>;
using Fr = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;
Expand All @@ -15,22 +16,24 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
void execute_gemini_and_verify_claims(std::vector<Fr>& multilinear_evaluation_point,
RefVector<Fr> multilinear_evaluations_unshifted,
RefVector<Fr> multilinear_evaluations_shifted,
RefSpan<Polynomial<Fr>> multilinear_polynomials,
RefSpan<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
RefVector<Polynomial<Fr>> multilinear_polynomials,
RefVector<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
RefVector<Commitment> multilinear_commitments,
RefVector<Commitment> multilinear_commitments_to_be_shifted)
{
auto prover_transcript = NativeTranscript::prover_init_empty();

size_t circuit_size = 1 << multilinear_evaluation_point.size();

PolynomialBatcher polynomial_batcher(circuit_size);
polynomial_batcher.set_unshifted(multilinear_polynomials);
polynomial_batcher.set_to_be_shifted_by_one(multilinear_polynomials_to_be_shifted);

// Compute:
// - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
// - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
auto prover_output = GeminiProver::prove(1 << multilinear_evaluation_point.size(),
multilinear_polynomials,
multilinear_polynomials_to_be_shifted,
multilinear_evaluation_point,
this->commitment_key,
prover_transcript);
auto prover_output = GeminiProver::prove(
circuit_size, polynomial_batcher, multilinear_evaluation_point, this->commitment_key, prover_transcript);

// Check that the Fold polynomials have been evaluated correctly in the prover
this->verify_batch_opening_pair(prover_output);
Expand Down Expand Up @@ -59,8 +62,8 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
std::vector<Fr>& multilinear_evaluation_point,
RefVector<Fr> multilinear_evaluations_unshifted,
RefVector<Fr> multilinear_evaluations_shifted,
RefSpan<Polynomial<Fr>> multilinear_polynomials,
RefSpan<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
RefVector<Polynomial<Fr>> multilinear_polynomials,
RefVector<Polynomial<Fr>> multilinear_polynomials_to_be_shifted,
RefVector<Commitment> multilinear_commitments,
RefVector<Commitment> multilinear_commitments_to_be_shifted,
RefSpan<Polynomial<Fr>> concatenated_polynomials = {},
Expand All @@ -71,12 +74,17 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
{
auto prover_transcript = NativeTranscript::prover_init_empty();

size_t circuit_size = 1 << multilinear_evaluation_point.size();

PolynomialBatcher polynomial_batcher(circuit_size);
polynomial_batcher.set_unshifted(multilinear_polynomials);
polynomial_batcher.set_to_be_shifted_by_one(multilinear_polynomials_to_be_shifted);

// Compute:
// - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
// - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
auto prover_output = GeminiProver::prove(1 << multilinear_evaluation_point.size(),
multilinear_polynomials,
multilinear_polynomials_to_be_shifted,
auto prover_output = GeminiProver::prove(circuit_size,
polynomial_batcher,
multilinear_evaluation_point,
this->commitment_key,
prover_transcript,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ template <typename Curve>
template <typename Transcript>
std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
Fr circuit_size,
RefSpan<Polynomial> f_polynomials, // unshifted
RefSpan<Polynomial> g_polynomials, // to-be-shifted
PolynomialBatcher& polynomial_batcher,
std::span<Fr> multilinear_challenge,
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
const std::shared_ptr<Transcript>& transcript,
Expand All @@ -57,37 +56,24 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(

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

// Compute batched polynomials
Polynomial batched_unshifted(n);
Polynomial batched_to_be_shifted = Polynomial::shiftable(n);

// To achieve ZK, we mask the batched polynomial by a random polynomial of the same size
if (has_zk) {
batched_unshifted = Polynomial::random(n);
transcript->send_to_verifier("Gemini:masking_poly_comm", commitment_key->commit(batched_unshifted));
Polynomial random_polynomial(n);
transcript->send_to_verifier("Gemini:masking_poly_comm", commitment_key->commit(random_polynomial));
// In the provers, the size of multilinear_challenge is CONST_PROOF_SIZE_LOG_N, but we need to evaluate the
// hiding polynomial as multilinear in log_n variables
transcript->send_to_verifier("Gemini:masking_poly_eval",
batched_unshifted.evaluate_mle(multilinear_challenge.subspan(0, log_n)));
random_polynomial.evaluate_mle(multilinear_challenge.subspan(0, log_n)));
// Initialize batched unshifted poly with the random masking poly so that the full batched poly is masked
polynomial_batcher.set_random_polynomial(std::move(random_polynomial));
}

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

Fr rho_challenge{ 1 };
if (has_zk) {
// ρ⁰ is used to batch the hiding polynomial
rho_challenge *= rho;
}
Fr running_scalar = has_zk ? rho : 1; // ρ⁰ is used to batch the hiding polynomial

for (size_t i = 0; i < f_polynomials.size(); i++) {
batched_unshifted.add_scaled(f_polynomials[i], rho_challenge);
rho_challenge *= rho;
}
for (size_t i = 0; i < g_polynomials.size(); i++) {
batched_to_be_shifted.add_scaled(g_polynomials[i], rho_challenge);
rho_challenge *= rho;
}
Polynomial A_0 = polynomial_batcher.compute_batched(rho, running_scalar);

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

for (size_t i = 0; i < num_groups; ++i) {
batched_concatenated.add_scaled(concatenated_polynomials[i], rho_challenge);
batched_concatenated.add_scaled(concatenated_polynomials[i], running_scalar);
for (size_t j = 0; j < num_chunks_per_group; ++j) {
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], rho_challenge);
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], running_scalar);
}
rho_challenge *= rho;
running_scalar *= rho;
}
}

// Construct the batched polynomial A₀(X) = F(X) + G↺(X) = F(X) + G(X)/X
Polynomial A_0 = batched_unshifted;
A_0 += batched_to_be_shifted.shifted();
if (has_concatenations) { // If proving for translator, add contribution of the batched concatenation polynomials
// If proving for translator, add contribution of the batched concatenation polynomials
A_0 += batched_concatenated;
}

Expand Down Expand Up @@ -141,8 +122,8 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
}

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

// Construct claims for the d + 1 univariate evaluations A₀₊(r), A₀₋(-r), and Foldₗ(−r^{2ˡ}), l = 1, ..., d-1
std::vector<Claim> claims = construct_univariate_opening_claims(
Expand Down Expand Up @@ -237,20 +218,11 @@ std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::com
template <typename Curve>
std::pair<typename GeminiProver_<Curve>::Polynomial, typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::
compute_partially_evaluated_batch_polynomials(const size_t log_n,
Polynomial&& batched_F,
Polynomial&& batched_G,
PolynomialBatcher& polynomial_batcher,
const Fr& r_challenge,
const std::vector<Polynomial>& batched_groups_to_be_concatenated)
{
Polynomial& A_0_pos = batched_F; // A₀₊ = F
Polynomial A_0_neg = batched_F; // A₀₋ = F

// Compute G/r
Fr r_inv = r_challenge.invert();
batched_G *= r_inv;

A_0_pos += batched_G; // A₀₊ = F + G/r
A_0_neg -= batched_G; // A₀₋ = F - G/r
auto [A_0_pos, A_0_neg] = polynomial_batcher.compute_partially_evaluated_batch_polynomials(r_challenge);

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

return { std::move(A_0_pos), std::move(A_0_neg) };
return { A_0_pos, A_0_neg };
};

/**
Expand Down
Loading

0 comments on commit 379aafa

Please sign in to comment.