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

feat: prove openings of masking polynomials in ECCVM and Translator #9726

Merged
merged 25 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2c37dc4
shplemini supports opening of libra masking univariates
iakovenkos Oct 28, 2024
7bbacc2
clean-up
iakovenkos Oct 29, 2024
c78ed6e
de-coupled sumcheck from zk sumcheck data
iakovenkos Oct 30, 2024
fe93b4d
eccvm native proves/verifies evals of masking polys
iakovenkos Oct 30, 2024
df7f731
debugging eccvm recursive
iakovenkos Oct 31, 2024
0b3e50a
debugggging still
iakovenkos Oct 31, 2024
525ae12
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Oct 31, 2024
4298d52
eccvm recursive fixed
iakovenkos Oct 31, 2024
bf468eb
opening proofs for libra polynomials in translator
iakovenkos Nov 4, 2024
ab9b81e
translator relation degree changes reverted
iakovenkos Nov 4, 2024
aca70fa
some clean-up
iakovenkos Nov 4, 2024
b7584ba
removed redundant line
iakovenkos Nov 4, 2024
6403477
clean-up
iakovenkos Nov 4, 2024
bec76b9
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Nov 4, 2024
8a1e95f
fix: link zero constraint relation in translator
iakovenkos Nov 4, 2024
2504556
fix bb native tests
iakovenkos Nov 5, 2024
d18f0c2
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Nov 5, 2024
937af2a
clean up
iakovenkos Nov 5, 2024
6824085
Merge branch 'si/zk-sumcheck-plus-shplemini' of github.com:AztecProto…
iakovenkos Nov 5, 2024
95d4160
fix build
iakovenkos Nov 5, 2024
69b913e
clean-up after review
iakovenkos Nov 6, 2024
ed57e42
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Nov 6, 2024
1775d41
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Nov 7, 2024
e8c691a
resolve conflicts with 'master'
iakovenkos Nov 7, 2024
5f62335
Merge branch 'master' into si/zk-sumcheck-plus-shplemini
iakovenkos Nov 7, 2024
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 @@ -41,6 +41,42 @@ TYPED_TEST(KZGTest, single)
EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true);
}

/**
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We usually commit to and open univariate polynomials represented in the monomial basis. Here is a correct flow for the univariates given in the Lagrange basis.

* @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,21 @@ 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;
if (!libra_univariates.empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

its a little redundant to have this check prior to the loop. If it's empty, the loop will just do nothing right?

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 +132,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 +271,16 @@ template <typename Curve> class ShpleminiVerifier_ {
commitments.emplace_back(g1_identity);
scalars.emplace_back(constant_term_accumulator);

if (!libra_univariate_evaluations.empty()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a more clarifying condition that could be used here? e.g is this always empty for non-zk flavors and always non-empty for zk flavors?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

exactly, but it's handled somewhat manually because the PCS doesn't know anything about the Flavor. added a clarifying comment

Copy link
Contributor

Choose a reason for hiding this comment

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

this is probably an indication that the PCS now needs to be templated on Flavor instead of just Curve

Copy link
Contributor Author

Choose a reason for hiding this comment

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

agree. I think I'll stop adding stuff to Shplemini quite soon and then we'll have to refactor it, because its interface is getting quite messy

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,69 @@ template <typename Curve> class ShpleminiVerifier_ {
commitments.emplace_back(std::move(fold_commitments[j]));
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

main feature of this PR - the method enabling efficient opening of claimed evaluations of Libra masking univariates. no extra challenges or batch mul calls required

/**
* @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;
}

// needed to keep track of the constant term contribution
const size_t idx_of_constant_term = commitments.size() - 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

another option here is to simply define contant_term or constant_scalar as a ref then update it below. E.g. Fr& constant_scalar = scalars.back(); then constant_scalar += constant_term;. just personal preference I suppose


// 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++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the number of libra univariates different from the size of the multivariate challenge? If not it might be better to use a range loop here on multivariate_challenge

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, the size of the challenge is CONST_PROOF_SIZE_LOG_N, and the number of libra univariates if the honest log circuit size. prob we'll need to constify it at some point, but I decided to avoid it here

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
Fr constant_term = 0;
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;
constant_term += scaling_factor * libra_univariate_evaluation;
}

scalars[idx_of_constant_term] += constant_term;
}
};
} // 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,115 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching)

EXPECT_EQ(shplemini_result, expected_result);
}

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;
const size_t LIBRA_UNIVARIATE_LENGTH = 12;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming this is just an arbitrary choice for this test but might be nice to leave a comment about how this is actually determined in practice since its sometimes useful to look at these tests to understand the protocol

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks, added a comment


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 intead of a vector)
Copy link
Contributor

Choose a reason for hiding this comment

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

typo

bb::Univariate<Fr, LIBRA_UNIVARIATE_LENGTH> libra_univariate;
for (size_t i = 0; i < LIBRA_UNIVARIATE_LENGTH; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI it looks like Univariate has it's own random_element() method - might be cleaner

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks! it's ugly here: Univariates are in the Lagrange basis, Polynomials are in the monomial, which means we can't commit to a Univariate without computing its coefficients in the monomial basis or computing the Lagrange basis using SRS + I want to store the masking data as a std::vector<Univariate<FF, 12>> (could/will be turned into std::array) instead of std::vector

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 };
Copy link
Contributor

Choose a reason for hiding this comment

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

unused?


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
Loading
Loading