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
314TEST(SumcheckRound, ComputeEffectiveRoundSizeZK)
315{
316 using Flavor = SumcheckTestFlavorZK; // ZK flavor
317 using FF = typename Flavor::FF;
319
320 const size_t full_size = 64;
321 const size_t round_size = full_size;
322 SumcheckProverRound<Flavor> round(round_size);
323
324 // Create polynomials - ZK flavor always uses full size
326 for (auto& poly : random_polynomials) {
327 // For ZK flavor, all polynomials (including witnesses) are allocated at full size
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 // For ZK flavors, should always return full round_size regardless of witness sizes
338 EXPECT_EQ(effective_size, round_size);
339}
340
346TEST(SumcheckRound, ExtendEdgesShortMonomial)
347{
349 using FF = typename Flavor::FF;
351 using SumcheckRound = SumcheckProverRound<Flavor>;
352 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
353
354 const size_t multivariate_d = 3; // 8 rows
355 const size_t multivariate_n = 1 << multivariate_d;
356 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
357
358 // Create test polynomials where poly[i] = i (simple linear values)
359 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
360 for (auto& poly : test_polynomials) {
361 poly = bb::Polynomial<FF>(multivariate_n);
362 for (size_t i = 0; i < multivariate_n; ++i) {
363 poly.at(i) = FF(i);
364 }
365 }
366
367 ProverPolynomials prover_polynomials;
368 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
369 prover_poly = test_poly.share();
370 }
371
372 SumcheckRound round(multivariate_n);
373
374 // Test that edge extension creates a linear univariate
375 // For poly[i] = i, edge at index 2 gives us points (2, 3)
376 // The univariate U(X) = 2 + X should satisfy U(0) = 2, U(1) = 3
377 {
378 const size_t edge_idx = 2;
379 ExtendedEdges extended_edges;
380
381 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
382
383 // Check the first polynomial (all have the same pattern)
384 auto& first_edge = extended_edges.get_all()[0];
385
386 // Verify the linear interpolation: U(X) = 2 + X
387 FF val_at_0 = first_edge.value_at(0); // Should be 2
388 FF val_at_1 = first_edge.value_at(1); // Should be 3
389
390 EXPECT_EQ(val_at_0, FF(2)) << "Extended univariate should evaluate to 2 at X=0";
391 EXPECT_EQ(val_at_1, FF(3)) << "Extended univariate should evaluate to 3 at X=1";
392
393 // UltraFlavor uses USE_SHORT_MONOMIALS=true, so extended edge is just length 2
394 EXPECT_EQ(first_edge.evaluations.size(), 2) << "UltraFlavor uses short monomials (length 2)";
395
396 info("Extended edges create correct degree-1 univariates for USE_SHORT_MONOMIALS flavors");
397 }
398}
399
405TEST(SumcheckRound, ExtendEdges)
406{
407 // Use a flavor without ShortMonomials
409 using FF = typename Flavor::FF;
411 using SumcheckRound = SumcheckProverRound<Flavor>;
412 using ExtendedEdges = typename SumcheckRound::ExtendedEdges;
413
414 const size_t multivariate_d = 3; // 8 rows
415 const size_t multivariate_n = 1 << multivariate_d;
416 const size_t NUM_POLYNOMIALS = Flavor::NUM_ALL_ENTITIES;
417
418 // Create test polynomials where poly[i] = i (simple linear values)
419 std::vector<bb::Polynomial<FF>> test_polynomials(NUM_POLYNOMIALS);
420 for (auto& poly : test_polynomials) {
421 poly = bb::Polynomial<FF>(multivariate_n);
422 for (size_t i = 0; i < multivariate_n; ++i) {
423 poly.at(i) = FF(i);
424 }
425 }
426
427 ProverPolynomials prover_polynomials;
428 for (auto [prover_poly, test_poly] : zip_view(prover_polynomials.get_all(), test_polynomials)) {
429 prover_poly = test_poly.share();
430 }
431
432 SumcheckRound round(multivariate_n);
433
434 // Test that edge extension creates a full barycentric extension
435 // For poly[i] = i, edge at index 2 gives us points (2, 3)
436 // The univariate U(X) = 2 + X should extend to MAX_PARTIAL_RELATION_LENGTH
437 {
438 const size_t edge_idx = 2;
439 ExtendedEdges extended_edges;
440
441 round.extend_edges(extended_edges, prover_polynomials, edge_idx);
442
443 // Check the first polynomial (all have the same pattern)
444 auto& first_edge = extended_edges.get_all()[0];
445
446 // Verify the linear interpolation at base points: U(X) = 2 + X
447 EXPECT_EQ(first_edge.value_at(0), FF(2)) << "U(0) should be 2";
448 EXPECT_EQ(first_edge.value_at(1), FF(3)) << "U(1) should be 3";
449
450 // Verify full extension to MAX_PARTIAL_RELATION_LENGTH
451 EXPECT_EQ(first_edge.evaluations.size(), Flavor::MAX_PARTIAL_RELATION_LENGTH)
452 << "Non-short-monomial flavor should extend to MAX_PARTIAL_RELATION_LENGTH";
453
454 // Verify the barycentric extension preserves the linear form at all extended points
455 // The univariate U(X) = 2 + X should give us U(2) = 4, U(3) = 5, U(4) = 6, etc.
456 for (size_t x = 2; x < std::min(static_cast<size_t>(7), first_edge.evaluations.size()); ++x) {
457 FF expected = FF(2 + x);
458 EXPECT_EQ(first_edge.value_at(x), expected)
459 << "Extended univariate U(X) = 2 + X should evaluate to " << (2 + x) << " at X=" << x
460 << " (barycentric extension should preserve linear form)";
461 }
462
463 info("Extended edges correctly perform full barycentric extension to MAX_PARTIAL_RELATION_LENGTH=",
465 }
466}
467
475TEST(SumcheckRound, AccumulateRelationUnivariatesSumcheckTestFlavor)
476{
478 using FF = typename Flavor::FF;
480 using SumcheckRound = SumcheckProverRound<Flavor>;
481
482 const size_t multivariate_d = 2; // log2(circuit_size) = 2 → 4 rows
483 const size_t multivariate_n = 1 << multivariate_d;
484
485 // Test 1: Arithmetic relation with simple values
486 // Simple circuit: w_l + w_r = w_o (using q_l=1, q_r=1, q_o=-1)
487 {
488 info("Test 1: Arithmetic relation accumulation");
489
490 // Create polynomial arrays
491 std::array<FF, multivariate_n> w_l = { FF(1), FF(2), FF(3), FF(4) };
492 std::array<FF, multivariate_n> w_r = { FF(5), FF(6), FF(7), FF(8) };
493 std::array<FF, multivariate_n> w_o = { FF(6), FF(8), FF(10), FF(12) }; // w_l + w_r
494 std::array<FF, multivariate_n> w_4 = { FF(0), FF(0), FF(0), FF(0) };
495 std::array<FF, multivariate_n> q_m = { FF(0), FF(0), FF(0), FF(0) };
496 std::array<FF, multivariate_n> q_l = { FF(1), FF(1), FF(1), FF(1) };
497 std::array<FF, multivariate_n> q_r = { FF(1), FF(1), FF(1), FF(1) };
498 std::array<FF, multivariate_n> q_o = { FF(-1), FF(-1), FF(-1), FF(-1) };
499 std::array<FF, multivariate_n> q_c = { FF(0), FF(0), FF(0), FF(0) };
500 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) }; // Enable arithmetic
501
502 // Create ProverPolynomials
503 ProverPolynomials prover_polynomials;
504 prover_polynomials.q_m = bb::Polynomial<FF>(q_m);
505 prover_polynomials.q_c = bb::Polynomial<FF>(q_c);
506 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
507 prover_polynomials.q_r = bb::Polynomial<FF>(q_r);
508 prover_polynomials.q_o = bb::Polynomial<FF>(q_o);
509 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
510 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
511 prover_polynomials.w_r = bb::Polynomial<FF>(w_r);
512 prover_polynomials.w_o = bb::Polynomial<FF>(w_o);
513 prover_polynomials.w_4 = bb::Polynomial<FF>(w_4);
514
515 // Initialize other required polynomials to zero
516 for (auto& poly : prover_polynomials.get_all()) {
517 if (poly.size() == 0) {
518 poly = bb::Polynomial<FF>(multivariate_n);
519 }
520 }
521
522 // Extend edges from the first edge (index 0)
523 SumcheckRound round(multivariate_n);
524 typename SumcheckRound::ExtendedEdges extended_edges;
525 round.extend_edges(extended_edges, prover_polynomials, 0);
526
527 // Accumulate relation
528 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
530 RelationParameters<FF> relation_parameters{};
531
532 // Scaling factor is set to 1
533 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
534
535 // Get arithmetic relation univariate
536 auto& arith_univariate = std::get<0>(std::get<0>(accumulator));
537
538 // For edge 0->1: relation should be q_arith * (q_l * w_l + q_r * w_r + q_o * w_o + q_c)
539 // At edge 0: 1 * (1*1 + 1*5 + (-1)*6 + 0) = 1 + 5 - 6 = 0 (satisfied)
540 // At edge 1: 1 * (1*2 + 1*6 + (-1)*8 + 0) = 2 + 6 - 8 = 0 (satisfied)
541 EXPECT_EQ(arith_univariate.value_at(0), FF(0)) << "Relation should be satisfied at edge 0";
542 EXPECT_EQ(arith_univariate.value_at(1), FF(0)) << "Relation should be satisfied at edge 1";
543
544 info("Arithmetic relation: verified relation is satisfied for valid circuit");
545 }
546
547 // Test 2: Scaling factor
548 {
549 info("Test 2: Scaling factor application");
550
551 // Create a simple non-zero contribution circuit
552 std::array<FF, multivariate_n> w_l = { FF(2), FF(2), FF(2), FF(2) };
553 std::array<FF, multivariate_n> q_l = { FF(3), FF(3), FF(3), FF(3) };
554 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
555
556 ProverPolynomials prover_polynomials;
557 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
558 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
559 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
560
561 for (auto& poly : prover_polynomials.get_all()) {
562 if (poly.size() == 0) {
563 poly = bb::Polynomial<FF>(multivariate_n);
564 }
565 }
566
567 SumcheckRound round(multivariate_n);
568 typename SumcheckRound::ExtendedEdges extended_edges;
569 round.extend_edges(extended_edges, prover_polynomials, 0);
570
571 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{};
572 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc2{};
575 RelationParameters<FF> relation_parameters{};
576
577 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
578 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
579
580 auto& arith1 = std::get<0>(std::get<0>(acc1));
581 auto& arith2 = std::get<0>(std::get<0>(acc2));
582
583 // With scale=2, result should be exactly double
584 EXPECT_EQ(arith2.value_at(0), arith1.value_at(0) * FF(2)) << "Scaling should multiply contribution";
585 EXPECT_EQ(arith2.value_at(1), arith1.value_at(1) * FF(2)) << "Scaling should multiply contribution";
586
587 info("Scaling factor: verified 2x scaling produces 2x contribution");
588 }
589
590 // Test 3: Multiple accumulations
591 {
592 info("Test 3: Multiple accumulation calls");
593
594 std::array<FF, multivariate_n> w_l = { FF(1), FF(1), FF(1), FF(1) };
595 std::array<FF, multivariate_n> q_l = { FF(5), FF(5), FF(5), FF(5) };
596 std::array<FF, multivariate_n> q_arith = { FF(1), FF(1), FF(1), FF(1) };
597
598 ProverPolynomials prover_polynomials;
599 prover_polynomials.w_l = bb::Polynomial<FF>(w_l);
600 prover_polynomials.q_l = bb::Polynomial<FF>(q_l);
601 prover_polynomials.q_arith = bb::Polynomial<FF>(q_arith);
602
603 for (auto& poly : prover_polynomials.get_all()) {
604 if (poly.size() == 0) {
605 poly = bb::Polynomial<FF>(multivariate_n);
606 }
607 }
608
609 SumcheckRound round(multivariate_n);
610 typename SumcheckRound::ExtendedEdges extended_edges;
611 round.extend_edges(extended_edges, prover_polynomials, 0);
612
613 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates accumulator{};
615 RelationParameters<FF> relation_parameters{};
616
617 // First accumulation
618 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
619 auto& arith = std::get<0>(std::get<0>(accumulator));
620 FF value_after_first = arith.value_at(0);
621
622 // Second accumulation (should add to first)
623 round.accumulate_relation_univariates_public(accumulator, extended_edges, relation_parameters, FF(1));
624 FF value_after_second = arith.value_at(0);
625
626 // Second value should be double the first (since we accumulated the same contribution twice)
627 EXPECT_EQ(value_after_second, value_after_first * FF(2)) << "Second accumulation should add to first";
628
629 info("Multiple accumulations: verified contributions are summed");
630 }
631 // Test 4: Linearly dependent subrelation should NOT be scaled
632 {
633 info("Test 4: DependentTestRelation (linearly dependent) is not scaled");
634
635 // Create a circuit with test relation polynomials
636 std::array<FF, multivariate_n> w_test_1 = { FF(1), FF(2), FF(3), FF(4) };
637 std::array<FF, multivariate_n> q_test = { FF(1), FF(1), FF(1), FF(1) };
638
639 ProverPolynomials prover_polynomials;
640 prover_polynomials.w_test_1 = bb::Polynomial<FF>(w_test_1);
641 prover_polynomials.q_test = bb::Polynomial<FF>(q_test);
642
643 for (auto& poly : prover_polynomials.get_all()) {
644 if (poly.size() == 0) {
645 poly = bb::Polynomial<FF>(multivariate_n);
646 }
647 }
648
649 SumcheckRound round(multivariate_n);
650 typename SumcheckRound::ExtendedEdges extended_edges;
651 round.extend_edges(extended_edges, prover_polynomials, 0);
652
653 typename SumcheckRound::SumcheckTupleOfTuplesOfUnivariates acc1{}, acc2{};
656
657 RelationParameters<FF> relation_parameters{};
658
659 // Accumulate with scale=1 and scale=2
660 round.accumulate_relation_univariates_public(acc1, extended_edges, relation_parameters, FF(1));
661 round.accumulate_relation_univariates_public(acc2, extended_edges, relation_parameters, FF(2));
662
663 // SumcheckTestFlavor::Relations = tuple<ArithmeticRelation, DependentTestRelation>
664 // ArithmeticRelation is at index 0 (has 2 subrelations, both linearly independent)
665 // DependentTestRelation is at index 1 (has 1 subrelation, linearly dependent)
666
667 // Check DependentTestRelation (index 1) - should NOT be scaled (linearly dependent)
668 auto& dependent_test_acc1 = std::get<0>(std::get<1>(acc1));
669 auto& dependent_test_acc2 = std::get<0>(std::get<1>(acc2));
670 EXPECT_EQ(dependent_test_acc2.value_at(0), dependent_test_acc1.value_at(0))
671 << "DependentTestRelation (linearly dependent) should NOT be scaled";
672 EXPECT_EQ(dependent_test_acc2.value_at(1), dependent_test_acc1.value_at(1))
673 << "DependentTestRelation (linearly dependent) should NOT be scaled";
674
675 info("DependentTestRelation: verified that linearly dependent relation is NOT scaled");
676 }
677}
678
683TEST(SumcheckRound, CheckSumFieldArithmetic)
684{
686 using FF = typename Flavor::FF;
688 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
689
690 info("Test: Field arithmetic edge cases for check_sum");
691
692 // Test 1: Large field elements near modulus
693 {
694 // Create large values near the field modulus
695 FF large_val_1 = FF(-1); // p - 1 (maximum field element)
696 FF large_val_2 = FF(-2); // p - 2
697 FF target = large_val_1 + large_val_2; // Should wrap around: (p-1) + (p-2) = 2p - 3 ≡ -3 (mod p)
698
700 univariate.value_at(0) = large_val_1;
701 univariate.value_at(1) = large_val_2;
702
703 SumcheckVerifierRound verifier_round(target);
704 verifier_round.check_sum(univariate, FF(1));
705
706 EXPECT_TRUE(!verifier_round.round_failed)
707 << "check_sum should handle large field elements correctly with wraparound";
708 info("Large field elements: check_sum correctly handles values near modulus");
709 }
710
711 // Test 2: Zero elements (edge case)
712 {
713 FF zero = FF(0);
715 univariate.value_at(0) = zero;
716 univariate.value_at(1) = zero;
717
718 SumcheckVerifierRound verifier_round(zero);
719 verifier_round.check_sum(univariate, FF(1));
720 bool result = !verifier_round.round_failed;
721
722 EXPECT_TRUE(result) << "check_sum should handle zero case correctly";
723 info("Zero case: check_sum correctly handles all-zero values");
724 }
725
726 // Test 3: Mixed signs (positive and negative)
727 {
728 FF positive = FF(12345);
729 FF negative = FF(-12345);
730 FF target = positive + negative; // Should be 0
731
733 univariate.value_at(0) = positive;
734 univariate.value_at(1) = negative;
735
736 SumcheckVerifierRound verifier_round(target);
737 verifier_round.check_sum(univariate, FF(1));
738 bool result = !verifier_round.round_failed;
739
740 EXPECT_TRUE(result) << "check_sum should handle mixed signs correctly";
741 EXPECT_EQ(target, FF(0)) << "Positive + negative should equal zero";
742 info("Mixed signs: check_sum correctly handles positive + negative = 0");
743 }
744}
745
751TEST(SumcheckRound, CheckSumPaddingIndicator)
752{
754 using FF = typename Flavor::FF;
756 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
757
758 info("Test: Padding indicator behavior in check_sum");
759
760 // Create a univariate with mismatched sum (should fail in normal round)
761 FF val_0 = FF(10);
762 FF val_1 = FF(20);
763 FF correct_sum = val_0 + val_1; // 30
764 FF wrong_target = FF(100); // Intentionally wrong
765
767 univariate.value_at(0) = val_0;
768 univariate.value_at(1) = val_1;
769
770 // Test 1: Non-padding round (indicator = 1) - should enforce the check
771 {
772 info("Test 1: Non-padding round (indicator=1) with wrong target - should FAIL");
773
774 SumcheckVerifierRound verifier_round(wrong_target);
775 verifier_round.check_sum(univariate, FF(1)); // indicator = 1
776 bool result = !verifier_round.round_failed;
777
778 EXPECT_FALSE(result) << "With indicator=1, check_sum should fail when target is wrong";
779 EXPECT_TRUE(verifier_round.round_failed) << "round_failed flag should be set";
780 info("Non-padding round: check_sum correctly enforces check and fails with wrong target");
781 }
782
783 // Test 2: Padding round (indicator = 0) - should bypass check even with wrong target
784 {
785 info("Test 3: Padding round (indicator=0) with wrong target - should PASS (bypassed)");
786
787 SumcheckVerifierRound verifier_round(wrong_target);
788 verifier_round.check_sum(univariate, FF(0)); // indicator = 0
789 bool result = !verifier_round.round_failed;
790
791 EXPECT_TRUE(result) << "With indicator=0, check_sum should pass even when target is wrong (padding round)";
792 EXPECT_FALSE(verifier_round.round_failed) << "round_failed flag should not be set in padding round";
793 info("Padding round: check_sum correctly bypasses verification");
794 }
795
796 // Test 5: Transition from padding to non-padding
797 {
798 info("Test 4: Multiple rounds with mixed padding/non-padding");
799
800 SumcheckVerifierRound verifier_round(wrong_target);
801
802 // First round: padding (indicator = 0) - should pass
803 verifier_round.check_sum(univariate, FF(0));
804 bool result1 = !verifier_round.round_failed;
805 EXPECT_TRUE(result1) << "First padding round should pass";
806 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should still be false after padding round";
807
808 // Update target to correct value for next check
809 verifier_round.target_total_sum = correct_sum;
810
811 // Second round: non-padding (indicator = 1) with correct target - should pass
812 verifier_round.check_sum(univariate, FF(1));
813 bool result2 = !verifier_round.round_failed;
814 EXPECT_TRUE(result2) << "Non-padding round with correct target should pass";
815 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should remain false";
816
817 info("Mixed rounds: check_sum correctly handles transition from padding to non-padding");
818 }
819}
820
825TEST(SumcheckRound, CheckSumRoundFailurePersistence)
826{
828 using FF = typename Flavor::FF;
830 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH;
831
832 info("Test: round_failed flag persistence across multiple checks");
833
834 // Test 1: Single failed check sets flag
835 {
836 info("Test 1: Single failed check sets round_failed flag");
837
838 FF wrong_target = FF(999);
839 SumcheckVerifierRound verifier_round(wrong_target);
840
842 univariate.value_at(0) = FF(10);
843 univariate.value_at(1) = FF(20);
844
845 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should initially be false";
846
847 verifier_round.check_sum(univariate, FF(1));
848 bool result = !verifier_round.round_failed;
849
850 EXPECT_FALSE(result) << "check_sum should return false for wrong target";
851 EXPECT_TRUE(verifier_round.round_failed) << "round_failed flag should be set after failed check";
852
853 info("Single failure: round_failed flag correctly set");
854 }
855
856 // Test 2: Multiple passing checks keep flag false
857 {
858 info("Test 2: Multiple passing checks keep round_failed false");
859
860 SumcheckVerifierRound verifier_round(FF(30)); // Start with correct target
861
863 univariate1.value_at(0) = FF(10);
864 univariate1.value_at(1) = FF(20);
865
867 univariate2.value_at(0) = FF(5);
868 univariate2.value_at(1) = FF(15);
869
870 verifier_round.check_sum(univariate1, FF(1));
871 bool result1 = !verifier_round.round_failed;
872 EXPECT_TRUE(result1) << "First check should pass";
873 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should be false after first pass";
874
875 verifier_round.target_total_sum = FF(20); // Update target for second check
876 verifier_round.check_sum(univariate2, FF(1));
877 bool result2 = !verifier_round.round_failed;
878 EXPECT_TRUE(result2) << "Second check should pass";
879 EXPECT_FALSE(verifier_round.round_failed) << "round_failed should remain false after second pass";
880
881 info("Multiple passes: round_failed correctly remains false");
882 }
883}
884
890TEST(SumcheckRound, CheckSumRecursiveUnsatisfiableWitness)
891{
892 using InnerBuilder = bb::UltraCircuitBuilder;
893 using RecursiveFlavor = bb::UltraRecursiveFlavor_<InnerBuilder>;
894 using FF = typename RecursiveFlavor::FF; // This is field_t<InnerBuilder> (stdlib field)
896 constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = RecursiveFlavor::BATCHED_RELATION_PARTIAL_LENGTH;
897
898 info("Test: Recursive check_sum with unsatisfiable witness");
899
900 // Test 1: Unsatisfiable witness - sum doesn't match target
901 {
902 info("Test 1: Unsatisfiable witness where target != S(0) + S(1)");
903
904 InnerBuilder builder;
905
906 // Create witness values that intentionally don't satisfy the check
907 auto native_val_0 = bb::fr(10);
908 auto native_val_1 = bb::fr(20);
909 auto native_wrong_target = bb::fr(100); // Intentionally wrong (correct would be 30)
910
911 // Create stdlib field elements (circuit variables)
912 FF val_0 = FF::from_witness(&builder, native_val_0);
913 FF val_1 = FF::from_witness(&builder, native_val_1);
914 FF wrong_target = FF::from_witness(&builder, native_wrong_target);
915
916 // Create univariate with these values
918 univariate.value_at(0) = val_0;
919 univariate.value_at(1) = val_1;
920
921 // Create verifier round with wrong target
922 SumcheckVerifierRound verifier_round(wrong_target);
923
924 // Call check_sum - this adds constraints to the circuit
925 // In recursive flavor, assert_equal is called which adds a constraint
926 FF indicator = FF(1); // Non-padding round
927 verifier_round.check_sum(univariate, indicator);
928 bool check_result = !verifier_round.round_failed;
929
930 // The check_sum itself should return false (based on witness values)
931 EXPECT_FALSE(check_result) << "check_sum should return false for mismatched values";
932
933 // The circuit should have failed (constraint violation detected)
934 EXPECT_TRUE(builder.failed()) << "Builder should detect constraint violation (unsatisfiable witness)";
935
936 info("Unsatisfiable witness: Builder correctly detects constraint violation");
937 }
938
939 // Test 2: Satisfiable witness - sum matches target
940 {
941 info("Test 2: Satisfiable witness where target == S(0) + S(1)");
942
943 InnerBuilder builder;
944
945 // Create witness values that DO satisfy the check
946 auto native_val_0 = bb::fr(10);
947 auto native_val_1 = bb::fr(20);
948 auto native_correct_target = native_val_0 + native_val_1; // 30 (correct)
949
950 // Create stdlib field elements
951 FF val_0 = FF::from_witness(&builder, native_val_0);
952 FF val_1 = FF::from_witness(&builder, native_val_1);
953 FF correct_target = FF::from_witness(&builder, native_correct_target);
954
955 // Create univariate
957 univariate.value_at(0) = val_0;
958 univariate.value_at(1) = val_1;
959
960 // Create verifier round with correct target
961 SumcheckVerifierRound verifier_round(correct_target);
962
963 // Call check_sum
964 FF indicator = FF(1);
965 verifier_round.check_sum(univariate, indicator);
966 bool check_result = !verifier_round.round_failed;
967
968 // Check should pass
969 EXPECT_TRUE(check_result) << "check_sum should return true for matching values";
970
971 // The circuit should NOT have failed
972 EXPECT_FALSE(builder.failed()) << "Builder should not fail for satisfiable witness";
973
974 // Verify the circuit is correct
975 EXPECT_TRUE(CircuitChecker::check(builder)) << "Circuit with satisfiable witness should pass CircuitChecker";
976
977 info("Satisfiable witness: Builder correctly validates constraint satisfaction");
978 }
979
980 // Test 3: Padding round with wrong values - should still pass (bypassed)
981 {
982 info("Test 3: Padding round (indicator=0) with wrong values - should not fail");
983
984 InnerBuilder builder;
985
986 // Create mismatched values
987 auto native_val_0 = bb::fr(10);
988 auto native_val_1 = bb::fr(20);
989 auto native_wrong_target = bb::fr(999); // Wrong
990
991 FF val_0 = FF::from_witness(&builder, native_val_0);
992 FF val_1 = FF::from_witness(&builder, native_val_1);
993 FF wrong_target = FF::from_witness(&builder, native_wrong_target);
994
996 univariate.value_at(0) = val_0;
997 univariate.value_at(1) = val_1;
998
999 SumcheckVerifierRound verifier_round(wrong_target);
1000
1001 // Padding round - indicator = 0
1002 FF indicator = FF(0);
1003 verifier_round.check_sum(univariate, indicator);
1004 bool check_result = !verifier_round.round_failed;
1005
1006 // Should pass because check is bypassed
1007 EXPECT_TRUE(check_result) << "check_sum should return true for padding round";
1008
1009 // Builder should NOT fail (no constraint violation in padding round)
1010 EXPECT_FALSE(builder.failed()) << "Builder should not fail for padding round (check bypassed)";
1011
1012 // Circuit should be valid
1013 EXPECT_TRUE(CircuitChecker::check(builder)) << "Padding round circuit should pass CircuitChecker";
1014
1015 info("Padding round: Builder correctly bypasses check (no constraint added)");
1016 }
1017
1018 // Test 4: Multiple rounds with one failure
1019 {
1020 info("Test 4: Multiple rounds where one has unsatisfiable witness");
1021
1022 InnerBuilder builder;
1023
1024 // First round: correct
1025 auto val_0_round1 = FF::from_witness(&builder, bb::fr(10));
1026 auto val_1_round1 = FF::from_witness(&builder, bb::fr(20));
1027 auto target_round1 = FF::from_witness(&builder, bb::fr(30));
1028
1030 univariate_1.value_at(0) = val_0_round1;
1031 univariate_1.value_at(1) = val_1_round1;
1032
1033 SumcheckVerifierRound verifier_round(target_round1);
1034 FF indicator = FF(1);
1035
1036 verifier_round.check_sum(univariate_1, indicator);
1037 bool result_1 = !verifier_round.round_failed;
1038 EXPECT_TRUE(result_1);
1039 EXPECT_FALSE(builder.failed()) << "First round should not fail";
1040
1041 // Second round: WRONG
1042 verifier_round.target_total_sum = FF::from_witness(&builder, bb::fr(999)); // Wrong target
1043 auto val_0_round2 = FF::from_witness(&builder, bb::fr(5));
1044 auto val_1_round2 = FF::from_witness(&builder, bb::fr(15));
1045
1047 univariate_2.value_at(0) = val_0_round2;
1048 univariate_2.value_at(1) = val_1_round2;
1049
1050 verifier_round.check_sum(univariate_2, indicator);
1051 bool result_2 = !verifier_round.round_failed;
1052 EXPECT_FALSE(result_2) << "Second round should fail";
1053
1054 // Builder should now have failed
1055 EXPECT_TRUE(builder.failed()) << "Builder should detect failure in second round";
1056
1057 info("Multiple rounds: Builder correctly detects failure in one of multiple rounds");
1058 }
1059}
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)
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 when !HasZK by finding the maximum end_index() across witness polyno...
Implementation of the Sumcheck Verifier Round.
void check_sum(bb::Univariate< FF, BATCHED_RELATION_PARTIAL_LENGTH > &univariate, const FF &indicator)
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)
void info(Args... args)
Definition log.hpp:75
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:174
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.