Skip to content

Commit

Permalink
feat: eccvm sumcheck with commitments to round univariates (#11206)
Browse files Browse the repository at this point in the history
[Protocol outline](https://hackmd.io/sxlCHpVISdaaQJbCpcXA-Q)

* combined with SmallSubgroup inner product argument, ensures that the
sumcheck round univariates do not leak witness information (In ECCVM)
* drastically reduces the eccvm proof size - instead of sending 24
coefficients of each round univariate, the prover sends evals at 0, 1,
and a group element
* reduces eccvm recursive verifier costs by avoiding expensive
evaluations of polys of degree 23 (360K gates -> 230K gates)
  • Loading branch information
iakovenkos authored Jan 22, 2025
1 parent 30a063a commit fe34b05
Show file tree
Hide file tree
Showing 34 changed files with 1,096 additions and 216 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ void eccvm_prove(State& state) noexcept
};
}

BENCHMARK(eccvm_generate_prover)->Unit(kMillisecond)->DenseRange(12, 18);
BENCHMARK(eccvm_prove)->Unit(kMillisecond)->DenseRange(12, 18);
BENCHMARK(eccvm_generate_prover)->Unit(kMillisecond)->DenseRange(12, CONST_ECCVM_LOG_N);
BENCHMARK(eccvm_prove)->Unit(kMillisecond)->DenseRange(12, CONST_ECCVM_LOG_N);
} // namespace

BENCHMARK_MAIN();
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,8 @@ TYPED_TEST(KZGTest, ShpleminiKzgWithShiftAndConcatenation)
&consistency_checked,
/* libra commitments = */ {},
/* libra evaluations = */ {},
{},
{},
to_vector_of_ref_vectors(concatenation_groups_commitments),
RefVector(c_evaluations));
const auto pairing_points = KZG::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "barretenberg/commitment_schemes/utils/instance_witness_generator.hpp"
#include "barretenberg/commitment_schemes/utils/test_settings.hpp"
#include "barretenberg/ecc/curves/bn254/g1.hpp"
#include "barretenberg/sumcheck/sumcheck.hpp"

#include <gtest/gtest.h>
#include <vector>
Expand All @@ -18,10 +19,53 @@ namespace bb {

template <class Flavor> class ShpleminiTest : public CommitmentTest<typename Flavor::Curve> {
public:
// Size of the test polynomials
static constexpr size_t n = 32;
static constexpr size_t log_n = 5;
// Total number of random polynomials in each test
static constexpr size_t num_polynomials = 5;
// Number of shiftable polynomials
static constexpr size_t num_shiftable = 2;

// The length of the mock sumcheck univariates.
static constexpr size_t sumcheck_univariate_length = 24;

using Fr = typename Flavor::Curve::ScalarField;
using GroupElement = typename Flavor::Curve::Element;
using Commitment = typename Flavor::Curve::AffineElement;
using CK = typename Flavor::CommitmentKey;

void compute_sumcheck_opening_data(std::vector<bb::Polynomial<Fr>>& round_univariates,
std::vector<Commitment>& sumcheck_commitments,
std::vector<std::array<Fr, 3>>& sumcheck_evaluations,
std::vector<Fr>& challenge,
std::shared_ptr<CK>& ck)
{
// Generate valid sumcheck polynomials of given length
auto mock_sumcheck_polynomials = ZKSumcheckData<Flavor>(log_n, sumcheck_univariate_length);
for (size_t idx = 0; idx < log_n; idx++) {
bb::Polynomial<Fr> round_univariate = mock_sumcheck_polynomials.libra_univariates[idx];

round_univariate.at(0) += mock_sumcheck_polynomials.libra_running_sum;

sumcheck_commitments.push_back(ck->commit(round_univariate));

sumcheck_evaluations.push_back({ round_univariate.at(0),
round_univariate.evaluate(Fr(1)),
round_univariate.evaluate(challenge[idx]) });

mock_sumcheck_polynomials.update_zk_sumcheck_data(challenge[idx], idx);
round_univariates.push_back(round_univariate);
}

// Simulate the `const proof size` logic
auto round_univariate = bb::Polynomial<Fr>(this->n);
for (size_t idx = this->log_n; idx < CONST_PROOF_SIZE_LOG_N; idx++) {
round_univariates.push_back(round_univariate);
sumcheck_commitments.push_back(ck->commit(round_univariate));
sumcheck_evaluations.push_back({ Fr(0), Fr(0), Fr(0) });
}
}
};

using TestSettings = ::testing::Types<BN254Settings, GrumpkinSettings>;
Expand Down Expand Up @@ -234,10 +278,10 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching)

/**
* @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver and Libra polynomials
* used to mask Sumcheck Round Univariates.
* used to mask Sumcheck Round Univariates. This abstracts the PCS step in each ZK Flavor running over BN254.
*
*/
TYPED_TEST(ShpleminiTest, ShpleminiWithZK)
TYPED_TEST(ShpleminiTest, ShpleminiZKNoSumcheckOpenings)
{
using ZKData = ZKSumcheckData<TypeParam>;
using Curve = TypeParam::Curve;
Expand All @@ -264,8 +308,8 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK)
const_size_mle_opening_point.begin() + this->log_n);

// Generate random prover polynomials, compute their evaluations and commitments
auto pcs_instance_witness =
InstanceWitnessGenerator<Curve>(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck);
InstanceWitnessGenerator<Curve> pcs_instance_witness(
this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck);

// Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges
const Fr claimed_inner_product = SmallSubgroupIPAProver<TypeParam>::compute_claimed_inner_product(
Expand All @@ -274,7 +318,7 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK)
prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product);

// Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials
auto small_subgroup_ipa_prover = SmallSubgroupIPAProver<TypeParam>(
SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
zk_sumcheck_data, const_size_mle_opening_point, claimed_inner_product, prover_transcript, ck);

// Reduce to KZG or IPA based on the curve used in the test Flavor
Expand Down Expand Up @@ -346,4 +390,116 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK)
}
}

/**
* @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver, Libra polynomials
* used to mask Sumcheck Round Univariates and prove/verify the claimed evaluations of committed sumcheck round
* univariates. This test abstracts the PCS step in each ZK Flavor running over Grumpkin.
*
*/
TYPED_TEST(ShpleminiTest, ShpleminiZKWithSumcheckOpenings)
{
using Curve = TypeParam::Curve;
using Fr = typename Curve::ScalarField;
using Commitment = typename Curve::AffineElement;
using CK = typename TypeParam::CommitmentKey;

using ShpleminiProver = ShpleminiProver_<Curve>;
using ShpleminiVerifier = ShpleminiVerifier_<Curve>;

std::shared_ptr<CK> ck = create_commitment_key<CK>(4096);

// Generate Sumcheck challenge, current implementation of Sumcheck Round Univariates batching in Shplemini assumes
// that the challenge is of CONST_PROOF_SIZE_LOG_N
std::vector<Fr> challenge = this->random_evaluation_point(CONST_PROOF_SIZE_LOG_N);

auto prover_transcript = TypeParam::Transcript::prover_init_empty();

// Generate masking polynomials for Sumcheck Round Univariates
ZKSumcheckData<TypeParam> zk_sumcheck_data(this->log_n, prover_transcript, ck);
// Generate mock witness
InstanceWitnessGenerator<Curve> pcs_instance_witness(this->n, 1);

// Generate valid sumcheck polynomials of given length
pcs_instance_witness.template compute_sumcheck_opening_data<TypeParam>(
this->n, this->log_n, this->sumcheck_univariate_length, challenge, ck);

// Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges
const Fr claimed_inner_product =
SmallSubgroupIPAProver<TypeParam>::compute_claimed_inner_product(zk_sumcheck_data, challenge, this->log_n);

prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product);

// Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials
SmallSubgroupIPAProver<TypeParam> small_subgroup_ipa_prover(
zk_sumcheck_data, challenge, claimed_inner_product, prover_transcript, ck);

// Reduce proving to a single claimed fed to KZG or IPA
const auto opening_claim = ShpleminiProver::prove(this->n,
RefVector(pcs_instance_witness.unshifted_polynomials),
RefVector(pcs_instance_witness.to_be_shifted_polynomials),
challenge,
ck,
prover_transcript,
small_subgroup_ipa_prover.get_witness_polynomials(),
pcs_instance_witness.round_univariates,
pcs_instance_witness.sumcheck_evaluations);

if constexpr (std::is_same_v<TypeParam, GrumpkinSettings>) {
IPA<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
} else {
KZG<Curve>::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
}

// Initialize verifier's transcript
auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);

std::array<Commitment, NUM_LIBRA_COMMITMENTS> libra_commitments = {};
libra_commitments[0] =
verifier_transcript->template receive_from_prover<Commitment>("Libra:concatenation_commitment");

// Place Libra data to the transcript
const Fr libra_total_sum = verifier_transcript->template receive_from_prover<Fr>("Libra:Sum");
const Fr libra_challenge = verifier_transcript->template get_challenge<Fr>("Libra:Challenge");
const Fr libra_evaluation = verifier_transcript->template receive_from_prover<Fr>("Libra:claimed_evaluation");

// Check that transcript is consistent
EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum);
EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge);
EXPECT_EQ(libra_evaluation, claimed_inner_product);

// Finalize the array of Libra/SmallSubgroupIpa commitments
libra_commitments[1] = verifier_transcript->template receive_from_prover<Commitment>("Libra:big_sum_commitment");
libra_commitments[2] = verifier_transcript->template receive_from_prover<Commitment>("Libra:quotient_commitment");

bool consistency_checked = true;

// Run Shplemini
const auto batch_opening_claim =
ShpleminiVerifier::compute_batch_opening_claim(this->n,
RefVector(pcs_instance_witness.unshifted_commitments),
{},
RefVector(pcs_instance_witness.unshifted_evals),
{},
challenge,
this->vk()->get_g1_identity(),
verifier_transcript,
{},
true,
&consistency_checked,
libra_commitments,
libra_evaluation,
pcs_instance_witness.sumcheck_commitments,
pcs_instance_witness.sumcheck_evaluations);
// Verify claim using KZG or IPA
if constexpr (std::is_same_v<TypeParam, GrumpkinSettings>) {
auto result =
IPA<Curve>::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript);
EXPECT_EQ(result, true);
} else {
const auto pairing_points =
KZG<Curve>::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript);
// Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2)
EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true);
}
}
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ template <typename Curve> class ShplonkProver_ {
*/
static Polynomial compute_batched_quotient(std::span<const ProverOpeningClaim<Curve>> opening_claims,
const Fr& nu,
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims)
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims,
std::span<const ProverOpeningClaim<Curve>> sumcheck_round_claims)
{
// Find n, the maximum size of all polynomials fⱼ(X)
size_t max_poly_size{ 0 };
Expand Down Expand Up @@ -86,6 +87,18 @@ template <typename Curve> class ShplonkProver_ {
Q.add_scaled(tmp, current_nu);
current_nu *= nu;
}

for (const auto& claim : sumcheck_round_claims) {

// Compute individual claim quotient tmp = ( fⱼ(X) − vⱼ) / ( X − xⱼ )
tmp = claim.polynomial;
tmp.at(0) = tmp[0] - claim.opening_pair.evaluation;
tmp.factor_roots(claim.opening_pair.challenge);

// Add the claim quotient to the batched quotient polynomial
Q.add_scaled(tmp, current_nu);
current_nu *= nu;
}
// Return batched quotient polynomial Q(X)
return Q;
};
Expand All @@ -105,7 +118,8 @@ template <typename Curve> class ShplonkProver_ {
Polynomial& batched_quotient_Q,
const Fr& nu_challenge,
const Fr& z_challenge,
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims = {})
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims = {},
std::span<const ProverOpeningClaim<Curve>> sumcheck_opening_claims = {})
{
const size_t num_opening_claims = opening_claims.size();

Expand All @@ -120,6 +134,11 @@ template <typename Curve> class ShplonkProver_ {
for (const auto& claim : libra_opening_claims) {
inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge);
}

for (const auto& claim : sumcheck_opening_claims) {
inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge);
}

Fr::batch_invert(inverse_vanishing_evals);

// G(X) = Q(X) - Q_z(X) = Q(X) - ∑ⱼ νʲ ⋅ ( fⱼ(X) − vⱼ) / ( z − xⱼ ),
Expand Down Expand Up @@ -160,6 +179,17 @@ template <typename Curve> class ShplonkProver_ {
idx++;
current_nu *= nu_challenge;
}

for (const auto& claim : sumcheck_opening_claims) {
tmp = claim.polynomial;
tmp.at(0) = tmp[0] - claim.opening_pair.evaluation;
Fr scaling_factor = current_nu * inverse_vanishing_evals[idx]; // = νʲ / (z − xⱼ )

// Add the claim quotient to the batched quotient polynomial
G.add_scaled(tmp, -scaling_factor);
idx++;
current_nu *= nu_challenge;
}
// Return opening pair (z, 0) and polynomial G(X) = Q(X) - Q_z(X)
return { .polynomial = G, .opening_pair = { .challenge = z_challenge, .evaluation = Fr::zero() } };
};
Expand All @@ -177,15 +207,17 @@ template <typename Curve> class ShplonkProver_ {
static ProverOpeningClaim<Curve> prove(const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
std::span<const ProverOpeningClaim<Curve>> opening_claims,
const std::shared_ptr<Transcript>& transcript,
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims = {})
std::span<const ProverOpeningClaim<Curve>> libra_opening_claims = {},
std::span<const ProverOpeningClaim<Curve>> sumcheck_round_claims = {})
{
const Fr nu = transcript->template get_challenge<Fr>("Shplonk:nu");
auto batched_quotient = compute_batched_quotient(opening_claims, nu, libra_opening_claims);
auto batched_quotient =
compute_batched_quotient(opening_claims, nu, libra_opening_claims, sumcheck_round_claims);
auto batched_quotient_commitment = commitment_key->commit(batched_quotient);
transcript->send_to_verifier("Shplonk:Q", batched_quotient_commitment);
const Fr z = transcript->template get_challenge<Fr>("Shplonk:z");
return compute_partially_evaluated_batched_quotient(
opening_claims, batched_quotient, nu, z, libra_opening_claims);
opening_claims, batched_quotient, nu, z, libra_opening_claims, sumcheck_round_claims);
}
};

Expand Down
Loading

1 comment on commit fe34b05

@AztecBot
Copy link
Collaborator

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'C++ Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.05.

Benchmark suite Current: fe34b05 Previous: 30a063a Ratio
nativeClientIVCBench/Full/6 24930.087184 ms/iter 21587.687391999963 ms/iter 1.15
wasmClientIVCBench/Full/6 80693.997431 ms/iter 72265.65905000002 ms/iter 1.12
commit(t) 3549754629 ns/iter 3284759030 ns/iter 1.08

This comment was automatically generated by workflow using github-action-benchmark.

CC: @ludamad @codygunton

Please sign in to comment.