From 33502e05667b5a7d295b9a19d9bab07ae9869f88 Mon Sep 17 00:00:00 2001 From: iakovenkos Date: Fri, 23 Aug 2024 16:50:56 +0000 Subject: [PATCH] shplonk and gemini refactored --- .../commitment_schemes/gemini/gemini.cpp | 25 +- .../commitment_schemes/gemini/gemini.hpp | 175 +++---------- .../commitment_schemes/gemini/gemini.test.cpp | 239 ------------------ .../commitment_schemes/ipa/ipa.test.cpp | 43 ++-- .../commitment_schemes/kzg/kzg.test.cpp | 47 ++-- .../commitment_schemes/shplonk/shplonk.hpp | 211 ++++++++++++++-- 6 files changed, 292 insertions(+), 448 deletions(-) delete mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp index 4422abdee0d6..ec3054bafcf1 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp @@ -1,4 +1,3 @@ - #include "gemini.hpp" #include "barretenberg/common/thread.hpp" @@ -86,7 +85,6 @@ std::vector::Polynomial> GeminiProver_::com // A_l_fold = Aₗ₊₁(X) = (1-uₗ)⋅even(Aₗ)(X) + uₗ⋅odd(Aₗ)(X) gemini_polynomials.emplace_back(Polynomial(n_l)); } - // A_l = Aₗ(X) is the polynomial being folded // in the first iteration, we take the batched polynomial // in the next iteration, it is the previously folded one @@ -116,8 +114,10 @@ std::vector::Polynomial> GeminiProver_::com // fold(Aₗ)[j] = (1-uₗ)⋅even(Aₗ)[j] + uₗ⋅odd(Aₗ)[j] // = (1-uₗ)⋅Aₗ[2j] + uₗ⋅Aₗ[2j+1] // = Aₗ₊₁[j] - A_l_fold[j] = A_l[j << 1] + u_l * (A_l[(j << 1) + 1] - A_l[j << 1]); - } + auto idx = static_cast(j); + auto idx_to_2 = static_cast(j << 1); + A_l_fold[idx] = A_l[idx_to_2] + u_l * (A_l[idx_to_2 + 1] - A_l[idx_to_2]); + }; }); // set Aₗ₊₁ = Aₗ for the next iteration A_l = A_l_fold; @@ -141,7 +141,7 @@ std::vector::Polynomial> GeminiProver_::com * @param r_challenge univariate opening challenge */ template -GeminiProverOutput GeminiProver_::compute_fold_polynomial_evaluations( +std::vector> GeminiProver_::compute_fold_polynomial_evaluations( std::span mle_opening_point, std::vector&& gemini_polynomials, const Fr& r_challenge) { const size_t num_variables = mle_opening_point.size(); // m @@ -183,9 +183,14 @@ GeminiProverOutput GeminiProver_::compute_fold_polynomial_evaluati OpeningPair{ -r_squares[l], gemini_polynomials[l + 1].evaluate(-r_squares[l]) }); } - return { fold_poly_opening_pairs, std::move(gemini_polynomials) }; + std::vector> result; + result.reserve(gemini_polynomials.size()); + for (size_t i = 0; i < gemini_polynomials.size(); ++i) { + result.push_back(ProverOpeningClaim{ .polynomial = std::move(gemini_polynomials[i]), + .opening_pair = std::move(fold_poly_opening_pairs[i]) }); + } + return result; }; - -template class GeminiProver_; -template class GeminiProver_; -}; // namespace bb +template class GeminiProver_; +template class GeminiProver_; +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp index 23142078def3..fba08daadb15 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp @@ -1,9 +1,7 @@ #pragma once - #include "../claim.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/transcript/transcript.hpp" - #include /** @@ -58,8 +56,8 @@ namespace bb { * @tparam Curve CommitmentScheme parameters */ template struct GeminiProverOutput { + std::vector> witnesses; std::vector> opening_pairs; - std::vector> witnesses; }; namespace gemini { @@ -108,11 +106,23 @@ template class GeminiProver_ { Polynomial&& batched_unshifted, Polynomial&& batched_to_be_shifted); - static GeminiProverOutput compute_fold_polynomial_evaluations(std::span mle_opening_point, - std::vector&& gemini_polynomials, - const Fr& r_challenge); + static std::vector> compute_fold_polynomial_evaluations( + std::span mle_opening_point, std::vector&& gemini_polynomials, const Fr& r_challenge); }; // namespace bb +/*! + * @brief Gemini Verifier designed to run in tandem with \ref bb::ShplonkVerifier_< Curve >::verify_gemini "verify +gemini" method of the Shplonk Verifier. + * + * @details This verifier obtains the commitments to Prover's folded polynomials \f$ A_1,\ldots, A_{d-1}\f$, where \f$ d += \text{log_circuit_size}\f$, gets the opening challenge \f$ r \f$, and receives the claimed evaluations \f$ A_1(-r), +\ldots, A_{d-1} (-r^{2^{d-1}})\f$. The output is a tuple consisting of a vector of the powers of the gemini challenge +\f$(r, r^2, \ldots, r^{2^{d-1}})\f$, the claimed evaluations of \f$ A_i \f$ at corresponding points, and a vector of +commitments \f$(A_1,\ldots, A_{d-1})\f$. The only computation performed at this stage is the computation of the vector +of powers of \f$ r \f$. + * + * @tparam Curve + */ template class GeminiVerifier_ { using Fr = typename Curve::ScalarField; using GroupElement = typename Curve::Element; @@ -120,149 +130,38 @@ template class GeminiVerifier_ { public: /** - * @brief Returns univariate opening claims for the Fold polynomials to be checked later + * @brief Returns a tuple of vectors \f$ (r, r^2, \ldots, r^{2^{d-1}} )\f$ , \f$ \left( A_0(-r), A_1(-r^2), \ldots, + * A_{d-1}(-r^{2^{d-1}}) \right)\f$, \f$ ( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{d-1} ) \f$ * - * @param mle_opening_point the MLE evaluation point u - * @param batched_evaluation batched evaluation from multivariate evals at the point u - * @param batched_f batched commitment to unshifted polynomials - * @param batched_g batched commitment to to-be-shifted polynomials - * @param proof commitments to the m-1 folded polynomials, and alleged evaluations. - * @param transcript - * @return Fold polynomial opening claims: (r, A₀(r), C₀₊), (-r, A₀(-r), C₀₋), and - * (Cⱼ, Aⱼ(-r^{2ʲ}), -r^{2}), j = [1, ..., m-1] + * @param log_circuit_size MLE evaluation point u + * @param r Gemini evaluation challenge. + * @param transcript verifier's transcript */ - static std::vector> reduce_verification(std::span mle_opening_point, /* u */ - const Fr batched_evaluation, /* all */ - GroupElement& batched_f, /* unshifted */ - GroupElement& batched_g, /* to-be-shifted */ - auto& transcript) + static std::tuple, std::vector, std::vector> reduce_efficient_verification( + const size_t log_circuit_size, // log circuit size + Fr& r, // gemini challenge + auto& transcript) { - const size_t num_variables = mle_opening_point.size(); - - // Get polynomials Fold_i, i = 1,...,m-1 from transcript + // Get commitments to polynomials A_i, i = 1,...,m-1 from transcript std::vector commitments; - commitments.reserve(num_variables - 1); - for (size_t i = 0; i < num_variables - 1; ++i) { + commitments.reserve(log_circuit_size - 1); + for (size_t i = 0; i < log_circuit_size - 1; ++i) { auto commitment = transcript->template receive_from_prover("Gemini:FOLD_" + std::to_string(i + 1)); commitments.emplace_back(commitment); } - - // compute vector of powers of random evaluation point r - const Fr r = transcript->template get_challenge("Gemini:r"); - std::vector r_squares = gemini::squares_of_r(r, num_variables); - - // Get evaluations a_i, i = 0,...,m-1 from transcript + // get the Gemini challenge r and compute its powers + r = transcript->template get_challenge("Gemini:r"); + std::vector r_squares = gemini::squares_of_r(r, log_circuit_size); + // get evaluations a_i, i = 0,...,m-1 from transcript std::vector evaluations; - evaluations.reserve(num_variables); - for (size_t i = 0; i < num_variables; ++i) { + evaluations.reserve(log_circuit_size); + // populate the output tuple + for (size_t i = 0; i < log_circuit_size; ++i) { auto eval = transcript->template receive_from_prover("Gemini:a_" + std::to_string(i)); evaluations.emplace_back(eval); } - - // Compute evaluation A₀(r) - auto a_0_pos = compute_eval_pos(batched_evaluation, mle_opening_point, r_squares, evaluations); - - // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] + r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] - // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] - r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] - auto [c0_r_pos, c0_r_neg] = compute_simulated_commitments(batched_f, batched_g, r); - - std::vector> fold_polynomial_opening_claims; - fold_polynomial_opening_claims.reserve(num_variables + 1); - - // ( [A₀₊], r, A₀(r) ) - fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { r, a_0_pos }, c0_r_pos }); - // ( [A₀₋], -r, A₀(-r) ) - fold_polynomial_opening_claims.emplace_back(OpeningClaim{ { -r, evaluations[0] }, c0_r_neg }); - for (size_t l = 0; l < num_variables - 1; ++l) { - // ([A₀₋], −r^{2ˡ}, Aₗ(−r^{2ˡ}) ) - fold_polynomial_opening_claims.emplace_back( - OpeningClaim{ { -r_squares[l + 1], evaluations[l + 1] }, commitments[l] }); - } - - return fold_polynomial_opening_claims; - } - - private: - /** - * @brief Compute the expected evaluation of the univariate commitment to the batched polynomial. - * - * @param batched_mle_eval The evaluation of the folded polynomials - * @param mle_vars MLE opening point u - * @param r_squares squares of r, r², ..., r^{2ᵐ⁻¹} - * @param fold_polynomial_evals series of Aᵢ₋₁(−r^{2ⁱ⁻¹}) - * @return evaluation A₀(r) - */ - static Fr compute_eval_pos(const Fr batched_mle_eval, - std::span mle_vars, - std::span r_squares, - std::span fold_polynomial_evals) - { - const size_t num_variables = mle_vars.size(); - - const auto& evals = fold_polynomial_evals; - - // Initialize eval_pos with batched MLE eval v = ∑ⱼ ρʲ vⱼ + ∑ⱼ ρᵏ⁺ʲ v↺ⱼ - Fr eval_pos = batched_mle_eval; - for (size_t l = num_variables; l != 0; --l) { - const Fr r = r_squares[l - 1]; // = rₗ₋₁ = r^{2ˡ⁻¹} - const Fr eval_neg = evals[l - 1]; // = Aₗ₋₁(−r^{2ˡ⁻¹}) - const Fr u = mle_vars[l - 1]; // = uₗ₋₁ - - // The folding property ensures that - // Aₗ₋₁(r^{2ˡ⁻¹}) + Aₗ₋₁(−r^{2ˡ⁻¹}) Aₗ₋₁(r^{2ˡ⁻¹}) - Aₗ₋₁(−r^{2ˡ⁻¹}) - // Aₗ(r^{2ˡ}) = (1-uₗ₋₁) ----------------------------- + uₗ₋₁ ----------------------------- - // 2 2r^{2ˡ⁻¹} - // We solve the above equation in Aₗ₋₁(r^{2ˡ⁻¹}), using the previously computed Aₗ(r^{2ˡ}) in eval_pos - // and using Aₗ₋₁(−r^{2ˡ⁻¹}) sent by the prover in the proof. - eval_pos = ((r * eval_pos * 2) - eval_neg * (r * (Fr(1) - u) - u)) / (r * (Fr(1) - u) + u); - } - - return eval_pos; // return A₀(r) - } - - /** - * @brief Computes two commitments to A₀ partially evaluated in r and -r. - * - * @param batched_f batched commitment to non-shifted polynomials - * @param batched_g batched commitment to to-be-shifted polynomials - * @param r evaluation point at which we have partially evaluated A₀ at r and -r. - * @return std::pair c0_r_pos, c0_r_neg - */ - static std::pair compute_simulated_commitments(GroupElement& batched_f, - GroupElement& batched_g, - Fr r) - { - // C₀ᵣ₊ = [F] + r⁻¹⋅[G] - GroupElement C0_r_pos; - // C₀ᵣ₋ = [F] - r⁻¹⋅[G] - GroupElement C0_r_neg; - Fr r_inv = r.invert(); // r⁻¹ - - // If in a recursive setting, perform a batch mul. Otherwise, accumulate directly. - // TODO(#673): The following if-else represents the stldib/native code paths. Once the "native" verifier is - // achieved through a builder Simulator, the stdlib codepath should become the only codepath. - if constexpr (Curve::is_stdlib_type) { - std::vector commitments = { batched_f, batched_g }; - auto builder = r.get_context(); - auto one = Fr(builder, 1); - // TODO(#707): these batch muls include the use of 1 as a scalar. This is handled appropriately as a non-mul - // (add-accumulate) in the goblin batch_mul but is done inefficiently as a scalar mul in the conventional - // emulated batch mul. - C0_r_pos = GroupElement::batch_mul(commitments, { one, r_inv }); - C0_r_neg = GroupElement::batch_mul(commitments, { one, -r_inv }); - } else { - C0_r_pos = batched_f; - C0_r_neg = batched_f; - if (!batched_g.is_point_at_infinity()) { - batched_g = batched_g * r_inv; - C0_r_pos += batched_g; - C0_r_neg -= batched_g; - } - } - - return { C0_r_pos, C0_r_neg }; + return std::make_tuple(r_squares, evaluations, commitments); } }; - -} // namespace bb +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp deleted file mode 100644 index 7e378bba2501..000000000000 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#include "gemini.hpp" - -#include "../commitment_key.test.hpp" -#include "barretenberg/polynomials/polynomial.hpp" -#include "barretenberg/transcript/transcript.hpp" -#include -#include -#include - -using namespace bb; - -template class GeminiTest : public CommitmentTest { - using GeminiProver = GeminiProver_; - using GeminiVerifier = GeminiVerifier_; - using Fr = typename Curve::ScalarField; - using GroupElement = typename Curve::Element; - using Polynomial = typename bb::Polynomial; - - public: - void execute_gemini_and_verify_claims(size_t log_n, - std::vector multilinear_evaluation_point, - std::vector multilinear_evaluations, - std::vector> multilinear_polynomials, - std::vector> multilinear_polynomials_to_be_shifted, - std::vector multilinear_commitments, - std::vector multilinear_commitments_to_be_shifted) - { - auto prover_transcript = NativeTranscript::prover_init_empty(); - - const Fr rho = Fr::random_element(); - - std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); - - // Compute batched multivariate evaluation - Fr batched_evaluation = Fr::zero(); - for (size_t i = 0; i < multilinear_evaluations.size(); ++i) { - batched_evaluation += multilinear_evaluations[i] * rhos[i]; - } - - Polynomial batched_unshifted(1 << log_n); - Polynomial batched_to_be_shifted(1 << log_n); - GroupElement batched_commitment_unshifted = GroupElement::zero(); - GroupElement batched_commitment_to_be_shifted = GroupElement::zero(); - const size_t num_unshifted = multilinear_polynomials.size(); - const size_t num_shifted = multilinear_polynomials_to_be_shifted.size(); - for (size_t i = 0; i < num_unshifted; ++i) { - batched_unshifted.add_scaled(multilinear_polynomials[i], rhos[i]); - batched_commitment_unshifted += multilinear_commitments[i] * rhos[i]; - } - for (size_t i = 0; i < num_shifted; ++i) { - size_t rho_idx = num_unshifted + i; - batched_to_be_shifted.add_scaled(multilinear_polynomials_to_be_shifted[i], rhos[rho_idx]); - batched_commitment_to_be_shifted += multilinear_commitments_to_be_shifted[i] * rhos[rho_idx]; - } - - // Compute: - // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 - // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto gemini_polynomials = GeminiProver::compute_gemini_polynomials( - multilinear_evaluation_point, std::move(batched_unshifted), std::move(batched_to_be_shifted)); - - for (size_t l = 0; l < log_n - 1; ++l) { - std::string label = "FOLD_" + std::to_string(l + 1); - auto commitment = this->ck()->commit(gemini_polynomials[l + 2]); - prover_transcript->send_to_verifier(label, commitment); - } - - const Fr r_challenge = prover_transcript->get_challenge("Gemini:r"); - - auto prover_output = GeminiProver::compute_fold_polynomial_evaluations( - multilinear_evaluation_point, std::move(gemini_polynomials), r_challenge); - - for (size_t l = 0; l < log_n; ++l) { - std::string label = "Gemini:a_" + std::to_string(l); - const auto& evaluation = prover_output.opening_pairs[l + 1].evaluation; - prover_transcript->send_to_verifier(label, evaluation); - } - - // Check that the Fold polynomials have been evaluated correctly in the prover - this->verify_batch_opening_pair(prover_output.opening_pairs, prover_output.witnesses); - - auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); - - // Compute: - // - Single opening pair: {r, \hat{a}_0} - // - 2 partially evaluated Fold polynomial commitments [Fold_{r}^(0)] and [Fold_{-r}^(0)] - // Aggregate: d+1 opening pairs and d+1 Fold poly commitments into verifier claim - auto verifier_claim = GeminiVerifier::reduce_verification(multilinear_evaluation_point, - batched_evaluation, - batched_commitment_unshifted, - batched_commitment_to_be_shifted, - verifier_transcript); - - // Check equality of the opening pairs computed by prover and verifier - for (size_t i = 0; i < (log_n + 1); ++i) { - ASSERT_EQ(prover_output.opening_pairs[i], verifier_claim[i].opening_pair); - } - - // Explicitly verify the claims computed by the verfier - this->verify_batch_opening_claim(verifier_claim, prover_output.witnesses); - } -}; - -using ParamsTypes = ::testing::Types; -TYPED_TEST_SUITE(GeminiTest, ParamsTypes); - -TYPED_TEST(GeminiTest, Single) -{ - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - - const size_t n = 16; - const size_t log_n = 4; - - auto u = this->random_evaluation_point(log_n); - auto poly = this->random_polynomial(n); - auto commitment = this->commit(poly); - auto eval = poly.evaluate_mle(u); - - // Collect multilinear polynomials evaluations, and commitments for input to prover/verifier - std::vector multilinear_evaluations = { eval }; - std::vector> multilinear_polynomials = { poly }; - std::vector> multilinear_polynomials_to_be_shifted = {}; - std::vector multilinear_commitments = { commitment }; - std::vector multilinear_commitments_to_be_shifted = {}; - - this->execute_gemini_and_verify_claims(log_n, - u, - multilinear_evaluations, - multilinear_polynomials, - multilinear_polynomials_to_be_shifted, - multilinear_commitments, - multilinear_commitments_to_be_shifted); -} - -TYPED_TEST(GeminiTest, SingleShift) -{ - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - - const size_t n = 16; - const size_t log_n = 4; - - auto u = this->random_evaluation_point(log_n); - - // shiftable polynomial must have 0 as last coefficient - auto poly = this->random_polynomial(n); - poly[0] = Fr::zero(); - - auto commitment = this->commit(poly); - auto eval_shift = poly.evaluate_mle(u, true); - - // Collect multilinear polynomials evaluations, and commitments for input to prover/verifier - std::vector multilinear_evaluations = { eval_shift }; - std::vector> multilinear_polynomials = {}; - std::vector> multilinear_polynomials_to_be_shifted = { poly }; - std::vector multilinear_commitments = {}; - std::vector multilinear_commitments_to_be_shifted = { commitment }; - - this->execute_gemini_and_verify_claims(log_n, - u, - multilinear_evaluations, - multilinear_polynomials, - multilinear_polynomials_to_be_shifted, - multilinear_commitments, - multilinear_commitments_to_be_shifted); -} - -TYPED_TEST(GeminiTest, Double) -{ - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - - const size_t n = 16; - const size_t log_n = 4; - - auto u = this->random_evaluation_point(log_n); - - auto poly1 = this->random_polynomial(n); - auto poly2 = this->random_polynomial(n); - - auto commitment1 = this->commit(poly1); - auto commitment2 = this->commit(poly2); - - auto eval1 = poly1.evaluate_mle(u); - auto eval2 = poly2.evaluate_mle(u); - - // Collect multilinear polynomials evaluations, and commitments for input to prover/verifier - std::vector multilinear_evaluations = { eval1, eval2 }; - std::vector> multilinear_polynomials = { poly1, poly2 }; - std::vector> multilinear_polynomials_to_be_shifted = {}; - std::vector multilinear_commitments = { commitment1, commitment2 }; - std::vector multilinear_commitments_to_be_shifted = {}; - - this->execute_gemini_and_verify_claims(log_n, - u, - multilinear_evaluations, - multilinear_polynomials, - multilinear_polynomials_to_be_shifted, - multilinear_commitments, - multilinear_commitments_to_be_shifted); -} - -TYPED_TEST(GeminiTest, DoubleWithShift) -{ - using Fr = typename TypeParam::ScalarField; - using GroupElement = typename TypeParam::Element; - - const size_t n = 16; - const size_t log_n = 4; - - auto u = this->random_evaluation_point(log_n); - - auto poly1 = this->random_polynomial(n); - auto poly2 = this->random_polynomial(n); - poly2[0] = Fr::zero(); // necessary for polynomial to be 'shiftable' - - auto commitment1 = this->commit(poly1); - auto commitment2 = this->commit(poly2); - - auto eval1 = poly1.evaluate_mle(u); - auto eval2 = poly2.evaluate_mle(u); - auto eval2_shift = poly2.evaluate_mle(u, true); - - // Collect multilinear polynomials evaluations, and commitments for input to prover/verifier - std::vector multilinear_evaluations = { eval1, eval2, eval2_shift }; - std::vector> multilinear_polynomials = { poly1, poly2 }; - std::vector> multilinear_polynomials_to_be_shifted = { poly2 }; - std::vector multilinear_commitments = { commitment1, commitment2 }; - std::vector multilinear_commitments_to_be_shifted = { commitment2 }; - - this->execute_gemini_and_verify_claims(log_n, - u, - multilinear_evaluations, - multilinear_polynomials, - multilinear_polynomials_to_be_shifted, - multilinear_commitments, - multilinear_commitments_to_be_shifted); -} diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp index 97c701075cc2..33ce37238178 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -1,4 +1,3 @@ - #include "../gemini/gemini.hpp" #include "../shplonk/shplonk.hpp" #include "./mock_transcript.hpp" @@ -238,6 +237,7 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift) using ShplonkVerifier = ShplonkVerifier_; using GeminiProver = GeminiProver_; using GeminiVerifier = GeminiVerifier_; + using Commitment = typename Curve::AffineElement; const size_t n = 8; const size_t log_n = 3; @@ -246,13 +246,15 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift) // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random // point. - const auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' + auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' auto poly1 = this->random_polynomial(n); auto poly2 = this->random_polynomial(n); poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used - GroupElement commitment1 = this->commit(poly1); - GroupElement commitment2 = this->commit(poly2); + Commitment commitment1 = this->commit(poly1); + Commitment commitment2 = this->commit(poly2); + std::vector unshifted_commitments = { commitment1, commitment2 }; + std::vector shifted_commitments = { commitment2 }; auto eval1 = poly1.evaluate_mle(mle_opening_point); auto eval2 = poly2.evaluate_mle(mle_opening_point); @@ -291,33 +293,38 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift) const Fr r_challenge = prover_transcript->template get_challenge("Gemini:r"); - const auto [gemini_opening_pairs, gemini_witnesses] = GeminiProver::compute_fold_polynomial_evaluations( + const auto gemini_output = GeminiProver::compute_fold_polynomial_evaluations( mle_opening_point, std::move(gemini_polynomials), r_challenge); std::vector> opening_claims; for (size_t l = 0; l < log_n; ++l) { std::string label = "Gemini:a_" + std::to_string(l); - const auto& evaluation = gemini_opening_pairs[l + 1].evaluation; + const auto& evaluation = gemini_output[l + 1].opening_pair.evaluation; prover_transcript->send_to_verifier(label, evaluation); - opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]); + opening_claims.emplace_back(gemini_output[l].polynomial, gemini_output[l].opening_pair); } - opening_claims.emplace_back(gemini_witnesses[log_n], gemini_opening_pairs[log_n]); + opening_claims.emplace_back(gemini_output[log_n].polynomial, gemini_output[log_n].opening_pair); const auto opening_claim = ShplonkProver::prove(this->ck(), opening_claims, prover_transcript); IPA::compute_opening_proof(this->ck(), opening_claim, prover_transcript); auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); - - auto gemini_verifier_claim = GeminiVerifier::reduce_verification(mle_opening_point, - batched_evaluation, - batched_commitment_unshifted, - batched_commitment_to_be_shifted, - verifier_transcript); - - const auto shplonk_verifier_claim = - ShplonkVerifier::reduce_verification(this->vk()->get_g1_identity(), gemini_verifier_claim, verifier_transcript); + Fr gemini_challenge; + + auto gemini_verifier_claim = + GeminiVerifier::reduce_efficient_verification(mle_opening_point.size(), gemini_challenge, verifier_transcript); + + const auto shplonk_verifier_claim = ShplonkVerifier::verify_gemini(this->vk()->get_g1_identity(), + RefVector(unshifted_commitments), + RefVector(shifted_commitments), + multilinear_evaluations, + mle_opening_point, + rho, + gemini_challenge, + gemini_verifier_claim, + verifier_transcript); auto result = IPA::reduce_verify(this->vk(), shplonk_verifier_claim, verifier_transcript); EXPECT_EQ(result, true); -} +} \ No newline at end of file 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 42cf6b9f8fb7..db86b6d3dae1 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp @@ -63,6 +63,7 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) using KZG = KZG; using Fr = typename TypeParam::ScalarField; using GroupElement = typename TypeParam::Element; + using Commitment = typename TypeParam::AffineElement; using Polynomial = typename bb::Polynomial; const size_t n = 16; @@ -72,13 +73,18 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random // point. - const auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' + auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' auto poly1 = this->random_polynomial(n); auto poly2 = this->random_polynomial(n); poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used - GroupElement commitment1 = this->commit(poly1); - GroupElement commitment2 = this->commit(poly2); + Commitment commitment1 = this->commit(poly1); + Commitment commitment2 = this->commit(poly2); + + std::vector unshifted_commitments = { commitment1, commitment2 }; + std::vector shifted_commitments = { commitment2 }; + // RefSpan f_commitments(unshifted_commitments); + // RefSpan g_commitments(shifted_commitments); auto eval1 = poly1.evaluate_mle(mle_opening_point); auto eval2 = poly2.evaluate_mle(mle_opening_point); @@ -126,17 +132,17 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) const Fr r_challenge = prover_transcript->template get_challenge("Gemini:r"); - const auto [gemini_opening_pairs, gemini_witnesses] = GeminiProver::compute_fold_polynomial_evaluations( + const auto gemini_output = GeminiProver::compute_fold_polynomial_evaluations( mle_opening_point, std::move(gemini_polynomials), r_challenge); std::vector> opening_claims; for (size_t l = 0; l < log_n; ++l) { std::string label = "Gemini:a_" + std::to_string(l); - const auto& evaluation = gemini_opening_pairs[l + 1].evaluation; + const auto& evaluation = gemini_output[l + 1].opening_pair.evaluation; prover_transcript->send_to_verifier(label, evaluation); - opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]); + opening_claims.emplace_back(gemini_output[l].polynomial, gemini_output[l].opening_pair); } - opening_claims.emplace_back(gemini_witnesses[log_n], gemini_opening_pairs[log_n]); + opening_claims.emplace_back(gemini_output[log_n].polynomial, gemini_output[log_n].opening_pair); // Shplonk prover output: // - opening pair: (z_challenge, 0) @@ -150,19 +156,21 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) // Run the full verifier PCS protocol with genuine opening claims (genuine commitment, genuine evaluation) auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); + Fr gemini_challenge; // Gemini verifier output: - // - claim: d+1 commitments to Fold_{r}^(0), Fold_{-r}^(0), Fold^(l), d+1 evaluations a_0_pos, a_l, l = 0:d-1 - auto gemini_verifier_claim = GeminiVerifier::reduce_verification(mle_opening_point, - batched_evaluation, - batched_commitment_unshifted, - batched_commitment_to_be_shifted, - verifier_transcript); - - // Shplonk verifier claim: commitment [Q] - [Q_z], opening point (z_challenge, 0) - const auto shplonk_verifier_claim = - ShplonkVerifier::reduce_verification(this->vk()->get_g1_identity(), gemini_verifier_claim, verifier_transcript); - + auto gemini_verifier_claim = + GeminiVerifier::reduce_efficient_verification(mle_opening_point.size(), gemini_challenge, verifier_transcript); + + const auto shplonk_verifier_claim = ShplonkVerifier::verify_gemini(this->vk()->get_g1_identity(), + RefVector(unshifted_commitments), + RefVector(shifted_commitments), + multilinear_evaluations, + mle_opening_point, + rho, + gemini_challenge, + gemini_verifier_claim, + verifier_transcript); // KZG verifier: // aggregates inputs [Q] - [Q_z] and [W] into an 'accumulator' (can perform pairing check on result) auto pairing_points = KZG::reduce_verify(shplonk_verifier_claim, verifier_transcript); @@ -171,5 +179,4 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true); } - -} // namespace bb +} // 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 9eac7b4a48c6..3d4dc296d64a 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp @@ -139,10 +139,6 @@ template class ShplonkProver_ { } }; -/** - * @brief Shplonk Verifier - * - */ template class ShplonkVerifier_ { using Fr = typename Curve::ScalarField; using GroupElement = typename Curve::Element; @@ -165,11 +161,12 @@ template class ShplonkVerifier_ { { const size_t num_claims = claims.size(); - + // nu is a batching challenge for shplonk polynomials, \gamma in paper const Fr nu = transcript->template get_challenge("Shplonk:nu"); - + // W in paper auto Q_commitment = transcript->template receive_from_prover("Shplonk:Q"); - + // opening point to check that L(z) = 0 => L == 0 (step 4 in Shplonk), + // r here const Fr z_challenge = transcript->template get_challenge("Shplonk:z"); // [G] = [Q] - ∑ⱼ ρʲ / ( r − xⱼ )⋅[fⱼ] + G₀⋅[1] @@ -206,36 +203,27 @@ template class ShplonkVerifier_ { // Note: no need for batch inversion; emulated inversion is cheap. (just show known inverse is valid) inverse_vanishing_evals.emplace_back((z_challenge - claim.opening_pair.challenge).invert()); } - auto current_nu = Fr(1); // Note: commitments and scalars vectors used only in recursion setting for batch mul for (size_t j = 0; j < num_claims; ++j) { // (Cⱼ, xⱼ, vⱼ) const auto& [opening_pair, commitment] = claims[j]; - Fr scaling_factor = current_nu * inverse_vanishing_evals[j]; // = ρʲ / ( r − xⱼ ) - // G₀ += ρʲ / ( r − xⱼ ) ⋅ vⱼ G_commitment_constant += scaling_factor * opening_pair.evaluation; - current_nu *= nu; - // Store MSM inputs for batch mul commitments.emplace_back(commitment); scalars.emplace_back(-scaling_factor); } - commitments.emplace_back(g1_identity); scalars.emplace_back(G_commitment_constant); - // [G] += G₀⋅[1] = [G] + (∑ⱼ ρʲ ⋅ vⱼ / ( r − xⱼ ))⋅[1] G_commitment = GroupElement::batch_mul(commitments, scalars); - } else { // [G] = [Q] - ∑ⱼ ρʲ / ( r − xⱼ )⋅[fⱼ] + G₀⋅[1] // = [Q] - [∑ⱼ ρʲ ⋅ ( fⱼ(X) − vⱼ) / ( r − xⱼ )] G_commitment = Q_commitment; - // Compute {ẑⱼ(r)}ⱼ , where ẑⱼ(r) = 1/zⱼ(r) = 1/(r - xⱼ) std::vector inverse_vanishing_evals; inverse_vanishing_evals.reserve(num_claims); @@ -243,30 +231,207 @@ template class ShplonkVerifier_ { inverse_vanishing_evals.emplace_back(z_challenge - claim.opening_pair.challenge); } Fr::batch_invert(inverse_vanishing_evals); - auto current_nu = Fr(1); // Note: commitments and scalars vectors used only in recursion setting for batch mul for (size_t j = 0; j < num_claims; ++j) { // (Cⱼ, xⱼ, vⱼ) const auto& [opening_pair, commitment] = claims[j]; - Fr scaling_factor = current_nu * inverse_vanishing_evals[j]; // = ρʲ / ( r − xⱼ ) - // G₀ += ρʲ / ( r − xⱼ ) ⋅ vⱼ G_commitment_constant += scaling_factor * opening_pair.evaluation; - // [G] -= ρʲ / ( r − xⱼ )⋅[fⱼ] G_commitment -= commitment * scaling_factor; - current_nu *= nu; } - // [G] += G₀⋅[1] = [G] + (∑ⱼ ρʲ ⋅ vⱼ / ( r − xⱼ ))⋅[1] G_commitment += g1_identity * G_commitment_constant; } + // Return opening pair (z, 0) and commitment [G] + return { { z_challenge, Fr(0) }, G_commitment }; + }; + + /** + Shplonk verifier optimized to verify gemini opening claims. + + This method receives commitments to all prover polynomials, their claimed evaluations, the sumcheck + challenge, a challenge \f$ \rho \f$ aimed to batch the commitments to prover polynomials, a challenge \f$ r \f$ for + the Gemini opening claims, and the Gemini claims. The latter is a tuple of a vector of powers of \f$ r \f$, a vector + of evaluations of Gemini fold polynomials at \f$ - r, + - r^2, \ldots, - r^{2^{d-1}} \f$ where \f$ d \f$ is the log_circuit_size, and a vector of commitments to the Gemini + fold polynomials. + + The verifier receives the challenges required in Shplonk and gradually populates the vectors of commitments and + scalars that will be multiplied at the very end. In the recursive setting, a batch_mul of size (NUM_ALL_ENTITIES + + log_circuit_size + 2) is performed. In the native setting, these operations are performed sequentially. + * + */ + static OpeningClaim verify_gemini( + Commitment g1_identity, + RefSpan f_commitments, + RefSpan g_commitments, + auto claimed_evaluations, + std::vector& multivariate_challenge, + const Fr& rho, + const Fr& gemini_r, + // Fr a_0_pos, + std::tuple, std::vector, std::vector> claims, + auto& transcript) + { + // (r, r^2, ... , r^{2^{d-1}}), where d = log circuit size + auto& r_squares = std::get<0>(claims); + // (A_0(-r), A_1(-r^2), ... , A_{d-1}(-r^{2^{d-1}})) + auto& gemini_evaluations = std::get<1>(claims); + // (com(A_1), com(A_2), ... , com(A_{d-1})) + auto& gemini_commitments = std::get<2>(claims); + const size_t num_claims = gemini_commitments.size(); + // get Shplonk batching challenge + const Fr shplonk_batching_challenge = transcript->template get_challenge("Shplonk:nu"); + // quotient commitment for the batched opening claim + auto Q_commitment = transcript->template receive_from_prover("Shplonk:Q"); + // get Shplonk opening point z, it used to check that the evaluation claims and the correctness of the batching + const Fr z_challenge = transcript->template get_challenge("Shplonk:z"); + // accumulator for scalar that will be multiplied by [1]_1 + auto constant_term_accumulator = Fr(0); + // to be populated as follows (Q, f_0,..., f_{k-1}, g_0,..., g_{m-1}, com(A_1),..., com(A_{d-1}), [1]_1) + std::vector commitments; + commitments.emplace_back(Q_commitment); + // initialize the vector of scalars for the final batch_mul + std::vector scalars; + if constexpr (Curve::is_stdlib_type) { + auto builder = shplonk_batching_challenge.get_context(); + scalars.emplace_back(Fr(builder, 1)); // Fr(1) + } else { + scalars.emplace_back(Fr(1)); + } + // compute 1/(z - r), 1/(z+r), 1/(z+r^2),..., 1/(z+r^{2^{d-1}}) + std::vector inverse_vanishing_evals; + inverse_vanishing_evals.reserve(num_claims + 2); + // place 1/(z-r) manually + inverse_vanishing_evals.emplace_back((z_challenge - gemini_r).invert()); + for (const auto& challenge_point : r_squares) { + inverse_vanishing_evals.emplace_back((z_challenge + challenge_point).invert()); + } + // the scalar corresponding to the batched unshifted prover polynomials, i-th unshifted commitment is + // multiplied by - rho^{i} * (1/(z-r) + shplonk_batching_challenge * 1/(z+r)) + Fr unshifted_scalar = inverse_vanishing_evals[0] + shplonk_batching_challenge * inverse_vanishing_evals[1]; + // the scalar corresponding to the batched shifted prover polynomials, i-th shifted commitment is + // multiplied by - rho^{i+k} * 1/r *(1/(z-r) - shplonk_batching_challenge * 1/(z+r)) + Fr shifted_scalar = + gemini_r.invert() * (inverse_vanishing_evals[0] - shplonk_batching_challenge * inverse_vanishing_evals[1]); + // place the commitments to prover polynomials in the commitments vector, compute the evaluation of the batched + // multilinear polynomial, populate the vector of scalars for the final batch mul + Fr current_batching_challenge = Fr(1); + Fr batched_evaluation = Fr(0); + size_t evaluation_idx = 0; + // handle commitments to unshifted polynomials + for (auto& unshifted_commitment : f_commitments) { + commitments.emplace_back(unshifted_commitment); + scalars.emplace_back(-unshifted_scalar * current_batching_challenge); + batched_evaluation += claimed_evaluations[evaluation_idx] * current_batching_challenge; + evaluation_idx += 1; + current_batching_challenge *= rho; + } + // handle commitments to shifted_polynomials + for (auto& shifted_commitment : g_commitments) { + commitments.emplace_back(shifted_commitment); + scalars.emplace_back(-shifted_scalar * current_batching_challenge); + batched_evaluation += claimed_evaluations[evaluation_idx] * current_batching_challenge; + evaluation_idx += 1; + current_batching_challenge *= rho; + } + // place the commitments to Gemini A_i to the vector of commitments, compute the contributions from + // A_i(-r^{2^i}) for i=1,..., d-1 to the constant term accumulator, populate scalars + current_batching_challenge = shplonk_batching_challenge * shplonk_batching_challenge; + for (size_t j = 0; j < num_claims; ++j) { + // (shplonk_batching_challenge)^(j+2) * (z + r^{2^j}) + Fr scaling_factor = current_batching_challenge * inverse_vanishing_evals[j + 2]; + // (shplonk_batching_challenge)^(j+2) * (z + r^{2^j}) * A_j(-r^{2^j}) + constant_term_accumulator += scaling_factor * gemini_evaluations[j + 1]; + current_batching_challenge *= shplonk_batching_challenge; + commitments.emplace_back(gemini_commitments[j]); + scalars.emplace_back(-scaling_factor); + } + // extract A_0(-r) + Fr a_0_neg = gemini_evaluations[0]; + // compute A_0(r) + auto a_0_pos = compute_eval_pos(batched_evaluation, multivariate_challenge, r_squares, gemini_evaluations); + // add A_0(r)/(z-r) to the constant term accumulator + constant_term_accumulator += a_0_pos * inverse_vanishing_evals[0]; + // add A_0(-r)/(z+r) to the constant term accumulator + constant_term_accumulator += a_0_neg * shplonk_batching_challenge * inverse_vanishing_evals[1]; + // finalize the vector of commitments by adding [1]_1 + commitments.emplace_back(g1_identity); + // finalize the vector of scalars + scalars.emplace_back(constant_term_accumulator); + GroupElement G_commitment; + if constexpr (Curve::is_stdlib_type) { + G_commitment = GroupElement::batch_mul(commitments, scalars, /*max_num_bits=*/0, /*with_edgecases=*/true); + } else { + G_commitment = batch_mul_native(commitments, scalars); + } // Return opening pair (z, 0) and commitment [G] return { { z_challenge, Fr(0) }, G_commitment }; }; + + /** Compute the evaluation \f$ A_0(r) = \sum \rho^i \cdot f_i + \frac{1}{r} \cdot \sum \rho^{i+k} g_i \f$. + + Initialize \f$A_{d}(r)\f$ with the batched evaluation \f$ \sum \rho^i f_i(\vec u) + \sum \rho^{i+k} g_i(\vec u)\f$. + The folding property ensures that + \f{align} + A_\ell(r^{2^\ell}) = (1 - u_{\ell-1}) \frac{A_{\ell-1}\left(r^{2^{\ell-1}}\right) + + A_{\ell-1}\left(-r^{2^{\ell-1}}\right)}{2} + u_{\ell-1} \frac{A_{\ell-1}\left(r^{2^{\ell-1}}\right) - + A_{\ell-1}\left(-r^{2^{\ell-1}}\right)}{2r^{2^{\ell-1}}} + \f} + Therefore, the verifier could recover \f$A_0(r)\f$ by solving several linear equations. + */ + static Fr compute_eval_pos(const Fr batched_evaluation, + std::span multivariate_challenge, + std::span r_squares, + std::span fold_polynomial_evals) + { + const size_t num_variables = multivariate_challenge.size(); + + const auto& evals = fold_polynomial_evals; + // initialize A_{d}(r^{2^d}) + Fr eval_pos = batched_evaluation; + // solve the sequence of linear equations to compute A_0(r) + for (size_t l = num_variables; l != 0; --l) { + const Fr r = r_squares[l - 1]; // = rₗ₋₁ = r^{2ˡ⁻¹} + const Fr eval_neg = evals[l - 1]; // = Aₗ₋₁(−r^{2ˡ⁻¹}) + const Fr u = multivariate_challenge[l - 1]; // = uₗ₋₁ + eval_pos = ((r * eval_pos * 2) - eval_neg * (r * (Fr(1) - u) - u)) / (r * (Fr(1) - u) + u); + } + return eval_pos; // return A₀(r) + } + + /** + * @brief Utility for native batch multiplication of group elements + * @note This is used only for native verification and is not optimized for efficiency + */ + static Commitment batch_mul_native(const std::vector& _points, const std::vector& _scalars) + { + std::vector points; + std::vector scalars; + for (auto [point, scalar] : zip_view(_points, _scalars)) { + // TODO(https://github.com/AztecProtocol/barretenberg/issues/866) Special handling of point at infinity here + // due to incorrect serialization. + if (!scalar.is_zero() && !point.is_point_at_infinity() && !point.y.is_zero()) { + points.emplace_back(point); + scalars.emplace_back(scalar); + } + } + + if (points.empty()) { + return Commitment::infinity(); + } + + auto result = points[0] * scalars[0]; + for (size_t idx = 1; idx < scalars.size(); ++idx) { + result = result + points[idx] * scalars[idx]; + } + return result; + } }; -} // namespace bb + +} // namespace bb \ No newline at end of file