From e51d157fc7ae9a8ffeba8e6f89dbe87034d36db4 Mon Sep 17 00:00:00 2001 From: iakovenkos <105737703+iakovenkos@users.noreply.github.com> Date: Fri, 6 Sep 2024 17:43:32 +0200 Subject: [PATCH] refactor: more efficient verification with shplonk and gemini (#8351) - Created a separate ShpleminiVerifier class - Reduced the number of batch_mul calls. Only 1 batch_mul call with KZG (compared to 6 in the existing Gemini+Shplonk and to 4 in the Zeromorph flow) - Shplemini Docs + minor docs improvements in other parts - Shplemini Tests: unit tests for shplemini functions, recursion test, integration tests with KZG, IPA - batch_mul_native moved to commitment_schemes/utils --- .../barretenberg/commitment_schemes/claim.hpp | 13 + .../commitment_schemes/gemini/gemini.cpp | 2 +- .../commitment_schemes/gemini/gemini.hpp | 108 +++--- .../commitment_schemes/ipa/ipa.hpp | 44 +++ .../commitment_schemes/ipa/ipa.test.cpp | 88 ++++- .../commitment_schemes/kzg/kzg.hpp | 48 ++- .../commitment_schemes/kzg/kzg.test.cpp | 114 ++++++- .../shplonk/shplemini_verifier.hpp | 320 ++++++++++++++++++ .../shplonk/shplemini_verifier.test.cpp | 232 +++++++++++++ .../commitment_schemes/shplonk/shplonk.hpp | 20 ++ .../shplonk/shplonk.test.cpp | 6 +- .../utils/batch_mul_native.hpp | 43 +++ .../zeromorph/zeromorph.hpp | 34 +- .../shplemini.test.cpp | 178 ++++++++++ .../zeromorph.test.cpp | 26 +- 15 files changed, 1183 insertions(+), 93 deletions(-) create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.hpp create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.test.cpp create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes/utils/batch_mul_native.hpp create mode 100644 barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/claim.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/claim.hpp index 61f32b8d6d1..129cb4df521 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/claim.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/claim.hpp @@ -72,4 +72,17 @@ template class OpeningClaim { bool operator==(const OpeningClaim& other) const = default; }; + +/** + * @brief An accumulator consisting of the Shplonk evaluation challenge and vectors of commitments and scalars. + * + * @details This structure is used in the `reduce_verify_batch_opening_claim` method of KZG or IPA. + * + * @tparam Curve: BN254 or Grumpkin. + */ +template struct BatchOpeningClaim { + std::vector commitments; + std::vector scalars; + typename Curve::ScalarField evaluation_point; +}; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp index 4422abdee0d..679857e1d66 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.cpp @@ -150,7 +150,7 @@ GeminiProverOutput GeminiProver_::compute_fold_polynomial_evaluati Polynomial& batched_G = gemini_polynomials[1]; // G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X) // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1 - std::vector r_squares = gemini::squares_of_r(r_challenge, num_variables); + std::vector r_squares = gemini::powers_of_evaluation_challenge(r_challenge, num_variables); // Compute G/r Fr r_inv = r_challenge.invert(); diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp index 23142078def..bc3034bc753 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp @@ -88,7 +88,7 @@ template inline std::vector powers_of_rho(const Fr rho, const siz * @param num_squares The number of foldings * @return std::vector */ -template inline std::vector squares_of_r(const Fr r, const size_t num_squares) +template inline std::vector powers_of_evaluation_challenge(const Fr r, const size_t num_squares) { std::vector squares = { r }; squares.reserve(num_squares); @@ -132,7 +132,7 @@ template class GeminiVerifier_ { * (Cⱼ, Aⱼ(-r^{2ʲ}), -r^{2}), j = [1, ..., m-1] */ static std::vector> reduce_verification(std::span mle_opening_point, /* u */ - const Fr batched_evaluation, /* all */ + Fr& batched_evaluation, /* all */ GroupElement& batched_f, /* unshifted */ GroupElement& batched_g, /* to-be-shifted */ auto& transcript) @@ -140,28 +140,17 @@ template class GeminiVerifier_ { const size_t num_variables = mle_opening_point.size(); // Get polynomials Fold_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) { - auto commitment = - transcript->template receive_from_prover("Gemini:FOLD_" + std::to_string(i + 1)); - commitments.emplace_back(commitment); - } + const std::vector commitments = get_gemini_commitments(num_variables, transcript); // 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); + const std::vector r_squares = gemini::powers_of_evaluation_challenge(r, num_variables); // 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) { - auto eval = transcript->template receive_from_prover("Gemini:a_" + std::to_string(i)); - evaluations.emplace_back(eval); - } - + const std::vector evaluations = get_gemini_evaluations(num_variables, transcript); // Compute evaluation A₀(r) - auto a_0_pos = compute_eval_pos(batched_evaluation, mle_opening_point, r_squares, evaluations); + auto a_0_pos = + compute_gemini_batched_univariate_evaluation(batched_evaluation, mle_opening_point, r_squares, evaluations); // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] + r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] // C₀_r_pos = ∑ⱼ ρʲ⋅[fⱼ] - r⁻¹⋅∑ⱼ ρᵏ⁺ʲ [gⱼ] @@ -183,42 +172,75 @@ template class GeminiVerifier_ { return fold_polynomial_opening_claims; } - private: + static std::vector get_gemini_commitments(const size_t log_circuit_size, auto& transcript) + { + std::vector gemini_commitments; + gemini_commitments.reserve(log_circuit_size - 1); + for (size_t i = 0; i < log_circuit_size - 1; ++i) { + const Commitment commitment = + transcript->template receive_from_prover("Gemini:FOLD_" + std::to_string(i + 1)); + gemini_commitments.emplace_back(commitment); + } + return gemini_commitments; + } + static std::vector get_gemini_evaluations(const size_t log_circuit_size, auto& transcript) + { + std::vector gemini_evaluations; + gemini_evaluations.reserve(log_circuit_size); + for (size_t i = 0; i < log_circuit_size; ++i) { + const Fr evaluation = transcript->template receive_from_prover("Gemini:a_" + std::to_string(i)); + gemini_evaluations.emplace_back(evaluation); + } + return gemini_evaluations; + } + /** * @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) + * Compute the evaluation \f$ A_0(r) = \sum \rho^i \cdot f_i + \frac{1}{r} \cdot \sum \rho^{i+k} g_i \f$, where \f$ + * k \f$ is the number of "unshifted" commitments. + * + * @details 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\left(r^{2^\ell}\right) = (1 - u_{\ell-1}) \cdot \frac{A_{\ell-1}\left(r^{2^{\ell-1}}\right) + + * A_{\ell-1}\left(-r^{2^{\ell-1}}\right)}{2} + * + u_{\ell-1} \cdot \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 can recover \f$ A_0(r) \f$ by solving several linear equations. + * + * @param batched_mle_eval The evaluation of the batched polynomial at \f$ (u_0, \ldots, u_{d-1})\f$. + * @param evaluation_point Evaluation point \f$ (u_0, \ldots, u_{d-1}) \f$. + * @param challenge_powers Powers of \f$ r \f$, \f$ r^2 \), ..., \( r^{2^{m-1}} \f$. + * @param fold_polynomial_evals Evaluations \f$ A_{i-1}(-r^{2^{i-1}}) \f$. + * @return Evaluation \f$ A_0(r) \f$. */ - static Fr compute_eval_pos(const Fr batched_mle_eval, - std::span mle_vars, - std::span r_squares, - std::span fold_polynomial_evals) + static Fr compute_gemini_batched_univariate_evaluation(Fr& batched_eval_accumulator, + std::span evaluation_point, + std::span challenge_powers, + std::span fold_polynomial_evals) { - const size_t num_variables = mle_vars.size(); + const size_t num_variables = evaluation_point.size(); const auto& evals = fold_polynomial_evals; - // Initialize eval_pos with batched MLE eval v = ∑ⱼ ρʲ vⱼ + ∑ⱼ ρᵏ⁺ʲ v↺ⱼ - Fr eval_pos = batched_mle_eval; + // Solve the sequence of linear equations 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); + // Get r²⁽ˡ⁻¹⁾ + const Fr& challenge_power = challenge_powers[l - 1]; + // Get A₍ₗ₋₁₎(−r²⁽ˡ⁻¹⁾) + const Fr& eval_neg = evals[l - 1]; + // Get uₗ₋₁ + const Fr& u = evaluation_point[l - 1]; + // Compute the numerator + batched_eval_accumulator = + ((challenge_power * batched_eval_accumulator * 2) - eval_neg * (challenge_power * (Fr(1) - u) - u)); + // Divide by the denominator + batched_eval_accumulator *= (challenge_power * (Fr(1) - u) + u).invert(); } - return eval_pos; // return A₀(r) + return batched_eval_accumulator; } /** diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp index 45b1bf209ab..4942301246a 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.hpp @@ -1,5 +1,6 @@ #pragma once #include "barretenberg/commitment_schemes/claim.hpp" +#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp" #include "barretenberg/commitment_schemes/verification_key.hpp" #include "barretenberg/common/assert.hpp" #include "barretenberg/common/container.hpp" @@ -579,6 +580,49 @@ template class IPA { { return reduce_verify_internal(vk, opening_claim, transcript); } + /** + * @brief A method that produces an IPA opening claim from Shplemini accumulator containing vectors of commitments + * and scalars and a Shplonk evaluation challenge. + * + * @details Compute the commitment \f$ C \f$ that will be used to prove that Shplonk batching is performed correctly + * and check the evaluation claims of the batched univariate polynomials. The check is done by verifying that the + * polynomial corresponding to \f$ C \f$ evaluates to \f$ 0 \f$ at the Shplonk challenge point \f$ z \f$. + * + */ + static OpeningClaim reduce_batch_opening_claim( + const BatchOpeningClaim& batch_opening_claim) + { + using Utils = CommitmentSchemesUtils; + // Extract batch_mul arguments from the accumulator + const auto& commitments = batch_opening_claim.commitments; + const auto& scalars = batch_opening_claim.scalars; + const Fr& shplonk_eval_challenge = batch_opening_claim.evaluation_point; + // Compute \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$ + GroupElement shplonk_output_commitment; + if constexpr (Curve::is_stdlib_type) { + shplonk_output_commitment = + GroupElement::batch_mul(commitments, scalars, /*max_num_bits=*/0, /*with_edgecases=*/true); + } else { + shplonk_output_commitment = Utils::batch_mul_native(commitments, scalars); + } + // Output an opening claim to be verified by the IPA opening protocol + return { { shplonk_eval_challenge, Fr(0) }, shplonk_output_commitment }; + } + /** + * @brief Verify the IPA opening claim obtained from a Shplemini accumulator + * + * @param batch_opening_claim + * @param vk + * @param transcript + * @return VerifierAccumulator + */ + static VerifierAccumulator reduce_verify_batch_opening_claim(const BatchOpeningClaim& batch_opening_claim, + const std::shared_ptr& vk, + auto& transcript) + { + const auto opening_claim = reduce_batch_opening_claim(batch_opening_claim); + return reduce_verify_internal(vk, opening_claim, transcript); + } }; } // namespace bb \ No newline at end of file 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 e03803a0132..48a78c4fd61 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -1,5 +1,6 @@ #include "../gemini/gemini.hpp" +#include "../shplonk/shplemini_verifier.hpp" #include "../shplonk/shplonk.hpp" #include "./mock_transcript.hpp" #include "barretenberg/commitment_schemes/commitment_key.test.hpp" @@ -22,6 +23,7 @@ class IPATest : public CommitmentTest { using CK = CommitmentKey; using VK = VerifierCommitmentKey; using Polynomial = bb::Polynomial; + using Commitment = typename Curve::AffineElement; }; } // namespace @@ -246,7 +248,7 @@ 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 @@ -321,3 +323,87 @@ TEST_F(IPATest, GeminiShplonkIPAWithShift) EXPECT_EQ(result, true); } +TEST_F(IPATest, ShpleminiIPAWithShift) +{ + using IPA = IPA; + using ShplonkProver = ShplonkProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using GeminiProver = GeminiProver_; + + const size_t n = 8; + const size_t log_n = 3; + + // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random + // point. + auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' + auto poly1 = this->random_polynomial(n); + auto poly2 = this->random_polynomial(n); + poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used + + 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); + auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + + std::vector multilinear_evaluations = { eval1, eval2, eval2_shift }; + + auto prover_transcript = NativeTranscript::prover_init_empty(); + Fr rho = prover_transcript->template get_challenge("rho"); + std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); + + Fr batched_evaluation = Fr::zero(); + for (size_t i = 0; i < rhos.size(); ++i) { + batched_evaluation += multilinear_evaluations[i] * rhos[i]; + } + + Polynomial batched_unshifted(n); + Polynomial batched_to_be_shifted(n); + batched_unshifted.add_scaled(poly1, rhos[0]); + batched_unshifted.add_scaled(poly2, rhos[1]); + batched_to_be_shifted.add_scaled(poly2, rhos[2]); + + auto gemini_polynomials = GeminiProver::compute_gemini_polynomials( + mle_opening_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->template get_challenge("Gemini:r"); + + const auto [gemini_opening_pairs, gemini_witnesses] = 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; + prover_transcript->send_to_verifier(label, evaluation); + opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]); + } + opening_claims.emplace_back(gemini_witnesses[log_n], gemini_opening_pairs[log_n]); + + 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); + + const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(log_n, + RefVector(unshifted_commitments), + RefVector(shifted_commitments), + RefVector(multilinear_evaluations), + mle_opening_point, + this->vk()->get_g1_identity(), + verifier_transcript); + + auto result = IPA::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript); + // auto result = IPA::reduce_verify(this->vk(), shplonk_verifier_claim, verifier_transcript); + + EXPECT_EQ(result, true); +} diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp index 0a1a873412c..a7fd610bec6 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.hpp @@ -2,6 +2,7 @@ #include "../claim.hpp" #include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp" #include "barretenberg/commitment_schemes/verification_key.hpp" #include "barretenberg/transcript/transcript.hpp" @@ -54,7 +55,7 @@ template class KZG { * @param claim OpeningClaim ({r, v}, C) * @return {P₀, P₁} where * - P₀ = C − v⋅[1]₁ + r⋅[W(x)]₁ - * - P₁ = [W(x)]₁ + * - P₁ = - [W(x)]₁ */ template static VerifierAccumulator reduce_verify(const OpeningClaim& claim, @@ -85,5 +86,50 @@ template class KZG { auto P_1 = -quotient_commitment; return { P_0, P_1 }; }; + + /** + * @brief Computes the input points for the pairing check needed to verify a KZG opening claim obtained from a + * Shplemini accumulator. + * + * @details This function is used in a recursive setting where we want to "aggregate" proofs. In the Shplemini case, + * the commitment \f$ C \f$ is encoded into the vectors `commitments` and `scalars` contained in the + * `batch_opening_claim`. More explicitly, \f$ C = \sum \text{commitments}_i \cdot \text{scalars}_i \f$. To avoid + * performing an extra `batch_mul`, we simply add the commitment \f$ [W]_1 \f$ to the vector of commitments and + * the Shplonk evaluation challenge to the vector of scalars and perform a single batch_mul that computes \f$C + + * W\cdot z \f$. + * + * @param batch_opening_claim \f$(\text{commitments}, \text{scalars}, \text{shplonk_evaluation_challenge})\f$ + * A struct containing the commitments, scalars, and the Shplonk evaluation challenge. + * @return \f$ \{P_0, P_1\}\f$ where: + * - \f$ P_0 = C + [W(x)]_1 \cdot z \f$ + * - \f$ P_1 = - [W(x)]_1 \f$ + */ + template + static VerifierAccumulator reduce_verify_batch_opening_claim(BatchOpeningClaim batch_opening_claim, + const std::shared_ptr& transcript) + { + using Utils = CommitmentSchemesUtils; + auto quotient_commitment = transcript->template receive_from_prover("KZG:W"); + + // The pairing check can be expressed as + // e(C + [W]₁ ⋅ z, [1]₂) * e(−[W]₁, [X]₂) = 1, where C = ∑ commitmentsᵢ ⋅ scalarsᵢ. + GroupElement P_0; + // Place the commitment to W to 'commitments' + batch_opening_claim.commitments.emplace_back(quotient_commitment); + // Update the scalars by adding the Shplonk evaluation challenge z + batch_opening_claim.scalars.emplace_back(batch_opening_claim.evaluation_point); + // Compute C + [W]₁ ⋅ z + if constexpr (Curve::is_stdlib_type) { + P_0 = GroupElement::batch_mul(batch_opening_claim.commitments, + batch_opening_claim.scalars, + /*max_num_bits=*/0, + /*with_edgecases=*/true); + } else { + P_0 = Utils::batch_mul_native(batch_opening_claim.commitments, batch_opening_claim.scalars); + } + auto P_1 = -quotient_commitment; + + return { P_0, P_1 }; + } }; } // namespace bb 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 19ead96b61f..169b70f61c1 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp @@ -1,6 +1,7 @@ #include "kzg.hpp" #include "../gemini/gemini.hpp" +#include "../shplonk/shplemini_verifier.hpp" #include "../shplonk/shplonk.hpp" #include "../commitment_key.test.hpp" @@ -72,7 +73,7 @@ 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 @@ -172,4 +173,115 @@ TYPED_TEST(KZGTest, GeminiShplonkKzgWithShift) EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true); } +TYPED_TEST(KZGTest, ShpleminiKzgWithShift) +{ + using ShplonkProver = ShplonkProver_; + using GeminiProver = GeminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + 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; + const size_t log_n = 4; + auto prover_transcript = NativeTranscript::prover_init_empty(); + // Get batching challenge + Fr rho = prover_transcript->template get_challenge("rho"); + // Generate multilinear polynomials, their commitments (genuine and mocked) and evaluations (genuine) at a random + // point. + auto mle_opening_point = this->random_evaluation_point(log_n); // sometimes denoted 'u' + auto poly1 = this->random_polynomial(n); + auto poly2 = this->random_polynomial(n); + poly2[0] = Fr::zero(); // this property is required of polynomials whose shift is used + + 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); + auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + + // Collect multilinear evaluations for input to prover + std::vector multilinear_evaluations = { eval1, eval2, eval2_shift }; + + 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 < rhos.size(); ++i) { + batched_evaluation += multilinear_evaluations[i] * rhos[i]; + } + + // Compute batched polynomials + Polynomial batched_unshifted(n); + Polynomial batched_to_be_shifted(n); + batched_unshifted.add_scaled(poly1, rhos[0]); + batched_unshifted.add_scaled(poly2, rhos[1]); + batched_to_be_shifted.add_scaled(poly2, rhos[2]); + + // Compute batched commitments + GroupElement batched_commitment_unshifted = GroupElement::zero(); + GroupElement batched_commitment_to_be_shifted = GroupElement::zero(); + batched_commitment_unshifted = commitment1 * rhos[0] + commitment2 * rhos[1]; + batched_commitment_to_be_shifted = commitment2 * rhos[2]; + + // Run the full prover PCS protocol: + + // 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( + mle_opening_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->template get_challenge("Gemini:r"); + + const auto [gemini_opening_pairs, gemini_witnesses] = 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; + prover_transcript->send_to_verifier(label, evaluation); + opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]); + } + opening_claims.emplace_back(gemini_witnesses[log_n], gemini_opening_pairs[log_n]); + + // Shplonk prover output: + // - opening pair: (z_challenge, 0) + // - witness: polynomial Q - Q_z + const auto opening_claim = ShplonkProver::prove(this->ck(), opening_claims, prover_transcript); + + // KZG prover: + // - Adds commitment [W] to transcript + KZG::compute_opening_proof(this->ck(), opening_claim, prover_transcript); + + // Run the full verifier PCS protocol with genuine opening claims (genuine commitment, genuine evaluation) + + auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); + + // Gemini verifier output: + // - claim: d+1 commitments to Fold_{r}^(0), Fold_{-r}^(0), Fold^(l), d+1 evaluations a_0_pos, a_l, l = 0:d-1 + const auto batch_opening_claim = ShpleminiVerifier::compute_batch_opening_claim(log_n, + RefVector(unshifted_commitments), + RefVector(shifted_commitments), + RefVector(multilinear_evaluations), + mle_opening_point, + this->vk()->get_g1_identity(), + verifier_transcript); + const auto pairing_points = KZG::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript); + // Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2) + + EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true); +} + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.hpp new file mode 100644 index 00000000000..53893f7b0cb --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.hpp @@ -0,0 +1,320 @@ +#pragma once +#include "barretenberg/commitment_schemes/claim.hpp" +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/commitment_schemes/gemini/gemini.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" +#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp" +#include "barretenberg/commitment_schemes/verification_key.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { +/** + * \brief An efficient verifier for the evaluation proofs of multilinear polynomials and their shifts. + * + * \details + * \subsection Context + * + * This Verifier combines verifiers from four protocols: + * 1. **Batch opening protocol**: Reduces various evaluation claims of multilinear polynomials and their shifts to the + * opening claim of a single batched polynomial. + * 2. **Gemini protocol**: Reduces the batched polynomial opening claim to a claim about openings of Gemini univariate + * polynomials. + * 3. **Shplonk protocol**: Reduces the opening of Gemini univariate polynomials at different points to a single opening + * of a batched univariate polynomial. Outputs \f$ \text{shplonk_opening_claim} \f$. + * 4. **KZG or IPA protocol**: Verifies the evaluation of the univariate batched by Shplonk. + * + * **Important Observation**: From step 1 to step 4, the Verifier is not required to hash any results of its group + * operations. Therefore, they could be performed at the very end, i.e. by the opening protocol of a chosen univariate + * PCS. Because of this and the shape of the pairing check in Shplonk, various batch_mul calls could be reduced to a + * single batch_mul call. This way we minimize the number of gates in the resulting recursive verifier circuits and save + * some group operations in the native setting. + * + * \remark The sequence of steps could be performed by performing batching of unshifted and shifted polynomials, feeding + * it to the existing GeminiVerifier, whose output would be passed to the ShplonkVerifier and then to the reduce_verify + * method of a chosen PCS. However, it would be less efficient than ShpleminiVerifier in terms of group and field + * operations. + * + * \subsection Implementation + * + * The method \ref compute_batch_opening_claim receives commitments to all prover polynomials, their claimed + * evaluations, the sumcheck challenge, the group element \f$ [1]_1 \f$, and a pointer to the transcript. Its logic + * could be divided into several steps: + * + * 1. Receive most of the challenges and prover data. + * 2. Run the \ref batch_multivariate_opening_claims method corresponding to step 1 above. + * 3. Corresponding to step 2 above: + * - Run the \ref batch_gemini_claims_received_from_prover method. + * - Compute the evaluation of the Gemini batched univariate. + * 4. Output a \ref bb::BatchOpeningClaim "batch opening claim", which is a atriple \f$ (\text{commitments}, + * \text{scalars}, \text{shplonk_evaluation_point}) \f$ that satisfies the following: \f[ \text{batch_mul} + * (\text{commitments},\ \text{scalars}) = \text{shplonk_opening_claim}.\text{point} \f] and the sizes of 'commitments' + * and 'scalars' are equal to: \f[ + * \#\text{claimed_evaluations} + \text{log_circuit_size} + 2 + * \f] + * + * The output triple is either fed to the corresponding \ref bb::KZG< Curve_ >::reduce_verify_batch_opening_claim + * "KZG method" or \ref bb::IPA< Curve_ >::reduce_verify_batch_opening_claim "IPA method". In the case of KZG, we reduce + * \f$ 6 \f$ batch_mul calls needed for the verification of the multivariate evaluation claims to the single batch_mul + * described above. In the case of IPA, the total number of batch_mul calls needed to verify the multivariate evaluation + * claims is reduced by \f$ 5 \f$. + * + * TODO (https://github.com/AztecProtocol/barretenberg/issues/1084) Reduce the size of batch_mul further by eliminating + * shifted commitments. + */ + +template class ShpleminiVerifier_ { + using Fr = typename Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::AffineElement; + using VK = VerifierCommitmentKey; + using ShplonkVerifier = ShplonkVerifier_; + using GeminiVerifier = GeminiVerifier_; + + public: + template + static BatchOpeningClaim compute_batch_opening_claim(const Fr log_N, + RefSpan unshifted_commitments, + RefSpan shifted_commitments, + RefSpan claimed_evaluations, + const std::vector& multivariate_challenge, + const Commitment& g1_identity, + std::shared_ptr& transcript) + { + // Extract log_circuit_size + size_t log_circuit_size{ 0 }; + if constexpr (Curve::is_stdlib_type) { + log_circuit_size = static_cast(log_N.get_value()); + } else { + log_circuit_size = static_cast(log_N); + } + + // Get the challenge ρ to batch commitments to multilinear polynomials and their shifts + const Fr multivariate_batching_challenge = transcript->template get_challenge("rho"); + + // Process Gemini transcript data: + // - Get Gemini commitments (com(A₁), com(A₂), … , com(Aₙ₋₁)) + const std::vector gemini_commitments = + GeminiVerifier::get_gemini_commitments(log_circuit_size, transcript); + // - Get Gemini evaluation challenge for Aᵢ, i = 0, … , d−1 + const Fr gemini_evaluation_challenge = transcript->template get_challenge("Gemini:r"); + // - Get evaluations (A₀(−r), A₁(−r²), ... , Aₙ₋₁(−r²⁽ⁿ⁻¹⁾)) + const std::vector gemini_evaluations = GeminiVerifier::get_gemini_evaluations(log_circuit_size, transcript); + // - Compute vector (r, r², ... , r²⁽ⁿ⁻¹⁾), where n = log_circuit_size + const std::vector gemini_eval_challenge_powers = + gemini::powers_of_evaluation_challenge(gemini_evaluation_challenge, log_circuit_size); + + // Process Shplonk transcript data: + // - Get Shplonk batching challenge + const Fr shplonk_batching_challenge = transcript->template get_challenge("Shplonk:nu"); + // - Get the quotient commitment for the Shplonk batching of Gemini opening claims + const auto Q_commitment = transcript->template receive_from_prover("Shplonk:Q"); + + // Start populating the vector (Q, f₀, ... , fₖ₋₁, g₀, ... , gₘ₋₁, com(A₁), ... , com(Aₙ₋₁), [1]₁) + std::vector commitments{ Q_commitment }; + // Get Shplonk opening point z + const Fr shplonk_evaluation_challenge = transcript->template get_challenge("Shplonk:z"); + // Start computing the scalar to be multiplied by [1]₁ + Fr constant_term_accumulator{ 0 }; + + // Initialize the vector of scalars placing the scalar 1 correposnding to Q_commitment + std::vector scalars; + if constexpr (Curve::is_stdlib_type) { + auto builder = shplonk_batching_challenge.get_context(); + scalars.emplace_back(Fr(builder, 1)); + } else { + scalars.emplace_back(Fr(1)); + } + // Compute 1/(z − r), 1/(z + r), 1/(z + r²), … , 1/(z + r²⁽ⁿ⁻¹⁾) needed for Shplonk batching + const std::vector inverse_vanishing_evals = ShplonkVerifier::compute_inverted_gemini_denominators( + log_circuit_size + 1, shplonk_evaluation_challenge, gemini_eval_challenge_powers); + + // i-th unshifted commitment is multiplied by −ρⁱ and the unshifted_scalar ( 1/(z−r) + ν/(z+r) ) + const Fr unshifted_scalar = + inverse_vanishing_evals[0] + shplonk_batching_challenge * inverse_vanishing_evals[1]; + // i-th shifted commitment is multiplied by −ρⁱ⁺ᵏ and the shifted_scalar r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) + const Fr shifted_scalar = + gemini_evaluation_challenge.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 batched_evaluation{ 0 }; + batch_multivariate_opening_claims(unshifted_commitments, + shifted_commitments, + claimed_evaluations, + multivariate_batching_challenge, + unshifted_scalar, + shifted_scalar, + commitments, + scalars, + batched_evaluation); + + // Place the commitments to Gemini Aᵢ to the vector of commitments, compute the contributions from + // Aᵢ(−r²ⁱ) for i=1, … , n−1 to the constant term accumulator, add corresponding scalars + batch_gemini_claims_received_from_prover(log_circuit_size, + gemini_commitments, + gemini_evaluations, + inverse_vanishing_evals, + shplonk_batching_challenge, + commitments, + scalars, + constant_term_accumulator); + + // Add contributions from A₀(r) and A₀(-r) to constant_term_accumulator: + // - Compute A₀(r) + const Fr a_0_pos = GeminiVerifier_::compute_gemini_batched_univariate_evaluation( + batched_evaluation, multivariate_challenge, gemini_eval_challenge_powers, gemini_evaluations); + // - Add A₀(r)/(z−r) to the constant term accumulator + constant_term_accumulator += a_0_pos * inverse_vanishing_evals[0]; + // 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); + + return { commitments, scalars, shplonk_evaluation_challenge }; + }; + /** + * @brief Populates the vectors of commitments and scalars, and computes the evaluation of the batched multilinear + * polynomial at the sumcheck challenge. + * + * @details This function iterates over all commitments and the claimed evaluations of the corresponding + * polynomials. The following notations are used: + * - \f$ \rho \f$: Batching challenge for multivariate claims. + * - \f$ z \f$: SHPLONK evaluation challenge. + * - \f$ r \f$: Gemini evaluation challenge. + * - \f$ \nu \f$: SHPLONK batching challenge. + * + * The vector of scalars is populated as follows: + * \f[ + * \left( + * - \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{i+k-1} \times \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right), + * - \rho^{i+k} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right), + * \ldots, + * - \rho^{k+m-1} \times \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) + * \right) + * \f] + * + * The following vector is concatenated to the vector of commitments: + * \f[ + * f_0, \ldots, f_{m-1}, f_{\text{shift}, 0}, \ldots, f_{\text{shift}, k-1} + * \f] + * + * Simultaneously, the evaluation of the multilinear polynomial + * \f[ + * \sum \rho^i \cdot f_i + \sum \rho^{i+k} \cdot f_{\text{shift}, i} + * \f] + * at the challenge point \f$ (u_0,\ldots, u_{n-1}) \f$ is computed. + * + * This approach minimizes the number of iterations over the commitments to multilinear polynomials + * and eliminates the need to store the powers of \f$ \rho \f$. + * + * @param unshifted_commitments Commitments to unshifted polynomials. + * @param shifted_commitments Commitments to shifted polynomials. + * @param claimed_evaluations Claimed evaluations of the corresponding polynomials. + * @param multivariate_batching_challenge Random challenge used for batching of multivariate evaluation claims. + * @param unshifted_scalar Scaling factor for unshifted polynomials. + * @param shifted_scalar Scaling factor for shifted polynomials. + * @param commitments The vector of commitments to be populated. + * @param scalars The vector of scalars to be populated. + * @param batched_evaluation The evaluation of the batched multilinear polynomial. + */ + static void batch_multivariate_opening_claims(RefSpan unshifted_commitments, + RefSpan shifted_commitments, + RefSpan claimed_evaluations, + const Fr& multivariate_batching_challenge, + const Fr& unshifted_scalar, + const Fr& shifted_scalar, + std::vector& commitments, + std::vector& scalars, + Fr& batched_evaluation) + { + size_t evaluation_idx = 0; + Fr current_batching_challenge = Fr(1); + for (auto& unshifted_commitment : unshifted_commitments) { + // Move unshifted commitments to the 'commitments' vector + commitments.emplace_back(std::move(unshifted_commitment)); + // Compute −ρⁱ ⋅ (1/(z−r) + ν/(z+r)) and place into 'scalars' + scalars.emplace_back(-unshifted_scalar * current_batching_challenge); + // Accumulate the evaluation of ∑ ρⁱ ⋅ fᵢ at the sumcheck challenge + batched_evaluation += claimed_evaluations[evaluation_idx] * current_batching_challenge; + evaluation_idx += 1; + // Update the batching challenge + current_batching_challenge *= multivariate_batching_challenge; + } + for (auto& shifted_commitment : shifted_commitments) { + // Move shifted commitments to the 'commitments' vector + commitments.emplace_back(std::move(shifted_commitment)); + // Compute −ρ⁽ⁱ⁺ᵏ⁾ ⋅ r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) and place into 'scalars' + scalars.emplace_back(-shifted_scalar * current_batching_challenge); + // Accumulate the evaluation of ∑ ρ⁽ⁱ⁺ᵏ⁾ ⋅ f_shift, i at the sumcheck challenge + batched_evaluation += claimed_evaluations[evaluation_idx] * current_batching_challenge; + evaluation_idx += 1; + // Update the batching challenge + current_batching_challenge *= multivariate_batching_challenge; + } + } + /** + * @brief Populates the 'commitments' and 'scalars' vectors with the commitments to Gemini fold polynomials \f$ A_i + * \f$. + * + * @details Once the commitments to Gemini "fold" polynomials \f$ A_i \f$ and their evaluations at \f$ -r^{2^i} \f$, + * where \f$ i = 1, \ldots, n-1 \f$, are received by the verifier, it performs the following operations: + * + * 1. Moves the vector + * \f[ + * \left( \text{com}(A_1), \text{com}(A_2), \ldots, \text{com}(A_{n-1}) \right) + * \f] + * to the 'commitments' vector. + * + * 2. Computes the scalars: + * \f[ + * \frac{\nu^{2}}{z + r^2}, \frac{\nu^3}{z + r^4}, \ldots, \frac{\nu^{n-1}}{z + r^{2^{n-1}}} + * \f] + * and places them into the 'scalars' vector. + * + * 3. Accumulates the summands of the constant term: + * \f[ + * \sum_{i=2}^{n-1} \frac{\nu^{i} \cdot A_i(-r^{2^i})}{z + r^{2^i}} + * \f] + * and adds them to the 'constant_term_accumulator'. + * + * @param log_circuit_size The logarithm of the circuit size, determining the depth of the Gemini protocol. + * @param gemini_commitments A vector containing the commitments to the Gemini fold polynomials \f$ A_i \f$. + * @param gemini_evaluations A vector containing the evaluations of the Gemini fold polynomials \f$ A_i \f$ at + * points \f$ -r^{2^i} \f$. + * @param inverse_vanishing_evals A vector containing the inverse evaluations of the vanishing polynomial. + * @param shplonk_batching_challenge The batching challenge \f$ \nu \f$ used in the SHPLONK protocol. + * @param commitments Output vector where the commitments to the Gemini fold polynomials will be stored. + * @param scalars Output vector where the computed scalars will be stored. + * @param constant_term_accumulator The accumulator for the summands of the constant term. + */ + static void batch_gemini_claims_received_from_prover(const size_t log_circuit_size, + const std::vector& gemini_commitments, + const std::vector& gemini_evaluations, + const std::vector& inverse_vanishing_evals, + const Fr& shplonk_batching_challenge, + std::vector& commitments, + std::vector& scalars, + Fr& constant_term_accumulator) + { + // Initialize batching challenge as ν² + Fr current_batching_challenge = shplonk_batching_challenge * shplonk_batching_challenge; + for (size_t j = 0; j < log_circuit_size - 1; ++j) { + // Compute the scaling factor (ν²⁺ⁱ) / (z + r²⁽ⁱ⁺²⁾) for i = 0, … , d-2 + Fr scaling_factor = current_batching_challenge * inverse_vanishing_evals[j + 2]; + // Place the scaling factor to the 'scalars' vector + scalars.emplace_back(-scaling_factor); + // Add Aᵢ(−r²ⁱ) for i = 1, … , n-1 to the constant term accumulator + constant_term_accumulator += scaling_factor * gemini_evaluations[j + 1]; + // Update the batching challenge + current_batching_challenge *= shplonk_batching_challenge; + // Move com(Aᵢ) to the 'commitments' vector + commitments.emplace_back(std::move(gemini_commitments[j])); + } + } +}; +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.test.cpp new file mode 100644 index 00000000000..14ad58811af --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_verifier.test.cpp @@ -0,0 +1,232 @@ + +#include "shplemini_verifier.hpp" +#include "../commitment_key.test.hpp" +#include "../gemini/gemini.hpp" +#include "../kzg/kzg.hpp" +#include "../shplonk/shplonk.hpp" +#include "../utils/batch_mul_native.hpp" +#include "barretenberg/commitment_schemes/claim.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" + +#include +#include + +namespace bb { + +template class ShpleminiTest : public CommitmentTest { + public: + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using GroupElement = typename Curve::Element; + using Polynomial = bb::Polynomial; +}; + +using CurveTypes = ::testing::Types; + +TYPED_TEST_SUITE(ShpleminiTest, CurveTypes); + +// This test checks that batch_multivariate_opening_claims method operates correctly +TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) +{ + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename TypeParam::ScalarField; + using GroupElement = typename TypeParam::Element; + using Commitment = typename TypeParam::AffineElement; + using Polynomial = typename bb::Polynomial; + using Utils = CommitmentSchemesUtils; + + const size_t n = 16; + const size_t log_n = 4; + + // Generate mock challenges + Fr rho = Fr::random_element(); + Fr gemini_eval_challenge = Fr::random_element(); + Fr shplonk_batching_challenge = Fr::random_element(); + Fr shplonk_eval_challenge = Fr::random_element(); + + // Generate multilinear polynomials and compute their commitments + auto mle_opening_point = this->random_evaluation_point(log_n); + auto poly1 = this->random_polynomial(n); + auto poly2 = this->random_polynomial(n); + Polynomial poly3(n); + + poly2[0] = Fr::zero(); // Necessary for polynomials whose shift is used + + Commitment commitment1 = this->commit(poly1); + Commitment commitment2 = this->commit(poly2); + Commitment commitment3 = this->commit(poly3); + EXPECT_TRUE(commitment3.is_point_at_infinity()); + + std::vector unshifted_commitments = { commitment1, commitment2, commitment3 }; + std::vector shifted_commitments = { commitment2, commitment3 }; + + // Evaluate the polynomials at the multivariate challenge, poly3 is not evaluated, because it is 0. + auto eval1 = poly1.evaluate_mle(mle_opening_point); + auto eval2 = poly2.evaluate_mle(mle_opening_point); + Fr eval3{ 0 }; + Fr eval3_shift{ 0 }; + auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + + // Collect multilinear evaluations + std::vector multilinear_evaluations = { eval1, eval2, eval3, eval2_shift, eval3_shift }; + std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); + + // Compute batched multivariate evaluation + Fr batched_evaluation = + std::inner_product(multilinear_evaluations.begin(), multilinear_evaluations.end(), rhos.begin(), Fr::zero()); + + // Compute batched commitments manually + GroupElement batched_commitment_unshifted = commitment1 * rhos[0] + commitment2 * rhos[1] + commitment3 * rhos[2]; + GroupElement batched_commitment_to_be_shifted = commitment2 * rhos[3] + commitment3 * rhos[4]; + + // Compute expected result manually + GroupElement commitment_to_univariate = + batched_commitment_unshifted + batched_commitment_to_be_shifted * gemini_eval_challenge.invert(); + GroupElement commitment_to_univariate_neg = + batched_commitment_unshifted - batched_commitment_to_be_shifted * gemini_eval_challenge.invert(); + + GroupElement expected_result = + commitment_to_univariate * (shplonk_eval_challenge - gemini_eval_challenge).invert() + + commitment_to_univariate_neg * + (shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert()); + + // Run the ShepliminiVerifier batching method + std::vector commitments; + std::vector scalars; + Fr verifier_batched_evaluation{ 0 }; + + Fr unshifted_scalar = (shplonk_eval_challenge - gemini_eval_challenge).invert() + + shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert(); + + Fr shifted_scalar = gemini_eval_challenge.invert() * + ((shplonk_eval_challenge - gemini_eval_challenge).invert() - + shplonk_batching_challenge * (shplonk_eval_challenge + gemini_eval_challenge).invert()); + + ShpleminiVerifier::batch_multivariate_opening_claims(RefVector(unshifted_commitments), + RefVector(shifted_commitments), + RefVector(multilinear_evaluations), + rho, + unshifted_scalar, + shifted_scalar, + commitments, + scalars, + verifier_batched_evaluation); + + // Final pairing check + GroupElement shplemini_result = Utils::batch_mul_native(commitments, scalars); + + EXPECT_EQ(commitments.size(), unshifted_commitments.size() + shifted_commitments.size()); + EXPECT_EQ(batched_evaluation, verifier_batched_evaluation); + EXPECT_EQ(-expected_result, shplemini_result); +} + +TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) +{ + using GeminiProver = GeminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using ShplonkVerifier = ShplonkVerifier_; + using Fr = typename TypeParam::ScalarField; + using GroupElement = typename TypeParam::Element; + using Commitment = typename TypeParam::AffineElement; + using Polynomial = typename bb::Polynomial; + using Utils = CommitmentSchemesUtils; + + const size_t n = 16; + const size_t log_n = 4; + + // Generate mock challenges + Fr rho = Fr::random_element(); + Fr gemini_eval_challenge = Fr::random_element(); + Fr shplonk_batching_challenge = Fr::random_element(); + Fr shplonk_eval_challenge = Fr::random_element(); + + // Generate multilinear polynomials and compute their commitments + auto mle_opening_point = this->random_evaluation_point(log_n); + auto poly1 = this->random_polynomial(n); + auto poly2 = this->random_polynomial(n); + Polynomial poly3(n); + + poly2[0] = Fr::zero(); // Necessary for polynomials whose shift is used + + // Evaluate the polynomials at the multivariate challenge, poly3 is not evaluated, because it is 0. + auto eval1 = poly1.evaluate_mle(mle_opening_point); + auto eval2 = poly2.evaluate_mle(mle_opening_point); + Fr eval3{ 0 }; + Fr eval3_shift{ 0 }; + auto eval2_shift = poly2.evaluate_mle(mle_opening_point, true); + + // Collect multilinear evaluations + std::vector multilinear_evaluations = { eval1, eval2, eval3, eval2_shift, eval3_shift }; + std::vector rhos = gemini::powers_of_rho(rho, multilinear_evaluations.size()); + + Polynomial batched_unshifted(n); + Polynomial batched_to_be_shifted(n); + batched_unshifted.add_scaled(poly1, rhos[0]); + batched_unshifted.add_scaled(poly2, rhos[1]); + batched_unshifted.add_scaled(poly3, rhos[2]); + batched_to_be_shifted.add_scaled(poly2, rhos[3]); + batched_to_be_shifted.add_scaled(poly3, rhos[4]); + + // 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( + mle_opening_point, std::move(batched_unshifted), std::move(batched_to_be_shifted)); + + std::vector prover_commitments; + for (size_t l = 0; l < log_n - 1; ++l) { + auto commitment = this->ck()->commit(gemini_polynomials[l + 2]); + prover_commitments.emplace_back(commitment); + } + + const auto [gemini_opening_pairs, gemini_witnesses] = GeminiProver::compute_fold_polynomial_evaluations( + mle_opening_point, std::move(gemini_polynomials), gemini_eval_challenge); + + std::vector prover_evaluations; + std::vector> opening_claims; + for (size_t l = 0; l < log_n; ++l) { + const auto& evaluation = gemini_opening_pairs[l + 1].evaluation; + prover_evaluations.emplace_back(evaluation); + } + + std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, log_n); + + GroupElement expected_result = GroupElement::zero(); + std::vector expected_inverse_vanishing_evals(log_n + 1); + // Compute expected inverses + expected_inverse_vanishing_evals[0] = (shplonk_eval_challenge - r_squares[0]).invert(); + expected_inverse_vanishing_evals[1] = (shplonk_eval_challenge + r_squares[0]).invert(); + expected_inverse_vanishing_evals[2] = (shplonk_eval_challenge + r_squares[1]).invert(); + expected_inverse_vanishing_evals[3] = (shplonk_eval_challenge + r_squares[2]).invert(); + expected_inverse_vanishing_evals[4] = (shplonk_eval_challenge + r_squares[3]).invert(); + + Fr current_challenge{ shplonk_batching_challenge * shplonk_batching_challenge }; + for (size_t idx = 0; idx < prover_commitments.size(); ++idx) { + expected_result -= prover_commitments[idx] * current_challenge * expected_inverse_vanishing_evals[idx + 2]; + current_challenge *= shplonk_batching_challenge; + } + + // Run the ShepliminiVerifier batching method + std::vector inverse_vanishing_evals = + ShplonkVerifier::compute_inverted_gemini_denominators(log_n + 1, shplonk_eval_challenge, r_squares); + + std::vector commitments; + std::vector scalars; + Fr expected_constant_term_accumulator{ 0 }; + + ShpleminiVerifier::batch_gemini_claims_received_from_prover(log_n, + prover_commitments, + prover_evaluations, + inverse_vanishing_evals, + shplonk_batching_challenge, + commitments, + scalars, + expected_constant_term_accumulator); + + EXPECT_EQ(commitments.size(), prover_commitments.size()); + // Compute the group element using the output of Shplemini method + GroupElement shplemini_result = Utils::batch_mul_native(commitments, scalars); + + EXPECT_EQ(shplemini_result, expected_result); +} +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp index 9eac7b4a48c..697d5e99d0e 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp @@ -268,5 +268,25 @@ template class ShplonkVerifier_ { // Return opening pair (z, 0) and commitment [G] return { { z_challenge, Fr(0) }, G_commitment }; }; + /** + * @brief Computes \f$ \frac{1}{z - r}, \frac{1}{z+r}, \ldots, \frac{1}{z+r^{2^{d-1}}} \f$. + * + * @param log_circuit_size \f$ d \f$ + * @param shplonk_eval_challenge \f$ z \f$ + * @param gemini_eval_challenge_powers \f$ (r , r^2, \ldots, r^{2^{d-1}}) \f$ + * @return \f[ \left( \frac{1}{z - r}, \frac{1}{z+r}, \ldots, \frac{1}{z+r^{2^{d-1}}} \right) \f] + */ + static std::vector compute_inverted_gemini_denominators(const size_t num_gemini_claims, + const Fr& shplonk_eval_challenge, + const std::vector& gemini_eval_challenge_powers) + { + std::vector inverted_denominators; + inverted_denominators.reserve(num_gemini_claims); + inverted_denominators.emplace_back((shplonk_eval_challenge - gemini_eval_challenge_powers[0]).invert()); + for (const auto& gemini_eval_challenge_power : gemini_eval_challenge_powers) { + inverted_denominators.emplace_back((shplonk_eval_challenge + gemini_eval_challenge_power).invert()); + } + return inverted_denominators; + } }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.test.cpp index 73e726ed34c..b8dd2bb6558 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.test.cpp @@ -1,14 +1,12 @@ #include "shplonk.hpp" +#include "../commitment_key.test.hpp" #include "../gemini/gemini.hpp" - +#include "barretenberg/commitment_schemes/claim.hpp" #include #include #include #include #include - -#include "../commitment_key.test.hpp" -#include "barretenberg/commitment_schemes/claim.hpp" namespace bb { template class ShplonkTest : public CommitmentTest {}; diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/batch_mul_native.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/batch_mul_native.hpp new file mode 100644 index 00000000000..59970ea6d0b --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/batch_mul_native.hpp @@ -0,0 +1,43 @@ +#pragma once +#include "barretenberg/common/ref_span.hpp" +#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" +#include + +namespace bb { +template class CommitmentSchemesUtils { + + public: + using Commitment = typename Curve::AffineElement; + using FF = typename Curve::ScalarField; + + /** + * @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 (size_t i = 0; i < _points.size(); ++i) { + const auto& point = _points[i]; + const auto& scalar = _scalars[i]; + + // TODO: 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 \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/zeromorph/zeromorph.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/zeromorph/zeromorph.hpp index aa0cf5b0273..c2ffc81c7de 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/zeromorph/zeromorph.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/zeromorph/zeromorph.hpp @@ -1,6 +1,7 @@ #pragma once #include "barretenberg/commitment_schemes/claim.hpp" #include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/commitment_schemes/utils/batch_mul_native.hpp" #include "barretenberg/commitment_schemes/verification_key.hpp" #include "barretenberg/common/ref_span.hpp" #include "barretenberg/common/ref_vector.hpp" @@ -449,6 +450,7 @@ template class ZeroMorphProver_ { template class ZeroMorphVerifier_ { using FF = typename Curve::ScalarField; using Commitment = typename Curve::AffineElement; + using Utils = CommitmentSchemesUtils; public: /** @@ -528,7 +530,7 @@ template class ZeroMorphVerifier_ { return Commitment::batch_mul(commitments, scalars); } } else { - return batch_mul_native(commitments, scalars); + return Utils::batch_mul_native(commitments, scalars); } } @@ -684,38 +686,10 @@ template class ZeroMorphVerifier_ { return Commitment::batch_mul(commitments, scalars); } } else { - return batch_mul_native(commitments, scalars); + return Utils::batch_mul_native(commitments, scalars); } } - /** - * @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; - } - /** * @brief Return the univariate opening claim used to verify, in a subsequent PCS, a set of multilinear evaluation * claims for unshifted polynomials f_i and to-be-shifted polynomials g_i diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp new file mode 100644 index 00000000000..a6b151035ad --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp @@ -0,0 +1,178 @@ +#include "barretenberg/circuit_checker/circuit_checker.hpp" +#include "barretenberg/commitment_schemes/commitment_key.test.hpp" +#include "barretenberg/commitment_schemes/gemini/gemini.hpp" +#include "barretenberg/commitment_schemes/ipa/ipa.hpp" +#include "barretenberg/commitment_schemes/kzg/kzg.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplemini_verifier.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" +#include "barretenberg/srs/global_crs.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/primitives/curves/grumpkin.hpp" +#include "barretenberg/stdlib/transcript/transcript.hpp" +#include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" +#include + +using namespace bb; + +template class ShpleminiRecursionTest : public CommitmentTest {}; + +numeric::RNG& shplemini_engine = numeric::get_debug_randomness(); + +/** + * @brief Test Recursive Verification for 2 multilinear polynomials and a shift of one of them + * + */ +TEST(ShpleminiRecursionTest, ProveAndVerifySingle) +{ + // Define some useful type aliases + using Builder = UltraCircuitBuilder; + using Curve = typename stdlib::bn254; + using NativeCurve = typename Curve::NativeCurve; + using Commitment = typename Curve::AffineElement; + using NativeCommitment = typename Curve::AffineElementNative; + using NativeCurve = typename Curve::NativeCurve; + using NativePCS = std::conditional_t, KZG, IPA>; + using CommitmentKey = typename NativePCS::CK; + using GeminiProver = GeminiProver_; + using ShplonkProver = ShplonkProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename Curve::ScalarField; + using NativeFr = typename Curve::NativeCurve::ScalarField; + using Polynomial = bb::Polynomial; + using Transcript = bb::BaseTranscript>; + + constexpr size_t N = 16; + constexpr size_t log_circuit_size = 4; + constexpr size_t NUM_UNSHIFTED = 2; + constexpr size_t NUM_SHIFTED = 1; + + srs::init_crs_factory("../srs_db/ignition"); + + std::vector u_challenge(log_circuit_size); + for (size_t idx = 0; idx < log_circuit_size; ++idx) { + u_challenge[idx] = NativeFr::random_element(&shplemini_engine); + }; + // Construct some random multilinear polynomials f_i and their evaluations v_i = f_i(u) + std::vector f_polynomials; // unshifted polynomials + std::vector v_evaluations; + for (size_t i = 0; i < NUM_UNSHIFTED; ++i) { + f_polynomials.emplace_back(Polynomial::random(N)); + f_polynomials[i][0] = NativeFr(0); // ensure f is "shiftable" + v_evaluations.emplace_back(f_polynomials[i].evaluate_mle(u_challenge)); + } + + // Construct some "shifted" multilinear polynomials h_i as the left-shift-by-1 of f_i + std::vector g_polynomials; // to-be-shifted polynomials + std::vector h_polynomials; // shifts of the to-be-shifted polynomials + std::vector w_evaluations; + if constexpr (NUM_SHIFTED > 0) { + for (size_t i = 0; i < NUM_SHIFTED; ++i) { + g_polynomials.emplace_back(f_polynomials[i]); + h_polynomials.emplace_back(g_polynomials[i].shifted()); + w_evaluations.emplace_back(h_polynomials[i].evaluate_mle(u_challenge)); + } + } + + std::vector claimed_evaluations; + claimed_evaluations.reserve(v_evaluations.size() + w_evaluations.size()); + claimed_evaluations.insert(claimed_evaluations.end(), v_evaluations.begin(), v_evaluations.end()); + claimed_evaluations.insert(claimed_evaluations.end(), w_evaluations.begin(), w_evaluations.end()); + // Compute commitments [f_i] + std::vector f_commitments; + auto commitment_key = std::make_shared(4096); + for (size_t i = 0; i < NUM_UNSHIFTED; ++i) { + f_commitments.emplace_back(commitment_key->commit(f_polynomials[i])); + } + // Construct container of commitments of the "to-be-shifted" polynomials [g_i] (= [f_i]) + std::vector g_commitments; + for (size_t i = 0; i < NUM_SHIFTED; ++i) { + g_commitments.emplace_back(f_commitments[i]); + } + + // Initialize an empty NativeTranscript + auto prover_transcript = NativeTranscript::prover_init_empty(); + + NativeFr rho = prover_transcript->template get_challenge("rho"); + std::vector rhos = gemini::powers_of_rho(rho, NUM_SHIFTED + NUM_UNSHIFTED); + // Batch the unshifted polynomials and the to-be-shifted polynomials using ρ + Polynomial batched_poly_unshifted(N); + size_t poly_idx = 0; + for (auto& unshifted_poly : f_polynomials) { + batched_poly_unshifted.add_scaled(unshifted_poly, rhos[poly_idx]); + ++poly_idx; + } + + Polynomial batched_poly_to_be_shifted(N); // batched to-be-shifted polynomials + for (auto& to_be_shifted_poly : g_polynomials) { + batched_poly_to_be_shifted.add_scaled(to_be_shifted_poly, rhos[poly_idx]); + ++poly_idx; + }; + + // Compute d-1 polynomials Fold^(i), i = 1, ..., d-1. + auto fold_polynomials = GeminiProver::compute_gemini_polynomials( + u_challenge, std::move(batched_poly_unshifted), std::move(batched_poly_to_be_shifted)); + // Comute and add to trasnscript the commitments [Fold^(i)], i = 1, ..., d-1 + for (size_t l = 0; l < log_circuit_size - 1; ++l) { + NativeCommitment current_commitment = commitment_key->commit(fold_polynomials[l + 2]); + prover_transcript->send_to_verifier("Gemini:FOLD_" + std::to_string(l + 1), current_commitment); + } + const NativeFr r_challenge = prover_transcript->template get_challenge("Gemini:r"); + + const auto [gemini_opening_pairs, gemini_witnesses] = + GeminiProver::compute_fold_polynomial_evaluations(u_challenge, std::move(fold_polynomials), r_challenge); + + std::vector> opening_claims; + for (size_t l = 0; l < log_circuit_size; ++l) { + std::string label = "Gemini:a_" + std::to_string(l); + const auto& evaluation = gemini_opening_pairs[l + 1].evaluation; + prover_transcript->send_to_verifier(label, evaluation); + opening_claims.emplace_back(gemini_witnesses[l], gemini_opening_pairs[l]); + } + opening_claims.emplace_back(gemini_witnesses[log_circuit_size], gemini_opening_pairs[log_circuit_size]); + + // Shplonk prover output: + // - opening pair: (z_challenge, 0) + // - witness: polynomial Q - Q_z + ShplonkProver::prove(commitment_key, opening_claims, prover_transcript); + + Builder builder; + StdlibProof stdlib_proof = bb::convert_proof_to_witness(&builder, prover_transcript->proof_data); + auto stdlib_verifier_transcript = std::make_shared(stdlib_proof); + [[maybe_unused]] auto _ = stdlib_verifier_transcript->template receive_from_prover("Init"); + + // Execute Verifier protocol without the need for vk prior the final check + const auto commitments_to_witnesses = [&builder](const auto& commitments) { + std::vector commitments_in_biggroup(commitments.size()); + std::transform(commitments.begin(), + commitments.end(), + commitments_in_biggroup.begin(), + [&builder](const auto& native_commitment) { + return Commitment::from_witness(&builder, native_commitment); + }); + return commitments_in_biggroup; + }; + const auto elements_to_witness = [&](const auto& elements) { + std::vector elements_in_circuit(elements.size()); + std::transform(elements.begin(), + elements.end(), + elements_in_circuit.begin(), + [&builder](const auto& native_element) { return Fr::from_witness(&builder, native_element); }); + return elements_in_circuit; + }; + auto stdlib_f_commitments = commitments_to_witnesses(f_commitments); + auto stdlib_g_commitments = commitments_to_witnesses(g_commitments); + auto stdlib_claimed_evaluations = elements_to_witness(claimed_evaluations); + + std::vector u_challenge_in_circuit = elements_to_witness(u_challenge); + + [[maybe_unused]] auto opening_claim = + ShpleminiVerifier::compute_batch_opening_claim(Fr::from_witness(&builder, log_circuit_size), + RefVector(stdlib_f_commitments), + RefVector(stdlib_g_commitments), + RefVector(stdlib_claimed_evaluations), + u_challenge_in_circuit, + Commitment::one(&builder), + stdlib_verifier_transcript); + + EXPECT_TRUE(CircuitChecker::check(builder)); +} diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/zeromorph.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/zeromorph.test.cpp index 322f589a600..6452191dc01 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/zeromorph.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/zeromorph.test.cpp @@ -10,8 +10,6 @@ #include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" #include -#include - using namespace bb; template class ZeroMorphRecursionTest : public CommitmentTest {}; @@ -40,13 +38,16 @@ TEST(ZeroMorphRecursionTest, ProveAndVerifySingle) using ZeroMorphVerifier = ZeroMorphVerifier_; using Transcript = bb::BaseTranscript>; - constexpr size_t N = 2; - constexpr size_t NUM_UNSHIFTED = 1; - constexpr size_t NUM_SHIFTED = 0; + constexpr size_t N = 8; + constexpr size_t LOG_N = 3; + constexpr size_t NUM_UNSHIFTED = 2; + constexpr size_t NUM_SHIFTED = 1; srs::init_crs_factory("../srs_db/ignition"); - - std::vector u_challenge = { NativeFr::random_element(&engine) }; + std::vector u_challenge(LOG_N); + for (size_t idx = 0; idx < LOG_N; ++idx) { + u_challenge[idx] = NativeFr::random_element(&engine); + }; // Construct some random multilinear polynomials f_i and their evaluations v_i = f_i(u) std::vector f_polynomials; // unshifted polynomials @@ -56,15 +57,16 @@ TEST(ZeroMorphRecursionTest, ProveAndVerifySingle) f_polynomials[i][0] = NativeFr(0); // ensure f is "shiftable" v_evaluations.emplace_back(f_polynomials[i].evaluate_mle(u_challenge)); } - // Construct some "shifted" multilinear polynomials h_i as the left-shift-by-1 of f_i std::vector g_polynomials; // to-be-shifted polynomials std::vector h_polynomials; // shifts of the to-be-shifted polynomials std::vector w_evaluations; - for (size_t i = 0; i < NUM_SHIFTED; ++i) { - g_polynomials.emplace_back(f_polynomials[i]); - h_polynomials.emplace_back(g_polynomials[i].shifted()); - w_evaluations.emplace_back(h_polynomials[i].evaluate_mle(u_challenge)); + if constexpr (NUM_SHIFTED > 0) { + for (size_t i = 0; i < NUM_SHIFTED; ++i) { + g_polynomials.emplace_back(f_polynomials[i]); + h_polynomials.emplace_back(g_polynomials[i].shifted()); + w_evaluations.emplace_back(h_polynomials[i].evaluate_mle(u_challenge)); + } } // Compute commitments [f_i]