Skip to content

Commit

Permalink
feat: k-shifts (#11663)
Browse files Browse the repository at this point in the history
Adds logic to the Shplemini PCS to open right-shift-by-k polynomials
from their unshifted counterparts. As a result of prior PRs in this
series, the logic for doing this is isolated to sub classes
`PolynomialBatcher` and `ClaimBatcher`.

Note: the mechanism is tested in the commitment_schemes_tests suite but
not yet used in any real protocols.
  • Loading branch information
ledwards2225 authored Feb 5, 2025
1 parent 1a83cd2 commit 511abe5
Show file tree
Hide file tree
Showing 25 changed files with 524 additions and 390 deletions.
2 changes: 2 additions & 0 deletions barretenberg/cpp/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
if(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 18)
# We target clang18 and need this, eventually warning should be fixed or this will be unconditional.
add_compile_options(-Wno-vla-cxx-extension)
# This gets in the way of a valid designated initializer pattern (i.e. MyClass my_class{ .my_member = init_value })
add_compile_options(-Wno-missing-field-initializers)
endif()
endif()

Expand Down
128 changes: 128 additions & 0 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/claim_batcher.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#pragma once
#include "barretenberg/common/ref_vector.hpp"
#include <optional>

namespace bb {

/**
* @brief Logic to support batching opening claims for unshifted and shifted polynomials in Shplemini
* @details Stores references to the commitments/evaluations of unshifted and shifted polynomials to be batched
* opened via Shplemini. Aggregates the commitments and batching scalars for each batch into the corresponding
* containers for Shplemini. Computes the batched evaluation. Contains logic for computing the per-batch scalars
* used to batch each set of claims (see details below).
* @note This class performs the actual batching of the evaluations but not of the commitments. The latter are
* simply appended to a larger container, along with the scalars used to batch them. This is because Shplemini
* is optimized to perform a single batch mul that includes all commitments from each stage of the PCS. See
* description of ShpleminiVerifier for more details.
*
*/
template <typename Curve> struct ClaimBatcher_ {
using Fr = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;

struct Batch {
RefVector<Commitment> commitments;
RefVector<Fr> evaluations;
// scalar used for batching the claims, excluding the power of batching challenge \rho
Fr scalar = 0;
};

std::optional<Batch> unshifted; // commitments and evaluations of unshifted polynomials
std::optional<Batch> shifted; // commitments of to-be-shifted-by-1 polys, evals of their shifts
std::optional<Batch> right_shifted_by_k; // commitments of to-be-right-shifted-by-k polys, evals of their shifts

Batch get_unshifted() { return (unshifted) ? *unshifted : Batch{}; }
Batch get_shifted() { return (shifted) ? *shifted : Batch{}; }
Batch get_right_shifted_by_k() { return (right_shifted_by_k) ? *right_shifted_by_k : Batch{}; }

size_t k_shift_magnitude = 0; // magnitude of right-shift-by-k (assumed even)

Fr get_unshifted_batch_scalar() const { return unshifted ? unshifted->scalar : Fr{ 0 }; }

/**
* @brief Compute scalars used to batch each set of claims, excluding contribution from batching challenge \rho
* @details Computes scalars s_0, s_1, s_2 given by
* \f[
* - s_0 = \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right) \f],
* - s_1 = \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right)
* - s_2 = r^{k} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right)
* \f]
* where the scalars used to batch the claims are given by
* \f[
* \left(
* - s_0,
* \ldots,
* - \rho^{i+k-1} \times s_0,
* - \rho^{i+k} \times s_1,
* \ldots,
* - \rho^{k+m-1} \times s_1
* \right)
* \f]
*
* @param inverse_vanishing_eval_pos 1/(z-r)
* @param inverse_vanishing_eval_neg 1/(z+r)
* @param nu_challenge ν (shplonk batching challenge)
* @param r_challenge r (gemini evaluation challenge)
*/
void compute_scalars_for_each_batch(const Fr& inverse_vanishing_eval_pos,
const Fr& inverse_vanishing_eval_neg,
const Fr& nu_challenge,
const Fr& r_challenge)
{
if (unshifted) {
// (1/(z−r) + ν/(z+r))
unshifted->scalar = inverse_vanishing_eval_pos + nu_challenge * inverse_vanishing_eval_neg;
}
if (shifted) {
// r⁻¹ ⋅ (1/(z−r) − ν/(z+r))
shifted->scalar =
r_challenge.invert() * (inverse_vanishing_eval_pos - nu_challenge * inverse_vanishing_eval_neg);
}
if (right_shifted_by_k) {
// r^k ⋅ (1/(z−r) + ν/(z+r))
right_shifted_by_k->scalar = r_challenge.pow(k_shift_magnitude) *
(inverse_vanishing_eval_pos + nu_challenge * inverse_vanishing_eval_neg);
}
}

/**
* @brief Append the commitments and scalars from each batch of claims to the Shplemini batch mul input vectors;
* update the batched evaluation and the running batching challenge (power of rho) in place.
*
* @param commitments commitment inputs to the single Shplemini batch mul
* @param scalars scalar inputs to the single Shplemini batch mul
* @param batched_evaluation running batched evaluation of the committed multilinear polynomials
* @param rho multivariate batching challenge \rho
* @param rho_power current power of \rho used in the batching scalar
*/
void update_batch_mul_inputs_and_batched_evaluation(std::vector<Commitment>& commitments,
std::vector<Fr>& scalars,
Fr& batched_evaluation,
const Fr& rho,
Fr& rho_power)
{
// Append the commitments/scalars from a given batch to the corresponding containers; update the batched
// evaluation and the running batching challenge in place
auto aggregate_claim_data_and_update_batched_evaluation = [&](const Batch& batch, Fr& rho_power) {
for (auto [commitment, evaluation] : zip_view(batch.commitments, batch.evaluations)) {
commitments.emplace_back(std::move(commitment));
scalars.emplace_back(-batch.scalar * rho_power);
batched_evaluation += evaluation * rho_power;
rho_power *= rho;
}
};

// Incorporate the claim data from each batch of claims that is present
if (unshifted) {
aggregate_claim_data_and_update_batched_evaluation(*unshifted, rho_power);
}
if (shifted) {
aggregate_claim_data_and_update_batched_evaluation(*shifted, rho_power);
}
if (right_shifted_by_k) {
aggregate_claim_data_and_update_batched_evaluation(*right_shifted_by_k, rho_power);
}
}
};

} // namespace bb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#pragma once

#include "barretenberg/commitment_schemes/claim.hpp"
#include "barretenberg/commitment_schemes/claim_batcher.hpp"
#include "barretenberg/polynomials/polynomial.hpp"
#include "barretenberg/transcript/transcript.hpp"

Expand Down Expand Up @@ -104,11 +105,12 @@ template <typename Curve> class GeminiProver_ {
* @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.
* combinations (based on challenge rho) of the unshifted, to-be-shifted-by-1, and to-be-right-shifted-by-k
* polynomials by F, G, and H respectively, then A₀ = F + G/X + X^k*H. (Note: 'k' is assumed even and thus a factor
* (-1)^k in not needed for the evaluation at -r). 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 + r^K*H, and A₀₋ = F - G/r + r^K*H. These are required in order to prove the opening of shifted polynomials
* G_i/X, X^k*H_i and from the commitments to their unshifted counterparts G_i and H_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.
*/
Expand All @@ -122,9 +124,13 @@ template <typename Curve> class GeminiProver_ {

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

size_t k_shift_magnitude = 0; // magnitude of right-shift-by-k (assumed even)

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

public:
PolynomialBatcher(const size_t full_batched_size)
Expand All @@ -135,10 +141,17 @@ template <typename Curve> class GeminiProver_ {

bool has_unshifted() const { return unshifted.size() > 0; }
bool has_to_be_shifted_by_one() const { return to_be_shifted_by_one.size() > 0; }
bool has_to_be_shifted_by_k() const { return to_be_shifted_by_k.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; }
void set_to_be_shifted_by_k(RefVector<Polynomial> polynomials, const size_t shift_magnitude)
{
ASSERT(k_shift_magnitude % 2 == 0); // k must be even for the formulas herein to be valid
to_be_shifted_by_k = polynomials;
k_shift_magnitude = shift_magnitude;
}

// Initialize the random polynomial used to add randomness to the batched polynomials for ZK
void set_random_polynomial(Polynomial&& random)
Expand Down Expand Up @@ -169,19 +182,26 @@ template <typename Curve> class GeminiProver_ {

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

// compute the linear combination F of the unshifted polynomials
if (has_unshifted()) {
batch(batched_unshifted, unshifted);
full_batched += batched_unshifted; // A₀ = F
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
full_batched += batched_to_be_shifted_by_one.shifted(); // A₀ += G/X
}

// compute the linear combination H of the to-be-shifted-by-k polynomials
if (has_to_be_shifted_by_k()) {
batched_to_be_shifted_by_k = Polynomial(full_batched_size - k_shift_magnitude, full_batched_size, 0);
batch(batched_to_be_shifted_by_k, to_be_shifted_by_k);
full_batched += batched_to_be_shifted_by_k.right_shifted(k_shift_magnitude); // A₀ += X^k * H
}

return full_batched;
Expand All @@ -206,14 +226,20 @@ template <typename Curve> class GeminiProver_ {
A_0_pos += batched_unshifted; // A₀₊ += F
}

if (has_to_be_shifted_by_k()) {
Fr r_pow_k = r_challenge.pow(k_shift_magnitude); // r^k
batched_to_be_shifted_by_k *= r_pow_k;
A_0_pos += batched_to_be_shifted_by_k; // A₀₊ += r^k * H
}

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
A_0_pos += batched_to_be_shifted_by_one; // A₀₊ += G/r
A_0_neg -= batched_to_be_shifted_by_one; // A₀₋ -= G/r
}

return { A_0_pos, A_0_neg };
Expand Down Expand Up @@ -252,6 +278,7 @@ template <typename Curve> class GeminiVerifier_ {
using Fr = typename Curve::ScalarField;
using GroupElement = typename Curve::Element;
using Commitment = typename Curve::AffineElement;
using ClaimBatcher = ClaimBatcher_<Curve>;

public:
/**
Expand All @@ -268,10 +295,7 @@ template <typename Curve> class GeminiVerifier_ {
*/
static std::vector<OpeningClaim<Curve>> reduce_verification(
std::span<Fr> multilinear_challenge,
RefSpan<Fr> unshifted_evaluations,
RefSpan<Fr> shifted_evaluations,
RefSpan<Commitment> unshifted_commitments,
RefSpan<Commitment> to_be_shifted_commitments,
ClaimBatcher& claim_batcher,
auto& transcript,
const std::vector<RefVector<Commitment>>& concatenation_group_commitments = {},
RefSpan<Fr> concatenated_evaluations = {})
Expand All @@ -288,13 +312,15 @@ template <typename Curve> class GeminiVerifier_ {

Fr batched_evaluation = Fr(0);
Fr batching_scalar = Fr(1);
for (auto [eval, comm] : zip_view(unshifted_evaluations, unshifted_commitments)) {
for (auto [eval, comm] :
zip_view(claim_batcher.get_unshifted().evaluations, claim_batcher.get_unshifted().commitments)) {
batched_evaluation += eval * batching_scalar;
batched_commitment_unshifted += comm * batching_scalar;
batching_scalar *= rho;
}

for (auto [eval, comm] : zip_view(shifted_evaluations, to_be_shifted_commitments)) {
for (auto [eval, comm] :
zip_view(claim_batcher.get_shifted().evaluations, claim_batcher.get_shifted().commitments)) {
batched_evaluation += eval * batching_scalar;
batched_commitment_to_be_shifted += comm * batching_scalar;
batching_scalar *= rho;
Expand Down
Loading

0 comments on commit 511abe5

Please sign in to comment.