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