Skip to content

Commit 907763d

Browse files
authored
chore: Gemini high degree test (#17055)
Test for knowledge soundness of Gemini. A malicious prover may commit to a high-degree polynomial (larger than what the verifier expects) given a larger SRS size. Positive test: In case if after d folds (d denotes the size of the multilinear challenge point) the resulting polynomial is a constant polynomial (because of the choice of the evaluation point), then the verifier accepts. Negative test: In case if after d folds, the resulting polynomial is not constant, then the verifier rejects with high probability.
1 parent bf99439 commit 907763d

File tree

2 files changed

+112
-5
lines changed

2 files changed

+112
-5
lines changed

barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.test.cpp

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
2222
using CK = CommitmentKey<Curve>;
2323
using VK = VerifierCommitmentKey<Curve>;
2424

25+
// is_big_ck is set to true in the high degree attack test. It uses a larger SRS size (big_ck_size=2^14) and allows
26+
// the prover
27+
// to commit to high degree polynomials (big_n=2^12).
28+
bool is_big_ck = false;
29+
static constexpr size_t big_n = 1UL << 12;
30+
static constexpr size_t small_log_n = 3;
31+
static constexpr size_t big_ck_size = 1 << 14;
32+
inline static CK big_ck = create_commitment_key<CK>(big_ck_size);
33+
bool is_reject_case = false;
34+
2535
static CK ck;
2636
static VK vk;
2737

@@ -34,21 +44,25 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
3444
void execute_gemini_and_verify_claims(std::vector<Fr>& multilinear_evaluation_point,
3545
MockClaimGenerator<Curve> mock_claims)
3646
{
47+
const size_t poly_size = is_big_ck ? big_n : n;
48+
const CK& comkey = is_big_ck ? big_ck : ck;
49+
const size_t multilinear_challenge_size = is_big_ck ? small_log_n : log_n;
50+
3751
auto prover_transcript = NativeTranscript::prover_init_empty();
3852

3953
// Compute:
4054
// - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1
4155
// - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1
4256
auto prover_output = GeminiProver::prove(
43-
this->n, mock_claims.polynomial_batcher, multilinear_evaluation_point, ck, prover_transcript);
57+
poly_size, mock_claims.polynomial_batcher, multilinear_evaluation_point, comkey, prover_transcript);
4458

4559
// The prover output needs to be completed by adding the "positive" Fold claims, i.e. evaluations of Fold^(i) at
4660
// r^{2^i} for i=1, ..., d-1. Although here we are copying polynomials, it is not the case when GeminiProver is
4761
// combined with ShplonkProver.
4862
std::vector<ProverOpeningClaim<Curve>> prover_claims_with_pos_evals;
4963
// `prover_output` consists of d+1 opening claims, we add another d-1 claims for each positive evaluation
5064
// Fold^i(r^{2^i}) for i = 1, ..., d-1
51-
const size_t total_num_claims = 2 * log_n;
65+
const size_t total_num_claims = 2 * multilinear_challenge_size;
5266
prover_claims_with_pos_evals.reserve(total_num_claims);
5367

5468
for (auto& claim : prover_output) {
@@ -81,9 +95,20 @@ template <class Curve> class GeminiTest : public CommitmentTest<Curve> {
8195
multilinear_evaluation_point, mock_claims.claim_batcher, verifier_transcript);
8296

8397
// Check equality of the opening pairs computed by prover and verifier
84-
for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
85-
this->verify_opening_claim(verifier_claim, prover_claim.polynomial, ck);
86-
ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
98+
if (this->is_reject_case) {
99+
bool mismatch = false;
100+
for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
101+
if (prover_claim.opening_pair != verifier_claim.opening_pair) {
102+
mismatch = true;
103+
break;
104+
}
105+
}
106+
EXPECT_TRUE(mismatch) << "Expected a mismatch in opening pairs, but all matched.";
107+
} else {
108+
for (auto [prover_claim, verifier_claim] : zip_view(prover_claims_with_pos_evals, verifier_claims)) {
109+
this->verify_opening_claim(verifier_claim, prover_claim.polynomial, comkey);
110+
ASSERT_EQ(prover_claim.opening_pair, verifier_claim.opening_pair);
111+
}
87112
}
88113
}
89114

@@ -335,5 +360,62 @@ TYPED_TEST(GeminiTest, SoundnessRegression)
335360
}
336361
}
337362

363+
// The prover commits to a higher degree polynomial than what is expected. The test considers the case where
364+
// this polynomial folds down to a constant (equal to the claimed evaluation) after the expected number of rounds
365+
// (due to the choice of the evaluation point). In this case, the verifier accepts.
366+
TYPED_TEST(GeminiTest, HighDegreeAttackAccept)
367+
{
368+
using Fr = typename TypeParam::ScalarField;
369+
370+
this->is_big_ck = true;
371+
372+
// Sample public opening point (u_0, u_1, u_2)
373+
auto u = this->random_evaluation_point(this->small_log_n);
374+
375+
// Choose a claimed eval at `u`
376+
Fr claimed_multilinear_eval = Fr::random_element();
377+
378+
// poly is of high degrees, as the SRS allows for it
379+
Polynomial<Fr> poly(this->big_n);
380+
381+
// Define poly to be of a specific form such that after small_log_n folds with u, it becomes a constant equal to
382+
// claimed_multilinear_eval.
383+
const Fr tail = ((Fr(1) - u[0]) * (Fr(1) - u[1])).invert();
384+
poly.at(4) = claimed_multilinear_eval * tail / u[2];
385+
poly.at(4088) = tail;
386+
poly.at(4092) = -tail * (Fr(1) - u[2]) / u[2];
387+
388+
MockClaimGenerator<TypeParam> mock_claims(
389+
this->big_n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, this->big_ck);
390+
391+
this->execute_gemini_and_verify_claims(u, mock_claims);
392+
}
393+
394+
// The prover commits to a higher degree polynomial than what is expected. The test considers the case where
395+
// this polynomial does not fold down to a constant after the expected number of rounds. In this case, the verifier
396+
// rejects with high probabililty.
397+
TYPED_TEST(GeminiTest, HighDegreeAttackReject)
398+
{
399+
using Fr = typename TypeParam::ScalarField;
400+
using Polynomial = bb::Polynomial<Fr>;
401+
402+
this->is_big_ck = true;
403+
this->is_reject_case = true;
404+
405+
// poly of high degree, as SRS allows for it
406+
Polynomial poly = Polynomial::random(this->big_n);
407+
408+
// Sample public opening point (u_0, u_1, u_2)
409+
auto u = this->random_evaluation_point(this->small_log_n);
410+
411+
// Choose a claimed eval at `u`
412+
Fr claimed_multilinear_eval = Fr::random_element();
413+
414+
MockClaimGenerator<TypeParam> mock_claims(
415+
this->big_n, std::vector{ std::move(poly) }, std::vector<Fr>{ claimed_multilinear_eval }, this->big_ck);
416+
417+
this->execute_gemini_and_verify_claims(u, mock_claims);
418+
}
419+
338420
template <class Curve> typename GeminiTest<Curve>::CK GeminiTest<Curve>::ck;
339421
template <class Curve> typename GeminiTest<Curve>::VK GeminiTest<Curve>::vk;

barretenberg/cpp/src/barretenberg/commitment_schemes/utils/mock_witness_generator.hpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,31 @@ template <typename Curve> struct MockClaimGenerator {
185185
ClaimBatcher{ .unshifted = ClaimBatch{ RefVector(unshifted.commitments), RefVector(unshifted.evals) } };
186186
}
187187

188+
// Generates mock claims by using the custom polynomials provided as input instead of random polynomials. Used for
189+
// the high degree attack tests.
190+
MockClaimGenerator(const size_t poly_size,
191+
const std::vector<Polynomial> custom_unshifted,
192+
const std::vector<Fr>& custom_unshifted_evals,
193+
const CommitmentKey& commitment_key)
194+
195+
: ck(commitment_key)
196+
, polynomial_batcher(poly_size)
197+
{
198+
199+
// ---------- Unshifted ----------
200+
for (size_t i = 0; i < custom_unshifted.size(); ++i) {
201+
auto& p = custom_unshifted[i];
202+
unshifted.commitments.push_back(ck.commit(p));
203+
unshifted.evals.push_back(custom_unshifted_evals[i]);
204+
unshifted.polys.push_back(std::move(p));
205+
}
206+
207+
polynomial_batcher.set_unshifted(RefVector(unshifted.polys));
208+
209+
claim_batcher =
210+
ClaimBatcher{ .unshifted = ClaimBatch{ RefVector(unshifted.commitments), RefVector(unshifted.evals) } };
211+
}
212+
188213
InterleaveData generate_interleaving_inputs(const std::vector<Fr>& u_challenge,
189214
const size_t num_interleaved,
190215
const size_t group_size,

0 commit comments

Comments
 (0)