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