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..60bcaa42ee06 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,72 @@ 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; + fold_evals.reserve(log_n); + Polynomial fold_2(2); + 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. + fold_1.at(0) = Fr(0); + 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); + + // Get Gemini evaluation challenge + const Fr gemini_r = Fr::random_element(); + + // 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, 2 + std::vector r_squares = gemini::powers_of_evaluation_challenge(gemini_r, log_n); + + // 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₀(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₀(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;