diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp index adafef90c7c..de417753927 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.test.hpp @@ -14,44 +14,54 @@ namespace bb { constexpr size_t COMMITMENT_TEST_NUM_BN254_POINTS = 4096; constexpr size_t COMMITMENT_TEST_NUM_GRUMPKIN_POINTS = 1 << CONST_ECCVM_LOG_N; -template inline std::shared_ptr CreateCommitmentKey(); +template inline std::shared_ptr create_commitment_key(const size_t num_points = 0); -template <> inline std::shared_ptr> CreateCommitmentKey>() +template <> +inline std::shared_ptr> create_commitment_key>( + const size_t num_points) { srs::init_crs_factory(bb::srs::get_ignition_crs_path()); + if (num_points != 0) { + return std::make_shared>(num_points); + }; return std::make_shared>(COMMITMENT_TEST_NUM_BN254_POINTS); } // For IPA -template <> inline std::shared_ptr> CreateCommitmentKey>() +template <> +inline std::shared_ptr> create_commitment_key>( + const size_t num_points) { srs::init_grumpkin_crs_factory(bb::srs::get_grumpkin_crs_path()); + if (num_points != 0) { + return std::make_shared>(num_points); + } return std::make_shared>(COMMITMENT_TEST_NUM_GRUMPKIN_POINTS); } -template inline std::shared_ptr CreateCommitmentKey() +template inline std::shared_ptr create_commitment_key(size_t num_points) // requires std::default_initializable { - return std::make_shared(); + return std::make_shared(num_points); } -template inline std::shared_ptr CreateVerifierCommitmentKey(); +template inline std::shared_ptr create_verifier_commitment_key(); template <> -inline std::shared_ptr> CreateVerifierCommitmentKey< +inline std::shared_ptr> create_verifier_commitment_key< VerifierCommitmentKey>() { return std::make_shared>(); } // For IPA template <> -inline std::shared_ptr> CreateVerifierCommitmentKey< +inline std::shared_ptr> create_verifier_commitment_key< VerifierCommitmentKey>() { auto crs_factory = std::make_shared>( bb::srs::get_grumpkin_crs_path(), COMMITMENT_TEST_NUM_GRUMPKIN_POINTS); return std::make_shared>(COMMITMENT_TEST_NUM_GRUMPKIN_POINTS, crs_factory); } -template inline std::shared_ptr CreateVerifierCommitmentKey() +template inline std::shared_ptr create_verifier_commitment_key() // requires std::default_initializable { return std::make_shared(); @@ -149,10 +159,10 @@ template class CommitmentTest : public ::testing::Test { { // Avoid reallocating static objects if called in subclasses of FooTest. if (commitment_key == nullptr) { - commitment_key = CreateCommitmentKey(); + commitment_key = create_commitment_key(); } if (verification_key == nullptr) { - verification_key = CreateVerifierCommitmentKey(); + verification_key = create_verifier_commitment_key(); } } diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp index a66387474d1..3828a009b6b 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp @@ -124,6 +124,15 @@ std::vector::Claim> GeminiProver_::prove( } const Fr r_challenge = transcript->template get_challenge("Gemini:r"); + const bool gemini_challenge_in_small_subgroup = (has_zk) && (r_challenge.pow(Curve::SUBGROUP_SIZE) == Fr(1)); + + // If Gemini evaluation challenge lands in the multiplicative subgroup used by SmallSubgroupIPA protocol, the + // evaluations of prover polynomials at this challenge would leak witness data. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS + if (gemini_challenge_in_small_subgroup) { + throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); + } + std::vector claims = compute_fold_polynomial_evaluations(log_n, std::move(fold_polynomials), r_challenge, std::move(batched_group)); diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp index bfb9fd93169..2bcc54538b2 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp @@ -5,8 +5,10 @@ #include "../kzg/kzg.hpp" #include "../shplonk/shplonk.hpp" #include "../utils/batch_mul_native.hpp" -#include "barretenberg/commitment_schemes/claim.hpp" #include "barretenberg/commitment_schemes/ipa/ipa.hpp" +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" +#include "barretenberg/commitment_schemes/utils/instance_witness_generator.hpp" +#include "barretenberg/commitment_schemes/utils/test_settings.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" #include @@ -14,29 +16,29 @@ namespace bb { -template class ShpleminiTest : public CommitmentTest { +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; + static constexpr size_t n = 32; + static constexpr size_t log_n = 5; + static constexpr size_t num_polynomials = 5; + static constexpr size_t num_shiftable = 2; }; -using CurveTypes = ::testing::Types; +using TestSettings = ::testing::Types; -TYPED_TEST_SUITE(ShpleminiTest, CurveTypes); +TYPED_TEST_SUITE(ShpleminiTest, TestSettings); // 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 Curve = typename TypeParam::Curve; + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::AffineElement; + using CK = typename TypeParam::CommitmentKey; - const size_t n = 16; - const size_t log_n = 4; + std::shared_ptr ck = create_commitment_key(this->n); // Generate mock challenges Fr rho = Fr::random_element(); @@ -45,37 +47,40 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) 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 = Polynomial::random(n); - auto poly2 = Polynomial::random(n, /*shiftable*/ 1); - Polynomial poly3(n); - - 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); + auto mle_opening_point = this->random_evaluation_point(this->log_n); + + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); // 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()); + std::vector rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable); // Compute batched multivariate evaluation - Fr batched_evaluation = - std::inner_product(multilinear_evaluations.begin(), multilinear_evaluations.end(), rhos.begin(), Fr::zero()); + Fr batched_evaluation = Fr(0); + size_t idx = 0; + for (auto& eval : pcs_instance_witness.unshifted_evals) { + batched_evaluation += eval * rhos[idx]; + idx++; + } + + for (auto& eval : pcs_instance_witness.shifted_evals) { + batched_evaluation += eval * rhos[idx]; + idx++; + } // 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]; + idx = 0; + GroupElement batched_commitment_unshifted = GroupElement::zero(); + for (auto& comm : pcs_instance_witness.unshifted_commitments) { + batched_commitment_unshifted += comm * rhos[idx]; + idx++; + } + + GroupElement batched_commitment_to_be_shifted = GroupElement::zero(); + for (auto& comm : pcs_instance_witness.to_be_shifted_commitments) { + batched_commitment_to_be_shifted += comm * rhos[idx]; + idx++; + } // Compute expected result manually GroupElement commitment_to_univariate = @@ -93,17 +98,17 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) 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(); + const 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()); + const 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), - RefArray{ eval1, eval2, eval3 }, - RefArray{ eval2_shift, eval3_shift }, + ShpleminiVerifier::batch_multivariate_opening_claims(RefVector(pcs_instance_witness.unshifted_commitments), + RefVector(pcs_instance_witness.to_be_shifted_commitments), + RefVector(pcs_instance_witness.unshifted_evals), + RefVector(pcs_instance_witness.shifted_evals), rho, unshifted_scalar, shifted_scalar, @@ -114,22 +119,25 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching) // Final pairing check GroupElement shplemini_result = batch_mul_native(commitments, scalars); - EXPECT_EQ(commitments.size(), unshifted_commitments.size() + shifted_commitments.size()); + EXPECT_EQ(commitments.size(), + pcs_instance_witness.unshifted_commitments.size() + + pcs_instance_witness.to_be_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 Curve = TypeParam::Curve; + using GeminiProver = GeminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using ShplonkVerifier = ShplonkVerifier_; + using Fr = typename Curve::ScalarField; + using GroupElement = typename Curve::Element; + using Commitment = typename Curve::AffineElement; using Polynomial = typename bb::Polynomial; + using CK = typename TypeParam::CommitmentKey; - const size_t n = 16; - const size_t log_n = 4; + std::shared_ptr ck = create_commitment_key(this->n); // Generate mock challenges Fr rho = Fr::random_element(); @@ -137,62 +145,58 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) 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 = Polynomial::random(n); - auto poly2 = Polynomial::random(n, /*shiftable*/ 1); - Polynomial poly3 = Polynomial::shiftable(n); - - // 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); + std::vector mle_opening_point = this->random_evaluation_point(this->log_n); + + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); // 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()); + std::vector rhos = gemini::powers_of_rho(rho, this->num_polynomials + this->num_shiftable); + + Polynomial batched_unshifted(this->n); + Polynomial batched_to_be_shifted = Polynomial::shiftable(this->n); - Polynomial batched_unshifted(n); - Polynomial batched_to_be_shifted = Polynomial::shiftable(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]); + size_t idx = 0; + for (auto& poly : pcs_instance_witness.unshifted_polynomials) { + batched_unshifted.add_scaled(poly, rhos[idx]); + idx++; + } + + for (auto& poly : pcs_instance_witness.to_be_shifted_polynomials) { + batched_unshifted.add_scaled(poly, rhos[idx]); + 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 fold_polynomials = GeminiProver::compute_fold_polynomials( - log_n, mle_opening_point, std::move(batched_unshifted), std::move(batched_to_be_shifted)); + this->log_n, 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(fold_polynomials[l + 2]); + for (size_t l = 0; l < this->log_n - 1; ++l) { + auto commitment = ck->commit(fold_polynomials[l + 2]); prover_commitments.emplace_back(commitment); } - const auto opening_claims = - GeminiProver::compute_fold_polynomial_evaluations(log_n, std::move(fold_polynomials), gemini_eval_challenge); + const auto opening_claims = GeminiProver::compute_fold_polynomial_evaluations( + this->log_n, std::move(fold_polynomials), gemini_eval_challenge); std::vector prover_evaluations; - for (size_t l = 0; l < log_n; ++l) { + for (size_t l = 0; l < this->log_n; ++l) { const auto& evaluation = opening_claims[l + 1].opening_pair.evaluation; prover_evaluations.emplace_back(evaluation); } - std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, log_n); + std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_eval_challenge, this->log_n); GroupElement expected_result = GroupElement::zero(); - std::vector expected_inverse_vanishing_evals(log_n + 1); + std::vector expected_inverse_vanishing_evals(this->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(); + for (size_t idx = 1; idx < this->log_n + 1; idx++) { + expected_inverse_vanishing_evals[idx] = (shplonk_eval_challenge + r_squares[idx - 1]).invert(); + } Fr current_challenge{ shplonk_batching_challenge * shplonk_batching_challenge }; for (size_t idx = 0; idx < prover_commitments.size(); ++idx) { @@ -202,13 +206,13 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) // Run the ShepliminiVerifier batching method std::vector inverse_vanishing_evals = - ShplonkVerifier::compute_inverted_gemini_denominators(log_n + 1, shplonk_eval_challenge, r_squares); + ShplonkVerifier::compute_inverted_gemini_denominators(this->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, + ShpleminiVerifier::batch_gemini_claims_received_from_prover(this->log_n, prover_commitments, prover_evaluations, inverse_vanishing_evals, @@ -223,4 +227,118 @@ TYPED_TEST(ShpleminiTest, CorrectnessOfGeminiClaimBatching) EXPECT_EQ(shplemini_result, expected_result); } -} // namespace bb +/** + * @brief Test Shplemini with ZK data consisting of a hiding polynomial generated by GeminiProver and Libra polynomials + * used to mask Sumcheck Round Univariates. + * + */ +TYPED_TEST(ShpleminiTest, ShpleminiWithZK) +{ + using ZKData = ZKSumcheckData; + using Curve = TypeParam::Curve; + using ShpleminiProver = ShpleminiProver_; + using ShpleminiVerifier = ShpleminiVerifier_; + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using CK = typename TypeParam::CommitmentKey; + + // Initialize transcript and commitment key + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = create_commitment_key(std::max(this->n, 1ULL << (log_subgroup_size + 1))); + + // Generate Libra polynomials, compute masked concatenated Libra polynomial, commit to it + ZKData zk_sumcheck_data(this->log_n, prover_transcript, ck); + + // Generate multivariate challenge of size CONST_PROOF_SIZE_LOG_N + std::vector const_size_mle_opening_point = this->random_evaluation_point(CONST_PROOF_SIZE_LOG_N); + // Truncate the multivariate challenge to evaluate prover polynomials (As in Sumcheck) + const std::vector mle_opening_point(const_size_mle_opening_point.begin(), + const_size_mle_opening_point.begin() + this->log_n); + + // Generate random prover polynomials, compute their evaluations and commitments + auto pcs_instance_witness = + InstanceWitnessGenerator(this->n, this->num_polynomials, this->num_shiftable, mle_opening_point, ck); + + // Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck challenges + const Fr claimed_inner_product = SmallSubgroupIPAProver::compute_claimed_inner_product( + zk_sumcheck_data, const_size_mle_opening_point, this->log_n); + + prover_transcript->template send_to_verifier("Libra:claimed_evaluation", claimed_inner_product); + + // Instantiate SmallSubgroupIPAProver, this prover sends commitments to Big Sum and Quotient polynomials + auto small_subgroup_ipa_prover = SmallSubgroupIPAProver( + zk_sumcheck_data, const_size_mle_opening_point, claimed_inner_product, prover_transcript, ck); + + // Reduce to KZG or IPA based on the curve used in the test Flavor + const auto opening_claim = ShpleminiProver::prove(this->n, + RefVector(pcs_instance_witness.unshifted_polynomials), + RefVector(pcs_instance_witness.to_be_shifted_polynomials), + const_size_mle_opening_point, + ck, + prover_transcript, + small_subgroup_ipa_prover.get_witness_polynomials()); + + if constexpr (std::is_same_v) { + IPA::compute_opening_proof(this->ck(), opening_claim, prover_transcript); + } else { + KZG::compute_opening_proof(this->ck(), opening_claim, prover_transcript); + } + + // Initialize verifier's transcript + auto verifier_transcript = NativeTranscript::verifier_init_empty(prover_transcript); + + // Start populating Verifier's array of Libra commitments + std::array libra_commitments = {}; + libra_commitments[0] = + verifier_transcript->template receive_from_prover("Libra:concatenation_commitment"); + + // Place Libra data to the transcript + const Fr libra_total_sum = verifier_transcript->template receive_from_prover("Libra:Sum"); + const Fr libra_challenge = verifier_transcript->template get_challenge("Libra:Challenge"); + const Fr libra_evaluation = verifier_transcript->template receive_from_prover("Libra:claimed_evaluation"); + + // Check that transcript is consistent + EXPECT_EQ(libra_total_sum, zk_sumcheck_data.libra_total_sum); + EXPECT_EQ(libra_challenge, zk_sumcheck_data.libra_challenge); + EXPECT_EQ(libra_evaluation, claimed_inner_product); + + // Finalize the array of Libra/SmallSubgroupIpa commitments + libra_commitments[1] = verifier_transcript->template receive_from_prover("Libra:big_sum_commitment"); + libra_commitments[2] = verifier_transcript->template receive_from_prover("Libra:quotient_commitment"); + + // Used to verify the consistency of the evaluations of the concatenated libra polynomial, big sum polynomial, and + // the quotient polynomial computed by SmallSubgroupIPAProver + bool consistency_checked = true; + + // Run Shplemini + const auto batch_opening_claim = + ShpleminiVerifier::compute_batch_opening_claim(this->n, + RefVector(pcs_instance_witness.unshifted_commitments), + RefVector(pcs_instance_witness.to_be_shifted_commitments), + RefVector(pcs_instance_witness.unshifted_evals), + RefVector(pcs_instance_witness.shifted_evals), + const_size_mle_opening_point, + this->vk()->get_g1_identity(), + verifier_transcript, + {}, + true, + &consistency_checked, + libra_commitments, + libra_evaluation); + // Verify claim using KZG or IPA + if constexpr (std::is_same_v) { + auto result = + IPA::reduce_verify_batch_opening_claim(batch_opening_claim, this->vk(), verifier_transcript); + EXPECT_EQ(result, true); + } else { + const auto pairing_points = + KZG::reduce_verify_batch_opening_claim(batch_opening_claim, verifier_transcript); + // Final pairing check: e([Q] - [Q_z] + z[W], [1]_2) = e([W], [x]_2) + EXPECT_EQ(this->vk()->pairing_check(pairing_points[0], pairing_points[1]), true); + } +} + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp index 39f9b27b625..54af7999a2a 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp @@ -374,6 +374,32 @@ template class SmallSubgroupIPAProver { remainder.at(idx - SUBGROUP_SIZE) += remainder.at(idx); } } + + /** + * @brief For test purposes: Compute the sum of the Libra constant term and Libra univariates evaluated at Sumcheck + * challenges. + * + * @param zk_sumcheck_data Contains Libra constant term and scaled Libra univariates + * @param multivariate_challenge Sumcheck challenge + * @param log_circuit_size + */ + static FF compute_claimed_inner_product(ZKSumcheckData& zk_sumcheck_data, + const std::vector& multivariate_challenge, + const size_t& log_circuit_size) + { + const FF libra_challenge_inv = zk_sumcheck_data.libra_challenge.invert(); + // Compute claimed inner product similarly to the SumcheckProver + FF claimed_inner_product = FF{ 0 }; + size_t idx = 0; + for (const auto& univariate : zk_sumcheck_data.libra_univariates) { + claimed_inner_product += univariate.evaluate(multivariate_challenge[idx]); + idx++; + } + // Libra Univariates are mutiplied by the Libra challenge in setup_auxiliary_data(), needs to be undone + claimed_inner_product *= libra_challenge_inv / FF(1 << (log_circuit_size - 1)); + claimed_inner_product += zk_sumcheck_data.constant_term; + return claimed_inner_product; + } }; /** @@ -426,6 +452,18 @@ template class SmallSubgroupIPAVerifier { // Compute the evaluation of the vanishing polynomia Z_H(X) at X = gemini_evaluation_challenge const FF vanishing_poly_eval = gemini_evaluation_challenge.pow(SUBGROUP_SIZE) - FF(1); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1186). Insecure pattern. + bool gemini_challenge_in_small_subgroup = false; + if constexpr (Curve::is_stdlib_type) { + gemini_challenge_in_small_subgroup = (vanishing_poly_eval.get_value() == FF(0).get_value()); + } else { + gemini_challenge_in_small_subgroup = (vanishing_poly_eval == FF(0)); + } + // The probability of this event is negligible but it has to be processed correctly + if (gemini_challenge_in_small_subgroup) { + throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); + } // Construct the challenge polynomial from the sumcheck challenge, the verifier has to evaluate it on its own const std::vector challenge_polynomial_lagrange = compute_challenge_polynomial(multilinear_challenge); @@ -542,4 +580,4 @@ template class SmallSubgroupIPAVerifier { return result; } }; -} // namespace bb +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp new file mode 100644 index 00000000000..cc445d3a394 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.test.cpp @@ -0,0 +1,248 @@ +#include "barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp" +#include "../commitment_key.test.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" +#include "barretenberg/commitment_schemes/utils/test_settings.hpp" + +#include +#include +#include + +namespace bb { +template class SmallSubgroupIPATest : public ::testing::Test { + public: + using Curve = typename Flavor::Curve; + using Transcript = typename Flavor::Transcript; + using FF = typename Curve::ScalarField; + + static constexpr size_t log_circuit_size = 7; + static constexpr size_t circuit_size = 1ULL << log_circuit_size; + + FF evaluation_challenge; + + void SetUp() override { evaluation_challenge = FF::random_element(); } + + static std::vector generate_random_vector(const size_t size) + { + std::vector multivariate_challenge(size); + for (auto& challenge : multivariate_challenge) { + challenge = FF::random_element(); + } + return multivariate_challenge; + } +}; + +using TestFlavors = ::testing::Types; +TYPED_TEST_SUITE(SmallSubgroupIPATest, TestFlavors); + +// Check the correctness of the computation of the claimed inner product and various polynomials needed for the +// SmallSubgroupIPA. +TYPED_TEST(SmallSubgroupIPATest, ProverComputationsCorrectness) +{ + using ZKData = ZKSumcheckData; + using SmallSubgroupIPA = SmallSubgroupIPAProver; + using FF = typename TypeParam::FF; + static constexpr size_t SUBGROUP_SIZE = TypeParam::SUBGROUP_SIZE; + + using CK = typename TypeParam::CommitmentKey; + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + std::vector multivariate_challenge = this->generate_random_vector(this->log_circuit_size); + + const FF claimed_inner_product = SmallSubgroupIPA::compute_claimed_inner_product( + zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + SmallSubgroupIPA small_subgroup_ipa_prover = + SmallSubgroupIPA(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + const Polynomial batched_polynomial = small_subgroup_ipa_prover.get_batched_polynomial(); + const Polynomial libra_concatenated_polynomial = small_subgroup_ipa_prover.get_witness_polynomials()[0]; + const Polynomial batched_quotient = small_subgroup_ipa_prover.get_witness_polynomials()[3]; + const Polynomial challenge_polynomial = small_subgroup_ipa_prover.get_challenge_polynomial(); + + // Check that claimed inner product coincides with the inner product of libra_concatenated_polynomial and + // challenge_polynomial. Since libra_concatenated_polynomial is masked, we also check that masking does not affect + // the evaluations over H + FF inner_product = FF(0); + const std::array domain = zk_sumcheck_data.interpolation_domain; + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + inner_product += + challenge_polynomial.evaluate(domain[idx]) * libra_concatenated_polynomial.evaluate(domain[idx]); + } + EXPECT_TRUE(inner_product == claimed_inner_product); + + // Check that batched polynomial is divisible by Z_H(X) + bool ipa_claim_consistency = true; + for (size_t idx = 0; idx < SUBGROUP_SIZE; idx++) { + ipa_claim_consistency = (batched_polynomial.evaluate(zk_sumcheck_data.interpolation_domain[idx]) == FF{ 0 }) && + ipa_claim_consistency; + } + EXPECT_EQ(ipa_claim_consistency, true); + + // Check that Z_H(X) * Q(X) = batched_polynomial + std::vector Z_H(SUBGROUP_SIZE + 1); + Z_H[0] = -FF(1); + Z_H[SUBGROUP_SIZE] = FF(1); + Polynomial product(batched_polynomial.size()); + + for (size_t i = 0; i < Z_H.size(); i++) { + for (size_t j = 0; j < batched_quotient.size(); j++) { + product.at(i + j) += Z_H[i] * batched_quotient.at(j); + } + } + bool quotient_is_correct = true; + for (const auto& [coeff_expected, coeff] : zip_view(product.coeffs(), batched_polynomial.coeffs())) { + quotient_is_correct = (coeff_expected == coeff) && quotient_is_correct; + } + EXPECT_EQ(quotient_is_correct, true); +} + +// Check the correctness of the evaluations of the challenge_polynomial, Lagrange first, and Lagrange last that the +// verifier has to compute on its own. Compare the values against the evaluations obtaned by applying Lagrange +// interpolation method used by Polynomial class constructor. +TYPED_TEST(SmallSubgroupIPATest, VerifierEvaluations) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using SmallSubgroupIPA = SmallSubgroupIPAVerifier; + + // Extract the constants + static constexpr size_t SUBGROUP_SIZE = TypeParam::SUBGROUP_SIZE; + const FF subgroup_generator_inverse = Curve::subgroup_generator_inverse; + const FF subgroup_generator = subgroup_generator_inverse.invert(); + + // Sample random Lagrange coefficients over H + std::vector challenge_poly_lagrange = this->generate_random_vector(SUBGROUP_SIZE); + + // Evaluate Verifier's polynomials at the challenge + const FF vanishing_poly_eval = this->evaluation_challenge.pow(SUBGROUP_SIZE) - 1; + + // Compute required evaluations using efficient batch evaluation + const auto [challenge_poly_eval, lagrange_first, lagrange_last] = + SmallSubgroupIPA::compute_batched_barycentric_evaluations( + challenge_poly_lagrange, this->evaluation_challenge, subgroup_generator_inverse, vanishing_poly_eval); + + // Compute the evaluations differently, namely, using Lagrange interpolation + std::array interpolation_domain; + interpolation_domain[0] = FF(1); + for (size_t idx = 1; idx < SUBGROUP_SIZE; idx++) { + interpolation_domain[idx] = interpolation_domain[idx - 1] * subgroup_generator; + } + Polynomial challenge_poly_monomial = + Polynomial(interpolation_domain, challenge_poly_lagrange, SUBGROUP_SIZE); + + // Evaluate at the challenge + const FF challenge_poly_expected_eval = challenge_poly_monomial.evaluate(this->evaluation_challenge); + + EXPECT_EQ(challenge_poly_eval, challenge_poly_expected_eval); + + // Compute Lagrange polynomials using interpolation + std::vector lagrange_poly(SUBGROUP_SIZE); + lagrange_poly.at(0) = FF(1); + Polynomial lagrange_first_monomial = Polynomial(interpolation_domain, lagrange_poly, SUBGROUP_SIZE); + EXPECT_EQ(lagrange_first, lagrange_first_monomial.evaluate(this->evaluation_challenge)); + + lagrange_poly.at(0) = FF(0); + lagrange_poly.at(SUBGROUP_SIZE - 1) = FF(1); + Polynomial lagrange_last_monomial = Polynomial(interpolation_domain, lagrange_poly, SUBGROUP_SIZE); + EXPECT_EQ(lagrange_last, lagrange_last_monomial.evaluate(this->evaluation_challenge)); +} + +// Simulate the interaction between the prover and the verifier leading to the consistency check performed by the +// verifier. +TYPED_TEST(SmallSubgroupIPATest, ProverAndVerifierSimple) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using Verifier = SmallSubgroupIPAVerifier; + using Prover = SmallSubgroupIPAProver; + using ZKData = ZKSumcheckData; + using CK = typename TypeParam::CommitmentKey; + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + + std::vector multivariate_challenge = this->generate_random_vector(CONST_PROOF_SIZE_LOG_N); + + const FF claimed_inner_product = + Prover::compute_claimed_inner_product(zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + Prover small_subgroup_ipa_prover = + Prover(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + const std::array, NUM_LIBRA_EVALUATIONS> witness_polynomials = + small_subgroup_ipa_prover.get_witness_polynomials(); + + std::array libra_evaluations = { + witness_polynomials[0].evaluate(this->evaluation_challenge), + witness_polynomials[1].evaluate(this->evaluation_challenge * Curve::subgroup_generator), + witness_polynomials[2].evaluate(this->evaluation_challenge), + witness_polynomials[3].evaluate(this->evaluation_challenge) + }; + + bool consistency_checked = Verifier::check_evaluations_consistency( + libra_evaluations, this->evaluation_challenge, multivariate_challenge, claimed_inner_product); + + EXPECT_TRUE(consistency_checked); +} + +// Check that consistency check fails when some of the prover's data is corrupted. +TYPED_TEST(SmallSubgroupIPATest, ProverAndVerifierSimpleFailure) +{ + using FF = typename TypeParam::FF; + using Curve = typename TypeParam::Curve; + using Verifier = SmallSubgroupIPAVerifier; + using Prover = SmallSubgroupIPAProver; + using ZKData = ZKSumcheckData; + using CK = typename TypeParam::CommitmentKey; + + auto prover_transcript = TypeParam::Transcript::prover_init_empty(); + + // SmallSubgroupIPAProver requires at least CURVE::SUBGROUP_SIZE + 3 elements in the ck. + static constexpr size_t log_subgroup_size = static_cast(numeric::get_msb(Curve::SUBGROUP_SIZE)); + std::shared_ptr ck = + create_commitment_key(std::max(this->circuit_size, 1ULL << (log_subgroup_size + 1))); + + ZKData zk_sumcheck_data(this->log_circuit_size, prover_transcript, ck); + + std::vector multivariate_challenge = this->generate_random_vector(CONST_PROOF_SIZE_LOG_N); + + const FF claimed_inner_product = + Prover::compute_claimed_inner_product(zk_sumcheck_data, multivariate_challenge, this->log_circuit_size); + + Prover small_subgroup_ipa_prover = + Prover(zk_sumcheck_data, multivariate_challenge, claimed_inner_product, prover_transcript, ck); + + std::array, NUM_LIBRA_EVALUATIONS> witness_polynomials = + small_subgroup_ipa_prover.get_witness_polynomials(); + + // Tamper with witness polynomials + witness_polynomials[0].at(0) = FF::random_element(); + + std::array libra_evaluations = { + witness_polynomials[0].evaluate(this->evaluation_challenge), + witness_polynomials[1].evaluate(this->evaluation_challenge * Curve::subgroup_generator), + witness_polynomials[2].evaluate(this->evaluation_challenge), + witness_polynomials[3].evaluate(this->evaluation_challenge) + }; + + bool consistency_checked = Verifier::check_evaluations_consistency( + libra_evaluations, this->evaluation_challenge, multivariate_challenge, claimed_inner_product); + + // Since witness polynomials were modified, the consistency check must fail + EXPECT_FALSE(consistency_checked); +} + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp new file mode 100644 index 00000000000..f3b99924b9f --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/instance_witness_generator.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { +/** + * @brief Constructs random polynomials, computes commitments and corresponding evaluations. + * + * @tparam Curve + */ +template struct InstanceWitnessGenerator { + public: + using CommitmentKey = bb::CommitmentKey; + using Fr = typename Curve::ScalarField; + using Commitment = typename Curve::AffineElement; + using Polynomial = bb::Polynomial; + + std::shared_ptr ck; + std::vector unshifted_polynomials; + std::vector to_be_shifted_polynomials; + std::vector const_size_mle_opening_point; + std::vector unshifted_commitments; + std::vector to_be_shifted_commitments; + std::vector unshifted_evals; + std::vector shifted_evals; + + InstanceWitnessGenerator(const size_t n, + const size_t num_polynomials, + const size_t num_shiftable, + const std::vector& mle_opening_point, + std::shared_ptr& commitment_key) + : ck(commitment_key) // Initialize the commitment key + , unshifted_polynomials(num_polynomials) + , to_be_shifted_polynomials(num_shiftable) + + { + construct_instance_and_witnesses(n, mle_opening_point); + } + + void construct_instance_and_witnesses(size_t n, const std::vector& mle_opening_point) + { + + const size_t num_unshifted = unshifted_polynomials.size() - to_be_shifted_polynomials.size(); + + // Constructs polynomials that are not shifted + for (size_t idx = 0; idx < num_unshifted; idx++) { + unshifted_polynomials[idx] = Polynomial::random(n); + unshifted_commitments.push_back(ck->commit(unshifted_polynomials[idx])); + unshifted_evals.push_back(unshifted_polynomials[idx].evaluate_mle(mle_opening_point)); + } + + // Constructs polynomials that are being shifted + size_t idx = num_unshifted; + for (auto& poly : to_be_shifted_polynomials) { + poly = Polynomial::random(n, /*shiftable*/ 1); + unshifted_polynomials[idx] = poly; + const Commitment comm = this->ck->commit(poly); + unshifted_commitments.push_back(comm); + to_be_shifted_commitments.push_back(comm); + unshifted_evals.push_back(poly.evaluate_mle(mle_opening_point)); + shifted_evals.push_back(poly.evaluate_mle(mle_opening_point, true)); + idx++; + } + } +}; + +} // namespace bb \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp new file mode 100644 index 00000000000..5023d02a8f7 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/test_settings.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include "barretenberg/commitment_schemes/commitment_key.hpp" +#include "barretenberg/ecc/curves/bn254/bn254.hpp" +#include "barretenberg/transcript/transcript.hpp" + +namespace bb { +/** + * @brief Mock Flavors to use ZKSumcheckData and SmallSubgroupIPAProver in the PCS tests. + * + */ +class BN254Settings { + public: + using Curve = curve::BN254; + using CommitmentKey = bb::CommitmentKey; + using Transcript = NativeTranscript; + using FF = typename Curve::ScalarField; + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; +}; + +class GrumpkinSettings { + public: + using Curve = curve::Grumpkin; + using CommitmentKey = bb::CommitmentKey; + using Transcript = NativeTranscript; + using FF = typename Curve::ScalarField; + static constexpr size_t SUBGROUP_SIZE = Curve::SUBGROUP_SIZE; +}; +} // namespace bb \ No newline at end of file