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.
15#include <gtest/gtest.h>
16
17#define STDLIB_TYPE_ALIASES \
18 using Builder = TypeParam; \
19 using cycle_group_ct = stdlib::cycle_group<Builder>; \
20 using Curve = typename stdlib::cycle_group<Builder>::Curve; \
21 using Element = typename Curve::Element; \
22 using AffineElement = typename Curve::AffineElement; \
23 using Group = typename Curve::Group; \
24 using bool_ct = stdlib::bool_t<Builder>; \
25 using witness_ct = stdlib::witness_t<Builder>; \
26 using cycle_scalar_ct = cycle_group_ct::cycle_scalar;
27
28using namespace bb;
29
30namespace {
32}
33#pragma GCC diagnostic push
34#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
35
36template <class Builder> class CycleGroupTest : public ::testing::Test {
37 public:
39 using Group = typename Curve::Group;
40
41 using Element = typename Curve::Element;
43
44 static constexpr size_t num_generators = 110;
45 static inline std::array<AffineElement, num_generators> generators{};
46
47 static void SetUpTestSuite()
48 {
49
50 for (size_t i = 0; i < num_generators; ++i) {
51 generators[i] = Group::one * Curve::ScalarField::random_element(&engine);
52 }
53 };
54};
55
56using CircuitTypes = ::testing::Types<bb::UltraCircuitBuilder, bb::MegaCircuitBuilder>;
58
59// Import the check_circuit_and_gate_count function from test_utils
61
67TYPED_TEST(CycleGroupTest, TestBasicTagLogic)
68{
71
72 // Create field elements with specific tags before constructing the cycle_group
73 auto lhs = TestFixture::generators[0];
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
81 // Construct cycle_group from pre-tagged field elements
82 // The 2-arg constructor auto-detects infinity from coordinates, so _is_infinity
83 // will have a tag derived from x and y (since it's computed from x² + 5y²)
84 cycle_group_ct a(x, y, /*assert_on_curve=*/true);
85
86 // The tag of the cycle_group should be the union of x and y tags
87 // (is_infinity is derived from x and y, so its tag is already included)
88 EXPECT_EQ(a.get_origin_tag(), first_two_merged_tag);
89
90#ifndef NDEBUG
91 // Test that instant_death_tag on x coordinate propagates correctly
92 auto x_death = stdlib::field_t<Builder>::from_witness(&builder, TestFixture::generators[1].x);
93 auto y_normal = stdlib::field_t<Builder>::from_witness(&builder, TestFixture::generators[1].y);
94
95 // Set constant tags on the other elements so they can be merged with instant_death_tag
96 y_normal.set_origin_tag(constant_tag);
97
98 // Use assert_on_curve=false to avoid triggering instant_death during validate_on_curve()
99 cycle_group_ct b(x_death, y_normal, /*assert_on_curve=*/false);
100
101 // Poison the x coordinate after construction so the throw happens inside operator+
102 b.x().set_origin_tag(instant_death_tag);
103
104 // Even requesting the tag of the whole structure can cause instant death
105 EXPECT_THROW(b.get_origin_tag(), std::runtime_error);
106#endif
107}
108
113TYPED_TEST(CycleGroupTest, TestInfConstantWintnessRegression)
114{
117
118 auto lhs = TestFixture::generators[0] * 0;
119 cycle_group_ct a = cycle_group_ct::from_constant_witness(&builder, lhs);
120 (void)a;
121 EXPECT_FALSE(builder.failed());
122 check_circuit_and_gate_count(builder, 0);
123}
124
129TYPED_TEST(CycleGroupTest, TestWitnessSumRegression)
130{
133
134 auto lhs = TestFixture::generators[0];
135 auto rhs = TestFixture::generators[1];
136 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
137 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
138 cycle_group_ct c = a + b;
139 EXPECT_FALSE(c.is_constant());
140 c = a - b;
141 EXPECT_FALSE(c.is_constant());
142}
143
148TYPED_TEST(CycleGroupTest, TestOperatorNegRegression)
149{
152
153 auto lhs = TestFixture::generators[0];
154 auto rhs = TestFixture::generators[1];
155 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
156 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
157 b = -b;
158 cycle_group_ct c = a.unconditional_add(b);
159 (void)c;
160 EXPECT_FALSE(builder.failed());
161 check_circuit_and_gate_count(builder, 23);
162}
163
168TYPED_TEST(CycleGroupTest, TestConstantWitnessMixupRegression)
169{
172
173 auto c1 = cycle_group_ct(AffineElement::one());
174 auto cw8 = cycle_group_ct::from_constant_witness(&builder, AffineElement::one() * 0);
175 auto w11 = cycle_group_ct::from_witness(&builder, TestFixture::generators[0]);
176
177 auto w9 = cw8 + c1; // mixup happens here due to _is_infinity being a constant
178 auto w26 = w9 + w11; // and here the circuit checker crashes
179
180 auto w10 = cw8 - c1;
181 auto w27 = w10 - w11; // and here
182 (void)w26;
183 (void)w27;
184 check_circuit_and_gate_count(builder, 44);
185}
186
191TYPED_TEST(CycleGroupTest, TestConditionalAssignRegression)
192{
195
196 auto c0 = cycle_group_ct(AffineElement::one() * 0);
197 auto c1 = cycle_group_ct::conditional_assign(bool_ct(witness_ct(&builder, false)), c0, c0);
198 auto w3 = c1.dbl();
199 (void)w3;
200 check_circuit_and_gate_count(builder, 1);
201}
202
207TYPED_TEST(CycleGroupTest, TestConditionalAssignSuperMixupRegression)
208{
211
212 auto c0 = cycle_group_ct(TestFixture::generators[0]);
213 auto c1 = cycle_group_ct(-TestFixture::generators[0]);
214 auto w2 = cycle_group_ct::conditional_assign(bool_ct(witness_ct(&builder, true)), c0, c1);
215 EXPECT_FALSE(w2.x().is_constant());
216 EXPECT_FALSE(w2.y().is_constant());
217 EXPECT_TRUE(w2.is_point_at_infinity().is_constant());
218 auto w3 = w2.dbl();
219 (void)w3;
220 check_circuit_and_gate_count(builder, 5);
221}
222
227TYPED_TEST(CycleGroupTest, TestValidateOnCurveSucceed)
228{
231
232 auto point_val = TestFixture::generators[0];
233 auto x = stdlib::field_t<Builder>::from_witness(&builder, point_val.x);
234 auto y = stdlib::field_t<Builder>::from_witness(&builder, point_val.y);
235
236 // The 2-arg constructor auto-detects infinity from (x == 0 && y == 0).
237 // For a generator point, this will correctly detect is_infinity = false.
238 cycle_group_ct point(x, y, /*assert_on_curve=*/true);
239 EXPECT_FALSE(builder.failed());
240 // Gate count includes infinity auto-detection + validate_on_curve
241 check_circuit_and_gate_count(builder, 10);
242}
243
247TYPED_TEST(CycleGroupTest, TestValidateOnCurveInfinitySucceed)
248{
251
252 cycle_group_ct a = cycle_group_ct::from_witness(&builder, AffineElement::infinity());
253 a.validate_on_curve();
254 EXPECT_FALSE(builder.failed());
255 check_circuit_and_gate_count(builder, 15);
256}
257
263TYPED_TEST(CycleGroupTest, TestValidateOnCurveFail)
264{
265 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
268
271
272 // Point (1, 1) is not on the curve - validate_on_curve should fail
273 // The 2-arg constructor auto-detects infinity as false for non-zero coordinates
274 cycle_group_ct a(x, y, /*assert_on_curve=*/true);
275 EXPECT_TRUE(builder.failed());
276 EXPECT_FALSE(CircuitChecker::check(builder));
277}
278
284TYPED_TEST(CycleGroupTest, TestValidateOnCurveFail2)
285{
286 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
289
292
293 // Point (1, 1) is not on the curve - validate_on_curve should fail
294 // The 2-arg constructor auto-detects infinity from coordinates (1² + 5*1² ≠ 0, so not infinity)
295 cycle_group_ct a(x, y, /*assert_on_curve=*/false);
296 a.validate_on_curve();
297 EXPECT_TRUE(builder.failed());
298 EXPECT_FALSE(CircuitChecker::check(builder));
299}
300
301TYPED_TEST(CycleGroupTest, TestStandardForm)
302{
304 auto builder = Builder();
305
306 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
307 cycle_group_ct input_a = cycle_group_ct::from_witness(&builder, Element::random_element());
308 cycle_group_ct input_b = cycle_group_ct::from_witness(&builder, affine_infinity);
309 cycle_group_ct input_c = cycle_group_ct(Element::random_element());
310 cycle_group_ct input_d = cycle_group_ct(affine_infinity);
311
312 // Assign different tags to all inputs
313 input_a.set_origin_tag(submitted_value_origin_tag);
314 input_b.set_origin_tag(challenge_origin_tag);
315 input_c.set_origin_tag(next_challenge_tag);
316 input_d.set_origin_tag(first_two_merged_tag);
317
318 input_a.standardize();
319 auto standard_a = input_a;
320 input_b.standardize();
321 auto standard_b = input_b;
322 input_c.standardize();
323 auto standard_c = input_c;
324 input_d.standardize();
325 auto standard_d = input_d;
326
327 EXPECT_EQ(standard_a.is_point_at_infinity().get_value(), false);
328 EXPECT_EQ(standard_b.is_point_at_infinity().get_value(), true);
329 EXPECT_EQ(standard_c.is_point_at_infinity().get_value(), false);
330 EXPECT_EQ(standard_d.is_point_at_infinity().get_value(), true);
331
332 // Ensure that the tags in the standard form remain the same
333 EXPECT_EQ(standard_a.get_origin_tag(), submitted_value_origin_tag);
334 EXPECT_EQ(standard_b.get_origin_tag(), challenge_origin_tag);
335 EXPECT_EQ(standard_c.get_origin_tag(), next_challenge_tag);
336 EXPECT_EQ(standard_d.get_origin_tag(), first_two_merged_tag);
337
338 auto input_a_x = input_a.x().get_value();
339 auto input_a_y = input_a.y().get_value();
340 auto input_c_x = input_c.x().get_value();
341 auto input_c_y = input_c.y().get_value();
342
343 auto standard_a_x = standard_a.x().get_value();
344 auto standard_a_y = standard_a.y().get_value();
345
346 auto standard_b_x = standard_b.x().get_value();
347 auto standard_b_y = standard_b.y().get_value();
348
349 auto standard_c_x = standard_c.x().get_value();
350 auto standard_c_y = standard_c.y().get_value();
351
352 auto standard_d_x = standard_d.x().get_value();
353 auto standard_d_y = standard_d.y().get_value();
354
355 EXPECT_EQ(input_a_x, standard_a_x);
356 EXPECT_EQ(input_a_y, standard_a_y);
357 EXPECT_EQ(standard_b_x, 0);
358 EXPECT_EQ(standard_b_y, 0);
359 EXPECT_EQ(input_c_x, standard_c_x);
360 EXPECT_EQ(input_c_y, standard_c_y);
361 EXPECT_EQ(standard_d_x, 0);
362 EXPECT_EQ(standard_d_y, 0);
363
364 check_circuit_and_gate_count(builder, 24);
365}
367{
369 auto builder = Builder();
370
371 auto lhs = TestFixture::generators[0];
372 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
373 cycle_group_ct b = cycle_group_ct(lhs);
374 // Assign two different tags
375 a.set_origin_tag(submitted_value_origin_tag);
376 b.set_origin_tag(challenge_origin_tag);
377 cycle_group_ct c;
378 cycle_group_ct d;
379 for (size_t i = 0; i < 3; ++i) {
380 c = a.dbl();
381 }
382 d = b.dbl();
383 AffineElement expected(Element(lhs).dbl());
384 AffineElement result = c.get_value();
385 EXPECT_EQ(result, expected);
386 EXPECT_EQ(d.get_value(), expected);
387
388 check_circuit_and_gate_count(builder, 19);
389
390 // Ensure the tags stay the same after doubling
391 EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag);
392 EXPECT_EQ(d.get_origin_tag(), challenge_origin_tag);
393}
394
395TYPED_TEST(CycleGroupTest, TestDblNonConstantPoints)
396{
398
399 // Test case 1: Witness point WITH hint
400 {
401 auto builder = Builder();
402 auto lhs = TestFixture::generators[0];
403 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
404
405 Element doubled_element = Element(lhs).dbl();
406 AffineElement hint(doubled_element);
407
408 cycle_group_ct result = a.dbl(hint);
409
410 EXPECT_EQ(result.get_value(), hint);
411 EXPECT_FALSE(result.is_point_at_infinity().get_value());
412
413 check_circuit_and_gate_count(builder, 13);
414 }
415
416 // Test case 2: Witness point WITHOUT hint
417 {
418 auto builder = Builder();
419 auto lhs = TestFixture::generators[1];
420 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
421
422 cycle_group_ct result = a.dbl();
423
424 Element expected_element = Element(lhs).dbl();
425 AffineElement expected(expected_element);
426 EXPECT_EQ(result.get_value(), expected);
427 EXPECT_FALSE(result.is_point_at_infinity().get_value());
428
429 // Note: same gate count as with hint - hint is a witness generation optimization only
430 check_circuit_and_gate_count(builder, 13);
431 }
432
433 // Test case 3: Witness infinity point WITHOUT hint
434 {
435 auto builder = Builder();
436 AffineElement infinity_element;
437 infinity_element.self_set_infinity();
438
439 cycle_group_ct infinity = cycle_group_ct::from_witness(&builder, infinity_element);
440
441 cycle_group_ct result = infinity.dbl();
442
443 EXPECT_TRUE(result.is_point_at_infinity().get_value());
444 // Note: from_witness sets x,y to witness(0,0) for infinity points
445 // After doubling, y becomes -1 (0x3064...) due to the modified_y logic
446 EXPECT_EQ(result.x().get_value(), 0);
447
448 // Same gate count as regular witness points
449 check_circuit_and_gate_count(builder, 13);
450 }
451}
452
453TYPED_TEST(CycleGroupTest, TestDblConstantPoints)
454{
456
457 // Test case 1: Constant point WITH hint
458 {
459 auto builder = Builder();
460 auto lhs = TestFixture::generators[0];
461 cycle_group_ct a(lhs);
462
463 Element doubled_element = Element(lhs).dbl();
464 AffineElement hint(doubled_element);
465
466 cycle_group_ct result = a.dbl(hint);
467
468 EXPECT_EQ(result.get_value(), hint);
469 EXPECT_TRUE(result.is_constant());
470 EXPECT_FALSE(result.is_point_at_infinity().get_value());
471
472 check_circuit_and_gate_count(builder, 0);
473 }
474
475 // Test case 2: Constant point WITHOUT hint
476 {
477 auto builder = Builder();
478 auto lhs = TestFixture::generators[1];
479 cycle_group_ct a(lhs);
480
481 cycle_group_ct result = a.dbl();
482
483 Element expected_element = Element(lhs).dbl();
484 AffineElement expected(expected_element);
485 EXPECT_EQ(result.get_value(), expected);
486 EXPECT_TRUE(result.is_constant());
487 EXPECT_FALSE(result.is_point_at_infinity().get_value());
488
489 check_circuit_and_gate_count(builder, 0);
490 }
491
492 // Test case 3: Constant infinity point WITHOUT hint
493 {
494 auto builder = Builder();
495 cycle_group_ct infinity = cycle_group_ct::constant_infinity(nullptr);
496
497 cycle_group_ct result = infinity.dbl();
498
499 EXPECT_TRUE(result.is_point_at_infinity().get_value());
500 EXPECT_TRUE(result.is_constant());
501 EXPECT_EQ(result.x().get_value(), 0);
502 EXPECT_EQ(result.y().get_value(), 0);
503
504 check_circuit_and_gate_count(builder, 0);
505 }
506
507 // Test case 4: Constant infinity point WITH hint
508 {
509 auto builder = Builder();
510 cycle_group_ct infinity = cycle_group_ct::constant_infinity(nullptr);
511
512 AffineElement hint;
513 hint.self_set_infinity();
514
515 cycle_group_ct result = infinity.dbl(hint);
516
517 EXPECT_TRUE(result.is_point_at_infinity().get_value());
518 EXPECT_TRUE(result.is_constant());
519 EXPECT_EQ(result.x().get_value(), 0);
520 EXPECT_EQ(result.y().get_value(), 0);
521
522 check_circuit_and_gate_count(builder, 0);
523 }
524}
525
526TYPED_TEST(CycleGroupTest, TestDblMixedConstantWitness)
527{
529 auto builder = Builder();
530
531 // Test doubling where x is constant but y is witness (edge case)
532 auto point = TestFixture::generators[1];
533 auto x = stdlib::field_t<Builder>(&builder, point.x); // constant
534 auto y = stdlib::field_t<Builder>(witness_ct(&builder, point.y)); // witness
535
536 // Mixed constancy is remedied inside the constructor; x will be converted to a fixed witness
537 // The point is known to be on the curve and not at infinity (it's a generator point)
538 cycle_group_ct a(x, y, /*assert_on_curve=*/false);
539
540 EXPECT_FALSE(a.x().is_constant());
541 EXPECT_FALSE(a.y().is_constant());
542
543 a.dbl();
544
545 check_circuit_and_gate_count(builder, 8);
546}
547
548TYPED_TEST(CycleGroupTest, TestUnconditionalAddNonConstantPoints)
549{
551
552 // Test case 1: Two witness points WITHOUT hint
553 {
554 auto builder = Builder();
555 auto lhs = TestFixture::generators[0];
556 auto rhs = TestFixture::generators[1];
557 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
558 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
559
560 cycle_group_ct result = a.unconditional_add(b);
561
562 Element expected_element = Element(lhs) + Element(rhs);
563 AffineElement expected(expected_element);
564 EXPECT_EQ(result.get_value(), expected);
565 EXPECT_FALSE(result.is_point_at_infinity().get_value());
566
567 check_circuit_and_gate_count(builder, 22);
568 }
569
570 // Test case 2: Two witness points WITH hint
571 {
572 auto builder = Builder();
573 auto lhs = TestFixture::generators[2];
574 auto rhs = TestFixture::generators[3];
575 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
576 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
577
578 Element sum_element = Element(lhs) + Element(rhs);
579 AffineElement hint(sum_element);
580
581 cycle_group_ct result = a.unconditional_add(b, hint);
582
583 EXPECT_EQ(result.get_value(), hint);
584 EXPECT_FALSE(result.is_point_at_infinity().get_value());
585
586 check_circuit_and_gate_count(builder, 22);
587 }
588
589 // Test case 3: Mixed witness and constant points
590 {
591 auto builder = Builder();
592 auto lhs = TestFixture::generators[0];
593 auto rhs = TestFixture::generators[1];
594 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
595 cycle_group_ct b(rhs); // constant
596
597 cycle_group_ct result = a.unconditional_add(b);
598
599 Element expected_element = Element(lhs) + Element(rhs);
600 AffineElement expected(expected_element);
601 EXPECT_EQ(result.get_value(), expected);
602 EXPECT_FALSE(result.is_constant());
603 EXPECT_FALSE(result.is_point_at_infinity().get_value());
604
605 check_circuit_and_gate_count(builder, 14);
606 }
607}
608
609TYPED_TEST(CycleGroupTest, TestUnconditionalAddConstantPoints)
610{
612
613 // Test case 1: Two constant points WITHOUT hint
614 {
615 auto builder = Builder();
616 auto lhs = TestFixture::generators[0];
617 auto rhs = TestFixture::generators[1];
618 cycle_group_ct a(lhs);
619 cycle_group_ct b(rhs);
620
621 cycle_group_ct result = a.unconditional_add(b);
622
623 Element expected_element = Element(lhs) + Element(rhs);
624 AffineElement expected(expected_element);
625 EXPECT_EQ(result.get_value(), expected);
626 EXPECT_TRUE(result.is_constant());
627 EXPECT_FALSE(result.is_point_at_infinity().get_value());
628
629 check_circuit_and_gate_count(builder, 0);
630 }
631
632 // Test case 2: Two constant points WITH hint
633 {
634 auto builder = Builder();
635 auto lhs = TestFixture::generators[2];
636 auto rhs = TestFixture::generators[3];
637 cycle_group_ct a(lhs);
638 cycle_group_ct b(rhs);
639
640 Element sum_element = Element(lhs) + Element(rhs);
641 AffineElement hint(sum_element);
642
643 cycle_group_ct result = a.unconditional_add(b, hint);
644
645 EXPECT_EQ(result.get_value(), hint);
646 EXPECT_TRUE(result.is_constant());
647 EXPECT_FALSE(result.is_point_at_infinity().get_value());
648
649 check_circuit_and_gate_count(builder, 0);
650 }
651}
652
653TYPED_TEST(CycleGroupTest, TestUnconditionalSubtractNonConstantPoints)
654{
656
657 // Test case 1: Two witness points WITHOUT hint
658 {
659 auto builder = Builder();
660 auto lhs = TestFixture::generators[0];
661 auto rhs = TestFixture::generators[1];
662 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
663 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
664
665 cycle_group_ct result = a.unconditional_subtract(b);
666
667 Element expected_element = Element(lhs) - Element(rhs);
668 AffineElement expected(expected_element);
669 EXPECT_EQ(result.get_value(), expected);
670 EXPECT_FALSE(result.is_point_at_infinity().get_value());
671
672 check_circuit_and_gate_count(builder, 22);
673 }
674
675 // Test case 2: Two witness points WITH hint
676 {
677 auto builder = Builder();
678 auto lhs = TestFixture::generators[2];
679 auto rhs = TestFixture::generators[3];
680 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
681 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
682
683 Element diff_element = Element(lhs) - Element(rhs);
684 AffineElement hint(diff_element);
685
686 cycle_group_ct result = a.unconditional_subtract(b, hint);
687
688 EXPECT_EQ(result.get_value(), hint);
689 EXPECT_FALSE(result.is_point_at_infinity().get_value());
690
691 // Same gate count as without hint - hint is a witness generation optimization only
692 check_circuit_and_gate_count(builder, 22);
693 }
694
695 // Test case 3: Mixed witness and constant points
696 {
697 auto builder = Builder();
698 auto lhs = TestFixture::generators[0];
699 auto rhs = TestFixture::generators[1];
700 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
701 cycle_group_ct b(rhs); // constant
702
703 cycle_group_ct result = a.unconditional_subtract(b);
704
705 Element expected_element = Element(lhs) - Element(rhs);
706 AffineElement expected(expected_element);
707 EXPECT_EQ(result.get_value(), expected);
708 EXPECT_FALSE(result.is_constant());
709 EXPECT_FALSE(result.is_point_at_infinity().get_value());
710
711 check_circuit_and_gate_count(builder, 14);
712 }
713}
714
715TYPED_TEST(CycleGroupTest, TestUnconditionalSubtractConstantPoints)
716{
718
719 // Test case 1: Two constant points WITHOUT hint
720 {
721 auto builder = Builder();
722 auto lhs = TestFixture::generators[0];
723 auto rhs = TestFixture::generators[1];
724 cycle_group_ct a(lhs);
725 cycle_group_ct b(rhs);
726
727 cycle_group_ct result = a.unconditional_subtract(b);
728
729 Element expected_element = Element(lhs) - Element(rhs);
730 AffineElement expected(expected_element);
731 EXPECT_EQ(result.get_value(), expected);
732 EXPECT_TRUE(result.is_constant());
733 EXPECT_FALSE(result.is_point_at_infinity().get_value());
734
735 check_circuit_and_gate_count(builder, 0);
736 }
737
738 // Test case 2: Two constant points WITH hint
739 {
740 auto builder = Builder();
741 auto lhs = TestFixture::generators[2];
742 auto rhs = TestFixture::generators[3];
743 cycle_group_ct a(lhs);
744 cycle_group_ct b(rhs);
745
746 Element diff_element = Element(lhs) - Element(rhs);
747 AffineElement hint(diff_element);
748
749 cycle_group_ct result = a.unconditional_subtract(b, hint);
750
751 EXPECT_EQ(result.get_value(), hint);
752 EXPECT_TRUE(result.is_constant());
753 EXPECT_FALSE(result.is_point_at_infinity().get_value());
754
755 check_circuit_and_gate_count(builder, 0);
756 }
757}
758
759TYPED_TEST(CycleGroupTest, TestUnconditionalAdd)
760{
762 auto builder = Builder();
763
764 auto add =
765 [&](const AffineElement& lhs, const AffineElement& rhs, const bool lhs_constant, const bool rhs_constant) {
766 cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&builder, lhs);
767 cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&builder, rhs);
768 // Assign two different tags
769 a.set_origin_tag(submitted_value_origin_tag);
770 b.set_origin_tag(challenge_origin_tag);
771 cycle_group_ct c = a.unconditional_add(b);
772 AffineElement expected(Element(lhs) + Element(rhs));
773 AffineElement result = c.get_value();
774 EXPECT_EQ(result, expected);
775 // Ensure the tags in the result are merged
776 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
777 };
778
779 add(TestFixture::generators[0], TestFixture::generators[1], false, false);
780 add(TestFixture::generators[0], TestFixture::generators[1], false, true);
781 add(TestFixture::generators[0], TestFixture::generators[1], true, false);
782 add(TestFixture::generators[0], TestFixture::generators[1], true, true);
783
784 check_circuit_and_gate_count(builder, 50);
785}
786
787TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddSucceed)
788{
790 auto builder = Builder();
791
792 auto lhs = TestFixture::generators[0];
793 auto rhs = TestFixture::generators[1];
794
795 // case 1. valid unconditional add
796 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
797 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
798 cycle_group_ct c = a.checked_unconditional_add(b);
799 AffineElement expected(Element(lhs) + Element(rhs));
800 AffineElement result = c.get_value();
801 EXPECT_EQ(result, expected);
802
803 check_circuit_and_gate_count(builder, 24);
804}
805
806TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalAddFail)
807{
808 BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor
810 auto builder = Builder();
811
812 auto lhs = TestFixture::generators[0];
813 auto rhs = -TestFixture::generators[0]; // ruh roh
814
815 // case 2. invalid unconditional add
816 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
817 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
818 a.checked_unconditional_add(b);
819
820 EXPECT_TRUE(builder.failed());
821 // No gate count check for failing test
822 EXPECT_FALSE(CircuitChecker::check(builder));
823}
824
825// Test regular addition of witness points (no edge cases)
827{
829 auto builder = Builder();
830
831 auto lhs = TestFixture::generators[0];
832 auto rhs = -TestFixture::generators[1];
833
834 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
835 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
836
837 // Test tag merging
838 a.set_origin_tag(submitted_value_origin_tag);
839 b.set_origin_tag(challenge_origin_tag);
840
841 cycle_group_ct c = a + b;
842
843 AffineElement expected(Element(lhs) + Element(rhs));
844 EXPECT_EQ(c.get_value(), expected);
845 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
846
847 check_circuit_and_gate_count(builder, 55);
848}
849
850// Test addition with LHS point at infinity
851TYPED_TEST(CycleGroupTest, TestAddLhsInfinity)
852{
854 auto builder = Builder();
855
856 auto rhs = -TestFixture::generators[1];
857 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
858
859 cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&builder, affine_infinity);
860
861 cycle_group_ct a = point_at_infinity;
862 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
863
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 // Result should be rhs since infinity + P = P
870 EXPECT_EQ(c.get_value(), rhs);
871 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
872
873 check_circuit_and_gate_count(builder, 55);
874}
875
876// Test addition with RHS point at infinity
877TYPED_TEST(CycleGroupTest, TestAddRhsInfinity)
878{
880 auto builder = Builder();
881
882 auto lhs = TestFixture::generators[0];
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 = cycle_group_ct::from_witness(&builder, lhs);
888 cycle_group_ct b = point_at_infinity;
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 // Result should be lhs since P + infinity = P
896 EXPECT_EQ(c.get_value(), lhs);
897 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
898
899 // Addition with witness infinity point
900 check_circuit_and_gate_count(builder, 55);
901}
902
903// Test addition with both points at infinity
904TYPED_TEST(CycleGroupTest, TestAddBothInfinity)
905{
907 auto builder = Builder();
908
909 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
910
911 cycle_group_ct point_at_infinity1 = cycle_group_ct::from_witness(&builder, affine_infinity);
912
913 cycle_group_ct point_at_infinity2 = cycle_group_ct::from_witness(&builder, affine_infinity);
914
915 cycle_group_ct a = point_at_infinity1;
916 cycle_group_ct b = point_at_infinity2;
917
918 a.set_origin_tag(submitted_value_origin_tag);
919 b.set_origin_tag(challenge_origin_tag);
920
921 cycle_group_ct c = a + b;
922
923 // Result should be infinity since infinity + infinity = infinity
924 EXPECT_TRUE(c.is_point_at_infinity().get_value());
925 EXPECT_TRUE(c.get_value().is_point_at_infinity());
926 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
927
928 check_circuit_and_gate_count(builder, 55);
929}
930
931// Test addition of inverse points (result is infinity)
932TYPED_TEST(CycleGroupTest, TestAddInversePoints)
933{
935 auto builder = Builder();
936
937 auto lhs = TestFixture::generators[0];
938
939 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
940 cycle_group_ct b = cycle_group_ct::from_witness(&builder, -lhs);
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, 55);
952}
953
954// Test doubling (adding point to itself)
955TYPED_TEST(CycleGroupTest, TestAddDoubling)
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 AffineElement expected((Element(lhs)).dbl());
971 EXPECT_EQ(c.get_value(), expected);
972 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
973
974 check_circuit_and_gate_count(builder, 55);
975}
976
977TYPED_TEST(CycleGroupTest, TestAddConstantPoints)
978{
980
981 // Test adding constant points - this takes a completely different path than witness points
982 // The existing TestAdd only tests witness points
983 {
984 auto builder = Builder();
985 auto lhs = TestFixture::generators[5];
986 auto rhs = TestFixture::generators[6];
987
988 cycle_group_ct a(lhs);
989 cycle_group_ct b(rhs);
990
991 cycle_group_ct result = a + b;
992
993 AffineElement expected(Element(lhs) + Element(rhs));
994 EXPECT_EQ(result.get_value(), expected);
995 EXPECT_TRUE(result.is_constant());
996
997 // No gates needed for constant arithmetic
998 check_circuit_and_gate_count(builder, 0);
999 }
1000
1001 // Test constant point + constant infinity (early return optimization)
1002 {
1003 auto builder = Builder();
1004 auto lhs = TestFixture::generators[7];
1005
1006 cycle_group_ct a(lhs);
1007 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1008
1009 cycle_group_ct result = a + b;
1010
1011 EXPECT_EQ(result.get_value(), lhs);
1012 EXPECT_TRUE(result.is_constant());
1013
1014 // Uses early return for constant infinity
1015 check_circuit_and_gate_count(builder, 0);
1016 }
1017}
1018
1019TYPED_TEST(CycleGroupTest, TestAddMixedConstantWitness)
1020{
1022
1023 // Test mixed constant/witness operations which use different code paths than pure witness ops
1024 // The existing TestAdd doesn't cover these mixed scenarios
1025
1026 // Test witness + constant infinity (early return path)
1027 {
1028 auto builder = Builder();
1029 auto lhs = TestFixture::generators[10];
1030
1031 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1032 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1033
1034 cycle_group_ct result = a + b;
1035
1036 EXPECT_EQ(result.get_value(), lhs);
1037 EXPECT_FALSE(result.is_constant());
1038
1039 // Early return optimization for constant infinity
1040 check_circuit_and_gate_count(builder, 10);
1041 }
1042
1043 // Test constant + witness point (different gate count than witness + witness)
1044 {
1045 auto builder = Builder();
1046 auto lhs = TestFixture::generators[11];
1047 auto rhs = TestFixture::generators[12];
1048
1049 cycle_group_ct a(lhs); // constant
1050 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs); // witness
1051
1052 cycle_group_ct result = a + b;
1053
1054 AffineElement expected(Element(lhs) + Element(rhs));
1055 EXPECT_EQ(result.get_value(), expected);
1056 EXPECT_FALSE(result.is_constant());
1057
1058 // Different gate count than pure witness addition
1059 check_circuit_and_gate_count(builder, 27);
1060 }
1061}
1062
1063// Test the infinity result logic specifically
1064TYPED_TEST(CycleGroupTest, TestAddInfinityResultLogic)
1065{
1067 auto builder = Builder();
1068
1069 // Test Case 1: P + (-P) = O (infinity_predicate true, neither input is infinity)
1070 {
1071 auto point = TestFixture::generators[0];
1072 auto neg_point = -point;
1073
1074 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1075 cycle_group_ct b = cycle_group_ct::from_witness(&builder, neg_point);
1076
1077 cycle_group_ct result = a + b;
1078
1079 // Verify result is infinity
1080 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1081 EXPECT_TRUE(result.get_value().is_point_at_infinity());
1082 }
1083
1084 // Test Case 2: O + O = O (both inputs are infinity)
1085 {
1086 cycle_group_ct inf1 = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1087 cycle_group_ct inf2 = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1088
1089 cycle_group_ct result = inf1 + inf2;
1090
1091 // Verify result is infinity
1092 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1093 EXPECT_TRUE(result.get_value().is_point_at_infinity());
1094 }
1095
1096 // Test Case 3: P + O = P (only rhs is infinity, result should NOT be infinity)
1097 {
1098 auto point = TestFixture::generators[1];
1099
1100 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1101 cycle_group_ct b = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1102
1103 cycle_group_ct result = a + b;
1104
1105 // Verify result is NOT infinity
1106 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1107 EXPECT_EQ(result.get_value(), point);
1108 }
1109
1110 // Test Case 4: O + P = P (only lhs is infinity, result should NOT be infinity)
1111 {
1112 auto point = TestFixture::generators[2];
1113
1114 cycle_group_ct a = cycle_group_ct::from_witness(&builder, Group::affine_point_at_infinity);
1115 cycle_group_ct b = cycle_group_ct::from_witness(&builder, point);
1116
1117 cycle_group_ct result = a + b;
1118
1119 // Verify result is NOT infinity
1120 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1121 EXPECT_EQ(result.get_value(), point);
1122 }
1123
1124 // Test Case 5: P + P = 2P (doubling, result should NOT be infinity unless P is special)
1125 {
1126 auto point = TestFixture::generators[3];
1127
1128 cycle_group_ct a = cycle_group_ct::from_witness(&builder, point);
1129 cycle_group_ct b = cycle_group_ct::from_witness(&builder, point);
1130
1131 cycle_group_ct result = a + b;
1132
1133 // Verify result is NOT infinity (it's 2P)
1134 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1135
1136 AffineElement expected(Element(point).dbl());
1137 EXPECT_EQ(result.get_value(), expected);
1138 }
1139
1140 check_circuit_and_gate_count(builder, 275);
1141}
1142
1143TYPED_TEST(CycleGroupTest, TestUnconditionalSubtract)
1144{
1146 auto builder = Builder();
1147
1148 auto subtract =
1149 [&](const AffineElement& lhs, const AffineElement& rhs, const bool lhs_constant, const bool rhs_constant) {
1150 cycle_group_ct a = lhs_constant ? cycle_group_ct(lhs) : cycle_group_ct::from_witness(&builder, lhs);
1151 cycle_group_ct b = rhs_constant ? cycle_group_ct(rhs) : cycle_group_ct::from_witness(&builder, rhs);
1152 // Assign two different tags
1153 a.set_origin_tag(submitted_value_origin_tag);
1154 b.set_origin_tag(challenge_origin_tag);
1155
1156 cycle_group_ct c = a.unconditional_subtract(b);
1157 AffineElement expected(Element(lhs) - Element(rhs));
1158 AffineElement result = c.get_value();
1159 EXPECT_EQ(result, expected);
1160 // Expect tags to be merged in the result
1161 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1162 };
1163
1164 subtract(TestFixture::generators[0], TestFixture::generators[1], false, false);
1165 subtract(TestFixture::generators[0], TestFixture::generators[1], false, true);
1166 subtract(TestFixture::generators[0], TestFixture::generators[1], true, false);
1167 subtract(TestFixture::generators[0], TestFixture::generators[1], true, true);
1168
1169 check_circuit_and_gate_count(builder, 50);
1170}
1171
1172TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractSucceed)
1173{
1175 auto builder = Builder();
1176
1177 auto lhs = TestFixture::generators[0];
1178 auto rhs = TestFixture::generators[1];
1179
1180 // case 1. valid unconditional add
1181 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1182 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1183 cycle_group_ct c = a.checked_unconditional_subtract(b);
1184 AffineElement expected(Element(lhs) - Element(rhs));
1185 AffineElement result = c.get_value();
1186 EXPECT_EQ(result, expected);
1187
1188 check_circuit_and_gate_count(builder, 24);
1189}
1190
1191TYPED_TEST(CycleGroupTest, TestConstrainedUnconditionalSubtractFail)
1192{
1194 auto builder = Builder();
1195
1196 auto lhs = TestFixture::generators[0];
1197 auto rhs = -TestFixture::generators[0]; // ruh roh
1198
1199 // case 2. invalid unconditional add
1200 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1201 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1202 a.checked_unconditional_subtract(b);
1203
1204 EXPECT_TRUE(builder.failed());
1205 // No gate count check for failing test
1206 EXPECT_FALSE(CircuitChecker::check(builder));
1207}
1208
1210{
1212 using bool_ct = stdlib::bool_t<Builder>;
1214 auto builder = Builder();
1215
1216 auto lhs = TestFixture::generators[0];
1217 auto rhs = -TestFixture::generators[1];
1218 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
1219
1220 cycle_group_ct point_at_infinity = cycle_group_ct::from_witness(&builder, affine_infinity);
1221
1222 // case 1. no edge-cases triggered
1223 {
1224 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1225 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1226 // Here and in the following cases we set 2 different tags to a and b
1227 a.set_origin_tag(submitted_value_origin_tag);
1228 b.set_origin_tag(challenge_origin_tag);
1229
1230 cycle_group_ct c = a - b;
1231 AffineElement expected(Element(lhs) - Element(rhs));
1232 AffineElement result = c.get_value();
1233 EXPECT_EQ(result, expected);
1234 // We expect the tag of the result to be the union of a and b tags
1235 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1236 }
1237
1238 // case 2. lhs is point at infinity
1239 {
1240 cycle_group_ct a = point_at_infinity;
1241 cycle_group_ct b = cycle_group_ct::from_witness(&builder, rhs);
1242 a.set_origin_tag(submitted_value_origin_tag);
1243 b.set_origin_tag(challenge_origin_tag);
1244
1245 cycle_group_ct c = a - b;
1246 AffineElement result = c.get_value();
1247 EXPECT_EQ(result, -rhs);
1248 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1249 }
1250
1251 // case 3. rhs is point at infinity
1252 {
1253 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1254 cycle_group_ct b = point_at_infinity;
1255 a.set_origin_tag(submitted_value_origin_tag);
1256 b.set_origin_tag(challenge_origin_tag);
1257
1258 cycle_group_ct c = a - b;
1259 AffineElement result = c.get_value();
1260 EXPECT_EQ(result, lhs);
1261 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1262 }
1263
1264 // case 4. both points are at infinity
1265 {
1266 cycle_group_ct a = point_at_infinity;
1267 cycle_group_ct b = point_at_infinity;
1268 a.set_origin_tag(submitted_value_origin_tag);
1269 b.set_origin_tag(challenge_origin_tag);
1270
1271 cycle_group_ct c = a - b;
1272 EXPECT_TRUE(c.is_point_at_infinity().get_value());
1273 EXPECT_TRUE(c.get_value().is_point_at_infinity());
1274 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1275 }
1276
1277 // case 5. lhs = -rhs
1278 {
1279 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1280 cycle_group_ct b = cycle_group_ct::from_witness(&builder, -lhs);
1281 a.set_origin_tag(submitted_value_origin_tag);
1282 b.set_origin_tag(challenge_origin_tag);
1283
1284 cycle_group_ct c = a - b;
1285 AffineElement expected((Element(lhs)).dbl());
1286 AffineElement result = c.get_value();
1287 EXPECT_EQ(result, expected);
1288 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1289 }
1290
1291 // case 6. lhs = rhs
1292 {
1293 cycle_group_ct a = cycle_group_ct::from_witness(&builder, lhs);
1294 cycle_group_ct b = cycle_group_ct::from_witness(&builder, lhs);
1295 a.set_origin_tag(submitted_value_origin_tag);
1296 b.set_origin_tag(challenge_origin_tag);
1297
1298 cycle_group_ct c = a - b;
1299 EXPECT_TRUE(c.is_point_at_infinity().get_value());
1300 EXPECT_TRUE(c.get_value().is_point_at_infinity());
1301 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1302 }
1303
1304 check_circuit_and_gate_count(builder, 297);
1305}
1306
1307TYPED_TEST(CycleGroupTest, TestSubtractConstantPoints)
1308{
1310
1311 // Test subtracting constant points - this takes a completely different path than witness points
1312 // The existing TestSubtract only tests witness points
1313 {
1314 auto builder = Builder();
1315 auto lhs = TestFixture::generators[5];
1316 auto rhs = TestFixture::generators[6];
1317
1318 cycle_group_ct a(lhs);
1319 cycle_group_ct b(rhs);
1320
1321 cycle_group_ct result = a - b;
1322
1323 AffineElement expected(Element(lhs) - Element(rhs));
1324 EXPECT_EQ(result.get_value(), expected);
1325 EXPECT_TRUE(result.is_constant());
1326
1327 // No gates needed for constant arithmetic
1328 check_circuit_and_gate_count(builder, 0);
1329 }
1330
1331 // Test constant point - constant infinity (early return optimization)
1332 {
1333 auto builder = Builder();
1334 auto lhs = TestFixture::generators[7];
1335
1336 cycle_group_ct a(lhs);
1337 cycle_group_ct b = cycle_group_ct::constant_infinity(&builder);
1338
1339 cycle_group_ct result = a - b;
1340
1341 EXPECT_EQ(result.get_value(), lhs);
1342 EXPECT_TRUE(result.is_constant());
1343
1344 // Uses early return for constant infinity
1345 check_circuit_and_gate_count(builder, 0);
1346 }
1347
1348 // Test constant infinity - constant point (early return optimization)
1349 {
1350 auto builder = Builder();
1351 auto rhs = TestFixture::generators[7];
1352
1353 cycle_group_ct a = cycle_group_ct::constant_infinity(&builder);
1354 cycle_group_ct b(rhs);
1355
1356 cycle_group_ct result = a - b;
1357
1358 EXPECT_EQ(result.get_value(), -rhs);
1359 EXPECT_TRUE(result.is_constant());
1360
1361 // Uses early return for constant infinity
1362 check_circuit_and_gate_count(builder, 0);
1363 }
1364}
1365
1372template <typename T1, typename T2> auto assign_and_merge_tags(T1& points, T2& scalars)
1373{
1374 OriginTag merged_tag = OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1375 for (size_t i = 0; i < points.size(); i++) {
1376 const auto point_tag = OriginTag(/*parent_index=*/0, /*round_index=*/i, /*is_submitted=*/true);
1377 const auto scalar_tag = OriginTag(/*parent_index=*/0, /*round_index=*/i, /*is_submitted=*/false);
1378
1379 merged_tag = OriginTag(merged_tag, OriginTag(point_tag, scalar_tag));
1380 points[i].set_origin_tag(point_tag);
1381 scalars[i].set_origin_tag(scalar_tag);
1382 }
1383 return merged_tag;
1384}
1385
1386TYPED_TEST(CycleGroupTest, TestBatchMulGeneralMSM)
1387{
1389 auto builder = Builder();
1390
1391 const size_t num_muls = 1;
1392 // case 1, general MSM with inputs that are combinations of constant and witnesses
1395 Element expected = Group::point_at_infinity;
1396
1397 for (size_t i = 0; i < num_muls; ++i) {
1398 auto element = TestFixture::generators[i];
1399 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1400
1401 // 1: add entry where point, scalar are witnesses
1402 expected += (element * scalar);
1403 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1404 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1405
1406 // 2: add entry where point is constant, scalar is witness
1407 expected += (element * scalar);
1408 points.emplace_back(cycle_group_ct(element));
1409 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1410
1411 // 3: add entry where point is witness, scalar is constant
1412 expected += (element * scalar);
1413 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1414 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1415
1416 // 4: add entry where point is constant, scalar is constant
1417 expected += (element * scalar);
1418 points.emplace_back(cycle_group_ct(element));
1419 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1420 }
1421
1422 // Here and in the following cases assign different tags to points and scalars and get the union of them back
1423 const auto expected_tag = assign_and_merge_tags(points, scalars);
1424
1425 auto result = cycle_group_ct::batch_mul(points, scalars);
1426 EXPECT_EQ(result.get_value(), AffineElement(expected));
1427 // The tag should the union of all tags
1428 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1429
1431 check_circuit_and_gate_count(builder, 4401); // Mega
1432 } else {
1433 check_circuit_and_gate_count(builder, 4404); // Ultra
1434 }
1435}
1436
1437TYPED_TEST(CycleGroupTest, TestBatchMulProducesInfinity)
1438{
1440 auto builder = Builder();
1441
1442 // case 2, MSM that produces point at infinity
1445
1446 auto element = TestFixture::generators[0];
1447 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1448 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1449 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1450
1451 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1452 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, -scalar));
1453
1454 const auto expected_tag = assign_and_merge_tags(points, scalars);
1455
1456 auto result = cycle_group_ct::batch_mul(points, scalars);
1457 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1458
1459 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1460
1462 check_circuit_and_gate_count(builder, 4027); // Mega
1463 } else {
1464 check_circuit_and_gate_count(builder, 4030); // Ultra
1465 }
1466}
1467
1468TYPED_TEST(CycleGroupTest, TestBatchMulMultiplyByZero)
1469{
1471 auto builder = Builder();
1472
1473 // case 3. Multiply by zero
1476
1477 auto element = TestFixture::generators[0];
1478 typename Group::Fr scalar = 0;
1479 points.emplace_back(cycle_group_ct::from_witness(&builder, element));
1480 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1481
1482 const auto expected_tag = assign_and_merge_tags(points, scalars);
1483 auto result = cycle_group_ct::batch_mul(points, scalars);
1484 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1485 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1486
1488 check_circuit_and_gate_count(builder, 3533); // Mega
1489 } else {
1490 check_circuit_and_gate_count(builder, 3536); // Ultra
1491 }
1492}
1493
1494TYPED_TEST(CycleGroupTest, TestBatchMulInputsAreInfinity)
1495{
1497 auto builder = Builder();
1498
1499 // Test batch_mul with witness point at infinity
1502
1503 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1504 auto affine_infinity = cycle_group_ct::AffineElement::infinity();
1505
1506 // is_infinity = witness
1507 {
1508 cycle_group_ct point = cycle_group_ct::from_witness(&builder, affine_infinity);
1509 points.emplace_back(point);
1510 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1511 }
1512 // is_infinity = constant
1513 {
1514 cycle_group_ct point = cycle_group_ct(affine_infinity);
1515 points.emplace_back(point);
1516 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1517 }
1518
1519 const auto expected_tag = assign_and_merge_tags(points, scalars);
1520 auto result = cycle_group_ct::batch_mul(points, scalars);
1521 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1522 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1523
1524 // Gate count difference due to additional constants added by default in Mega builder
1526 check_circuit_and_gate_count(builder, 3584); // Mega
1527 } else {
1528 check_circuit_and_gate_count(builder, 3587); // Ultra
1529 }
1530}
1531
1532TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseInLookupTable)
1533{
1535 auto builder = Builder();
1536
1537 const size_t num_muls = 1;
1538 // case 5, fixed-base MSM with inputs that are combinations of constant and witnesses (group elements are in
1539 // lookup table)
1542 std::vector<typename Group::Fq> scalars_native;
1543 Element expected = Group::point_at_infinity;
1544 for (size_t i = 0; i < num_muls; ++i) {
1546 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1547
1548 // 1: add entry where point is constant, scalar is witness
1549 expected += (element * scalar);
1550 points.emplace_back(element);
1551 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1552 scalars_native.emplace_back(uint256_t(scalar));
1553
1554 // 2: add entry where point is constant, scalar is constant
1556 expected += (element * scalar);
1557 points.emplace_back(element);
1558 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1559 scalars_native.emplace_back(uint256_t(scalar));
1560 }
1561 const auto expected_tag = assign_and_merge_tags(points, scalars);
1562 auto result = cycle_group_ct::batch_mul(points, scalars);
1563 EXPECT_EQ(result.get_value(), AffineElement(expected));
1564 EXPECT_EQ(result.get_value(), crypto::pedersen_commitment::commit_native(scalars_native));
1565 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1566
1567 check_circuit_and_gate_count(builder, 2822);
1568}
1569
1570TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseSomeInLookupTable)
1571{
1573 auto builder = Builder();
1574
1575 const size_t num_muls = 1;
1576 // case 6, fixed-base MSM with inputs that are combinations of constant and witnesses (some group elements are
1577 // in lookup table)
1580 std::vector<typename Group::Fr> scalars_native;
1581 Element expected = Group::point_at_infinity;
1582 for (size_t i = 0; i < num_muls; ++i) {
1584 typename Group::Fr scalar = Group::Fr::random_element(&engine);
1585
1586 // 1: add entry where point is constant, scalar is witness
1587 expected += (element * scalar);
1588 points.emplace_back(element);
1589 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1590 scalars_native.emplace_back(scalar);
1591
1592 // 2: add entry where point is constant, scalar is constant
1594 expected += (element * scalar);
1595 points.emplace_back(element);
1596 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1597 scalars_native.emplace_back(scalar);
1598
1599 // 3: add entry where point is constant, scalar is witness
1600 scalar = Group::Fr::random_element(&engine);
1601 element = Group::one * Group::Fr::random_element(&engine);
1602 expected += (element * scalar);
1603 points.emplace_back(element);
1604 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1605 scalars_native.emplace_back(scalar);
1606 }
1607 const auto expected_tag = assign_and_merge_tags(points, scalars);
1608 auto result = cycle_group_ct::batch_mul(points, scalars);
1609 EXPECT_EQ(result.get_value(), AffineElement(expected));
1610 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1611
1612 // Gate count difference due to additional constants added by default in Mega builder
1614 check_circuit_and_gate_count(builder, 3395); // Mega
1615 } else {
1616 check_circuit_and_gate_count(builder, 3398); // Ultra
1617 }
1618}
1619
1620TYPED_TEST(CycleGroupTest, TestBatchMulFixedBaseZeroScalars)
1621{
1623 auto builder = Builder();
1624
1625 const size_t num_muls = 1;
1626 // case 7, Fixed-base MSM where input scalars are 0
1629
1630 for (size_t i = 0; i < num_muls; ++i) {
1632 typename Group::Fr scalar = 0;
1633
1634 // 1: add entry where point is constant, scalar is witness
1635 points.emplace_back((element));
1636 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
1637
1638 // 2: add entry where point is constant, scalar is constant
1639 points.emplace_back((element));
1640 scalars.emplace_back(typename cycle_group_ct::cycle_scalar(scalar));
1641 }
1642 const auto expected_tag = assign_and_merge_tags(points, scalars);
1643 auto result = cycle_group_ct::batch_mul(points, scalars);
1644 EXPECT_EQ(result.is_point_at_infinity().get_value(), true);
1645 EXPECT_EQ(result.get_origin_tag(), expected_tag);
1646
1647 check_circuit_and_gate_count(builder, 2837);
1648}
1649
1651{
1653 auto builder = Builder();
1654
1655 const size_t num_muls = 5;
1656
1657 // case 1, general MSM with inputs that are combinations of constant and witnesses
1658 {
1659 cycle_group_ct point;
1660 typename cycle_group_ct::cycle_scalar scalar;
1661 cycle_group_ct result;
1662 for (size_t i = 0; i < num_muls; ++i) {
1663 auto element = TestFixture::generators[i];
1664 typename Group::Fr native_scalar = Group::Fr::random_element(&engine);
1665 auto expected_result = element * native_scalar;
1666
1667 // 1: perform mul where point, scalar are witnesses
1668 point = (cycle_group_ct::from_witness(&builder, element));
1669 scalar = (cycle_group_ct::cycle_scalar::from_witness(&builder, native_scalar));
1670 point.set_origin_tag(submitted_value_origin_tag);
1671 scalar.set_origin_tag(challenge_origin_tag);
1672 result = point * scalar;
1673 EXPECT_EQ((result).get_value(), (expected_result));
1674
1675 // 2: perform mul where point is constant, scalar is witness
1676 point = (cycle_group_ct(element));
1677 scalar = (cycle_group_ct::cycle_scalar::from_witness(&builder, native_scalar));
1678 result = point * scalar;
1679 EXPECT_EQ((result).get_value(), (expected_result));
1680
1681 // 3: perform mul where point is witness, scalar is constant
1682 point = (cycle_group_ct::from_witness(&builder, element));
1683 scalar = (typename cycle_group_ct::cycle_scalar(native_scalar));
1684 result = point * scalar;
1685 EXPECT_EQ((result).get_value(), (expected_result));
1686
1687 // 4: perform mul where point is constant, scalar is constant
1688 point = (cycle_group_ct(element));
1689 scalar = (typename cycle_group_ct::cycle_scalar(native_scalar));
1690 result = point * scalar;
1691 EXPECT_EQ((result).get_value(), (expected_result));
1692 }
1693 }
1694
1695 // Gate count difference due to additional constants added by default in Mega builder
1697 check_circuit_and_gate_count(builder, 12973); // Mega
1698 } else {
1699 check_circuit_and_gate_count(builder, 12976); // Ultra
1700 }
1701}
1702
1704{
1707 cycle_group_ct one = cycle_group_ct::one(&builder);
1708 auto expected_one_native = Group::one;
1709 auto one_native = one.get_value();
1710 EXPECT_EQ(one_native.x, expected_one_native.x);
1711 EXPECT_EQ(one_native.y, expected_one_native.y);
1712}
1713
1719TYPED_TEST(CycleGroupTest, TestConversionFromBigfield)
1720{
1722 using FF = typename Curve::ScalarField;
1724
1725 const auto run_test = [](bool construct_witnesses) {
1727 auto elt = FF::random_element(&engine);
1728 FF_ct big_elt;
1729 if (construct_witnesses) {
1730 big_elt = FF_ct::from_witness(&builder, elt);
1731 } else {
1732 big_elt = FF_ct(elt);
1733 }
1734 big_elt.set_origin_tag(submitted_value_origin_tag);
1735 cycle_scalar_ct scalar_from_big_elt(big_elt);
1736 EXPECT_EQ(elt, scalar_from_big_elt.get_value());
1737 EXPECT_EQ(scalar_from_big_elt.get_origin_tag(), big_elt.get_origin_tag());
1738 if (construct_witnesses) {
1739 EXPECT_FALSE(big_elt.is_constant());
1740 EXPECT_FALSE(scalar_from_big_elt.is_constant());
1741 check_circuit_and_gate_count(builder, 3523);
1742 }
1743 };
1744 run_test(/*construct_witnesses=*/true);
1745 run_test(/*construct_witnesses=*/false);
1746}
1747
1748TYPED_TEST(CycleGroupTest, TestBatchMulIsConsistent)
1749{
1751 using FF = typename Curve::ScalarField;
1753
1754 const auto run_test = [](bool construct_witnesses) {
1756 auto scalar1 = FF::random_element(&engine);
1757 auto scalar2 = FF::random_element(&engine);
1758
1759 FF_ct big_scalar1;
1760 FF_ct big_scalar2;
1761 if (construct_witnesses) {
1762 big_scalar1 = FF_ct::from_witness(&builder, scalar1);
1763 big_scalar2 = FF_ct::from_witness(&builder, scalar2);
1764 } else {
1765 big_scalar1 = FF_ct(scalar1);
1766 big_scalar2 = FF_ct(scalar2);
1767 }
1768 cycle_group_ct result1 = cycle_group_ct::batch_mul({ TestFixture::generators[0], TestFixture::generators[1] },
1769 { big_scalar1, big_scalar2 });
1770
1771 cycle_group_ct result2 =
1772 cycle_group_ct::batch_mul({ TestFixture::generators[0], TestFixture::generators[1] },
1773 { cycle_scalar_ct(big_scalar1), cycle_scalar_ct(big_scalar2) });
1774
1775 AffineElement result1_native = result1.get_value();
1776 AffineElement result2_native = result2.get_value();
1777 EXPECT_EQ(result1_native.x, result2_native.x);
1778 EXPECT_EQ(result1_native.y, result2_native.y);
1779 if (construct_witnesses) {
1780 EXPECT_FALSE(result1.is_constant());
1781 EXPECT_FALSE(result2.is_constant());
1782 // Gate count difference due to additional constants added by default in Mega builder
1784 check_circuit_and_gate_count(builder, 5285); // Mega
1785 } else {
1786 check_circuit_and_gate_count(builder, 5288); // Ultra
1787 }
1788 }
1789 };
1790 run_test(/*construct_witnesses=*/true);
1791 run_test(/*construct_witnesses=*/false);
1792}
1793
1799TYPED_TEST(CycleGroupTest, TestFixedBaseBatchMul)
1800{
1803
1804 // Get the fixed base points that have lookup tables
1807
1808 // Test with two scalars and both generators
1811
1812 auto scalar1_val = Group::Fr::random_element(&engine);
1813 auto scalar2_val = Group::Fr::random_element(&engine);
1814
1815 scalars.push_back(cycle_scalar_ct::from_witness(&builder, scalar1_val));
1816 scalars.push_back(cycle_scalar_ct::from_witness(&builder, scalar2_val));
1817 points.push_back(cycle_group_ct(lhs_generator)); // constant point
1818 points.push_back(cycle_group_ct(rhs_generator)); // constant point
1819
1820 auto result = cycle_group_ct::batch_mul(points, scalars);
1821
1822 // Compute expected result natively
1823 AffineElement expected = lhs_generator * scalar1_val + rhs_generator * scalar2_val;
1824
1825 EXPECT_EQ(result.get_value(), expected);
1826
1827 check_circuit_and_gate_count(builder, 2908);
1828}
1829
1835TYPED_TEST(CycleGroupTest, TestInfinityChainedOperations)
1836{
1839
1840 // Case 1: (a + infinity) - a = infinity
1841 {
1842 auto input = TestFixture::generators[0];
1843 cycle_group_ct a = cycle_group_ct::from_witness(&builder, input);
1844 cycle_group_ct inf = cycle_group_ct::constant_infinity(&builder);
1845
1846 cycle_group_ct temp = a + inf;
1847 cycle_group_ct result = temp - a;
1848
1849 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1850 // Note: raw coordinates may not be (0,0) for non-canonical intermediates.
1851 // Canonicalization happens at observation boundaries (serialize, set_public, ==).
1852 EXPECT_TRUE(result.get_value().is_point_at_infinity());
1853 }
1854
1855 // Case 2: a + (b - b) = a
1856 {
1857 auto input_a = TestFixture::generators[0];
1858 auto input_b = TestFixture::generators[1];
1859 cycle_group_ct a = cycle_group_ct::from_witness(&builder, input_a);
1860 cycle_group_ct b = cycle_group_ct::from_witness(&builder, input_b);
1861
1862 cycle_group_ct zero = b - b; // Should be infinity
1863 cycle_group_ct result = a + zero;
1864
1865 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1866 EXPECT_EQ(result.get_value().x, input_a.x);
1867 EXPECT_EQ(result.get_value().y, input_a.y);
1868 }
1869
1870 // Case 3: (infinity - infinity) + a = a
1871 {
1872 auto input = TestFixture::generators[0];
1873 cycle_group_ct a = cycle_group_ct::from_witness(&builder, input);
1874 cycle_group_ct inf1 = cycle_group_ct::constant_infinity(&builder);
1875 cycle_group_ct inf2 = cycle_group_ct::constant_infinity(&builder);
1876
1877 cycle_group_ct zero = inf1 - inf2;
1878 cycle_group_ct result = zero + a;
1879
1880 EXPECT_EQ(result.get_value().x, input.x);
1881 EXPECT_EQ(result.get_value().y, input.y);
1882 }
1883
1884 EXPECT_FALSE(builder.failed());
1885 EXPECT_TRUE(CircuitChecker::check(builder));
1886}
1887
1893TYPED_TEST(CycleGroupTest, TestConditionalAssignWithInfinity)
1894{
1897
1898 auto input = TestFixture::generators[0];
1899 cycle_group_ct a = cycle_group_ct::from_witness(&builder, input);
1900 cycle_group_ct inf = cycle_group_ct::constant_infinity(&builder);
1901
1902 // Case 1: Select finite point when predicate is false (returns rhs = a)
1903 // conditional_assign(pred, lhs, rhs) returns lhs if pred is true, rhs otherwise
1904 {
1905 bool_ct pred(witness_ct(&builder, false));
1906 cycle_group_ct result = cycle_group_ct::conditional_assign(pred, inf, a);
1907
1908 EXPECT_FALSE(result.is_point_at_infinity().get_value());
1909 EXPECT_EQ(result.get_value().x, input.x);
1910 EXPECT_EQ(result.get_value().y, input.y);
1911 }
1912
1913 // Case 2: Select infinity when predicate is true (returns lhs = inf)
1914 {
1915 bool_ct pred(witness_ct(&builder, true));
1916 cycle_group_ct result = cycle_group_ct::conditional_assign(pred, inf, a);
1917
1918 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1919 EXPECT_TRUE(result.x().get_value() == 0);
1920 EXPECT_TRUE(result.y().get_value() == 0);
1921 }
1922
1923 // Case 3: Select between two infinity points
1924 {
1925 cycle_group_ct inf2 = cycle_group_ct::constant_infinity(&builder);
1926 bool_ct pred(witness_ct(&builder, true));
1927 cycle_group_ct result = cycle_group_ct::conditional_assign(pred, inf, inf2);
1928
1929 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1930 EXPECT_TRUE(result.x().get_value() == 0);
1931 EXPECT_TRUE(result.y().get_value() == 0);
1932 }
1933
1934 EXPECT_FALSE(builder.failed());
1935 EXPECT_TRUE(CircuitChecker::check(builder));
1936}
1937
1944TYPED_TEST(CycleGroupTest, TestWitnessInfinityFromOperations)
1945{
1948
1949 // Create infinity as P - P (witness-based infinity)
1950 auto input = TestFixture::generators[0];
1951 cycle_group_ct P = cycle_group_ct::from_witness(&builder, input);
1952 cycle_group_ct witness_inf = P - P;
1953
1954 EXPECT_TRUE(witness_inf.is_point_at_infinity().get_value());
1955
1956 // Use this witness infinity in operations
1957 auto input2 = TestFixture::generators[1];
1958 cycle_group_ct Q = cycle_group_ct::from_witness(&builder, input2);
1959
1960 // Q + witness_inf = Q
1961 cycle_group_ct result = Q + witness_inf;
1962 EXPECT_EQ(result.get_value().x, input2.x);
1963 EXPECT_EQ(result.get_value().y, input2.y);
1964
1965 // witness_inf + Q = Q
1966 cycle_group_ct result2 = witness_inf + Q;
1967 EXPECT_EQ(result2.get_value().x, input2.x);
1968 EXPECT_EQ(result2.get_value().y, input2.y);
1969
1970 EXPECT_FALSE(builder.failed());
1971 EXPECT_TRUE(CircuitChecker::check(builder));
1972}
1973
1977TYPED_TEST(CycleGroupTest, TestBatchMulCompleteCancellation)
1978{
1981
1982 // P*a + Q*b + P*(-a) + Q*(-b) = infinity
1983 auto P_val = TestFixture::generators[0];
1984 auto Q_val = TestFixture::generators[1];
1985 auto a = Group::Fr::random_element(&engine);
1986 auto b = Group::Fr::random_element(&engine);
1987
1989 cycle_group_ct::from_witness(&builder, P_val),
1990 cycle_group_ct::from_witness(&builder, Q_val),
1991 cycle_group_ct::from_witness(&builder, P_val),
1992 cycle_group_ct::from_witness(&builder, Q_val),
1993 };
1994
1996 cycle_scalar_ct::from_witness(&builder, a),
1997 cycle_scalar_ct::from_witness(&builder, b),
1998 cycle_scalar_ct::from_witness(&builder, -a),
1999 cycle_scalar_ct::from_witness(&builder, -b),
2000 };
2001
2002 cycle_group_ct result = cycle_group_ct::batch_mul(points, scalars);
2003
2004 EXPECT_TRUE(result.is_point_at_infinity().get_value());
2005 // Note: raw coordinates may not be (0,0) for non-canonical intermediates.
2006 EXPECT_TRUE(result.get_value().is_point_at_infinity());
2007
2008 EXPECT_FALSE(builder.failed());
2009 EXPECT_TRUE(CircuitChecker::check(builder));
2010}
2011
2015TYPED_TEST(CycleGroupTest, TestInfinityCanonicalRepresentation)
2016{
2019
2020 // Note: For grumpkin, native AffineElement uses x=modulus for infinity, not (0,0)
2021 // The circuit representation uses (0,0) but get_value() returns native format
2022
2023 // Case 1: constant_infinity is correctly identified
2024 {
2025 cycle_group_ct inf = cycle_group_ct::constant_infinity(&builder);
2026 EXPECT_TRUE(inf.is_point_at_infinity().get_value());
2027 EXPECT_TRUE(inf.get_value().is_point_at_infinity());
2028 // Circuit coordinates should be (0, 0)
2029 EXPECT_EQ(inf.x().get_value(), 0);
2030 EXPECT_EQ(inf.y().get_value(), 0);
2031 }
2032
2033 // Case 2: P + (-P) returns infinity
2034 {
2035 auto input = TestFixture::generators[0];
2036 cycle_group_ct P = cycle_group_ct::from_witness(&builder, input);
2037 cycle_group_ct neg_P = -P;
2038 cycle_group_ct result = P + neg_P;
2039
2040 EXPECT_TRUE(result.is_point_at_infinity().get_value());
2041 // Note: raw coordinates may not be (0,0) for non-canonical intermediates.
2042 // Canonicalization happens at observation boundaries (serialize, set_public, ==).
2043 EXPECT_TRUE(result.get_value().is_point_at_infinity());
2044 }
2045
2046 // Case 3: 2 * infinity returns infinity
2047 {
2048 cycle_group_ct inf = cycle_group_ct::constant_infinity(&builder);
2049 cycle_group_ct result = inf.dbl();
2050
2051 EXPECT_TRUE(result.is_point_at_infinity().get_value());
2052 // Note: raw coordinates may not be (0,0) for non-canonical intermediates.
2053 EXPECT_TRUE(result.get_value().is_point_at_infinity());
2054 }
2055
2056 EXPECT_FALSE(builder.failed());
2057 EXPECT_TRUE(CircuitChecker::check(builder));
2058}
2059
2065TYPED_TEST(CycleGroupTest, TestInfinityAutoDetectionInConstructor)
2066{
2069
2070 // Create element with (0, 0) coordinates - should auto-detect as infinity
2072 auto x_zero = field_t::from_witness(&builder, typename field_t::native(0));
2073 auto y_zero = field_t::from_witness(&builder, typename field_t::native(0));
2074
2075 // Use 3-arg constructor which should auto-detect infinity
2076 cycle_group_ct point(x_zero, y_zero, /*assert_on_curve=*/false);
2077
2078 EXPECT_TRUE(point.is_point_at_infinity().get_value());
2079
2080 EXPECT_FALSE(builder.failed());
2081 EXPECT_TRUE(CircuitChecker::check(builder));
2082}
2083
2087TYPED_TEST(CycleGroupTest, TestFixedBatchMul)
2088{
2090 auto builder = Builder();
2091
2092 constexpr size_t num_points = 8;
2095 Element expected = Group::point_at_infinity;
2096
2097 for (size_t i = 0; i < num_points; ++i) {
2098 auto element = TestFixture::generators[i];
2099 typename Group::Fr scalar = Group::Fr::random_element(&engine);
2100 expected += (element * scalar);
2101 // Points are constant, scalars are witnesses
2102 points.emplace_back(cycle_group_ct(element));
2103 scalars.emplace_back(cycle_group_ct::cycle_scalar::from_witness(&builder, scalar));
2104 }
2105
2106 auto result = cycle_group_ct::fixed_batch_mul(points, scalars);
2107 EXPECT_EQ(result.get_value(), AffineElement(expected));
2108
2109 EXPECT_FALSE(builder.failed());
2110 EXPECT_TRUE(CircuitChecker::check(builder));
2111}
2112
2116TYPED_TEST(CycleGroupTest, TestFixedBatchMulSinglePoint)
2117{
2119 auto builder = Builder();
2120
2121 auto element = TestFixture::generators[0];
2122 typename Group::Fr scalar = Group::Fr::random_element(&engine);
2123 Element expected = element * scalar;
2124
2125 std::vector<cycle_group_ct> points{ cycle_group_ct(element) };
2126 std::vector<typename cycle_group_ct::cycle_scalar> scalars{ cycle_group_ct::cycle_scalar::from_witness(&builder,
2127 scalar) };
2128
2129 auto result = cycle_group_ct::fixed_batch_mul(points, scalars);
2130 EXPECT_EQ(result.get_value(), AffineElement(expected));
2131
2132 EXPECT_FALSE(builder.failed());
2133 EXPECT_TRUE(CircuitChecker::check(builder));
2134}
2135
2139TYPED_TEST(CycleGroupTest, TestFixedBatchMulZeroScalar)
2140{
2142 auto builder = Builder();
2143
2144 auto element = TestFixture::generators[0];
2145 typename Group::Fr zero_scalar = 0;
2146
2147 std::vector<cycle_group_ct> points{ cycle_group_ct(element) };
2148 std::vector<typename cycle_group_ct::cycle_scalar> scalars{ cycle_group_ct::cycle_scalar::from_witness(
2149 &builder, zero_scalar) };
2150
2151 auto result = cycle_group_ct::fixed_batch_mul(points, scalars);
2152 EXPECT_TRUE(result.is_point_at_infinity().get_value());
2153
2154 EXPECT_FALSE(builder.failed());
2155 EXPECT_TRUE(CircuitChecker::check(builder));
2156}
2157
2158#pragma GCC diagnostic pop
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
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:60
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:467
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
bb::curve::BN254::Element Element
numeric::RNG & engine
stdlib::witness_t< Builder > witness_ct
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
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(CommitmentKeyTest, Curves)
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
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
testing::Types< bb::MegaCircuitBuilder, bb::UltraCircuitBuilder > CircuitTypes
static OriginTag constant()
static field random_element(numeric::RNG *engine=nullptr) noexcept