From cd329b70fd23f5498cda57c8917bb3ec67c82180 Mon Sep 17 00:00:00 2001 From: iakovenkos Date: Fri, 7 Mar 2025 13:24:16 +0000 Subject: [PATCH 1/2] add test describing the attack --- .../commitment_schemes/gemini/gemini.test.cpp | 69 +++++++++++++++++++ .../commitment_schemes/ipa/ipa.test.cpp | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp index 14c65de7457d..9139f97d14ef 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp @@ -117,5 +117,74 @@ TYPED_TEST(GeminiTest, DoubleWithShiftAndConcatenation) this->execute_gemini_and_verify_claims(u, mock_claims); } + +/** + * @brief Implementation of the [attack described by Ariel](https://hackmd.io/zm5SDfBqTKKXGpI-zQHtpA?view). + * + */ +TYPED_TEST(GeminiTest, SoundnessRegression) +{ + using Fr = TypeParam::ScalarField; + const size_t log_n = 3; + const size_t n = 8; + + auto prover_transcript = NativeTranscript::prover_init_empty(); + + bb::Polynomial zero_polynomial(n); + auto u = this->random_evaluation_point(this->log_n); + + // Generate a random evaluation v, the prover claims that `zero_polynomial`(u) = v + Fr claimed_multilinear_eval = Fr::random_element(); + + // Go through the Gemini Prover steps: compute fold polynomials and their evaluations + std::vector fold_evals; + std::vector> fold_polynomials; + fold_polynomials.reserve(log_n - 1); + fold_evals.reserve(log_n); + + // Tamper with `fold_2` and `fold_1'. + Polynomial fold_2(2); + // This allows the prover to make sure that fold_1(r^2) = 0, and hence fold_0(r) computed by the verifier is also 0. + // While at the same time the prover can open fold_2(-r^2) to its honest value. + fold_2.at(0) = + claimed_multilinear_eval * u[1].sqr() * ((Fr(1) - u[2]) * u[1].sqr() - u[2] * (Fr(1) - u[1]).sqr()).invert(); + fold_2.at(1) = -(Fr(1) - u[1]).sqr() * fold_2.at(0) * (u[1].sqr()).invert(); + + // The coefficients of fold_1 are determined by the constant term of fold_2. + Polynomial fold_1(4); + fold_1.at(0) = Fr(0); + fold_1.at(1) = Fr(2) * fold_2.at(0) * u[1].invert(); + fold_1.at(2) = -(Fr(1) - u[1]) * fold_1.at(1) * u[1].invert(); + fold_1.at(3) = Fr(0); + + fold_polynomials = { fold_1, fold_2 }; + + // Get Gemini evaluation challenge + const Fr gemini_r = Fr::random_element(); + + // Place honest eval of `fold_0(-r)` to the vector of evals + fold_evals.emplace_back(Fr(0)); + + // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1 + std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_r, log_n); + + // Compute honest evaluations `fold_1(-r^2)` and `fold_2(-r^4)` + for (size_t l = 0; l < log_n - 1; ++l) { + Fr evaluation = fold_polynomials[l].evaluate(-r_squares[l + 1]); + fold_evals.emplace_back(evaluation); + } + + // Compute the powers of r used by the verifier. It is an artifact of the const proof size logic. + const std::vector gemini_eval_challenge_powers = + gemini::powers_of_evaluation_challenge(gemini_r, CONST_PROOF_SIZE_LOG_N); + + // Compute `fold_0(r)` as Verifier would compute it + const Fr full_a_0_pos = GeminiVerifier_::compute_gemini_batched_univariate_evaluation( + log_n, claimed_multilinear_eval, u, gemini_eval_challenge_powers, fold_evals); + + // Check that `fold_0(r) = 0`. Therefore, a malicious prover could open it using the commitment to the zero + // polynomial. + EXPECT_TRUE(full_a_0_pos == Fr(0)); +} template std::shared_ptr::CK> GeminiTest::ck = nullptr; template std::shared_ptr::VK> GeminiTest::vk = nullptr; 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 3da495da900d..86ac4ff43969 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -366,4 +366,4 @@ TEST_F(IPATest, ShpleminiIPAShiftsRemoval) EXPECT_EQ(result, true); } std::shared_ptr IPATest::ck = nullptr; -std::shared_ptr IPATest::vk = nullptr; \ No newline at end of file +std::shared_ptr IPATest::vk = nullptr; From 4f2d88292c1edb3146177e4f1260cb8ce5b14d46 Mon Sep 17 00:00:00 2001 From: iakovenkos Date: Fri, 7 Mar 2025 13:50:21 +0000 Subject: [PATCH 2/2] docs cleanup --- .../commitment_schemes/gemini/gemini.test.cpp | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp index 9139f97d14ef..60bcaa42ee06 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp @@ -138,53 +138,51 @@ TYPED_TEST(GeminiTest, SoundnessRegression) // Go through the Gemini Prover steps: compute fold polynomials and their evaluations std::vector fold_evals; - std::vector> fold_polynomials; - fold_polynomials.reserve(log_n - 1); fold_evals.reserve(log_n); - - // Tamper with `fold_2` and `fold_1'. Polynomial fold_2(2); - // This allows the prover to make sure that fold_1(r^2) = 0, and hence fold_0(r) computed by the verifier is also 0. - // While at the same time the prover can open fold_2(-r^2) to its honest value. + Polynomial fold_1(4); + + // By defining the coefficients of fold polynomials as below, a malicious prover can make sure that the values + // fold₁(r²) = 0, and hence fold₀(r), computed by the verifier, are 0. At the same time, the prover can open + // fold₁(-r²) and fold₂(-r⁴)`to their honest value. + + // fold₂[0] = claimed_multilinear_eval ⋅ u₁² ⋅ [(1 - u₂) ⋅ u₁² - u₂ ⋅ (1 - u₁)²]⁻¹ fold_2.at(0) = claimed_multilinear_eval * u[1].sqr() * ((Fr(1) - u[2]) * u[1].sqr() - u[2] * (Fr(1) - u[1]).sqr()).invert(); + // fold₂[1] = - (1 - u₁)² ⋅ fold₂[0] ⋅ u₁⁻² fold_2.at(1) = -(Fr(1) - u[1]).sqr() * fold_2.at(0) * (u[1].sqr()).invert(); // The coefficients of fold_1 are determined by the constant term of fold_2. - Polynomial fold_1(4); fold_1.at(0) = Fr(0); - fold_1.at(1) = Fr(2) * fold_2.at(0) * u[1].invert(); - fold_1.at(2) = -(Fr(1) - u[1]) * fold_1.at(1) * u[1].invert(); + fold_1.at(1) = Fr(2) * fold_2.at(0) * u[1].invert(); // fold₁[1] = 2 ⋅ fold₂[0] / u₁ + fold_1.at(2) = -(Fr(1) - u[1]) * fold_1.at(1) * u[1].invert(); // fold₁[2] = -(1 - u₁) ⋅ fold₁[1] / u₁ fold_1.at(3) = Fr(0); - fold_polynomials = { fold_1, fold_2 }; - // Get Gemini evaluation challenge const Fr gemini_r = Fr::random_element(); - // Place honest eval of `fold_0(-r)` to the vector of evals + // Place honest eval of fold₀(-r) to the vector of evals fold_evals.emplace_back(Fr(0)); - // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1 + // Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, 2 std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_r, log_n); - // Compute honest evaluations `fold_1(-r^2)` and `fold_2(-r^4)` - for (size_t l = 0; l < log_n - 1; ++l) { - Fr evaluation = fold_polynomials[l].evaluate(-r_squares[l + 1]); - fold_evals.emplace_back(evaluation); - } + // Compute honest evaluations fold₁(-r²) and fold₂(-r⁴) + fold_evals.emplace_back(fold_1.evaluate(-r_squares[1])); + fold_evals.emplace_back(fold_2.evaluate(-r_squares[2])); // Compute the powers of r used by the verifier. It is an artifact of the const proof size logic. const std::vector gemini_eval_challenge_powers = gemini::powers_of_evaluation_challenge(gemini_r, CONST_PROOF_SIZE_LOG_N); - // Compute `fold_0(r)` as Verifier would compute it + // Compute fold₀(r) as Verifier would compute it const Fr full_a_0_pos = GeminiVerifier_::compute_gemini_batched_univariate_evaluation( log_n, claimed_multilinear_eval, u, gemini_eval_challenge_powers, fold_evals); - // Check that `fold_0(r) = 0`. Therefore, a malicious prover could open it using the commitment to the zero + // Check that fold₀(r) = 0. Therefore, a malicious prover could open it using the commitment to the zero // polynomial. EXPECT_TRUE(full_a_0_pos == Fr(0)); } + template std::shared_ptr::CK> GeminiTest::ck = nullptr; template std::shared_ptr::VK> GeminiTest::vk = nullptr;