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 // Randomly construct the prover polynomials that are input to Sumcheck.
179 // Note: ProverPolynomials are defined as spans so the polynomials they point to need to exist in memory.
180 std::vector<Polynomial<FF>> random_polynomials(NUM_POLYNOMIALS);
181 for (auto& poly : random_polynomials) {
182 poly = random_poly(multivariate_n);
183 }
184 auto full_polynomials = construct_ultra_full_polynomials(random_polynomials);
185
186 auto transcript = Flavor::Transcript::test_prover_init_empty();
187
188 FF alpha = transcript->template get_challenge<FF>("Sumcheck:alpha");
189
190 auto gate_challenges =
191 transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", CONST_PROOF_SIZE_LOG_N);
192
193 SumcheckProver<Flavor> sumcheck(
194 multivariate_n, full_polynomials, transcript, alpha, gate_challenges, {}, CONST_PROOF_SIZE_LOG_N);
195
197
198 if constexpr (Flavor::HasZK) {
199 // ZKData needs univariates for ALL rounds (real + virtual) since libra covers the full range
200 ZKData zk_sumcheck_data = ZKData(CONST_PROOF_SIZE_LOG_N, transcript);
201 output = sumcheck.prove(zk_sumcheck_data);
202 } else {
203 output = sumcheck.prove();
204 }
205 FF u_0 = output.challenge[0];
206 FF u_1 = output.challenge[1];
207 std::vector<FF> expected_values;
208 for (auto& polynomial_ptr : full_polynomials.get_all()) {
209 auto& polynomial = polynomial_ptr;
210 // using knowledge of inputs here to derive the evaluation
211 FF expected_lo = polynomial[0] * (FF(1) - u_0) + polynomial[1] * u_0;
212 expected_lo *= (FF(1) - u_1);
213 FF expected_hi = polynomial[2] * (FF(1) - u_0) + polynomial[3] * u_0;
214 expected_hi *= u_1;
215 expected_values.emplace_back(expected_lo + expected_hi);
216 }
217
218 for (auto [eval, expected] : zip_view(output.claimed_evaluations.get_all(), expected_values)) {
219 eval = expected;
220 }
221 }
222
223 // TODO(#225): make the inputs to this test more interesting, e.g. non-trivial permutations
224 void test_prover_verifier_flow()
225 {
226 const size_t multivariate_d = UseRowDisablingPolynomial<Flavor> ? 4 : 3;
227 const size_t multivariate_n(1 << multivariate_d);
228
229 const size_t virtual_log_n = 6;
230
231 auto full_polynomials = create_satisfiable_trace<Flavor>(multivariate_n);
232
233 // SumcheckTestFlavor doesn't need complex relation parameters (no permutation, lookup, etc.)
234 RelationParameters<FF> relation_parameters{};
235 auto prover_transcript = Flavor::Transcript::test_prover_init_empty();
236 FF prover_alpha = prover_transcript->template get_challenge<FF>("Sumcheck:alpha");
237
238 std::vector<FF> prover_gate_challenges(virtual_log_n);
239 prover_gate_challenges =
240 prover_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", virtual_log_n);
241
242 SumcheckProver<Flavor> sumcheck_prover(multivariate_n,
243 full_polynomials,
244 prover_transcript,
245 prover_alpha,
246 prover_gate_challenges,
247 relation_parameters,
248 virtual_log_n);
249
251 if constexpr (Flavor::HasZK) {
252 ZKData zk_sumcheck_data = ZKData(virtual_log_n, prover_transcript);
253 output = sumcheck_prover.prove(zk_sumcheck_data);
254 } else {
255 output = sumcheck_prover.prove();
256 }
257
258 auto verifier_transcript = Flavor::Transcript::test_verifier_init_empty(prover_transcript);
259
260 FF verifier_alpha = verifier_transcript->template get_challenge<FF>("Sumcheck:alpha");
261
262 auto sumcheck_verifier = SumcheckVerifier<Flavor>(verifier_transcript, verifier_alpha, virtual_log_n);
263
264 std::vector<FF> verifier_gate_challenges(virtual_log_n);
265 verifier_gate_challenges =
266 verifier_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", virtual_log_n);
267
268 auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_gate_challenges);
269
270 auto verified = verifier_output.verified;
271
272 EXPECT_EQ(verified, true);
273 };
274
275 void test_failure_prover_verifier_flow()
276 {
277 const size_t multivariate_d = UseRowDisablingPolynomial<Flavor> ? 4 : 3;
278 const size_t multivariate_n(1 << multivariate_d);
279
280 // Start with a satisfiable trace, then break it
281 auto full_polynomials = create_satisfiable_trace<Flavor>(multivariate_n);
282
283 // Break the circuit at the first active gate (after disabled region for row-disabling flavors).
284 constexpr size_t gate_row = UseRowDisablingPolynomial<Flavor> ? NUM_DISABLED_ROWS_IN_SUMCHECK : 1;
285 full_polynomials.w_l.at(gate_row) = FF(0);
286
287 // SumcheckTestFlavor doesn't need complex relation parameters
288 RelationParameters<FF> relation_parameters{};
289 auto prover_transcript = Flavor::Transcript::test_prover_init_empty();
290 FF prover_alpha = prover_transcript->template get_challenge<FF>("Sumcheck:alpha");
291
292 auto prover_gate_challenges =
293 prover_transcript->template get_dyadic_powers_of_challenge<FF>("Sumcheck:gate_challenge", multivariate_d);
294
295 SumcheckProver<Flavor> sumcheck_prover(multivariate_n,
296 full_polynomials,
297 prover_transcript,
298 prover_alpha,
299 prover_gate_challenges,
300 relation_parameters,
301 multivariate_d);
302
304 if constexpr (Flavor::HasZK) {
305 // construct libra masking polynomials and compute auxiliary data
306 ZKData zk_sumcheck_data = ZKData(multivariate_d, prover_transcript);
307 output = sumcheck_prover.prove(zk_sumcheck_data);
308 } else {
309 output = sumcheck_prover.prove();
310 }
311
312 auto verifier_transcript = Flavor::Transcript::test_verifier_init_empty(prover_transcript);
313
314 FF verifier_alpha = verifier_transcript->template get_challenge<FF>("Sumcheck:alpha");
315
316 SumcheckVerifier<Flavor> sumcheck_verifier(verifier_transcript, verifier_alpha, multivariate_d);
317
318 std::vector<FF> verifier_gate_challenges(multivariate_d);
319 for (size_t idx = 0; idx < multivariate_d; idx++) {
320 verifier_gate_challenges[idx] =
321 verifier_transcript->template get_challenge<FF>("Sumcheck:gate_challenge_" + std::to_string(idx));
322 }
323
324 auto verifier_output = sumcheck_verifier.verify(relation_parameters, verifier_gate_challenges);
325
326 auto verified = verifier_output.verified;
327
328 EXPECT_EQ(verified, false);
329 };
330};
331
332// Define the FlavorTypes using SumcheckTestFlavor variants
333// Note: Only testing short monomials since full barycentric adds complexity without testing sumcheck-specific logic
334// Note: Grumpkin sumcheck requires ZK mode for commitment-based protocol (used in ECCVM/IVC)
335using FlavorTypes = testing::Types<SumcheckTestFlavor, // BN254, non-ZK, short monomials
336 SumcheckTestFlavorZK, // BN254, ZK, short monomials
337 SumcheckTestFlavorGrumpkinZK>; // Grumpkin, ZK, short monomials
338
339TYPED_TEST_SUITE(SumcheckTests, FlavorTypes);
340
341TYPED_TEST(SumcheckTests, PolynomialNormalization)
342{
343 if constexpr (!TypeParam::HasZK) {
344 this->test_polynomial_normalization();
345 } else {
346 GTEST_SKIP() << "Skipping test for ZK-enabled flavors";
347 }
348}
349// Test the prover
350TYPED_TEST(SumcheckTests, Prover)
351{
352 this->test_prover();
353}
354// Tests the prover-verifier flow
355TYPED_TEST(SumcheckTests, ProverAndVerifierSimple)
356{
357 this->test_prover_verifier_flow();
358}
359// This tests is fed an invalid circuit and checks that the verifier would output false.
360TYPED_TEST(SumcheckTests, ProverAndVerifierSimpleFailure)
361{
362 this->test_failure_prover_verifier_flow();
363}
364
365} // namespace
A container for the prover polynomials.
static constexpr bool HasZK
typename Curve::ScalarField FF
static constexpr size_t NUM_ALL_ENTITIES
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
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:294
SumcheckOutput< Flavor > prove()
Non-ZK version: Compute round univariate, place it in transcript, compute challenge,...
Definition sumcheck.hpp:392
A flexible, minimal test flavor for sumcheck testing.
Implementation of the sumcheck Verifier for statements of the form for multilinear polynomials .
Definition sumcheck.hpp:804
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.