Skip to content

Commit

Permalink
feat: fix commitments and openings of masking polynomials used in zk …
Browse files Browse the repository at this point in the history
…sumcheck (#10773)

We have updated the approach for committing to Libra masking
polynomials. Instead of committing to them and opening them separately,
we now utilize the [inner products using KZG with ZK and a linear-time
verifier](https://hackmd.io/xYHn1qqvQjey1yJutcuXdg?both#inner-products-using-KZG-with-zk-and-linear-time-verifier)
protocol, referred to as **SmallSubgroupIPA**.

### Key Changes in this PR
-  Addressed ZK issues of the previous approach.
- Reduced the number of scalar multiplications required in our ZK
verifiers over BN254.
-  Finalized the necessary logic for UltraZK.

### Remark
However, the non-native arithmetic required by `ECCVMRecursiveVerifier`
becomes prohibitively expensive if we continue sending the coefficients
of `SumcheckUnivariates`. To address this, we have implemented a
Grumpkin-based version of **SmallSubgroupIPA**, which assumes sending
commitments to the `SumcheckRound` univariates. This will be done in a
follow-up update.
  • Loading branch information
iakovenkos authored Jan 7, 2025
1 parent b42756b commit fc48dcc
Show file tree
Hide file tree
Showing 32 changed files with 1,181 additions and 452 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ TYPED_TEST(KZGTest, ShpleminiKzgWithShiftAndConcatenation)

// Gemini verifier output:
// - claim: d+1 commitments to Fold_{r}^(0), Fold_{-r}^(0), Fold^(l), d+1 evaluations a_0_pos, a_l, l = 0:d-1
bool consistency_checked = true;
const auto batch_opening_claim =
ShpleminiVerifier::compute_batch_opening_claim(n,
RefVector(unshifted_commitments),
Expand All @@ -318,7 +319,9 @@ TYPED_TEST(KZGTest, ShpleminiKzgWithShiftAndConcatenation)
mle_opening_point,
this->vk()->get_g1_identity(),
verifier_transcript,
{},
/* repeated commitments= */ {},
/* has zk = */ {},
&consistency_checked,
/* libra commitments = */ {},
/* libra evaluations = */ {},
to_vector_of_ref_vectors(concatenation_groups_commitments),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
#include "barretenberg/commitment_schemes/commitment_key.hpp"
#include "barretenberg/commitment_schemes/gemini/gemini_impl.hpp"
#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp"
#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp"
#include "barretenberg/commitment_schemes/verification_key.hpp"
#include "barretenberg/flavor/repeated_commitments_data.hpp"
#include "barretenberg/sumcheck/zk_sumcheck_data.hpp"
#include "barretenberg/transcript/transcript.hpp"

namespace bb {
Expand All @@ -28,13 +30,12 @@ template <typename Curve> class ShpleminiProver_ {
std::span<FF> multilinear_challenge,
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
const std::shared_ptr<Transcript>& transcript,
const std::vector<bb::Univariate<FF, LENGTH>>& libra_univariates = {},
const std::vector<FF>& libra_evaluations = {},
const std::array<Polynomial, NUM_LIBRA_EVALUATIONS>& libra_polynomials = {},
RefSpan<Polynomial> concatenated_polynomials = {},
const std::vector<RefVector<Polynomial>>& groups_to_be_concatenated = {})
{
// While Shplemini is not templated on Flavor, we derive ZK flag this way
const bool has_zk = !libra_evaluations.empty();
const bool has_zk = (libra_polynomials[0].size() > 0);
std::vector<OpeningClaim> opening_claims = GeminiProver::prove(circuit_size,
f_polynomials,
g_polynomials,
Expand All @@ -46,15 +47,25 @@ template <typename Curve> class ShpleminiProver_ {
has_zk);
// Create opening claims for Libra masking univariates
std::vector<OpeningClaim> libra_opening_claims;
size_t idx = 0;
for (auto [libra_univariate, libra_evaluation] : zip_view(libra_univariates, libra_evaluations)) {
OpeningClaim new_claim;
new_claim.polynomial = Polynomial(libra_univariate);
new_claim.opening_pair.challenge = multilinear_challenge[idx];
new_claim.opening_pair.evaluation = libra_evaluation;
libra_opening_claims.push_back(new_claim);
idx++;
OpeningClaim new_claim;

if (has_zk) {
static constexpr FF subgroup_generator = Curve::subgroup_generator;
const auto gemini_r = opening_claims[0].opening_pair.challenge;

std::array<std::string, NUM_LIBRA_EVALUATIONS> libra_eval_labels = {
"Libra:concatenation_eval", "Libra:shifted_big_sum_eval", "Libra:big_sum_eval", "Libra:quotient_eval"
};
const std::array<FF, 4> evaluation_points = { gemini_r, gemini_r * subgroup_generator, gemini_r, gemini_r };
for (size_t idx = 0; idx < 4; idx++) {
new_claim.polynomial = std::move(libra_polynomials[idx]);
new_claim.opening_pair.challenge = evaluation_points[idx];
new_claim.opening_pair.evaluation = new_claim.polynomial.evaluate(evaluation_points[idx]);
transcript->send_to_verifier(libra_eval_labels[idx], new_claim.opening_pair.evaluation);
libra_opening_claims.push_back(new_claim);
}
}

const OpeningClaim batched_claim =
ShplonkProver::prove(commitment_key, opening_claims, transcript, libra_opening_claims);
return batched_claim;
Expand Down Expand Up @@ -134,13 +145,15 @@ template <typename Curve> class ShpleminiVerifier_ {
const Commitment& g1_identity,
const std::shared_ptr<Transcript>& transcript,
const RepeatedCommitmentsData& repeated_commitments = {},
RefSpan<Commitment> libra_univariate_commitments = {},
const std::vector<Fr>& libra_univariate_evaluations = {},
const bool has_zk = false,
bool* consistency_checked = nullptr, // TODO(https://github.com/AztecProtocol/barretenberg/issues/1191).
// Shplemini Refactoring: Remove bool pointer
const std::array<Commitment, NUM_LIBRA_COMMITMENTS>& libra_commitments = {},
const Fr& libra_univariate_evaluation = Fr{ 0 },
const std::vector<RefVector<Commitment>>& concatenation_group_commitments = {},
RefSpan<Fr> concatenated_evaluations = {})

{

// Extract log_circuit_size
size_t log_circuit_size{ 0 };
if constexpr (Curve::is_stdlib_type) {
Expand All @@ -152,7 +165,6 @@ template <typename Curve> class ShpleminiVerifier_ {
Fr batched_evaluation = Fr{ 0 };

// While Shplemini is not templated on Flavor, we derive ZK flag this way
const bool has_zk = !libra_univariate_evaluations.empty();
Commitment hiding_polynomial_commitment;
if (has_zk) {
hiding_polynomial_commitment =
Expand All @@ -176,6 +188,14 @@ template <typename Curve> class ShpleminiVerifier_ {
const std::vector<Fr> gemini_eval_challenge_powers =
gemini::powers_of_evaluation_challenge(gemini_evaluation_challenge, CONST_PROOF_SIZE_LOG_N);

std::array<Fr, NUM_LIBRA_EVALUATIONS> libra_evaluations;
if (has_zk) {
libra_evaluations[0] = transcript->template receive_from_prover<Fr>("Libra:concatenation_eval");
libra_evaluations[1] = transcript->template receive_from_prover<Fr>("Libra:shifted_big_sum_eval");
libra_evaluations[2] = transcript->template receive_from_prover<Fr>("Libra:big_sum_eval");
libra_evaluations[3] = transcript->template receive_from_prover<Fr>("Libra:quotient_eval");
}

// Process Shplonk transcript data:
// - Get Shplonk batching challenge
const Fr shplonk_batching_challenge = transcript->template get_challenge<Fr>("Shplonk:nu");
Expand Down Expand Up @@ -297,11 +317,14 @@ template <typename Curve> class ShpleminiVerifier_ {
if (has_zk) {
add_zk_data(commitments,
scalars,
libra_univariate_commitments,
libra_univariate_evaluations,
multivariate_challenge,
libra_commitments,
libra_evaluations,
gemini_evaluation_challenge,
shplonk_batching_challenge,
shplonk_evaluation_challenge);

*consistency_checked = SmallSubgroupIPAVerifier<Curve>::check_evaluations_consistency(
libra_evaluations, gemini_evaluation_challenge, multivariate_challenge, libra_univariate_evaluation);
}

return { commitments, scalars, shplonk_evaluation_challenge };
Expand Down Expand Up @@ -588,17 +611,17 @@ template <typename Curve> class ShpleminiVerifier_ {
*
* @param commitments
* @param scalars
* @param libra_univariate_commitments
* @param libra_commitments
* @param libra_univariate_evaluations
* @param multivariate_challenge
* @param shplonk_batching_challenge
* @param shplonk_evaluation_challenge
*/
static void add_zk_data(std::vector<Commitment>& commitments,
std::vector<Fr>& scalars,
RefSpan<Commitment> libra_univariate_commitments,
const std::vector<Fr>& libra_univariate_evaluations,
const std::vector<Fr>& multivariate_challenge,
const std::array<Commitment, NUM_LIBRA_COMMITMENTS>& libra_commitments,
const std::array<Fr, NUM_LIBRA_EVALUATIONS>& libra_evaluations,
const Fr& gemini_evaluation_challenge,
const Fr& shplonk_batching_challenge,
const Fr& shplonk_evaluation_challenge)

Expand All @@ -611,32 +634,35 @@ template <typename Curve> class ShpleminiVerifier_ {

// need to keep track of the contribution to the constant term
Fr& constant_term = scalars.back();
// compute shplonk denominators and batch invert them
std::vector<Fr> denominators;
size_t num_libra_univariates = libra_univariate_commitments.size();

// compute Shplonk denominators and invert them
for (size_t idx = 0; idx < num_libra_univariates; idx++) {
if constexpr (Curve::is_stdlib_type) {
denominators.push_back(Fr(1) / (shplonk_evaluation_challenge - multivariate_challenge[idx]));
} else {
denominators.push_back(shplonk_evaluation_challenge - multivariate_challenge[idx]);
}
};
if constexpr (!Curve::is_stdlib_type) {
Fr::batch_invert(denominators);
}
// add Libra commitments to the vector of commitments; compute corresponding scalars and the correction to
// the constant term
for (const auto [libra_univariate_commitment, denominator, libra_univariate_evaluation] :
zip_view(libra_univariate_commitments, denominators, libra_univariate_evaluations)) {
commitments.push_back(std::move(libra_univariate_commitment));
Fr scaling_factor = denominator * shplonk_challenge_power;
scalars.push_back((-scaling_factor));
for (size_t idx = 0; idx < libra_commitments.size(); idx++) {
commitments.push_back(libra_commitments[idx]);
}

std::array<Fr, NUM_LIBRA_EVALUATIONS> denominators;
std::array<Fr, NUM_LIBRA_EVALUATIONS> batching_scalars;
// compute Shplonk denominators and invert them
denominators[0] = Fr(1) / (shplonk_evaluation_challenge - gemini_evaluation_challenge);
denominators[1] =
Fr(1) / (shplonk_evaluation_challenge - Fr(Curve::subgroup_generator) * gemini_evaluation_challenge);
denominators[2] = denominators[0];
denominators[3] = denominators[0];

// compute the scalars to be multiplied against the commitments [libra_concatenated], [big_sum], [big_sum], and
// [libra_quotient]
for (size_t idx = 0; idx < libra_evaluations.size(); idx++) {
Fr scaling_factor = denominators[idx] * shplonk_challenge_power;
batching_scalars[idx] = -scaling_factor;
shplonk_challenge_power *= shplonk_batching_challenge;
// update the constant term of the Shplonk batched claim
constant_term += scaling_factor * libra_univariate_evaluation;
constant_term += scaling_factor * libra_evaluations[idx];
}

// to save a scalar mul, add the sum of the batching scalars corresponding to the big sum evaluations
scalars.push_back(batching_scalars[0]);
scalars.push_back(batching_scalars[1] + batching_scalars[2]);
scalars.push_back(batching_scalars[3]);
}
};
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -223,118 +223,4 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching)
EXPECT_EQ(shplemini_result, expected_result);
}

/**
* @brief Libra masking univariates are used in sumcheck to prevent the leakage of witness data through the evaluations
* of round univariates. Here we test the opening of log_n Libra masking univariates batched with the opening of several
* prover polynomials and their shifts.
*
*/
TYPED_TEST(ShpleminiTest, ShpleminiWithMaskingLibraUnivariates)
{
using ShpleminiProver = ShpleminiProver_<TypeParam>;
using ShpleminiVerifier = ShpleminiVerifier_<TypeParam>;
using KZG = KZG<TypeParam>;
using IPA = IPA<TypeParam>;
using Fr = typename TypeParam::ScalarField;
using Commitment = typename TypeParam::AffineElement;
using Polynomial = typename bb::Polynomial<Fr>;

const size_t n = 16;
const size_t log_n = 4;
// In practice, the length of Libra univariates is equal to FLAVOR::BATCHED_RELATION_PARTIAL_LENGTH
const size_t LIBRA_UNIVARIATE_LENGTH = 12;

std::array<Fr, LIBRA_UNIVARIATE_LENGTH> interpolation_domain;
for (size_t idx = 0; idx < LIBRA_UNIVARIATE_LENGTH; idx++) {
interpolation_domain[idx] = Fr(idx);
}
// 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 = Polynomial::random(n);
auto poly2 = Polynomial::random(n, 1);
auto poly3 = Polynomial::random(n, 1);
auto poly4 = Polynomial::random(n);

std::vector<bb::Univariate<Fr, LIBRA_UNIVARIATE_LENGTH>> libra_univariates;
std::vector<Commitment> libra_commitments;
std::vector<Fr> libra_evaluations;
for (size_t idx = 0; idx < log_n; idx++) {
// generate random polynomial
Polynomial libra_polynomial = Polynomial::random(LIBRA_UNIVARIATE_LENGTH);
// create a univariate with the same coefficients (to store an array instead of a vector)
bb::Univariate<Fr, LIBRA_UNIVARIATE_LENGTH> libra_univariate;
for (size_t i = 0; i < LIBRA_UNIVARIATE_LENGTH; i++) {
libra_univariate.value_at(i) = libra_polynomial[i];
}
libra_univariates.push_back(libra_univariate);

// commit to libra polynomial and populate the vector of libra commitments
Commitment libra_commitment = this->commit(libra_polynomial);
libra_commitments.push_back(libra_commitment);

// evaluate current libra univariate at the corresponding challenge and store the value in libra evaluations
libra_evaluations.push_back(libra_polynomial.evaluate(mle_opening_point[idx]));
}

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

// Collect multilinear evaluations for input to prover
// std::vector<Fr> multilinear_evaluations = { eval1, eval2, eval3, eval4, eval2_shift, eval3_shift };

auto prover_transcript = NativeTranscript::prover_init_empty();

// Run the full prover PCS protocol:
auto opening_claim = ShpleminiProver::prove(Fr{ n },
RefArray{ poly1, poly2, poly3, poly4 },
RefArray{ poly2, poly3 },
mle_opening_point,
this->ck(),
prover_transcript,
libra_univariates,
libra_evaluations);
if constexpr (std::is_same_v<TypeParam, curve::Grumpkin>) {
IPA::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
} else {
KZG::compute_opening_proof(this->ck(), opening_claim, prover_transcript);
}

// Run the full verifier PCS protocol with genuine opening claims (genuine commitment, genuine evaluation)

auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);

// Gemini verifier output:
// - claim: d+1 commitments to Fold_{r}^(0), Fold_{-r}^(0), Fold^(l), d+1 evaluations a_0_pos, a_l, l = 0:d-1
auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(n,
RefVector(unshifted_commitments),
RefVector(shifted_commitments),
RefArray{ eval1, eval2, eval3, eval4 },
RefArray{ eval2_shift, eval3_shift },
mle_opening_point,
this->vk()->get_g1_identity(),
verifier_transcript,
{},
RefVector(libra_commitments),
libra_evaluations);

if constexpr (std::is_same_v<TypeParam, curve::Grumpkin>) {
auto result = IPA::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript);
EXPECT_EQ(result, true);
} else {
const auto pairing_points = KZG::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 @@ -45,6 +45,13 @@ template <typename Curve> class ShplonkProver_ {
{
// Find n, the maximum size of all polynomials fⱼ(X)
size_t max_poly_size{ 0 };

if (!libra_opening_claims.empty()) {
// Max size of the polynomials in Libra opening claims is Curve::SUBGROUP_SIZE*2 + 2; we round it up to the
// next power of 2
const size_t log_subgroup_size = static_cast<size_t>(numeric::get_msb(Curve::SUBGROUP_SIZE));
max_poly_size = 1 << (log_subgroup_size + 1);
};
for (const auto& claim : opening_claims) {
max_poly_size = std::max(max_poly_size, claim.polynomial.size());
}
Expand Down
Loading

0 comments on commit fc48dcc

Please sign in to comment.