Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
sumcheck_round.test.cpp
Go to the documentation of this file.
1#include "sumcheck_round.hpp"
12
13#include <gtest/gtest.h>
14
15using namespace bb;
16
21TEST(SumcheckRound, SumcheckTupleOfTuplesOfUnivariates)
22{
24 using FF = typename Flavor::FF;
25 using Utils = RelationUtils<Flavor>;
26 using SubrelationSeparators = typename Utils::SubrelationSeparators;
27
28 // Define three linear univariates of different sizes
29 // SumcheckTestFlavor has: ArithmeticRelation (2 subrelations) + DependentTestRelation (1 subrelation)
30 Univariate<FF, 3> univariate_1({ 1, 2, 3 }); // ArithmeticRelation subrelation 0
31 Univariate<FF, 5> univariate_2({ 3, 4, 5, 6, 7 }); // ArithmeticRelation subrelation 1
32 Univariate<FF, 2> univariate_3({ 2, 4 }); // DependentTestRelation subrelation 0
33 const size_t MAX_LENGTH = 5;
34
35 // Construct a tuple of tuples matching SumcheckTestFlavor's relation structure:
36 // {{subrelation_0, subrelation_1}, {subrelation_0}}
37 auto tuple_of_tuples = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_1, univariate_2),
38 flat_tuple::make_tuple(univariate_3));
39
40 // Use scale_univariate_accumulators to scale by challenge powers
41 // SumcheckTestFlavor has 3 subrelations total, so we need 2 separators
42 SubrelationSeparators challenge{};
43 challenge[0] = 5; // Separator between arithmetic subrelations
44 challenge[1] = 25; // Separator before dependent test relation
45 Utils::scale_univariates(tuple_of_tuples, challenge);
46
47 // Use extend_and_batch_univariates to extend to MAX_LENGTH then accumulate
48 GateSeparatorPolynomial<FF> gate_separators({ 1 });
51
52 // Repeat the batching process manually
53 auto result_expected = univariate_1.template extend_to<MAX_LENGTH>() +
54 univariate_2.template extend_to<MAX_LENGTH>() * challenge[0] +
55 univariate_3.template extend_to<MAX_LENGTH>() * challenge[1];
56
57 // Compare final batched univariates
58 EXPECT_EQ(result, result_expected);
59
60 // Reinitialize univariate accumulators to zero
62
63 // Check that reinitialization was successful
64 Univariate<FF, 3> expected_1({ 0, 0, 0 }); // Arithmetic subrelation 0
65 Univariate<FF, 5> expected_2({ 0, 0, 0, 0, 0 }); // Arithmetic subrelation 1
66 Univariate<FF, 2> expected_3({ 0, 0 }); // DependentTest subrelation 0
67 EXPECT_EQ(std::get<0>(std::get<0>(tuple_of_tuples)), expected_1); // Arithmetic subrelation 0
68 EXPECT_EQ(std::get<1>(std::get<0>(tuple_of_tuples)), expected_2); // Arithmetic subrelation 1
69 EXPECT_EQ(std::get<0>(std::get<1>(tuple_of_tuples)), expected_3); // DependentTest subrelation 0
70}
71
76TEST(SumcheckRound, TuplesOfEvaluationArrays)
77{
79 using Utils = RelationUtils<Flavor>;
80 using FF = typename Flavor::FF;
81 using SubrelationSeparators = typename Utils::SubrelationSeparators;
82
83 // SumcheckTestFlavor has 3 subrelations: ArithmeticRelation(2) + DependentTestRelation(1)
84 // So we need arrays matching this structure
85 std::array<FF, 2> evaluations_arithmetic = { 4, 3 }; // ArithmeticRelation's 2 subrelations
86 std::array<FF, 1> evaluations_dependent = { 6 }; // DependentTestRelation's 1 subrelation
87
88 // Construct a tuple matching the relation structure
89 auto tuple_of_arrays = flat_tuple::make_tuple(evaluations_arithmetic, evaluations_dependent);
90
91 // Use scale_and_batch_elements to scale by challenge powers
92 // SumcheckTestFlavor has 3 subrelations, so SubrelationSeparators has 2 elements
93 SubrelationSeparators challenge{ 5, 25 };
94
95 FF result = Utils::scale_and_batch_elements(tuple_of_arrays, challenge);
96
97 // Repeat the batching process manually: first element not scaled, rest scaled by separators
98 auto result_expected = evaluations_arithmetic[0] + // no scaling
99 evaluations_arithmetic[1] * challenge[0] + // separator[0]
100 evaluations_dependent[0] * challenge[1]; // separator[1]
101
102 // Compare batched result
103 EXPECT_EQ(result, result_expected);
104
105 // Reinitialize elements to zero
106 Utils::zero_elements(tuple_of_arrays);
107
108 // Verify all elements were zeroed
109 EXPECT_EQ(std::get<0>(tuple_of_arrays)[0], 0); // ArithmeticRelation subrelation 0
110 EXPECT_EQ(std::get<0>(tuple_of_arrays)[1], 0); // ArithmeticRelation subrelation 1
111 EXPECT_EQ(std::get<1>(tuple_of_arrays)[0], 0); // DependentTestRelation subrelation 0
112}
113
118TEST(SumcheckRound, AddTuplesOfTuplesOfUnivariates)
119{
121 using FF = typename Flavor::FF;
122
123 // Define some arbitrary univariates
124 Univariate<FF, 2> univariate_1({ 1, 2 });
125 Univariate<FF, 2> univariate_2({ 2, 4 });
126 Univariate<FF, 3> univariate_3({ 3, 4, 5 });
127
128 Univariate<FF, 2> univariate_4({ 3, 6 });
129 Univariate<FF, 2> univariate_5({ 8, 1 });
130 Univariate<FF, 3> univariate_6({ 3, 7, 1 });
131
132 Univariate<FF, 2> expected_sum_1 = univariate_1 + univariate_4;
133 Univariate<FF, 2> expected_sum_2 = univariate_2 + univariate_5;
134 Univariate<FF, 3> expected_sum_3 = univariate_3 + univariate_6;
135
136 // Construct two tuples of tuples of univariates
137 auto tuple_of_tuples_1 = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_1),
138 flat_tuple::make_tuple(univariate_2, univariate_3));
139 auto tuple_of_tuples_2 = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_4),
140 flat_tuple::make_tuple(univariate_5, univariate_6));
141
142 RelationUtils<Flavor>::add_nested_tuples(tuple_of_tuples_1, tuple_of_tuples_2);
143
144 EXPECT_EQ(std::get<0>(std::get<0>(tuple_of_tuples_1)), expected_sum_1);
145 EXPECT_EQ(std::get<0>(std::get<1>(tuple_of_tuples_1)), expected_sum_2);
146 EXPECT_EQ(std::get<1>(std::get<1>(tuple_of_tuples_1)), expected_sum_3);
147}
148
154TEST(SumcheckRound, ComputeEffectiveRoundSize)
155{
156 using Flavor = SumcheckTestFlavor; // Non-ZK flavor
157 using FF = typename Flavor::FF;
159
160 // Test Case 1: All witness polynomials have full size
161 {
162 const size_t full_size = 32;
163 const size_t round_size = full_size;
164 SumcheckProverRound<Flavor> round(round_size);
165
166 // Create full-sized polynomials (all entities at full size)
168 for (auto& poly : random_polynomials) {
169 poly = bb::Polynomial<FF>(full_size);
170 }
171
172 ProverPolynomials prover_polynomials;
173 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
174 prover_poly = random_poly.share();
175 }
176
177 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
178 EXPECT_EQ(effective_size, round_size);
179 }
180
181 // Test Case 2: Witness polynomials have reduced active range
182 {
183 const size_t full_size = 64;
184 const size_t active_size = 20; // Active witness data ends at index 20
185 const size_t round_size = full_size;
186 SumcheckProverRound<Flavor> round(round_size);
187
188 // Note: AllEntities ordering is: PrecomputedEntities, WitnessEntities, ShiftedEntities
189 // For SumcheckTestFlavor: Precomputed (0-7), Witness (8-13), Shifted (14-15)
191 size_t poly_idx = 0;
192 for (auto& poly : random_polynomials) {
193 // Witness entities: use shiftable to simulate reduced active range
194 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
196 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
197 } else {
198 // Precomputed and shifted entities at full size
199 poly = bb::Polynomial<FF>(full_size);
200 }
201 poly_idx++;
202 }
203
204 ProverPolynomials prover_polynomials;
205 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
206 prover_poly = random_poly.share();
207 }
208
209 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
210 // Should be rounded up to next even number: 20 is even, so stays 20
211 EXPECT_EQ(effective_size, active_size);
212 EXPECT_LE(effective_size, round_size);
213 }
214
215 // Test Case 3: Odd active size should be rounded up to even
216 {
217 const size_t full_size = 64;
218 const size_t active_size = 23; // Odd number
219 const size_t expected_effective_size = 24; // Rounded up to even
220 const size_t round_size = full_size;
221 SumcheckProverRound<Flavor> round(round_size);
222
224 size_t poly_idx = 0;
225 for (auto& poly : random_polynomials) {
226 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
228 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
229 } else {
230 poly = bb::Polynomial<FF>(full_size);
231 }
232 poly_idx++;
233 }
234
235 ProverPolynomials prover_polynomials;
236 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
237 prover_poly = random_poly.share();
238 }
239
240 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
241 EXPECT_EQ(effective_size, expected_effective_size);
242 }
243
244 // Test Case 4: Different witness polynomials have different active sizes
245 // (should use the maximum)
246 {
247 const size_t full_size = 64;
248 const size_t round_size = full_size;
249 SumcheckProverRound<Flavor> round(round_size);
250
252 size_t poly_idx = 0;
253 size_t witness_idx = 0;
254 for (auto& poly : random_polynomials) {
255 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
257 // Set different sizes for different witness polynomials
258 if (witness_idx == 0) {
259 poly = bb::Polynomial<FF>::shiftable(10, full_size);
260 } else if (witness_idx == 1) {
261 poly = bb::Polynomial<FF>::shiftable(30, full_size); // This is the maximum
262 } else if (witness_idx == 2) {
263 poly = bb::Polynomial<FF>::shiftable(15, full_size);
264 } else {
265 poly = bb::Polynomial<FF>::shiftable(20, full_size);
266 }
267 witness_idx++;
268 } else {
269 poly = bb::Polynomial<FF>(full_size);
270 }
271 poly_idx++;
272 }
273
274 ProverPolynomials prover_polynomials;
275 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
276 prover_poly = random_poly.share();
277 }
278
279 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
280 // Should use maximum witness size (30), which is already even
281 EXPECT_EQ(effective_size, 30);
282 }
283
284 // Test Case 5: Very small active size
285 {
286 const size_t full_size = 128;
287 const size_t active_size = 2;
288 const size_t round_size = full_size;
289 SumcheckProverRound<Flavor> round(round_size);
290
292 size_t poly_idx = 0;
293 for (auto& poly : random_polynomials) {
294 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
296 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
297 } else {
298 poly = bb::Polynomial<FF>(full_size);
299 }
300 poly_idx++;
301 }
302
303 ProverPolynomials prover_polynomials;
304 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
305 prover_poly = random_poly.share();
306 }
307
308 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
309 EXPECT_EQ(effective_size, active_size);
310 EXPECT_GE(effective_size, 2); // Minimum reasonable size
311 }
312}
313
319TEST(SumcheckRound, ComputeEffectiveRoundSizeZK)
320{
321 using Flavor = SumcheckTestFlavorZK; // ZK flavor
322 using FF = typename Flavor::FF;
324
325 const size_t full_size = 64;
326 const size_t round_size = full_size;
327 SumcheckProverRound<Flavor> round(round_size);
328
329 // Create polynomials for ZK flavor
331 for (auto& poly : random_polynomials) {
332 poly = bb::Polynomial<FF>(full_size);
333 }
334
335 ProverPolynomials prover_polynomials;
336 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
337 prover_poly = random_poly.share();
338 }
339
340 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
341 // compute_effective_round_size returns the extent of non-zero polynomial data (capped at round_size).
342 // The excluded_head_size is applied separately in compute_univariate, not here.
343 // Since all polynomials span the full round_size, effective_size == round_size.
344 EXPECT_EQ(effective_size, round_size);
345}
346
352TEST(SumcheckRound, ExtendEdgesShortMonomial)
353{
355 using FF = typename Flavor::FF;
357 using SumcheckRound = SumcheckProverRound<Flavor>;
358 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
359
360 const size_t multivariate_d = 3; // 8 rows
361 const size_t multivariate_n = 1 << multivariate_d;
362 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
363
364 // Create test polynomials where poly[i] = i (simple linear values)
365 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
366 for (auto& poly : test_polynomials) {
367 poly = bb::Polynomial<FF>(multivariate_n);
368 for (size_t i = 0; i < multivariate_n; ++i) {
369 poly.at(i) = FF(i);
370 }
371 }
372
373 ProverPolynomials prover_polynomials;
374 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
375 prover_poly = test_poly.share();
376 }
377
378 SumcheckRound round(multivariate_n);
379
380 // Test that edge extension creates a linear univariate
381 // For poly[i] = i, edge at index 2 gives us points (2, 3)
382 // The univariate U(X) = 2 + X should satisfy U(0) = 2, U(1) = 3
383 {
384 const size_t edge_idx = 2;
385 ExtendedEdges extended_edges;
386
387 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
388
389 // Check the first polynomial (all have the same pattern)
390 auto& first_edge = extended_edges.get_all()[0];
391
392 // Verify the linear interpolation: U(X) = 2 + X
393 FF val_at_0 = first_edge.value_at(0); // Should be 2
394 FF val_at_1 = first_edge.value_at(1); // Should be 3
395
396 EXPECT_EQ(val_at_0, FF(2)) << "Extended univariate should evaluate to 2 at X=0";
397 EXPECT_EQ(val_at_1, FF(3)) << "Extended univariate should evaluate to 3 at X=1";
398
399 // UltraFlavor uses USE_SHORT_MONOMIALS=true, so extended edge is just length 2
400 EXPECT_EQ(first_edge.evaluations.size(), 2) << "UltraFlavor uses short monomials (length 2)";
401
402 info("Extended edges create correct degree-1 univariates for USE_SHORT_MONOMIALS flavors");
403 }
404}
405
411TEST(SumcheckRound, ExtendEdges)
412{
413 // Use a flavor without ShortMonomials
415 using FF = typename Flavor::FF;
417 using SumcheckRound = SumcheckProverRound<Flavor>;
418 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
419
420 const size_t multivariate_d = 3; // 8 rows
421 const size_t multivariate_n = 1 << multivariate_d;
422 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
423
424 // Create test polynomials where poly[i] = i (simple linear values)
425 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
426 for (auto& poly : test_polynomials) {
427 poly = bb::Polynomial<FF>(multivariate_n);
428 for (size_t i = 0; i < multivariate_n; ++i) {
429 poly.at(i) = FF(i);
430 }
431 }
432
433 ProverPolynomials prover_polynomials;
434 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
435 prover_poly = test_poly.share();
436 }
437
438 SumcheckRound round(multivariate_n);
439
440 // Test that edge extension creates a full barycentric extension
441 // For poly[i] = i, edge at index 2 gives us points (2, 3)
442 // The univariate U(X) = 2 + X should extend to MAX_PARTIAL_RELATION_LENGTH
443 {
444 const size_t edge_idx = 2;
445 ExtendedEdges extended_edges;
446
447 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
448
449 // Check the first polynomial (all have the same pattern)
450 auto& first_edge = extended_edges.get_all()[0];
451
452 // Verify the linear interpolation at base points: U(X) = 2 + X
453 EXPECT_EQ(first_edge.value_at(0), FF(2)) << "U(0) should be 2";
454 EXPECT_EQ(first_edge.value_at(1), FF(3)) << "U(1) should be 3";
455
456 // Verify full extension to MAX_PARTIAL_RELATION_LENGTH
457 EXPECT_EQ(first_edge.evaluations.size(), Flavor::MAX_PARTIAL_RELATION_LENGTH)
458 << "Non-short-monomial flavor should extend to MAX_PARTIAL_RELATION_LENGTH";
459
460 // Verify the barycentric extension preserves the linear form at all extended points
461 // The univariate U(X) = 2 + X should give us U(2) = 4, U(3) = 5, U(4) = 6, etc.
462 for (size_t x = 2; x < std::min(static_cast<size_t>(7), first_edge.evaluations.size()); ++x) {
463 FF expected = FF(2 + x);
464 EXPECT_EQ(first_edge.value_at(x), expected)
465 << "Extended univariate U(X) = 2 + X should evaluate to " << (2 + x) << " at X=" << x
466 << " (barycentric extension should preserve linear form)";
467 }
468
469 info("Extended edges correctly perform full barycentric extension to MAX_PARTIAL_RELATION_LENGTH=",
471 }
472}
473
481TEST(SumcheckRound, AccumulateRelationUnivariatesSumcheckTestFlavor)
482{
484 using FF = typename Flavor::FF;
486 using SumcheckRound = SumcheckProverRound<Flavor>;
487
488 const size_t multivariate_d = 2; // log2(circuit_size) = 2 → 4 rows
489 const size_t multivariate_n = 1 << multivariate_d;
490
491 // Test 1: Arithmetic relation with simple values
492 // Simple circuit: w_l + w_r = w_o (using q_l=1, q_r=1, q_o=-1)
493 {
494 info("Test 1: Arithmetic relation accumulation");
495
496 // Create polynomial arrays
497 std::array<FF, multivariate_n> w_l = { FF(1), FF(2), FF(3), FF(4) };
498 std::array<FF, multivariate_n> w_r = { FF(5), FF(6), FF(7), FF(8) };
499 std::array<FF, multivariate_n> w_o = { FF(6), FF(8), FF(10), FF(12) }; // w_l + w_r
500 std::array<FF, multivariate_n> w_4 = { FF(0), FF(0), FF(0), FF(0) };
501 std::array<FF, multivariate_n> q_m = { FF(0), FF(0), FF(0), FF(0) };
502 std::array<FF, multivariate_n> q_l = { FF(1), FF(1), FF(1), FF(1) };
503 std::array<FF, multivariate_n> q_r = { FF(1), FF(1), FF(1), FF(1) };
504 std::array<FF, multivariate_n> q_o = { FF(-1), FF(-1), FF(-1), FF(-1) };
505 std::array<FF, multivariate_n> q_c = { FF(0), FF(0), FF(0), FF(0) };
506 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) }; // Enable arithmetic
507
508 // Create ProverPolynomials
509 ProverPolynomials prover_polynomials;
510 prover_polynomials.q_m = bb::Polynomial<FF>(q_m);
511 prover_polynomials.q_c = bb::Polynomial<FF>(q_c);
512 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
513 prover_polynomials.q_r = bb::Polynomial<FF>(q_r);
514 prover_polynomials.q_o = bb::Polynomial<FF>(q_o);
515 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
516 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
517 prover_polynomials.w_r = bb::Polynomial<FF>(w_r);
518 prover_polynomials.w_o = bb::Polynomial<FF>(w_o);
519 prover_polynomials.w_4 = bb::Polynomial<FF>(w_4);
520
521 // Initialize other required polynomials to zero
522 for (auto& poly : prover_polynomials.get_all()) {
523 if (poly.size() == 0) {
524 poly = bb::Polynomial<FF>(multivariate_n);
525 }
526 }
527
528 // Extend edges from the first edge (index 0)
529 SumcheckRound round(multivariate_n);
530 typename SumcheckRound::ExtendedEdges extended_edges;
531 round.extend_edges(extended_edges, prover_polynomials, 0);
532
533 // Accumulate relation
534 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
536 RelationParameters<FF> relation_parameters{};
537
538 // Scaling factor is set to 1
539 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
540
541 // Get arithmetic relation univariate
542 auto& arith_univariate = std::get<0>(std::get<0>(accumulator));
543
544 // For edge 0->1: relation should be q_arith * (q_l * w_l + q_r * w_r + q_o * w_o + q_c)
545 // At edge 0: 1 * (1*1 + 1*5 + (-1)*6 + 0) = 1 + 5 - 6 = 0 (satisfied)
546 // At edge 1: 1 * (1*2 + 1*6 + (-1)*8 + 0) = 2 + 6 - 8 = 0 (satisfied)
547 EXPECT_EQ(arith_univariate.value_at(0), FF(0)) << "Relation should be satisfied at edge 0";
548 EXPECT_EQ(arith_univariate.value_at(1), FF(0)) << "Relation should be satisfied at edge 1";
549
550 info("Arithmetic relation: verified relation is satisfied for valid circuit");
551 }
552
553 // Test 2: Scaling factor
554 {
555 info("Test 2: Scaling factor application");
556
557 // Create a simple non-zero contribution circuit
558 std::array<FF, multivariate_n> w_l = { FF(2), FF(2), FF(2), FF(2) };
559 std::array<FF, multivariate_n> q_l = { FF(3), FF(3), FF(3), FF(3) };
560 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
561
562 ProverPolynomials prover_polynomials;
563 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
564 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
565 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
566
567 for (auto& poly : prover_polynomials.get_all()) {
568 if (poly.size() == 0) {
569 poly = bb::Polynomial<FF>(multivariate_n);
570 }
571 }
572
573 SumcheckRound round(multivariate_n);
574 typename SumcheckRound::ExtendedEdges extended_edges;
575 round.extend_edges(extended_edges, prover_polynomials, 0);
576
577 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{};
578 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc2{};
581 RelationParameters<FF> relation_parameters{};
582
583 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
584 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
585
586 auto& arith1 = std::get<0>(std::get<0>(acc1));
587 auto& arith2 = std::get<0>(std::get<0>(acc2));
588
589 // With scale=2, result should be exactly double
590 EXPECT_EQ(arith2.value_at(0), arith1.value_at(0) * FF(2)) << "Scaling should multiply contribution";
591 EXPECT_EQ(arith2.value_at(1), arith1.value_at(1) * FF(2)) << "Scaling should multiply contribution";
592
593 info("Scaling factor: verified 2x scaling produces 2x contribution");
594 }
595
596 // Test 3: Multiple accumulations
597 {
598 info("Test 3: Multiple accumulation calls");
599
600 std::array<FF, multivariate_n> w_l = { FF(1), FF(1), FF(1), FF(1) };
601 std::array<FF, multivariate_n> q_l = { FF(5), FF(5), FF(5), FF(5) };
602 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
603
604 ProverPolynomials prover_polynomials;
605 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
606 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
607 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
608
609 for (auto& poly : prover_polynomials.get_all()) {
610 if (poly.size() == 0) {
611 poly = bb::Polynomial<FF>(multivariate_n);
612 }
613 }
614
615 SumcheckRound round(multivariate_n);
616 typename SumcheckRound::ExtendedEdges extended_edges;
617 round.extend_edges(extended_edges, prover_polynomials, 0);
618
619 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
621 RelationParameters<FF> relation_parameters{};
622
623 // First accumulation
624 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
625 auto& arith = std::get<0>(std::get<0>(accumulator));
626 FF value_after_first = arith.value_at(0);
627
628 // Second accumulation (should add to first)
629 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
630 FF value_after_second = arith.value_at(0);
631
632 // Second value should be double the first (since we accumulated the same contribution twice)
633 EXPECT_EQ(value_after_second, value_after_first * FF(2)) << "Second accumulation should add to first";
634
635 info("Multiple accumulations: verified contributions are summed");
636 }
637 // Test 4: Linearly dependent subrelation should NOT be scaled
638 {
639 info("Test 4: DependentTestRelation (linearly dependent) is not scaled");
640
641 // Create a circuit with test relation polynomials
642 std::array<FF, multivariate_n> w_test_1 = { FF(1), FF(2), FF(3), FF(4) };
643 std::array<FF, multivariate_n> q_test = { FF(1), FF(1), FF(1), FF(1) };
644
645 ProverPolynomials prover_polynomials;
646 prover_polynomials.w_test_1 = bb::Polynomial<FF>(w_test_1);
647 prover_polynomials.q_test = bb::Polynomial<FF>(q_test);
648
649 for (auto& poly : prover_polynomials.get_all()) {
650 if (poly.size() == 0) {
651 poly = bb::Polynomial<FF>(multivariate_n);
652 }
653 }
654
655 SumcheckRound round(multivariate_n);
656 typename SumcheckRound::ExtendedEdges extended_edges;
657 round.extend_edges(extended_edges, prover_polynomials, 0);
658
659 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{}, acc2{};
662
663 RelationParameters<FF> relation_parameters{};
664
665 // Accumulate with scale=1 and scale=2
666 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
667 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
668
669 // SumcheckTestFlavor::Relations = tuple<ArithmeticRelation, DependentTestRelation>
670 // ArithmeticRelation is at index 0 (has 2 subrelations, both linearly independent)
671 // DependentTestRelation is at index 1 (has 1 subrelation, linearly dependent)
672
673 // Check DependentTestRelation (index 1) - should NOT be scaled (linearly dependent)
674 auto& dependent_test_acc1 = std::get<0>(std::get<1>(acc1));
675 auto& dependent_test_acc2 = std::get<0>(std::get<1>(acc2));
676 EXPECT_EQ(dependent_test_acc2.value_at(0), dependent_test_acc1.value_at(0))
677 << "DependentTestRelation (linearly dependent) should NOT be scaled";
678 EXPECT_EQ(dependent_test_acc2.value_at(1), dependent_test_acc1.value_at(1))
679 << "DependentTestRelation (linearly dependent) should NOT be scaled";
680
681 info("DependentTestRelation: verified that linearly dependent relation is NOT scaled");
682 }
683}
684
689TEST(SumcheckRound, CheckSumFieldArithmetic)
690{
692 using FF = typename Flavor::FF;
694 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
695
696 info("Test: Field arithmetic edge cases for check_sum");
697
698 // Test 1: Large field elements near modulus
699 {
700 // Create large values near the field modulus
701 FF large_val_1 = FF(-1); // p - 1 (maximum field element)
702 FF large_val_2 = FF(-2); // p - 2
703 FF target = large_val_1 + large_val_2; // Should wrap around: (p-1) + (p-2) = 2p - 3 ≡ -3 (mod p)
704
706 univariate.value_at(0) = large_val_1;
707 univariate.value_at(1) = large_val_2;
708
709 SumcheckVerifierRound verifier_round(target);
710 verifier_round.check_sum(univariate);
711
712 EXPECT_TRUE(!verifier_round.round_failed)
713 << "check_sum should handle large field elements correctly with wraparound";
714 info("Large field elements: check_sum correctly handles values near modulus");
715 }
716
717 // Test 2: Zero elements (edge case)
718 {
719 FF zero = FF(0);
721 univariate.value_at(0) = zero;
722 univariate.value_at(1) = zero;
723
724 SumcheckVerifierRound verifier_round(zero);
725 verifier_round.check_sum(univariate);
726 bool result = !verifier_round.round_failed;
727
728 EXPECT_TRUE(result) << "check_sum should handle zero case correctly";
729 info("Zero case: check_sum correctly handles all-zero values");
730 }
731
732 // Test 3: Mixed signs (positive and negative)
733 {
734 FF positive = FF(12345);
735 FF negative = FF(-12345);
736 FF target = positive + negative; // Should be 0
737
739 univariate.value_at(0) = positive;
740 univariate.value_at(1) = negative;
741
742 SumcheckVerifierRound verifier_round(target);
743 verifier_round.check_sum(univariate);
744 bool result = !verifier_round.round_failed;
745
746 EXPECT_TRUE(result) << "check_sum should handle mixed signs correctly";
747 EXPECT_EQ(target, FF(0)) << "Positive + negative should equal zero";
748 info("Mixed signs: check_sum correctly handles positive + negative = 0");
749 }
750}
751
756TEST(SumcheckRound, CheckSumRoundFailurePersistence)
757{
759 using FF = typename Flavor::FF;
761 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
762
763 info("Test: round_failed flag persistence across multiple checks");
764
765 // Test 1: Single failed check sets flag
766 {
767 info("Test 1: Single failed check sets round_failed flag");
768
769 FF wrong_target = FF(999);
770 SumcheckVerifierRound verifier_round(wrong_target);
771
773 univariate.value_at(0) = FF(10);
774 univariate.value_at(1) = FF(20);
775
776 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should initially be false";
777
778 verifier_round.check_sum(univariate);
779 bool result = !verifier_round.round_failed;
780
781 EXPECT_FALSE(result) << "check_sum should return false for wrong target";
782 EXPECT_TRUE(verifier_round.round_failed) << "round_failed flag should be set after failed check";
783
784 info("Single failure: round_failed flag correctly set");
785 }
786
787 // Test 2: Multiple passing checks keep flag false
788 {
789 info("Test 2: Multiple passing checks keep round_failed false");
790
791 SumcheckVerifierRound verifier_round(FF(30)); // Start with correct target
792
794 univariate1.value_at(0) = FF(10);
795 univariate1.value_at(1) = FF(20);
796
798 univariate2.value_at(0) = FF(5);
799 univariate2.value_at(1) = FF(15);
800
801 verifier_round.check_sum(univariate1);
802 bool result1 = !verifier_round.round_failed;
803 EXPECT_TRUE(result1) << "First check should pass";
804 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should be false after first pass";
805
806 verifier_round.target_total_sum = FF(20); // Update target for second check
807 verifier_round.check_sum(univariate2);
808 bool result2 = !verifier_round.round_failed;
809 EXPECT_TRUE(result2) << "Second check should pass";
810 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should remain false after second pass";
811
812 info("Multiple passes: round_failed correctly remains false");
813 }
814}
815
821TEST(SumcheckRound, CheckSumRecursiveUnsatisfiableWitness)
822{
823 using InnerBuilder = bb::UltraCircuitBuilder;
824 using RecursiveFlavor = bb::UltraRecursiveFlavor_<InnerBuilder>;
825 using FF = typename RecursiveFlavor::FF; // This is field_t<InnerBuilder> (stdlib field)
827 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = RecursiveFlavor::BATCHED_RELATION_PARTIAL_LENGTH;
828
829 info("Test: Recursive check_sum with unsatisfiable witness");
830
831 // Test 1: Unsatisfiable witness - sum doesn't match target
832 {
833 info("Test 1: Unsatisfiable witness where target != S(0) + S(1)");
834
835 InnerBuilder builder;
836
837 // Create witness values that intentionally don't satisfy the check
838 auto native_val_0 = bb::fr(10);
839 auto native_val_1 = bb::fr(20);
840 auto native_wrong_target = bb::fr(100); // Intentionally wrong (correct would be 30)
841
842 // Create stdlib field elements (circuit variables)
843 FF val_0 = FF::from_witness(&builder, native_val_0);
844 FF val_1 = FF::from_witness(&builder, native_val_1);
845 FF wrong_target = FF::from_witness(&builder, native_wrong_target);
846
847 // Create univariate with these values
849 univariate.value_at(0) = val_0;
850 univariate.value_at(1) = val_1;
851
852 // Create verifier round with wrong target
853 SumcheckVerifierRound verifier_round(wrong_target);
854
855 // Call check_sum - this adds constraints to the circuit
856 // In recursive flavor, assert_equal is called which adds a constraint
857 verifier_round.check_sum(univariate);
858 bool check_result = !verifier_round.round_failed;
859
860 // The check_sum itself should return false (based on witness values)
861 EXPECT_FALSE(check_result) << "check_sum should return false for mismatched values";
862
863 // The circuit should have failed (constraint violation detected)
864 EXPECT_TRUE(builder.failed()) << "Builder should detect constraint violation (unsatisfiable witness)";
865
866 info("Unsatisfiable witness: Builder correctly detects constraint violation");
867 }
868
869 // Test 2: Satisfiable witness - sum matches target
870 {
871 info("Test 2: Satisfiable witness where target == S(0) + S(1)");
872
873 InnerBuilder builder;
874
875 // Create witness values that DO satisfy the check
876 auto native_val_0 = bb::fr(10);
877 auto native_val_1 = bb::fr(20);
878 auto native_correct_target = native_val_0 + native_val_1; // 30 (correct)
879
880 // Create stdlib field elements
881 FF val_0 = FF::from_witness(&builder, native_val_0);
882 FF val_1 = FF::from_witness(&builder, native_val_1);
883 FF correct_target = FF::from_witness(&builder, native_correct_target);
884
885 // Create univariate
887 univariate.value_at(0) = val_0;
888 univariate.value_at(1) = val_1;
889
890 // Create verifier round with correct target
891 SumcheckVerifierRound verifier_round(correct_target);
892
893 // Call check_sum
894 verifier_round.check_sum(univariate);
895 bool check_result = !verifier_round.round_failed;
896
897 // Check should pass
898 EXPECT_TRUE(check_result) << "check_sum should return true for matching values";
899
900 // The circuit should NOT have failed
901 EXPECT_FALSE(builder.failed()) << "Builder should not fail for satisfiable witness";
902
903 // Verify the circuit is correct
904 EXPECT_TRUE(CircuitChecker::check(builder)) << "Circuit with satisfiable witness should pass CircuitChecker";
905
906 info("Satisfiable witness: Builder correctly validates constraint satisfaction");
907 }
908
909 // Test 3: Multiple rounds with one failure
910 {
911 info("Test 4: Multiple rounds where one has unsatisfiable witness");
912
913 InnerBuilder builder;
914
915 // First round: correct
916 auto val_0_round1 = FF::from_witness(&builder, bb::fr(10));
917 auto val_1_round1 = FF::from_witness(&builder, bb::fr(20));
918 auto target_round1 = FF::from_witness(&builder, bb::fr(30));
919
921 univariate_1.value_at(0) = val_0_round1;
922 univariate_1.value_at(1) = val_1_round1;
923
924 SumcheckVerifierRound verifier_round(target_round1);
925
926 verifier_round.check_sum(univariate_1);
927 bool result_1 = !verifier_round.round_failed;
928 EXPECT_TRUE(result_1);
929 EXPECT_FALSE(builder.failed()) << "First round should not fail";
930
931 // Second round: WRONG
932 verifier_round.target_total_sum = FF::from_witness(&builder, bb::fr(999)); // Wrong target
933 auto val_0_round2 = FF::from_witness(&builder, bb::fr(5));
934 auto val_1_round2 = FF::from_witness(&builder, bb::fr(15));
935
937 univariate_2.value_at(0) = val_0_round2;
938 univariate_2.value_at(1) = val_1_round2;
939
940 verifier_round.check_sum(univariate_2);
941 bool result_2 = !verifier_round.round_failed;
942 EXPECT_FALSE(result_2) << "Second round should fail";
943
944 // Builder should now have failed
945 EXPECT_TRUE(builder.failed()) << "Builder should detect failure in second round";
946
947 info("Multiple rounds: Builder correctly detects failure in one of multiple rounds");
948 }
949}
950
969template <typename Flavor> void check_row_parallel_matches_scalar(const std::vector<size_t>& zero_entity_indices = {})
970{
971 using FF = typename Flavor::FF;
973
974 static_assert(SupportsSimdSumcheck<Flavor>, "flavor should support the row-parallel path");
975 // The SIMD element path doesn't implement row-skipping; locking the absence of `skip_entire_row` here
976 // keeps future flavors honest -- adding it must come with a row-parallel implementation.
977 static_assert(!isRowSkippable<Flavor, typename Flavor::ProverPolynomials, size_t>,
978 "row-parallel target must not be row-skippable");
979
980 const size_t log_n = 5;
981 const size_t round_size = size_t{ 1 } << log_n; // 32
982
983 // Random full-size polynomials per entity. Parity only cares that the two paths read the same inputs;
984 // proof validity is irrelevant.
986 for (size_t entity = 0; entity < random_polynomials.size(); ++entity) {
987 auto& poly = random_polynomials[entity];
988 poly = bb::Polynomial<FF>(round_size);
989 const bool zero_this =
990 std::find(zero_entity_indices.begin(), zero_entity_indices.end(), entity) != zero_entity_indices.end();
991 if (!zero_this) {
992 for (size_t i = 0; i < round_size; ++i) {
993 poly.at(i) = FF::random_element();
994 }
995 }
996 }
997 ProverPolynomials prover_polynomials;
998 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
999 prover_poly = random_poly.share();
1000 }
1001
1002 const auto relation_parameters = bb::RelationParameters<FF>::get_random();
1003
1004 std::vector<FF> betas(log_n);
1005 for (auto& beta : betas) {
1006 beta = FF::random_element();
1007 }
1008 GateSeparatorPolynomial<FF> gate_separators(betas, log_n);
1009
1010 std::array<FF, Flavor::NUM_SUBRELATIONS - 1> alphas;
1011 for (auto& alpha : alphas) {
1012 alpha = FF::random_element();
1013 }
1014
1015 using Round = SumcheckProverRound<Flavor>;
1016
1017 Round round_scalar(round_size);
1018 const auto result_scalar =
1019 round_scalar.template compute_univariate<FF>(prover_polynomials, relation_parameters, gate_separators, alphas);
1020
1021 Round round_vec(round_size);
1022 const auto result_vec = round_vec.template compute_univariate<bb::VectorField<typename FF::Params>>(
1023 prover_polynomials, relation_parameters, gate_separators, alphas);
1024
1025 EXPECT_EQ(result_scalar, result_vec);
1026}
1027
1028// Non-row-skipping flavors that expose `Relations_` (Ultra/Mega family) dispatch to `VectorField` under
1029// WASM; each must be bit-identical to the scalar (`FF`) path. Add follow-up short-monomial flavors here.
1030template <typename Flavor> class RowParallelParity : public ::testing::Test {};
1031using RowParallelFlavors = ::testing::Types<MegaFlavor, MegaZKFlavor, UltraFlavor, UltraZKFlavor>;
1033
1035{
1036 check_row_parallel_matches_scalar<TypeParam>();
1037}
1038
1039// Standalone (not in the typed suite) because it names `MegaFlavor::EntityId::q_arith`. Zeroing `q_arith`
1040// makes `ArithmeticRelation::skip()` fire on every batch / row, exercising the batched-skip branch on the
1041// row-parallel side and the per-row skip on the scalar side -- they must still agree.
1042TEST(SumcheckRound, RowParallelSkipFiresMatchesScalarMega)
1043{
1044 check_row_parallel_matches_scalar<MegaFlavor>({ static_cast<size_t>(MegaFlavor::EntityId::q_arith) });
1045}
A container for the prover polynomials.
typename Curve::ScalarField FF
static constexpr size_t NUM_SUBRELATIONS
static constexpr size_t NUM_ALL_ENTITIES
static constexpr size_t MAX_PARTIAL_RELATION_LENGTH
static constexpr size_t NUM_WITNESS_ENTITIES
static constexpr size_t NUM_PRECOMPUTED_ENTITIES
static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH
static Polynomial shiftable(size_t virtual_size, bool masked=false)
Utility to create a shiftable polynomial of given virtual size.
static void zero_univariates(auto &tuple)
Set all coefficients of Univariates to zero.
Definition utils.hpp:61
static constexpr void add_nested_tuples(Tuple &tuple_1, const Tuple &tuple_2)
Componentwise addition of nested tuples (tuples of tuples)
Definition utils.hpp:118
Imlementation of the Sumcheck prover round.
size_t compute_effective_round_size(const ProverPolynomialsOrPartiallyEvaluatedMultivariates &multivariates) const
Compute the effective round size by finding the maximum end_index() across witness polynomials.
static void extend_and_batch_univariates(const TupleOfTuplesOfUnivariates &tuple, ExtendedUnivariate &result, const bb::GateSeparatorPolynomial< FF > &gate_separators, const RowDisablingPolynomial< FF > *row_disabling_polynomial=nullptr)
Extend Univariates then sum them multiplying by the current -contributions.
Implementation of the Sumcheck Verifier Round.
void check_sum(bb::Univariate< FF, BATCHED_RELATION_PARTIAL_LENGTH > &univariate)
Check that the round target sum is correct.
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
The recursive counterpart to the "native" Ultra flavor.
A univariate polynomial represented by its values on {0, 1,..., domain_end - 1}.
Fr & value_at(size_t i)
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
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.
field< Bn254FrParams > fr
Definition fr.hpp:155
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
SumcheckTestFlavor_< curve::BN254, false, false > SumcheckTestFlavorFullBary
Full barycentric extension variant.
SumcheckTestFlavor_< curve::BN254, false, true > SumcheckTestFlavor
Base test flavor (BN254, non-ZK, short monomials)
UltraCircuitBuilder_< UltraExecutionTraceBlocks > UltraCircuitBuilder
TEST(BoomerangMegaCircuitBuilder, BasicCircuit)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
TUPLET_INLINE constexpr auto make_tuple(Ts &&... args)
Definition tuplet.hpp:1062
std::vector< Instruction > target
Container for parameters used by the grand product (permutation, lookup) Honk relations.
static RelationParameters get_random()
static field random_element(numeric::RNG *engine=nullptr) noexcept
void check_row_parallel_matches_scalar(const std::vector< size_t > &zero_entity_indices={})
The row-parallel main-loop contribution must equal the scalar path bit-for-bit.
::testing::Types< MegaFlavor, MegaZKFlavor, UltraFlavor, UltraZKFlavor > RowParallelFlavors
Minimal test flavors for sumcheck testing without UltraFlavor dependencies.
VectorField result