diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp index a50c0a8919af..dcf3b4fe3155 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp @@ -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), @@ -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), diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp index 1f594bbd3ebd..330759436618 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp @@ -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 { @@ -28,13 +30,12 @@ template class ShpleminiProver_ { std::span multilinear_challenge, const std::shared_ptr>& commitment_key, const std::shared_ptr& transcript, - const std::vector>& libra_univariates = {}, - const std::vector& libra_evaluations = {}, + const std::array& libra_polynomials = {}, RefSpan concatenated_polynomials = {}, const std::vector>& 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 opening_claims = GeminiProver::prove(circuit_size, f_polynomials, g_polynomials, @@ -46,15 +47,25 @@ template class ShpleminiProver_ { has_zk); // Create opening claims for Libra masking univariates std::vector 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 libra_eval_labels = { + "Libra:concatenation_eval", "Libra:shifted_big_sum_eval", "Libra:big_sum_eval", "Libra:quotient_eval" + }; + const std::array 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; @@ -134,13 +145,15 @@ template class ShpleminiVerifier_ { const Commitment& g1_identity, const std::shared_ptr& transcript, const RepeatedCommitmentsData& repeated_commitments = {}, - RefSpan libra_univariate_commitments = {}, - const std::vector& 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& libra_commitments = {}, + const Fr& libra_univariate_evaluation = Fr{ 0 }, const std::vector>& concatenation_group_commitments = {}, RefSpan concatenated_evaluations = {}) { - // Extract log_circuit_size size_t log_circuit_size{ 0 }; if constexpr (Curve::is_stdlib_type) { @@ -152,7 +165,6 @@ template 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 = @@ -176,6 +188,14 @@ template class ShpleminiVerifier_ { const std::vector gemini_eval_challenge_powers = gemini::powers_of_evaluation_challenge(gemini_evaluation_challenge, CONST_PROOF_SIZE_LOG_N); + std::array libra_evaluations; + if (has_zk) { + libra_evaluations[0] = transcript->template receive_from_prover("Libra:concatenation_eval"); + libra_evaluations[1] = transcript->template receive_from_prover("Libra:shifted_big_sum_eval"); + libra_evaluations[2] = transcript->template receive_from_prover("Libra:big_sum_eval"); + libra_evaluations[3] = transcript->template receive_from_prover("Libra:quotient_eval"); + } + // Process Shplonk transcript data: // - Get Shplonk batching challenge const Fr shplonk_batching_challenge = transcript->template get_challenge("Shplonk:nu"); @@ -297,11 +317,14 @@ template 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::check_evaluations_consistency( + libra_evaluations, gemini_evaluation_challenge, multivariate_challenge, libra_univariate_evaluation); } return { commitments, scalars, shplonk_evaluation_challenge }; @@ -588,7 +611,7 @@ template class ShpleminiVerifier_ { * * @param commitments * @param scalars - * @param libra_univariate_commitments + * @param libra_commitments * @param libra_univariate_evaluations * @param multivariate_challenge * @param shplonk_batching_challenge @@ -596,9 +619,9 @@ template class ShpleminiVerifier_ { */ static void add_zk_data(std::vector& commitments, std::vector& scalars, - RefSpan libra_univariate_commitments, - const std::vector& libra_univariate_evaluations, - const std::vector& multivariate_challenge, + const std::array& libra_commitments, + const std::array& libra_evaluations, + const Fr& gemini_evaluation_challenge, const Fr& shplonk_batching_challenge, const Fr& shplonk_evaluation_challenge) @@ -611,32 +634,35 @@ template 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 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 denominators; + std::array 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 \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp index e3537c00a756..bfb9fd931694 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp @@ -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_; - using ShpleminiVerifier = ShpleminiVerifier_; - using KZG = KZG; - using IPA = IPA; - using Fr = typename TypeParam::ScalarField; - using Commitment = typename TypeParam::AffineElement; - using Polynomial = typename bb::Polynomial; - - 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 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> libra_univariates; - std::vector libra_commitments; - std::vector 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 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 unshifted_commitments = { commitment1, commitment2, commitment3, commitment4 }; - std::vector 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 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) { - 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) { - 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 diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp index 5e90d4a00bb7..65b0fca780d8 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp @@ -45,6 +45,13 @@ template 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(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()); } diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp new file mode 100644 index 000000000000..39f9b27b6253 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp @@ -0,0 +1,545 @@ +#pragma once + +#include "barretenberg/constants.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/polynomials/univariate.hpp" +#include "barretenberg/sumcheck/zk_sumcheck_data.hpp" + +#include +#include + +namespace bb { + +/** + * @brief Small Subgroup IPA Prover for Zero-Knowledge Opening of Libra Polynomials. + * + * @details Implements a less general version of the protocol described in + * [Ariel's HackMD](https://hackmd.io/xYHn1qqvQjey1yJutcuXdg). This version is specialized for making + * commitments and openings of Libra polynomials zero-knowledge. + * + * ### Overview + * + * Let \f$ G \f$ be the masked concatenated Libra polynomial. Without masking, it is defined by concatenating Libra + * constant term and the monomial coefficients of the Libra univariates \f$ g_i \f$ in the Lagrange basis over \f$ H + * \f$. More explicitly, unmasked concatenated Libra polynomial is given by the following vector of coefficients: + * \f[ \big( \text{libra_constant_term}, g_{0,0}, \ldots, g_{0, + * \text{LIBRA_UNIVARIATES_LENGTH} - 1}, \ldots, g_{d-1, 0}, g_{d-1, \text{LIBRA_UNIVARIATES_LENGTH} - 1} \big) \f], + * where \f$ d = \text{log_circuit_size}\f$. + * It is masked by adding \f$ (r_0 + r_1 X) Z_{H}(X)\f$, where \f$ Z_H(X) \f$ is the vanishing polynomial for \f$ H \f$. + * + * This class enables the prover to: + * + * - Open the commitment to concatenated Libra polynomial with zero-knowledge while proving correctness of the claimed + * inner product. The concatenated polynomial is commited to during the construction of ZKSumcheckData structure. + * + * ### Inputs + * The prover receives: + * - **ZKSumcheckData:** Contains: + * - Monomial coefficients of the masked concatenated Libra polynomial \f$ G \f$. + * - Interpolation domain for a small subgroup \( H \subset \mathbb{F}^\ast \), where \(\mathbb{F} \) is the + * ScalarField of a given curve. + * - **Sumcheck challenges:** \( u_0, \ldots, u_{D-1} \), where \( D = \text{CONST_PROOF_SIZE_LOG_N} \). + * - **Claimed inner product:** \( s = \text{claimed\_ipa\_eval} \), defined as: + * \f[ + * s = \sum_{i=1}^{|H|} F(g^i) G(g^i), + * \f] + * where \( F(X) \) is the ``challenge`` polynomial constructed from the Sumcheck round challenges (see the formula + * below) and \( G(X) \) is the concatenated Libra polynomial. + * + * ### Prover's Construction + * 1. Define a polynomial \( A(X) \), called the **big sum polynomial**, which is analogous to the big product + * polynomial used to prove claims about \f$ \prod_{h\in H} f(h) \cdot g(h) \f$. It is uniquely defined by the + * following: + * - \( A(1) = 0 \), + * - \( A(g^i) = A(g^{i-1}) + F(g^{i-1}) G(g^{i-1}) \) for \( i = 1, \ldots, |H|-1 \). + * 2. Mask \( A(X) \) by adding \( Z_H(X) R(X) \), where \( R(X) \) is a random polynomial of degree 3. + * 3. Commit to \( A(X) \) and send the commitment to the verifier. + * + * ### Key Identity + * \( A(X) \) is honestly constructed, i.e. + * - \f$ A_0 = 0\f$, + * - \f$ A_{i} = A_{i-1} + F_{i-1} * G_{i-1}\f$ (Lagrange coefficients over \f$ H \f$) for \f$ i = 1,\ldots, |H|\f$ + * - \f$ A_{|H|} \f$ is equal to the claimed inner product \f$s\f$. + * if and only if the following identity holds: + * \f[ L_1(X) A(X) + (X - g^{-1}) (A(g \cdot X) - A(X) - + * F(X) G(X)) + L_{|H|}(X) (A(X) - s) = Z_H(X) Q(X), \f] where \( Q(X) \) is the quotient of the left-hand side by \( + * Z_H(X) \). The second summand is the translation of the second condition using the fact that the coefficients of \f$ + * A(gX) \f$ are given by a cyclic shift of the coefficients of \f$ A(X) \f$. + * + * The methods of this class allow the prover to compute \( A(X) \) and \( Q(X) \). + * + * After receiveing a random evaluation challenge \f$ r \f$ , the prover sends \f$ G(r), A(g\cdot r), A(r), Q(r) \f$ to + * the verifier. In our case, \f$ r \f$ is the Gemini evaluation challenge, and this part is taken care of by Shplemini. + */ +template class SmallSubgroupIPAProver { + using Curve = typename Flavor::Curve; + using FF = typename Curve::ScalarField; + // The size of a multiplicative subgroup in the ScalarField of a curve + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; + // Size of the polynomial to be divided by Z_H + static constexpr size_t BATCHED_POLYNOMIAL_LENGTH = 2 * SUBGROUP_SIZE + 2; + // Size of Q(X) + static constexpr size_t QUOTIENT_LENGTH = SUBGROUP_SIZE + 2; + // The length of a random polynomial to mask Prover's Sumcheck Univariates. In the case of BN254-based Flavors, we + // send the coefficients of the univariates, hence we choose these value to be the max sumcheck univariate length + // over Translator, Ultra, and Mega. In ECCVM, the Sumcheck prover will commit to its univariates, which reduces the + // required length from 23 to 3. + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = (std::is_same_v) ? 9 : 3; + // Fixed generator of H + static constexpr FF subgroup_generator = Curve::subgroup_generator; + + // Interpolation domain {1, g, \ldots, g^{SUBGROUP_SIZE - 1}} used by ECCVM + std::array interpolation_domain; + // We use IFFT over BN254 scalar field + EvaluationDomain bn_evaluation_domain; + + // Monomial coefficients of the concatenated Libra masking polynomial extracted from ZKSumcheckData + Polynomial concatenated_polynomial; + // Lagrange coefficeints of the concatenated Libra masking polynomial = constant_term || g_0 || ... || g_{d-1} + Polynomial libra_concatenated_lagrange_form; + + // Claimed evaluation s = constant_term + g_0(u_0) + ... + g_{d-1}(u_{d-1}), where g_i is the i'th Libra masking + // univariate + FF claimed_evaluation; + + // The polynomial obtained by concatenated powers of sumcheck challenges + Polynomial challenge_polynomial; + Polynomial challenge_polynomial_lagrange; + + // Big sum polynomial A(X) + Polynomial big_sum_polynomial_unmasked; + Polynomial big_sum_polynomial; + std::array big_sum_lagrange_coeffs; + + // The RHS of the key identity, denoted C(X) in the HackMD + Polynomial batched_polynomial; + + // Quotient of the batched polynomial C(X) by the subgroup vanishing polynomial X^{|H|} - 1 + Polynomial batched_quotient; + + public: + SmallSubgroupIPAProver(ZKSumcheckData& zk_sumcheck_data, + const std::vector& multivariate_challenge, + const FF claimed_ipa_eval, + std::shared_ptr transcript, + std::shared_ptr commitment_key) + : interpolation_domain(zk_sumcheck_data.interpolation_domain) + , concatenated_polynomial(zk_sumcheck_data.libra_concatenated_monomial_form) + , libra_concatenated_lagrange_form(zk_sumcheck_data.libra_concatenated_lagrange_form) + , challenge_polynomial(SUBGROUP_SIZE) + , challenge_polynomial_lagrange(SUBGROUP_SIZE) + , big_sum_polynomial_unmasked(SUBGROUP_SIZE) + , big_sum_polynomial(SUBGROUP_SIZE + 3) // + 3 to account for masking + , batched_polynomial(BATCHED_POLYNOMIAL_LENGTH) + , batched_quotient(QUOTIENT_LENGTH) + + { + // Extract the evaluation domain computed by ZKSumcheckData + if constexpr (std::is_same_v) { + bn_evaluation_domain = std::move(zk_sumcheck_data.bn_evaluation_domain); + } + + // Construct the challenge polynomial in Lagrange basis, compute its monomial coefficients + compute_challenge_polynomial(multivariate_challenge); + + // Construct unmasked big sum polynomial in Lagrange basis, compute its monomial coefficients and mask it + compute_big_sum_polynomial(); + + // Send masked commitment [A + Z_H * R] to the verifier, where R is of degree 2 + transcript->template send_to_verifier("Libra:big_sum_commitment", commitment_key->commit(big_sum_polynomial)); + + // Compute C(X) + compute_batched_polynomial(claimed_ipa_eval); + + // Compute Q(X) + compute_batched_quotient(); + + // Send commitment [Q] to the verifier + if (commitment_key) { + transcript->template send_to_verifier("Libra:quotient_commitment", + commitment_key->commit(batched_quotient)); + } + } + + // Getter to pass the witnesses to ShpleminiProver. Big sum polynomial is evaluated at 2 points (and is small) + std::array, NUM_LIBRA_EVALUATIONS> get_witness_polynomials() const + { + return { concatenated_polynomial, big_sum_polynomial, big_sum_polynomial, batched_quotient }; + } + // Getters for test purposes + const Polynomial& get_batched_polynomial() const { return batched_polynomial; } + const Polynomial& get_challenge_polynomial() const { return challenge_polynomial; } + + /** + * @brief Computes the challenge polynomial F(X) based on the provided multivariate challenges. + * + * This method generates a polynomial in both Lagrange basis and monomial basis from Sumcheck's + * multivariate_challenge vector. The result is stored in `challenge_polynomial_lagrange` and + * `challenge_polynomial`. The former is re-used in the computation of the big sum polynomial A(X) + * + * ### Lagrange Basis + * The Lagrange basis polynomial is constructed as follows: + * - Initialize the first coefficient as `1`. + * - For each challenge index `idx_poly` in the `CONST_PROOF_SIZE_LOG_N` range, compute a sequence of coefficients + * recursively as powers of the corresponding multivariate challenge. + * - Store these coefficients in `coeffs_lagrange_basis`. + * More explicitly, + * \f$ F = (1 , 1 , u_0, \ldots, u_0^{LIBRA_UNIVARIATES_LENGTH-1}, \ldots, 1, u_{D-1}, \ldots, + * u_{D-1}^{LIBRA_UNVIARIATES_LENGTH-1} ) \f$ in the Lagrange basis over \f$ H \f$. + * + * ### Monomial Basis + * If the curve is not `BN254`, the monomial polynomial is constructed directly using un-optimized Lagrange + * interpolation. Otherwise, an IFFT is used to convert the Lagrange basis coefficients into monomial basis + * coefficients. + * + * @param multivariate_challenge A vector of field elements used to compute the challenge polynomial. + */ + void compute_challenge_polynomial(const std::vector& multivariate_challenge) + { + std::vector coeffs_lagrange_basis(SUBGROUP_SIZE); + coeffs_lagrange_basis[0] = FF(1); + + for (size_t challenge_idx = 0; challenge_idx < CONST_PROOF_SIZE_LOG_N; challenge_idx++) { + // We concatenate 1 with CONST_PROOF_SIZE_LOG_N Libra Univariates of length LIBRA_UNIVARIATES_LENGTH + const size_t poly_to_concatenate_start = 1 + LIBRA_UNIVARIATES_LENGTH * challenge_idx; + coeffs_lagrange_basis[poly_to_concatenate_start] = FF(1); + for (size_t idx = poly_to_concatenate_start + 1; idx < poly_to_concatenate_start + LIBRA_UNIVARIATES_LENGTH; + idx++) { + // Recursively compute the powers of the challenge + coeffs_lagrange_basis[idx] = coeffs_lagrange_basis[idx - 1] * multivariate_challenge[challenge_idx]; + } + } + + challenge_polynomial_lagrange = Polynomial(coeffs_lagrange_basis); + + // Compute monomial coefficients + if constexpr (!std::is_same_v) { + challenge_polynomial = Polynomial(interpolation_domain, coeffs_lagrange_basis, SUBGROUP_SIZE); + } else { + std::vector challenge_polynomial_ifft(SUBGROUP_SIZE); + polynomial_arithmetic::ifft( + coeffs_lagrange_basis.data(), challenge_polynomial_ifft.data(), bn_evaluation_domain); + challenge_polynomial = Polynomial(challenge_polynomial_ifft); + } + } + + /** + * @brief Computes the big sum polynomial A(X) + * + * #### Lagrange Basis + * - First, we recursively compute the coefficients of the unmasked big sum polynomial, i.e. we set the first + * coefficient to `0`. + * - For each i, the coefficient is updated as: + * \f$ \texttt{big_sum_lagrange_coeffs} (g^{i}) = + * \texttt{big_sum_lagrange_coeffs} (g^{i-1}) + + * \texttt{challenge_polynomial_lagrange[prev_idx]} (g^{i-1}) \cdot + * \texttt{libra_concatenated_lagrange_form[prev_idx]} (g^{i-1}) \f$ + * #### Masking Term + * - A random polynomial of degree 2 is generated and added to the Big Sum Polynomial. + * - The masking term is applied as \f$ Z_H(X) \cdot \texttt{masking_term} \f$, where \f$ Z_H(X) \f$ is the + * vanishing polynomial. + * + */ + void compute_big_sum_polynomial() + { + big_sum_lagrange_coeffs[0] = 0; + + // Compute the big sum coefficients recursively + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + size_t prev_idx = idx - 1; + big_sum_lagrange_coeffs[idx] = + big_sum_lagrange_coeffs[prev_idx] + + challenge_polynomial_lagrange.at(prev_idx) * libra_concatenated_lagrange_form.at(prev_idx); + }; + + // Get the coefficients in the monomial basis + if constexpr (!std::is_same_v) { + big_sum_polynomial_unmasked = Polynomial(interpolation_domain, big_sum_lagrange_coeffs, SUBGROUP_SIZE); + } else { + std::vector big_sum_ifft(SUBGROUP_SIZE); + polynomial_arithmetic::ifft(big_sum_lagrange_coeffs.data(), big_sum_ifft.data(), bn_evaluation_domain); + big_sum_polynomial_unmasked = Polynomial(big_sum_ifft); + } + // Generate random masking_term of degree 2, add Z_H(X) * masking_term + bb::Univariate masking_term = bb::Univariate::get_random(); + big_sum_polynomial += big_sum_polynomial_unmasked; + + for (size_t idx = 0; idx < masking_term.size(); idx++) { + big_sum_polynomial.at(idx) -= masking_term.value_at(idx); + big_sum_polynomial.at(idx + SUBGROUP_SIZE) += masking_term.value_at(idx); + } + }; + + /** + * @brief Compute \f$ L_1(X) * A(X) + (X - 1/g) (A(gX) - A(X) - F(X) G(X)) + L_{|H|}(X)(A(X) - s) \f$, where \f$ g + * \f$ is the fixed generator of \f$ H \f$. + * + */ + void compute_batched_polynomial(const FF& claimed_evaluation) + { + // Compute shifted big sum polynomial A(gX) + Polynomial shifted_big_sum(SUBGROUP_SIZE + 3); + + for (size_t idx = 0; idx < SUBGROUP_SIZE + 3; idx++) { + shifted_big_sum.at(idx) = big_sum_polynomial.at(idx) * interpolation_domain[idx % SUBGROUP_SIZE]; + } + + const auto& [lagrange_first, lagrange_last] = + compute_lagrange_polynomials(interpolation_domain, bn_evaluation_domain); + + // Compute -F(X)*G(X), the negated product of challenge_polynomial and libra_concatenated_monomial_form + for (size_t i = 0; i < concatenated_polynomial.size(); ++i) { + for (size_t j = 0; j < challenge_polynomial.size(); ++j) { + batched_polynomial.at(i + j) -= concatenated_polynomial.at(i) * challenge_polynomial.at(j); + } + } + + // Compute - F(X) * G(X) + A(gX) - A(X) + for (size_t idx = 0; idx < shifted_big_sum.size(); idx++) { + batched_polynomial.at(idx) += shifted_big_sum.at(idx) - big_sum_polynomial.at(idx); + } + + // Mutiply - F(X) * G(X) + A(gX) - A(X) by X-g: + // 1. Multiply by X + for (size_t idx = batched_polynomial.size() - 1; idx > 0; idx--) { + batched_polynomial.at(idx) = batched_polynomial.at(idx - 1); + } + batched_polynomial.at(0) = FF(0); + // 2. Subtract 1/g(A(gX) - A(X) - F(X) * G(X)) + for (size_t idx = 0; idx < batched_polynomial.size() - 1; idx++) { + batched_polynomial.at(idx) -= batched_polynomial.at(idx + 1) * interpolation_domain[SUBGROUP_SIZE - 1]; + } + + // Add (L_1 + L_{|H|}) * A(X) to the result + for (size_t i = 0; i < big_sum_polynomial.size(); ++i) { + for (size_t j = 0; j < SUBGROUP_SIZE; ++j) { + batched_polynomial.at(i + j) += big_sum_polynomial.at(i) * (lagrange_first.at(j) + lagrange_last.at(j)); + } + } + // Subtract L_{|H|} * s + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + batched_polynomial.at(idx) -= lagrange_last.at(idx) * claimed_evaluation; + } + } + /** + * @brief Compute monomial coefficients of the first and last Lagrange polynomials + * + * @param interpolation_domain + * @param bn_evaluation_domain + * @return std::array, 2> + */ + std::array, 2> static compute_lagrange_polynomials( + const std::array& interpolation_domain, const EvaluationDomain& bn_evaluation_domain) + { + // Compute the monomial coefficients of L_1 + std::array lagrange_coeffs; + lagrange_coeffs[0] = FF(1); + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + lagrange_coeffs[idx] = FF(0); + } + + Polynomial lagrange_first_monomial(SUBGROUP_SIZE); + if constexpr (!std::is_same_v) { + lagrange_first_monomial = Polynomial(interpolation_domain, lagrange_coeffs, SUBGROUP_SIZE); + } else { + std::vector lagrange_first_ifft(SUBGROUP_SIZE); + polynomial_arithmetic::ifft(lagrange_coeffs.data(), lagrange_first_ifft.data(), bn_evaluation_domain); + lagrange_first_monomial = Polynomial(lagrange_first_ifft); + } + + // Compute the monomial coefficients of L_{|H|}, the last Lagrange polynomial + lagrange_coeffs[0] = FF(0); + lagrange_coeffs[SUBGROUP_SIZE - 1] = FF(1); + + Polynomial lagrange_last_monomial; + if constexpr (!std::is_same_v) { + lagrange_last_monomial = Polynomial(interpolation_domain, lagrange_coeffs, SUBGROUP_SIZE); + } else { + std::vector lagrange_last_ifft(SUBGROUP_SIZE); + polynomial_arithmetic::ifft(lagrange_coeffs.data(), lagrange_last_ifft.data(), bn_evaluation_domain); + lagrange_last_monomial = Polynomial(lagrange_last_ifft); + } + + return { lagrange_first_monomial, lagrange_last_monomial }; + } + /** @brief Efficiently compute the quotient of batched_polynomial by Z_H = X ^ { | H | } - 1 + */ + void compute_batched_quotient() + { + + auto remainder = batched_polynomial; + for (size_t idx = BATCHED_POLYNOMIAL_LENGTH - 1; idx >= SUBGROUP_SIZE; idx--) { + batched_quotient.at(idx - SUBGROUP_SIZE) = remainder.at(idx); + remainder.at(idx - SUBGROUP_SIZE) += remainder.at(idx); + } + } +}; + +/** + * @brief Verifier class for Small Subgroup IPA Prover. + * + * @details Checks the consistency of polynomial evaluations provided by the prover against + * the values derived from the sumcheck challenge and a random evaluation challenge. + */ +template class SmallSubgroupIPAVerifier { + using FF = typename Curve::ScalarField; + + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; + + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = (std::is_same_v) ? 9 : 3; + + public: + /*! + * @brief Verifies the consistency of polynomial evaluations provided by the prover. + * + * @details + * Given a subgroup of \f$ \mathbb{F}^\ast \f$, its generator \f$ g\f$, this function checks whether the following + * equation holds: + * \f[ L_1(r) A(r) + (r - g^{-1}) \left( A(g*r) - A(r) - F(r) G(r) \right) + L_{|H|}(r) \left( A(r) - s + * \right) = T(r) Z_H(r) \f] Where the following are sent by the prover + * - \f$ A(r), A(g\cdot r) \f$ are the evaluation of the "big sum polynomial" + * - \f$ G(r) \f$ is the evaluation of the concatenation of the coefficients of the masking Libra polynomials + * + * - \f$ T(r) \f$ is the evaluation of the quotient of the left hand side above by the vanishing polynomial for + * \f$H\f$ + * and the following evaluations computed by the verifier + * - \f$ L_1 \f$ and \f$ L_{|H|} \f$ are the Lagrange polynomials corresponding to \f$ 1 \f$ and \f$ g^{-1} \f$. + * - \f$ F(r) \f$ is the evaluation of the polynomial obtained by concatenating powers of sumcheck round challenges + * - \f$ Z_H(r) \f$ is the vanishing polynomial \f$ X^{|H|} - 1\f$ evaluated at the challenge point. + * + * @param libra_evaluations A vector of polynomial evaluations containing: + * - \f$ G(r), A(g\cdot r), A(r), T(r) \f$. + * @param gemini_evaluation_challenge The challenge point \f$ r \f$ at which evaluations are verified. + * @param multilinear_challenge A vector of sumcheck round challenges. + * @param eval_claim The claimed inner proudct of the coefficients of \f$G\f$ and \f$F\f$. + * @return True if the consistency check passes, false otherwise. + */ + static bool check_evaluations_consistency(const std::array& libra_evaluations, + const FF& gemini_evaluation_challenge, + const std::vector& multilinear_challenge, + const FF& inner_product_eval_claim) + { + + const FF subgroup_generator_inverse = Curve::subgroup_generator_inverse; + + // Compute the evaluation of the vanishing polynomia Z_H(X) at X = gemini_evaluation_challenge + const FF vanishing_poly_eval = gemini_evaluation_challenge.pow(SUBGROUP_SIZE) - FF(1); + + // Construct the challenge polynomial from the sumcheck challenge, the verifier has to evaluate it on its own + const std::vector challenge_polynomial_lagrange = compute_challenge_polynomial(multilinear_challenge); + + // Compute the evaluations of the challenge polynomial, Lagrange first, and Lagrange last for the fixed small + // subgroup + auto [challenge_poly, lagrange_first, lagrange_last] = + compute_batched_barycentric_evaluations(challenge_polynomial_lagrange, + gemini_evaluation_challenge, + subgroup_generator_inverse, + vanishing_poly_eval); + + const FF& concatenated_at_r = libra_evaluations[0]; + const FF& big_sum_shifted_eval = libra_evaluations[1]; + const FF& big_sum_eval = libra_evaluations[2]; + const FF& quotient_eval = libra_evaluations[3]; + + // Compute the evaluation of + // L_1(X) * A(X) + (X - 1/g) (A(gX) - A(X) - F(X) G(X)) + L_{|H|}(X)(A(X) - s) - Z_H(X) * Q(X) + FF diff = lagrange_first * big_sum_eval; + diff += (gemini_evaluation_challenge - subgroup_generator_inverse) * + (big_sum_shifted_eval - big_sum_eval - concatenated_at_r * challenge_poly); + diff += lagrange_last * (big_sum_eval - inner_product_eval_claim) - vanishing_poly_eval * quotient_eval; + + if constexpr (Curve::is_stdlib_type) { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1186). Insecure pattern. + return (diff.get_value() == FF(0).get_value()); + } else { + return (diff == FF(0)); + }; + } + + /** + * @brief Given the sumcheck multivariate challenge \f$ (u_0,\ldots, u_{D-1})\f$, where \f$ D = + * \text{CONST_PROOF_SIZE_LOG_N}\f$, the verifier has to construct and evaluate the polynomial whose + * coefficients are given by \f$ (1, u_0, u_0^2, u_1,\ldots, 1, u_{D-1}, u_{D-1}^2) \f$. We spend \f$ D \f$ + * multiplications to construct the coefficients. + * + * @param multivariate_challenge + * @return Polynomial + */ + static std::vector compute_challenge_polynomial(const std::vector& multivariate_challenge) + { + std::vector challenge_polynomial_lagrange(SUBGROUP_SIZE); + + challenge_polynomial_lagrange[0] = FF{ 1 }; + + // Populate the vector with the powers of the challenges + for (size_t idx_poly = 0; idx_poly < CONST_PROOF_SIZE_LOG_N; idx_poly++) { + size_t current_idx = 1 + LIBRA_UNIVARIATES_LENGTH * idx_poly; + challenge_polynomial_lagrange[current_idx] = FF(1); + for (size_t idx = 1; idx < LIBRA_UNIVARIATES_LENGTH; idx++) { + // Recursively compute the powers of the challenge + challenge_polynomial_lagrange[current_idx + idx] = + challenge_polynomial_lagrange[current_idx + idx - 1] * multivariate_challenge[idx_poly]; + } + } + return challenge_polynomial_lagrange; + } + + /** + * @brief Efficient batch evaluation of the challenge polynomial, Lagrange first, and Lagrange last + * + * @details It is a modification of \ref bb::polynomial_arithmetic::compute_barycentric_evaluation + * "compute_barycentric_evaluation" method that does not require EvaluationDomain object and outputs the barycentric + * evaluation of a polynomial along with the evaluations of the first and last Lagrange polynomials. The + * interpolation domain is given by \f$ (1, g, g^2, \ldots, g^{|H| -1 } )\f$ + * + * @param coeffs Coefficients of the polynomial to be evaluated, in our case it is the challenge polynomial + * @param z Evaluation point, we are using the Gemini evaluation challenge + * @param inverse_root_of_unity Inverse of the generator of the subgroup H + * @return std::array + */ + static std::array compute_batched_barycentric_evaluations(const std::vector& coeffs, + const FF& r, + const FF& inverse_root_of_unity, + const FF& vanishing_poly_eval) + { + std::array denominators; + FF one = FF{ 1 }; + FF numerator = vanishing_poly_eval; + + numerator *= one / FF(SUBGROUP_SIZE); // (r^n - 1) / n + + denominators[0] = r - one; + FF work_root = inverse_root_of_unity; // g^{-1} + // + // Compute the denominators of the Lagrange polynomials evaluated at r + for (size_t i = 1; i < SUBGROUP_SIZE; ++i) { + denominators[i] = work_root * r; + denominators[i] -= one; // r * g^{-i} - 1 + work_root *= inverse_root_of_unity; + } + + // Invert/Batch invert denominators + if constexpr (Curve::is_stdlib_type) { + for (FF& denominator : denominators) { + denominator = one / denominator; + } + } else { + FF::batch_invert(&denominators[0], SUBGROUP_SIZE); + } + std::array result; + + // Accumulate the evaluation of the polynomials given by `coeffs` vector + result[0] = FF{ 0 }; + for (const auto& [coeff, denominator] : zip_view(coeffs, denominators)) { + result[0] += coeff * denominator; // + coeffs_i * 1/(r * g^{-i} - 1) + } + + result[0] = result[0] * numerator; // The evaluation of the polynomials given by its evaluations over H + result[1] = denominators[0] * numerator; // Lagrange first evaluated at r + result[2] = denominators[SUBGROUP_SIZE - 1] * numerator; // Lagrange last evaluated at r + + return result; + } +}; +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/constants.hpp b/barretenberg/cpp/src/barretenberg/constants.hpp index 218761ae5506..a1cfae900adb 100644 --- a/barretenberg/cpp/src/barretenberg/constants.hpp +++ b/barretenberg/cpp/src/barretenberg/constants.hpp @@ -20,4 +20,7 @@ static constexpr uint32_t MAX_DATABUS_SIZE = 10000; // The number of entries in ProverPolynomials reserved for randomness intended to mask witness commitments, witness // evaluation at the sumcheck challenge, and, if necessary, the evaluation of the corresponding shift static constexpr uint32_t MASKING_OFFSET = 4; +// For ZK Flavors: the number of the commitments required by Libra and SmallSubgroupIPA. +static constexpr uint32_t NUM_LIBRA_COMMITMENTS = 3; +static constexpr uint32_t NUM_LIBRA_EVALUATIONS = 4; } // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp index 8b2308d53b78..b8ea6f839c81 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp @@ -23,5 +23,17 @@ class BN254 { // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; + + // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of + // the ScalarField and satisfy SUBGROUP_SIZE > CONST_PROOF_SIZE_LOG_N * Flavor::BATCHED_RELATION_PARTIAL_LENGTH, for + // each BN254-Flavor, since in every round of Sumcheck, the prover sends Flavor::BATCHED_RELATION_PARTIAL_LENGTH + // elements to the verifier. + static constexpr size_t SUBGROUP_SIZE = 256; + // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5. The generator below is + // 5^{2^{20}}. To avoid inversion in the recursive verifier, we also store the inverse of the chosen generator. + static constexpr ScalarField subgroup_generator = + ScalarField(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); + static constexpr ScalarField subgroup_generator_inverse = + ScalarField(uint256_t("0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6")); }; } // namespace bb::curve \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp index cbba4dc346c0..f195fbd7e53f 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp @@ -54,5 +54,18 @@ class Grumpkin { // classes are instantiated with "native" curve types. Eventually, the verifier classes will be instantiated only // with stdlib types, and "native" verification will be acheived via a simulated builder. static constexpr bool is_stdlib_type = false; + + // Required by SmallSubgroupIPA argument. This constant needs to divide the size of the multiplicative subgroup of + // the ScalarField and satisfy SUBGROUP_SIZE > CONST_PROOF_SIZE_LOG_N * 3, since in every round of Sumcheck, the + // prover sends 3 elements to the verifier. + static constexpr size_t SUBGROUP_SIZE = 87; + // The generator below was derived by factoring r - 1 into primes, where r is the modulus of the Grumkin scalar + // field. A random field element was sampled and raised to the power (r - 1) / (3 * 29). We verified that the + // resulting element does not generate a smaller subgroup by further raising it to the powers of 3 and 29. To + // optimize the recursive verifier and avoid costly inversions, we also precompute and store its inverse. + static constexpr ScalarField subgroup_generator = + ScalarField(uint256_t("0x147c647c09fb639514909e9f0513f31ec1a523bf8a0880bc7c24fbc962a9586b")); + static constexpr ScalarField subgroup_generator_inverse = + ScalarField("0x0c68e27477b5e78cfab790bd3b59806fa871771f71ec7452cde5384f6e3a1988"); }; } // namespace bb::curve \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp index 9648f2aab23e..7afbebf92d63 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp @@ -955,11 +955,17 @@ class ECCVMFlavor { Commitment transcript_msm_count_at_transition_inverse_comm; Commitment z_perm_comm; Commitment lookup_inverses_comm; - std::vector libra_commitments; + Commitment libra_concatenation_commitment; FF libra_sum; std::vector> sumcheck_univariates; - std::vector libra_evaluations; + FF libra_claimed_evaluation; + Commitment libra_big_sum_commitment; + Commitment libra_quotient_commitment; std::array sumcheck_evaluations; + FF libra_concatenation_eval; + FF libra_shifted_big_sum_eval; + FF libra_big_sum_eval; + FF libra_quotient_eval; Commitment hiding_polynomial_commitment; FF hiding_polynomial_eval; std::vector gemini_fold_comms; @@ -1160,11 +1166,8 @@ class ECCVMFlavor { NativeTranscript::proof_data, num_frs_read); z_perm_comm = NativeTranscript::template deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); - size_t log_circuit_size = static_cast(numeric::get_msb(circuit_size)); - for (size_t i = 0; i < log_circuit_size; i++) { - libra_commitments.emplace_back(NativeTranscript::template deserialize_from_buffer( - NativeTranscript::proof_data, num_frs_read)); - }; + libra_concatenation_commitment = + NativeTranscript::template deserialize_from_buffer(proof_data, num_frs_read); libra_sum = NativeTranscript::template deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { @@ -1173,12 +1176,13 @@ class ECCVMFlavor { NativeTranscript::proof_data, num_frs_read)); } - for (size_t i = 0; i < log_circuit_size; i++) { - libra_evaluations.emplace_back( - NativeTranscript::template deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read)); - } + libra_claimed_evaluation = NativeTranscript::template deserialize_from_buffer(proof_data, num_frs_read); sumcheck_evaluations = NativeTranscript::template deserialize_from_buffer>( NativeTranscript::proof_data, num_frs_read); + libra_big_sum_commitment = + NativeTranscript::template deserialize_from_buffer(proof_data, num_frs_read); + libra_quotient_commitment = + NativeTranscript::template deserialize_from_buffer(proof_data, num_frs_read); hiding_polynomial_commitment = deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); hiding_polynomial_eval = deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); @@ -1189,6 +1193,10 @@ class ECCVMFlavor { for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { gemini_fold_evals.push_back(deserialize_from_buffer(proof_data, num_frs_read)); } + libra_concatenation_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_shifted_big_sum_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_big_sum_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_quotient_eval = deserialize_from_buffer(proof_data, num_frs_read); shplonk_q_comm = deserialize_from_buffer(proof_data, num_frs_read); translation_eval_op = @@ -1208,7 +1216,6 @@ class ECCVMFlavor { void serialize_full_transcript() { size_t old_proof_length = NativeTranscript::proof_data.size(); - size_t log_circuit_size = static_cast(numeric::get_msb(circuit_size)); NativeTranscript::proof_data.clear(); @@ -1313,9 +1320,7 @@ class ECCVMFlavor { NativeTranscript::template serialize_to_buffer(lookup_inverses_comm, NativeTranscript::proof_data); NativeTranscript::template serialize_to_buffer(z_perm_comm, NativeTranscript::proof_data); - for (size_t i = 0; i < log_circuit_size; ++i) { - NativeTranscript::template serialize_to_buffer(libra_commitments[i], NativeTranscript::proof_data); - } + NativeTranscript::template serialize_to_buffer(libra_concatenation_commitment, proof_data); NativeTranscript::template serialize_to_buffer(libra_sum, NativeTranscript::proof_data); @@ -1323,10 +1328,11 @@ class ECCVMFlavor { NativeTranscript::template serialize_to_buffer(sumcheck_univariates[i], NativeTranscript::proof_data); } - for (size_t i = 0; i < log_circuit_size; ++i) { - NativeTranscript::template serialize_to_buffer(libra_evaluations[i], NativeTranscript::proof_data); - } + NativeTranscript::template serialize_to_buffer(libra_claimed_evaluation, proof_data); + NativeTranscript::template serialize_to_buffer(sumcheck_evaluations, NativeTranscript::proof_data); + NativeTranscript::template serialize_to_buffer(libra_big_sum_commitment, proof_data); + NativeTranscript::template serialize_to_buffer(libra_quotient_commitment, proof_data); NativeTranscript::template serialize_to_buffer(hiding_polynomial_commitment, NativeTranscript::proof_data); NativeTranscript::template serialize_to_buffer(hiding_polynomial_eval, NativeTranscript::proof_data); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N - 1; ++i) { @@ -1335,6 +1341,10 @@ class ECCVMFlavor { for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { NativeTranscript::template serialize_to_buffer(gemini_fold_evals[i], proof_data); } + NativeTranscript::template serialize_to_buffer(libra_concatenation_eval, proof_data); + NativeTranscript::template serialize_to_buffer(libra_shifted_big_sum_eval, proof_data); + NativeTranscript::template serialize_to_buffer(libra_big_sum_eval, proof_data); + NativeTranscript::template serialize_to_buffer(libra_quotient_eval, proof_data); NativeTranscript::template serialize_to_buffer(shplonk_q_comm, proof_data); NativeTranscript::template serialize_to_buffer(translation_eval_op, NativeTranscript::proof_data); diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index 4a4cfe0a6d23..35f2e3191751 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -3,6 +3,7 @@ #include "barretenberg/commitment_schemes/commitment_key.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" #include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" #include "barretenberg/common/ref_array.hpp" #include "barretenberg/honk/proof_system/logderivative_library.hpp" #include "barretenberg/plonk_honk_shared/library/grand_product_library.hpp" @@ -103,7 +104,7 @@ void ECCVMProver::execute_relation_check_rounds() gate_challenges[idx] = transcript->template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); } - zk_sumcheck_data = ZKSumcheckData(key->log_circuit_size, transcript, key->commitment_key); + zk_sumcheck_data = ZKData(key->log_circuit_size, transcript, key->commitment_key); sumcheck_output = sumcheck.prove(key->polynomials, relation_parameters, alpha, gate_challenges, zk_sumcheck_data); } @@ -121,6 +122,11 @@ void ECCVMProver::execute_pcs_rounds() using Shplonk = ShplonkProver_; using OpeningClaim = ProverOpeningClaim; + SmallSubgroupIPA small_subgroup_ipa_prover(zk_sumcheck_data, + sumcheck_output.challenge, + sumcheck_output.claimed_libra_evaluation, + transcript, + key->commitment_key); // Execute the Shplemini (Gemini + Shplonk) protocol to produce a univariate opening claim for the multilinear // evaluations produced by Sumcheck const OpeningClaim multivariate_to_univariate_opening_claim = @@ -130,8 +136,7 @@ void ECCVMProver::execute_pcs_rounds() sumcheck_output.challenge, key->commitment_key, transcript, - zk_sumcheck_data.libra_univariates_monomial, - sumcheck_output.claimed_libra_evaluations); + small_subgroup_ipa_prover.get_witness_polynomials()); // Get the challenge at which we evaluate all transcript polynomials as univariates evaluation_challenge_x = transcript->template get_challenge("Translation:evaluation_challenge_x"); diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.hpp index dab9d015ab2b..4217b3818622 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.hpp @@ -1,4 +1,5 @@ #pragma once +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" #include "barretenberg/eccvm/eccvm_flavor.hpp" #include "barretenberg/goblin/translation_evaluations.hpp" #include "barretenberg/honk/proof_system/types/proof.hpp" @@ -25,6 +26,8 @@ class ECCVMProver { using Transcript = typename Flavor::Transcript; using TranslationEvaluations = bb::TranslationEvaluations_; using CircuitBuilder = typename Flavor::CircuitBuilder; + using ZKData = ZKSumcheckData; + using SmallSubgroupIPA = SmallSubgroupIPAProver; explicit ECCVMProver(CircuitBuilder& builder, const std::shared_ptr& transcript = std::make_shared(), @@ -53,7 +56,7 @@ class ECCVMProver { std::shared_ptr key; CommitmentLabels commitment_labels; - ZKSumcheckData zk_sumcheck_data; + ZKData zk_sumcheck_data; Polynomial batched_quotient_Q; // batched quotient poly computed by Shplonk FF nu_challenge; // needed in both Shplonk rounds diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp index c285c8112e89..8166d607aca2 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp @@ -26,10 +26,9 @@ class ECCVMTranscriptTests : public ::testing::Test { * * @return TranscriptManifest */ - TranscriptManifest construct_eccvm_honk_manifest(size_t circuit_size) + TranscriptManifest construct_eccvm_honk_manifest() { TranscriptManifest manifest_expected; - auto log_n = numeric::get_msb(circuit_size); size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; // Size of types is number of bb::frs needed to represent the type size_t frs_per_Fr = bb::field_conversion::calc_num_bn254_frs(); @@ -139,10 +138,7 @@ class ECCVMTranscriptTests : public ::testing::Test { } round++; - for (size_t i = 0; i < log_n; i++) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Libra:commitment_" + idx, frs_per_G); - } + manifest_expected.add_entry(round, "Libra:concatenation_commitment", frs_per_G); manifest_expected.add_entry(round, "Libra:Sum", frs_per_Fr); // get the challenge for the ZK Sumcheck claim manifest_expected.add_challenge(round, "Libra:Challenge"); @@ -157,13 +153,10 @@ class ECCVMTranscriptTests : public ::testing::Test { round++; - for (size_t i = 0; i < log_n; i++) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Libra:evaluation_" + idx, frs_per_Fr); - } - // manifest_expected.add_entry(round, "Libra:evaluation", log_n * frs_per_Fr); - + manifest_expected.add_entry(round, "Libra:claimed_evaluation", frs_per_Fr); manifest_expected.add_entry(round, "Sumcheck:evaluations", frs_per_evals); + manifest_expected.add_entry(round, "Libra:big_sum_commitment", frs_per_G); + manifest_expected.add_entry(round, "Libra:quotient_commitment", frs_per_G); manifest_expected.add_entry(round, "Gemini:masking_poly_comm", frs_per_G); manifest_expected.add_entry(round, "Gemini:masking_poly_eval", frs_per_Fr); @@ -180,7 +173,10 @@ class ECCVMTranscriptTests : public ::testing::Test { std::string idx = std::to_string(i); manifest_expected.add_entry(round, "Gemini:a_" + idx, frs_per_Fr); } - + manifest_expected.add_entry(round, "Libra:concatenation_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:shifted_big_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:big_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:quotient_eval", frs_per_Fr); manifest_expected.add_challenge(round, "Shplonk:nu"); round++; manifest_expected.add_entry(round, "Shplonk:Q", frs_per_G); @@ -285,7 +281,7 @@ TEST_F(ECCVMTranscriptTests, ProverManifestConsistency) ECCVMProof proof = prover.construct_proof(); // Check that the prover generated manifest agrees with the manifest hard coded in this suite - auto manifest_expected = this->construct_eccvm_honk_manifest(prover.key->circuit_size); + auto manifest_expected = this->construct_eccvm_honk_manifest(); auto prover_manifest = prover.transcript->get_manifest(); // Note: a manifest can be printed using manifest.print() diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp index f4a106833357..b2a260a8be7f 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp @@ -55,15 +55,16 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) } // Receive commitments to Libra masking polynomials - std::vector libra_commitments; - for (size_t idx = 0; idx < log_circuit_size; idx++) { - Commitment libra_commitment = - transcript->receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - } + std::array libra_commitments = {}; + + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); - auto [multivariate_challenge, claimed_evaluations, libra_evaluations, sumcheck_verified] = + auto [multivariate_challenge, claimed_evaluations, libra_evaluation, sumcheck_verified] = sumcheck.verify(relation_parameters, alpha, gate_challenges); + + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); + // If Sumcheck did not verify, return false if (sumcheck_verified.has_value() && !sumcheck_verified.value()) { vinfo("eccvm sumcheck failed"); @@ -71,6 +72,7 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) } // Compute the Shplemini accumulator consisting of the Shplonk evaluation and the commitments and scalars vector // produced by the unified protocol + bool consistency_checked = true; BatchOpeningClaim sumcheck_batch_opening_claims = Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted(), @@ -81,8 +83,10 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) key->pcs_verification_key->get_g1_identity(), transcript, Flavor::REPEATED_COMMITMENTS, - RefVector(libra_commitments), - libra_evaluations); + Flavor::HasZK, + &consistency_checked, + libra_commitments, + libra_evaluation); // Reduce the accumulator to a single opening claim const OpeningClaim multivariate_to_univariate_opening_claim = @@ -132,6 +136,6 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) PCS::reduce_verify(key->pcs_verification_key, batch_opening_claim, ipa_transcript); vinfo("eccvm sumcheck verified?: ", sumcheck_verified.value()); vinfo("batch opening verified?: ", batched_opening_verified); - return sumcheck_verified.value() && batched_opening_verified; + return sumcheck_verified.value() && batched_opening_verified && consistency_checked; } } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.cpp index a94224828541..248fa30346e1 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.cpp @@ -76,18 +76,19 @@ ECCVMRecursiveVerifier_::verify_proof(const ECCVMProof& proof) } // Receive commitments to Libra masking polynomials - std::vector libra_commitments; - for (size_t idx = 0; idx < log_circuit_size; idx++) { - Commitment libra_commitment = - transcript->template receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - } + std::array libra_commitments = {}; + + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); - auto [multivariate_challenge, claimed_evaluations, libra_evaluations, sumcheck_verified] = + auto [multivariate_challenge, claimed_evaluations, claimed_libra_evaluation, sumcheck_verified] = sumcheck.verify(relation_parameters, alpha, gate_challenges); + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); + // Compute the Shplemini accumulator consisting of the Shplonk evaluation and the commitments and scalars vector // produced by the unified protocol + bool consistency_checked = true; BatchOpeningClaim sumcheck_batch_opening_claims = Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted(), @@ -98,8 +99,10 @@ ECCVMRecursiveVerifier_::verify_proof(const ECCVMProof& proof) key->pcs_verification_key->get_g1_identity(), transcript, Flavor::REPEATED_COMMITMENTS, - RefVector(libra_commitments), - libra_evaluations); + Flavor::HasZK, + &consistency_checked, + libra_commitments, + claimed_libra_evaluation); // Reduce the accumulator to a single opening claim const OpeningClaim multivariate_to_univariate_opening_claim = diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/decider_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/decider_recursive_verifier.cpp index 162a890e5ff4..3be2eb346a55 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/decider_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/decider_recursive_verifier.cpp @@ -39,7 +39,8 @@ std::array DeciderRecursiveVerifier_:: multivariate_challenge, Commitment::one(builder), transcript, - Flavor::REPEATED_COMMITMENTS); + Flavor::REPEATED_COMMITMENTS, + Flavor::HasZK); auto pairing_points = PCS::reduce_verify_batch_opening_claim(opening_claim, transcript); return pairing_points; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp index 937ca0a48ecb..058a12342909 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.cpp @@ -106,24 +106,22 @@ UltraRecursiveVerifier_::Output UltraRecursiveVerifier_::verify_ auto sumcheck = Sumcheck(log_circuit_size, transcript); // Receive commitments to Libra masking polynomials - std::vector libra_commitments = {}; + std::array libra_commitments = {}; + FF libra_evaluation{ 0 }; if constexpr (Flavor::HasZK) { - for (size_t idx = 0; idx < log_circuit_size; idx++) { - Commitment libra_commitment = - transcript->template receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - }; + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); } SumcheckOutput sumcheck_output = sumcheck.verify(verification_key->relation_parameters, verification_key->alphas, gate_challenges); // For MegaZKFlavor: the sumcheck output contains claimed evaluations of the Libra polynomials - std::vector libra_evaluations = {}; if constexpr (Flavor::HasZK) { - libra_evaluations = std::move(sumcheck_output.claimed_libra_evaluations); + libra_evaluation = std::move(sumcheck_output.claimed_libra_evaluation); + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); } - // Execute Shplemini to produce a batch opening claim subsequently verified by a univariate PCS + bool consistency_checked = true; const BatchOpeningClaim opening_claim = Shplemini::compute_batch_opening_claim(key->circuit_size, commitments.get_unshifted(), @@ -134,8 +132,10 @@ UltraRecursiveVerifier_::Output UltraRecursiveVerifier_::verify_ Commitment::one(builder), transcript, Flavor::REPEATED_COMMITMENTS, - RefVector(libra_commitments), - libra_evaluations); + Flavor::HasZK, + &consistency_checked, + libra_commitments, + libra_evaluation); auto pairing_points = PCS::reduce_verify_batch_opening_claim(opening_claim, transcript); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp index c15d39d586d3..5feb3b6e872a 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp @@ -41,5 +41,15 @@ template struct bn254 { using bigfr_ct = bigfield; using g1_bigfr_ct = element; + // Required by SmallSubgroupIPA argument + static constexpr size_t SUBGROUP_SIZE = 256; + // BN254's scalar field has a multiplicative subgroup of order 2^28. It is generated by 5. The generator below is + // 5^{2^{20}}. To avoid inversion in the recursive verifier, we also store ir + static constexpr bb::fr subgroup_generator = + bb::fr(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); + static constexpr bb::fr subgroup_generator_inverse = + bb::fr(uint256_t("0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6")); + }; // namespace bn254 + } // namespace bb::stdlib diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp index 8f8555886e66..c8630ac735ea 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp @@ -32,5 +32,16 @@ template struct grumpkin { using byte_array_ct = byte_array; using bool_ct = bool_t; using uint32_ct = stdlib::uint32; + + // Required by SmallSubgroupIPA argument + static constexpr size_t SUBGROUP_SIZE = 87; + // To find the generator below, we factored r - 1 into primes, where r is the modulus of the Grumkin scalar field, + // sampled a random field element, raised it to (r-1)/(3*29), and ensured that the resulting element is + // not generating a smaller subgroup. To avoid inversion in the recursive verifier, we also store its inverse. + static constexpr bb::fq subgroup_generator = + bb::fq("0x147c647c09fb639514909e9f0513f31ec1a523bf8a0880bc7c24fbc962a9586b"); + static constexpr bb::fq subgroup_generator_inverse = + bb::fq("0x0c68e27477b5e78cfab790bd3b59806fa871771f71ec7452cde5384f6e3a1988"); }; + } // namespace bb::stdlib \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp index ce321b18ea89..c0cbacf2e925 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.cpp @@ -111,18 +111,17 @@ std::array TranslatorRecursiveVerifier_template get_challenge("Sumcheck:gate_challenge_" + std::to_string(idx)); } - std::vector libra_commitments; - for (size_t idx = 0; idx < log_circuit_size; idx++) { - Commitment libra_commitment = - transcript->template receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - } - auto [multivariate_challenge, claimed_evaluations, libra_evaluations, sumcheck_verified] = + std::array libra_commitments = {}; + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); + + auto [multivariate_challenge, claimed_evaluations, libra_evaluation, sumcheck_verified] = sumcheck.verify(relation_parameters, alpha, gate_challenges); - // Execute ZeroMorph rounds followed by the univariate PCS. See https://hackmd.io/dlf9xEwhTQyE3hiGbq4FsA?view for a - // complete description of the unrolled protocol. + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); + // Execute Shplemini + bool consistency_checked = true; const BatchOpeningClaim opening_claim = Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted_without_concatenated(), @@ -133,8 +132,10 @@ std::array TranslatorRecursiveVerifier_ libra_commitments; + Commitment libra_concatenation_commitment; FF libra_sum; std::vector> sumcheck_univariates; - std::vector libra_evaluations; + FF libra_claimed_evaluation; + Commitment libra_big_sum_commitment; + Commitment libra_quotient_commitment; std::array sumcheck_evaluations; + FF libra_concatenation_eval; + FF libra_shifted_big_sum_eval; + FF libra_big_sum_eval; + FF libra_quotient_eval; Commitment hiding_polynomial_commitment; FF hiding_polynomial_eval; std::vector gemini_fold_comms; @@ -87,7 +93,6 @@ class MegaZKFlavor : public bb::MegaFlavor { // take current proof and put them into the struct size_t num_frs_read = 0; circuit_size = deserialize_from_buffer(proof_data, num_frs_read); - size_t log_circuit_size = static_cast(numeric::get_msb(circuit_size)); public_input_size = deserialize_from_buffer(proof_data, num_frs_read); pub_inputs_offset = deserialize_from_buffer(proof_data, num_frs_read); @@ -118,10 +123,7 @@ class MegaZKFlavor : public bb::MegaFlavor { w_4_comm = deserialize_from_buffer(proof_data, num_frs_read); lookup_inverses_comm = deserialize_from_buffer(proof_data, num_frs_read); z_perm_comm = deserialize_from_buffer(proof_data, num_frs_read); - for (size_t i = 0; i < log_circuit_size; i++) { - libra_commitments.emplace_back(NativeTranscript::template deserialize_from_buffer( - NativeTranscript::proof_data, num_frs_read)); - }; + libra_concatenation_commitment = deserialize_from_buffer(proof_data, num_frs_read); libra_sum = NativeTranscript::template deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); @@ -130,11 +132,10 @@ class MegaZKFlavor : public bb::MegaFlavor { deserialize_from_buffer>(proof_data, num_frs_read)); } - for (size_t i = 0; i < log_circuit_size; i++) { - libra_evaluations.emplace_back( - NativeTranscript::template deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read)); - } + libra_claimed_evaluation = deserialize_from_buffer(proof_data, num_frs_read); sumcheck_evaluations = deserialize_from_buffer>(proof_data, num_frs_read); + libra_big_sum_commitment = deserialize_from_buffer(proof_data, num_frs_read); + libra_quotient_commitment = deserialize_from_buffer(proof_data, num_frs_read); hiding_polynomial_commitment = deserialize_from_buffer(proof_data, num_frs_read); hiding_polynomial_eval = deserialize_from_buffer(NativeTranscript::proof_data, num_frs_read); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N - 1; ++i) { @@ -143,6 +144,10 @@ class MegaZKFlavor : public bb::MegaFlavor { for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { gemini_fold_evals.push_back(deserialize_from_buffer(proof_data, num_frs_read)); } + libra_concatenation_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_shifted_big_sum_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_big_sum_eval = deserialize_from_buffer(proof_data, num_frs_read); + libra_quotient_eval = deserialize_from_buffer(proof_data, num_frs_read); shplonk_q_comm = deserialize_from_buffer(proof_data, num_frs_read); kzg_w_comm = deserialize_from_buffer(proof_data, num_frs_read); @@ -151,7 +156,6 @@ class MegaZKFlavor : public bb::MegaFlavor { void serialize_full_transcript() { size_t old_proof_length = proof_data.size(); - size_t log_circuit_size = static_cast(numeric::get_msb(circuit_size)); proof_data.clear(); serialize_to_buffer(circuit_size, proof_data); serialize_to_buffer(public_input_size, proof_data); @@ -184,19 +188,17 @@ class MegaZKFlavor : public bb::MegaFlavor { serialize_to_buffer(lookup_inverses_comm, proof_data); serialize_to_buffer(z_perm_comm, proof_data); - for (size_t i = 0; i < log_circuit_size; ++i) { - NativeTranscript::template serialize_to_buffer(libra_commitments[i], NativeTranscript::proof_data); - } + serialize_to_buffer(libra_concatenation_commitment, proof_data); NativeTranscript::template serialize_to_buffer(libra_sum, NativeTranscript::proof_data); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { serialize_to_buffer(sumcheck_univariates[i], proof_data); } - for (size_t i = 0; i < log_circuit_size; ++i) { - NativeTranscript::template serialize_to_buffer(libra_evaluations[i], NativeTranscript::proof_data); - } + serialize_to_buffer(libra_claimed_evaluation, proof_data); serialize_to_buffer(sumcheck_evaluations, proof_data); + serialize_to_buffer(libra_big_sum_commitment, proof_data); + serialize_to_buffer(libra_quotient_commitment, proof_data); serialize_to_buffer(hiding_polynomial_commitment, NativeTranscript::proof_data); serialize_to_buffer(hiding_polynomial_eval, NativeTranscript::proof_data); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N - 1; ++i) { @@ -205,6 +207,10 @@ class MegaZKFlavor : public bb::MegaFlavor { for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { serialize_to_buffer(gemini_fold_evals[i], proof_data); } + serialize_to_buffer(libra_concatenation_eval, proof_data); + serialize_to_buffer(libra_shifted_big_sum_eval, proof_data); + serialize_to_buffer(libra_big_sum_eval, proof_data); + serialize_to_buffer(libra_quotient_eval, proof_data); serialize_to_buffer(shplonk_q_comm, proof_data); serialize_to_buffer(kzg_w_comm, proof_data); diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp index b4645bf0d0a6..afd1cb3a6630 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.hpp @@ -154,6 +154,7 @@ template class SumcheckProver { // Define the length of Libra Univariates. For non-ZK Flavors: set to 0. static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::HasZK ? Flavor::BATCHED_RELATION_PARTIAL_LENGTH : 0; using LibraUnivariates = std::vector>; + using ZKData = ZKSumcheckData; std::shared_ptr transcript; SumcheckProverRound round; @@ -177,8 +178,8 @@ template class SumcheckProver { , partially_evaluated_polynomials(multivariate_n){}; /** - * @brief Compute round univariate, place it in transcript, compute challenge, partially evaluate. Repeat - * until final round, then get full evaluations of prover polynomials, and place them in transcript. + * @brief Non-ZK version: Compute round univariate, place it in transcript, compute challenge, partially evaluate. + * Repeat until final round, then get full evaluations of prover polynomials, and place them in transcript. * @details See Detailed description of \ref bb::SumcheckProver< Flavor > "Sumcheck Prover . * @param full_polynomials Container for ProverPolynomials * @param relation_parameters @@ -186,11 +187,89 @@ template class SumcheckProver { * @param gate_challenges * @return SumcheckOutput */ + SumcheckOutput prove(ProverPolynomials& full_polynomials, + const bb::RelationParameters& relation_parameters, + const RelationSeparator alpha, + const std::vector& gate_challenges) + { + + bb::GateSeparatorPolynomial gate_separators(gate_challenges, multivariate_d); + + std::vector multivariate_challenge; + multivariate_challenge.reserve(multivariate_d); + // In the first round, we compute the first univariate polynomial and populate the book-keeping table of + // #partially_evaluated_polynomials, which has \f$ n/2 \f$ rows and \f$ N \f$ columns. When the Flavor has ZK, + // compute_univariate also takes into account the zk_sumcheck_data. + auto round_univariate = round.compute_univariate(full_polynomials, relation_parameters, gate_separators, alpha); + vinfo("starting sumcheck rounds..."); + { + + PROFILE_THIS_NAME("rest of sumcheck round 1"); + + // Place the evaluations of the round univariate into transcript. + transcript->send_to_verifier("Sumcheck:univariate_0", round_univariate); + FF round_challenge = transcript->template get_challenge("Sumcheck:u_0"); + multivariate_challenge.emplace_back(round_challenge); + // Prepare sumcheck book-keeping table for the next round + partially_evaluate(full_polynomials, multivariate_n, round_challenge); + gate_separators.partially_evaluate(round_challenge); + round.round_size = round.round_size >> 1; // TODO(#224)(Cody): Maybe partially_evaluate should do this and + // release memory? // All but final round + // We operate on partially_evaluated_polynomials in place. + } + for (size_t round_idx = 1; round_idx < multivariate_d; round_idx++) { + + PROFILE_THIS_NAME("sumcheck loop"); + + // Write the round univariate to the transcript + round_univariate = + round.compute_univariate(partially_evaluated_polynomials, relation_parameters, gate_separators, alpha); + // Place evaluations of Sumcheck Round Univariate in the transcript + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(round_idx), round_univariate); + FF round_challenge = transcript->template get_challenge("Sumcheck:u_" + std::to_string(round_idx)); + multivariate_challenge.emplace_back(round_challenge); + // Prepare sumcheck book-keeping table for the next round + partially_evaluate(partially_evaluated_polynomials, round.round_size, round_challenge); + gate_separators.partially_evaluate(round_challenge); + round.round_size = round.round_size >> 1; + } + vinfo("completed ", multivariate_d, " rounds of sumcheck"); + + // Zero univariates are used to pad the proof to the fixed size CONST_PROOF_SIZE_LOG_N. + auto zero_univariate = bb::Univariate::zero(); + for (size_t idx = multivariate_d; idx < CONST_PROOF_SIZE_LOG_N; idx++) { + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(idx), zero_univariate); + FF round_challenge = transcript->template get_challenge("Sumcheck:u_" + std::to_string(idx)); + multivariate_challenge.emplace_back(round_challenge); + } + // Claimed evaluations of Prover polynomials are extracted and added to the transcript. When Flavor has ZK, the + // evaluations of all witnesses are masked. + ClaimedEvaluations multivariate_evaluations; + multivariate_evaluations = extract_claimed_evaluations(partially_evaluated_polynomials); + transcript->send_to_verifier("Sumcheck:evaluations", multivariate_evaluations.get_all()); + // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output + + return SumcheckOutput{ multivariate_challenge, multivariate_evaluations }; + vinfo("finished sumcheck"); + }; + + /** + * @brief ZK-version of `prove` that runs Sumcheck with disabled rows and masking of Round Univariates. + * The masking is ensured by adding random Libra univariates to the Sumcheck round univariates. + * + * @param full_polynomials + * @param relation_parameters + * @param alpha + * @param gate_challenges + * @param zk_sumcheck_data + * @return SumcheckOutput + */ SumcheckOutput prove(ProverPolynomials& full_polynomials, const bb::RelationParameters& relation_parameters, const RelationSeparator alpha, const std::vector& gate_challenges, - ZKSumcheckData zk_sumcheck_data = ZKSumcheckData()) + ZKData& zk_sumcheck_data) + requires FlavorHasZK { bb::GateSeparatorPolynomial gate_separators(gate_challenges, multivariate_d); @@ -221,10 +300,8 @@ template class SumcheckProver { // Prepare sumcheck book-keeping table for the next round partially_evaluate(full_polynomials, multivariate_n, round_challenge); // Prepare ZK Sumcheck data for the next round - if constexpr (Flavor::HasZK) { - update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); - row_disabling_polynomial.update_evaluations(round_challenge, round_idx); - }; + update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + row_disabling_polynomial.update_evaluations(round_challenge, round_idx); gate_separators.partially_evaluate(round_challenge); round.round_size = round.round_size >> 1; // TODO(#224)(Cody): Maybe partially_evaluate should do this and // release memory? // All but final round @@ -249,10 +326,8 @@ template class SumcheckProver { // Prepare sumcheck book-keeping table for the next round partially_evaluate(partially_evaluated_polynomials, round.round_size, round_challenge); // Prepare evaluation masking and libra structures for the next round (for ZK Flavors) - if constexpr (Flavor::HasZK) { - update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); - row_disabling_polynomial.update_evaluations(round_challenge, round_idx); - }; + update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + row_disabling_polynomial.update_evaluations(round_challenge, round_idx); gate_separators.partially_evaluate(round_challenge); round.round_size = round.round_size >> 1; @@ -268,27 +343,23 @@ template class SumcheckProver { } // The evaluations of Libra uninvariates at \f$ g_0(u_0), \ldots, g_{d-1} (u_{d-1}) \f$ are added to the // transcript. - if constexpr (Flavor::HasZK) { - for (size_t idx = 0; idx < multivariate_d; idx++) { - const FF& libra_evaluation = zk_sumcheck_data.libra_evaluations[idx]; - std::string libra_evaluation_label = "Libra:evaluation_" + std::to_string(idx); - transcript->send_to_verifier(libra_evaluation_label, libra_evaluation); - } - }; + FF libra_evaluation{ 0 }; + + for (auto& libra_eval : zk_sumcheck_data.libra_evaluations) { + libra_evaluation += libra_eval; + } + libra_evaluation += zk_sumcheck_data.constant_term; + std::string libra_evaluation_label = "Libra:claimed_evaluation"; + transcript->send_to_verifier(libra_evaluation_label, libra_evaluation); // Claimed evaluations of Prover polynomials are extracted and added to the transcript. When Flavor has ZK, the // evaluations of all witnesses are masked. ClaimedEvaluations multivariate_evaluations; multivariate_evaluations = extract_claimed_evaluations(partially_evaluated_polynomials); transcript->send_to_verifier("Sumcheck:evaluations", multivariate_evaluations.get_all()); - // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output - if constexpr (!Flavor::HasZK) { - return SumcheckOutput{ multivariate_challenge, multivariate_evaluations }; - } else { - return SumcheckOutput{ multivariate_challenge, - multivariate_evaluations, - zk_sumcheck_data.libra_evaluations }; - } + // The sum of the Libra constant term and the evaluations of Libra univariates at corresponding sumcheck + // challenges is included in the Sumcheck Output + return SumcheckOutput{ multivariate_challenge, multivariate_evaluations, libra_evaluation }; vinfo("finished sumcheck"); }; @@ -400,23 +471,24 @@ polynomials that are sent in clear. * @param libra_running_sum * @param libra_evaluations */ - void update_zk_sumcheck_data(ZKSumcheckData& zk_sumcheck_data, const FF round_challenge, size_t round_idx) + void update_zk_sumcheck_data(ZKData& zk_sumcheck_data, const FF round_challenge, size_t round_idx) { + static constexpr FF two_inv = FF(1) / FF(2); // when round_idx = d - 1, the update is not needed if (round_idx < zk_sumcheck_data.libra_univariates.size() - 1) { for (auto& univariate : zk_sumcheck_data.libra_univariates) { - univariate *= FF(1) / FF(2); + univariate *= two_inv; }; // compute the evaluation \f$ \rho \cdot 2^{d-2-i} \çdot g_i(u_i) \f$ auto libra_evaluation = zk_sumcheck_data.libra_univariates[round_idx].evaluate(round_challenge); auto next_libra_univariate = zk_sumcheck_data.libra_univariates[round_idx + 1]; // update the running sum by adding g_i(u_i) and subtracting (g_i(0) + g_i(1)) zk_sumcheck_data.libra_running_sum += - -next_libra_univariate.value_at(0) - next_libra_univariate.value_at(1); - zk_sumcheck_data.libra_running_sum *= FF(1) / FF(2); + -next_libra_univariate.evaluate(FF(0)) - next_libra_univariate.evaluate(FF(1)); + zk_sumcheck_data.libra_running_sum *= two_inv; zk_sumcheck_data.libra_running_sum += libra_evaluation; - zk_sumcheck_data.libra_scaling_factor *= FF(1) / FF(2); + zk_sumcheck_data.libra_scaling_factor *= two_inv; zk_sumcheck_data.libra_evaluations.emplace_back(libra_evaluation / zk_sumcheck_data.libra_scaling_factor); } else { @@ -583,14 +655,11 @@ template class SumcheckVerifier { } } // Extract claimed evaluations of Libra univariates and compute their sum multiplied by the Libra challenge - ClaimedLibraEvaluations libra_evaluations(multivariate_d); + FF libra_evaluation{ 0 }; FF full_libra_purported_value = FF(0); if constexpr (Flavor::HasZK) { - for (size_t idx = 0; idx < multivariate_d; idx++) { - libra_evaluations[idx] = - transcript->template receive_from_prover("Libra:evaluation_" + std::to_string(idx)); - full_libra_purported_value += libra_evaluations[idx]; - }; + libra_evaluation = transcript->template receive_from_prover("Libra:claimed_evaluation"); + full_libra_purported_value += libra_evaluation; full_libra_purported_value *= libra_challenge; }; // Final round @@ -627,7 +696,7 @@ template class SumcheckVerifier { if constexpr (!Flavor::HasZK) { return SumcheckOutput{ multivariate_challenge, purported_evaluations, verified }; } else { - return SumcheckOutput{ multivariate_challenge, purported_evaluations, libra_evaluations, verified }; + return SumcheckOutput{ multivariate_challenge, purported_evaluations, libra_evaluation, verified }; } }; }; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp index 2ceab9efb918..1f384dae67d2 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp @@ -21,6 +21,8 @@ template class SumcheckTests : public ::testing::Test { using FF = typename Flavor::FF; using ProverPolynomials = typename Flavor::ProverPolynomials; using RelationSeparator = Flavor::RelationSeparator; + using ZKData = ZKSumcheckData; + const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES; static void SetUpTestSuite() { bb::srs::init_crs_factory(bb::srs::get_ignition_crs_path()); } @@ -118,6 +120,7 @@ template class SumcheckTests : public ::testing::Test { void test_prover() { + const size_t multivariate_d(2); const size_t multivariate_n(1 << multivariate_d); @@ -146,7 +149,7 @@ template class SumcheckTests : public ::testing::Test { SumcheckOutput output; if constexpr (Flavor::HasZK) { - ZKSumcheckData zk_sumcheck_data(multivariate_d, transcript); + ZKData zk_sumcheck_data = ZKData(multivariate_d, transcript); output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges, zk_sumcheck_data); } else { output = sumcheck.prove(full_polynomials, {}, alpha, gate_challenges); @@ -252,7 +255,7 @@ template class SumcheckTests : public ::testing::Test { } SumcheckOutput output; if constexpr (Flavor::HasZK) { - ZKSumcheckData zk_sumcheck_data(multivariate_d, prover_transcript); + ZKData zk_sumcheck_data = ZKData(multivariate_d, prover_transcript); output = sumcheck_prover.prove( full_polynomials, relation_parameters, prover_alpha, prover_gate_challenges, zk_sumcheck_data); } else { @@ -342,7 +345,7 @@ template class SumcheckTests : public ::testing::Test { SumcheckOutput output; if constexpr (Flavor::HasZK) { // construct libra masking polynomials and compute auxiliary data - ZKSumcheckData zk_sumcheck_data(multivariate_d, prover_transcript); + ZKData zk_sumcheck_data = ZKData(multivariate_d, prover_transcript); output = sumcheck_prover.prove( full_polynomials, relation_parameters, prover_alpha, prover_gate_challenges, zk_sumcheck_data); } else { @@ -375,15 +378,13 @@ using FlavorTypes = testing::Types TYPED_TEST_SUITE(SumcheckTests, FlavorTypes); -#define SKIP_IF_ZK() \ - if (std::is_same::value || std::is_same::value) { \ - GTEST_SKIP() << "Skipping test for ZK-enabled flavors"; \ - } - TYPED_TEST(SumcheckTests, PolynomialNormalization) { - SKIP_IF_ZK(); - this->test_polynomial_normalization(); + if constexpr (std::is_same_v) { + this->test_polynomial_normalization(); + } else { + GTEST_SKIP() << "Skipping test for ZK-enabled flavors"; + } } // Test the prover TYPED_TEST(SumcheckTests, Prover) @@ -400,4 +401,4 @@ TYPED_TEST(SumcheckTests, ProverAndVerifierSimpleFailure) { this->test_failure_prover_verifier_flow(); } -} // namespace +} // namespace \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp index a18353446a24..ea0c014a6153 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp @@ -35,8 +35,8 @@ template struct SumcheckOutput challenge; // Evaluations at \f$ \vec u \f$ of the polynomials used in Sumcheck ClaimedEvaluations claimed_evaluations; - // Include ClaimedLibraEvaluations conditioned on FlavorHasZK concept - std::vector claimed_libra_evaluations; + // For ZK Flavors: the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges + FF claimed_libra_evaluation; // Whether or not the evaluations of multilinear polynomials \f$ P_1, \ldots, P_N \f$ and final Sumcheck evaluation // have been confirmed std::optional verified = false; // Optional b/c this struct is shared by the Prover/Verifier diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp index dc42333a19a3..092ba3ca9415 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp @@ -43,6 +43,7 @@ template class SumcheckProverRound { using ExtendedEdges = std::conditional_t, typename Flavor::ExtendedEdges>; + using ZKData = ZKSumcheckData; /** * @brief In Round \f$i = 0,\ldots, d-1\f$, equals \f$2^{d-i}\f$. */ @@ -65,6 +66,9 @@ template class SumcheckProverRound { static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; using SumcheckRoundUnivariate = bb::Univariate; SumcheckTupleOfTuplesOfUnivariates univariate_accumulators; + + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = + (std::is_same_v) ? BATCHED_RELATION_PARTIAL_LENGTH : 3; // Prover constructor SumcheckProverRound(size_t initial_round_size) : round_size(initial_round_size) @@ -120,8 +124,8 @@ template class SumcheckProverRound { } /** - * @brief Return the evaluations of the univariate round polynomials \f$ \tilde{S}_{i} (X_{i}) \f$ at \f$ X_{i } = - 0,\ldots, D \f$. Most likely, \f$ D \f$ is around \f$ 12 \f$. At the + * @brief Non-ZK version: Return the evaluations of the univariate round polynomials \f$ \tilde{S}_{i} (X_{i}) \f$ + at \f$ X_{i } = 0,\ldots, D \f$. Most likely, \f$ D \f$ is around \f$ 12 \f$. At the * end, reset all * univariate accumulators to be zero. * @details First, the vector of \ref pow_challenges "pow challenges" is computed. @@ -142,14 +146,68 @@ template class SumcheckProverRound { method \ref extend_and_batch_univariates "extend and batch univariates". */ template - SumcheckRoundUnivariate compute_univariate( - const size_t round_idx, - ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials, - const bb::RelationParameters& relation_parameters, - const bb::GateSeparatorPolynomial& gate_sparators, - const RelationSeparator alpha, - ZKSumcheckData zk_sumcheck_data, // only populated when Flavor HasZK - RowDisablingPolynomial row_disabling_poly) + SumcheckRoundUnivariate compute_univariate(ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials, + const bb::RelationParameters& relation_parameters, + const bb::GateSeparatorPolynomial& gate_sparators, + const RelationSeparator alpha) + { + PROFILE_THIS_NAME("compute_univariate"); + + // Determine number of threads for multithreading. + // Note: Multithreading is "on" for every round but we reduce the number of threads from the max available based + // on a specified minimum number of iterations per thread. This eventually leads to the use of a single thread. + // For now we use a power of 2 number of threads simply to ensure the round size is evenly divided. + size_t min_iterations_per_thread = 1 << 6; // min number of iterations for which we'll spin up a unique thread + size_t num_threads = bb::calculate_num_threads_pow2(round_size, min_iterations_per_thread); + size_t iterations_per_thread = round_size / num_threads; // actual iterations per thread + + // Construct univariate accumulator containers; one per thread + std::vector thread_univariate_accumulators(num_threads); + + // Accumulate the contribution from each sub-relation accross each edge of the hyper-cube + parallel_for(num_threads, [&](size_t thread_idx) { + // Initialize the thread accumulator to 0 + Utils::zero_univariates(thread_univariate_accumulators[thread_idx]); + // Construct extended univariates containers; one per thread + ExtendedEdges extended_edges; + size_t start = thread_idx * iterations_per_thread; + size_t end = (thread_idx + 1) * iterations_per_thread; + for (size_t edge_idx = start; edge_idx < end; edge_idx += 2) { + extend_edges(extended_edges, polynomials, edge_idx); + // Compute the \f$ \ell \f$-th edge's univariate contribution, + // scale it by the corresponding \f$ pow_{\beta} \f$ contribution and add it to the accumulators for \f$ + // \tilde{S}^i(X_i) \f$. If \f$ \ell \f$'s binary representation is given by \f$ (\ell_{i+1},\ldots, + // \ell_{d-1})\f$, the \f$ pow_{\beta}\f$-contribution is \f$\beta_{i+1}^{\ell_{i+1}} \cdot \ldots \cdot + // \beta_{d-1}^{\ell_{d-1}}\f$. + accumulate_relation_univariates(thread_univariate_accumulators[thread_idx], + extended_edges, + relation_parameters, + gate_sparators[(edge_idx >> 1) * gate_sparators.periodicity]); + } + }); + + // Accumulate the per-thread univariate accumulators into a single set of accumulators + for (auto& accumulators : thread_univariate_accumulators) { + Utils::add_nested_tuples(univariate_accumulators, accumulators); + } + + // Batch the univariate contributions from each sub-relation to obtain the round univariate + return batch_over_relations(univariate_accumulators, alpha, gate_sparators); + } + + /** + * @brief ZK-version of `compute_univariate` that runs Sumcheck with disabled rows and masking of Round Univariates. + * The masking is ensured by adding random Libra univariates to the Sumcheck round univariates. + * + */ + template + SumcheckRoundUnivariate compute_univariate(const size_t round_idx, + ProverPolynomialsOrPartiallyEvaluatedMultivariates& polynomials, + const bb::RelationParameters& relation_parameters, + const bb::GateSeparatorPolynomial& gate_sparators, + const RelationSeparator alpha, + const ZKData& zk_sumcheck_data, // only populated when Flavor HasZK + RowDisablingPolynomial row_disabling_poly) { PROFILE_THIS_NAME("compute_univariate"); @@ -192,21 +250,15 @@ template class SumcheckProverRound { } // For ZK Flavors: The evaluations of the round univariates are masked by the evaluations of Libra univariates // and corrected by subtracting the contribution from the disabled rows - if constexpr (Flavor::HasZK) { - const auto contribution_from_disabled_rows = compute_disabled_contribution( - polynomials, relation_parameters, gate_sparators, alpha, round_idx, row_disabling_poly); - const auto libra_round_univariate = compute_libra_round_univariate(zk_sumcheck_data, round_idx); - // Batch the univariate contributions from each sub-relation to obtain the round univariate - const auto round_univariate = - batch_over_relations(univariate_accumulators, alpha, gate_sparators); - // Mask the round univariate - return round_univariate + libra_round_univariate - contribution_from_disabled_rows; - } + const auto contribution_from_disabled_rows = compute_disabled_contribution( + polynomials, relation_parameters, gate_sparators, alpha, round_idx, row_disabling_poly); + const auto libra_round_univariate = compute_libra_round_univariate(zk_sumcheck_data, round_idx); // Batch the univariate contributions from each sub-relation to obtain the round univariate - else { - return batch_over_relations(univariate_accumulators, alpha, gate_sparators); - } - } + const auto round_univariate = + batch_over_relations(univariate_accumulators, alpha, gate_sparators); + // Mask the round univariate + return round_univariate + libra_round_univariate - contribution_from_disabled_rows; + }; /*! * @brief For ZK Flavors: A method disabling the last 4 rows of the ProverPolynomials @@ -336,18 +388,22 @@ template class SumcheckProverRound { * @param zk_sumcheck_data * @param round_idx */ - static SumcheckRoundUnivariate compute_libra_round_univariate(ZKSumcheckData zk_sumcheck_data, - size_t round_idx) + static SumcheckRoundUnivariate compute_libra_round_univariate(const ZKData& zk_sumcheck_data, size_t round_idx) { - SumcheckRoundUnivariate libra_round_univariate; + bb::Univariate libra_round_univariate; // select the i'th column of Libra book-keeping table const auto& current_column = zk_sumcheck_data.libra_univariates[round_idx]; // the evaluation of Libra round univariate at k=0...D are equal to \f$\texttt{libra_univariates}_{i}(k)\f$ // corrected by the Libra running sum - for (size_t idx = 0; idx < BATCHED_RELATION_PARTIAL_LENGTH; ++idx) { - libra_round_univariate.value_at(idx) = current_column.value_at(idx) + zk_sumcheck_data.libra_running_sum; + for (size_t idx = 0; idx < LIBRA_UNIVARIATES_LENGTH; ++idx) { + libra_round_univariate.value_at(idx) = + current_column.evaluate(FF(idx)) + zk_sumcheck_data.libra_running_sum; }; - return libra_round_univariate; + if constexpr (BATCHED_RELATION_PARTIAL_LENGTH == LIBRA_UNIVARIATES_LENGTH) { + return libra_round_univariate; + } else { + return libra_round_univariate.template extend_to(); + } } private: diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp index efe7f8d84be8..d35f188d1a7b 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp @@ -1,9 +1,10 @@ #pragma once +#include "barretenberg/constants.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/polynomials/univariate.hpp" #include -#include #include namespace bb { @@ -13,31 +14,34 @@ namespace bb { * */ template struct ZKSumcheckData { - using FF = typename Flavor::FF; - /** - * @brief The total algebraic degree of the Sumcheck relation \f$ F \f$ as a polynomial in Prover Polynomials - * \f$P_1,\ldots, P_N\f$. - */ - static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::MAX_PARTIAL_RELATION_LENGTH; + using Curve = typename Flavor::Curve; + using FF = typename Curve::ScalarField; + + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; + + static constexpr FF subgroup_generator = Curve::subgroup_generator; - /** - * @brief The total algebraic degree of the Sumcheck relation \f$ F \f$ as a polynomial in Prover Polynomials - * \f$P_1,\ldots, P_N\f$ incremented by 1, i.e. it is equal \ref MAX_PARTIAL_RELATION_LENGTH - * "MAX_PARTIAL_RELATION_LENGTH + 1". - */ - static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; - // The size of the LibraUnivariates. We ensure that they do not take extra space when Flavor runs non-ZK Sumcheck. // The size of the LibraUnivariates. We ensure that they do not take extra space when Flavor runs non-ZK Sumcheck. - static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::HasZK ? Flavor::BATCHED_RELATION_PARTIAL_LENGTH : 0; - // Container for the Libra Univariates. Their number depends on the size of the circuit. - using LibraUnivariates = std::vector>; + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = (std::is_same_v) ? 9 : 3; + + static constexpr FF one_half = FF(1) / FF(2); + // Container for the evaluations of Libra Univariates that have to be proven. using ClaimedLibraEvaluations = std::vector; - LibraUnivariates libra_univariates; - LibraUnivariates libra_univariates_monomial; + FF constant_term; + + EvaluationDomain bn_evaluation_domain = EvaluationDomain(); + std::array interpolation_domain; + // to compute product in lagrange basis + Polynomial libra_concatenated_lagrange_form; + Polynomial libra_concatenated_monomial_form; + + std::vector> libra_univariates{}; + size_t log_circuit_size{ 0 }; FF libra_scaling_factor{ 1 }; FF libra_challenge; + FF libra_total_sum; FF libra_running_sum; ClaimedLibraEvaluations libra_evaluations; @@ -48,23 +52,24 @@ template struct ZKSumcheckData { ZKSumcheckData(const size_t multivariate_d, std::shared_ptr transcript, std::shared_ptr commitment_key = nullptr) - : libra_univariates(generate_libra_univariates(multivariate_d)) // Created in Lagrange basis for Sumcheck - , libra_univariates_monomial(transform_to_monomial(libra_univariates)) // Required for commiting and by Shplonk + : constant_term(FF::random_element()) + , libra_concatenated_monomial_form(SUBGROUP_SIZE + 2) // includes masking + , libra_univariates(generate_libra_univariates(multivariate_d)) // random univariates of degree 2 + , log_circuit_size(multivariate_d) { + create_interpolation_domain(); + + compute_concatenated_libra_polynomial(); - // If proving_key is provided, commit to libra_univariates + // If proving_key is provided, commit to the concatenated and masked libra polynomial if (commitment_key != nullptr) { - size_t idx = 0; - for (auto& libra_univariate_monomial : libra_univariates_monomial) { - auto libra_commitment = commitment_key->commit(Polynomial(libra_univariate_monomial)); - transcript->template send_to_verifier("Libra:commitment_" + std::to_string(idx), libra_commitment); - idx++; - } + auto libra_commitment = commitment_key->commit(libra_concatenated_monomial_form); + transcript->template send_to_verifier("Libra:concatenation_commitment", libra_commitment); } // Compute the total sum of the Libra polynomials libra_scaling_factor = FF(1); - FF libra_total_sum = compute_libra_total_sum(libra_univariates, libra_scaling_factor); + libra_total_sum = compute_libra_total_sum(libra_univariates, libra_scaling_factor, constant_term); // Send the Libra total sum to the transcript transcript->send_to_verifier("Libra:Sum", libra_total_sum); @@ -85,50 +90,16 @@ template struct ZKSumcheckData { * independent uniformly random coefficients. * */ - static LibraUnivariates generate_libra_univariates(const size_t number_of_polynomials) + static std::vector> generate_libra_univariates(const size_t number_of_polynomials) { - LibraUnivariates libra_full_polynomials(number_of_polynomials); + std::vector> libra_full_polynomials(number_of_polynomials); for (auto& libra_polynomial : libra_full_polynomials) { - libra_polynomial = bb::Univariate::get_random(); + libra_polynomial = Polynomial::random(LIBRA_UNIVARIATES_LENGTH); }; return libra_full_polynomials; }; - /** - * @brief Transform Libra univariates from Lagrange to monomial form - * - * @param libra_full_polynomials - * @return LibraUnivariates - */ - static LibraUnivariates transform_to_monomial(LibraUnivariates& libra_full_polynomials) - { - std::array interpolation_domain; - LibraUnivariates libra_univariates_monomial; - libra_univariates_monomial.reserve(libra_full_polynomials.size()); - - for (size_t idx = 0; idx < LIBRA_UNIVARIATES_LENGTH; idx++) { - interpolation_domain[idx] = FF(idx); - } - - for (auto& libra_polynomial : libra_full_polynomials) { - - // Use the efficient Lagrange interpolation - Polynomial libra_polynomial_monomial(std::span(interpolation_domain), - std::span(libra_polynomial.evaluations), - LIBRA_UNIVARIATES_LENGTH); - - // To avoid storing Polynomials (coefficients are vectors), we define a univariate with the coefficients - // interpolated above - bb::Univariate libra_univariate; - for (size_t idx = 0; idx < LIBRA_UNIVARIATES_LENGTH; idx++) { - libra_univariate.value_at(idx) = libra_polynomial_monomial[idx]; - } - libra_univariates_monomial.push_back(libra_univariate); - }; - return libra_univariates_monomial; - }; - /** * @brief Compute the sum of the randomly sampled multivariate polynomial \f$ G = \sum_{i=0}^{n-1} g_i(X_i) \f$ over * the Boolean hypercube. @@ -137,18 +108,20 @@ template struct ZKSumcheckData { * @param scaling_factor * @return FF */ - static FF compute_libra_total_sum(const LibraUnivariates& libra_univariates, FF& scaling_factor) + static FF compute_libra_total_sum(const std::vector>& libra_univariates, + FF& scaling_factor, + const FF& constant_term) { FF total_sum = 0; - scaling_factor = scaling_factor / 2; + scaling_factor *= one_half; for (auto& univariate : libra_univariates) { - total_sum += univariate.value_at(0) + univariate.value_at(1); + total_sum += univariate.evaluate(FF(0)) + univariate.evaluate(FF(1)); scaling_factor *= 2; } total_sum *= scaling_factor; - return total_sum; + return total_sum + constant_term * (1 << libra_univariates.size()); } /** @@ -173,8 +146,74 @@ template struct ZKSumcheckData { univariate *= libra_scaling_factor; }; // subtract the contribution of the first libra univariate from libra total sum - libra_running_sum += -libra_univariates[0].value_at(0) - libra_univariates[0].value_at(1); - libra_running_sum *= FF(1) / FF(2); + libra_running_sum += -libra_univariates[0].evaluate(FF(0)) - libra_univariates[0].evaluate(FF(1)); + libra_running_sum *= one_half; + } + + /** + * @brief Create a interpolation domain object and initialize the evaluation domain in the case of BN254 scalar + * field + * + */ + void create_interpolation_domain() + { + if constexpr (std::is_same_v) { + bn_evaluation_domain = EvaluationDomain(SUBGROUP_SIZE, SUBGROUP_SIZE); + if (bn_evaluation_domain.size > 0) { + bn_evaluation_domain.compute_lookup_table(); + } + } + + interpolation_domain[0] = FF{ 1 }; + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + interpolation_domain[idx] = interpolation_domain[idx - 1] * subgroup_generator; + } + } + + /** @brief Compute concatenated libra polynomial in lagrange basis, transform to monomial, add masking term Z_H(m_0 + * + m_1 + * + */ + void compute_concatenated_libra_polynomial() + { + std::array coeffs_lagrange_subgroup; + coeffs_lagrange_subgroup[0] = constant_term; + + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + coeffs_lagrange_subgroup[idx] = FF{ 0 }; + } + + for (size_t poly_idx = 0; poly_idx < log_circuit_size; poly_idx++) { + for (size_t idx = 0; idx < LIBRA_UNIVARIATES_LENGTH; idx++) { + size_t idx_to_populate = 1 + poly_idx * LIBRA_UNIVARIATES_LENGTH + idx; + coeffs_lagrange_subgroup[idx_to_populate] = libra_univariates[poly_idx].at(idx); + } + } + + libra_concatenated_lagrange_form = Polynomial(coeffs_lagrange_subgroup); + + bb::Univariate masking_scalars = bb::Univariate::get_random(); + + Polynomial libra_concatenated_monomial_form_unmasked(SUBGROUP_SIZE); + if constexpr (!std::is_same_v) { + libra_concatenated_monomial_form_unmasked = + Polynomial(interpolation_domain, coeffs_lagrange_subgroup, SUBGROUP_SIZE); + } else { + std::vector coeffs_lagrange_subgroup_ifft(SUBGROUP_SIZE); + polynomial_arithmetic::ifft( + coeffs_lagrange_subgroup.data(), coeffs_lagrange_subgroup_ifft.data(), bn_evaluation_domain); + libra_concatenated_monomial_form_unmasked = Polynomial(coeffs_lagrange_subgroup_ifft); + } + + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + libra_concatenated_monomial_form.at(idx) = libra_concatenated_monomial_form_unmasked.at(idx); + } + + for (size_t idx = 0; idx < masking_scalars.size(); idx++) { + libra_concatenated_monomial_form.at(idx) -= masking_scalars.value_at(idx); + libra_concatenated_monomial_form.at(SUBGROUP_SIZE + idx) += masking_scalars.value_at(idx); + } } }; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp index 3e0485b7ede7..1774a98f6f42 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp @@ -2,6 +2,7 @@ #include "barretenberg/commitment_schemes/claim.hpp" #include "barretenberg/commitment_schemes/commitment_key.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" #include "barretenberg/plonk_honk_shared/library/grand_product_library.hpp" #include "barretenberg/sumcheck/sumcheck.hpp" @@ -119,8 +120,7 @@ void TranslatorProver::execute_relation_check_rounds() } // // create masking polynomials for sumcheck round univariates and auxiliary data - zk_sumcheck_data = - ZKSumcheckData(key->proving_key->log_circuit_size, transcript, key->proving_key->commitment_key); + zk_sumcheck_data = ZKData(key->proving_key->log_circuit_size, transcript, key->proving_key->commitment_key); sumcheck_output = sumcheck.prove(key->proving_key->polynomials, relation_parameters, alpha, gate_challenges, zk_sumcheck_data); @@ -138,6 +138,14 @@ void TranslatorProver::execute_pcs_rounds() using OpeningClaim = ProverOpeningClaim; + using SmallSubgroupIPA = SmallSubgroupIPAProver; + + SmallSubgroupIPA small_subgroup_ipa_prover(zk_sumcheck_data, + sumcheck_output.challenge, + sumcheck_output.claimed_libra_evaluation, + transcript, + key->proving_key->commitment_key); + const OpeningClaim prover_opening_claim = ShpleminiProver_::prove(key->proving_key->circuit_size, key->proving_key->polynomials.get_unshifted_without_concatenated(), @@ -145,8 +153,7 @@ void TranslatorProver::execute_pcs_rounds() sumcheck_output.challenge, key->proving_key->commitment_key, transcript, - zk_sumcheck_data.libra_univariates_monomial, - sumcheck_output.claimed_libra_evaluations, + small_subgroup_ipa_prover.get_witness_polynomials(), key->proving_key->polynomials.get_concatenated(), key->proving_key->polynomials.get_groups_to_be_concatenated()); diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.hpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.hpp index 357c7aa6a908..2acd99027cfd 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.hpp @@ -23,6 +23,7 @@ class TranslatorProver { using CommitmentLabels = typename Flavor::CommitmentLabels; using PCS = typename Flavor::PCS; using Transcript = typename Flavor::Transcript; + using ZKData = ZKSumcheckData; static constexpr size_t MINIMUM_MINI_CIRCUIT_SIZE = 2048; size_t total_num_gates = 0; // num_gates (already include zero row offset) (used to compute dyadic size) size_t dyadic_circuit_size = 0; // final power-of-2 circuit size @@ -47,7 +48,7 @@ class TranslatorProver { CommitmentLabels commitment_labels; - ZKSumcheckData zk_sumcheck_data; + ZKData zk_sumcheck_data; SumcheckOutput sumcheck_output; diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp index 8acaa89f7369..4cff080cf23b 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp @@ -99,22 +99,22 @@ bool TranslatorVerifier::verify_proof(const HonkProof& proof) } // Receive commitments to Libra masking polynomials - std::vector libra_commitments; - for (size_t idx = 0; idx < log_circuit_size; idx++) { - Commitment libra_commitment = - transcript->receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - } + std::array libra_commitments = {}; + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); - auto [multivariate_challenge, claimed_evaluations, libra_evaluations, sumcheck_verified] = + auto [multivariate_challenge, claimed_evaluations, libra_evaluation, sumcheck_verified] = sumcheck.verify(relation_parameters, alpha, gate_challenges); // If Sumcheck did not verify, return false if (sumcheck_verified.has_value() && !sumcheck_verified.value()) { return false; } - // Execute Shplemini + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); + + // Execute Shplemini + bool consistency_checked = false; const BatchOpeningClaim opening_claim = Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted_without_concatenated(), @@ -125,15 +125,17 @@ bool TranslatorVerifier::verify_proof(const HonkProof& proof) Commitment::one(), transcript, Flavor::REPEATED_COMMITMENTS, - RefVector(libra_commitments), - libra_evaluations, + Flavor::HasZK, + &consistency_checked, + libra_commitments, + libra_evaluation, commitments.get_groups_to_be_concatenated(), claimed_evaluations.get_concatenated()); const auto pairing_points = PCS::reduce_verify_batch_opening_claim(opening_claim, transcript); auto verified = key->pcs_verification_key->pairing_check(pairing_points[0], pairing_points[1]); - return verified; + return verified && consistency_checked; } bool TranslatorVerifier::verify_translation(const TranslationEvaluations& translation_evaluations) diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.cpp index 2a27bd4b2f01..9317545152b1 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.cpp @@ -1,4 +1,5 @@ #include "decider_prover.hpp" +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" #include "barretenberg/common/op_count.hpp" #include "barretenberg/sumcheck/sumcheck.hpp" @@ -32,15 +33,18 @@ template void DeciderProver_::execute_relation_ch { PROFILE_THIS_NAME("sumcheck.prove"); + if constexpr (Flavor::HasZK) { - auto commitment_key = std::make_shared(Flavor::BATCHED_RELATION_PARTIAL_LENGTH); - zk_sumcheck_data = ZKSumcheckData(numeric::get_msb(polynomial_size), transcript, commitment_key); + const size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + auto commitment_key = std::make_shared(1 << (log_subgroup_size + 1)); + zk_sumcheck_data = ZKData(numeric::get_msb(polynomial_size), transcript, commitment_key); sumcheck_output = sumcheck.prove(proving_key->proving_key.polynomials, proving_key->relation_parameters, proving_key->alphas, proving_key->gate_challenges, zk_sumcheck_data); } else { + sumcheck_output = sumcheck.prove(proving_key->proving_key.polynomials, proving_key->relation_parameters, proving_key->alphas, @@ -71,14 +75,17 @@ template void DeciderProver_::execute_pcs_rounds( ck, transcript); } else { + + SmallSubgroupIPA small_subgroup_ipa_prover( + zk_sumcheck_data, sumcheck_output.challenge, sumcheck_output.claimed_libra_evaluation, transcript, ck); + prover_opening_claim = ShpleminiProver_::prove(proving_key->proving_key.circuit_size, proving_key->proving_key.polynomials.get_unshifted(), proving_key->proving_key.polynomials.get_to_be_shifted(), sumcheck_output.challenge, ck, transcript, - zk_sumcheck_data.libra_univariates_monomial, - sumcheck_output.claimed_libra_evaluations); + small_subgroup_ipa_prover.get_witness_polynomials()); } vinfo("executed multivariate-to-univariate reduction"); PCS::compute_opening_proof(ck, prover_opening_claim, transcript); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.hpp index 1d8a7a854248..47baefd297b5 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_prover.hpp @@ -25,6 +25,8 @@ template class DeciderProver_ { using DeciderPK = DeciderProvingKey_; using Transcript = typename Flavor::Transcript; using RelationSeparator = typename Flavor::RelationSeparator; + using ZKData = ZKSumcheckData; + using SmallSubgroupIPA = SmallSubgroupIPAProver; public: explicit DeciderProver_(const std::shared_ptr&, @@ -46,7 +48,7 @@ template class DeciderProver_ { Polynomial quotient_W; - ZKSumcheckData zk_sumcheck_data; + ZKData zk_sumcheck_data; SumcheckOutput sumcheck_output; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp index 22e93f763600..96d69a3ed305 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp @@ -46,22 +46,19 @@ template bool DeciderVerifier_::verify() auto sumcheck = SumcheckVerifier( static_cast(accumulator->verification_key->log_circuit_size), transcript, accumulator->target_sum); // For MegaZKFlavor: receive commitments to Libra masking polynomials - std::vector libra_commitments = {}; + std::array libra_commitments = {}; if constexpr (Flavor::HasZK) { - for (size_t idx = 0; idx < static_cast(accumulator->verification_key->log_circuit_size); idx++) { - Commitment libra_commitment = - transcript->template receive_from_prover("Libra:commitment_" + std::to_string(idx)); - libra_commitments.push_back(libra_commitment); - }; + libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); } - SumcheckOutput sumcheck_output = sumcheck.verify(accumulator->relation_parameters, accumulator->alphas, accumulator->gate_challenges); // For MegaZKFlavor: the sumcheck output contains claimed evaluations of the Libra polynomials - std::vector libra_evaluations = {}; + FF libra_evaluation{ 0 }; if constexpr (Flavor::HasZK) { - libra_evaluations = std::move(sumcheck_output.claimed_libra_evaluations); + libra_evaluation = std::move(sumcheck_output.claimed_libra_evaluation); + libra_commitments[1] = transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); } // If Sumcheck did not verify, return false @@ -69,7 +66,7 @@ template bool DeciderVerifier_::verify() info("Sumcheck verification failed."); return false; } - + bool consistency_checked = true; const BatchOpeningClaim opening_claim = Shplemini::compute_batch_opening_claim(accumulator->verification_key->circuit_size, commitments.get_unshifted(), @@ -80,11 +77,13 @@ template bool DeciderVerifier_::verify() Commitment::one(), transcript, Flavor::REPEATED_COMMITMENTS, - RefVector(libra_commitments), - libra_evaluations); + Flavor::HasZK, + &consistency_checked, + libra_commitments, + libra_evaluation); const auto pairing_points = PCS::reduce_verify_batch_opening_claim(opening_claim, transcript); bool verified = pcs_verification_key->pairing_check(pairing_points[0], pairing_points[1]); - return sumcheck_output.verified.value() && verified; + return sumcheck_output.verified.value() && verified && consistency_checked; } template class DeciderVerifier_; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp index 4808bb2c2a88..e47333d43687 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp @@ -30,11 +30,10 @@ template class MegaTranscriptTests : public ::testing::Test { * * @return TranscriptManifest */ - static TranscriptManifest construct_mega_honk_manifest(size_t circuit_size = 0) + static TranscriptManifest construct_mega_honk_manifest() { using Commitment = typename Flavor::Commitment; TranscriptManifest manifest_expected; - auto log_n = numeric::get_msb(circuit_size); size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; size_t NUM_SUBRELATIONS = Flavor::NUM_SUBRELATIONS; @@ -97,10 +96,7 @@ template class MegaTranscriptTests : public ::testing::Test { } if constexpr (Flavor::HasZK) { - for (size_t i = 0; i < log_n; i++) { - std::string label = "Libra:commitment_" + std::to_string(i); - manifest_expected.add_entry(round, label, frs_per_G); - } + manifest_expected.add_entry(round, "Libra:concatenation_commitment", frs_per_G); manifest_expected.add_entry(round, "Libra:Sum", frs_per_Fr); manifest_expected.add_challenge(round, "Libra:Challenge"); round++; @@ -115,15 +111,14 @@ template class MegaTranscriptTests : public ::testing::Test { } if constexpr (Flavor::HasZK) { - for (size_t i = 0; i < log_n; i++) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Libra:evaluation_" + idx, frs_per_Fr); - } + manifest_expected.add_entry(round, "Libra:claimed_evaluation", frs_per_Fr); } manifest_expected.add_entry(round, "Sumcheck:evaluations", frs_per_evals); if constexpr (Flavor::HasZK) { + manifest_expected.add_entry(round, "Libra:big_sum_commitment", frs_per_G); + manifest_expected.add_entry(round, "Libra:quotient_commitment", frs_per_G); manifest_expected.add_entry(round, "Gemini:masking_poly_comm", frs_per_G); manifest_expected.add_entry(round, "Gemini:masking_poly_eval", frs_per_Fr); } @@ -141,6 +136,12 @@ template class MegaTranscriptTests : public ::testing::Test { std::string idx = std::to_string(i); manifest_expected.add_entry(round, "Gemini:a_" + idx, frs_per_Fr); } + if constexpr (Flavor::HasZK) { + manifest_expected.add_entry(round, "Libra:concatenation_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:shifted_big_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:big_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:quotient_eval", frs_per_Fr); + } manifest_expected.add_challenge(round, "Shplonk:nu"); round++; @@ -198,7 +199,7 @@ TYPED_TEST(MegaTranscriptTests, ProverManifestConsistency) auto proof = prover.construct_proof(); // Check that the prover generated manifest agrees with the manifest hard coded in this suite - auto manifest_expected = TestFixture::construct_mega_honk_manifest(proving_key->proving_key.circuit_size); + auto manifest_expected = TestFixture::construct_mega_honk_manifest(); auto prover_manifest = prover.transcript->get_manifest(); // Note: a manifest can be printed using manifest.print() for (size_t round = 0; round < manifest_expected.size(); ++round) {