Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: more efficient verification with shplonk and gemini #8351

Merged
merged 17 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
112 changes: 68 additions & 44 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 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side note: every prover/verifier class should have prove/verify methods

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);
}
std::vector<Commitment> commitments = get_gemini_commitments(num_variables, transcript);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const in various places


// 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);
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);
}

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,77 @@ template <typename Curve> class GeminiVerifier_ {
return fold_polynomial_opening_claims;
}

private:
static std::vector<Commitment> get_gemini_commitments(size_t log_circuit_size, auto& transcript)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log_circuit_size is const here and below, so is commitment in th body of this function

{
std::vector<Commitment> gemini_commitments;
gemini_commitments.reserve(log_circuit_size - 1);
for (size_t i = 0; i < log_circuit_size - 1; ++i) {
auto 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(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) {
auto 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_mle_eval,
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;
/// Initialize the evaluation of the univariatization of the batched multilinear polynomial with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that all of these /// comments render below the Returns part of the doxygen documentation, and I see this is nice to have as an overview of what's done on the docs, but I think the cost to readability of the actual code comments is too high. Eg I find the review of this stuff kind of hard becaus there is a lot of noise in the comments and the code looks really dense. So could you please revert to not using /// here and elsewhere for comments and not using any special latex tags on the code itsself, so all of the stuff that renders will just be in block comments at the declarations or headers of functions and classes? Sorry to be a pain but I think it's an important precedent to set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, makes sense. I rendered the latex formulas into unicode, so that they are less noisy and tried to slightly reduce the volume of comments

/// the evaluation of \f$ \sum \rho^i f_i + \sum \rho^{i+k} f_{i,\text{shift}} \f$ at \f$ (u_0,\ldots, u_{d-1})
/// \f$
Fr& batched_eval = batched_mle_eval;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't you just rename the input to this function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/// 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 \f$ r^{2^{\ell - 1}} \f$
const Fr& challenge_power = challenge_powers[l - 1];
/// Get \f$ A_{\ell-1}(−r^{2^{\ell -1 }})\f$
const Fr& eval_neg = evals[l - 1];
/// Get \f$ u_{\ell-1}\f$
const Fr& u = evaluation_point[l - 1];
/// Compute the numerator
batched_eval = ((challenge_power * batched_eval * 2) - eval_neg * (challenge_power * (Fr(1) - u) - u));
/// Divide by the denominator
batched_eval *= (challenge_power * (Fr(1) - u) + u).invert();
}

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

/**
Expand Down
45 changes: 45 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,7 @@
#pragma once
#include "barretenberg/commitment_schemes/claim.hpp"
#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp"
#include "barretenberg/commitment_schemes/utils/shplemini_accumulator.hpp"
#include "barretenberg/commitment_schemes/verification_key.hpp"
#include "barretenberg/common/assert.hpp"
#include "barretenberg/common/container.hpp"
Expand Down Expand Up @@ -572,6 +574,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> compute_opening_claim_from_shplemini_accumulators(
ShpleminiAccumulator<Curve>& shplemini_accumulator)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like input should be const as should the three aliases that you make.

{
using Utils = CommitmentSchemesUtils<Curve>;
/// Extract batch_mul arguments from the accumulator
auto& commitments = shplemini_accumulator.commitments;
auto& scalars = shplemini_accumulator.scalars;
Fr& shplonk_eval_challenge = shplemini_accumulator.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 shplemini_accumulator
* @param vk
* @param transcript
* @return VerifierAccumulator
*/
static VerifierAccumulator reduce_verify_shplemini_accumulator(ShpleminiAccumulator<Curve>& shplemini_accumulator,
const std::shared_ptr<VK>& vk,
auto& transcript)
{
auto opening_claim = compute_opening_claim_from_shplemini_accumulators(shplemini_accumulator);
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);

auto shplemini_accumulator = ShpleminiVerifier::accumulate_batch_mul_arguments(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_shplemini_accumulator(shplemini_accumulator, this->vk(), verifier_transcript);
// auto result = IPA::reduce_verify(this->vk(), shplonk_verifier_claim, verifier_transcript);

EXPECT_EQ(result, true);
}
Loading
Loading