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"
8
9#include <gtest/gtest.h>
10
11using namespace bb;
12
17TEST(SumcheckRound, SumcheckTupleOfTuplesOfUnivariates)
18{
20 using FF = typename Flavor::FF;
21 using Utils = RelationUtils<Flavor>;
22 using SubrelationSeparators = typename Utils::SubrelationSeparators;
23
24 // Define three linear univariates of different sizes
25 // SumcheckTestFlavor has: ArithmeticRelation (2 subrelations) + DependentTestRelation (1 subrelation)
26 Univariate<FF, 3> univariate_1({ 1, 2, 3 }); // ArithmeticRelation subrelation 0
27 Univariate<FF, 5> univariate_2({ 3, 4, 5, 6, 7 }); // ArithmeticRelation subrelation 1
28 Univariate<FF, 2> univariate_3({ 2, 4 }); // DependentTestRelation subrelation 0
29 const size_t MAX_LENGTH = 5;
30
31 // Construct a tuple of tuples matching SumcheckTestFlavor's relation structure:
32 // {{subrelation_0, subrelation_1}, {subrelation_0}}
33 auto tuple_of_tuples = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_1, univariate_2),
34 flat_tuple::make_tuple(univariate_3));
35
36 // Use scale_univariate_accumulators to scale by challenge powers
37 // SumcheckTestFlavor has 3 subrelations total, so we need 2 separators
38 SubrelationSeparators challenge{};
39 challenge[0] = 5; // Separator between arithmetic subrelations
40 challenge[1] = 25; // Separator before dependent test relation
41 Utils::scale_univariates(tuple_of_tuples, challenge);
42
43 // Use extend_and_batch_univariates to extend to MAX_LENGTH then accumulate
44 GateSeparatorPolynomial<FF> gate_separators({ 1 });
45 auto result = Univariate<FF, MAX_LENGTH>();
46 SumcheckProverRound<Flavor>::extend_and_batch_univariates(tuple_of_tuples, result, gate_separators);
47
48 // Repeat the batching process manually
49 auto result_expected = univariate_1.template extend_to<MAX_LENGTH>() +
50 univariate_2.template extend_to<MAX_LENGTH>() * challenge[0] +
51 univariate_3.template extend_to<MAX_LENGTH>() * challenge[1];
52
53 // Compare final batched univariates
54 EXPECT_EQ(result, result_expected);
55
56 // Reinitialize univariate accumulators to zero
58
59 // Check that reinitialization was successful
60 Univariate<FF, 3> expected_1({ 0, 0, 0 }); // Arithmetic subrelation 0
61 Univariate<FF, 5> expected_2({ 0, 0, 0, 0, 0 }); // Arithmetic subrelation 1
62 Univariate<FF, 2> expected_3({ 0, 0 }); // DependentTest subrelation 0
63 EXPECT_EQ(std::get<0>(std::get<0>(tuple_of_tuples)), expected_1); // Arithmetic subrelation 0
64 EXPECT_EQ(std::get<1>(std::get<0>(tuple_of_tuples)), expected_2); // Arithmetic subrelation 1
65 EXPECT_EQ(std::get<0>(std::get<1>(tuple_of_tuples)), expected_3); // DependentTest subrelation 0
66}
67
72TEST(SumcheckRound, TuplesOfEvaluationArrays)
73{
75 using Utils = RelationUtils<Flavor>;
76 using FF = typename Flavor::FF;
77 using SubrelationSeparators = typename Utils::SubrelationSeparators;
78
79 // SumcheckTestFlavor has 3 subrelations: ArithmeticRelation(2) + DependentTestRelation(1)
80 // So we need arrays matching this structure
81 std::array<FF, 2> evaluations_arithmetic = { 4, 3 }; // ArithmeticRelation's 2 subrelations
82 std::array<FF, 1> evaluations_dependent = { 6 }; // DependentTestRelation's 1 subrelation
83
84 // Construct a tuple matching the relation structure
85 auto tuple_of_arrays = flat_tuple::make_tuple(evaluations_arithmetic, evaluations_dependent);
86
87 // Use scale_and_batch_elements to scale by challenge powers
88 // SumcheckTestFlavor has 3 subrelations, so SubrelationSeparators has 2 elements
89 SubrelationSeparators challenge{ 5, 25 };
90
91 FF result = Utils::scale_and_batch_elements(tuple_of_arrays, challenge);
92
93 // Repeat the batching process manually: first element not scaled, rest scaled by separators
94 auto result_expected = evaluations_arithmetic[0] + // no scaling
95 evaluations_arithmetic[1] * challenge[0] + // separator[0]
96 evaluations_dependent[0] * challenge[1]; // separator[1]
97
98 // Compare batched result
99 EXPECT_EQ(result, result_expected);
100
101 // Reinitialize elements to zero
102 Utils::zero_elements(tuple_of_arrays);
103
104 // Verify all elements were zeroed
105 EXPECT_EQ(std::get<0>(tuple_of_arrays)[0], 0); // ArithmeticRelation subrelation 0
106 EXPECT_EQ(std::get<0>(tuple_of_arrays)[1], 0); // ArithmeticRelation subrelation 1
107 EXPECT_EQ(std::get<1>(tuple_of_arrays)[0], 0); // DependentTestRelation subrelation 0
108}
109
114TEST(SumcheckRound, AddTuplesOfTuplesOfUnivariates)
115{
117 using FF = typename Flavor::FF;
118
119 // Define some arbitrary univariates
120 Univariate<FF, 2> univariate_1({ 1, 2 });
121 Univariate<FF, 2> univariate_2({ 2, 4 });
122 Univariate<FF, 3> univariate_3({ 3, 4, 5 });
123
124 Univariate<FF, 2> univariate_4({ 3, 6 });
125 Univariate<FF, 2> univariate_5({ 8, 1 });
126 Univariate<FF, 3> univariate_6({ 3, 7, 1 });
127
128 Univariate<FF, 2> expected_sum_1 = univariate_1 + univariate_4;
129 Univariate<FF, 2> expected_sum_2 = univariate_2 + univariate_5;
130 Univariate<FF, 3> expected_sum_3 = univariate_3 + univariate_6;
131
132 // Construct two tuples of tuples of univariates
133 auto tuple_of_tuples_1 = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_1),
134 flat_tuple::make_tuple(univariate_2, univariate_3));
135 auto tuple_of_tuples_2 = flat_tuple::make_tuple(flat_tuple::make_tuple(univariate_4),
136 flat_tuple::make_tuple(univariate_5, univariate_6));
137
138 RelationUtils<Flavor>::add_nested_tuples(tuple_of_tuples_1, tuple_of_tuples_2);
139
140 EXPECT_EQ(std::get<0>(std::get<0>(tuple_of_tuples_1)), expected_sum_1);
141 EXPECT_EQ(std::get<0>(std::get<1>(tuple_of_tuples_1)), expected_sum_2);
142 EXPECT_EQ(std::get<1>(std::get<1>(tuple_of_tuples_1)), expected_sum_3);
143}
144
150TEST(SumcheckRound, ComputeEffectiveRoundSize)
151{
152 using Flavor = SumcheckTestFlavor; // Non-ZK flavor
153 using FF = typename Flavor::FF;
155
156 // Test Case 1: All witness polynomials have full size
157 {
158 const size_t full_size = 32;
159 const size_t round_size = full_size;
160 SumcheckProverRound<Flavor> round(round_size);
161
162 // Create full-sized polynomials (all entities at full size)
164 for (auto& poly : random_polynomials) {
165 poly = bb::Polynomial<FF>(full_size);
166 }
167
168 ProverPolynomials prover_polynomials;
169 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
170 prover_poly = random_poly.share();
171 }
172
173 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
174 EXPECT_EQ(effective_size, round_size);
175 }
176
177 // Test Case 2: Witness polynomials have reduced active range
178 {
179 const size_t full_size = 64;
180 const size_t active_size = 20; // Active witness data ends at index 20
181 const size_t round_size = full_size;
182 SumcheckProverRound<Flavor> round(round_size);
183
184 // Note: AllEntities ordering is: PrecomputedEntities, WitnessEntities, ShiftedEntities
185 // For SumcheckTestFlavor: Precomputed (0-7), Witness (8-13), Shifted (14-15)
187 size_t poly_idx = 0;
188 for (auto& poly : random_polynomials) {
189 // Witness entities: use shiftable to simulate reduced active range
190 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
192 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
193 } else {
194 // Precomputed and shifted entities at full size
195 poly = bb::Polynomial<FF>(full_size);
196 }
197 poly_idx++;
198 }
199
200 ProverPolynomials prover_polynomials;
201 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
202 prover_poly = random_poly.share();
203 }
204
205 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
206 // Should be rounded up to next even number: 20 is even, so stays 20
207 EXPECT_EQ(effective_size, active_size);
208 EXPECT_LE(effective_size, round_size);
209 }
210
211 // Test Case 3: Odd active size should be rounded up to even
212 {
213 const size_t full_size = 64;
214 const size_t active_size = 23; // Odd number
215 const size_t expected_effective_size = 24; // Rounded up to even
216 const size_t round_size = full_size;
217 SumcheckProverRound<Flavor> round(round_size);
218
220 size_t poly_idx = 0;
221 for (auto& poly : random_polynomials) {
222 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
224 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
225 } else {
226 poly = bb::Polynomial<FF>(full_size);
227 }
228 poly_idx++;
229 }
230
231 ProverPolynomials prover_polynomials;
232 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
233 prover_poly = random_poly.share();
234 }
235
236 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
237 EXPECT_EQ(effective_size, expected_effective_size);
238 }
239
240 // Test Case 4: Different witness polynomials have different active sizes
241 // (should use the maximum)
242 {
243 const size_t full_size = 64;
244 const size_t round_size = full_size;
245 SumcheckProverRound<Flavor> round(round_size);
246
248 size_t poly_idx = 0;
249 size_t witness_idx = 0;
250 for (auto& poly : random_polynomials) {
251 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
253 // Set different sizes for different witness polynomials
254 if (witness_idx == 0) {
255 poly = bb::Polynomial<FF>::shiftable(10, full_size);
256 } else if (witness_idx == 1) {
257 poly = bb::Polynomial<FF>::shiftable(30, full_size); // This is the maximum
258 } else if (witness_idx == 2) {
259 poly = bb::Polynomial<FF>::shiftable(15, full_size);
260 } else {
261 poly = bb::Polynomial<FF>::shiftable(20, full_size);
262 }
263 witness_idx++;
264 } else {
265 poly = bb::Polynomial<FF>(full_size);
266 }
267 poly_idx++;
268 }
269
270 ProverPolynomials prover_polynomials;
271 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
272 prover_poly = random_poly.share();
273 }
274
275 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
276 // Should use maximum witness size (30), which is already even
277 EXPECT_EQ(effective_size, 30);
278 }
279
280 // Test Case 5: Very small active size
281 {
282 const size_t full_size = 128;
283 const size_t active_size = 2;
284 const size_t round_size = full_size;
285 SumcheckProverRound<Flavor> round(round_size);
286
288 size_t poly_idx = 0;
289 for (auto& poly : random_polynomials) {
290 if (poly_idx >= Flavor::NUM_PRECOMPUTED_ENTITIES &&
292 poly = bb::Polynomial<FF>::shiftable(active_size, full_size);
293 } else {
294 poly = bb::Polynomial<FF>(full_size);
295 }
296 poly_idx++;
297 }
298
299 ProverPolynomials prover_polynomials;
300 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
301 prover_poly = random_poly.share();
302 }
303
304 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
305 EXPECT_EQ(effective_size, active_size);
306 EXPECT_GE(effective_size, 2); // Minimum reasonable size
307 }
308}
309
315TEST(SumcheckRound, ComputeEffectiveRoundSizeZK)
316{
317 using Flavor = SumcheckTestFlavorZK; // ZK flavor
318 using FF = typename Flavor::FF;
320
321 const size_t full_size = 64;
322 const size_t round_size = full_size;
323 SumcheckProverRound<Flavor> round(round_size);
324
325 // Create polynomials for ZK flavor
327 for (auto& poly : random_polynomials) {
328 poly = bb::Polynomial<FF>(full_size);
329 }
330
331 ProverPolynomials prover_polynomials;
332 for (auto [prover_poly, random_poly] : zip_view(prover_polynomials.get_all(), random_polynomials)) {
333 prover_poly = random_poly.share();
334 }
335
336 size_t effective_size = round.compute_effective_round_size(prover_polynomials);
337 // compute_effective_round_size returns the extent of non-zero polynomial data (capped at round_size).
338 // The excluded_head_size is applied separately in compute_univariate, not here.
339 // Since all polynomials span the full round_size, effective_size == round_size.
340 EXPECT_EQ(effective_size, round_size);
341}
342
348TEST(SumcheckRound, ExtendEdgesShortMonomial)
349{
351 using FF = typename Flavor::FF;
353 using SumcheckRound = SumcheckProverRound<Flavor>;
354 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
355
356 const size_t multivariate_d = 3; // 8 rows
357 const size_t multivariate_n = 1 << multivariate_d;
358 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
359
360 // Create test polynomials where poly[i] = i (simple linear values)
361 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
362 for (auto& poly : test_polynomials) {
363 poly = bb::Polynomial<FF>(multivariate_n);
364 for (size_t i = 0; i < multivariate_n; ++i) {
365 poly.at(i) = FF(i);
366 }
367 }
368
369 ProverPolynomials prover_polynomials;
370 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
371 prover_poly = test_poly.share();
372 }
373
374 SumcheckRound round(multivariate_n);
375
376 // Test that edge extension creates a linear univariate
377 // For poly[i] = i, edge at index 2 gives us points (2, 3)
378 // The univariate U(X) = 2 + X should satisfy U(0) = 2, U(1) = 3
379 {
380 const size_t edge_idx = 2;
381 ExtendedEdges extended_edges;
382
383 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
384
385 // Check the first polynomial (all have the same pattern)
386 auto& first_edge = extended_edges.get_all()[0];
387
388 // Verify the linear interpolation: U(X) = 2 + X
389 FF val_at_0 = first_edge.value_at(0); // Should be 2
390 FF val_at_1 = first_edge.value_at(1); // Should be 3
391
392 EXPECT_EQ(val_at_0, FF(2)) << "Extended univariate should evaluate to 2 at X=0";
393 EXPECT_EQ(val_at_1, FF(3)) << "Extended univariate should evaluate to 3 at X=1";
394
395 // UltraFlavor uses USE_SHORT_MONOMIALS=true, so extended edge is just length 2
396 EXPECT_EQ(first_edge.evaluations.size(), 2) << "UltraFlavor uses short monomials (length 2)";
397
398 info("Extended edges create correct degree-1 univariates for USE_SHORT_MONOMIALS flavors");
399 }
400}
401
407TEST(SumcheckRound, ExtendEdges)
408{
409 // Use a flavor without ShortMonomials
411 using FF = typename Flavor::FF;
413 using SumcheckRound = SumcheckProverRound<Flavor>;
414 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
415
416 const size_t multivariate_d = 3; // 8 rows
417 const size_t multivariate_n = 1 << multivariate_d;
418 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
419
420 // Create test polynomials where poly[i] = i (simple linear values)
421 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
422 for (auto& poly : test_polynomials) {
423 poly = bb::Polynomial<FF>(multivariate_n);
424 for (size_t i = 0; i < multivariate_n; ++i) {
425 poly.at(i) = FF(i);
426 }
427 }
428
429 ProverPolynomials prover_polynomials;
430 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
431 prover_poly = test_poly.share();
432 }
433
434 SumcheckRound round(multivariate_n);
435
436 // Test that edge extension creates a full barycentric extension
437 // For poly[i] = i, edge at index 2 gives us points (2, 3)
438 // The univariate U(X) = 2 + X should extend to MAX_PARTIAL_RELATION_LENGTH
439 {
440 const size_t edge_idx = 2;
441 ExtendedEdges extended_edges;
442
443 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
444
445 // Check the first polynomial (all have the same pattern)
446 auto& first_edge = extended_edges.get_all()[0];
447
448 // Verify the linear interpolation at base points: U(X) = 2 + X
449 EXPECT_EQ(first_edge.value_at(0), FF(2)) << "U(0) should be 2";
450 EXPECT_EQ(first_edge.value_at(1), FF(3)) << "U(1) should be 3";
451
452 // Verify full extension to MAX_PARTIAL_RELATION_LENGTH
453 EXPECT_EQ(first_edge.evaluations.size(), Flavor::MAX_PARTIAL_RELATION_LENGTH)
454 << "Non-short-monomial flavor should extend to MAX_PARTIAL_RELATION_LENGTH";
455
456 // Verify the barycentric extension preserves the linear form at all extended points
457 // The univariate U(X) = 2 + X should give us U(2) = 4, U(3) = 5, U(4) = 6, etc.
458 for (size_t x = 2; x < std::min(static_cast<size_t>(7), first_edge.evaluations.size()); ++x) {
459 FF expected = FF(2 + x);
460 EXPECT_EQ(first_edge.value_at(x), expected)
461 << "Extended univariate U(X) = 2 + X should evaluate to " << (2 + x) << " at X=" << x
462 << " (barycentric extension should preserve linear form)";
463 }
464
465 info("Extended edges correctly perform full barycentric extension to MAX_PARTIAL_RELATION_LENGTH=",
467 }
468}
469
477TEST(SumcheckRound, AccumulateRelationUnivariatesSumcheckTestFlavor)
478{
480 using FF = typename Flavor::FF;
482 using SumcheckRound = SumcheckProverRound<Flavor>;
483
484 const size_t multivariate_d = 2; // log2(circuit_size) = 2 → 4 rows
485 const size_t multivariate_n = 1 << multivariate_d;
486
487 // Test 1: Arithmetic relation with simple values
488 // Simple circuit: w_l + w_r = w_o (using q_l=1, q_r=1, q_o=-1)
489 {
490 info("Test 1: Arithmetic relation accumulation");
491
492 // Create polynomial arrays
493 std::array<FF, multivariate_n> w_l = { FF(1), FF(2), FF(3), FF(4) };
494 std::array<FF, multivariate_n> w_r = { FF(5), FF(6), FF(7), FF(8) };
495 std::array<FF, multivariate_n> w_o = { FF(6), FF(8), FF(10), FF(12) }; // w_l + w_r
496 std::array<FF, multivariate_n> w_4 = { FF(0), FF(0), FF(0), FF(0) };
497 std::array<FF, multivariate_n> q_m = { FF(0), FF(0), FF(0), FF(0) };
498 std::array<FF, multivariate_n> q_l = { FF(1), FF(1), FF(1), FF(1) };
499 std::array<FF, multivariate_n> q_r = { FF(1), FF(1), FF(1), FF(1) };
500 std::array<FF, multivariate_n> q_o = { FF(-1), FF(-1), FF(-1), FF(-1) };
501 std::array<FF, multivariate_n> q_c = { FF(0), FF(0), FF(0), FF(0) };
502 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) }; // Enable arithmetic
503
504 // Create ProverPolynomials
505 ProverPolynomials prover_polynomials;
506 prover_polynomials.q_m = bb::Polynomial<FF>(q_m);
507 prover_polynomials.q_c = bb::Polynomial<FF>(q_c);
508 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
509 prover_polynomials.q_r = bb::Polynomial<FF>(q_r);
510 prover_polynomials.q_o = bb::Polynomial<FF>(q_o);
511 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
512 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
513 prover_polynomials.w_r = bb::Polynomial<FF>(w_r);
514 prover_polynomials.w_o = bb::Polynomial<FF>(w_o);
515 prover_polynomials.w_4 = bb::Polynomial<FF>(w_4);
516
517 // Initialize other required polynomials to zero
518 for (auto& poly : prover_polynomials.get_all()) {
519 if (poly.size() == 0) {
520 poly = bb::Polynomial<FF>(multivariate_n);
521 }
522 }
523
524 // Extend edges from the first edge (index 0)
525 SumcheckRound round(multivariate_n);
526 typename SumcheckRound::ExtendedEdges extended_edges;
527 round.extend_edges(extended_edges, prover_polynomials, 0);
528
529 // Accumulate relation
530 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
532 RelationParameters<FF> relation_parameters{};
533
534 // Scaling factor is set to 1
535 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
536
537 // Get arithmetic relation univariate
538 auto& arith_univariate = std::get<0>(std::get<0>(accumulator));
539
540 // For edge 0->1: relation should be q_arith * (q_l * w_l + q_r * w_r + q_o * w_o + q_c)
541 // At edge 0: 1 * (1*1 + 1*5 + (-1)*6 + 0) = 1 + 5 - 6 = 0 (satisfied)
542 // At edge 1: 1 * (1*2 + 1*6 + (-1)*8 + 0) = 2 + 6 - 8 = 0 (satisfied)
543 EXPECT_EQ(arith_univariate.value_at(0), FF(0)) << "Relation should be satisfied at edge 0";
544 EXPECT_EQ(arith_univariate.value_at(1), FF(0)) << "Relation should be satisfied at edge 1";
545
546 info("Arithmetic relation: verified relation is satisfied for valid circuit");
547 }
548
549 // Test 2: Scaling factor
550 {
551 info("Test 2: Scaling factor application");
552
553 // Create a simple non-zero contribution circuit
554 std::array<FF, multivariate_n> w_l = { FF(2), FF(2), FF(2), FF(2) };
555 std::array<FF, multivariate_n> q_l = { FF(3), FF(3), FF(3), FF(3) };
556 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
557
558 ProverPolynomials prover_polynomials;
559 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
560 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
561 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
562
563 for (auto& poly : prover_polynomials.get_all()) {
564 if (poly.size() == 0) {
565 poly = bb::Polynomial<FF>(multivariate_n);
566 }
567 }
568
569 SumcheckRound round(multivariate_n);
570 typename SumcheckRound::ExtendedEdges extended_edges;
571 round.extend_edges(extended_edges, prover_polynomials, 0);
572
573 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{};
574 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc2{};
577 RelationParameters<FF> relation_parameters{};
578
579 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
580 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
581
582 auto& arith1 = std::get<0>(std::get<0>(acc1));
583 auto& arith2 = std::get<0>(std::get<0>(acc2));
584
585 // With scale=2, result should be exactly double
586 EXPECT_EQ(arith2.value_at(0), arith1.value_at(0) * FF(2)) << "Scaling should multiply contribution";
587 EXPECT_EQ(arith2.value_at(1), arith1.value_at(1) * FF(2)) << "Scaling should multiply contribution";
588
589 info("Scaling factor: verified 2x scaling produces 2x contribution");
590 }
591
592 // Test 3: Multiple accumulations
593 {
594 info("Test 3: Multiple accumulation calls");
595
596 std::array<FF, multivariate_n> w_l = { FF(1), FF(1), FF(1), FF(1) };
597 std::array<FF, multivariate_n> q_l = { FF(5), FF(5), FF(5), FF(5) };
598 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
599
600 ProverPolynomials prover_polynomials;
601 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
602 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
603 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
604
605 for (auto& poly : prover_polynomials.get_all()) {
606 if (poly.size() == 0) {
607 poly = bb::Polynomial<FF>(multivariate_n);
608 }
609 }
610
611 SumcheckRound round(multivariate_n);
612 typename SumcheckRound::ExtendedEdges extended_edges;
613 round.extend_edges(extended_edges, prover_polynomials, 0);
614
615 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
617 RelationParameters<FF> relation_parameters{};
618
619 // First accumulation
620 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
621 auto& arith = std::get<0>(std::get<0>(accumulator));
622 FF value_after_first = arith.value_at(0);
623
624 // Second accumulation (should add to first)
625 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
626 FF value_after_second = arith.value_at(0);
627
628 // Second value should be double the first (since we accumulated the same contribution twice)
629 EXPECT_EQ(value_after_second, value_after_first * FF(2)) << "Second accumulation should add to first";
630
631 info("Multiple accumulations: verified contributions are summed");
632 }
633 // Test 4: Linearly dependent subrelation should NOT be scaled
634 {
635 info("Test 4: DependentTestRelation (linearly dependent) is not scaled");
636
637 // Create a circuit with test relation polynomials
638 std::array<FF, multivariate_n> w_test_1 = { FF(1), FF(2), FF(3), FF(4) };
639 std::array<FF, multivariate_n> q_test = { FF(1), FF(1), FF(1), FF(1) };
640
641 ProverPolynomials prover_polynomials;
642 prover_polynomials.w_test_1 = bb::Polynomial<FF>(w_test_1);
643 prover_polynomials.q_test = bb::Polynomial<FF>(q_test);
644
645 for (auto& poly : prover_polynomials.get_all()) {
646 if (poly.size() == 0) {
647 poly = bb::Polynomial<FF>(multivariate_n);
648 }
649 }
650
651 SumcheckRound round(multivariate_n);
652 typename SumcheckRound::ExtendedEdges extended_edges;
653 round.extend_edges(extended_edges, prover_polynomials, 0);
654
655 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{}, acc2{};
658
659 RelationParameters<FF> relation_parameters{};
660
661 // Accumulate with scale=1 and scale=2
662 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
663 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
664
665 // SumcheckTestFlavor::Relations = tuple<ArithmeticRelation, DependentTestRelation>
666 // ArithmeticRelation is at index 0 (has 2 subrelations, both linearly independent)
667 // DependentTestRelation is at index 1 (has 1 subrelation, linearly dependent)
668
669 // Check DependentTestRelation (index 1) - should NOT be scaled (linearly dependent)
670 auto& dependent_test_acc1 = std::get<0>(std::get<1>(acc1));
671 auto& dependent_test_acc2 = std::get<0>(std::get<1>(acc2));
672 EXPECT_EQ(dependent_test_acc2.value_at(0), dependent_test_acc1.value_at(0))
673 << "DependentTestRelation (linearly dependent) should NOT be scaled";
674 EXPECT_EQ(dependent_test_acc2.value_at(1), dependent_test_acc1.value_at(1))
675 << "DependentTestRelation (linearly dependent) should NOT be scaled";
676
677 info("DependentTestRelation: verified that linearly dependent relation is NOT scaled");
678 }
679}
680
685TEST(SumcheckRound, CheckSumFieldArithmetic)
686{
688 using FF = typename Flavor::FF;
690 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
691
692 info("Test: Field arithmetic edge cases for check_sum");
693
694 // Test 1: Large field elements near modulus
695 {
696 // Create large values near the field modulus
697 FF large_val_1 = FF(-1); // p - 1 (maximum field element)
698 FF large_val_2 = FF(-2); // p - 2
699 FF target = large_val_1 + large_val_2; // Should wrap around: (p-1) + (p-2) = 2p - 3 ≡ -3 (mod p)
700
702 univariate.value_at(0) = large_val_1;
703 univariate.value_at(1) = large_val_2;
704
705 SumcheckVerifierRound verifier_round(target);
706 verifier_round.check_sum(univariate);
707
708 EXPECT_TRUE(!verifier_round.round_failed)
709 << "check_sum should handle large field elements correctly with wraparound";
710 info("Large field elements: check_sum correctly handles values near modulus");
711 }
712
713 // Test 2: Zero elements (edge case)
714 {
715 FF zero = FF(0);
717 univariate.value_at(0) = zero;
718 univariate.value_at(1) = zero;
719
720 SumcheckVerifierRound verifier_round(zero);
721 verifier_round.check_sum(univariate);
722 bool result = !verifier_round.round_failed;
723
724 EXPECT_TRUE(result) << "check_sum should handle zero case correctly";
725 info("Zero case: check_sum correctly handles all-zero values");
726 }
727
728 // Test 3: Mixed signs (positive and negative)
729 {
730 FF positive = FF(12345);
731 FF negative = FF(-12345);
732 FF target = positive + negative; // Should be 0
733
735 univariate.value_at(0) = positive;
736 univariate.value_at(1) = negative;
737
738 SumcheckVerifierRound verifier_round(target);
739 verifier_round.check_sum(univariate);
740 bool result = !verifier_round.round_failed;
741
742 EXPECT_TRUE(result) << "check_sum should handle mixed signs correctly";
743 EXPECT_EQ(target, FF(0)) << "Positive + negative should equal zero";
744 info("Mixed signs: check_sum correctly handles positive + negative = 0");
745 }
746}
747
752TEST(SumcheckRound, CheckSumRoundFailurePersistence)
753{
755 using FF = typename Flavor::FF;
757 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
758
759 info("Test: round_failed flag persistence across multiple checks");
760
761 // Test 1: Single failed check sets flag
762 {
763 info("Test 1: Single failed check sets round_failed flag");
764
765 FF wrong_target = FF(999);
766 SumcheckVerifierRound verifier_round(wrong_target);
767
769 univariate.value_at(0) = FF(10);
770 univariate.value_at(1) = FF(20);
771
772 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should initially be false";
773
774 verifier_round.check_sum(univariate);
775 bool result = !verifier_round.round_failed;
776
777 EXPECT_FALSE(result) << "check_sum should return false for wrong target";
778 EXPECT_TRUE(verifier_round.round_failed) << "round_failed flag should be set after failed check";
779
780 info("Single failure: round_failed flag correctly set");
781 }
782
783 // Test 2: Multiple passing checks keep flag false
784 {
785 info("Test 2: Multiple passing checks keep round_failed false");
786
787 SumcheckVerifierRound verifier_round(FF(30)); // Start with correct target
788
790 univariate1.value_at(0) = FF(10);
791 univariate1.value_at(1) = FF(20);
792
794 univariate2.value_at(0) = FF(5);
795 univariate2.value_at(1) = FF(15);
796
797 verifier_round.check_sum(univariate1);
798 bool result1 = !verifier_round.round_failed;
799 EXPECT_TRUE(result1) << "First check should pass";
800 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should be false after first pass";
801
802 verifier_round.target_total_sum = FF(20); // Update target for second check
803 verifier_round.check_sum(univariate2);
804 bool result2 = !verifier_round.round_failed;
805 EXPECT_TRUE(result2) << "Second check should pass";
806 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should remain false after second pass";
807
808 info("Multiple passes: round_failed correctly remains false");
809 }
810}
811
817TEST(SumcheckRound, CheckSumRecursiveUnsatisfiableWitness)
818{
819 using InnerBuilder = bb::UltraCircuitBuilder;
820 using RecursiveFlavor = bb::UltraRecursiveFlavor_<InnerBuilder>;
821 using FF = typename RecursiveFlavor::FF; // This is field_t<InnerBuilder> (stdlib field)
823 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = RecursiveFlavor::BATCHED_RELATION_PARTIAL_LENGTH;
824
825 info("Test: Recursive check_sum with unsatisfiable witness");
826
827 // Test 1: Unsatisfiable witness - sum doesn't match target
828 {
829 info("Test 1: Unsatisfiable witness where target != S(0) + S(1)");
830
831 InnerBuilder builder;
832
833 // Create witness values that intentionally don't satisfy the check
834 auto native_val_0 = bb::fr(10);
835 auto native_val_1 = bb::fr(20);
836 auto native_wrong_target = bb::fr(100); // Intentionally wrong (correct would be 30)
837
838 // Create stdlib field elements (circuit variables)
839 FF val_0 = FF::from_witness(&builder, native_val_0);
840 FF val_1 = FF::from_witness(&builder, native_val_1);
841 FF wrong_target = FF::from_witness(&builder, native_wrong_target);
842
843 // Create univariate with these values
845 univariate.value_at(0) = val_0;
846 univariate.value_at(1) = val_1;
847
848 // Create verifier round with wrong target
849 SumcheckVerifierRound verifier_round(wrong_target);
850
851 // Call check_sum - this adds constraints to the circuit
852 // In recursive flavor, assert_equal is called which adds a constraint
853 verifier_round.check_sum(univariate);
854 bool check_result = !verifier_round.round_failed;
855
856 // The check_sum itself should return false (based on witness values)
857 EXPECT_FALSE(check_result) << "check_sum should return false for mismatched values";
858
859 // The circuit should have failed (constraint violation detected)
860 EXPECT_TRUE(builder.failed()) << "Builder should detect constraint violation (unsatisfiable witness)";
861
862 info("Unsatisfiable witness: Builder correctly detects constraint violation");
863 }
864
865 // Test 2: Satisfiable witness - sum matches target
866 {
867 info("Test 2: Satisfiable witness where target == S(0) + S(1)");
868
869 InnerBuilder builder;
870
871 // Create witness values that DO satisfy the check
872 auto native_val_0 = bb::fr(10);
873 auto native_val_1 = bb::fr(20);
874 auto native_correct_target = native_val_0 + native_val_1; // 30 (correct)
875
876 // Create stdlib field elements
877 FF val_0 = FF::from_witness(&builder, native_val_0);
878 FF val_1 = FF::from_witness(&builder, native_val_1);
879 FF correct_target = FF::from_witness(&builder, native_correct_target);
880
881 // Create univariate
883 univariate.value_at(0) = val_0;
884 univariate.value_at(1) = val_1;
885
886 // Create verifier round with correct target
887 SumcheckVerifierRound verifier_round(correct_target);
888
889 // Call check_sum
890 verifier_round.check_sum(univariate);
891 bool check_result = !verifier_round.round_failed;
892
893 // Check should pass
894 EXPECT_TRUE(check_result) << "check_sum should return true for matching values";
895
896 // The circuit should NOT have failed
897 EXPECT_FALSE(builder.failed()) << "Builder should not fail for satisfiable witness";
898
899 // Verify the circuit is correct
900 EXPECT_TRUE(CircuitChecker::check(builder)) << "Circuit with satisfiable witness should pass CircuitChecker";
901
902 info("Satisfiable witness: Builder correctly validates constraint satisfaction");
903 }
904
905 // Test 3: Multiple rounds with one failure
906 {
907 info("Test 4: Multiple rounds where one has unsatisfiable witness");
908
909 InnerBuilder builder;
910
911 // First round: correct
912 auto val_0_round1 = FF::from_witness(&builder, bb::fr(10));
913 auto val_1_round1 = FF::from_witness(&builder, bb::fr(20));
914 auto target_round1 = FF::from_witness(&builder, bb::fr(30));
915
917 univariate_1.value_at(0) = val_0_round1;
918 univariate_1.value_at(1) = val_1_round1;
919
920 SumcheckVerifierRound verifier_round(target_round1);
921
922 verifier_round.check_sum(univariate_1);
923 bool result_1 = !verifier_round.round_failed;
924 EXPECT_TRUE(result_1);
925 EXPECT_FALSE(builder.failed()) << "First round should not fail";
926
927 // Second round: WRONG
928 verifier_round.target_total_sum = FF::from_witness(&builder, bb::fr(999)); // Wrong target
929 auto val_0_round2 = FF::from_witness(&builder, bb::fr(5));
930 auto val_1_round2 = FF::from_witness(&builder, bb::fr(15));
931
933 univariate_2.value_at(0) = val_0_round2;
934 univariate_2.value_at(1) = val_1_round2;
935
936 verifier_round.check_sum(univariate_2);
937 bool result_2 = !verifier_round.round_failed;
938 EXPECT_FALSE(result_2) << "Second round should fail";
939
940 // Builder should now have failed
941 EXPECT_TRUE(builder.failed()) << "Builder should detect failure in second round";
942
943 info("Multiple rounds: Builder correctly detects failure in one of multiple rounds");
944 }
945}
A container for the prover polynomials.
typename Curve::ScalarField FF
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
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.
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.
static void extend_and_batch_univariates(const TupleOfTuplesOfUnivariates &tuple, ExtendedUnivariate &result, const bb::GateSeparatorPolynomial< FF > &gate_separators)
Extend Univariates then sum them multiplying by the current -contributions.
size_t compute_effective_round_size(const ProverPolynomialsOrPartiallyEvaluatedMultivariates &multivariates) const
Compute the effective round size by finding the maximum end_index() across witness polynomials.
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
SumcheckTestFlavor_< curve::BN254, true, true > SumcheckTestFlavorZK
Zero-knowledge variant.
field< Bn254FrParams > fr
Definition fr.hpp:155
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
Implementation of the methods for the -polynomials used in in Sumcheck.
Container for parameters used by the grand product (permutation, lookup) Honk relations.
Minimal test flavors for sumcheck testing without UltraFlavor dependencies.