From fe34b0580a308665c655a897c72f06bd05dcd4c4 Mon Sep 17 00:00:00 2001 From: sergei iakovenko <105737703+iakovenkos@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:32:11 +0100 Subject: [PATCH] feat: eccvm sumcheck with commitments to round univariates (#11206) [Protocol outline](https://hackmd.io/sxlCHpVISdaaQJbCpcXA-Q) * combined with SmallSubgroup inner product argument, ensures that the sumcheck round univariates do not leak witness information (In ECCVM) * drastically reduces the eccvm proof size - instead of sending 24 coefficients of each round univariate, the prover sends evals at 0, 1, and a group element * reduces eccvm recursive verifier costs by avoiding expensive evaluations of polys of degree 23 (360K gates -> 230K gates) --- .../benchmark/goblin_bench/eccvm.bench.cpp | 4 +- .../commitment_schemes/kzg/kzg.test.cpp | 2 + .../commitment_schemes/shplonk/shplemini.hpp | 257 +++++++++-- .../shplonk/shplemini.test.cpp | 166 ++++++- .../commitment_schemes/shplonk/shplonk.hpp | 42 +- .../small_subgroup_ipa/small_subgroup_ipa.hpp | 18 +- .../utils/instance_witness_generator.hpp | 50 +++ .../barretenberg/ecc/curves/bn254/bn254.hpp | 3 + .../ecc/curves/grumpkin/grumpkin.hpp | 4 + .../eccvm/eccvm_composer.test.cpp | 87 +++- .../src/barretenberg/eccvm/eccvm_flavor.hpp | 15 +- .../src/barretenberg/eccvm/eccvm_prover.cpp | 4 +- .../eccvm/eccvm_transcript.test.cpp | 6 +- .../src/barretenberg/eccvm/eccvm_verifier.cpp | 19 +- .../cpp/src/barretenberg/flavor/flavor.hpp | 2 + .../polynomials/row_disabling_polynomial.hpp | 22 + .../eccvm_recursive_verifier.cpp | 14 +- .../stdlib/primitives/curves/bn254.hpp | 3 + .../stdlib/primitives/curves/grumpkin.hpp | 4 + .../translator_recursive_verifier.cpp | 15 +- .../src/barretenberg/sumcheck/sumcheck.hpp | 413 +++++++++++++----- .../barretenberg/sumcheck/sumcheck.test.cpp | 4 +- .../barretenberg/sumcheck/sumcheck_output.hpp | 16 +- .../barretenberg/sumcheck/sumcheck_round.hpp | 5 +- .../sumcheck/zk_sumcheck_data.hpp | 102 ++++- .../translator_vm/translator_prover.cpp | 2 + .../translator_vm/translator_verifier.cpp | 17 +- .../ultra_honk/decider_verifier.cpp | 4 +- .../barretenberg/ultra_honk/sumcheck.test.cpp | 2 +- .../vm/avm/generated/recursive_verifier.cpp | 2 +- .../vm/avm/generated/verifier.cpp | 2 +- .../barretenberg/vm2/generated/verifier.cpp | 2 +- .../templates/recursive_verifier.cpp.hbs | 2 +- .../bb-pil-backend/templates/verifier.cpp.hbs | 2 +- 34 files changed, 1096 insertions(+), 216 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp index 10bf6c077e6..9f96f20bea9 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp @@ -65,8 +65,8 @@ void eccvm_prove(State& state) noexcept }; } -BENCHMARK(eccvm_generate_prover)->Unit(kMillisecond)->DenseRange(12, 18); -BENCHMARK(eccvm_prove)->Unit(kMillisecond)->DenseRange(12, 18); +BENCHMARK(eccvm_generate_prover)->Unit(kMillisecond)->DenseRange(12, CONST_ECCVM_LOG_N); +BENCHMARK(eccvm_prove)->Unit(kMillisecond)->DenseRange(12, CONST_ECCVM_LOG_N); } // namespace BENCHMARK_MAIN(); 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 dcf3b4fe315..1612747e470 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp @@ -324,6 +324,8 @@ TYPED_TEST(KZGTest, ShpleminiKzgWithShiftAndConcatenation) &consistency_checked, /* libra commitments = */ {}, /* libra evaluations = */ {}, + {}, + {}, to_vector_of_ref_vectors(concatenation_groups_commitments), RefVector(c_evaluations)); const auto pairing_points = KZG::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript); diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp index 714be37b753..71f763302a9 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp @@ -23,7 +23,7 @@ template class ShpleminiProver_ { using ShplonkProver = ShplonkProver_; using GeminiProver = GeminiProver_; - template + template static OpeningClaim prove(const FF circuit_size, RefSpan f_polynomials, RefSpan g_polynomials, @@ -31,6 +31,8 @@ template class ShpleminiProver_ { const std::shared_ptr>& commitment_key, const std::shared_ptr& transcript, const std::array& libra_polynomials = {}, + const std::vector& sumcheck_round_univariates = {}, + const std::vector>& sumcheck_round_evaluations = {}, RefSpan concatenated_polynomials = {}, const std::vector>& groups_to_be_concatenated = {}) { @@ -45,31 +47,92 @@ template class ShpleminiProver_ { concatenated_polynomials, groups_to_be_concatenated, has_zk); - // Create opening claims for Libra masking univariates - std::vector libra_opening_claims; + // Create opening claims for Libra masking univariates and Sumcheck Round Univariates OpeningClaim new_claim; + std::vector libra_opening_claims; if (has_zk) { - static constexpr FF subgroup_generator = Curve::subgroup_generator; const auto gemini_r = opening_claims[0].opening_pair.challenge; + libra_opening_claims = compute_libra_opening_claims(gemini_r, libra_polynomials, transcript); + } - 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); - } + // Currently, only used in ECCVM. + std::vector sumcheck_round_claims; + + if (!sumcheck_round_univariates.empty()) { + sumcheck_round_claims = compute_sumcheck_round_claims( + circuit_size, multilinear_challenge, sumcheck_round_univariates, sumcheck_round_evaluations); } - const OpeningClaim batched_claim = - ShplonkProver::prove(commitment_key, opening_claims, transcript, libra_opening_claims); + const OpeningClaim batched_claim = ShplonkProver::prove( + commitment_key, opening_claims, transcript, libra_opening_claims, sumcheck_round_claims); return batched_claim; }; + + /** + * @brief For ZK Flavors: Evaluate the polynomials used in SmallSubgroupIPA argument, send the evaluations to the + * verifier, and populate a vector of the opening claims. + * + */ + template + static std::vector compute_libra_opening_claims( + const FF gemini_r, + const std::array& libra_polynomials, + const std::shared_ptr& transcript) + { + OpeningClaim new_claim; + + std::vector libra_opening_claims = {}; + + static constexpr FF subgroup_generator = Curve::subgroup_generator; + + 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); + } + + return libra_opening_claims; + } + + /** + * @brief Create a vector of 3*log_circuit_size opening claims for the evaluations of Sumcheck Round Univariates at + * 0, 1, and a round challenge. + * + */ + static std::vector compute_sumcheck_round_claims( + const FF circuit_size, + std::span multilinear_challenge, + const std::vector& sumcheck_round_univariates, + const std::vector>& sumcheck_round_evaluations) + { + OpeningClaim new_claim; + std::vector sumcheck_round_claims = {}; + + const size_t log_circuit_size = numeric::get_msb(static_cast(circuit_size)); + for (size_t idx = 0; idx < log_circuit_size; idx++) { + const std::vector evaluation_points = { FF(0), FF(1), multilinear_challenge[idx] }; + size_t eval_idx = 0; + new_claim.polynomial = std::move(sumcheck_round_univariates[idx]); + + for (auto& eval_point : evaluation_points) { + new_claim.opening_pair.challenge = eval_point; + new_claim.opening_pair.evaluation = sumcheck_round_evaluations[idx][eval_idx]; + sumcheck_round_claims.push_back(new_claim); + eval_idx++; + } + } + + return sumcheck_round_claims; + } }; /** * \brief An efficient verifier for the evaluation proofs of multilinear polynomials and their shifts. @@ -150,6 +213,8 @@ template class ShpleminiVerifier_ { // Shplemini Refactoring: Remove bool pointer const std::array& libra_commitments = {}, const Fr& libra_univariate_evaluation = Fr{ 0 }, + const std::vector& sumcheck_round_commitments = {}, + const std::vector>& sumcheck_round_evaluations = {}, const std::vector>& concatenation_group_commitments = {}, RefSpan concatenated_evaluations = {}) @@ -306,10 +371,6 @@ template class ShpleminiVerifier_ { // Add A₀(−r)/(z+r) to the constant term accumulator constant_term_accumulator += gemini_evaluations[0] * shplonk_batching_challenge * inverse_vanishing_evals[1]; - // Finalize the batch opening claim - commitments.emplace_back(g1_identity); - scalars.emplace_back(constant_term_accumulator); - remove_repeated_commitments(commitments, scalars, repeated_commitments, has_zk); // For ZK flavors, the sumcheck output contains the evaluations of Libra univariates that submitted to the @@ -317,6 +378,7 @@ template class ShpleminiVerifier_ { if (has_zk) { add_zk_data(commitments, scalars, + constant_term_accumulator, libra_commitments, libra_evaluations, gemini_evaluation_challenge, @@ -327,6 +389,23 @@ template class ShpleminiVerifier_ { libra_evaluations, gemini_evaluation_challenge, multivariate_challenge, libra_univariate_evaluation); } + // Currently, only used in ECCVM + if (!sumcheck_round_evaluations.empty()) { + batch_sumcheck_round_claims(log_circuit_size, + commitments, + scalars, + constant_term_accumulator, + multivariate_challenge, + shplonk_batching_challenge, + shplonk_evaluation_challenge, + sumcheck_round_commitments, + sumcheck_round_evaluations); + } + + // Finalize the batch opening claim + commitments.emplace_back(g1_identity); + scalars.emplace_back(constant_term_accumulator); + return { commitments, scalars, shplonk_evaluation_challenge }; }; /** @@ -619,6 +698,7 @@ template class ShpleminiVerifier_ { */ static void add_zk_data(std::vector& commitments, std::vector& scalars, + Fr& constant_term_accumulator, const std::array& libra_commitments, const std::array& libra_evaluations, const Fr& gemini_evaluation_challenge, @@ -632,9 +712,6 @@ template class ShpleminiVerifier_ { shplonk_challenge_power *= shplonk_batching_challenge; } - // need to keep track of the contribution to the constant term - Fr& constant_term = scalars.back(); - // add Libra commitments to the vector of commitments for (size_t idx = 0; idx < libra_commitments.size(); idx++) { commitments.push_back(libra_commitments[idx]); @@ -652,11 +729,11 @@ template class ShpleminiVerifier_ { // 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++) { + for (size_t idx = 0; idx < NUM_LIBRA_EVALUATIONS; idx++) { Fr scaling_factor = denominators[idx] * shplonk_challenge_power; batching_scalars[idx] = -scaling_factor; shplonk_challenge_power *= shplonk_batching_challenge; - constant_term += scaling_factor * libra_evaluations[idx]; + constant_term_accumulator += scaling_factor * libra_evaluations[idx]; } // to save a scalar mul, add the sum of the batching scalars corresponding to the big sum evaluations @@ -664,5 +741,133 @@ template class ShpleminiVerifier_ { scalars.push_back(batching_scalars[1] + batching_scalars[2]); scalars.push_back(batching_scalars[3]); } + + /** + * @brief Adds the Sumcheck data into the Shplemini BatchOpeningClaim. + * + * @details This method computes denominators for the evaluations of Sumcheck Round Unviariates, combines them with + * powers of the Shplonk batching challenge (\f$\nu\f$), and appends the resulting batched scalar factors to + * \p scalars. It also updates \p commitments with Sumcheck's round commitments. The \p constant_term_accumulator is + * incremented by each round's constant term contribution. + * + * Specifically, for round \f$i\f$ (with Sumcheck challenge \f$u_i\f$), we define: + * \f[ + * \alpha_i^0 = \frac{\nu^{k+3i}}{z}, \quad + * \alpha_i^1 = \frac{\nu^{k+3i+1}}{z - 1}, \quad + * \alpha_i^2 = \frac{\nu^{k+3i+2}}{z - u_i}, + * \f] + * where \f$ z\f$ is the Shplonk evaluation challenge, \f$\nu\f$ is the batching challenge, and \f$k\f$ is an + * offset exponent equal to CONST_PROOF_SIZE_LOG_N + 2 + NUM_LIBRA_EVALATIONS. Then: + * + * - The **batched scalar** appended to \p scalars is + * \f[ + * \text{batched_scaling_factor}_i \;=\; + * -\bigl(\alpha_i^0 + \alpha_i^1 + \alpha_i^2\bigr). + * \f] + * - The **constant term** contribution for round \f$i\f$ is + * \f[ + * \text{const_term_contribution}_i \;=\; + * \alpha_i^0 \cdot S_i(0) + * + \alpha_i^1 \cdot S_i(1) + * + \alpha_i^2 \cdot S_i\bigl(u_i\bigr), + * \f] + * where \f$S_i(x)\f$ denotes the Sumcheck round-\f$i\f$ univariate polynomial. This contribution is added to + * \p constant_term_accumulator. + * + * @param log_circuit_size + * @param commitments + * @param scalars + * @param constant_term_accumulator + * @param multilinear_challenge + * @param shplonk_batching_challenge + * @param shplonk_evaluation_challenge + * @param sumcheck_round_commitments + * @param sumcheck_round_evaluations + */ + static void batch_sumcheck_round_claims(const size_t log_circuit_size, + std::vector& commitments, + std::vector& scalars, + Fr& constant_term_accumulator, + const std::vector& multilinear_challenge, + const Fr& shplonk_batching_challenge, + const Fr& shplonk_evaluation_challenge, + const std::vector& sumcheck_round_commitments, + const std::vector>& sumcheck_round_evaluations) + { + + std::vector denominators = {}; + + // Compute the next power of Shplonk batching challenge \nu + Fr shplonk_challenge_power = Fr{ 1 }; + for (size_t j = 0; j < CONST_PROOF_SIZE_LOG_N + 2 + NUM_LIBRA_EVALUATIONS; ++j) { + shplonk_challenge_power *= shplonk_batching_challenge; + } + + // Denominators for the opening claims at 0 and 1. Need to be computed only once as opposed to the claims at the + // sumcheck round challenges. + std::array const_denominators; + + const_denominators[0] = Fr(1) / (shplonk_evaluation_challenge); + const_denominators[1] = Fr(1) / (shplonk_evaluation_challenge - Fr{ 1 }); + + // Compute the denominators corresponding to the evaluation claims at the round challenges and add the + // commitments to the sumcheck round univariates to the vector of commitments + for (const auto& [challenge, comm] : zip_view(multilinear_challenge, sumcheck_round_commitments)) { + denominators.push_back(shplonk_evaluation_challenge - challenge); + commitments.push_back(comm); + } + + // Invert denominators + if constexpr (!Curve::is_stdlib_type) { + Fr::batch_invert(denominators); + } else { + for (auto& denominator : denominators) { + denominator = Fr{ 1 } / denominator; + } + } + + // Each commitment to a sumcheck round univariate [S_i] is multiplied by the sum of three scalars corresponding + // to the evaluations at 0, 1, and the round challenge u_i + size_t round_idx = 0; + for (const auto& [eval_array, denominator] : zip_view(sumcheck_round_evaluations, denominators)) { + // Initialize batched_scalar corresponding to 3 evaluations claims + Fr batched_scalar = Fr(0); + Fr const_term_contribution = Fr(0); + + // Compute the contribution from the evaluations at 0 and 1 + for (size_t idx = 0; idx < 2; idx++) { + Fr current_scaling_factor = const_denominators[idx] * shplonk_challenge_power; + batched_scalar -= current_scaling_factor; + shplonk_challenge_power *= shplonk_batching_challenge; + const_term_contribution += current_scaling_factor * eval_array[idx]; + } + + // Compute the contribution from the evaluation at the challenge u_i + Fr current_scaling_factor = denominator * shplonk_challenge_power; + batched_scalar -= current_scaling_factor; + shplonk_challenge_power *= shplonk_batching_challenge; + const_term_contribution += current_scaling_factor * eval_array[2]; + + // Pad the accumulators with dummy 0 values + const Fr zero = Fr(0); + if constexpr (Curve::is_stdlib_type) { + auto builder = shplonk_batching_challenge.get_context(); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1114): insecure! + stdlib::bool_t dummy_round = stdlib::witness_t(builder, round_idx >= log_circuit_size); + const_term_contribution = Fr::conditional_assign(dummy_round, zero, const_term_contribution); + batched_scalar = Fr::conditional_assign(dummy_round, zero, batched_scalar); + } else { + if (round_idx >= log_circuit_size) { + const_term_contribution = 0; + batched_scalar = 0; + } + } + + // Update Shplonk constant term accumualator + constant_term_accumulator += const_term_contribution; + scalars.push_back(batched_scalar); + round_idx++; + } + }; }; } // 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 78de7b2ed2a..d7ba379ae3b 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp @@ -10,6 +10,7 @@ #include "barretenberg/commitment_schemes/utils/instance_witness_generator.hpp" #include "barretenberg/commitment_schemes/utils/test_settings.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/sumcheck/sumcheck.hpp" #include #include @@ -18,10 +19,53 @@ namespace bb { template class ShpleminiTest : public CommitmentTest { public: + // Size of the test polynomials static constexpr size_t n = 32; static constexpr size_t log_n = 5; + // Total number of random polynomials in each test static constexpr size_t num_polynomials = 5; + // Number of shiftable polynomials static constexpr size_t num_shiftable = 2; + + // The length of the mock sumcheck univariates. + static constexpr size_t sumcheck_univariate_length = 24; + + using Fr = typename Flavor::Curve::ScalarField; + using GroupElement = typename Flavor::Curve::Element; + using Commitment = typename Flavor::Curve::AffineElement; + using CK = typename Flavor::CommitmentKey; + + void compute_sumcheck_opening_data(std::vector>& round_univariates, + std::vector& sumcheck_commitments, + std::vector>& sumcheck_evaluations, + std::vector& challenge, + std::shared_ptr& ck) + { + // Generate valid sumcheck polynomials of given length + auto mock_sumcheck_polynomials = ZKSumcheckData(log_n, sumcheck_univariate_length); + for (size_t idx = 0; idx < log_n; idx++) { + bb::Polynomial round_univariate = mock_sumcheck_polynomials.libra_univariates[idx]; + + round_univariate.at(0) += mock_sumcheck_polynomials.libra_running_sum; + + sumcheck_commitments.push_back(ck->commit(round_univariate)); + + sumcheck_evaluations.push_back({ round_univariate.at(0), + round_univariate.evaluate(Fr(1)), + round_univariate.evaluate(challenge[idx]) }); + + mock_sumcheck_polynomials.update_zk_sumcheck_data(challenge[idx], idx); + round_univariates.push_back(round_univariate); + } + + // Simulate the `const proof size` logic + auto round_univariate = bb::Polynomial(this->n); + for (size_t idx = this->log_n; idx < CONST_PROOF_SIZE_LOG_N; idx++) { + round_univariates.push_back(round_univariate); + sumcheck_commitments.push_back(ck->commit(round_univariate)); + sumcheck_evaluations.push_back({ Fr(0), Fr(0), Fr(0) }); + } + } }; using TestSettings = ::testing::Types; @@ -234,10 +278,10 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) /** * @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver and Libra polynomials - * used to mask Sumcheck Round Univariates. + * used to mask Sumcheck Round Univariates. This abstracts the PCS step in each ZK Flavor running over BN254. * */ -TYPED_TEST(ShpleminiTest, ShpleminiWithZK) +TYPED_TEST(ShpleminiTest, ShpleminiZKNoSumcheckOpenings) { using ZKData = ZKSumcheckData; using Curve = TypeParam::Curve; @@ -264,8 +308,8 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK) const_size_mle_opening_point.begin() + this->log_n); // Generate random prover polynomials, compute their evaluations and commitments - auto pcs_instance_witness = - InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); + InstanceWitnessGenerator pcs_instance_witness( + this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges const Fr claimed_inner_product = SmallSubgroupIPAProver::compute_claimed_inner_product( @@ -274,7 +318,7 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK) prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product); // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials - auto small_subgroup_ipa_prover = SmallSubgroupIPAProver( + SmallSubgroupIPAProver small_subgroup_ipa_prover( zk_sumcheck_data, const_size_mle_opening_point, claimed_inner_product, prover_transcript, ck); // Reduce to KZG or IPA based on the curve used in the test Flavor @@ -346,4 +390,116 @@ TYPED_TEST(ShpleminiTest, ShpleminiWithZK) } } +/** + * @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver, Libra polynomials + * used to mask Sumcheck Round Univariates and prove/verify the claimed evaluations of committed sumcheck round + * univariates. This test abstracts the PCS step in each ZK Flavor running over Grumpkin. + * + */ +TYPED_TEST(ShpleminiTest, ShpleminiZKWithSumcheckOpenings) +{ + using Curve = TypeParam::Curve; + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using CK = typename TypeParam::CommitmentKey; + + using ShpleminiProver = ShpleminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + + std::shared_ptr ck = create_commitment_key(4096); + + // Generate Sumcheck challenge, current implementation of Sumcheck Round Univariates batching in Shplemini assumes + // that the challenge is of CONST_PROOF_SIZE_LOG_N + std::vector challenge = this->random_evaluation_point(CONST_PROOF_SIZE_LOG_N); + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // Generate masking polynomials for Sumcheck Round Univariates + ZKSumcheckData zk_sumcheck_data(this->log_n, prover_transcript, ck); + // Generate mock witness + InstanceWitnessGenerator pcs_instance_witness(this->n, 1); + + // Generate valid sumcheck polynomials of given length + pcs_instance_witness.template compute_sumcheck_opening_data( + this->n, this->log_n, this->sumcheck_univariate_length, challenge, ck); + + // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges + const Fr claimed_inner_product = + SmallSubgroupIPAProver::compute_claimed_inner_product(zk_sumcheck_data, challenge, this->log_n); + + prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product); + + // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials + SmallSubgroupIPAProver small_subgroup_ipa_prover( + zk_sumcheck_data, challenge, claimed_inner_product, prover_transcript, ck); + + // Reduce proving to a single claimed fed to KZG or IPA + const auto opening_claim = ShpleminiProver::prove(this->n, + RefVector(pcs_instance_witness.unshifted_polynomials), + RefVector(pcs_instance_witness.to_be_shifted_polynomials), + challenge, + ck, + prover_transcript, + small_subgroup_ipa_prover.get_witness_polynomials(), + pcs_instance_witness.round_univariates, + pcs_instance_witness.sumcheck_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); + } + + // Initialize verifier's transcript + auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); + + std::array libra_commitments = {}; + libra_commitments[0] = + verifier_transcript->template receive_from_prover("Libra:concatenation_commitment"); + + // Place Libra data to the transcript + const Fr libra_total_sum = verifier_transcript->template receive_from_prover("Libra:Sum"); + const Fr libra_challenge = verifier_transcript->template get_challenge("Libra:Challenge"); + const Fr libra_evaluation = verifier_transcript->template receive_from_prover("Libra:claimed_evaluation"); + + // Check that transcript is consistent + EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum); + EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge); + EXPECT_EQ(libra_evaluation, claimed_inner_product); + + // Finalize the array of Libra/SmallSubgroupIpa commitments + libra_commitments[1] = verifier_transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = verifier_transcript->template receive_from_prover("Libra:quotient_commitment"); + + bool consistency_checked = true; + + // Run Shplemini + const auto batch_opening_claim = + ShpleminiVerifier::compute_batch_opening_claim(this->n, + RefVector(pcs_instance_witness.unshifted_commitments), + {}, + RefVector(pcs_instance_witness.unshifted_evals), + {}, + challenge, + this->vk()->get_g1_identity(), + verifier_transcript, + {}, + true, + &consistency_checked, + libra_commitments, + libra_evaluation, + pcs_instance_witness.sumcheck_commitments, + pcs_instance_witness.sumcheck_evaluations); + // Verify claim using KZG or IPA + 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 \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp index 65b0fca780d..f7cd6b15c02 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp @@ -41,7 +41,8 @@ template class ShplonkProver_ { */ static Polynomial compute_batched_quotient(std::span> opening_claims, const Fr& nu, - std::span> libra_opening_claims) + std::span> libra_opening_claims, + std::span> sumcheck_round_claims) { // Find n, the maximum size of all polynomials fⱼ(X) size_t max_poly_size{ 0 }; @@ -86,6 +87,18 @@ template class ShplonkProver_ { Q.add_scaled(tmp, current_nu); current_nu *= nu; } + + for (const auto& claim : sumcheck_round_claims) { + + // Compute individual claim quotient tmp = ( fⱼ(X) − vⱼ) / ( X − xⱼ ) + tmp = claim.polynomial; + tmp.at(0) = tmp[0] - claim.opening_pair.evaluation; + tmp.factor_roots(claim.opening_pair.challenge); + + // Add the claim quotient to the batched quotient polynomial + Q.add_scaled(tmp, current_nu); + current_nu *= nu; + } // Return batched quotient polynomial Q(X) return Q; }; @@ -105,7 +118,8 @@ template class ShplonkProver_ { Polynomial& batched_quotient_Q, const Fr& nu_challenge, const Fr& z_challenge, - std::span> libra_opening_claims = {}) + std::span> libra_opening_claims = {}, + std::span> sumcheck_opening_claims = {}) { const size_t num_opening_claims = opening_claims.size(); @@ -120,6 +134,11 @@ template class ShplonkProver_ { for (const auto& claim : libra_opening_claims) { inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge); } + + for (const auto& claim : sumcheck_opening_claims) { + inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge); + } + Fr::batch_invert(inverse_vanishing_evals); // G(X) = Q(X) - Q_z(X) = Q(X) - ∑ⱼ νʲ ⋅ ( fⱼ(X) − vⱼ) / ( z − xⱼ ), @@ -160,6 +179,17 @@ template class ShplonkProver_ { idx++; current_nu *= nu_challenge; } + + for (const auto& claim : sumcheck_opening_claims) { + tmp = claim.polynomial; + tmp.at(0) = tmp[0] - claim.opening_pair.evaluation; + Fr scaling_factor = current_nu * inverse_vanishing_evals[idx]; // = νʲ / (z − xⱼ ) + + // Add the claim quotient to the batched quotient polynomial + G.add_scaled(tmp, -scaling_factor); + idx++; + current_nu *= nu_challenge; + } // Return opening pair (z, 0) and polynomial G(X) = Q(X) - Q_z(X) return { .polynomial = G, .opening_pair = { .challenge = z_challenge, .evaluation = Fr::zero() } }; }; @@ -177,15 +207,17 @@ template class ShplonkProver_ { static ProverOpeningClaim prove(const std::shared_ptr>& commitment_key, std::span> opening_claims, const std::shared_ptr& transcript, - std::span> libra_opening_claims = {}) + std::span> libra_opening_claims = {}, + std::span> sumcheck_round_claims = {}) { const Fr nu = transcript->template get_challenge("Shplonk:nu"); - auto batched_quotient = compute_batched_quotient(opening_claims, nu, libra_opening_claims); + auto batched_quotient = + compute_batched_quotient(opening_claims, nu, libra_opening_claims, sumcheck_round_claims); auto batched_quotient_commitment = commitment_key->commit(batched_quotient); transcript->send_to_verifier("Shplonk:Q", batched_quotient_commitment); const Fr z = transcript->template get_challenge("Shplonk:z"); return compute_partially_evaluated_batched_quotient( - opening_claims, batched_quotient, nu, z, libra_opening_claims); + opening_claims, batched_quotient, nu, z, libra_opening_claims, sumcheck_round_claims); } }; 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 index 9ca4c61dc97..1cddeb6fd0b 100644 --- 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 @@ -4,6 +4,7 @@ #include "barretenberg/ecc/curves/bn254/bn254.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/polynomials/univariate.hpp" +#include "barretenberg/stdlib/primitives/curves/grumpkin.hpp" #include "barretenberg/sumcheck/zk_sumcheck_data.hpp" #include @@ -81,11 +82,11 @@ template class SmallSubgroupIPAProver { 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 + // The length of a random polynomial masking 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; + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Curve::LIBRA_UNIVARIATES_LENGTH; // Fixed generator of H static constexpr FF subgroup_generator = Curve::subgroup_generator; @@ -191,7 +192,7 @@ template class SmallSubgroupIPAProver { * - 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$. + * u_{D-1}^{LIBRA_UNIVARIATES_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 @@ -418,7 +419,11 @@ template class SmallSubgroupIPAVerifier { static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; - static constexpr size_t LIBRA_UNIVARIATES_LENGTH = (std::is_same_v) ? 9 : 3; + // The length of a random polynomial masking 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 = Curve::LIBRA_UNIVARIATES_LENGTH; public: /*! @@ -493,6 +498,11 @@ template class SmallSubgroupIPAVerifier { diff += lagrange_last * (big_sum_eval - inner_product_eval_claim) - vanishing_poly_eval * quotient_eval; if constexpr (Curve::is_stdlib_type) { + if constexpr (std::is_same_v>) { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1197) + diff.self_reduce(); + } + diff.assert_equal(FF(0)); // TODO(https://github.com/AztecProtocol/barretenberg/issues/1186). Insecure pattern. return (diff.get_value() == FF(0).get_value()); } else { diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp index f3b99924b9f..2d36d1e535a 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp @@ -27,6 +27,11 @@ template struct InstanceWitnessGenerator { std::vector unshifted_evals; std::vector shifted_evals; + // Containers for mock Sumcheck data + std::vector> round_univariates; + std::vector sumcheck_commitments; + std::vector> sumcheck_evaluations; + InstanceWitnessGenerator(const size_t n, const size_t num_polynomials, const size_t num_shiftable, @@ -65,6 +70,51 @@ template struct InstanceWitnessGenerator { idx++; } } + + // Generate zero polynomials to test edge cases in PCS + InstanceWitnessGenerator(const size_t n, const size_t num_zero_polynomials) + : unshifted_polynomials(num_zero_polynomials) + { + for (size_t idx = 0; idx < num_zero_polynomials; idx++) { + unshifted_polynomials[idx] = Polynomial(n); + unshifted_commitments.push_back(Commitment::infinity()); + unshifted_evals.push_back(Fr(0)); + } + } + + template + void compute_sumcheck_opening_data(const size_t n, + const size_t log_n, + const size_t sumcheck_univariate_length, + std::vector& challenge, + std::shared_ptr& ck) + { + // Generate valid sumcheck polynomials of given length + auto mock_sumcheck_polynomials = ZKSumcheckData(log_n, sumcheck_univariate_length); + + for (size_t idx = 0; idx < log_n; idx++) { + bb::Polynomial round_univariate = mock_sumcheck_polynomials.libra_univariates[idx]; + + round_univariate.at(0) += mock_sumcheck_polynomials.libra_running_sum; + + sumcheck_commitments.push_back(ck->commit(round_univariate)); + + sumcheck_evaluations.push_back({ round_univariate.at(0), + round_univariate.evaluate(Fr(1)), + round_univariate.evaluate(challenge[idx]) }); + + mock_sumcheck_polynomials.update_zk_sumcheck_data(challenge[idx], idx); + round_univariates.push_back(round_univariate); + } + + // Simulate the `const proof size` logic + auto round_univariate = bb::Polynomial(n); + for (size_t idx = log_n; idx < CONST_PROOF_SIZE_LOG_N; idx++) { + round_univariates.push_back(round_univariate); + sumcheck_commitments.push_back(ck->commit(round_univariate)); + sumcheck_evaluations.push_back({ Fr(0), Fr(0), Fr(0) }); + } + } }; } // 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 b8ea6f839c8..3106a73a861 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/bn254/bn254.hpp @@ -35,5 +35,8 @@ class BN254 { ScalarField(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); static constexpr ScalarField subgroup_generator_inverse = ScalarField(uint256_t("0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6")); + // The length of the polynomials used to mask the Sumcheck Round Univariates. Computed as + // max(BATCHED_PARTIAL_RELATION_LENGTH) for BN254 Flavors with ZK + static constexpr uint32_t LIBRA_UNIVARIATES_LENGTH = 9; }; } // 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 f195fbd7e53..ba5869c0d0c 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/curves/grumpkin/grumpkin.hpp @@ -67,5 +67,9 @@ class Grumpkin { ScalarField(uint256_t("0x147c647c09fb639514909e9f0513f31ec1a523bf8a0880bc7c24fbc962a9586b")); static constexpr ScalarField subgroup_generator_inverse = ScalarField("0x0c68e27477b5e78cfab790bd3b59806fa871771f71ec7452cde5384f6e3a1988"); + // The length of the polynomials used to mask the Sumcheck Round Univariates. In the ECCVM Sumcheck, the prover only + // sends 3 elements in every round - a commitment to the round univariate and its evaluations at 0 and 1. Therefore, + // length 3 is sufficient. + static constexpr uint32_t LIBRA_UNIVARIATES_LENGTH = 3; }; } // namespace bb::curve \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.test.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.test.cpp index 16525eb8b9f..448b0094155 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.test.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_composer.test.cpp @@ -10,10 +10,12 @@ #include "barretenberg/plonk_honk_shared/library/grand_product_delta.hpp" #include "barretenberg/relations/permutation_relation.hpp" #include "barretenberg/relations/relation_parameters.hpp" +#include "barretenberg/sumcheck/sumcheck.hpp" #include "barretenberg/sumcheck/sumcheck_round.hpp" using namespace bb; - +using FF = ECCVMFlavor::FF; +using PK = ECCVMFlavor::ProvingKey; class ECCVMTests : public ::testing::Test { protected: void SetUp() override { srs::init_grumpkin_crs_factory(bb::srs::get_grumpkin_crs_path()); }; @@ -58,6 +60,33 @@ ECCVMCircuitBuilder generate_circuit(numeric::RNG* engine = nullptr) ECCVMCircuitBuilder builder{ op_queue }; return builder; } +void complete_proving_key_for_test(bb::RelationParameters& relation_parameters, + std::shared_ptr& pk, + std::vector& gate_challenges) +{ + // Prepare the inputs for the sumcheck prover: + // Compute and add beta to relation parameters + const FF beta = FF::random_element(); + const FF gamma = FF::random_element(); + const FF beta_sqr = beta * beta; + relation_parameters.gamma = gamma; + relation_parameters.beta = beta; + relation_parameters.beta_sqr = beta_sqr; + relation_parameters.beta_cube = beta_sqr * beta; + relation_parameters.eccvm_set_permutation_delta = + gamma * (gamma + beta_sqr) * (gamma + beta_sqr + beta_sqr) * (gamma + beta_sqr + beta_sqr + beta_sqr); + relation_parameters.eccvm_set_permutation_delta = relation_parameters.eccvm_set_permutation_delta.invert(); + + // Compute z_perm and inverse polynomial for our logarithmic-derivative lookup method + compute_logderivative_inverse( + pk->polynomials, relation_parameters, pk->circuit_size); + compute_grand_products(pk->polynomials, relation_parameters); + + // Generate gate challenges + for (size_t idx = 0; idx < CONST_PROOF_SIZE_LOG_N; idx++) { + gate_challenges[idx] = FF::random_element(); + } +} TEST_F(ECCVMTests, BaseCase) { @@ -84,3 +113,59 @@ TEST_F(ECCVMTests, EqFails) bool verified = verifier.verify_proof(proof); ASSERT_FALSE(verified); } + +TEST_F(ECCVMTests, CommittedSumcheck) +{ + using Flavor = ECCVMFlavor; + using ProvingKey = ECCVMFlavor::ProvingKey; + using SumcheckProver = SumcheckProver; + using FF = ECCVMFlavor::FF; + using Transcript = Flavor::Transcript; + using ZKData = ZKSumcheckData; + + bb::RelationParameters relation_parameters; + std::vector gate_challenges(CONST_PROOF_SIZE_LOG_N); + + ECCVMCircuitBuilder builder = generate_circuit(&engine); + + ECCVMProver prover(builder); + auto pk = std::make_shared(builder); + const size_t log_circuit_size = pk->log_circuit_size; + + std::shared_ptr prover_transcript = std::make_shared(); + + // Prepare the inputs for the sumcheck prover: + // Compute and add beta to relation parameters + const FF alpha = FF::random_element(); + complete_proving_key_for_test(relation_parameters, pk, gate_challenges); + + auto sumcheck_prover = SumcheckProver(pk->circuit_size, prover_transcript); + + ZKData zk_sumcheck_data = ZKData(log_circuit_size, prover_transcript); + + auto prover_output = + sumcheck_prover.prove(pk->polynomials, relation_parameters, alpha, gate_challenges, zk_sumcheck_data); + + ECCVMVerifier verifier(prover.key); + std::shared_ptr verifier_transcript = std::make_shared(prover_transcript->proof_data); + + // Execute Sumcheck Verifier + SumcheckVerifier sumcheck_verifier = SumcheckVerifier(log_circuit_size, verifier_transcript); + SumcheckOutput verifier_output = sumcheck_verifier.verify(relation_parameters, alpha, gate_challenges); + + // Evaluate prover's round univariates at corresponding challenges and compare them with the claimed evaluations + // computed by the verifier + for (size_t idx = 0; idx < log_circuit_size; idx++) { + FF true_eval_at_the_challenge = prover_output.round_univariates[idx].evaluate(prover_output.challenge[idx]); + FF verifier_eval_at_the_challenge = verifier_output.round_univariate_evaluations[idx][2]; + EXPECT_TRUE(true_eval_at_the_challenge == verifier_eval_at_the_challenge); + } + + // Check that the first sumcheck univariate is consistent with the claimed ZK Sumchek Sum + FF prover_target_sum = zk_sumcheck_data.libra_challenge * zk_sumcheck_data.libra_total_sum; + + EXPECT_TRUE(prover_target_sum == verifier_output.round_univariate_evaluations[0][0] + + verifier_output.round_univariate_evaluations[0][1]); + + EXPECT_TRUE(verifier_output.verified); +} diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp index 37c0e0d7003..ee44fac3dcd 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp @@ -950,7 +950,8 @@ class ECCVMFlavor { Commitment lookup_inverses_comm; Commitment libra_concatenation_commitment; FF libra_sum; - std::vector> sumcheck_univariates; + std::array sumcheck_round_commitments; + std::array, CONST_PROOF_SIZE_LOG_N> sumcheck_round_evaluations; FF libra_claimed_evaluation; Commitment libra_big_sum_commitment; Commitment libra_quotient_commitment; @@ -1164,9 +1165,10 @@ class ECCVMFlavor { 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) { - sumcheck_univariates.emplace_back(NativeTranscript::template deserialize_from_buffer< - bb::Univariate>( - NativeTranscript::proof_data, num_frs_read)); + sumcheck_round_commitments[i] = NativeTranscript::template deserialize_from_buffer( + NativeTranscript::proof_data, num_frs_read); + sumcheck_round_evaluations[i] = NativeTranscript::template deserialize_from_buffer>( + NativeTranscript::proof_data, num_frs_read); } libra_claimed_evaluation = NativeTranscript::template deserialize_from_buffer(proof_data, num_frs_read); @@ -1318,7 +1320,10 @@ class ECCVMFlavor { NativeTranscript::template serialize_to_buffer(libra_sum, NativeTranscript::proof_data); for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { - NativeTranscript::template serialize_to_buffer(sumcheck_univariates[i], NativeTranscript::proof_data); + NativeTranscript::template serialize_to_buffer(sumcheck_round_commitments[i], + NativeTranscript::proof_data); + NativeTranscript::template serialize_to_buffer(sumcheck_round_evaluations[i], + NativeTranscript::proof_data); } NativeTranscript::template serialize_to_buffer(libra_claimed_evaluation, proof_data); diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index 35f2e319175..1defb569f97 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -136,7 +136,9 @@ void ECCVMProver::execute_pcs_rounds() sumcheck_output.challenge, key->commitment_key, transcript, - small_subgroup_ipa_prover.get_witness_polynomials()); + small_subgroup_ipa_prover.get_witness_polynomials(), + sumcheck_output.round_univariates, + sumcheck_output.round_univariate_evaluations); // 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_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp index 2ba91400446..be64494b572 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp @@ -29,11 +29,9 @@ class ECCVMTranscriptTests : public ::testing::Test { TranscriptManifest construct_eccvm_honk_manifest() { TranscriptManifest manifest_expected; - 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(); size_t frs_per_G = bb::field_conversion::calc_num_bn254_frs(); - size_t frs_per_uni = MAX_PARTIAL_RELATION_LENGTH * frs_per_Fr; size_t frs_per_evals = (Flavor::NUM_ALL_ENTITIES)*frs_per_Fr; size_t frs_per_uint32 = bb::field_conversion::calc_num_bn254_frs(); @@ -146,7 +144,9 @@ class ECCVMTranscriptTests : public ::testing::Test { for (size_t i = 0; i < CONST_PROOF_SIZE_LOG_N; ++i) { round++; std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Sumcheck:univariate_" + idx, frs_per_uni); + manifest_expected.add_entry(round, "Sumcheck:univariate_comm_" + idx, frs_per_G); + manifest_expected.add_entry(round, "Sumcheck:univariate_" + idx + "_eval_0", frs_per_Fr); + manifest_expected.add_entry(round, "Sumcheck:univariate_" + idx + "_eval_1", frs_per_Fr); std::string label = "Sumcheck:u_" + idx; manifest_expected.add_challenge(round, label); } diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp index b2a260a8be7..04a9a3a0d1b 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp @@ -59,14 +59,13 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) 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); + auto sumcheck_output = 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()) { + if (!sumcheck_output.verified) { vinfo("eccvm sumcheck failed"); return false; } @@ -77,16 +76,18 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted(), commitments.get_to_be_shifted(), - claimed_evaluations.get_unshifted(), - claimed_evaluations.get_shifted(), - multivariate_challenge, + sumcheck_output.claimed_evaluations.get_unshifted(), + sumcheck_output.claimed_evaluations.get_shifted(), + sumcheck_output.challenge, key->pcs_verification_key->get_g1_identity(), transcript, Flavor::REPEATED_COMMITMENTS, Flavor::HasZK, &consistency_checked, libra_commitments, - libra_evaluation); + sumcheck_output.claimed_libra_evaluation, + sumcheck_output.round_univariate_commitments, + sumcheck_output.round_univariate_evaluations); // Reduce the accumulator to a single opening claim const OpeningClaim multivariate_to_univariate_opening_claim = @@ -134,8 +135,8 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof) const bool batched_opening_verified = PCS::reduce_verify(key->pcs_verification_key, batch_opening_claim, ipa_transcript); - vinfo("eccvm sumcheck verified?: ", sumcheck_verified.value()); + vinfo("eccvm sumcheck verified?: ", sumcheck_output.verified); vinfo("batch opening verified?: ", batched_opening_verified); - return sumcheck_verified.value() && batched_opening_verified && consistency_checked; + return sumcheck_output.verified && batched_opening_verified && consistency_checked; } } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp index 0dc5e962210..7a1cddb0069 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp @@ -419,6 +419,8 @@ concept IsRecursiveFlavor = IsAnyOf, AvmRecursiveFlavor_>; +// These concepts are relevant for Sumcheck, where the logic is different for BN254 and Grumpkin Flavors +template concept IsGrumpkinFlavor = IsAnyOf>; template concept IsECCVMRecursiveFlavor = IsAnyOf>; diff --git a/barretenberg/cpp/src/barretenberg/polynomials/row_disabling_polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/row_disabling_polynomial.hpp index e5c2abe7f67..e4af64d824f 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/row_disabling_polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/row_disabling_polynomial.hpp @@ -174,6 +174,28 @@ template struct RowDisablingPolynomial { return FF{ 1 } - evaluation_at_multivariate_challenge; } + /** + * @brief stdlib version of the above that ensures that the verifier's work does not depend on `log_circuit_size`. + * + */ + template + static FF evaluate_at_challenge(std::vector multivariate_challenge, + const size_t log_circuit_size, + Builder* builder) + { + FF evaluation_at_multivariate_challenge{ 1 }; + const FF one = FF{ 1 }; + + for (size_t idx = 2; idx < CONST_PROOF_SIZE_LOG_N; idx++) { + stdlib::bool_t dummy_round = stdlib::witness_t(builder, idx >= log_circuit_size); + evaluation_at_multivariate_challenge = + FF::conditional_assign(dummy_round, + evaluation_at_multivariate_challenge * one, + evaluation_at_multivariate_challenge * multivariate_challenge[idx]); + } + + return one - evaluation_at_multivariate_challenge; + } }; } // namespace bb \ No newline at end of file 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 248fa30346e..cdd92da6198 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 @@ -80,8 +80,7 @@ ECCVMRecursiveVerifier_::verify_proof(const ECCVMProof& proof) libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); - auto [multivariate_challenge, claimed_evaluations, claimed_libra_evaluation, sumcheck_verified] = - sumcheck.verify(relation_parameters, alpha, gate_challenges); + auto sumcheck_output = 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"); @@ -93,16 +92,18 @@ ECCVMRecursiveVerifier_::verify_proof(const ECCVMProof& proof) Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted(), commitments.get_to_be_shifted(), - claimed_evaluations.get_unshifted(), - claimed_evaluations.get_shifted(), - multivariate_challenge, + sumcheck_output.claimed_evaluations.get_unshifted(), + sumcheck_output.claimed_evaluations.get_shifted(), + sumcheck_output.challenge, key->pcs_verification_key->get_g1_identity(), transcript, Flavor::REPEATED_COMMITMENTS, Flavor::HasZK, &consistency_checked, libra_commitments, - claimed_libra_evaluation); + sumcheck_output.claimed_libra_evaluation, + sumcheck_output.round_univariate_commitments, + sumcheck_output.round_univariate_evaluations); // Reduce the accumulator to a single opening claim const OpeningClaim multivariate_to_univariate_opening_claim = @@ -148,7 +149,6 @@ ECCVMRecursiveVerifier_::verify_proof(const ECCVMProof& proof) const OpeningClaim batch_opening_claim = Shplonk::reduce_verification(key->pcs_verification_key->get_g1_identity(), opening_claims, transcript); - ASSERT(sumcheck_verified); return { batch_opening_claim, ipa_transcript }; } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp index 5feb3b6e872..29504a56e71 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/bn254.hpp @@ -49,6 +49,9 @@ template struct bn254 { bb::fr(uint256_t("0x07b0c561a6148404f086204a9f36ffb0617942546750f230c893619174a57a76")); static constexpr bb::fr subgroup_generator_inverse = bb::fr(uint256_t("0x204bd3277422fad364751ad938e2b5e6a54cf8c68712848a692c553d0329f5d6")); + // The length of the polynomials used to mask the Sumcheck Round Univariates. Computed as + // max(BATCHED_PARTIAL_RELATION_LENGTH) for BN254 Flavors with ZK + static constexpr uint32_t LIBRA_UNIVARIATES_LENGTH = 9; }; // namespace bn254 diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp index c8630ac735e..fdaad5616c6 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/curves/grumpkin.hpp @@ -42,6 +42,10 @@ template struct grumpkin { bb::fq("0x147c647c09fb639514909e9f0513f31ec1a523bf8a0880bc7c24fbc962a9586b"); static constexpr bb::fq subgroup_generator_inverse = bb::fq("0x0c68e27477b5e78cfab790bd3b59806fa871771f71ec7452cde5384f6e3a1988"); + // The length of the polynomials used to mask the Sumcheck Round Univariates. In the ECCVM Sumcheck, the prover only + // sends 3 elements in every round - a commitment to the round univariate and its evaluations at 0 and 1. Therefore, + // length 3 is sufficient. + static constexpr uint32_t LIBRA_UNIVARIATES_LENGTH = 3; }; } // 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 c0cbacf2e92..62a7b0643ea 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 @@ -114,8 +114,7 @@ std::array TranslatorRecursiveVerifier_ 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); + auto sumcheck_output = 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"); @@ -126,18 +125,20 @@ std::array TranslatorRecursiveVerifier_ class SumcheckProver { using ProverPolynomials = typename Flavor::ProverPolynomials; using PartiallyEvaluatedMultivariates = typename Flavor::PartiallyEvaluatedMultivariates; using ClaimedEvaluations = typename Flavor::AllValues; - + using ZKData = ZKSumcheckData; using Transcript = typename Flavor::Transcript; using RelationSeparator = typename Flavor::RelationSeparator; + using CommitmentKey = typename Flavor::CommitmentKey; + /** * @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$. @@ -133,32 +135,27 @@ template class SumcheckProver { // this constant specifies the number of coefficients of libra polynomials, and evaluations of round univariate static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; - // Specify the number of all witnesses including shifts and derived witnesses from flavors that have ZK, - // otherwise, set this constant to 0 - static constexpr size_t NUM_ALL_WITNESS_ENTITIES = Flavor::NUM_ALL_WITNESS_ENTITIES; - /** - * @brief The size of the hypercube, i.e. \f$ 2^d\f$. - * - */ using SumcheckRoundUnivariate = typename bb::Univariate; - using EvaluationMaskingTable = - std::array, NUM_ALL_WITNESS_ENTITIES>; + + // The size of the hypercube, i.e. \f$ 2^d\f$. const size_t multivariate_n; - /** - * @brief The number of variables - * - */ + // The number of variables const size_t multivariate_d; - using EvalMaskingScalars = std::array; - // 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; + std::vector multivariate_challenge; + + std::vector round_univariate_commitments = {}; + std::vector> round_evaluations = {}; + std::vector> round_univariates = {}; + std::vector eval_domain = {}; + FF libra_evaluation = FF{ 0 }; + + RowDisablingPolynomial row_disabling_polynomial; + /** * * @brief Container for partially evaluated Prover Polynomials at a current challenge. Upon computing challenge \f$ @@ -195,7 +192,6 @@ template class SumcheckProver { 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, @@ -244,12 +240,12 @@ template class SumcheckProver { } // 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); + ClaimedEvaluations 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 }; + return SumcheckOutput{ .challenge = multivariate_challenge, + .claimed_evaluations = multivariate_evaluations }; vinfo("finished sumcheck"); }; @@ -271,13 +267,25 @@ template class SumcheckProver { ZKData& zk_sumcheck_data) requires Flavor::HasZK { + std::shared_ptr ck = nullptr; + + if constexpr (IsGrumpkinFlavor) { + ck = std::make_shared(BATCHED_RELATION_PARTIAL_LENGTH); + // Compute the vector {0, 1, \ldots, BATCHED_RELATION_PARTIAL_LENGTH-1} needed to transform the round + // univariates from Lagrange to monomial basis + for (size_t idx = 0; idx < BATCHED_RELATION_PARTIAL_LENGTH; idx++) { + eval_domain.push_back(FF(idx)); + } + } else { + // Ensure that the length of Sumcheck Round Univariates does not exceed the length of Libra masking + // polynomials. + ASSERT(BATCHED_RELATION_PARTIAL_LENGTH <= Flavor::Curve::LIBRA_UNIVARIATES_LENGTH); + } bb::GateSeparatorPolynomial gate_separators(gate_challenges, multivariate_d); - std::vector multivariate_challenge; multivariate_challenge.reserve(multivariate_d); size_t round_idx = 0; - RowDisablingPolynomial row_disabling_polynomial; // 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. @@ -293,14 +301,24 @@ template class SumcheckProver { 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"); + if constexpr (!IsGrumpkinFlavor) { + // Place the evaluations of the round univariate into transcript. + transcript->send_to_verifier("Sumcheck:univariate_0", round_univariate); + } else { + + // Compute monomial coefficients of the round univariate, commit to it, populate an auxiliary structure + // needed in the PCS round + commit_to_round_univariate( + round_idx, round_univariate, eval_domain, transcript, ck, round_univariates, round_evaluations); + } + + const 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); // Prepare ZK Sumcheck data for the next round - update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + zk_sumcheck_data.update_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 @@ -319,47 +337,76 @@ template class SumcheckProver { alpha, zk_sumcheck_data, row_disabling_polynomial); - // 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)); + if constexpr (!IsGrumpkinFlavor) { + // Place evaluations of Sumcheck Round Univariate in the transcript + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(round_idx), round_univariate); + } else { + + // Compute monomial coefficients of the round univariate, commit to it, populate an auxiliary structure + // needed in the PCS round + commit_to_round_univariate( + round_idx, round_univariate, eval_domain, transcript, ck, round_univariates, round_evaluations); + } + const 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); // Prepare evaluation masking and libra structures for the next round (for ZK Flavors) - update_zk_sumcheck_data(zk_sumcheck_data, round_challenge, round_idx); + zk_sumcheck_data.update_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; } + + if constexpr (IsGrumpkinFlavor) { + round_evaluations[multivariate_d - 1][2] = + round_univariate.evaluate(multivariate_challenge[multivariate_d - 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); + if constexpr (!IsGrumpkinFlavor) { + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(idx), zero_univariate); + } else { + transcript->send_to_verifier("Sumcheck:univariate_comm_" + std::to_string(idx), + ck->commit(Polynomial(std::span(zero_univariate)))); + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(idx) + "_eval_0", FF(0)); + transcript->send_to_verifier("Sumcheck:univariate_" + std::to_string(idx) + "_eval_1", FF(0)); + } 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); + ClaimedEvaluations multivariate_evaluations = extract_claimed_evaluations(partially_evaluated_polynomials); transcript->send_to_verifier("Sumcheck:evaluations", multivariate_evaluations.get_all()); // The evaluations of Libra uninvariates at \f$ g_0(u_0), \ldots, g_{d-1} (u_{d-1}) \f$ are added to the // transcript. - FF libra_evaluation{ 0 }; + FF libra_evaluation = zk_sumcheck_data.constant_term; for (const auto& libra_eval : zk_sumcheck_data.libra_evaluations) { libra_evaluation += libra_eval; } - libra_evaluation += zk_sumcheck_data.constant_term; transcript->send_to_verifier("Libra:claimed_evaluation", libra_evaluation); // 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 }; + if constexpr (!IsGrumpkinFlavor) { + return SumcheckOutput{ .challenge = multivariate_challenge, + .claimed_evaluations = multivariate_evaluations, + .claimed_libra_evaluation = libra_evaluation }; + } else { + return SumcheckOutput{ .challenge = multivariate_challenge, + .claimed_evaluations = multivariate_evaluations, + .claimed_libra_evaluation = libra_evaluation, + .round_univariates = round_univariates, + .round_univariate_evaluations = round_evaluations }; + } vinfo("finished sumcheck"); }; @@ -447,59 +494,45 @@ polynomials that are sent in clear. }; /** - * @brief Upon receiving the challenge \f$u_i\f$, the prover updates Libra data. If \f$ i < d-1\f$ - - - update the table of Libra univariates by multiplying every term by \f$1/2\f$. - - computes the value \f$2^{d-i - 2} \cdot \texttt{libra_challenge} \cdot g_0(u_0)\f$ applying \ref - bb::Univariate::evaluate "evaluate" method to the first univariate in the table \f$\texttt{libra_univariates}\f$ - - places the value \f$ g_0(u_0)\f$ to the vector \f$ \texttt{libra_evaluations}\f$ - - update the running sum - \f{align}{ - \texttt{libra_running_sum} \gets 2^{d-i-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0) + 2^{-1} - \cdot \left( \texttt{libra_running_sum} - (\texttt{libra_univariates}_{i+1}(0) + - \texttt{libra_univariates}_{i+1}(1)) \right) \f} If \f$ i = d-1\f$ - - compute the value \f$ g_{d-1}(u_{d-1})\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the - last univariate in the table \f$\texttt{libra_univariates}\f$ and dividing the result by \f$ - \texttt{libra_challenge} \f$. - - update the table of Libra univariates by multiplying every term by \f$\texttt{libra_challenge}^{-1}\f$. - @todo Refactor once the Libra univariates are extracted from the Proving Key. Then the prover does not need to - update the first round_idx - 1 univariates and could release the memory. Also, use batch_invert / reduce - the number of divisions by 2. - * @param libra_univariates - * @param round_challenge + * @brief Compute monomial coefficients of the round univariate, commit to it, populate an auxiliary structure + * needed in the PCS round + * * @param round_idx - * @param libra_running_sum - * @param libra_evaluations + * @param round_univariate Sumcheck Round Univariate + * @param eval_domain {0, 1, ... , BATCHED_RELATION_PARTIAL_LENGTH-1} + * @param transcript + * @param ck Commitment key of size BATCHED_RELATION_PARTIAL_LENGTH + * @param round_univariates Auxiliary container to be fed to Shplemini + * @param round_univariate_evaluations Auxiliary container to be fed to Shplemini */ - void update_zk_sumcheck_data(ZKData& zk_sumcheck_data, const FF round_challenge, size_t round_idx) + void commit_to_round_univariate(const size_t round_idx, + bb::Univariate& round_univariate, + const std::vector& eval_domain, + const std::shared_ptr& transcript, + const std::shared_ptr& ck, + std::vector>& round_univariates, + std::vector>& round_univariate_evaluations) { - 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 *= 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.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 *= two_inv; - - zk_sumcheck_data.libra_evaluations.emplace_back(libra_evaluation / zk_sumcheck_data.libra_scaling_factor); - } else { - // compute the evaluation of the last Libra univariate at the challenge u_{d-1} - auto libra_evaluation = zk_sumcheck_data.libra_univariates[round_idx].evaluate(round_challenge) / - zk_sumcheck_data.libra_scaling_factor; - // place the evalution into the vector of Libra evaluations - zk_sumcheck_data.libra_evaluations.emplace_back(libra_evaluation); - for (auto univariate : zk_sumcheck_data.libra_univariates) { - univariate *= FF(1) / zk_sumcheck_data.libra_challenge; - } + + const std::string idx = std::to_string(round_idx); + + // Transform to monomial form and commit to it + Polynomial round_poly_monomial( + eval_domain, std::span(round_univariate.evaluations), BATCHED_RELATION_PARTIAL_LENGTH); + transcript->send_to_verifier("Sumcheck:univariate_comm_" + idx, ck->commit(round_poly_monomial)); + + // Store round univariate in monomial, as it is required by Shplemini + round_univariates.push_back(std::move(round_poly_monomial)); + + // Send the evaluations of the round univariate at 0 and 1 + transcript->send_to_verifier("Sumcheck:univariate_" + idx + "_eval_0", round_univariate.value_at(0)); + transcript->send_to_verifier("Sumcheck:univariate_" + idx + "_eval_1", round_univariate.value_at(1)); + + // Store the evaluations to be used by ShpleminiProver. + round_univariate_evaluations.push_back({ round_univariate.value_at(0), round_univariate.value_at(1), FF(0) }); + if (round_idx > 0) { + round_univariate_evaluations[round_idx - 1][2] = + round_univariate.value_at(0) + round_univariate.value_at(1); }; } }; @@ -555,6 +588,7 @@ template class SumcheckVerifier { using ClaimedLibraEvaluations = typename std::vector; using Transcript = typename Flavor::Transcript; using RelationSeparator = typename Flavor::RelationSeparator; + using Commitment = typename Flavor::Commitment; /** * @brief Maximum partial algebraic degree of the relation \f$\tilde F = pow_{\beta} \cdot F \f$, i.e. \ref @@ -574,6 +608,12 @@ template class SumcheckVerifier { std::shared_ptr transcript; SumcheckVerifierRound round; + FF libra_evaluation{ 0 }; + FF libra_challenge; + FF libra_total_sum; + + std::vector round_univariate_commitments = {}; + std::vector> round_univariate_evaluations = {}; // Verifier instantiates sumcheck with circuit size, optionally a different target sum than 0 can be specified. explicit SumcheckVerifier(size_t multivariate_d, std::shared_ptr transcript, FF target_sum = 0) @@ -605,11 +645,12 @@ template class SumcheckVerifier { throw_or_abort("Number of variables in multivariate is 0."); } - FF libra_challenge; + bb::Univariate round_univariate; + if constexpr (Flavor::HasZK) { // If running zero-knowledge sumcheck the target total sum is corrected by the claimed sum of libra masking // multivariate over the hypercube - FF libra_total_sum = transcript->template receive_from_prover("Libra:Sum"); + libra_total_sum = transcript->template receive_from_prover("Libra:Sum"); libra_challenge = transcript->template get_challenge("Libra:Challenge"); round.target_total_sum += libra_total_sum * libra_challenge; } @@ -619,10 +660,11 @@ template class SumcheckVerifier { for (size_t round_idx = 0; round_idx < CONST_PROOF_SIZE_LOG_N; round_idx++) { // Obtain the round univariate from the transcript std::string round_univariate_label = "Sumcheck:univariate_" + std::to_string(round_idx); - auto round_univariate = + round_univariate = transcript->template receive_from_prover>( round_univariate_label); FF round_challenge = transcript->template get_challenge("Sumcheck:u_" + std::to_string(round_idx)); + multivariate_challenge.emplace_back(round_challenge); if constexpr (IsRecursiveFlavor) { typename Flavor::CircuitBuilder* builder = round_challenge.get_context(); @@ -633,7 +675,6 @@ template class SumcheckVerifier { if (round_idx < multivariate_d) { verified = verified && checked; } - multivariate_challenge.emplace_back(round_challenge); round.compute_next_target_sum(round_univariate, round_challenge, dummy_round); gate_separators.partially_evaluate(round_challenge, dummy_round); @@ -642,11 +683,8 @@ template class SumcheckVerifier { if (round_idx < multivariate_d) { bool checked = round.check_sum(round_univariate); verified = verified && checked; - multivariate_challenge.emplace_back(round_challenge); round.compute_next_target_sum(round_univariate, round_challenge); gate_separators.partially_evaluate(round_challenge); - } else { - multivariate_challenge.emplace_back(round_challenge); } } } @@ -665,17 +703,12 @@ template class SumcheckVerifier { purported_evaluations, relation_parameters, gate_separators, alpha); // For ZK Flavors: the evaluation of the Row Disabling Polynomial at the sumcheck challenge - FF libra_evaluation{ 0 }; if constexpr (Flavor::HasZK) { libra_evaluation = transcript->template receive_from_prover("Libra:claimed_evaluation"); FF correcting_factor = RowDisablingPolynomial::evaluate_at_challenge(multivariate_challenge, multivariate_d); full_honk_purported_value = full_honk_purported_value * correcting_factor + libra_evaluation * libra_challenge; - if constexpr (IsECCVMRecursiveFlavor) { - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1197) - full_honk_purported_value.self_reduce(); - } } //! [Final Verification Step] @@ -685,12 +718,178 @@ template class SumcheckVerifier { verified = verified && (full_honk_purported_value == round.target_total_sum); } - return SumcheckOutput{ - multivariate_challenge, - purported_evaluations, - libra_evaluation, - verified, - }; + return SumcheckOutput{ .challenge = multivariate_challenge, + .claimed_evaluations = purported_evaluations, + .verified = verified, + .claimed_libra_evaluation = libra_evaluation }; + }; + + /** + * @brief Sumcheck Verifier for ECCVM and ECCVMRecursive. + * @details The verifier receives commitments to RoundUnivariates, along with their evaluations at 0 and 1. These + * evaluations will be proved as a part of Shplemini. The only check that the Verifier performs in this version is + * the comparison of the target sumcheck sum with the claimed evaluations of the first sumcheck round univariate at + * 0 and 1. + * + * Note that the SumcheckOutput in this case contains a vector of commitments and a vector of arrays (of size 3) of + * evaluations at 0, 1, and a round challenge. + * + * @param relation_parameters + * @param alpha + * @param gate_challenges + * @return SumcheckOutput + */ + SumcheckOutput verify(const bb::RelationParameters& relation_parameters, + RelationSeparator alpha, + const std::vector& gate_challenges) + requires IsGrumpkinFlavor + { + bool verified(false); + + bb::GateSeparatorPolynomial gate_separators(gate_challenges); + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1144): Add proper constraints for taking the log of + // a field_t link multivariate_d. + if (multivariate_d == 0) { + throw_or_abort("Number of variables in multivariate is 0."); + } + + // get the claimed sum of libra masking multivariate over the hypercube + libra_total_sum = transcript->template receive_from_prover("Libra:Sum"); + // get the challenge for the ZK Sumcheck claim + const FF libra_challenge = transcript->template get_challenge("Libra:Challenge"); + + std::vector multivariate_challenge; + multivariate_challenge.reserve(CONST_PROOF_SIZE_LOG_N); + // if Flavor has ZK, the target total sum is corrected by Libra total sum multiplied by the Libra + // challenge + round.target_total_sum += libra_total_sum * libra_challenge; + + for (size_t round_idx = 0; round_idx < CONST_PROOF_SIZE_LOG_N; round_idx++) { + // Obtain the round univariate from the transcript + const std::string round_univariate_comm_label = "Sumcheck:univariate_comm_" + std::to_string(round_idx); + const std::string univariate_eval_label_0 = "Sumcheck:univariate_" + std::to_string(round_idx) + "_eval_0"; + const std::string univariate_eval_label_1 = "Sumcheck:univariate_" + std::to_string(round_idx) + "_eval_1"; + + // Receive the commitment to the round univariate + round_univariate_commitments.push_back( + transcript->template receive_from_prover(round_univariate_comm_label)); + // Receive evals at 0 and 1 + round_univariate_evaluations.push_back( + { transcript->template receive_from_prover(univariate_eval_label_0), + transcript->template receive_from_prover(univariate_eval_label_1) }); + + const FF round_challenge = + transcript->template get_challenge("Sumcheck:u_" + std::to_string(round_idx)); + multivariate_challenge.emplace_back(round_challenge); + + if constexpr (IsRecursiveFlavor) { + typename Flavor::CircuitBuilder* builder = round_challenge.get_context(); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1114): insecure dummy_round derivation! + stdlib::bool_t dummy_round = stdlib::witness_t(builder, round_idx >= multivariate_d); + // Only utilize the checked value if this is not a constant proof size padding round + gate_separators.partially_evaluate(round_challenge, dummy_round); + + } else { + if (round_idx < multivariate_d) { + gate_separators.partially_evaluate(round_challenge); + } + } + } + + FF first_sumcheck_round_evaluations_sum = + round_univariate_evaluations[0][0] + round_univariate_evaluations[0][1]; + + // Populate claimed evaluations at the challenge + ClaimedEvaluations purported_evaluations; + auto transcript_evaluations = + transcript->template receive_from_prover>("Sumcheck:evaluations"); + for (auto [eval, transcript_eval] : zip_view(purported_evaluations.get_all(), transcript_evaluations)) { + eval = transcript_eval; + } + // For ZK Flavors: the evaluation of the Row Disabling Polynomial at the sumcheck challenge + // Evaluate the Honk relation at the point (u_0, ..., u_{d-1}) using claimed evaluations of prover polynomials. + // In ZK Flavors, the evaluation is corrected by full_libra_purported_value + FF full_honk_purported_value = round.compute_full_relation_purported_value( + purported_evaluations, relation_parameters, gate_separators, alpha); + + // Extract claimed evaluations of Libra univariates and compute their sum multiplied by the Libra challenge + const FF libra_evaluation = transcript->template receive_from_prover("Libra:claimed_evaluation"); + + // We have to branch here for two reasons: + // 1) need to make the vk constant + // 2) ECCVMRecursive uses big_field where we need to self_reduce(). + if constexpr (IsRecursiveFlavor) { + typename Flavor::CircuitBuilder* builder = libra_challenge.get_context(); + + // Compute the evaluations of the polynomial (1 - \sum L_i) where the sum is for i corresponding to the rows + // where all sumcheck relations are disabled + const FF correcting_factor = + RowDisablingPolynomial::evaluate_at_challenge(multivariate_challenge, multivariate_d, builder); + + // Verifier computes full ZK Honk value, taking into account the contribution from the disabled row and the + // Libra polynomials + full_honk_purported_value = + full_honk_purported_value * correcting_factor + libra_evaluation * libra_challenge; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1197) + full_honk_purported_value.self_reduce(); + + // Populate claimed evaluations of Sumcheck Round Unviariates at the round challenges. These will be + // checked as a part of Shplemini and pad claimed evaluations to the CONST_PROOF_SIZE_LOG_N + for (size_t round_idx = 1; round_idx < CONST_PROOF_SIZE_LOG_N; round_idx++) { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1114): insecure dummy_round derivation! + stdlib::bool_t dummy_round = stdlib::witness_t(builder, round_idx >= multivariate_d); + round_univariate_evaluations[round_idx - 1][2] = FF::conditional_assign( + dummy_round, + full_honk_purported_value, + round_univariate_evaluations[round_idx][0] + round_univariate_evaluations[round_idx][1]); + }; + + first_sumcheck_round_evaluations_sum.self_reduce(); + round.target_total_sum.self_reduce(); + + // Ensure that the sum of the evaluations of the first Sumcheck Round Univariate is equal to the claimed + // target total sum + first_sumcheck_round_evaluations_sum.assert_equal(round.target_total_sum); + verified = (first_sumcheck_round_evaluations_sum.get_value() == round.target_total_sum.get_value()); + } else { + // Compute the evaluations of the polynomial (1 - \sum L_i) where the sum is for i corresponding to the rows + // where all sumcheck relations are disabled + const FF correcting_factor = + RowDisablingPolynomial::evaluate_at_challenge(multivariate_challenge, multivariate_d); + + // Verifier computes full ZK Honk value, taking into account the contribution from the disabled row and the + // Libra polynomials + full_honk_purported_value = + full_honk_purported_value * correcting_factor + libra_evaluation * libra_challenge; + + // Populate claimed evaluations of Sumcheck Round Unviariates at the round challenges. These will be checked + // as a part of Shplemini + for (size_t round_idx = 1; round_idx < multivariate_d; round_idx++) { + round_univariate_evaluations[round_idx - 1][2] = + round_univariate_evaluations[round_idx][0] + round_univariate_evaluations[round_idx][1]; + }; + + // Pad claimed evaluations to the CONST_PROOF_SIZE_LOG_N + for (size_t round_idx = multivariate_d; round_idx < CONST_PROOF_SIZE_LOG_N; round_idx++) { + round_univariate_evaluations[round_idx - 1][2] = full_honk_purported_value; + }; + + // Ensure that the sum of the evaluations of the first Sumcheck Round Univariate is equal to the claimed + // target total sum + verified = (first_sumcheck_round_evaluations_sum == round.target_total_sum); + } + + //! [Final Verification Step] + // For ZK Flavors: the evaluations of Libra univariates are included in the Sumcheck Output + return SumcheckOutput{ .challenge = multivariate_challenge, + .claimed_evaluations = purported_evaluations, + .verified = verified, + .claimed_libra_evaluation = libra_evaluation, + .round_univariate_commitments = round_univariate_commitments, + .round_univariate_evaluations = round_univariate_evaluations }; }; }; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp index 0cf586f47eb..4d8aeee3c96 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck.test.cpp @@ -277,7 +277,7 @@ template class SumcheckTests : public ::testing::Test { } auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_alpha, verifier_gate_challenges); - auto verified = verifier_output.verified.value(); + auto verified = verifier_output.verified; EXPECT_EQ(verified, true); }; @@ -367,7 +367,7 @@ template class SumcheckTests : public ::testing::Test { } auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_alpha, verifier_gate_challenges); - auto verified = verifier_output.verified.value(); + auto verified = verifier_output.verified; EXPECT_EQ(verified, false); }; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp index a2534bd68d0..2d415ee23fe 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_output.hpp @@ -1,5 +1,6 @@ #pragma once #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/polynomials/polynomial.hpp" #include #include #include @@ -15,15 +16,22 @@ namespace bb { template struct SumcheckOutput { using FF = typename Flavor::FF; using ClaimedEvaluations = typename Flavor::AllValues; + using Commitment = typename Flavor::Commitment; + // \f$ \vec u = (u_0, ..., u_{d-1}) \f$ std::vector challenge; // Evaluations at \f$ \vec u \f$ of the polynomials used in Sumcheck ClaimedEvaluations claimed_evaluations; - // For ZK Flavors: the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges, - // otherwise remains the default value 0 - FF claimed_libra_evaluation = 0; - std::optional verified = false; // Optional b/c this struct is shared by the Prover/Verifier // Whether or not the evaluations of multilinear polynomials \f$ P_1, \ldots, P_N \f$ and final Sumcheck evaluation // have been confirmed + bool verified = false; + // For ZK Flavors: the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges + FF claimed_libra_evaluation = FF{ 0 }; + // For ECCVMVerifier: Commitments to round univariates + std::vector round_univariate_commitments = {}; + // For ECCVMProver: Round univariates in monomial basis + std::vector> round_univariates = {}; + // For ECCVMProver/Verifier: evaluations of round univariates at 0, 1, and round challenge + std::vector> round_univariate_evaluations = {}; }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp index f1c81c62e3f..a8a188985d6 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/sumcheck_round.hpp @@ -67,8 +67,9 @@ template class SumcheckProverRound { using SumcheckRoundUnivariate = bb::Univariate; SumcheckTupleOfTuplesOfUnivariates univariate_accumulators; - static constexpr size_t LIBRA_UNIVARIATES_LENGTH = - (std::is_same_v) ? BATCHED_RELATION_PARTIAL_LENGTH : 3; + // The length of the polynomials used to mask the Sumcheck Round Univariates. + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Flavor::Curve::LIBRA_UNIVARIATES_LENGTH; + // Prover constructor SumcheckProverRound(size_t initial_round_size) : round_size(initial_round_size) diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp index d35f188d1a7..4c1a671eed4 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/zk_sumcheck_data.hpp @@ -21,8 +21,8 @@ template struct ZKSumcheckData { static constexpr FF subgroup_generator = Curve::subgroup_generator; - // 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 = (std::is_same_v) ? 9 : 3; + // The size of the LibraUnivariates. + static constexpr size_t LIBRA_UNIVARIATES_LENGTH = Curve::LIBRA_UNIVARIATES_LENGTH; static constexpr FF one_half = FF(1) / FF(2); @@ -45,17 +45,19 @@ template struct ZKSumcheckData { FF libra_running_sum; ClaimedLibraEvaluations libra_evaluations; + size_t univariate_length; // Default constructor ZKSumcheckData() = default; // Main constructor ZKSumcheckData(const size_t multivariate_d, - std::shared_ptr transcript, + std::shared_ptr transcript = nullptr, std::shared_ptr commitment_key = nullptr) : 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 + , libra_concatenated_monomial_form(SUBGROUP_SIZE + 2) // includes masking + , libra_univariates(generate_libra_univariates(multivariate_d, LIBRA_UNIVARIATES_LENGTH)) , log_circuit_size(multivariate_d) + , univariate_length(LIBRA_UNIVARIATES_LENGTH) { create_interpolation_domain(); @@ -80,22 +82,46 @@ template struct ZKSumcheckData { // Initialize the Libra running sum libra_running_sum = libra_total_sum * libra_challenge; - // Setup the Libra data + // Prepare the Libra data for the first round of sumcheck + setup_auxiliary_data(libra_univariates, libra_scaling_factor, libra_challenge, libra_running_sum); } + /** + * @brief For test purposes: Constructs a sumcheck instance from the polynomial \f$ g + \sum_{i=0}^d g_i(X_i)\f$, + * where \f$ g_i \f$ is a random univariate of a given length and \f$ g\f$ is a random constant term. + * + * @details To test Shplemini with commitments to Sumcheck Round Univariates, we need to create valid Sumcheck Round + * Univariates. Fortunately, the functionality of ZKSumcheckData could be re-used for this purpose. + * @param multivariate_d + * @param univariate_length + */ + ZKSumcheckData(const size_t multivariate_d, const size_t univariate_length) + : constant_term(FF::random_element()) + , libra_univariates(generate_libra_univariates(multivariate_d, univariate_length)) + , log_circuit_size(multivariate_d) + , libra_scaling_factor(FF(1)) + , libra_challenge(FF::random_element()) + , libra_total_sum(compute_libra_total_sum(libra_univariates, libra_scaling_factor, constant_term)) + , libra_running_sum(libra_total_sum * libra_challenge) + , univariate_length(univariate_length) + + { + setup_auxiliary_data(libra_univariates, libra_scaling_factor, libra_challenge, libra_running_sum); + } /** * @brief Given number of univariate polynomials and the number of their evaluations meant to be hidden, this method * produces a vector of univariate polynomials of length Flavor::BATCHED_RELATION_PARTIAL_LENGTH with * independent uniformly random coefficients. * */ - static std::vector> generate_libra_univariates(const size_t number_of_polynomials) + static std::vector> generate_libra_univariates(const size_t number_of_polynomials, + const size_t univariate_length) { std::vector> libra_full_polynomials(number_of_polynomials); for (auto& libra_polynomial : libra_full_polynomials) { - libra_polynomial = Polynomial::random(LIBRA_UNIVARIATES_LENGTH); + libra_polynomial = Polynomial::random(univariate_length); }; return libra_full_polynomials; }; @@ -116,7 +142,7 @@ template struct ZKSumcheckData { scaling_factor *= one_half; for (auto& univariate : libra_univariates) { - total_sum += univariate.evaluate(FF(0)) + univariate.evaluate(FF(1)); + total_sum += univariate.at(0) + univariate.evaluate(FF(1)); scaling_factor *= 2; } total_sum *= scaling_factor; @@ -146,7 +172,7 @@ 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].evaluate(FF(0)) - libra_univariates[0].evaluate(FF(1)); + libra_running_sum += -libra_univariates[0].at(0) - libra_univariates[0].evaluate(FF(1)); libra_running_sum *= one_half; } @@ -214,6 +240,62 @@ template struct ZKSumcheckData { libra_concatenated_monomial_form.at(SUBGROUP_SIZE + idx) += masking_scalars.value_at(idx); } } + + /** + * @brief Upon receiving the challenge \f$u_i\f$, the prover updates Libra data. If \f$ i < d-1\f$ + + - update the table of Libra univariates by multiplying every term by \f$1/2\f$. + - computes the value \f$2^{d-i - 2} \cdot \texttt{libra_challenge} \cdot g_0(u_0)\f$ applying \ref + bb::Univariate::evaluate "evaluate" method to the first univariate in the table \f$\texttt{libra_univariates}\f$ + - places the value \f$ g_0(u_0)\f$ to the vector \f$ \texttt{libra_evaluations}\f$ + - update the running sum + \f{align}{ + \texttt{libra_running_sum} \gets 2^{d-i-2} \cdot \texttt{libra_challenge} \cdot g_0(u_0) + 2^{-1} + \cdot \left( \texttt{libra_running_sum} - (\texttt{libra_univariates}_{i+1}(0) + + \texttt{libra_univariates}_{i+1}(1)) \right) \f} If \f$ i = d-1\f$ + - compute the value \f$ g_{d-1}(u_{d-1})\f$ applying \ref bb::Univariate::evaluate "evaluate" method to the + last univariate in the table \f$\texttt{libra_univariates}\f$ and dividing the result by \f$ + \texttt{libra_challenge} \f$. + - update the table of Libra univariates by multiplying every term by \f$\texttt{libra_challenge}^{-1}\f$. + @todo Refactor once the Libra univariates are extracted from the Proving Key. Then the prover does not need to + update the first round_idx - 1 univariates and could release the memory. Also, use batch_invert / reduce + the number of divisions by 2. + * @param libra_univariates + * @param round_challenge + * @param round_idx + * @param libra_running_sum + * @param libra_evaluations + */ + void update_zk_sumcheck_data(const FF round_challenge, const 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 < this->log_circuit_size - 1) { + for (auto& univariate : this->libra_univariates) { + univariate *= two_inv; + }; + // compute the evaluation \f$ \rho \cdot 2^{d-2-i} \çdot g_i(u_i) \f$ + const FF libra_evaluation = this->libra_univariates[round_idx].evaluate(round_challenge); + const auto& next_libra_univariate = this->libra_univariates[round_idx + 1]; + // update the running sum by adding g_i(u_i) and subtracting (g_i(0) + g_i(1)) + this->libra_running_sum += -next_libra_univariate.at(0) - next_libra_univariate.evaluate(FF(1)); + this->libra_running_sum *= two_inv; + + this->libra_running_sum += libra_evaluation; + this->libra_scaling_factor *= two_inv; + + this->libra_evaluations.emplace_back(libra_evaluation / this->libra_scaling_factor); + } else { + // compute the evaluation of the last Libra univariate at the challenge u_{d-1} + const FF libra_evaluation = + this->libra_univariates[round_idx].evaluate(round_challenge) / this->libra_scaling_factor; + // place the evalution into the vector of Libra evaluations + this->libra_evaluations.emplace_back(libra_evaluation); + for (auto univariate : this->libra_univariates) { + univariate *= FF(1) / this->libra_challenge; + } + }; + } }; } // 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 1774a98f6f4..ee6be78d732 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp @@ -154,6 +154,8 @@ void TranslatorProver::execute_pcs_rounds() key->proving_key->commitment_key, transcript, 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_verifier.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp index 4cff080cf23..c84942c3aec 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_verifier.cpp @@ -102,11 +102,10 @@ bool TranslatorVerifier::verify_proof(const HonkProof& proof) 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); + auto sumcheck_output = sumcheck.verify(relation_parameters, alpha, gate_challenges); // If Sumcheck did not verify, return false - if (sumcheck_verified.has_value() && !sumcheck_verified.value()) { + if (!sumcheck_output.verified) { return false; } @@ -119,18 +118,20 @@ bool TranslatorVerifier::verify_proof(const HonkProof& proof) Shplemini::compute_batch_opening_claim(circuit_size, commitments.get_unshifted_without_concatenated(), commitments.get_to_be_shifted(), - claimed_evaluations.get_unshifted_without_concatenated(), - claimed_evaluations.get_shifted(), - multivariate_challenge, + sumcheck_output.claimed_evaluations.get_unshifted_without_concatenated(), + sumcheck_output.claimed_evaluations.get_shifted(), + sumcheck_output.challenge, Commitment::one(), transcript, Flavor::REPEATED_COMMITMENTS, Flavor::HasZK, &consistency_checked, libra_commitments, - libra_evaluation, + sumcheck_output.claimed_libra_evaluation, + {}, + {}, commitments.get_groups_to_be_concatenated(), - claimed_evaluations.get_concatenated()); + sumcheck_output.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]); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp index 21065dd96e8..d75dbdd0e8f 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/decider_verifier.cpp @@ -60,7 +60,7 @@ template bool DeciderVerifier_::verify() } // If Sumcheck did not verify, return false - if (sumcheck_output.verified.has_value() && !sumcheck_output.verified.value()) { + if (!sumcheck_output.verified) { info("Sumcheck verification failed."); return false; } @@ -81,7 +81,7 @@ template bool DeciderVerifier_::verify() sumcheck_output.claimed_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 && consistency_checked; + return sumcheck_output.verified && verified && consistency_checked; } template class DeciderVerifier_; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/sumcheck.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/sumcheck.test.cpp index 5a84fb27069..69bd748e319 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/sumcheck.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/sumcheck.test.cpp @@ -189,7 +189,7 @@ TEST_F(SumcheckTestsRealCircuit, Ultra) auto verifier_output = sumcheck_verifier.verify(decider_pk->relation_parameters, verifier_alphas, verifier_gate_challenges); - auto verified = verifier_output.verified.value(); + auto verified = verifier_output.verified; ASSERT_TRUE(verified); } diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/generated/recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/generated/recursive_verifier.cpp index d1766b839b1..dd40f449baf 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/generated/recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/generated/recursive_verifier.cpp @@ -120,7 +120,7 @@ AvmRecursiveVerifier_::AggregationObject AvmRecursiveVerifier_:: // when called over a "circuit field" types. SumcheckOutput output = sumcheck.verify(relation_parameters, alpha, gate_challenges); - vinfo("verified sumcheck: ", (output.verified.has_value() && output.verified.value())); + vinfo("verified sumcheck: ", (output.verified)); // Public columns evaluation checks std::vector mle_challenge(output.challenge.begin(), diff --git a/barretenberg/cpp/src/barretenberg/vm/avm/generated/verifier.cpp b/barretenberg/cpp/src/barretenberg/vm/avm/generated/verifier.cpp index a9d581a53e0..92dbb2175d5 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm/generated/verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm/generated/verifier.cpp @@ -88,7 +88,7 @@ bool AvmVerifier::verify_proof(const HonkProof& proof, const std::vector output = sumcheck.verify(relation_parameters, alpha, gate_challenges); // If Sumcheck did not verify, return false - if (!output.verified.has_value() || !output.verified.value()) { + if (!output.verified) { vinfo("Sumcheck verification failed"); return false; } diff --git a/barretenberg/cpp/src/barretenberg/vm2/generated/verifier.cpp b/barretenberg/cpp/src/barretenberg/vm2/generated/verifier.cpp index 717071f3c8f..ce863f34dd8 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/generated/verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/generated/verifier.cpp @@ -88,7 +88,7 @@ bool AvmVerifier::verify_proof(const HonkProof& proof, const std::vector output = sumcheck.verify(relation_parameters, alpha, gate_challenges); // If Sumcheck did not verify, return false - if (!output.verified.has_value() || !output.verified.value()) { + if (!output.verified) { vinfo("Sumcheck verification failed"); return false; } diff --git a/bb-pilcom/bb-pil-backend/templates/recursive_verifier.cpp.hbs b/bb-pilcom/bb-pil-backend/templates/recursive_verifier.cpp.hbs index 6e8f8b35829..737bad86df6 100644 --- a/bb-pilcom/bb-pil-backend/templates/recursive_verifier.cpp.hbs +++ b/bb-pilcom/bb-pil-backend/templates/recursive_verifier.cpp.hbs @@ -120,7 +120,7 @@ AvmRecursiveVerifier_::AggregationObject AvmRecursiveVerifier_:: // when called over a "circuit field" types. SumcheckOutput output = sumcheck.verify(relation_parameters, alpha, gate_challenges); - vinfo("verified sumcheck: ", (output.verified.has_value() && output.verified.value())); + vinfo("verified sumcheck: ", (output.verified)); // Public columns evaluation checks std::vector mle_challenge(output.challenge.begin(), diff --git a/bb-pilcom/bb-pil-backend/templates/verifier.cpp.hbs b/bb-pilcom/bb-pil-backend/templates/verifier.cpp.hbs index 32edcc25983..3abad919132 100644 --- a/bb-pilcom/bb-pil-backend/templates/verifier.cpp.hbs +++ b/bb-pilcom/bb-pil-backend/templates/verifier.cpp.hbs @@ -88,7 +88,7 @@ bool AvmVerifier::verify_proof(const HonkProof& proof, const std::vector output = sumcheck.verify(relation_parameters, alpha, gate_challenges); // If Sumcheck did not verify, return false - if (!output.verified.has_value() || !output.verified.value()) { + if (!output.verified) { vinfo("Sumcheck verification failed"); return false; }