Skip to content

Commit

Permalink
feat: prove openings of masking polynomials in ECCVM and Translator (#…
Browse files Browse the repository at this point in the history
…9726)

As a part of ZK-fication of Honk, we have to mask the evaluations of
round univariates that the prover sends to the verifier. The evaluations
were masked in Sumcheck in PR #7517. However, the logic for proving
evaluations of Libra masking polynomials was missing. This PR fixes this
issue and enables efficient batch opening of these polynomials.
* Added necessary logic to Shplonk Prover, Shplemini Prover, and
Shplemini Verifer
* Better handling of the ZKSumcheckData
* Removed methods and reverted changes that became obsolete because of
the new ZK strategy
* Enabled the opening of Libra masking univariates in ECCVM and
Translator
  • Loading branch information
iakovenkos authored Nov 7, 2024
1 parent 2509e2f commit f1cdc2d
Show file tree
Hide file tree
Showing 34 changed files with 613 additions and 294 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,42 @@ TYPED_TEST(KZGTest, single)
EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true);
}

/**
* @brief Test opening proof of a polynomial given by its evaluations at \f$ i = 0, \ldots, n \f$. Should only be used
* for small values of \f$ n \f$.
*
*/
TYPED_TEST(KZGTest, SingleInLagrangeBasis)
{
const size_t n = 4;

using KZG = KZG<TypeParam>;
using Fr = typename TypeParam::ScalarField;

// create a random univariate (coefficients are in Lagrange basis)
auto witness = bb::Univariate<Fr, n>::get_random();
// define the interpolation domain
std::array<Fr, 4> eval_points = { Fr(0), Fr(1), Fr(2), Fr(3) };
// compute the monomial coefficients
Polynomial<Fr> witness_polynomial(std::span<Fr>(eval_points), std::span<Fr>(witness), n);
// commit to the polynomial in the monomial form
g1::element commitment = this->commit(witness_polynomial);

auto challenge = Fr::random_element();
// evaluate the original univariate
auto evaluation = witness.evaluate(challenge);
auto opening_pair = OpeningPair<TypeParam>{ challenge, evaluation };
auto opening_claim = OpeningClaim<TypeParam>{ opening_pair, commitment };

auto prover_transcript = NativeTranscript::prover_init_empty();

KZG::compute_opening_proof(this->ck(), { witness_polynomial, opening_pair }, prover_transcript);

auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript);
auto pairing_points = KZG::reduce_verify(opening_claim, verifier_transcript);

EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true);
}
/**
* @brief Test full PCS protocol: Gemini, Shplonk, KZG and pairing check
* @details Demonstrates the full PCS protocol as it is used in the construction and verification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ template <typename Curve> class ShpleminiProver_ {
using ShplonkProver = ShplonkProver_<Curve>;
using GeminiProver = GeminiProver_<Curve>;

template <typename Transcript>
template <typename Transcript, size_t LENGTH = 0>
static OpeningClaim prove(const FF circuit_size,
RefSpan<Polynomial> f_polynomials,
RefSpan<Polynomial> g_polynomials,
std::span<FF> multilinear_challenge,
const std::shared_ptr<CommitmentKey<Curve>>& commitment_key,
const std::shared_ptr<Transcript>& transcript,
RefSpan<Polynomial> concatenated_polynomials = {},
const std::vector<RefVector<Polynomial>>& groups_to_be_concatenated = {})
const std::vector<RefVector<Polynomial>>& groups_to_be_concatenated = {},
const std::vector<bb::Univariate<FF, LENGTH>>& libra_univariates = {},
const std::vector<FF>& libra_evaluations = {})
{
std::vector<OpeningClaim> opening_claims = GeminiProver::prove(circuit_size,
f_polynomials,
Expand All @@ -38,8 +40,19 @@ template <typename Curve> class ShpleminiProver_ {
transcript,
concatenated_polynomials,
groups_to_be_concatenated);

OpeningClaim batched_claim = ShplonkProver::prove(commitment_key, opening_claims, transcript);
// 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 batched_claim =
ShplonkProver::prove(commitment_key, opening_claims, transcript, libra_opening_claims);
return batched_claim;
};
};
Expand Down Expand Up @@ -117,7 +130,9 @@ template <typename Curve> class ShpleminiVerifier_ {
const Commitment& g1_identity,
const std::shared_ptr<Transcript>& transcript,
const std::vector<RefVector<Commitment>>& concatenation_group_commitments = {},
RefSpan<Fr> concatenated_evaluations = {})
RefSpan<Fr> concatenated_evaluations = {},
RefSpan<Commitment> libra_univariate_commitments = {},
const std::vector<Fr>& libra_univariate_evaluations = {})

{

Expand Down Expand Up @@ -254,6 +269,18 @@ template <typename Curve> class ShpleminiVerifier_ {
commitments.emplace_back(g1_identity);
scalars.emplace_back(constant_term_accumulator);

// For ZK flavors, the sumcheck output contains the evaluations of Libra univariates that submitted to the
// ShpleminiVerifier, otherwise this argument is set to be empty
if (!libra_univariate_evaluations.empty()) {
add_zk_data(commitments,
scalars,
libra_univariate_commitments,
libra_univariate_evaluations,
multivariate_challenge,
shplonk_batching_challenge,
shplonk_evaluation_challenge);
}

return { commitments, scalars, shplonk_evaluation_challenge };
};
/**
Expand Down Expand Up @@ -439,5 +466,66 @@ template <typename Curve> class ShpleminiVerifier_ {
commitments.emplace_back(std::move(fold_commitments[j]));
}
}

/**
* @brief Add the opening data corresponding to Libra masking univariates to the batched opening claim
*
* @details After verifying ZK Sumcheck, the verifier has to validate the claims about the evaluations of Libra
* univariates used to mask Sumcheck round univariates. To minimize the overhead of such openings, we continue the
* Shplonk batching started in Gemini, i.e. we add new claims multiplied by a suitable power of the Shplonk batching
* challenge and re-use the evaluation challenge sampled to prove the evaluations of Gemini polynomials.
*
* @param commitments
* @param scalars
* @param libra_univariate_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 Fr& shplonk_batching_challenge,
const Fr& shplonk_evaluation_challenge)

{
// compute current power of Shplonk batching challenge taking into account the const proof size
Fr shplonk_challenge_power = Fr{ 1 };
for (size_t j = 0; j < CONST_PROOF_SIZE_LOG_N + 2; ++j) {
shplonk_challenge_power *= shplonk_batching_challenge;
}

// 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));
shplonk_challenge_power *= shplonk_batching_challenge;
// update the constant term of the Shplonk batched claim
constant_term += scaling_factor * libra_univariate_evaluation;
}
}
};
} // namespace bb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "../shplonk/shplonk.hpp"
#include "../utils/batch_mul_native.hpp"
#include "barretenberg/commitment_schemes/claim.hpp"
#include "barretenberg/commitment_schemes/ipa/ipa.hpp"
#include "barretenberg/ecc/curves/bn254/g1.hpp"

#include <gtest/gtest.h>
Expand Down Expand Up @@ -221,4 +222,123 @@ 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,
/* concatenated_polynomials = */ {},
/* groups_to_be_concatenated = */ {},
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,
/* concatenation_group_commitments = */ {},
/* concatenated_evaluations = */ {},
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
Loading

0 comments on commit f1cdc2d

Please sign in to comment.