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