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