Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
cycle_group.test.cpp
Go to the documentation of this file.
14#include <gtest/gtest.h>
15
16#define STDLIB_TYPE_ALIASES \
17 using Builder = TypeParam; \
18 using cycle_group_ct = stdlib::cycle_group<Builder>; \
19 using Curve = typename stdlib::cycle_group<Builder>::Curve; \
20 using Element = typename Curve::Element; \
21 using AffineElement = typename Curve::AffineElement; \
22 using Group = typename Curve::Group; \
23 using bool_ct = stdlib::bool_t<Builder>; \
24 using witness_ct = stdlib::witness_t<Builder>; \
25 using cycle_scalar_ct = cycle_group_ct::cycle_scalar;
26
27using namespace bb;
28
29namespace {
31}
32#pragma GCC diagnostic push
33#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
34
35template <class Builder> class CycleGroupTest : public ::testing::Test {
36 public:
38 using Group = typename Curve::Group;
39
40 using Element = typename Curve::Element;
42
43 static constexpr size_t num_generators = 110;
44 static inline std::array<AffineElement, num_generators> generators{};
45
46 static void SetUpTestSuite()
47 {
48
49 for (size_t i = 0; i < num_generators; ++i) {
50 generators[i] = Group::one * Curve::ScalarField::random_element(&engine);
51 }
52 };
53};
54
55using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder, bb::MegaCircuitBuilder>;
57
58// Import the check_circuit_and_gate_count function from test_utils
60
66TYPED_TEST(CycleGroupTest, TestBasicTagLogic)
67{
70
71 // Create field elements with specific tags before constructing the cycle_group
72 auto lhs = TestFixture::generators[0];
75 auto is_infinity = bool_ct(witness_ct(&builder, lhs.is_point_at_infinity()));
76
77 // Set tags on the individual field elements
78 x.set_origin_tag(submitted_value_origin_tag);
79 y.set_origin_tag(challenge_origin_tag);
80 is_infinity.set_origin_tag(next_challenge_tag);
81
82 // Construct cycle_group from pre-tagged field elements
83 cycle_group_ct a(x, y, is_infinity, /*assert_on_curve=*/true);
84
85 // The tag of the cycle_group should be the union of all 3 member tags
86 EXPECT_EQ(a.get_origin_tag(), first_second_third_merged_tag);
87
88#ifndef NDEBUG
89 // Test that instant_death_tag on x coordinate propagates correctly
90 auto x_death = stdlib::field_t<Builder>::from_witness(&builder, TestFixture::generators[1].x);
91 auto y_normal = stdlib::field_t<Builder>::from_witness(&builder, TestFixture::generators[1].y);
92 auto is_infinity_normal = bool_ct(witness_ct(&builder, TestFixture::generators[1].is_point_at_infinity()));
93
94 x_death.set_origin_tag(instant_death_tag);
95
96 cycle_group_ct b(x_death, y_normal, is_infinity_normal, /*assert_on_curve=*/true);
97 // Even requesting the tag of the whole structure can cause instant death
98 EXPECT_THROW(b.get_origin_tag(), std::runtime_error);
99#endif
100}
101
106TYPED_TEST(CycleGroupTest, TestInfConstantWintnessRegression)
107{
110
111 auto lhs = TestFixture::generators[0] * 0;
112 cycle_group_ct a = cycle_group_ct::from_constant_witness(&builder, lhs);
113 (void)a;
114 EXPECT_FALSE(builder.failed());
115 check_circuit_and_gate_count(builder, 0);
116}
117
122TYPED_TEST(CycleGroupTest, TestInfWintnessRegression)
123{
126
127 auto lhs = TestFixture::generators[0] * 0;
128 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
129 (void)a;
130 EXPECT_FALSE(builder.failed());
131 check_circuit_and_gate_count(builder, 6);
132}
133
138TYPED_TEST(CycleGroupTest, TestWitnessSumRegression)
139{
142
143 auto lhs = TestFixture::generators[0];
144 auto rhs = TestFixture::generators[1];
145 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
146 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
147 cycle_group_ct c = a + b;
148 EXPECT_FALSE(c.is_constant());
149 c = a - b;
150 EXPECT_FALSE(c.is_constant());
151}
152
157TYPED_TEST(CycleGroupTest, TestOperatorNegRegression)
158{
161
162 auto lhs = TestFixture::generators[0];
163 auto rhs = TestFixture::generators[1];
164 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
165 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
166 b = -b;
167 cycle_group_ct c = a.unconditional_add(b);
168 (void)c;
169 EXPECT_FALSE(builder.failed());
170 check_circuit_and_gate_count(builder, 15);
171}
172
177TYPED_TEST(CycleGroupTest, TestConstantWitnessMixupRegression)
178{
181
182 auto c1 = cycle_group_ct(AffineElement::one());
183 auto cw8 = cycle_group_ct::from_constant_witness(&builder, AffineElement::one() * 0);
184 auto w11 = cycle_group_ct::from_witness(&builder, TestFixture::generators[0]);
185
186 auto w9 = cw8 + c1; // mixup happens here due to _is_infinity being a constant
187 auto w26 = w9 + w11; // and here the circuit checker crashes
188
189 auto w10 = cw8 - c1;
190 auto w27 = w10 - w11; // and here
191 (void)w26;
192 (void)w27;
193 check_circuit_and_gate_count(builder, 40);
194}
195
200TYPED_TEST(CycleGroupTest, TestConditionalAssignRegression)
201{
204
205 auto c0 = cycle_group_ct(AffineElement::one() * 0);
206 auto c1 = cycle_group_ct::conditional_assign(bool_ct(witness_ct(&builder, false)), c0, c0);
207 auto w3 = c1.dbl();
208 (void)w3;
209 check_circuit_and_gate_count(builder, 1);
210}
211
216TYPED_TEST(CycleGroupTest, TestConditionalAssignSuperMixupRegression)
217{
220
221 auto c0 = cycle_group_ct(TestFixture::generators[0]);
222 auto c1 = cycle_group_ct(-TestFixture::generators[0]);
223 auto w2 = cycle_group_ct::conditional_assign(bool_ct(witness_ct(&builder, true)), c0, c1);
224 EXPECT_FALSE(w2.x().is_constant());
225 EXPECT_FALSE(w2.y().is_constant());
226 EXPECT_TRUE(w2.is_point_at_infinity().is_constant());
227 auto w3 = w2.dbl();
228 (void)w3;
229 check_circuit_and_gate_count(builder, 5);
230}
231
236TYPED_TEST(CycleGroupTest, TestValidateOnCurveSucceed)
237{
240
241 auto point_val = TestFixture::generators[0];
242 auto x = stdlib::field_t<Builder>::from_witness(&builder, point_val.x);
243 auto y = stdlib::field_t<Builder>::from_witness(&builder, point_val.y);
244 auto is_infinity = bool_ct(witness_ct(&builder, point_val.is_point_at_infinity()));
245
246 cycle_group_ct point(x, y, is_infinity, /*assert_on_curve=*/true);
247 EXPECT_FALSE(builder.failed());
248 check_circuit_and_gate_count(builder, 6);
249}
250
256TYPED_TEST(CycleGroupTest, TestValidateOnCurveInfinitySucceed)
257{
260
263
264 cycle_group_ct a(x, y, /*_is_infinity=*/true, /*assert_on_curve=*/true);
265 EXPECT_FALSE(builder.failed());
266 check_circuit_and_gate_count(builder, 0);
267}
268
274TYPED_TEST(CycleGroupTest, TestValidateOnCurveFail)
275{
276 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
279
282
283 cycle_group_ct a(x, y, /*_is_infinity=*/false, /*assert_on_curve=*/true);
284 EXPECT_TRUE(builder.failed());
285 EXPECT_FALSE(CircuitChecker::check(builder));
286}
287
293TYPED_TEST(CycleGroupTest, TestValidateOnCurveFail2)
294{
295 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
298
301
302 cycle_group_ct a(x, y, /*_is_infinity=*/bool_ct(witness_ct(&builder, false)), /*assert_on_curve=*/true);
303 EXPECT_TRUE(builder.failed());
304 EXPECT_FALSE(CircuitChecker::check(builder));
305}
306
307TYPED_TEST(CycleGroupTest, TestStandardForm)
308{
310 auto builder = Builder();
311
312 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
313 cycle_group_ct input_a = cycle_group_ct::from_witness(&builder, Element::random_element());
314 cycle_group_ct input_b = cycle_group_ct::from_witness(&builder, affine_infinity);
315 cycle_group_ct input_c = cycle_group_ct(Element::random_element());
316 cycle_group_ct input_d = cycle_group_ct(affine_infinity);
317
320 cycle_group_ct input_e = cycle_group_ct(x, y, true, /*assert_on_curve=*/true);
321 cycle_group_ct input_f = cycle_group_ct(x, y, bool_ct(witness_ct(&builder, true)), /*assert_on_curve=*/true);
322
323 // Assign different tags to all inputs
324 input_a.set_origin_tag(submitted_value_origin_tag);
325 input_b.set_origin_tag(challenge_origin_tag);
326 input_c.set_origin_tag(next_challenge_tag);
327 input_d.set_origin_tag(first_two_merged_tag);
328
329 input_a.standardize();
330 auto standard_a = input_a;
331 input_b.standardize();
332 auto standard_b = input_b;
333 input_c.standardize();
334 auto standard_c = input_c;
335 input_d.standardize();
336 auto standard_d = input_d;
337 input_e.standardize();
338 auto standard_e = input_e;
339 input_f.standardize();
340 auto standard_f = input_f;
341
342 EXPECT_EQ(standard_a.is_point_at_infinity().get_value(), false);
343 EXPECT_EQ(standard_b.is_point_at_infinity().get_value(), true);
344 EXPECT_EQ(standard_c.is_point_at_infinity().get_value(), false);
345 EXPECT_EQ(standard_d.is_point_at_infinity().get_value(), true);
346 EXPECT_EQ(standard_e.is_point_at_infinity().get_value(), true);
347 EXPECT_EQ(standard_f.is_point_at_infinity().get_value(), true);
348
349 // Ensure that the tags in the standard form remain the same
350 EXPECT_EQ(standard_a.get_origin_tag(), submitted_value_origin_tag);
351 EXPECT_EQ(standard_b.get_origin_tag(), challenge_origin_tag);
352 EXPECT_EQ(standard_c.get_origin_tag(), next_challenge_tag);
353 EXPECT_EQ(standard_d.get_origin_tag(), first_two_merged_tag);
354
355 auto input_a_x = input_a.x().get_value();
356 auto input_a_y = input_a.y().get_value();
357 auto input_c_x = input_c.x().get_value();
358 auto input_c_y = input_c.y().get_value();
359
360 auto standard_a_x = standard_a.x().get_value();
361 auto standard_a_y = standard_a.y().get_value();
362
363 auto standard_b_x = standard_b.x().get_value();
364 auto standard_b_y = standard_b.y().get_value();
365
366 auto standard_c_x = standard_c.x().get_value();
367 auto standard_c_y = standard_c.y().get_value();
368
369 auto standard_d_x = standard_d.x().get_value();
370 auto standard_d_y = standard_d.y().get_value();
371
372 auto standard_e_x = standard_e.x().get_value();
373 auto standard_e_y = standard_e.y().get_value();
374
375 auto standard_f_x = standard_f.x().get_value();
376 auto standard_f_y = standard_f.y().get_value();
377
378 EXPECT_EQ(input_a_x, standard_a_x);
379 EXPECT_EQ(input_a_y, standard_a_y);
380 EXPECT_EQ(standard_b_x, 0);
381 EXPECT_EQ(standard_b_y, 0);
382 EXPECT_EQ(input_c_x, standard_c_x);
383 EXPECT_EQ(input_c_y, standard_c_y);
384 EXPECT_EQ(standard_d_x, 0);
385 EXPECT_EQ(standard_d_y, 0);
386 EXPECT_EQ(standard_e_x, 0);
387 EXPECT_EQ(standard_e_y, 0);
388 EXPECT_EQ(standard_f_x, 0);
389 EXPECT_EQ(standard_f_y, 0);
390
391 check_circuit_and_gate_count(builder, 24);
392}
394{
396 auto builder = Builder();
397
398 auto lhs = TestFixture::generators[0];
399 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
400 cycle_group_ct b = cycle_group_ct(lhs);
401 // Assign two different tags
402 a.set_origin_tag(submitted_value_origin_tag);
403 b.set_origin_tag(challenge_origin_tag);
404 cycle_group_ct c;
405 cycle_group_ct d;
406 for (size_t i = 0; i < 3; ++i) {
407 c = a.dbl();
408 }
409 d = b.dbl();
410 AffineElement expected(Element(lhs).dbl());
411 AffineElement result = c.get_value();
412 EXPECT_EQ(result, expected);
413 EXPECT_EQ(d.get_value(), expected);
414
415 check_circuit_and_gate_count(builder, 15);
416
417 // Ensure the tags stay the same after doubling
418 EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag);
419 EXPECT_EQ(d.get_origin_tag(), challenge_origin_tag);
420}
421
422TYPED_TEST(CycleGroupTest, TestDblNonConstantPoints)
423{
425
426 // Test case 1: Witness point WITH hint
427 {
428 auto builder = Builder();
429 auto lhs = TestFixture::generators[0];
430 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
431
432 Element doubled_element = Element(lhs).dbl();
433 AffineElement hint(doubled_element);
434
435 cycle_group_ct result = a.dbl(hint);
436
437 EXPECT_EQ(result.get_value(), hint);
438 EXPECT_FALSE(result.is_point_at_infinity().get_value());
439
440 check_circuit_and_gate_count(builder, 9);
441 }
442
443 // Test case 2: Witness point WITHOUT hint
444 {
445 auto builder = Builder();
446 auto lhs = TestFixture::generators[1];
447 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
448
449 cycle_group_ct result = a.dbl();
450
451 Element expected_element = Element(lhs).dbl();
452 AffineElement expected(expected_element);
453 EXPECT_EQ(result.get_value(), expected);
454 EXPECT_FALSE(result.is_point_at_infinity().get_value());
455
456 // Note: same gate count as with hint - hint is a witness generation optimization only
457 check_circuit_and_gate_count(builder, 9);
458 }
459
460 // Test case 3: Witness infinity point WITHOUT hint
461 {
462 auto builder = Builder();
463 AffineElement infinity_element;
464 infinity_element.self_set_infinity();
465
466 cycle_group_ct infinity = cycle_group_ct::from_witness(&builder, infinity_element);
467
468 cycle_group_ct result = infinity.dbl();
469
470 EXPECT_TRUE(result.is_point_at_infinity().get_value());
471 // Note: from_witness sets x,y to witness(0,0) for infinity points
472 // After doubling, y becomes -1 (0x3064...) due to the modified_y logic
473 EXPECT_EQ(result.x().get_value(), 0);
474
475 // Same gate count as regular witness points
476 check_circuit_and_gate_count(builder, 9);
477 }
478}
479
480TYPED_TEST(CycleGroupTest, TestDblConstantPoints)
481{
483
484 // Test case 1: Constant point WITH hint
485 {
486 auto builder = Builder();
487 auto lhs = TestFixture::generators[0];
488 cycle_group_ct a(lhs);
489
490 Element doubled_element = Element(lhs).dbl();
491 AffineElement hint(doubled_element);
492
493 cycle_group_ct result = a.dbl(hint);
494
495 EXPECT_EQ(result.get_value(), hint);
496 EXPECT_TRUE(result.is_constant());
497 EXPECT_FALSE(result.is_point_at_infinity().get_value());
498
499 check_circuit_and_gate_count(builder, 0);
500 }
501
502 // Test case 2: Constant point WITHOUT hint
503 {
504 auto builder = Builder();
505 auto lhs = TestFixture::generators[1];
506 cycle_group_ct a(lhs);
507
508 cycle_group_ct result = a.dbl();
509
510 Element expected_element = Element(lhs).dbl();
511 AffineElement expected(expected_element);
512 EXPECT_EQ(result.get_value(), expected);
513 EXPECT_TRUE(result.is_constant());
514 EXPECT_FALSE(result.is_point_at_infinity().get_value());
515
516 check_circuit_and_gate_count(builder, 0);
517 }
518
519 // Test case 3: Constant infinity point WITHOUT hint
520 {
521 auto builder = Builder();
522 cycle_group_ct infinity = cycle_group_ct::constant_infinity(nullptr);
523
524 cycle_group_ct result = infinity.dbl();
525
526 EXPECT_TRUE(result.is_point_at_infinity().get_value());
527 EXPECT_TRUE(result.is_constant());
528 EXPECT_EQ(result.x().get_value(), 0);
529 EXPECT_EQ(result.y().get_value(), 0);
530
531 check_circuit_and_gate_count(builder, 0);
532 }
533
534 // Test case 4: Constant infinity point WITH hint
535 {
536 auto builder = Builder();
537 cycle_group_ct infinity = cycle_group_ct::constant_infinity(nullptr);
538
539 AffineElement hint;
540 hint.self_set_infinity();
541
542 cycle_group_ct result = infinity.dbl(hint);
543
544 EXPECT_TRUE(result.is_point_at_infinity().get_value());
545 EXPECT_TRUE(result.is_constant());
546 EXPECT_EQ(result.x().get_value(), 0);
547 EXPECT_EQ(result.y().get_value(), 0);
548
549 check_circuit_and_gate_count(builder, 0);
550 }
551}
552
553TYPED_TEST(CycleGroupTest, TestDblMixedConstantWitness)
554{
556 auto builder = Builder();
557
558 // Test doubling where x is constant but y is witness (edge case)
559 auto point = TestFixture::generators[1];
560 auto x = stdlib::field_t<Builder>(&builder, point.x); // constant
561 auto y = stdlib::field_t<Builder>(witness_ct(&builder, point.y)); // witness
562
563 // Mixed constancy is remedied inside the constructor; x will be converted to a fixed witness
564 cycle_group_ct a(x, y, false, /*assert_on_curve=*/false);
565
566 EXPECT_FALSE(a.x().is_constant());
567 EXPECT_FALSE(a.y().is_constant());
568
569 a.dbl();
570
571 check_circuit_and_gate_count(builder, 3);
572}
573
574TYPED_TEST(CycleGroupTest, TestUnconditionalAddNonConstantPoints)
575{
577
578 // Test case 1: Two witness points WITHOUT hint
579 {
580 auto builder = Builder();
581 auto lhs = TestFixture::generators[0];
582 auto rhs = TestFixture::generators[1];
583 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
584 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
585
586 cycle_group_ct result = a.unconditional_add(b);
587
588 Element expected_element = Element(lhs) + Element(rhs);
589 AffineElement expected(expected_element);
590 EXPECT_EQ(result.get_value(), expected);
591 EXPECT_FALSE(result.is_point_at_infinity().get_value());
592
593 check_circuit_and_gate_count(builder, 14);
594 }
595
596 // Test case 2: Two witness points WITH hint
597 {
598 auto builder = Builder();
599 auto lhs = TestFixture::generators[2];
600 auto rhs = TestFixture::generators[3];
601 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
602 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
603
604 Element sum_element = Element(lhs) + Element(rhs);
605 AffineElement hint(sum_element);
606
607 cycle_group_ct result = a.unconditional_add(b, hint);
608
609 EXPECT_EQ(result.get_value(), hint);
610 EXPECT_FALSE(result.is_point_at_infinity().get_value());
611
612 check_circuit_and_gate_count(builder, 14);
613 }
614
615 // Test case 3: Mixed witness and constant points
616 {
617 auto builder = Builder();
618 auto lhs = TestFixture::generators[0];
619 auto rhs = TestFixture::generators[1];
620 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
621 cycle_group_ct b(rhs); // constant
622
623 cycle_group_ct result = a.unconditional_add(b);
624
625 Element expected_element = Element(lhs) + Element(rhs);
626 AffineElement expected(expected_element);
627 EXPECT_EQ(result.get_value(), expected);
628 EXPECT_FALSE(result.is_constant());
629 EXPECT_FALSE(result.is_point_at_infinity().get_value());
630
631 check_circuit_and_gate_count(builder, 10);
632 }
633}
634
635TYPED_TEST(CycleGroupTest, TestUnconditionalAddConstantPoints)
636{
638
639 // Test case 1: Two constant points WITHOUT hint
640 {
641 auto builder = Builder();
642 auto lhs = TestFixture::generators[0];
643 auto rhs = TestFixture::generators[1];
644 cycle_group_ct a(lhs);
645 cycle_group_ct b(rhs);
646
647 cycle_group_ct result = a.unconditional_add(b);
648
649 Element expected_element = Element(lhs) + Element(rhs);
650 AffineElement expected(expected_element);
651 EXPECT_EQ(result.get_value(), expected);
652 EXPECT_TRUE(result.is_constant());
653 EXPECT_FALSE(result.is_point_at_infinity().get_value());
654
655 check_circuit_and_gate_count(builder, 0);
656 }
657
658 // Test case 2: Two constant points WITH hint
659 {
660 auto builder = Builder();
661 auto lhs = TestFixture::generators[2];
662 auto rhs = TestFixture::generators[3];
663 cycle_group_ct a(lhs);
664 cycle_group_ct b(rhs);
665
666 Element sum_element = Element(lhs) + Element(rhs);
667 AffineElement hint(sum_element);
668
669 cycle_group_ct result = a.unconditional_add(b, hint);
670
671 EXPECT_EQ(result.get_value(), hint);
672 EXPECT_TRUE(result.is_constant());
673 EXPECT_FALSE(result.is_point_at_infinity().get_value());
674
675 check_circuit_and_gate_count(builder, 0);
676 }
677}
678
679TYPED_TEST(CycleGroupTest, TestUnconditionalSubtractNonConstantPoints)
680{
682
683 // Test case 1: Two witness points WITHOUT hint
684 {
685 auto builder = Builder();
686 auto lhs = TestFixture::generators[0];
687 auto rhs = TestFixture::generators[1];
688 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
689 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
690
691 cycle_group_ct result = a.unconditional_subtract(b);
692
693 Element expected_element = Element(lhs) - Element(rhs);
694 AffineElement expected(expected_element);
695 EXPECT_EQ(result.get_value(), expected);
696 EXPECT_FALSE(result.is_point_at_infinity().get_value());
697
698 check_circuit_and_gate_count(builder, 14);
699 }
700
701 // Test case 2: Two witness points WITH hint
702 {
703 auto builder = Builder();
704 auto lhs = TestFixture::generators[2];
705 auto rhs = TestFixture::generators[3];
706 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
707 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
708
709 Element diff_element = Element(lhs) - Element(rhs);
710 AffineElement hint(diff_element);
711
712 cycle_group_ct result = a.unconditional_subtract(b, hint);
713
714 EXPECT_EQ(result.get_value(), hint);
715 EXPECT_FALSE(result.is_point_at_infinity().get_value());
716
717 // Same gate count as without hint - hint is a witness generation optimization only
718 check_circuit_and_gate_count(builder, 14);
719 }
720
721 // Test case 3: Mixed witness and constant points
722 {
723 auto builder = Builder();
724 auto lhs = TestFixture::generators[0];
725 auto rhs = TestFixture::generators[1];
726 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
727 cycle_group_ct b(rhs); // constant
728
729 cycle_group_ct result = a.unconditional_subtract(b);
730
731 Element expected_element = Element(lhs) - Element(rhs);
732 AffineElement expected(expected_element);
733 EXPECT_EQ(result.get_value(), expected);
734 EXPECT_FALSE(result.is_constant());
735 EXPECT_FALSE(result.is_point_at_infinity().get_value());
736
737 check_circuit_and_gate_count(builder, 10);
738 }
739}
740
741TYPED_TEST(CycleGroupTest, TestUnconditionalSubtractConstantPoints)
742{
744
745 // Test case 1: Two constant points WITHOUT hint
746 {
747 auto builder = Builder();
748 auto lhs = TestFixture::generators[0];
749 auto rhs = TestFixture::generators[1];
750 cycle_group_ct a(lhs);
751 cycle_group_ct b(rhs);
752
753 cycle_group_ct result = a.unconditional_subtract(b);
754
755 Element expected_element = Element(lhs) - Element(rhs);
756 AffineElement expected(expected_element);
757 EXPECT_EQ(result.get_value(), expected);
758 EXPECT_TRUE(result.is_constant());
759 EXPECT_FALSE(result.is_point_at_infinity().get_value());
760
761 check_circuit_and_gate_count(builder, 0);
762 }
763
764 // Test case 2: Two constant points WITH hint
765 {
766 auto builder = Builder();
767 auto lhs = TestFixture::generators[2];
768 auto rhs = TestFixture::generators[3];
769 cycle_group_ct a(lhs);
770 cycle_group_ct b(rhs);
771
772 Element diff_element = Element(lhs) - Element(rhs);
773 AffineElement hint(diff_element);
774
775 cycle_group_ct result = a.unconditional_subtract(b, hint);
776
777 EXPECT_EQ(result.get_value(), hint);
778 EXPECT_TRUE(result.is_constant());
779 EXPECT_FALSE(result.is_point_at_infinity().get_value());
780
781 check_circuit_and_gate_count(builder, 0);
782 }
783}
784
785TYPED_TEST(CycleGroupTest, TestUnconditionalAdd)
786{
788 auto builder = Builder();
789
790 auto add =
791 [&](const AffineElement& lhs, const AffineElement& rhs, const bool lhs_constant, const bool rhs_constant) {
792 cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&builder, lhs);
793 cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&builder, rhs);
794 // Assign two different tags
795 a.set_origin_tag(submitted_value_origin_tag);
796 b.set_origin_tag(challenge_origin_tag);
797 cycle_group_ct c = a.unconditional_add(b);
798 AffineElement expected(Element(lhs) + Element(rhs));
799 AffineElement result = c.get_value();
800 EXPECT_EQ(result, expected);
801 // Ensure the tags in the result are merged
802 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
803 };
804
805 add(TestFixture::generators[0], TestFixture::generators[1], false, false);
806 add(TestFixture::generators[0], TestFixture::generators[1], false, true);
807 add(TestFixture::generators[0], TestFixture::generators[1], true, false);
808 add(TestFixture::generators[0], TestFixture::generators[1], true, true);
809
810 check_circuit_and_gate_count(builder, 34);
811}
812
813TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddSucceed)
814{
816 auto builder = Builder();
817
818 auto lhs = TestFixture::generators[0];
819 auto rhs = TestFixture::generators[1];
820
821 // case 1. valid unconditional add
822 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
823 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
824 cycle_group_ct c = a.checked_unconditional_add(b);
825 AffineElement expected(Element(lhs) + Element(rhs));
826 AffineElement result = c.get_value();
827 EXPECT_EQ(result, expected);
828
829 check_circuit_and_gate_count(builder, 16);
830}
831
832TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddFail)
833{
834 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
836 auto builder = Builder();
837
838 auto lhs = TestFixture::generators[0];
839 auto rhs = -TestFixture::generators[0]; // ruh roh
840
841 // case 2. invalid unconditional add
842 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
843 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
844 a.checked_unconditional_add(b);
845
846 EXPECT_TRUE(builder.failed());
847 // No gate count check for failing test
848 EXPECT_FALSE(CircuitChecker::check(builder));
849}
850
851// Test regular addition of witness points (no edge cases)
853{
855 auto builder = Builder();
856
857 auto lhs = TestFixture::generators[0];
858 auto rhs = -TestFixture::generators[1];
859
860 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
861 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
862
863 // Test tag merging
864 a.set_origin_tag(submitted_value_origin_tag);
865 b.set_origin_tag(challenge_origin_tag);
866
867 cycle_group_ct c = a + b;
868
869 AffineElement expected(Element(lhs) + Element(rhs));
870 EXPECT_EQ(c.get_value(), expected);
871 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
872
873 check_circuit_and_gate_count(builder, 47);
874}
875
876// Test addition with LHS point at infinity
877TYPED_TEST(CycleGroupTest, TestAddLhsInfinity)
878{
880 auto builder = Builder();
881
882 auto rhs = -TestFixture::generators[1];
883 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
884
885 cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&builder, affine_infinity);
886
887 cycle_group_ct a = point_at_infinity;
888 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
889
890 a.set_origin_tag(submitted_value_origin_tag);
891 b.set_origin_tag(challenge_origin_tag);
892
893 cycle_group_ct c = a + b;
894
895 EXPECT_EQ(c.get_value(), rhs);
896 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
897
898 check_circuit_and_gate_count(builder, 47);
899}
900
901// Test addition with RHS point at infinity
902TYPED_TEST(CycleGroupTest, TestAddRhsInfinity)
903{
905 auto builder = Builder();
906
907 auto lhs = TestFixture::generators[0];
908 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
909
910 cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&builder, affine_infinity);
911
912 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
913 cycle_group_ct b = point_at_infinity;
914
915 a.set_origin_tag(submitted_value_origin_tag);
916 b.set_origin_tag(challenge_origin_tag);
917
918 cycle_group_ct c = a + b;
919
920 EXPECT_EQ(c.get_value(), lhs);
921 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
922
923 // Addition with witness infinity point
924 check_circuit_and_gate_count(builder, 47);
925}
926
927// Test addition with both points at infinity
928TYPED_TEST(CycleGroupTest, TestAddBothInfinity)
929{
931 auto builder = Builder();
932
933 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
934
935 cycle_group_ct point_at_infinity1 = cycle_group_ct::from_witness(&builder, affine_infinity);
936
937 cycle_group_ct point_at_infinity2 = cycle_group_ct::from_witness(&builder, affine_infinity);
938
939 cycle_group_ct a = point_at_infinity1;
940 cycle_group_ct b = point_at_infinity2;
941
942 a.set_origin_tag(submitted_value_origin_tag);
943 b.set_origin_tag(challenge_origin_tag);
944
945 cycle_group_ct c = a + b;
946
947 EXPECT_TRUE(c.is_point_at_infinity().get_value());
948 EXPECT_TRUE(c.get_value().is_point_at_infinity());
949 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
950
951 check_circuit_and_gate_count(builder, 47);
952}
953
954// Test addition of inverse points (result is infinity)
955TYPED_TEST(CycleGroupTest, TestAddInversePoints)
956{
958 auto builder = Builder();
959
960 auto lhs = TestFixture::generators[0];
961
962 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
963 cycle_group_ct b = cycle_group_ct::from_witness(&builder, -lhs);
964
965 a.set_origin_tag(submitted_value_origin_tag);
966 b.set_origin_tag(challenge_origin_tag);
967
968 cycle_group_ct c = a + b;
969
970 EXPECT_TRUE(c.is_point_at_infinity().get_value());
971 EXPECT_TRUE(c.get_value().is_point_at_infinity());
972 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
973
974 check_circuit_and_gate_count(builder, 47);
975}
976
977// Test doubling (adding point to itself)
978TYPED_TEST(CycleGroupTest, TestAddDoubling)
979{
981 auto builder = Builder();
982
983 auto lhs = TestFixture::generators[0];
984
985 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
986 cycle_group_ct b = cycle_group_ct::from_witness(&builder, lhs);
987
988 a.set_origin_tag(submitted_value_origin_tag);
989 b.set_origin_tag(challenge_origin_tag);
990
991 cycle_group_ct c = a + b;
992
993 AffineElement expected((Element(lhs)).dbl());
994 EXPECT_EQ(c.get_value(), expected);
995 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
996
997 check_circuit_and_gate_count(builder, 47);
998}
999
1000TYPED_TEST(CycleGroupTest, TestAddConstantPoints)
1001{
1003
1004 // Test adding constant points - this takes a completely different path than witness points
1005 // The existing TestAdd only tests witness points
1006 {
1007 auto builder = Builder();
1008 auto lhs = TestFixture::generators[5];
1009 auto rhs = TestFixture::generators[6];
1010
1011 cycle_group_ct a(lhs);
1012 cycle_group_ct b(rhs);
1013
1014 cycle_group_ct result = a + b;
1015
1016 AffineElement expected(Element(lhs) + Element(rhs));
1017 EXPECT_EQ(result.get_value(), expected);
1018 EXPECT_TRUE(result.is_constant());
1019
1020 // No gates needed for constant arithmetic
1021 check_circuit_and_gate_count(builder, 0);
1022 }
1023
1024 // Test constant point + constant infinity (early return optimization)
1025 {
1026 auto builder = Builder();
1027 auto lhs = TestFixture::generators[7];
1028
1029 cycle_group_ct a(lhs);
1030 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1031
1032 cycle_group_ct result = a + b;
1033
1034 EXPECT_EQ(result.get_value(), lhs);
1035 EXPECT_TRUE(result.is_constant());
1036
1037 // Uses early return for constant infinity
1038 check_circuit_and_gate_count(builder, 0);
1039 }
1040}
1041
1042TYPED_TEST(CycleGroupTest, TestAddMixedConstantWitness)
1043{
1045
1046 // Test mixed constant/witness operations which use different code paths than pure witness ops
1047 // The existing TestAdd doesn't cover these mixed scenarios
1048
1049 // Test witness + constant infinity (early return path)
1050 {
1051 auto builder = Builder();
1052 auto lhs = TestFixture::generators[10];
1053
1054 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1055 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1056
1057 cycle_group_ct result = a + b;
1058
1059 EXPECT_EQ(result.get_value(), lhs);
1060 EXPECT_FALSE(result.is_constant());
1061
1062 // Early return optimization for constant infinity
1063 check_circuit_and_gate_count(builder, 6);
1064 }
1065
1066 // Test constant + witness point (different gate count than witness + witness)
1067 {
1068 auto builder = Builder();
1069 auto lhs = TestFixture::generators[11];
1070 auto rhs = TestFixture::generators[12];
1071
1072 cycle_group_ct a(lhs); // constant
1073 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs); // witness
1074
1075 cycle_group_ct result = a + b;
1076
1077 AffineElement expected(Element(lhs) + Element(rhs));
1078 EXPECT_EQ(result.get_value(), expected);
1079 EXPECT_FALSE(result.is_constant());
1080
1081 // Different gate count than pure witness addition
1082 check_circuit_and_gate_count(builder, 23);
1083 }
1084}
1085
1086// Test the infinity result logic specifically
1087TYPED_TEST(CycleGroupTest, TestAddInfinityResultLogic)
1088{
1090 auto builder = Builder();
1091
1092 // Test Case 1: P + (-P) = O (infinity_predicate true, neither input is infinity)
1093 {
1094 auto point = TestFixture::generators[0];
1095 auto neg_point = -point;
1096
1097 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1098 cycle_group_ct b = cycle_group_ct::from_witness(&builder, neg_point);
1099
1100 cycle_group_ct result = a + b;
1101
1102 // Verify result is infinity
1103 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1104 EXPECT_TRUE(result.get_value().is_point_at_infinity());
1105 }
1106
1107 // Test Case 2: O + O = O (both inputs are infinity)
1108 {
1109 cycle_group_ct inf1 = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1110 cycle_group_ct inf2 = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1111
1112 cycle_group_ct result = inf1 + inf2;
1113
1114 // Verify result is infinity
1115 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1116 EXPECT_TRUE(result.get_value().is_point_at_infinity());
1117 }
1118
1119 // Test Case 3: P + O = P (only rhs is infinity, result should NOT be infinity)
1120 {
1121 auto point = TestFixture::generators[1];
1122
1123 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1124 cycle_group_ct b = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1125
1126 cycle_group_ct result = a + b;
1127
1128 // Verify result is NOT infinity
1129 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1130 EXPECT_EQ(result.get_value(), point);
1131 }
1132
1133 // Test Case 4: O + P = P (only lhs is infinity, result should NOT be infinity)
1134 {
1135 auto point = TestFixture::generators[2];
1136
1137 cycle_group_ct a = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1138 cycle_group_ct b = cycle_group_ct::from_witness(&builder, point);
1139
1140 cycle_group_ct result = a + b;
1141
1142 // Verify result is NOT infinity
1143 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1144 EXPECT_EQ(result.get_value(), point);
1145 }
1146
1147 // Test Case 5: P + P = 2P (doubling, result should NOT be infinity unless P is special)
1148 {
1149 auto point = TestFixture::generators[3];
1150
1151 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1152 cycle_group_ct b = cycle_group_ct::from_witness(&builder, point);
1153
1154 cycle_group_ct result = a + b;
1155
1156 // Verify result is NOT infinity (it's 2P)
1157 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1158
1159 AffineElement expected(Element(point).dbl());
1160 EXPECT_EQ(result.get_value(), expected);
1161 }
1162
1163 check_circuit_and_gate_count(builder, 235);
1164}
1165
1166TYPED_TEST(CycleGroupTest, TestUnconditionalSubtract)
1167{
1169 auto builder = Builder();
1170
1171 auto subtract =
1172 [&](const AffineElement& lhs, const AffineElement& rhs, const bool lhs_constant, const bool rhs_constant) {
1173 cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&builder, lhs);
1174 cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&builder, rhs);
1175 // Assign two different tags
1176 a.set_origin_tag(submitted_value_origin_tag);
1177 b.set_origin_tag(challenge_origin_tag);
1178
1179 cycle_group_ct c = a.unconditional_subtract(b);
1180 AffineElement expected(Element(lhs) - Element(rhs));
1181 AffineElement result = c.get_value();
1182 EXPECT_EQ(result, expected);
1183 // Expect tags to be merged in the result
1184 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1185 };
1186
1187 subtract(TestFixture::generators[0], TestFixture::generators[1], false, false);
1188 subtract(TestFixture::generators[0], TestFixture::generators[1], false, true);
1189 subtract(TestFixture::generators[0], TestFixture::generators[1], true, false);
1190 subtract(TestFixture::generators[0], TestFixture::generators[1], true, true);
1191
1192 check_circuit_and_gate_count(builder, 34);
1193}
1194
1195TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractSucceed)
1196{
1198 auto builder = Builder();
1199
1200 auto lhs = TestFixture::generators[0];
1201 auto rhs = TestFixture::generators[1];
1202
1203 // case 1. valid unconditional add
1204 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1205 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1206 cycle_group_ct c = a.checked_unconditional_subtract(b);
1207 AffineElement expected(Element(lhs) - Element(rhs));
1208 AffineElement result = c.get_value();
1209 EXPECT_EQ(result, expected);
1210
1211 check_circuit_and_gate_count(builder, 16);
1212}
1213
1214TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractFail)
1215{
1217 auto builder = Builder();
1218
1219 auto lhs = TestFixture::generators[0];
1220 auto rhs = -TestFixture::generators[0]; // ruh roh
1221
1222 // case 2. invalid unconditional add
1223 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1224 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1225 a.checked_unconditional_subtract(b);
1226
1227 EXPECT_TRUE(builder.failed());
1228 // No gate count check for failing test
1229 EXPECT_FALSE(CircuitChecker::check(builder));
1230}
1231
1233{
1235 using bool_ct = stdlib::bool_t<Builder>;
1237 auto builder = Builder();
1238
1239 auto lhs = TestFixture::generators[0];
1240 auto rhs = -TestFixture::generators[1];
1241 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
1242
1243 cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&builder, affine_infinity);
1244
1245 // case 1. no edge-cases triggered
1246 {
1247 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1248 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1249 // Here and in the following cases we set 2 different tags to a and b
1250 a.set_origin_tag(submitted_value_origin_tag);
1251 b.set_origin_tag(challenge_origin_tag);
1252
1253 cycle_group_ct c = a - b;
1254 AffineElement expected(Element(lhs) - Element(rhs));
1255 AffineElement result = c.get_value();
1256 EXPECT_EQ(result, expected);
1257 // We expect the tag of the result to be the union of a and b tags
1258 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1259 }
1260
1261 // case 2. lhs is point at infinity
1262 {
1263 cycle_group_ct a = point_at_infinity;
1264 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1265 a.set_origin_tag(submitted_value_origin_tag);
1266 b.set_origin_tag(challenge_origin_tag);
1267
1268 cycle_group_ct c = a - b;
1269 AffineElement result = c.get_value();
1270 EXPECT_EQ(result, -rhs);
1271 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1272 }
1273
1274 // case 3. rhs is point at infinity
1275 {
1276 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1277 cycle_group_ct b = point_at_infinity;
1278 a.set_origin_tag(submitted_value_origin_tag);
1279 b.set_origin_tag(challenge_origin_tag);
1280
1281 cycle_group_ct c = a - b;
1282 AffineElement result = c.get_value();
1283 EXPECT_EQ(result, lhs);
1284 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1285 }
1286
1287 // case 4. both points are at infinity
1288 {
1289 cycle_group_ct a = point_at_infinity;
1290 cycle_group_ct b = point_at_infinity;
1291 a.set_origin_tag(submitted_value_origin_tag);
1292 b.set_origin_tag(challenge_origin_tag);
1293
1294 cycle_group_ct c = a - b;
1295 EXPECT_TRUE(c.is_point_at_infinity().get_value());
1296 EXPECT_TRUE(c.get_value().is_point_at_infinity());
1297 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1298 }
1299
1300 // case 5. lhs = -rhs
1301 {
1302 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1303 cycle_group_ct b = cycle_group_ct::from_witness(&builder, -lhs);
1304 a.set_origin_tag(submitted_value_origin_tag);
1305 b.set_origin_tag(challenge_origin_tag);
1306
1307 cycle_group_ct c = a - b;
1308 AffineElement expected((Element(lhs)).dbl());
1309 AffineElement result = c.get_value();
1310 EXPECT_EQ(result, expected);
1311 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1312 }
1313
1314 // case 6. lhs = rhs
1315 {
1316 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1317 cycle_group_ct b = cycle_group_ct::from_witness(&builder, lhs);
1318 a.set_origin_tag(submitted_value_origin_tag);
1319 b.set_origin_tag(challenge_origin_tag);
1320
1321 cycle_group_ct c = a - b;
1322 EXPECT_TRUE(c.is_point_at_infinity().get_value());
1323 EXPECT_TRUE(c.get_value().is_point_at_infinity());
1324 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1325 }
1326
1327 check_circuit_and_gate_count(builder, 261);
1328}
1329
1330TYPED_TEST(CycleGroupTest, TestSubtractConstantPoints)
1331{
1333
1334 // Test subtracting constant points - this takes a completely different path than witness points
1335 // The existing TestSubtract only tests witness points
1336 {
1337 auto builder = Builder();
1338 auto lhs = TestFixture::generators[5];
1339 auto rhs = TestFixture::generators[6];
1340
1341 cycle_group_ct a(lhs);
1342 cycle_group_ct b(rhs);
1343
1344 cycle_group_ct result = a - b;
1345
1346 AffineElement expected(Element(lhs) - Element(rhs));
1347 EXPECT_EQ(result.get_value(), expected);
1348 EXPECT_TRUE(result.is_constant());
1349
1350 // No gates needed for constant arithmetic
1351 check_circuit_and_gate_count(builder, 0);
1352 }
1353
1354 // Test constant point - constant infinity (early return optimization)
1355 {
1356 auto builder = Builder();
1357 auto lhs = TestFixture::generators[7];
1358
1359 cycle_group_ct a(lhs);
1360 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1361
1362 cycle_group_ct result = a - b;
1363
1364 EXPECT_EQ(result.get_value(), lhs);
1365 EXPECT_TRUE(result.is_constant());
1366
1367 // Uses early return for constant infinity
1368 check_circuit_and_gate_count(builder, 0);
1369 }
1370
1371 // Test constant infinity - constant point (early return optimization)
1372 {
1373 auto builder = Builder();
1374 auto rhs = TestFixture::generators[7];
1375
1376 cycle_group_ct a = cycle_group_ct::constant_infinity(&builder);
1377 cycle_group_ct b(rhs);
1378
1379 cycle_group_ct result = a - b;
1380
1381 EXPECT_EQ(result.get_value(), -rhs);
1382 EXPECT_TRUE(result.is_constant());
1383
1384 // Uses early return for constant infinity
1385 check_circuit_and_gate_count(builder, 0);
1386 }
1387}
1388
1395template <typename T1, typename T2> auto assign_and_merge_tags(T1& points, T2& scalars)
1396{
1397 OriginTag merged_tag;
1398 for (size_t i = 0; i < points.size(); i++) {
1399 const auto point_tag = OriginTag(/*parent_index=*/0, /*round_index=*/i, /*is_submitted=*/true);
1400 const auto scalar_tag = OriginTag(/*parent_index=*/0, /*round_index=*/i, /*is_submitted=*/false);
1401
1402 merged_tag = OriginTag(merged_tag, OriginTag(point_tag, scalar_tag));
1403 points[i].set_origin_tag(point_tag);
1404 scalars[i].set_origin_tag(scalar_tag);
1405 }
1406 return merged_tag;
1407}
1408
1409TYPED_TEST(CycleGroupTest, TestBatchMulGeneralMSM)
1410{
1412 auto builder = Builder();
1413
1414 const size_t num_muls = 1;
1415 // case 1, general MSM with inputs that are combinations of constant and witnesses
1418 Element expected = Group::point_at_infinity;
1419
1420 for (size_t i = 0; i < num_muls; ++i) {
1421 auto element = TestFixture::generators[i];
1422 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1423
1424 // 1: add entry where point, scalar are witnesses
1425 expected += (element * scalar);
1426 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1427 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1428
1429 // 2: add entry where point is constant, scalar is witness
1430 expected += (element * scalar);
1431 points.emplace_back(cycle_group_ct(element));
1432 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1433
1434 // 3: add entry where point is witness, scalar is constant
1435 expected += (element * scalar);
1436 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1437 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1438
1439 // 4: add entry where point is constant, scalar is constant
1440 expected += (element * scalar);
1441 points.emplace_back(cycle_group_ct(element));
1442 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1443 }
1444
1445 // Here and in the following cases assign different tags to points and scalars and get the union of them back
1446 const auto expected_tag = assign_and_merge_tags(points, scalars);
1447
1448 auto result = cycle_group_ct::batch_mul(points, scalars);
1449 EXPECT_EQ(result.get_value(), AffineElement(expected));
1450 // The tag should the union of all tags
1451 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1452
1453 // Gate count difference due to additional constants added by default in Mega builder
1455 check_circuit_and_gate_count(builder, 4393); // Mega
1456 } else {
1457 check_circuit_and_gate_count(builder, 4396); // Ultra
1458 }
1459}
1460
1461TYPED_TEST(CycleGroupTest, TestBatchMulProducesInfinity)
1462{
1464 auto builder = Builder();
1465
1466 // case 2, MSM that produces point at infinity
1469
1470 auto element = TestFixture::generators[0];
1471 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1472 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1473 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1474
1475 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1476 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, -scalar));
1477
1478 const auto expected_tag = assign_and_merge_tags(points, scalars);
1479
1480 auto result = cycle_group_ct::batch_mul(points, scalars);
1481 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1482
1483 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1484
1485 // Gate count difference due to additional constants added by default in Mega builder
1487 check_circuit_and_gate_count(builder, 4019); // Mega
1488 } else {
1489 check_circuit_and_gate_count(builder, 4022); // Ultra
1490 }
1491}
1492
1493TYPED_TEST(CycleGroupTest, TestBatchMulMultiplyByZero)
1494{
1496 auto builder = Builder();
1497
1498 // case 3. Multiply by zero
1501
1502 auto element = TestFixture::generators[0];
1503 typename Group::Fr scalar = 0;
1504 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1505 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1506
1507 const auto expected_tag = assign_and_merge_tags(points, scalars);
1508 auto result = cycle_group_ct::batch_mul(points, scalars);
1509 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1510 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1511
1512 // Gate count difference due to additional constants added by default in Mega builder
1514 check_circuit_and_gate_count(builder, 3529); // Mega
1515 } else {
1516 check_circuit_and_gate_count(builder, 3532); // Ultra
1517 }
1518}
1519
1520TYPED_TEST(CycleGroupTest, TestBatchMulInputsAreInfinity)
1521{
1523 auto builder = Builder();
1524
1525 // case 4. Inputs are points at infinity
1528
1529 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1530 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
1531
1532 // is_infinity = witness
1533 {
1534 cycle_group_ct point = cycle_group_ct::from_witness(&builder, affine_infinity);
1535 points.emplace_back(point);
1536 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1537 }
1538 // is_infinity = constant
1539 {
1540 cycle_group_ct point = cycle_group_ct(affine_infinity);
1541 points.emplace_back(point);
1542 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1543 }
1544
1545 const auto expected_tag = assign_and_merge_tags(points, scalars);
1546 auto result = cycle_group_ct::batch_mul(points, scalars);
1547 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1548 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1549
1550 // Gate count difference due to additional constants added by default in Mega builder
1552 check_circuit_and_gate_count(builder, 3542); // Mega
1553 } else {
1554 check_circuit_and_gate_count(builder, 3545); // Ultra
1555 }
1556}
1557
1558TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseInLookupTable)
1559{
1561 auto builder = Builder();
1562
1563 const size_t num_muls = 1;
1564 // case 5, fixed-base MSM with inputs that are combinations of constant and witnesses (group elements are in
1565 // lookup table)
1568 std::vector<typename Group::Fq> scalars_native;
1569 Element expected = Group::point_at_infinity;
1570 for (size_t i = 0; i < num_muls; ++i) {
1572 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1573
1574 // 1: add entry where point is constant, scalar is witness
1575 expected += (element * scalar);
1576 points.emplace_back(element);
1577 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1578 scalars_native.emplace_back(uint256_t(scalar));
1579
1580 // 2: add entry where point is constant, scalar is constant
1582 expected += (element * scalar);
1583 points.emplace_back(element);
1584 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1585 scalars_native.emplace_back(uint256_t(scalar));
1586 }
1587 const auto expected_tag = assign_and_merge_tags(points, scalars);
1588 auto result = cycle_group_ct::batch_mul(points, scalars);
1589 EXPECT_EQ(result.get_value(), AffineElement(expected));
1590 EXPECT_EQ(result.get_value(), crypto::pedersen_commitment::commit_native(scalars_native));
1591 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1592
1593 check_circuit_and_gate_count(builder, 2822);
1594}
1595
1596TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseSomeInLookupTable)
1597{
1599 auto builder = Builder();
1600
1601 const size_t num_muls = 1;
1602 // case 6, fixed-base MSM with inputs that are combinations of constant and witnesses (some group elements are
1603 // in lookup table)
1606 std::vector<typename Group::Fr> scalars_native;
1607 Element expected = Group::point_at_infinity;
1608 for (size_t i = 0; i < num_muls; ++i) {
1610 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1611
1612 // 1: add entry where point is constant, scalar is witness
1613 expected += (element * scalar);
1614 points.emplace_back(element);
1615 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1616 scalars_native.emplace_back(scalar);
1617
1618 // 2: add entry where point is constant, scalar is constant
1620 expected += (element * scalar);
1621 points.emplace_back(element);
1622 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1623 scalars_native.emplace_back(scalar);
1624
1625 // 3: add entry where point is constant, scalar is witness
1626 scalar = Group::Fr::random_element(&engine);
1627 element = Group::one * Group::Fr::random_element(&engine);
1628 expected += (element * scalar);
1629 points.emplace_back(element);
1630 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1631 scalars_native.emplace_back(scalar);
1632 }
1633 const auto expected_tag = assign_and_merge_tags(points, scalars);
1634 auto result = cycle_group_ct::batch_mul(points, scalars);
1635 EXPECT_EQ(result.get_value(), AffineElement(expected));
1636 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1637
1638 // Gate count difference due to additional constants added by default in Mega builder
1640 check_circuit_and_gate_count(builder, 3395); // Mega
1641 } else {
1642 check_circuit_and_gate_count(builder, 3398); // Ultra
1643 }
1644}
1645
1646TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseZeroScalars)
1647{
1649 auto builder = Builder();
1650
1651 const size_t num_muls = 1;
1652 // case 7, Fixed-base MSM where input scalars are 0
1655
1656 for (size_t i = 0; i < num_muls; ++i) {
1658 typename Group::Fr scalar = 0;
1659
1660 // 1: add entry where point is constant, scalar is witness
1661 points.emplace_back((element));
1662 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1663
1664 // 2: add entry where point is constant, scalar is constant
1665 points.emplace_back((element));
1666 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1667 }
1668 const auto expected_tag = assign_and_merge_tags(points, scalars);
1669 auto result = cycle_group_ct::batch_mul(points, scalars);
1670 EXPECT_EQ(result.is_point_at_infinity().get_value(), true);
1671 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1672
1673 check_circuit_and_gate_count(builder, 2837);
1674}
1675
1677{
1679 auto builder = Builder();
1680
1681 const size_t num_muls = 5;
1682
1683 // case 1, general MSM with inputs that are combinations of constant and witnesses
1684 {
1685 cycle_group_ct point;
1686 typename cycle_group_ct::cycle_scalar scalar;
1687 cycle_group_ct result;
1688 for (size_t i = 0; i < num_muls; ++i) {
1689 auto element = TestFixture::generators[i];
1690 typename Group::Fr native_scalar = Group::Fr::random_element(&engine);
1691 auto expected_result = element * native_scalar;
1692
1693 // 1: perform mul where point, scalar are witnesses
1694 point = (cycle_group_ct::from_witness(&builder, element));
1695 scalar = (cycle_group_ct::cycle_scalar::from_witness(&builder, native_scalar));
1696 point.set_origin_tag(submitted_value_origin_tag);
1697 scalar.set_origin_tag(challenge_origin_tag);
1698 result = point * scalar;
1699 EXPECT_EQ((result).get_value(), (expected_result));
1700
1701 // 2: perform mul where point is constant, scalar is witness
1702 point = (cycle_group_ct(element));
1703 scalar = (cycle_group_ct::cycle_scalar::from_witness(&builder, native_scalar));
1704 result = point * scalar;
1705 EXPECT_EQ((result).get_value(), (expected_result));
1706
1707 // 3: perform mul where point is witness, scalar is constant
1708 point = (cycle_group_ct::from_witness(&builder, element));
1709 scalar = (typename cycle_group_ct::cycle_scalar(native_scalar));
1710 result = point * scalar;
1711 EXPECT_EQ((result).get_value(), (expected_result));
1712
1713 // 4: perform mul where point is constant, scalar is constant
1714 point = (cycle_group_ct(element));
1715 scalar = (typename cycle_group_ct::cycle_scalar(native_scalar));
1716 result = point * scalar;
1717 EXPECT_EQ((result).get_value(), (expected_result));
1718 }
1719 }
1720
1721 // Gate count difference due to additional constants added by default in Mega builder
1723 check_circuit_and_gate_count(builder, 12933); // Mega
1724 } else {
1725 check_circuit_and_gate_count(builder, 12936); // Ultra
1726 }
1727}
1728
1730{
1733 cycle_group_ct one = cycle_group_ct::one(&builder);
1734 auto expected_one_native = Group::one;
1735 auto one_native = one.get_value();
1736 EXPECT_EQ(one_native.x, expected_one_native.x);
1737 EXPECT_EQ(one_native.y, expected_one_native.y);
1738}
1739
1745TYPED_TEST(CycleGroupTest, TestConversionFromBigfield)
1746{
1748 using FF = typename Curve::ScalarField;
1750
1751 const auto run_test = [](bool construct_witnesses) {
1753 auto elt = FF::random_element(&engine);
1754 FF_ct big_elt;
1755 if (construct_witnesses) {
1756 big_elt = FF_ct::from_witness(&builder, elt);
1757 } else {
1758 big_elt = FF_ct(elt);
1759 }
1760 big_elt.set_origin_tag(submitted_value_origin_tag);
1761 cycle_scalar_ct scalar_from_big_elt(big_elt);
1762 EXPECT_EQ(elt, scalar_from_big_elt.get_value());
1763 EXPECT_EQ(scalar_from_big_elt.get_origin_tag(), big_elt.get_origin_tag());
1764 if (construct_witnesses) {
1765 EXPECT_FALSE(big_elt.is_constant());
1766 EXPECT_FALSE(scalar_from_big_elt.is_constant());
1767 check_circuit_and_gate_count(builder, 3523);
1768 }
1769 };
1770 run_test(/*construct_witnesses=*/true);
1771 run_test(/*construct_witnesses=*/false);
1772}
1773
1774TYPED_TEST(CycleGroupTest, TestBatchMulIsConsistent)
1775{
1777 using FF = typename Curve::ScalarField;
1779
1780 const auto run_test = [](bool construct_witnesses) {
1782 auto scalar1 = FF::random_element(&engine);
1783 auto scalar2 = FF::random_element(&engine);
1784
1785 FF_ct big_scalar1;
1786 FF_ct big_scalar2;
1787 if (construct_witnesses) {
1788 big_scalar1 = FF_ct::from_witness(&builder, scalar1);
1789 big_scalar2 = FF_ct::from_witness(&builder, scalar2);
1790 } else {
1791 big_scalar1 = FF_ct(scalar1);
1792 big_scalar2 = FF_ct(scalar2);
1793 }
1794 cycle_group_ct result1 = cycle_group_ct::batch_mul({ TestFixture::generators[0], TestFixture::generators[1] },
1795 { big_scalar1, big_scalar2 });
1796
1797 cycle_group_ct result2 =
1798 cycle_group_ct::batch_mul({ TestFixture::generators[0], TestFixture::generators[1] },
1799 { cycle_scalar_ct(big_scalar1), cycle_scalar_ct(big_scalar2) });
1800
1801 AffineElement result1_native = result1.get_value();
1802 AffineElement result2_native = result2.get_value();
1803 EXPECT_EQ(result1_native.x, result2_native.x);
1804 EXPECT_EQ(result1_native.y, result2_native.y);
1805 if (construct_witnesses) {
1806 EXPECT_FALSE(result1.is_constant());
1807 EXPECT_FALSE(result2.is_constant());
1808 // Gate count difference due to additional constants added by default in Mega builder
1810 check_circuit_and_gate_count(builder, 5285); // Mega
1811 } else {
1812 check_circuit_and_gate_count(builder, 5288); // Ultra
1813 }
1814 }
1815 };
1816 run_test(/*construct_witnesses=*/true);
1817 run_test(/*construct_witnesses=*/false);
1818}
1819
1825TYPED_TEST(CycleGroupTest, TestFixedBaseBatchMul)
1826{
1829
1830 // Get the fixed base points that have lookup tables
1833
1834 // Test with two scalars and both generators
1837
1838 auto scalar1_val = Group::Fr::random_element(&engine);
1839 auto scalar2_val = Group::Fr::random_element(&engine);
1840
1841 scalars.push_back(cycle_scalar_ct::from_witness(&builder, scalar1_val));
1842 scalars.push_back(cycle_scalar_ct::from_witness(&builder, scalar2_val));
1843 points.push_back(cycle_group_ct(lhs_generator)); // constant point
1844 points.push_back(cycle_group_ct(rhs_generator)); // constant point
1845
1846 auto result = cycle_group_ct::batch_mul(points, scalars);
1847
1848 // Compute expected result natively
1849 AffineElement expected = lhs_generator * scalar1_val + rhs_generator * scalar2_val;
1850
1851 EXPECT_EQ(result.get_value(), expected);
1852
1853 check_circuit_and_gate_count(builder, 2908);
1854}
1855#pragma GCC diagnostic pop
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:32
static void SetUpTestSuite()
static std::array< AffineElement, num_generators > generators
typename Curve::Element Element
typename stdlib::cycle_group< Builder >::Curve Curve
typename Curve::Group Group
static constexpr size_t num_generators
typename Curve::AffineElement AffineElement
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
static AffineElement commit_native(const std::vector< Fq > &inputs, GeneratorContext context={})
Given a vector of fields, generate a pedersen commitment using the indexed generators.
Definition pedersen.cpp:24
typename Group::element Element
Definition bn254.hpp:21
typename bb::g1 Group
Definition bn254.hpp:20
typename Group::affine_element AffineElement
Definition bn254.hpp:22
static constexpr affine_element rhs_generator_point()
static constexpr affine_element lhs_generator_point()
Implements boolean logic in-circuit.
Definition bool.hpp:59
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:454
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
bool expected_result
#define STDLIB_TYPE_ALIASES
auto assign_and_merge_tags(T1 &points, T2 &scalars)
Assign different tags to all points and scalars and return the union of that tag.
ECCVMCircuitBuilder Builder
numeric::RNG & engine
bn254::witness_ct witness_ct
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:190
void check_circuit_and_gate_count(Builder &builder, uint32_t expected_gates_without_base)
Utility function for gate count checking and circuit verification.
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(ShpleminiTest, TestSettings)
TYPED_TEST(ShpleminiTest, CorrectnessOfMultivariateClaimBatching)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
#define STANDARD_TESTING_TAGS
Curve::Element Element
testing::Types< bb::MegaCircuitBuilder, bb::UltraCircuitBuilder > CircuitTypes
static field random_element(numeric::RNG *engine=nullptr) noexcept