Skip to content

Commit

Permalink
refactor: more efficient verification with shplonk and gemini (#8351)
Browse files Browse the repository at this point in the history
- Created a separate ShpleminiVerifier class
- Reduced the number of batch_mul calls. Only 1 batch_mul call with KZG (compared to 6 in the existing Gemini+Shplonk and to 4 in the Zeromorph flow)
- Shplemini Docs + minor docs improvements in other parts
- Shplemini Tests: unit tests for shplemini functions, recursion test, integration tests with KZG, IPA
- batch_mul_native moved to commitment_schemes/utils
  • Loading branch information
iakovenkos authored Sep 6, 2024
1 parent 2894b68 commit e51d157
Show file tree
Hide file tree
Showing 15 changed files with 1,183 additions and 93 deletions.
13 changes: 13 additions & 0 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/claim.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,17 @@ template <typename Curve> class OpeningClaim {

bool operator==(const OpeningClaim& other) const = default;
};

/**
* @brief An accumulator consisting of the Shplonk evaluation challenge and vectors of commitments and scalars.
*
* @details This structure is used in the `reduce_verify_batch_opening_claim` method of KZG or IPA.
*
* @tparam Curve: BN254 or Grumpkin.
*/
template <typename Curve> struct BatchOpeningClaim {
std::vector<typename Curve::AffineElement> commitments;
std::vector<typename Curve::ScalarField> scalars;
typename Curve::ScalarField evaluation_point;
};
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ GeminiProverOutput<Curve> GeminiProver_<Curve>::compute_fold_polynomial_evaluati
Polynomial& batched_G = gemini_polynomials[1]; // G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X)

// Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1
std::vector<Fr> r_squares = gemini::squares_of_r(r_challenge, num_variables);
std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(r_challenge, num_variables);

// Compute G/r
Fr r_inv = r_challenge.invert();
Expand Down
108 changes: 65 additions & 43 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ template <class Fr> inline std::vector<Fr> powers_of_rho(const Fr rho, const siz
* @param num_squares The number of foldings
* @return std::vector<typename Curve::ScalarField>
*/
template <class Fr> inline std::vector<Fr> squares_of_r(const Fr r, const size_t num_squares)
template <class Fr> inline std::vector<Fr> powers_of_evaluation_challenge(const Fr r, const size_t num_squares)
{
std::vector<Fr> squares = { r };
squares.reserve(num_squares);
Expand Down Expand Up @@ -132,36 +132,25 @@ template <typename Curve> class GeminiVerifier_ {
* (Cⱼ, Aⱼ(-r^{2ʲ}), -r^{2}), j = [1, ..., m-1]
*/
static std::vector<OpeningClaim<Curve>> reduce_verification(std::span<const Fr> mle_opening_point, /* u */
const Fr batched_evaluation, /* all */
Fr& batched_evaluation, /* all */
GroupElement& batched_f, /* unshifted */
GroupElement& batched_g, /* to-be-shifted */
auto& transcript)
{
const size_t num_variables = mle_opening_point.size();

// Get polynomials Fold_i, i = 1,...,m-1 from transcript
std::vector<Commitment> commitments;
commitments.reserve(num_variables - 1);
for (size_t i = 0; i < num_variables - 1; ++i) {
auto commitment =
transcript->template receive_from_prover<Commitment>("Gemini:FOLD_" + std::to_string(i + 1));
commitments.emplace_back(commitment);
}
const std::vector<Commitment> commitments = get_gemini_commitments(num_variables, transcript);

// compute vector of powers of random evaluation point r
const Fr r = transcript->template get_challenge<Fr>("Gemini:r");
std::vector<Fr> r_squares = gemini::squares_of_r(r, num_variables);
const std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(r, num_variables);

// Get evaluations a_i, i = 0,...,m-1 from transcript
std::vector<Fr> evaluations;
evaluations.reserve(num_variables);
for (size_t i = 0; i < num_variables; ++i) {
auto eval = transcript->template receive_from_prover<Fr>("Gemini:a_" + std::to_string(i));
evaluations.emplace_back(eval);
}

const std::vector<Fr> evaluations = get_gemini_evaluations(num_variables, transcript);
// Compute evaluation A₀(r)
auto a_0_pos = compute_eval_pos(batched_evaluation, mle_opening_point, r_squares, evaluations);
auto a_0_pos =
compute_gemini_batched_univariate_evaluation(batched_evaluation, mle_opening_point, r_squares, evaluations);

// C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] + r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ]
// C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] - r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ]
Expand All @@ -183,42 +172,75 @@ template <typename Curve> class GeminiVerifier_ {
return fold_polynomial_opening_claims;
}

private:
static std::vector<Commitment> get_gemini_commitments(const size_t log_circuit_size, auto& transcript)
{
std::vector<Commitment> gemini_commitments;
gemini_commitments.reserve(log_circuit_size - 1);
for (size_t i = 0; i < log_circuit_size - 1; ++i) {
const Commitment commitment =
transcript->template receive_from_prover<Commitment>("Gemini:FOLD_" + std::to_string(i + 1));
gemini_commitments.emplace_back(commitment);
}
return gemini_commitments;
}
static std::vector<Fr> get_gemini_evaluations(const size_t log_circuit_size, auto& transcript)
{
std::vector<Fr> gemini_evaluations;
gemini_evaluations.reserve(log_circuit_size);
for (size_t i = 0; i < log_circuit_size; ++i) {
const Fr evaluation = transcript->template receive_from_prover<Fr>("Gemini:a_" + std::to_string(i));
gemini_evaluations.emplace_back(evaluation);
}
return gemini_evaluations;
}

/**
* @brief Compute the expected evaluation of the univariate commitment to the batched polynomial.
*
* @param batched_mle_eval The evaluation of the folded polynomials
* @param mle_vars MLE opening point u
* @param r_squares squares of r, r², ..., r^{2ᵐ⁻¹}
* @param fold_polynomial_evals series of Aᵢ₋₁(−r^{2ⁱ⁻¹})
* @return evaluation A₀(r)
* Compute the evaluation \f$ A_0(r) = \sum \rho^i \cdot f_i + \frac{1}{r} \cdot \sum \rho^{i+k} g_i \f$, where \f$
* k \f$ is the number of "unshifted" commitments.
*
* @details Initialize \f$ A_{d}(r) \f$ with the batched evaluation \f$ \sum \rho^i f_i(\vec{u}) + \sum \rho^{i+k}
* g_i(\vec{u}) \f$. The folding property ensures that
* \f{align}{
* A_\ell\left(r^{2^\ell}\right) = (1 - u_{\ell-1}) \cdot \frac{A_{\ell-1}\left(r^{2^{\ell-1}}\right) +
* A_{\ell-1}\left(-r^{2^{\ell-1}}\right)}{2}
* + u_{\ell-1} \cdot \frac{A_{\ell-1}\left(r^{2^{\ell-1}}\right) -
* A_{\ell-1}\left(-r^{2^{\ell-1}}\right)}{2r^{2^{\ell-1}}}
* \f}
* Therefore, the verifier can recover \f$ A_0(r) \f$ by solving several linear equations.
*
* @param batched_mle_eval The evaluation of the batched polynomial at \f$ (u_0, \ldots, u_{d-1})\f$.
* @param evaluation_point Evaluation point \f$ (u_0, \ldots, u_{d-1}) \f$.
* @param challenge_powers Powers of \f$ r \f$, \f$ r^2 \), ..., \( r^{2^{m-1}} \f$.
* @param fold_polynomial_evals Evaluations \f$ A_{i-1}(-r^{2^{i-1}}) \f$.
* @return Evaluation \f$ A_0(r) \f$.
*/
static Fr compute_eval_pos(const Fr batched_mle_eval,
std::span<const Fr> mle_vars,
std::span<const Fr> r_squares,
std::span<const Fr> fold_polynomial_evals)
static Fr compute_gemini_batched_univariate_evaluation(Fr& batched_eval_accumulator,
std::span<const Fr> evaluation_point,
std::span<const Fr> challenge_powers,
std::span<const Fr> fold_polynomial_evals)
{
const size_t num_variables = mle_vars.size();
const size_t num_variables = evaluation_point.size();

const auto& evals = fold_polynomial_evals;

// Initialize eval_pos with batched MLE eval v = ∑ⱼ ρʲ vⱼ + ∑ⱼ ρᵏ⁺ʲ v↺ⱼ
Fr eval_pos = batched_mle_eval;
// Solve the sequence of linear equations
for (size_t l = num_variables; l != 0; --l) {
const Fr r = r_squares[l - 1]; // = rₗ₋₁ = r^{2ˡ⁻¹}
const Fr eval_neg = evals[l - 1]; // = Aₗ₋₁(−r^{2ˡ⁻¹})
const Fr u = mle_vars[l - 1]; // = uₗ₋₁

// The folding property ensures that
// Aₗ₋₁(r^{2ˡ⁻¹}) + Aₗ₋₁(−r^{2ˡ⁻¹}) Aₗ₋₁(r^{2ˡ⁻¹}) - Aₗ₋₁(−r^{2ˡ⁻¹})
// Aₗ(r^{2ˡ}) = (1-uₗ₋₁) ----------------------------- + uₗ₋₁ -----------------------------
// 2 2r^{2ˡ⁻¹}
// We solve the above equation in Aₗ₋₁(r^{2ˡ⁻¹}), using the previously computed Aₗ(r^{2ˡ}) in eval_pos
// and using Aₗ₋₁(−r^{2ˡ⁻¹}) sent by the prover in the proof.
eval_pos = ((r * eval_pos * 2) - eval_neg * (r * (Fr(1) - u) - u)) / (r * (Fr(1) - u) + u);
// Get r²⁽ˡ⁻¹⁾
const Fr& challenge_power = challenge_powers[l - 1];
// Get A₍ₗ₋₁₎(−r²⁽ˡ⁻¹⁾)
const Fr& eval_neg = evals[l - 1];
// Get uₗ₋₁
const Fr& u = evaluation_point[l - 1];
// Compute the numerator
batched_eval_accumulator =
((challenge_power * batched_eval_accumulator * 2) - eval_neg * (challenge_power * (Fr(1) - u) - u));
// Divide by the denominator
batched_eval_accumulator *= (challenge_power * (Fr(1) - u) + u).invert();
}

return eval_pos; // return A₀(r)
return batched_eval_accumulator;
}

/**
Expand Down
44 changes: 44 additions & 0 deletions barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once
#include "barretenberg/commitment_schemes/claim.hpp"
#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp"
#include "barretenberg/commitment_schemes/verification_key.hpp"
#include "barretenberg/common/assert.hpp"
#include "barretenberg/common/container.hpp"
Expand Down Expand Up @@ -579,6 +580,49 @@ template <typename Curve_> class IPA {
{
return reduce_verify_internal(vk, opening_claim, transcript);
}
/**
* @brief A method that produces an IPA opening claim from Shplemini accumulator containing vectors of commitments
* and scalars and a Shplonk evaluation challenge.
*
* @details Compute the commitment \f$ C \f$ that will be used to prove that Shplonk batching is performed correctly
* and check the evaluation claims of the batched univariate polynomials. The check is done by verifying that the
* polynomial corresponding to \f$ C \f$ evaluates to \f$ 0 \f$ at the Shplonk challenge point \f$ z \f$.
*
*/
static OpeningClaim<Curve> reduce_batch_opening_claim(
const BatchOpeningClaim<Curve>& batch_opening_claim)
{
using Utils = CommitmentSchemesUtils<Curve>;
// Extract batch_mul arguments from the accumulator
const auto& commitments = batch_opening_claim.commitments;
const auto& scalars = batch_opening_claim.scalars;
const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point;
// Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$
GroupElement shplonk_output_commitment;
if constexpr (Curve::is_stdlib_type) {
shplonk_output_commitment =
GroupElement::batch_mul(commitments, scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
} else {
shplonk_output_commitment = Utils::batch_mul_native(commitments, scalars);
}
// Output an opening claim to be verified by the IPA opening protocol
return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment };
}
/**
* @brief Verify the IPA opening claim obtained from a Shplemini accumulator
*
* @param batch_opening_claim
* @param vk
* @param transcript
* @return VerifierAccumulator
*/
static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim<Curve>& batch_opening_claim,
const std::shared_ptr<VK>& vk,
auto& transcript)
{
const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim);
return reduce_verify_internal(vk, opening_claim, transcript);
}
};

} // namespace bb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

#include "../gemini/gemini.hpp"
#include "../shplonk/shplemini_verifier.hpp"
#include "../shplonk/shplonk.hpp"
#include "./mock_transcript.hpp"
#include "barretenberg/commitment_schemes/commitment_key.test.hpp"
Expand All @@ -22,6 +23,7 @@ class IPATest : public CommitmentTest<Curve> {
using CK = CommitmentKey<Curve>;
using VK = VerifierCommitmentKey<Curve>;
using Polynomial = bb::Polynomial<Fr>;
using Commitment = typename Curve::AffineElement;
};
} // namespace

Expand Down Expand Up @@ -246,7 +248,7 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift)

// Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random
// point.
const auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
auto poly1 = this->random_polynomial(n);
auto poly2 = this->random_polynomial(n);
poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used
Expand Down Expand Up @@ -321,3 +323,87 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift)

EXPECT_EQ(result, true);
}
TEST_F(IPATest, ShpleminiIPAWithShift)
{
using IPA = IPA<Curve>;
using ShplonkProver = ShplonkProver_<Curve>;
using ShpleminiVerifier = ShpleminiVerifier_<Curve>;
using GeminiProver = GeminiProver_<Curve>;

const size_t n = 8;
const size_t log_n = 3;

// Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random
// point.
auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u'
auto poly1 = this->random_polynomial(n);
auto poly2 = this->random_polynomial(n);
poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used

Commitment commitment1 = this->commit(poly1);
Commitment commitment2 = this->commit(poly2);
std::vector<Commitment> unshifted_commitments = { commitment1, commitment2 };
std::vector<Commitment> shifted_commitments = { commitment2 };
auto eval1 = poly1.evaluate_mle(mle_opening_point);
auto eval2 = poly2.evaluate_mle(mle_opening_point);
auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true);

std::vector<Fr> multilinear_evaluations = { eval1, eval2, eval2_shift };

auto prover_transcript = NativeTranscript::prover_init_empty();
Fr rho = prover_transcript->template get_challenge<Fr>("rho");
std::vector<Fr> rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size());

Fr batched_evaluation = Fr::zero();
for (size_t i = 0; i < rhos.size(); ++i) {
batched_evaluation += multilinear_evaluations[i] * rhos[i];
}

Polynomial batched_unshifted(n);
Polynomial batched_to_be_shifted(n);
batched_unshifted.add_scaled(poly1, rhos[0]);
batched_unshifted.add_scaled(poly2, rhos[1]);
batched_to_be_shifted.add_scaled(poly2, rhos[2]);

auto gemini_polynomials = GeminiProver::compute_gemini_polynomials(
mle_opening_point, std::move(batched_unshifted), std::move(batched_to_be_shifted));

for (size_t l = 0; l < log_n - 1; ++l) {
std::string label = "FOLD_" + std::to_string(l + 1);
auto commitment = this->ck()->commit(gemini_polynomials[l + 2]);
prover_transcript->send_to_verifier(label, commitment);
}

const Fr r_challenge = prover_transcript->template get_challenge<Fr>("Gemini:r");

const auto [gemini_opening_pairs, gemini_witnesses] = GeminiProver::compute_fold_polynomial_evaluations(
mle_opening_point, std::move(gemini_polynomials), r_challenge);

std::vector<ProverOpeningClaim<Curve>> opening_claims;

for (size_t l = 0; l < log_n; ++l) {
std::string label = "Gemini:a_" + std::to_string(l);
const auto& evaluation = gemini_opening_pairs[l + 1].evaluation;
prover_transcript->send_to_verifier(label, evaluation);
opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]);
}
opening_claims.emplace_back(gemini_witnesses[log_n], gemini_opening_pairs[log_n]);

const auto opening_claim = ShplonkProver::prove(this->ck(), opening_claims, prover_transcript);
IPA::compute_opening_proof(this->ck(), opening_claim, prover_transcript);

auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);

const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(log_n,
RefVector(unshifted_commitments),
RefVector(shifted_commitments),
RefVector(multilinear_evaluations),
mle_opening_point,
this->vk()->get_g1_identity(),
verifier_transcript);

auto result = IPA::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript);
// auto result = IPA::reduce_verify(this->vk(), shplonk_verifier_claim, verifier_transcript);

EXPECT_EQ(result, true);
}
Loading

0 comments on commit e51d157

Please sign in to comment.