-
Notifications
You must be signed in to change notification settings - Fork 240
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
Changes from all commits
2c37dc4
7bbacc2
c78ed6e
fe93b4d
df7f731
0b3e50a
525ae12
4298d52
bf468eb
ab9b81e
aca70fa
b7584ba
6403477
bec76b9
8a1e95f
2504556
d18f0c2
937af2a
6824085
95d4160
69b913e
ed57e42
1775d41
e8c691a
5f62335
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
@@ -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; | ||
}; | ||
}; | ||
|
@@ -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 = {}) | ||
|
||
{ | ||
|
||
|
@@ -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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }; | ||
}; | ||
/** | ||
|
@@ -439,5 +466,66 @@ template <typename Curve> class ShpleminiVerifier_ { | |
commitments.emplace_back(std::move(fold_commitments[j])); | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
|
||
// 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++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
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 |
---|---|---|
|
@@ -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> | ||
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 instead of a vector) | ||
bb::Univariate<Fr, LIBRA_UNIVARIATE_LENGTH> libra_univariate; | ||
for (size_t i = 0; i < LIBRA_UNIVARIATE_LENGTH; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI it looks like Univariate has it's own There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
/* 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 |
There was a problem hiding this comment.
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.