Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
sumcheck.test.cpp
Go to the documentation of this file.
1#include "sumcheck.hpp"
4
7#include <gtest/gtest.h>
8
9using namespace bb;
10
11namespace {
12
26template <typename Flavor> typename Flavor::ProverPolynomials create_satisfiable_trace(size_t circuit_size)
27{
28 using FF = typename Flavor::FF;
31
32 ProverPolynomials full_polynomials;
33
34 // Initialize precomputed polynomials (selectors)
35 for (auto& poly : full_polynomials.get_precomputed()) {
36 poly = Polynomial(circuit_size);
37 }
38
39 // Initialize witness polynomials as shiftable (start_index = 1) to allow shifting
40 for (auto& poly : full_polynomials.get_witness()) {
41 poly = Polynomial::shiftable(circuit_size);
42 }
43
44 // Initialize shifted polynomials (will be populated by set_shifted())
45 for (auto& poly : full_polynomials.get_shifted()) {
46 poly = Polynomial(circuit_size);
47 }
48
49 // Create a simple arithmetic circuit with a few gates.
50 // Gates start after the disabled region (NUM_DISABLED_ROWS_IN_SUMCHECK = 4) for flavors with row disabling.
51 constexpr size_t gate_start = UseRowDisablingPolynomial<Flavor> ? NUM_DISABLED_ROWS_IN_SUMCHECK : 1;
52
53 // Gate 0: Addition gate: w_l + w_r = w_o (1 + 1 = 2)
54 if (circuit_size > gate_start) {
55 full_polynomials.w_l.at(gate_start) = FF(1);
56 full_polynomials.w_r.at(gate_start) = FF(1);
57 full_polynomials.w_o.at(gate_start) = FF(2);
58 full_polynomials.q_l.at(gate_start) = FF(1);
59 full_polynomials.q_r.at(gate_start) = FF(1);
60 full_polynomials.q_o.at(gate_start) = FF(-1);
61 full_polynomials.q_arith.at(gate_start) = FF(1);
62 }
63
64 // Gate 1: Multiplication gate: w_l * w_r = w_o (2 * 2 = 4)
65 if (circuit_size > gate_start + 1) {
66 full_polynomials.w_l.at(gate_start + 1) = FF(2);
67 full_polynomials.w_r.at(gate_start + 1) = FF(2);
68 full_polynomials.w_o.at(gate_start + 1) = FF(4);
69 full_polynomials.q_m.at(gate_start + 1) = FF(1);
70 full_polynomials.q_o.at(gate_start + 1) = FF(-1);
71 full_polynomials.q_arith.at(gate_start + 1) = FF(1);
72 }
73
74 // For ZK flavors: add randomness to the disabled rows (first 4 rows) which are masked by row-disabling polynomial.
75 // These rows don't need to satisfy the relation because they're disabled.
76 if constexpr (Flavor::HasZK) {
77 for (size_t i = 1; i < NUM_DISABLED_ROWS_IN_SUMCHECK; ++i) { // start at 1 (row 0 is zero row for shiftable)
78 full_polynomials.w_l.at(i) = FF::random_element();
79 full_polynomials.w_r.at(i) = FF::random_element();
80 full_polynomials.w_o.at(i) = FF::random_element();
81 full_polynomials.w_4.at(i) = FF::random_element();
82 full_polynomials.w_test_1.at(i) = FF::random_element();
83 full_polynomials.w_test_2.at(i) = FF::random_element();
84 }
85 }
86
87 // Compute shifted polynomials using the set_shifted() method
88 full_polynomials.set_shifted();
89
90 return full_polynomials;
91}
92
93template <typename Flavor> class SumcheckTests : public ::testing::Test {
94 public:
95 using FF = typename Flavor::FF;
97 using ZKData = ZKSumcheckData<Flavor>;
98
99 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
100 static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); }
101
102 Polynomial<FF> random_poly(size_t size)
103 {
104 auto poly = bb::Polynomial<FF>(size);
105 for (auto& coeff : poly.coeffs()) {
106 coeff = FF::random_element();
107 }
108 return poly;
109 }
110
111 ProverPolynomials construct_ultra_full_polynomials(auto& input_polynomials)
112 {
113 ProverPolynomials full_polynomials;
114 for (auto [full_poly, input_poly] : zip_view(full_polynomials.get_all(), input_polynomials)) {
115 full_poly = input_poly.share();
116 }
117 return full_polynomials;
118 }
119
120 void test_polynomial_normalization()
121 {
122 // TODO(#225)(Cody): We should not use real constants like this in the tests, at least not in so many of them.
123 const size_t multivariate_d(3);
124 const size_t multivariate_n(1 << multivariate_d);
125
126 // Randomly construct the prover polynomials that are input to Sumcheck.
127 // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory.
128 std::vector<bb::Polynomial<FF>> random_polynomials(NUM_POLYNOMIALS);
129 for (auto& poly : random_polynomials) {
130 poly = random_poly(multivariate_n);
131 }
132 auto full_polynomials = construct_ultra_full_polynomials(random_polynomials);
133
134 auto transcript = Flavor::Transcript::test_prover_init_empty();
135
136 FF alpha = transcript->template get_challenge<FF>("Sumcheck:alpha");
137
138 std::vector<FF> gate_challenges(multivariate_d);
139 for (size_t idx = 0; idx < multivariate_d; idx++) {
140 gate_challenges[idx] =
141 transcript->template get_challenge<FF>("Sumcheck:gate_challenge_" + std::to_string(idx));
142 }
143
144 SumcheckProver<Flavor> sumcheck(
145 multivariate_n, full_polynomials, transcript, alpha, gate_challenges, {}, multivariate_d);
146
147 auto output = sumcheck.prove();
148
149 FF u_0 = output.challenge[0];
150 FF u_1 = output.challenge[1];
151 FF u_2 = output.challenge[2];
152
153 /* sumcheck.prove() terminates with sumcheck.multivariates.folded_polynoimals as an array such that
154 * sumcheck.multivariates.folded_polynoimals[i][0] is the evaluatioin of the i'th multivariate at the vector of
155 challenges u_i. What does this mean?
156
157 Here we show that if the multivariate is F(X0, X1, X2) defined as above, then what we get is F(u0, u1, u2) and
158 not, say F(u2, u1, u0). This is in accordance with Adrian's thesis (cf page 9).
159 */
160
161 // Check the correctness of the multilinear evaluations produced by Sumcheck by directly evaluating
162 // the full polynomials at challenge u via the evaluate_mle() function
163 std::vector<FF> u_challenge = { u_0, u_1, u_2 };
164 for (auto [full_poly, claimed_eval] :
165 zip_view(full_polynomials.get_all(), output.claimed_evaluations.get_all())) {
166 Polynomial<FF> poly(full_poly);
167 auto v_expected = poly.evaluate_mle(u_challenge);
168 EXPECT_EQ(v_expected, claimed_eval);
169 }
170 }
171
172 void test_prover()
173 {
174 // Need at least 4 rounds for row-disabling flavors (disabled region = 4 rows = 2^2, needs n > 2^2)
175 const size_t multivariate_d = UseRowDisablingPolynomial<Flavor> ? 4 : 2;
176 const size_t multivariate_n(1 << multivariate_d);
177
178 // Grumpkin flavors run at a fixed number of rounds (no padding); their Libra concatenation only fits
179 // CONST_ECCVM_LOG_N * LIBRA_UNIVARIATES_LENGTH + 1 coefficients in the SmallSubgroupIPA subgroup.
180 const size_t virtual_log_n = IsGrumpkinFlavor<Flavor> ? CONST_ECCVM_LOG_N : CONST_PROOF_SIZE_LOG_N;
181
182 // Randomly construct the prover polynomials that are input to Sumcheck.
183 // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory.
184 std::vector<Polynomial<FF>> random_polynomials(NUM_POLYNOMIALS);
185 for (auto& poly : random_polynomials) {
186 poly = random_poly(multivariate_n);
187 }
188 auto full_polynomials = construct_ultra_full_polynomials(random_polynomials);
189
190 auto transcript = Flavor::Transcript::test_prover_init_empty();
191
192 FF alpha = transcript->template get_challenge<FF>("Sumcheck:alpha");
193
194 auto gate_challenges =
195 transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", virtual_log_n);
196
197 SumcheckProver<Flavor> sumcheck(
198 multivariate_n, full_polynomials, transcript, alpha, gate_challenges, {}, virtual_log_n);
199
201
202 if constexpr (Flavor::HasZK) {
203 // ZKData needs univariates for ALL rounds (real + virtual) since libra covers the full range
204 ZKData zk_sumcheck_data = ZKData(virtual_log_n, transcript);
205 output = sumcheck.prove(zk_sumcheck_data);
206 } else {
207 output = sumcheck.prove();
208 }
209 FF u_0 = output.challenge[0];
210 FF u_1 = output.challenge[1];
211 std::vector<FF> expected_values;
212 for (auto& polynomial_ptr : full_polynomials.get_all()) {
213 auto& polynomial = polynomial_ptr;
214 // using knowledge of inputs here to derive the evaluation
215 FF expected_lo = polynomial[0] * (FF(1) - u_0) + polynomial[1] * u_0;
216 expected_lo *= (FF(1) - u_1);
217 FF expected_hi = polynomial[2] * (FF(1) - u_0) + polynomial[3] * u_0;
218 expected_hi *= u_1;
219 expected_values.emplace_back(expected_lo + expected_hi);
220 }
221
222 for (auto [eval, expected] : zip_view(output.claimed_evaluations.get_all(), expected_values)) {
223 eval = expected;
224 }
225 }
226
227 // TODO(#225): make the inputs to this test more interesting, e.g. non-trivial permutations
228 void test_prover_verifier_flow()
229 {
230 const size_t multivariate_d = UseRowDisablingPolynomial<Flavor> ? 4 : 3;
231 const size_t multivariate_n(1 << multivariate_d);
232
233 const size_t virtual_log_n = 6;
234
235 auto full_polynomials = create_satisfiable_trace<Flavor>(multivariate_n);
236
237 // SumcheckTestFlavor doesn't need complex relation parameters (no permutation, lookup, etc.)
238 RelationParameters<FF> relation_parameters{};
239 auto prover_transcript = Flavor::Transcript::test_prover_init_empty();
240 FF prover_alpha = prover_transcript->template get_challenge<FF>("Sumcheck:alpha");
241
242 std::vector<FF> prover_gate_challenges(virtual_log_n);
243 prover_gate_challenges =
244 prover_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", virtual_log_n);
245
246 SumcheckProver<Flavor> sumcheck_prover(multivariate_n,
247 full_polynomials,
248 prover_transcript,
249 prover_alpha,
250 prover_gate_challenges,
251 relation_parameters,
252 virtual_log_n);
253
255 if constexpr (Flavor::HasZK) {
256 ZKData zk_sumcheck_data = ZKData(virtual_log_n, prover_transcript);
257 output = sumcheck_prover.prove(zk_sumcheck_data);
258 } else {
259 output = sumcheck_prover.prove();
260 }
261
262 auto verifier_transcript = Flavor::Transcript::test_verifier_init_empty(prover_transcript);
263
264 FF verifier_alpha = verifier_transcript->template get_challenge<FF>("Sumcheck:alpha");
265
266 auto sumcheck_verifier = SumcheckVerifier<Flavor>(verifier_transcript, verifier_alpha, virtual_log_n);
267
268 std::vector<FF> verifier_gate_challenges(virtual_log_n);
269 verifier_gate_challenges =
270 verifier_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", virtual_log_n);
271
272 auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_gate_challenges);
273
274 auto verified = verifier_output.verified;
275
276 EXPECT_EQ(verified, true);
277 };
278
279 void test_failure_prover_verifier_flow()
280 {
281 const size_t multivariate_d = UseRowDisablingPolynomial<Flavor> ? 4 : 3;
282 const size_t multivariate_n(1 << multivariate_d);
283
284 // Start with a satisfiable trace, then break it
285 auto full_polynomials = create_satisfiable_trace<Flavor>(multivariate_n);
286
287 // Break the circuit at the first active gate (after disabled region for row-disabling flavors).
288 constexpr size_t gate_row = UseRowDisablingPolynomial<Flavor> ? NUM_DISABLED_ROWS_IN_SUMCHECK : 1;
289 full_polynomials.w_l.at(gate_row) = FF(0);
290
291 // SumcheckTestFlavor doesn't need complex relation parameters
292 RelationParameters<FF> relation_parameters{};
293 auto prover_transcript = Flavor::Transcript::test_prover_init_empty();
294 FF prover_alpha = prover_transcript->template get_challenge<FF>("Sumcheck:alpha");
295
296 auto prover_gate_challenges =
297 prover_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", multivariate_d);
298
299 SumcheckProver<Flavor> sumcheck_prover(multivariate_n,
300 full_polynomials,
301 prover_transcript,
302 prover_alpha,
303 prover_gate_challenges,
304 relation_parameters,
305 multivariate_d);
306
308 if constexpr (Flavor::HasZK) {
309 // construct libra masking polynomials and compute auxiliary data
310 ZKData zk_sumcheck_data = ZKData(multivariate_d, prover_transcript);
311 output = sumcheck_prover.prove(zk_sumcheck_data);
312 } else {
313 output = sumcheck_prover.prove();
314 }
315
316 auto verifier_transcript = Flavor::Transcript::test_verifier_init_empty(prover_transcript);
317
318 FF verifier_alpha = verifier_transcript->template get_challenge<FF>("Sumcheck:alpha");
319
320 SumcheckVerifier<Flavor> sumcheck_verifier(verifier_transcript, verifier_alpha, multivariate_d);
321
322 std::vector<FF> verifier_gate_challenges(multivariate_d);
323 for (size_t idx = 0; idx < multivariate_d; idx++) {
324 verifier_gate_challenges[idx] =
325 verifier_transcript->template get_challenge<FF>("Sumcheck:gate_challenge_" + std::to_string(idx));
326 }
327
328 auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_gate_challenges);
329
330 auto verified = verifier_output.verified;
331
332 EXPECT_EQ(verified, false);
333 };
334};
335
336// Define the FlavorTypes using SumcheckTestFlavor variants
337// Note: Only testing short monomials since full barycentric adds complexity without testing sumcheck-specific logic
338// Note: Grumpkin sumcheck requires ZK mode for commitment-based protocol (used in ECCVM/IVC)
339using FlavorTypes = testing::Types<SumcheckTestFlavor, // BN254, non-ZK, short monomials
340 SumcheckTestFlavorZK, // BN254, ZK, short monomials
341 SumcheckTestFlavorGrumpkinZK>; // Grumpkin, ZK, short monomials
342
343TYPED_TEST_SUITE(SumcheckTests, FlavorTypes);
344
345TYPED_TEST(SumcheckTests, PolynomialNormalization)
346{
347 if constexpr (!TypeParam::HasZK) {
348 this->test_polynomial_normalization();
349 } else {
350 GTEST_SKIP() << "Skipping test for ZK-enabled flavors";
351 }
352}
353// Test the prover
354TYPED_TEST(SumcheckTests, Prover)
355{
356 this->test_prover();
357}
358// Tests the prover-verifier flow
359TYPED_TEST(SumcheckTests, ProverAndVerifierSimple)
360{
361 this->test_prover_verifier_flow();
362}
363// This tests is fed an invalid circuit and checks that the verifier would output false.
364TYPED_TEST(SumcheckTests, ProverAndVerifierSimpleFailure)
365{
366 this->test_failure_prover_verifier_flow();
367}
368
369} // namespace
A container for the prover polynomials.
static constexpr bool HasZK
typename Curve::ScalarField FF
static constexpr size_t NUM_ALL_ENTITIES
static Polynomial shiftable(size_t virtual_size, bool masked=false)
Utility to create a shiftable polynomial of given virtual size.
The implementation of the sumcheck Prover for statements of the form for multilinear polynomials .
Definition sumcheck.hpp:304
SumcheckOutput< Flavor > prove()
Non-ZK version: Compute round univariate, place it in transcript, compute challenge,...
Definition sumcheck.hpp:398
A flexible, minimal test flavor for sumcheck testing.
Implementation of the sumcheck Verifier for statements of the form for multilinear polynomials .
Definition sumcheck.hpp:802
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
testing::Types< UltraFlavor, UltraKeccakFlavor, MegaFlavor > FlavorTypes
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
SumcheckTestFlavor_< curve::BN254, true, true > SumcheckTestFlavorZK
Zero-knowledge variant.
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
SumcheckTestFlavor_< curve::BN254, false, true > SumcheckTestFlavor
Base test flavor (BN254, non-ZK, short monomials)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
Container for parameters used by the grand product (permutation, lookup) Honk relations.
Contains the evaluations of multilinear polynomials at the challenge point . These are computed by S...
This structure is created to contain various polynomials and constants required by ZK Sumcheck.
static field random_element(numeric::RNG *engine=nullptr) noexcept
Minimal test flavors for sumcheck testing without UltraFlavor dependencies.