Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
biggroup.test.cpp
Go to the documentation of this file.
1#include "../biggroup/biggroup.hpp"
2#include "../bigfield/bigfield.hpp"
3#include "../bool/bool.hpp"
4#include "../field/field.hpp"
16#include <vector>
17
18using namespace bb;
19
20namespace {
22}
23
24enum struct InputType {
25 WITNESS,
27};
28
33
34template <typename T>
36
37// One can only define a TYPED_TEST with a single template paramter.
38// Our workaround is to pass parameters of the following type.
39template <typename Curve_, typename ScalarField_, bool use_bigfield> struct TestType {
40 public:
41 using Curve = Curve_;
42 // The base field is always a bigfield, so we only have to select the scalar field type
43 using bigfield_element = bb::stdlib::
44 element<typename Curve::Builder, typename Curve::BaseField, ScalarField_, typename Curve::GroupNative>;
46 // the field of scalars acting on element_ct
47 using scalar_ct = ScalarField_;
48};
49
51template <typename TestType> class stdlib_biggroup : public testing::Test {
52 public:
53 using Curve = typename TestType::Curve;
56
57 using fq = typename Curve::BaseFieldNative;
58 using fr = typename Curve::ScalarFieldNative;
59 using g1 = typename Curve::GroupNative;
61 using element = typename g1::element;
62
63 using Builder = typename Curve::Builder;
67
68 static constexpr auto EXPECT_CIRCUIT_CORRECTNESS = [](Builder& builder, bool expected_result = true) {
69 info("num gates = ", builder.get_num_finalized_gates_inefficient());
71 EXPECT_EQ(builder.failed(), !expected_result);
72 };
73
74 // Helper to check the infinity status of a circuit element.
75 // Ultra: reads the in-circuit is_point_at_infinity flag.
76 // Goblin/Mega: derives infinity from native (0,0) coordinates (no circuit flag exists).
77 static bool is_infinity(const element_ct& e)
78 {
79 if constexpr (HasGoblinBuilder<TestType>) {
80 return e.get_value().is_point_at_infinity();
81 } else {
82 return e.is_point_at_infinity().get_value();
83 }
84 }
85
86 // Create a random point as a witness
88 {
89 affine_element point_native(element::random_element());
90 element_ct point_ct = element_ct::from_witness(builder, point_native);
91 return std::make_pair(point_native, point_ct);
92 }
93
94 // Create a random point as a constant
96 {
97 affine_element point_native(element::random_element());
98 // Create constant coordinates with builder context
99 using Fq = typename element_ct::BaseField;
100 Fq x_const(builder, uint256_t(point_native.x));
101 Fq y_const(builder, uint256_t(point_native.y));
102 element_ct point_ct(x_const, y_const);
103 return std::make_pair(point_native, point_ct);
104 }
105
106 // Create a random point based on InputType
114
115 // Create a random scalar as a witness
117 {
118 fr scalar_native = fr::random_element();
119 if (even && uint256_t(scalar_native).get_bit(0)) {
120 scalar_native -= fr(1); // make it even if it's odd
121 }
122 scalar_ct scalar_ct_val = scalar_ct::from_witness(builder, scalar_native);
123 return std::make_pair(scalar_native, scalar_ct_val);
124 }
125
126 // Create a random scalar as a constant
128 {
129 fr scalar_native = fr::random_element();
130 if (even && uint256_t(scalar_native).get_bit(0)) {
131 scalar_native -= fr(1); // make it even if it's odd
132 }
133 scalar_ct scalar_ct_val = scalar_ct(builder, scalar_native);
134 return std::make_pair(scalar_native, scalar_ct_val);
135 }
136
137 // Create a random scalar based on InputType
139 {
140 if (type == InputType::WITNESS) {
142 }
144 }
145
147 {
148 uint256_t scalar_u256 = engine.get_random_uint256();
149 scalar_u256 = scalar_u256 >> (256 - num_bits); // keep only the lower num_bits bits
150
151 fr scalar_native(scalar_u256);
152 scalar_ct scalar_ct_val;
153 if (type == InputType::WITNESS) {
154 scalar_ct_val = scalar_ct::from_witness(builder, scalar_native);
155 } else {
156 scalar_ct_val = scalar_ct(builder, scalar_native);
157 }
158 return std::make_pair(scalar_native, scalar_ct_val);
159 }
160
161 public:
162 // Smoke tests for origin tag propagation across all basic operations
164 {
167
168 // Setup: two points with different tags
169 auto [input_a, a] = get_random_point(&builder, InputType::WITNESS);
170 auto [input_b, b] = get_random_point(&builder, InputType::WITNESS);
171 a.set_origin_tag(submitted_value_origin_tag);
172 b.set_origin_tag(challenge_origin_tag);
173
174 // Tag is preserved after being set
175 EXPECT_EQ(a.get_origin_tag(), submitted_value_origin_tag);
176 EXPECT_EQ(b.get_origin_tag(), challenge_origin_tag);
177
178 // Binary operations merge tags
179 EXPECT_EQ((a + b).get_origin_tag(), first_two_merged_tag);
180 EXPECT_EQ((a - b).get_origin_tag(), first_two_merged_tag);
181
182 // Unary operations preserve tags
183 EXPECT_EQ(a.dbl().get_origin_tag(), submitted_value_origin_tag);
184 EXPECT_EQ((-a).get_origin_tag(), submitted_value_origin_tag);
185
186 // Scalar multiplication merges tags
187 auto scalar = scalar_ct::from_witness(&builder, fr::random_element());
188 scalar.set_origin_tag(challenge_origin_tag);
189 EXPECT_EQ((a * scalar).get_origin_tag(), first_two_merged_tag);
190
191 // Conditional operations merge tags
192 auto predicate = bool_ct(witness_ct(&builder, true));
193 predicate.set_origin_tag(challenge_origin_tag);
194 EXPECT_EQ(a.conditional_negate(predicate).get_origin_tag(), first_two_merged_tag);
195
196 // conditional_select merges all three input tags
197 predicate.set_origin_tag(next_challenge_tag);
198 EXPECT_EQ(a.conditional_select(b, predicate).get_origin_tag(), first_second_third_merged_tag);
199
200 // Construction from tagged field elements merges member tags
201 affine_element input_c(element::random_element());
202 auto x = element_ct::BaseField::from_witness(&builder, input_c.x);
203 auto y = element_ct::BaseField::from_witness(&builder, input_c.y);
204
205 // Set tags on the individual field elements
206 x.set_origin_tag(submitted_value_origin_tag);
207 y.set_origin_tag(challenge_origin_tag);
208
209 // Construct biggroup element from pre-tagged field elements
210 // The is_infinity flag is auto-detected from coordinates and won't have a user-set tag
211 element_ct c(x, y);
212
213 // The tag of the biggroup element should be the union of x and y member tags
214 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
215
216 // compute_naf propagates tag to output bits (not available on goblin elements)
217 if constexpr (!HasGoblinBuilder<TestType>) {
218 auto naf_scalar = scalar_ct::from_witness(&builder, fr(12345));
219 naf_scalar.set_origin_tag(submitted_value_origin_tag);
220 auto naf = element_ct::compute_naf(naf_scalar, 16);
221 for (const auto& bit : naf) {
222 EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag);
223 }
224 }
225
226#ifndef NDEBUG
227 // Instant death tag causes exception on use
228 affine_element input_death(element::random_element());
229 auto x_death = element_ct::BaseField::from_witness(&builder, input_death.x);
230 auto y_normal = element_ct::BaseField::from_witness(&builder, input_death.y);
231 x_death.set_origin_tag(instant_death_tag);
232 y_normal.set_origin_tag(constant_tag);
233 element_ct death_point(x_death, y_normal, /*assert_on_curve=*/false);
234 EXPECT_THROW(death_point + death_point, std::runtime_error);
235
236 // AUDITTODO: incomplete_assert_equal has inconsistent instant_death behavior between builders. (this was simply
237 // untested before).
238 //
239 // Design intent: assert_equal methods explicitly disable tag checking to allow comparing
240 // values from different transcript sources. So instant_death should NOT be triggered.
241 //
242 // Current behavior:
243 // - bigfield: instant_death IS triggered because bigfield::get_origin_tag()
244 // merges 5 limb tags, which invokes the OriginTag merge constructor that checks for
245 // instant_death. This happens BEFORE tags are cleared.
246 // - goblin_field: instant_death is NOT triggered because goblin_field::assert_equal
247 // delegates to field_t::assert_equal on each limb, which saves tags individually without
248 // merging.
249 //
250 // Potential fix: In bigfield::assert_equal, save/restore tags at the limb level instead of
251 // calling get_origin_tag() which merges tags.
252#endif
253 }
254
256 {
257 // Only test for non-goblin builders (goblin elements don't have assert_coordinates_in_field
258 // because coordinate checks are done in the ECCVM circuit)
259 if constexpr (!HasGoblinBuilder<TestType>) {
260 // Test 1: Valid coordinates should pass
261 {
263
264 // Test multiple random points to ensure assert_coordinates_in_field works correctly
265 for (size_t i = 0; i < 3; ++i) {
266 affine_element valid_point(element::random_element());
267 element_ct point = element_ct::from_witness(&builder, valid_point);
268
269 // This should not fail - coordinates are in field
270 point.assert_coordinates_in_field();
271 }
272
273 // Verify the circuit is correct
275 }
276
277 // Test 2: Invalid x coordinate should cause circuit to fail
278 {
280 affine_element valid_point(element::random_element());
281
282 // Create a bigfield element with x coordinate that will be out of range
283 // We do this by creating a valid witness but then manipulating the limb values
284 // to make them represent a value >= the modulus
285 auto x_coord = element_ct::BaseField::from_witness(&builder, valid_point.x);
286 auto y_coord = element_ct::BaseField::from_witness(&builder, valid_point.y);
287
288 // Manipulate the limbs to create an invalid value
289 // Set the highest limb to a very large value that would make the total >= modulus
290 x_coord.binary_basis_limbs[3].element = field_ct::from_witness(&builder, bb::fr(uint256_t(1) << 68));
291 x_coord.binary_basis_limbs[3].maximum_value = uint256_t(1) << 68;
292
293 // Skip curve check since we're intentionally creating an invalid point
294 // Note: is_infinity is auto-detected as false since coords are non-zero
295 element_ct point(x_coord, y_coord, /*assert_on_curve=*/false);
296 point.assert_coordinates_in_field();
297
298 // Circuit should fail because x coordinate is out of field
300 }
301
302 // Test 3: Invalid y coordinate should cause circuit to fail
303 {
305 affine_element valid_point(element::random_element());
306
307 auto x_coord = element_ct::BaseField::from_witness(&builder, valid_point.x);
308 auto y_coord = element_ct::BaseField::from_witness(&builder, valid_point.y);
309
310 // Manipulate the limbs to create an invalid value
311 // Set the highest limb to a very large value that would make the total >= modulus
312 y_coord.binary_basis_limbs[3].element = field_ct::from_witness(&builder, bb::fr(uint256_t(1) << 68));
313 y_coord.binary_basis_limbs[3].maximum_value = uint256_t(1) << 68;
314
315 // Skip curve check since we're intentionally creating an invalid point
316 // Note: is_infinity is auto-detected as false since coords are non-zero
317 element_ct point(x_coord, y_coord, /*assert_on_curve=*/false);
318 point.assert_coordinates_in_field();
319
320 // Circuit should fail because y coordinate is out of field
322 }
323 }
324 }
325
327 {
329 size_t num_repetitions = 10;
330 for (size_t i = 0; i < num_repetitions; ++i) {
331 auto [input_a, a] = get_random_point(&builder, a_type);
332 auto [input_b, b] = get_random_point(&builder, b_type);
333
334 uint64_t before = builder.get_num_finalized_gates_inefficient();
335 element_ct c = a + b;
336 uint64_t after = builder.get_num_finalized_gates_inefficient();
337
338 if (i == num_repetitions - 1) {
339 benchmark_info(Builder::NAME_STRING, "Biggroup", "ADD", "Gate Count", after - before);
340 }
341
342 affine_element c_expected(element(input_a) + element(input_b));
343
344 uint256_t c_x_u256 = c.x().get_value().lo;
345 uint256_t c_y_u256 = c.y().get_value().lo;
346
347 fq c_x_result(c_x_u256);
348 fq c_y_result(c_y_u256);
349
350 EXPECT_EQ(c_x_result, c_expected.x);
351 EXPECT_EQ(c_y_result, c_expected.y);
352 }
353
355 }
356
358 {
360 size_t num_repetitions = 10;
361 for (size_t i = 0; i < num_repetitions; ++i) {
362 auto [input_a, a] = get_random_point(&builder, a_type);
363 auto [input_b, b] = get_random_point(&builder, b_type);
364
365 element_ct original_a = a;
366 a += b;
367
368 affine_element expected(element(input_a) + element(input_b));
369 uint256_t result_x = a.x().get_value().lo;
370 uint256_t result_y = a.y().get_value().lo;
371
372 EXPECT_EQ(fq(result_x), expected.x);
373 EXPECT_EQ(fq(result_y), expected.y);
374 }
376 }
377
379 {
381 size_t num_repetitions = 1;
382 for (size_t i = 0; i < num_repetitions; ++i) {
383 affine_element input_a(element::random_element());
384 affine_element input_b(element::random_element());
385 input_b.self_set_infinity();
386 element_ct a = element_ct::from_witness(&builder, input_a);
387 element_ct a_alternate = element_ct::from_witness(&builder, input_a);
388 element_ct a_negated = element_ct::from_witness(&builder, -input_a);
389 element_ct b = element_ct::from_witness(&builder, input_b);
390
391 element_ct c = a + b;
392 element_ct d = b + a;
393 element_ct e = b + b;
394 element_ct f = a + a;
395 element_ct g = a + a_alternate;
396 element_ct h = a + a_negated;
397
398 affine_element c_expected = affine_element(element(input_a) + element(input_b));
399 affine_element d_expected = affine_element(element(input_b) + element(input_a));
400 affine_element e_expected = affine_element(element(input_b) + element(input_b));
401 affine_element f_expected = affine_element(element(input_a) + element(input_a));
402 affine_element g_expected = affine_element(element(input_a) + element(input_a));
403 affine_element h_expected = affine_element(element(input_a) + element(-input_a));
404
405 EXPECT_EQ(c.get_value(), c_expected);
406 EXPECT_EQ(d.get_value(), d_expected);
407 EXPECT_EQ(e.get_value(), e_expected);
408 EXPECT_EQ(f.get_value(), f_expected);
409 EXPECT_EQ(g.get_value(), g_expected);
410 EXPECT_EQ(h.get_value(), h_expected);
411 }
412
414 }
420 {
422 size_t num_repetitions = 5;
423 for (size_t i = 0; i < num_repetitions; ++i) {
424 // Create canonical point at infinity (constant and witness cases)
425 element_ct input_a = element_ct::constant_infinity(&builder);
426 element_ct input_b = element_ct::from_witness(&builder, affine_element::infinity());
427
428 auto standard_a = input_a.get_standard_form();
429 auto standard_b = input_b.get_standard_form();
430
431 EXPECT_EQ(is_infinity(standard_a), true);
432 EXPECT_EQ(is_infinity(standard_b), true);
433
434 fq standard_a_x = standard_a.x().get_value().lo;
435 fq standard_a_y = standard_a.y().get_value().lo;
436
437 fq standard_b_x = standard_b.x().get_value().lo;
438 fq standard_b_y = standard_b.y().get_value().lo;
439
440 // Canonical infinity points should maintain (0, 0) coordinates
441 EXPECT_EQ(standard_a_x, 0);
442 EXPECT_EQ(standard_a_y, 0);
443 EXPECT_EQ(standard_b_x, 0);
444 EXPECT_EQ(standard_b_y, 0);
445 }
446
448 }
449
451 {
453 size_t num_repetitions = 10;
454 for (size_t i = 0; i < num_repetitions; ++i) {
455 auto [input_a, a] = get_random_point(&builder, a_type);
456 auto [input_b, b] = get_random_point(&builder, b_type);
457
458 element_ct c = a - b;
459
460 affine_element c_expected(element(input_a) - element(input_b));
461
462 uint256_t c_x_u256 = c.x().get_value().lo;
463 uint256_t c_y_u256 = c.y().get_value().lo;
464
465 fq c_x_result(c_x_u256);
466 fq c_y_result(c_y_u256);
467
468 EXPECT_EQ(c_x_result, c_expected.x);
469 EXPECT_EQ(c_y_result, c_expected.y);
470 }
471
473 }
474
476 {
478 size_t num_repetitions = 10;
479 for (size_t i = 0; i < num_repetitions; ++i) {
480 auto [input_a, a] = get_random_point(&builder, a_type);
481 auto [input_b, b] = get_random_point(&builder, b_type);
482
483 a -= b;
484
485 affine_element expected(element(input_a) - element(input_b));
486 uint256_t result_x = a.x().get_value().lo;
487 uint256_t result_y = a.y().get_value().lo;
488
489 EXPECT_EQ(fq(result_x), expected.x);
490 EXPECT_EQ(fq(result_y), expected.y);
491 }
493 }
494
496 {
498 size_t num_repetitions = 1;
499 for (size_t i = 0; i < num_repetitions; ++i) {
500 affine_element input_a(element::random_element());
501 affine_element input_b(element::random_element());
502 input_b.self_set_infinity();
503 element_ct a = element_ct::from_witness(&builder, input_a);
504 element_ct a_alternate = element_ct::from_witness(&builder, input_a);
505 element_ct a_negated = element_ct::from_witness(&builder, -input_a);
506 element_ct b = element_ct::from_witness(&builder, input_b);
507
508 element_ct c = a - b;
509 element_ct d = b - a;
510 element_ct e = b - b;
511 element_ct f = a - a;
512 element_ct g = a - a_alternate;
513 element_ct h = a - a_negated;
514
515 affine_element c_expected = affine_element(element(input_a) - element(input_b));
516 affine_element d_expected = affine_element(element(input_b) - element(input_a));
517 affine_element e_expected = affine_element(element(input_b) - element(input_b));
518 affine_element f_expected = affine_element(element(input_a) - element(input_a));
519 affine_element g_expected = affine_element(element(input_a) - element(input_a));
520 affine_element h_expected = affine_element(element(input_a) - element(-input_a));
521
522 EXPECT_EQ(c.get_value(), c_expected);
523 EXPECT_EQ(d.get_value(), d_expected);
524 EXPECT_EQ(e.get_value(), e_expected);
525 EXPECT_EQ(f.get_value(), f_expected);
526 EXPECT_EQ(g.get_value(), g_expected);
527 EXPECT_EQ(h.get_value(), h_expected);
528 }
529
531 }
532
535 {
537 size_t num_repetitions = 10;
538 for (size_t i = 0; i < num_repetitions; ++i) {
539 auto [input_a, a] = get_random_point(&builder, a_type);
540 auto [input_b, b] = get_random_point(&builder, b_type);
541
542 element_ct result = a.checked_unconditional_add(b);
543
544 affine_element expected(element(input_a) + element(input_b));
545 uint256_t result_x = result.x().get_value().lo;
546 uint256_t result_y = result.y().get_value().lo;
547
548 EXPECT_EQ(fq(result_x), expected.x);
549 EXPECT_EQ(fq(result_y), expected.y);
550 }
552 }
553
556 {
558 size_t num_repetitions = 10;
559 for (size_t i = 0; i < num_repetitions; ++i) {
560 auto [input_a, a] = get_random_point(&builder, a_type);
561 auto [input_b, b] = get_random_point(&builder, b_type);
562
563 element_ct result = a.checked_unconditional_subtract(b);
564
565 affine_element expected(element(input_a) - element(input_b));
566 uint256_t result_x = result.x().get_value().lo;
567 uint256_t result_y = result.y().get_value().lo;
568
569 EXPECT_EQ(fq(result_x), expected.x);
570 EXPECT_EQ(fq(result_y), expected.y);
571 }
573 }
574
577 {
579 size_t num_repetitions = 10;
580 for (size_t i = 0; i < num_repetitions; ++i) {
581 const auto [input_a, a] = get_random_point(&builder, a_type);
582 const auto [input_b, b] = get_random_point(&builder, b_type);
583
584 // Since unchecked_unconditional_add_sub is private in biggroup, we test it via the element_test_accessor
586
587 affine_element expected_sum(element(input_a) + element(input_b));
588 affine_element expected_diff(element(input_a) - element(input_b));
589
590 uint256_t sum_x = sum.x().get_value().lo;
591 uint256_t sum_y = sum.y().get_value().lo;
592 uint256_t diff_x = diff.x().get_value().lo;
593 uint256_t diff_y = diff.y().get_value().lo;
594
595 EXPECT_EQ(fq(sum_x), expected_sum.x);
596 EXPECT_EQ(fq(sum_y), expected_sum.y);
597 EXPECT_EQ(fq(diff_x), expected_diff.x);
598 EXPECT_EQ(fq(diff_y), expected_diff.y);
599 }
601 }
602
604 {
606 size_t num_repetitions = 10;
607 for (size_t i = 0; i < num_repetitions; ++i) {
608 auto [input_a, a] = get_random_point(&builder, a_type);
609
610 element_ct c = a.dbl();
611
612 affine_element c_expected(element(input_a).dbl());
613
614 uint256_t c_x_u256 = c.x().get_value().lo;
615 uint256_t c_y_u256 = c.y().get_value().lo;
616
617 fq c_x_result(c_x_u256);
618 fq c_y_result(c_y_u256);
619
620 EXPECT_EQ(c_x_result, c_expected.x);
621 EXPECT_EQ(c_y_result, c_expected.y);
622 }
624 }
625
627 {
629 {
630 // Case 1: Doubling point at infinity should return point at infinity
631 affine_element input_infinity(element::random_element());
632 input_infinity.self_set_infinity();
633 element_ct a_infinity = element_ct::from_witness(&builder, input_infinity);
634
635 element_ct result_infinity = a_infinity.dbl();
636
637 // Result should be point at infinity
638 EXPECT_TRUE(is_infinity(result_infinity));
639 }
640 {
641 // Case 2: Doubling a normal point should not result in infinity
642 affine_element input_normal(element::random_element());
643 element_ct a_normal = element_ct::from_witness(&builder, input_normal);
644
645 element_ct result_normal = a_normal.dbl();
646
647 // Result should not be point at infinity (with overwhelming probability)
648 EXPECT_FALSE(is_infinity(result_normal));
649
650 // Verify correctness
651 affine_element expected_normal(element(input_normal).dbl());
652 uint256_t result_x = result_normal.x().get_value().lo;
653 uint256_t result_y = result_normal.y().get_value().lo;
654 fq expected_x(result_x);
655 fq expected_y(result_y);
656 EXPECT_EQ(expected_x, expected_normal.x);
657 EXPECT_EQ(expected_y, expected_normal.y);
658 }
660 }
661
663 {
665
666 // For bn254 curve: y^2 = x^3 + 3
667 // We need a point where y = 0, which means x^3 = -3
668 // For most curves, there may not be a rational point with y = 0
669 // So we test the logic by creating a witness point with y = 0 explicitly
670 // Even if it's not on the curve, we can test the doubling logic
671 affine_element test_point(element::random_element());
672
673 // Create a point with y = 0 (may not be on curve, but tests the edge case)
674 auto x_coord = element_ct::BaseField::from_witness(&builder, test_point.x);
675 auto y_coord = element_ct::BaseField::from_witness(&builder, fq(0));
676 // Skip curve check since we're intentionally creating an invalid point to test edge case
677 // Note: is_infinity is auto-detected as false since x coordinate is non-zero
678 element_ct a(x_coord, y_coord, /*assert_on_curve=*/false);
679
680 // With the new assertion, attempting to double a point with y = 0 should throw
681 // because for valid curves like bn254, y = 0 cannot occur on the curve
682 EXPECT_THROW_WITH_MESSAGE(a.dbl(), "Attempting to dbl a point with y = 0, not allowed.");
683 }
684
686 {
687 // Test that P + P equals P.dbl()
689 size_t num_repetitions = 5;
690 for (size_t i = 0; i < num_repetitions; ++i) {
691 auto [input_a, a] = get_random_point(&builder, InputType::WITNESS);
692
693 element_ct sum = a + a;
694 element_ct doubled = a.dbl();
695
696 // Results should match
697 uint256_t sum_x = sum.x().get_value().lo;
698 uint256_t sum_y = sum.y().get_value().lo;
699 uint256_t dbl_x = doubled.x().get_value().lo;
700 uint256_t dbl_y = doubled.y().get_value().lo;
701
702 EXPECT_EQ(fq(sum_x), fq(dbl_x));
703 EXPECT_EQ(fq(sum_y), fq(dbl_y));
704 EXPECT_EQ(is_infinity(sum), is_infinity(doubled));
705 }
707 }
708
710 {
711 // Test that P - (-P) equals 2P
713 size_t num_repetitions = 5;
714 for (size_t i = 0; i < num_repetitions; ++i) {
715 auto [input_a, a] = get_random_point(&builder, InputType::WITNESS);
716
717 element_ct neg_a = -a;
718 element_ct result = a - neg_a;
719 element_ct expected = a.dbl();
720
721 // P - (-P) = P + P = 2P
722 uint256_t result_x = result.x().get_value().lo;
723 uint256_t result_y = result.y().get_value().lo;
724 uint256_t expected_x = expected.x().get_value().lo;
725 uint256_t expected_y = expected.y().get_value().lo;
726
727 EXPECT_EQ(fq(result_x), fq(expected_x));
728 EXPECT_EQ(fq(result_y), fq(expected_y));
729 }
731 }
732
736 {
738 size_t num_repetitions = 10;
739 for (size_t i = 0; i < num_repetitions; ++i) {
740
741 auto [input_a, a] = get_random_point(&builder, a_type);
742 auto [input_b, b] = get_random_point(&builder, b_type);
743 auto [input_c, c] = get_random_point(&builder, c_type);
744
745 auto acc = element_ct::chain_add_start(a, b);
746 auto acc_out = element_ct::chain_add(c, acc);
747 element_ct result = element_ct::chain_add_end(acc_out);
748
749 // Verify result
750 affine_element expected(element(input_a) + element(input_b) + element(input_c));
751 uint256_t result_x = result.x().get_value().lo;
752 uint256_t result_y = result.y().get_value().lo;
753 EXPECT_EQ(fq(result_x), expected.x);
754 EXPECT_EQ(fq(result_y), expected.y);
755
756 // Check intermediate values
757 auto lambda_prev = (input_b.y - input_a.y) / (input_b.x - input_a.x);
758 auto x3_prev = lambda_prev * lambda_prev - input_b.x - input_a.x;
759 auto y3_prev = lambda_prev * (input_a.x - x3_prev) - input_a.y;
760 auto lambda = (y3_prev - input_c.y) / (x3_prev - input_c.x);
761 auto x3 = lambda * lambda - x3_prev - input_c.x;
762
763 uint256_t x3_u256 = acc_out.x3_prev.get_value().lo;
764 uint256_t lambda_u256 = acc_out.lambda_prev.get_value().lo;
765
766 fq x3_result(x3_u256);
767 fq lambda_result(lambda_u256);
768
769 EXPECT_EQ(x3_result, x3);
770 EXPECT_EQ(lambda_result, lambda);
771 }
772
774 }
775
777 {
779 size_t num_repetitions = 10;
780 for (size_t i = 0; i < num_repetitions; ++i) {
781 affine_element acc_small(element::random_element());
782 element_ct acc_big = element_ct::from_witness(&builder, acc_small);
783
785 for (size_t j = 0; j < i; ++j) {
786 affine_element add_1_small_0(element::random_element());
787 element_ct add_1_big_0 = element_ct::from_witness(&builder, add_1_small_0);
788 affine_element add_2_small_0(element::random_element());
789 element_ct add_2_big_0 = element_ct::from_witness(&builder, add_2_small_0);
790 typename element_ct::chain_add_accumulator add_1 =
791 element_ct::chain_add_start(add_1_big_0, add_2_big_0);
792 to_add.emplace_back(add_1);
793 }
794 acc_big.multiple_montgomery_ladder(to_add);
795 }
796
798 }
799
801 {
803 size_t num_repetitions = 10;
804 for (size_t i = 0; i < num_repetitions; ++i) {
805 auto [input_a, a] = get_random_point(&builder, point_type);
806
807 element_ct normalized = a.normalize();
808
809 // Normalized should equal the original
810 uint256_t x_before = a.x().get_value().lo;
811 uint256_t y_before = a.y().get_value().lo;
812 uint256_t x_after = normalized.x().get_value().lo;
813 uint256_t y_after = normalized.y().get_value().lo;
814
815 EXPECT_EQ(fq(x_before), fq(x_after));
816 EXPECT_EQ(fq(y_before), fq(y_after));
817 }
819 }
820
821 static void test_reduce(InputType point_type = InputType::WITNESS)
822 {
824 size_t num_repetitions = 10;
825 for (size_t i = 0; i < num_repetitions; ++i) {
826 auto [input_a, a] = get_random_point(&builder, point_type);
827
828 element_ct reduced = a.reduce();
829
830 // Reduced should equal the original
831 uint256_t x_before = a.x().get_value().lo;
832 uint256_t y_before = a.y().get_value().lo;
833 uint256_t x_after = reduced.x().get_value().lo;
834 uint256_t y_after = reduced.y().get_value().lo;
835
836 EXPECT_EQ(fq(x_before), fq(x_after));
837 EXPECT_EQ(fq(y_before), fq(y_after));
838 }
840 }
841
843 {
845 auto [input_a, a] = get_random_point(&builder, a_type);
846
847 element_ct neg_a = -a;
848
849 affine_element expected = affine_element(-element(input_a));
850 uint512_t neg_x_u512 = uint512_t(neg_a.x().get_value()) % uint512_t(fq::modulus);
851 uint512_t neg_y_u512 = uint512_t(neg_a.y().get_value()) % uint512_t(fq::modulus);
852 uint256_t neg_x = neg_x_u512.lo;
853 uint256_t neg_y = neg_y_u512.lo;
854
855 EXPECT_EQ(fq(neg_x), expected.x);
856 EXPECT_EQ(fq(neg_y), expected.y);
857
859 }
860
862 InputType predicate_type = InputType::WITNESS)
863 {
865 size_t num_repetitions = 10;
866 for (size_t i = 0; i < num_repetitions; ++i) {
867 // Get random point
868 auto [input_a, a] = get_random_point(&builder, point_type);
869
870 // Get random predicate
871 bool predicate_value = (engine.get_random_uint8() % 2) != 0;
872 bool_ct predicate = (predicate_type == InputType::WITNESS) ? bool_ct(witness_ct(&builder, predicate_value))
873 : bool_ct(predicate_value);
874
875 element_ct c = a.conditional_negate(predicate);
876
877 affine_element c_expected = predicate_value ? affine_element(-element(input_a)) : input_a;
878 EXPECT_EQ(c.get_value(), c_expected);
879 }
881 }
882
885 InputType predicate_type = InputType::WITNESS)
886 {
888 size_t num_repetitions = 10;
889 for (size_t i = 0; i < num_repetitions; ++i) {
890 auto [input_a, a] = get_random_point(&builder, a_type);
891 auto [input_b, b] = get_random_point(&builder, b_type);
892
893 bool predicate_value = (engine.get_random_uint8() % 2) != 0;
894 bool_ct predicate = (predicate_type == InputType::WITNESS) ? bool_ct(witness_ct(&builder, predicate_value))
895 : bool_ct(predicate_value);
896
897 element_ct c = a.conditional_select(b, predicate);
898
899 affine_element c_expected = predicate_value ? input_b : input_a;
900 EXPECT_EQ(c.get_value(), c_expected);
901 }
903 }
904
906 {
907 // Case 1: Should pass because the points are identical
908 {
910 size_t num_repetitions = 10;
911 for (size_t i = 0; i < num_repetitions; ++i) {
912 affine_element input_a(element::random_element());
913 element_ct a = element_ct::from_witness(&builder, input_a);
914 element_ct b = element_ct::from_witness(&builder, input_a);
915
916 a.incomplete_assert_equal(b, "elements don't match");
917 }
919 }
920 // Case 2: Should pass because the points are identical and at infinity (canonical representation)
921 {
923 size_t num_repetitions = 10;
924 for (size_t i = 0; i < num_repetitions; ++i) {
925 affine_element input_a(element::random_element());
926 input_a.self_set_infinity();
927 element_ct a = element_ct::from_witness(&builder, input_a);
928 element_ct b = element_ct::from_witness(&builder, input_a);
929
930 a.incomplete_assert_equal(b, "elements don't match");
931 }
933 }
934 // Case 3: Self-assertion (point equals itself)
935 {
937 affine_element input(element::random_element());
938 element_ct a = element_ct::from_witness(&builder, input);
939
940 a.incomplete_assert_equal(a, "self assertion test");
941
943 }
944 }
945
947 {
948 // Case 1: Should fail because the points are different
949 {
951 affine_element input_a(element::random_element());
952 affine_element input_b(element::random_element());
953 // Ensure inputs are different
954 while (input_a == input_b) {
955 input_b = element::random_element();
956 }
957 element_ct a = element_ct::from_witness(&builder, input_a);
958 element_ct b = element_ct::from_witness(&builder, input_b);
959
960 a.incomplete_assert_equal(b, "elements don't match");
961
962 // Circuit should fail (Circuit checker doesn't fail because it doesn't actually check copy constraints,
963 // it only checks gate constraints)
964 EXPECT_EQ(builder.failed(), true);
965 EXPECT_EQ(builder.err(), "elements don't match (x coordinate)");
966 }
967 // Case 2: Should fail because the points have same x but different y
968 {
970 affine_element input_a(element::random_element());
971
972 // Create a point with the same x coordinate but different y
973 // For an elliptic curve y^2 = x^3 + ax + b, if (x, y) is on the curve, then (x, -y) is also on the
974 // curve
975 affine_element input_b = input_a;
976 input_b.y = -input_a.y; // Negate y to get a different point with same x
977
978 // Construct the circuit elements with same x but different y
979 auto x_coord = element_ct::BaseField::from_witness(&builder, input_a.x);
980 auto y_coord_a = element_ct::BaseField::from_witness(&builder, input_a.y);
981 auto y_coord_b = element_ct::BaseField::from_witness(&builder, input_b.y);
982
983 // Note: is_infinity is auto-detected as false since coordinates are non-zero
984 element_ct a(x_coord, y_coord_a);
985 element_ct b(x_coord, y_coord_b);
986
987 a.incomplete_assert_equal(b, "elements don't match");
988
989 // Circuit should fail with y coordinate error
990 EXPECT_EQ(builder.failed(), true);
991 EXPECT_EQ(builder.err(), "elements don't match (y coordinate)");
992 }
993 // Case 3: Infinity flag mismatch (one point at infinity, one not)
994 {
996 affine_element input_a(element::random_element());
997 affine_element input_b(element::random_element());
998
999 input_a.self_set_infinity();
1000 element_ct a = element_ct::from_witness(&builder, input_a); // at infinity
1001 element_ct b = element_ct::from_witness(&builder, input_b); // not at infinity
1002
1003 a.incomplete_assert_equal(b, "infinity flag mismatch test");
1004
1005 EXPECT_EQ(builder.failed(), true);
1006 if constexpr (HasGoblinBuilder<TestType>) {
1007 // Goblin has no infinity flag; (0,0) coords differ from b's coords
1008 EXPECT_EQ(builder.err(), "infinity flag mismatch test (x coordinate)");
1009 } else {
1010 EXPECT_EQ(builder.err(), "infinity flag mismatch test (infinity flag)");
1011 }
1012 }
1013 }
1014
1015 static void test_compute_naf()
1016 {
1018 size_t max_num_bits = 254;
1019 for (size_t length = 2; length < max_num_bits; length += 1) {
1020
1021 fr scalar_val;
1022
1023 uint256_t scalar_raw = engine.get_random_uint256();
1024 scalar_raw = scalar_raw >> (256 - length);
1025
1026 scalar_val = fr(scalar_raw);
1027
1028 // We test non-zero scalars here
1029 if (scalar_val == fr(0)) {
1030 scalar_val += 1;
1031 };
1032 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1033 auto naf = element_ct::compute_naf(scalar, length);
1034
1035 // scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1036 fr reconstructed_val(0);
1037 for (size_t i = 0; i < length; i++) {
1038 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1039 };
1040 reconstructed_val -= fr(naf[length].get_value());
1041 EXPECT_EQ(scalar_val, reconstructed_val);
1042 }
1043
1045 }
1046
1048 {
1050 size_t length = fr::modulus.get_msb() + 1;
1051
1052 // Our algorithm for input 0 outputs the NAF representation of r (the field modulus)
1053 fr scalar_val(0);
1054
1055 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1056 auto naf = element_ct::compute_naf(scalar, length);
1057
1058 // scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1059 fr reconstructed_val(0);
1060 uint256_t reconstructed_u256(0);
1061 for (size_t i = 0; i < length; i++) {
1062 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1063 reconstructed_u256 +=
1064 (uint256_t(1) - uint256_t(2) * uint256_t(naf[i].get_value())) * (uint256_t(1) << (length - 1 - i));
1065 };
1066 reconstructed_val -= fr(naf[length].get_value());
1067 EXPECT_EQ(scalar_val, reconstructed_val);
1068 EXPECT_EQ(reconstructed_u256, uint256_t(fr::modulus));
1069
1071 }
1072
1074 {
1076
1077 // Create a scalar that is even (skew=1) and has least-significant 2L bits all 0 (L=68, 2L=136)
1078 // This causes overflow in negative_lo = skew + sum_{i=0}^{135} a'_{i+1} * 2^i = 1 + (2^136 - 1) = 2^136
1079 //
1080 // Scalar chosen such that least significant 136 bits are zero:
1081 fr scalar_native = fr::random_element();
1082 uint256_t scalar_raw = uint256_t(scalar_native);
1083 scalar_raw = (scalar_raw >> 136) << 136;
1084 fr scalar_val = fr(scalar_raw);
1085 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1086 scalar.set_origin_tag(submitted_value_origin_tag);
1087
1088 // Compute NAF with full field size
1089 const size_t length = fr::modulus.get_msb() + 1;
1090
1091 // This should not overflow with the fix in place
1092 auto naf = element_ct::compute_naf(scalar, length);
1093
1094 // Verify NAF correctness
1095 for (const auto& bit : naf) {
1096 EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag);
1097 }
1098
1099 // Reconstruct scalar from NAF: scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1100 fr reconstructed_val(0);
1101 for (size_t i = 0; i < length; i++) {
1102 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1103 }
1104 reconstructed_val -= fr(naf[length].get_value());
1105
1106 EXPECT_EQ(scalar_val, reconstructed_val);
1108 }
1109
1110 static void test_mul(InputType scalar_type = InputType::WITNESS, InputType point_type = InputType::WITNESS)
1111 {
1113 size_t num_repetitions = 1;
1114 for (size_t i = 0; i < num_repetitions; ++i) {
1115 auto [input, P] = get_random_point(&builder, point_type);
1116 auto [scalar, x] = get_random_scalar(&builder, scalar_type, /*even*/ true);
1117
1118 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1119 element_ct c = P * x;
1120 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1121 affine_element c_expected(element(input) * scalar);
1122
1123 fq c_x_result(c.x().get_value().lo);
1124 fq c_y_result(c.y().get_value().lo);
1125
1126 EXPECT_EQ(c_x_result, c_expected.x);
1127 EXPECT_EQ(c_y_result, c_expected.y);
1128 }
1129
1131 }
1132
1134 InputType point_type = InputType::WITNESS)
1135 {
1137
1138 const auto run_mul_and_check = [&](element_ct& P, scalar_ct& x, const affine_element& expected) {
1139 // Perform multiplication
1140 element_ct result = P * x;
1141
1142 // Check if result is infinity
1143 bool result_is_inf = is_infinity(result);
1144 bool expected_is_inf = expected.is_point_at_infinity();
1145
1146 EXPECT_EQ(result_is_inf, expected_is_inf);
1147
1148 // If not infinity, check if the coordinates match
1149 if (!expected_is_inf) {
1150 uint256_t result_x = result.x().get_value().lo;
1151 uint256_t result_y = result.y().get_value().lo;
1152
1153 EXPECT_EQ(fq(result_x), expected.x);
1154 EXPECT_EQ(fq(result_y), expected.y);
1155 }
1156 };
1157
1158 // Case 1: P * 0 = ∞
1159 {
1160 auto [input, P] = get_random_point(&builder, point_type);
1161 scalar_ct x = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, fr(0))
1162 : scalar_ct(&builder, fr(0));
1163 affine_element expected_infinity = affine_element(element::infinity());
1164 run_mul_and_check(P, x, expected_infinity);
1165 }
1166 // Case 2: (∞) * k = ∞
1167 {
1168 auto [input, P] = get_random_point(&builder, point_type);
1169 if (point_type == InputType::CONSTANT) {
1170 P = element_ct::constant_infinity(&builder);
1171 } else {
1172 input.self_set_infinity();
1173 P = element_ct::from_witness(&builder, input);
1174 }
1175
1176 auto [scalar, x] = get_random_scalar(&builder, scalar_type, /*even*/ true);
1177 affine_element expected_infinity = affine_element(element::infinity());
1178 run_mul_and_check(P, x, expected_infinity);
1179 }
1180 // Case 3: P * 1 = P
1181 {
1182 auto [input, P] = get_random_point(&builder, point_type);
1183 scalar_ct one = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, fr(1))
1184 : scalar_ct(&builder, fr(1));
1185 run_mul_and_check(P, one, input);
1186 }
1187 // Case 4: P * (-1) = -P
1188 {
1189 auto [input, P] = get_random_point(&builder, point_type);
1190 fr neg_one = -fr(1);
1191 scalar_ct neg_one_ct = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, neg_one)
1192 : scalar_ct(&builder, neg_one);
1193 affine_element expected = affine_element(-element(input));
1194 run_mul_and_check(P, neg_one_ct, expected);
1195 }
1197 }
1198
1199 // Test short scalar mul with variable bit lengths.
1201 {
1203
1204 std::vector<size_t> test_lengths = { 2, 3, 10, 11, 31, 32, 63, 64, 127, 128, 252, 253 };
1205
1206 for (size_t i : test_lengths) {
1207 affine_element input(element::random_element());
1208 // Get a random 256 integer
1209 uint256_t scalar_raw = engine.get_random_uint256();
1210 // Produce a length =< i scalar.
1211 scalar_raw = scalar_raw >> (256 - i);
1212 fr scalar = fr(scalar_raw);
1213
1214 // Avoid multiplication by 0 that may occur when `i` is small
1215 if (scalar == fr(0)) {
1216 scalar += 1;
1217 };
1218
1219 element_ct P = element_ct::from_witness(&builder, input);
1220 scalar_ct x = scalar_ct::from_witness(&builder, scalar);
1221
1222 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1223 // Multiply using specified scalar length
1224 element_ct c = P.scalar_mul(x, i);
1225 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1226 affine_element c_expected(element(input) * scalar);
1227
1228 fq c_x_result(c.x().get_value().lo);
1229 fq c_y_result(c.y().get_value().lo);
1230
1231 EXPECT_EQ(c_x_result, c_expected.x);
1232
1233 EXPECT_EQ(c_y_result, c_expected.y);
1234 }
1235
1237 }
1238
1240 {
1241 // We check that a point at infinity preserves `is_point_at_infinity()` flag after being multiplied against
1242 // a short scalar and also check that the number of gates in this case is more than the number of gates
1243 // spent on a finite point.
1244
1245 // Populate test points.
1246 std::vector<element> points(2);
1247
1248 points[0] = element::infinity();
1249 points[1] = element::random_element();
1250 // Containter for gate counts.
1251 std::vector<size_t> gates(2);
1252
1253 // We initialize this flag as `true`, because the first result is expected to be the point at infinity.
1254 bool expect_infinity = true;
1255
1256 for (auto [point, num_gates] : zip_view(points, gates)) {
1258
1259 const size_t max_num_bits = 128;
1260 // Get a random 256-bit integer
1261 uint256_t scalar_raw = engine.get_random_uint256();
1262 // Produce a length =< max_num_bits scalar.
1263 scalar_raw = scalar_raw >> (256 - max_num_bits);
1264 fr scalar = fr(scalar_raw);
1265
1266 element_ct P = element_ct::from_witness(&builder, point);
1267 scalar_ct x = scalar_ct::from_witness(&builder, scalar);
1268
1269 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1270 element_ct c = P.scalar_mul(x, max_num_bits);
1271 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1272 num_gates = builder.get_num_finalized_gates_inefficient();
1273
1274 EXPECT_EQ(is_infinity(c), expect_infinity);
1276 // The second point is finite, hence we flip the flag
1277 expect_infinity = false;
1278 }
1279 // Check that the numbers of gates are greater when multiplying by point at infinity,
1280 // because we transform (s * ∞) into (0 * G), and NAF representation of 0 ≡ NAF(r) which is 254 bits long.
1281 EXPECT_GT(gates[0], gates[1]);
1282 }
1283
1284 static void test_twin_mul()
1285 {
1287 size_t num_repetitions = 1;
1288 for (size_t i = 0; i < num_repetitions; ++i) {
1289 affine_element input_a(element::random_element());
1290 affine_element input_b(element::random_element());
1291 fr scalar_a(fr::random_element());
1292 fr scalar_b(fr::random_element());
1293 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
1294 scalar_a -= fr(1); // skew bit is 1
1295 }
1296 if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) {
1297 scalar_b += fr(1); // skew bit is 0
1298 }
1299 element_ct P_a = element_ct::from_witness(&builder, input_a);
1300 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
1301 element_ct P_b = element_ct::from_witness(&builder, input_b);
1302 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b);
1303
1304 element_ct c = element_ct::batch_mul({ P_a, P_b }, { x_a, x_b });
1305
1306 element input_c = (element(input_a) * scalar_a);
1307 element input_d = (element(input_b) * scalar_b);
1308 affine_element expected(input_c + input_d);
1309 fq c_x_result(c.x().get_value().lo);
1310 fq c_y_result(c.y().get_value().lo);
1311
1312 EXPECT_EQ(c_x_result, expected.x);
1313 EXPECT_EQ(c_y_result, expected.y);
1314 }
1316 }
1317
1319 {
1321 size_t num_repetitions = 1;
1322 for (size_t i = 0; i < num_repetitions; ++i) {
1323 affine_element input_a(element::random_element());
1324 affine_element input_b(element::random_element());
1325 input_b.self_set_infinity();
1326
1327 // Get two 128-bit scalars
1328 const size_t max_num_bits = 128;
1329 uint256_t scalar_raw_a = engine.get_random_uint256();
1330 scalar_raw_a = scalar_raw_a >> (256 - max_num_bits);
1331 fr scalar_a = fr(scalar_raw_a);
1332
1333 uint256_t scalar_raw_b = engine.get_random_uint256();
1334 scalar_raw_b = scalar_raw_b >> (256 - max_num_bits);
1335 fr scalar_b = fr(scalar_raw_b);
1336
1337 element_ct P_a = element_ct::from_witness(&builder, input_a); // A
1338 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a); // s_1 (128 bits)
1339 element_ct P_b = element_ct::from_witness(&builder, input_b); // ∞
1340 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b); // s_2 (128 bits)
1341
1342 element_ct c = element_ct::batch_mul({ P_a, P_b }, { x_a, x_b }, 128);
1343
1344 element input_c = (element(input_a) * scalar_a);
1345 element input_d = (element(input_b) * scalar_b);
1346 affine_element expected(input_c + input_d);
1347 fq c_x_result(c.x().get_value().lo);
1348 fq c_y_result(c.y().get_value().lo);
1349
1350 EXPECT_EQ(c_x_result, expected.x);
1351 EXPECT_EQ(c_y_result, expected.y);
1352 }
1354 }
1355
1357 {
1359 affine_element input_P(element::random_element());
1360
1361 affine_element input_P_a = affine_element(element(input_P) + element(input_P)); // 2P
1362 affine_element input_P_b = affine_element(element(input_P_a) + element(input_P)); // 3P
1363 affine_element input_P_c = affine_element(element(input_P_a) + element(input_P_b)); // 5P
1364 std::vector<affine_element> input_points = { input_P_a, input_P_b, input_P_c };
1365
1366 // Choose scalars such that their NAF representations are:
1367 // skew msd lsd
1368 // a: 0 [+1, +1, -1, +1] = -0 + 2^3 + 2^2 - 2^1 + 2^0 = 11
1369 // b: 1 [+1, +1, +1, +1] = -1 + 2^3 + 2^2 + 2^1 + 2^0 = 14
1370 // c: 1 [+1, -1, +1, +1] = -1 + 2^3 - 2^2 + 2^1 + 2^0 = 6
1371 fr scalar_a(11);
1372 fr scalar_b(14);
1373 fr scalar_c(6);
1374 std::vector<fr> input_scalars = { scalar_a, scalar_b, scalar_c };
1375
1376 std::vector<scalar_ct> scalars;
1378 for (size_t i = 0; i < 3; ++i) {
1379 const element_ct point = element_ct::from_witness(&builder, input_points[i]);
1380 const scalar_ct scalar = scalar_ct::from_witness(&builder, input_scalars[i]);
1381 scalars.emplace_back(scalar);
1382 points.emplace_back(point);
1383 }
1384
1385 // If with_edgecases = true, should handle linearly dependent points correctly
1386 // (offset generator is now a free witness sampled inside batch_mul)
1387 element_ct c = element_ct::batch_mul(points,
1388 scalars,
1389 /*max_num_bits*/ 128,
1390 /*with_edgecases*/ true);
1391 element input_e = (element(input_P_a) * scalar_a);
1392 element input_f = (element(input_P_b) * scalar_b);
1393 element input_g = (element(input_P_c) * scalar_c);
1394
1395 affine_element expected(input_e + input_f + input_g);
1396 fq c_x_result(c.x().get_value().lo);
1397 fq c_y_result(c.y().get_value().lo);
1398
1399 EXPECT_EQ(c_x_result, expected.x);
1400 EXPECT_EQ(c_y_result, expected.y);
1401
1403 }
1404
1406 {
1408 affine_element input_P(element::random_element());
1409
1410 affine_element input_P_a = affine_element(element(input_P) + element(input_P)); // 2P
1411 affine_element input_P_b = affine_element(element(input_P_a) + element(input_P)); // 3P
1412 affine_element input_P_c = affine_element(element(input_P_a) + element(input_P_b)); // 5P
1413 std::vector<affine_element> input_points = { input_P_a, input_P_b, input_P_c };
1414
1415 // Choose scalars similar to the previous test
1416 fr scalar_a(11);
1417 fr scalar_b(14);
1418 fr scalar_c(6);
1419 std::vector<fr> input_scalars = { scalar_a, scalar_b, scalar_c };
1420
1421 std::vector<scalar_ct> scalars;
1423 for (size_t i = 0; i < 3; ++i) {
1424 const element_ct point = element_ct::from_witness(&builder, input_points[i]);
1425 points.emplace_back(point);
1426
1427 const scalar_ct scalar = scalar_ct::from_witness(&builder, input_scalars[i]);
1428 scalars.emplace_back(scalar);
1429 }
1430
1431 // with_edgecases = false should fail due to linearly dependent points
1432 // This will fail only while using ultra builder
1433 element_ct::batch_mul(points, scalars, /*max_num_bits*/ 4, /*with_edgecases*/ false);
1434
1436 EXPECT_EQ(builder.err(), "bigfield: prime limb diff is zero, but expected non-zero");
1437 }
1438
1439 static void test_one()
1440 {
1442 size_t num_repetitions = 1;
1443 for (size_t i = 0; i < num_repetitions; ++i) {
1444 fr scalar_a(fr::random_element());
1445 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
1446 scalar_a -= fr(1); // skew bit is 1
1447 }
1448 element_ct P_a = element_ct::one(&builder);
1449 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
1450 element_ct c = P_a * x_a;
1451
1452 affine_element expected(g1::one * scalar_a);
1453 fq c_x_result(c.x().get_value().lo);
1454 fq c_y_result(c.y().get_value().lo);
1455
1456 EXPECT_EQ(c_x_result, expected.x);
1457 EXPECT_EQ(c_y_result, expected.y);
1458 }
1459
1461 }
1462
1463 // Overload: defaults to all WITNESS types for given num_points
1464 static void test_helper_batch_mul(size_t num_points,
1465 const bool short_scalars = false,
1466 const bool with_edgecases = false)
1467 {
1468 std::vector<InputType> point_types(num_points, InputType::WITNESS);
1469 std::vector<InputType> scalar_types(num_points, InputType::WITNESS);
1470 test_helper_batch_mul(point_types, scalar_types, short_scalars, with_edgecases);
1471 }
1472
1474 std::vector<InputType> scalar_types,
1475 const bool short_scalars = false,
1476 const bool with_edgecases = false)
1477 {
1479
1480 const size_t num_points = point_types.size();
1482 std::vector<fr> scalars;
1483 std::vector<element_ct> circuit_points;
1484 std::vector<scalar_ct> circuit_scalars;
1485
1486 for (size_t i = 0; i < num_points; ++i) {
1487 // Generate scalars
1488 if (short_scalars) {
1489 auto [input_scalar, x] = get_random_short_scalar(&builder, scalar_types[i], /*num_bits*/ 128);
1490 scalars.push_back(input_scalar);
1491 circuit_scalars.push_back(x);
1492 } else {
1493 auto [input_scalar, x] = get_random_scalar(&builder, scalar_types[i], /*even*/ true);
1494 scalars.push_back(input_scalar);
1495 circuit_scalars.push_back(x);
1496 }
1497
1498 // Generate points
1499 auto [input_point, P] = get_random_point(&builder, point_types[i]);
1500 points.push_back(input_point);
1501 circuit_points.push_back(P);
1502 }
1503
1504 element_ct result_point =
1505 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, with_edgecases);
1506
1507 element expected_point = g1::one;
1508 expected_point.self_set_infinity();
1509 for (size_t i = 0; i < num_points; ++i) {
1510 expected_point += (element(points[i]) * scalars[i]);
1511 }
1512
1513 expected_point = expected_point.normalize();
1514 fq result_x(result_point.x().get_value().lo);
1515 fq result_y(result_point.y().get_value().lo);
1516
1517 EXPECT_EQ(result_x, expected_point.x);
1518 EXPECT_EQ(result_y, expected_point.y);
1519
1521 }
1522
1523 static void test_batch_mul()
1524 {
1525 const size_t num_points = 5;
1528 std::vector<fr> scalars;
1529 for (size_t i = 0; i < num_points; ++i) {
1530 points.push_back(affine_element(element::random_element()));
1531 scalars.push_back(fr::random_element());
1532 }
1533
1534 std::vector<element_ct> circuit_points;
1535 std::vector<scalar_ct> circuit_scalars;
1536 for (size_t i = 0; i < num_points; ++i) {
1537 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1538 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1539 }
1540
1541 element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars);
1542
1543 element expected_point = g1::one;
1544 expected_point.self_set_infinity();
1545 for (size_t i = 0; i < num_points; ++i) {
1546 expected_point += (element(points[i]) * scalars[i]);
1547 }
1548
1549 expected_point = expected_point.normalize();
1550 fq result_x(result_point.x().get_value().lo);
1551 fq result_y(result_point.y().get_value().lo);
1552
1553 EXPECT_EQ(result_x, expected_point.x);
1554 EXPECT_EQ(result_y, expected_point.y);
1555
1557 }
1558
1560 {
1561 const size_t num_points = 5;
1564 std::vector<fr> scalars;
1565 for (size_t i = 0; i < num_points; ++i) {
1566 points.push_back(affine_element(element::random_element()));
1567 scalars.push_back(fr::random_element());
1568 }
1569
1570 std::vector<element_ct> circuit_points;
1571 std::vector<scalar_ct> circuit_scalars;
1572 for (size_t i = 0; i < num_points; ++i) {
1573 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1574 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1575 }
1576
1577 element_ct result_point2 =
1578 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1579
1580 element expected_point = g1::one;
1581 expected_point.self_set_infinity();
1582 for (size_t i = 0; i < num_points; ++i) {
1583 expected_point += (element(points[i]) * scalars[i]);
1584 }
1585
1586 expected_point = expected_point.normalize();
1587
1588 fq result2_x(result_point2.x().get_value().lo);
1589 fq result2_y(result_point2.y().get_value().lo);
1590
1591 EXPECT_EQ(result2_x, expected_point.x);
1592 EXPECT_EQ(result2_y, expected_point.y);
1593
1595 }
1596
1598 {
1599 const auto test_repeated_points = [](const uint32_t num_points) {
1600 // batch P + ... + P = m*P
1601 info("num points: ", num_points);
1603 std::vector<fr> scalars;
1604 for (size_t idx = 0; idx < num_points; idx++) {
1605 points.push_back(affine_element::one());
1606 scalars.push_back(1);
1607 }
1608
1610 ASSERT_EQ(points.size(), scalars.size());
1611
1612 std::vector<element_ct> circuit_points;
1613 std::vector<scalar_ct> circuit_scalars;
1614 for (size_t i = 0; i < num_points; ++i) {
1615 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1616 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1617 }
1618 element_ct result_point =
1619 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1620
1621 auto expected_point = element::infinity();
1622 for (const auto& point : points) {
1623 expected_point += point;
1624 }
1625 expected_point = expected_point.normalize();
1626
1627 fq result_x(result_point.x().get_value().lo);
1628 fq result_y(result_point.y().get_value().lo);
1629
1630 EXPECT_EQ(result_x, expected_point.x);
1631 EXPECT_EQ(result_y, expected_point.y);
1632
1634 };
1635 test_repeated_points(2);
1636 test_repeated_points(3);
1637 test_repeated_points(4);
1638 test_repeated_points(5);
1639 test_repeated_points(6);
1640 test_repeated_points(7);
1641 }
1643 {
1644 {
1645 // batch oo + P = P
1647 points.push_back(affine_element::infinity());
1648 points.push_back(affine_element(element::random_element()));
1649 std::vector<fr> scalars;
1650 scalars.push_back(1);
1651 scalars.push_back(1);
1652
1654 ASSERT_EQ(points.size(), scalars.size());
1655 const size_t num_points = points.size();
1656
1657 std::vector<element_ct> circuit_points;
1658 std::vector<scalar_ct> circuit_scalars;
1659 for (size_t i = 0; i < num_points; ++i) {
1660 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1661 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1662 }
1663
1664 element_ct result_point =
1665 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1666
1667 element expected_point = points[1];
1668 expected_point = expected_point.normalize();
1669
1670 fq result_x(result_point.x().get_value().lo);
1671 fq result_y(result_point.y().get_value().lo);
1672
1673 EXPECT_EQ(result_x, expected_point.x);
1674 EXPECT_EQ(result_y, expected_point.y);
1675
1677 }
1678 {
1679 // batch 0 * P1 + P2 = P2
1681 points.push_back(affine_element(element::random_element()));
1682 points.push_back(affine_element(element::random_element()));
1683 std::vector<fr> scalars;
1684 scalars.push_back(0);
1685 scalars.push_back(1);
1686
1688 ASSERT_EQ(points.size(), scalars.size());
1689 const size_t num_points = points.size();
1690
1691 std::vector<element_ct> circuit_points;
1692 std::vector<scalar_ct> circuit_scalars;
1693 for (size_t i = 0; i < num_points; ++i) {
1694 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1695 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1696 }
1697
1698 element_ct result_point =
1699 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1700
1701 element expected_point = points[1];
1702 expected_point = expected_point.normalize();
1703
1704 fq result_x(result_point.x().get_value().lo);
1705 fq result_y(result_point.y().get_value().lo);
1706
1707 EXPECT_EQ(result_x, expected_point.x);
1708 EXPECT_EQ(result_y, expected_point.y);
1709
1711 }
1712 }
1713
1714 // Test batch_mul with all points at infinity
1716 {
1719 std::vector<fr> scalars;
1720
1721 for (size_t i = 0; i < 5; ++i) {
1722 points.push_back(affine_element::infinity());
1723 scalars.push_back(fr::random_element());
1724 }
1725
1726 std::vector<element_ct> circuit_points;
1727 std::vector<scalar_ct> circuit_scalars;
1728
1729 for (size_t i = 0; i < points.size(); ++i) {
1730 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1731 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1732 }
1733
1734 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1735
1736 // Result should be point at infinity
1737 EXPECT_TRUE(is_infinity(result));
1739 }
1740
1741 // Test batch_mul with all zero scalars
1743 {
1746 std::vector<fr> scalars;
1747
1748 for (size_t i = 0; i < 5; ++i) {
1749 points.push_back(affine_element(element::random_element()));
1750 scalars.push_back(fr::zero());
1751 }
1752
1753 std::vector<element_ct> circuit_points;
1754 std::vector<scalar_ct> circuit_scalars;
1755
1756 for (size_t i = 0; i < points.size(); ++i) {
1757 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1758 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1759 }
1760
1761 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1762
1763 // Result should be point at infinity
1764 EXPECT_TRUE(is_infinity(result));
1766 }
1767
1768 // Test batch_mul with mixed zero and non-zero scalars
1770 {
1773 std::vector<fr> scalars;
1774
1775 for (size_t i = 0; i < 6; ++i) {
1776 points.push_back(affine_element(element::random_element()));
1777 // Alternate between zero and non-zero scalars
1778 scalars.push_back((i % 2 == 0) ? fr::zero() : fr::random_element());
1779 }
1780
1781 std::vector<element_ct> circuit_points;
1782 std::vector<scalar_ct> circuit_scalars;
1783
1784 for (size_t i = 0; i < points.size(); ++i) {
1785 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1786 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1787 }
1788
1789 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1790
1791 // Compute expected result
1792 element expected = element::infinity();
1793 for (size_t i = 0; i < points.size(); ++i) {
1794 expected += (element(points[i]) * scalars[i]);
1795 }
1796 affine_element expected_affine = affine_element(expected);
1797
1798 uint256_t result_x = result.x().get_value().lo;
1799 uint256_t result_y = result.y().get_value().lo;
1800
1801 EXPECT_EQ(fq(result_x), expected_affine.x);
1802 EXPECT_EQ(fq(result_y), expected_affine.y);
1803
1805 }
1806
1807 // Test batch_mul with mixed infinity and valid points
1809 {
1812 std::vector<fr> scalars;
1813
1814 for (size_t i = 0; i < 6; ++i) {
1815 // Alternate between infinity and valid points
1816 points.push_back((i % 2 == 0) ? affine_element::infinity() : affine_element(element::random_element()));
1817 scalars.push_back(fr::random_element());
1818 }
1819
1820 std::vector<element_ct> circuit_points;
1821 std::vector<scalar_ct> circuit_scalars;
1822
1823 for (size_t i = 0; i < points.size(); ++i) {
1824 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1825 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1826 }
1827
1828 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1829
1830 // Compute expected result
1831 element expected = element::infinity();
1832 for (size_t i = 0; i < points.size(); ++i) {
1833 if (!points[i].is_point_at_infinity()) {
1834 expected += (element(points[i]) * scalars[i]);
1835 }
1836 }
1837 affine_element expected_affine = affine_element(expected);
1838
1839 uint256_t result_x = result.x().get_value().lo;
1840 uint256_t result_y = result.y().get_value().lo;
1841
1842 EXPECT_EQ(fq(result_x), expected_affine.x);
1843 EXPECT_EQ(fq(result_y), expected_affine.y);
1844
1846 }
1847
1848 // Test batch_mul with points that cancel out
1850 {
1853 std::vector<fr> scalars;
1854
1855 // Add P and -P with same scalar
1856 affine_element P(element::random_element());
1858 fr scalar = fr::random_element();
1859
1860 points.push_back(P);
1861 scalars.push_back(scalar);
1862 points.push_back(neg_P);
1863 scalars.push_back(scalar);
1864
1865 // Add some other points to make it non-trivial
1866 for (size_t i = 0; i < 3; ++i) {
1867 points.push_back(affine_element(element::random_element()));
1868 scalars.push_back(fr::random_element());
1869 }
1870
1871 std::vector<element_ct> circuit_points;
1872 std::vector<scalar_ct> circuit_scalars;
1873
1874 for (size_t i = 0; i < points.size(); ++i) {
1875 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1876 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1877 }
1878
1879 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1880
1881 // Compute expected result
1882 element expected = element::infinity();
1883 for (size_t i = 0; i < points.size(); ++i) {
1884 expected += (element(points[i]) * scalars[i]);
1885 }
1886 affine_element expected_affine = affine_element(expected);
1887
1888 uint256_t result_x = result.x().get_value().lo;
1889 uint256_t result_y = result.y().get_value().lo;
1890
1891 EXPECT_EQ(fq(result_x), expected_affine.x);
1892 EXPECT_EQ(fq(result_y), expected_affine.y);
1893
1895 }
1896
1897 // Test batch_mul with constant and witness points mixed
1899 {
1901 std::vector<affine_element> points_native;
1902 std::vector<fr> scalars_native;
1903 std::vector<element_ct> circuit_points;
1904 std::vector<scalar_ct> circuit_scalars;
1905
1906 // Add constant-constant points
1907 for (size_t i = 0; i < 3; ++i) {
1908 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
1909 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
1910 points_native.push_back(point);
1911 scalars_native.push_back(scalar);
1912 circuit_points.push_back(point_ct); // Constant
1913 circuit_scalars.push_back(scalar_ct); // Constant
1914 }
1915
1916 // Add witness-witness points
1917 for (size_t i = 0; i < 3; ++i) {
1918 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
1919 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
1920 points_native.push_back(point);
1921 scalars_native.push_back(scalar);
1922 circuit_points.push_back(point_ct); // Witness
1923 circuit_scalars.push_back(scalar_ct); // Witness
1924 }
1925
1926 // Add constant-witness points
1927 for (size_t i = 0; i < 4; ++i) {
1928 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
1929 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
1930 points_native.push_back(point);
1931 scalars_native.push_back(scalar);
1932 circuit_points.push_back(element_ct(point.x, point.y)); // Constant
1933 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalar)); // Witness
1934 }
1935
1936 // Add witness-constant points
1937 for (size_t i = 0; i < 4; ++i) {
1938 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
1939 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
1940 points_native.push_back(point);
1941 scalars_native.push_back(scalar);
1942 circuit_points.push_back(point_ct); // Witness
1943 circuit_scalars.push_back(scalar_ct); // Constant
1944 }
1945
1946 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
1947
1948 // Compute expected result
1949 element expected = element::infinity();
1950 for (size_t i = 0; i < points_native.size(); ++i) {
1951 expected += (element(points_native[i]) * scalars_native[i]);
1952 }
1953 affine_element expected_affine = affine_element(expected);
1954
1955 uint256_t result_x = result.x().get_value().lo;
1956 uint256_t result_y = result.y().get_value().lo;
1957
1958 EXPECT_EQ(fq(result_x), expected_affine.x);
1959 EXPECT_EQ(fq(result_y), expected_affine.y);
1960
1962 }
1963
1964 // Test batch_mul with large number of points (stress test)
1966 {
1969 std::vector<fr> scalars;
1970 constexpr size_t num_points = 20;
1971
1972 for (size_t i = 0; i < num_points; ++i) {
1973 points.push_back(affine_element(element::random_element()));
1974 scalars.push_back(fr::random_element());
1975 }
1976
1977 std::vector<element_ct> circuit_points;
1978 std::vector<scalar_ct> circuit_scalars;
1979
1980 for (size_t i = 0; i < points.size(); ++i) {
1981 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1982 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1983 }
1984
1985 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
1986
1987 // Compute expected result
1988 element expected = element::infinity();
1989 for (size_t i = 0; i < points.size(); ++i) {
1990 expected += (element(points[i]) * scalars[i]);
1991 }
1992 affine_element expected_affine = affine_element(expected);
1993
1994 uint256_t result_x = result.x().get_value().lo;
1995 uint256_t result_y = result.y().get_value().lo;
1996
1997 EXPECT_EQ(fq(result_x), expected_affine.x);
1998 EXPECT_EQ(fq(result_y), expected_affine.y);
1999
2001 }
2002
2003 // Test that infinity representation is canonical (x=0, y=0) after all operations
2005 {
2007
2008 // Case 1: constant_infinity() returns canonical form
2009 {
2010 element_ct inf = element_ct::constant_infinity(&builder);
2011 EXPECT_TRUE(is_infinity(inf));
2012 // Verify coordinates are (0, 0)
2013 EXPECT_EQ(fq(inf.x().get_value().lo), fq(0));
2014 EXPECT_EQ(fq(inf.y().get_value().lo), fq(0));
2015 }
2016
2017 // Case 2: P + (-P) = infinity with canonical coords
2018 {
2019 affine_element input(element::random_element());
2020 element_ct P = element_ct::from_witness(&builder, input);
2021 element_ct neg_P = -P;
2022 element_ct result = P + neg_P;
2023
2024 EXPECT_TRUE(is_infinity(result));
2025 // After standardization, coordinates should be (0, 0)
2026 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2027 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2028 }
2029
2030 // Case 3: P - P = infinity with canonical coords
2031 {
2032 affine_element input(element::random_element());
2033 element_ct P = element_ct::from_witness(&builder, input);
2034 element_ct result = P - P;
2035
2036 EXPECT_TRUE(is_infinity(result));
2037 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2038 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2039 }
2040
2041 // Case 4: infinity + infinity = infinity with canonical coords
2042 {
2043 element_ct inf1 = element_ct::constant_infinity(&builder);
2044 element_ct inf2 = element_ct::constant_infinity(&builder);
2045 element_ct result = inf1 + inf2;
2046
2047 EXPECT_TRUE(is_infinity(result));
2048 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2049 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2050 }
2051
2052 // Case 5: 2 * infinity = infinity with canonical coords
2053 {
2054 element_ct inf = element_ct::constant_infinity(&builder);
2055 element_ct result = inf.dbl();
2056
2057 EXPECT_TRUE(is_infinity(result));
2058 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2059 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2060 }
2061
2063 }
2064
2065 // Test chained operations involving infinity
2067 {
2069
2070 // (a + infinity) - a = infinity
2071 {
2072 affine_element input(element::random_element());
2073 element_ct a = element_ct::from_witness(&builder, input);
2074 element_ct inf = element_ct::constant_infinity(&builder);
2075
2076 element_ct temp = a + inf;
2077 element_ct result = temp - a;
2078
2079 EXPECT_TRUE(is_infinity(result));
2080 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2081 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2082 }
2083
2084 // a + (b - b) = a
2085 {
2086 affine_element input_a(element::random_element());
2087 affine_element input_b(element::random_element());
2088 element_ct a = element_ct::from_witness(&builder, input_a);
2089 element_ct b = element_ct::from_witness(&builder, input_b);
2090
2091 element_ct zero = b - b; // Should be infinity
2092 element_ct result = a + zero;
2093
2094 // Result should equal a
2095 EXPECT_EQ(fq(result.x().get_value().lo), input_a.x);
2096 EXPECT_EQ(fq(result.y().get_value().lo), input_a.y);
2097 EXPECT_FALSE(is_infinity(result));
2098 }
2099
2100 // (infinity - infinity) + a = a
2101 {
2102 affine_element input(element::random_element());
2103 element_ct a = element_ct::from_witness(&builder, input);
2104 element_ct inf1 = element_ct::constant_infinity(&builder);
2105 element_ct inf2 = element_ct::constant_infinity(&builder);
2106
2107 element_ct zero = inf1 - inf2;
2108 element_ct result = zero + a;
2109
2110 EXPECT_EQ(fq(result.x().get_value().lo), input.x);
2111 EXPECT_EQ(fq(result.y().get_value().lo), input.y);
2112 }
2113
2115 }
2116
2117 // Test conditional_select with infinity points
2119 {
2121
2122 affine_element input_a(element::random_element());
2123 element_ct a = element_ct::from_witness(&builder, input_a);
2124 element_ct inf = element_ct::constant_infinity(&builder);
2125
2126 // Case 1: Select finite point when predicate is false
2127 {
2128 bool_ct pred(witness_ct(&builder, false));
2129 element_ct result = a.conditional_select(inf, pred);
2130
2131 EXPECT_FALSE(is_infinity(result));
2132 EXPECT_EQ(fq(result.x().get_value().lo), input_a.x);
2133 EXPECT_EQ(fq(result.y().get_value().lo), input_a.y);
2134 }
2135
2136 // Case 2: Select infinity when predicate is true
2137 {
2138 bool_ct pred(witness_ct(&builder, true));
2139 element_ct result = a.conditional_select(inf, pred);
2140
2141 EXPECT_TRUE(is_infinity(result));
2142 }
2143
2144 // Case 3: Select between two infinity points
2145 {
2146 element_ct inf2 = element_ct::constant_infinity(&builder);
2147 bool_ct pred(witness_ct(&builder, true));
2148 element_ct result = inf.conditional_select(inf2, pred);
2149
2150 EXPECT_TRUE(is_infinity(result));
2151 }
2152
2154 }
2155
2156 // Test conditional_negate with infinity
2158 {
2160
2161 element_ct inf = element_ct::constant_infinity(&builder);
2162
2163 // Negating infinity should still be infinity
2164 {
2165 bool_ct pred(witness_ct(&builder, true));
2166 element_ct result = inf.conditional_negate(pred);
2167
2168 EXPECT_TRUE(is_infinity(result));
2169 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2170 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2171 }
2172
2173 // Not negating infinity should still be infinity
2174 {
2175 bool_ct pred(witness_ct(&builder, false));
2176 element_ct result = inf.conditional_negate(pred);
2177
2178 EXPECT_TRUE(is_infinity(result));
2179 }
2180
2182 }
2183
2184 // Test get_standard_form preserves canonical infinity representation
2186 {
2188
2189 // Use constant_infinity() factory to create canonical infinity with (0, 0) coordinates
2190 // Note: We no longer support non-canonical infinity representations (points with
2191 // random coords but is_infinity=true) through the public API
2192 element_ct P = element_ct::constant_infinity(&builder);
2193
2194 // Canonical infinity has (0, 0) coordinates
2195 EXPECT_EQ(fq(P.x().get_value().lo), fq(0));
2196 EXPECT_EQ(fq(P.y().get_value().lo), fq(0));
2197 EXPECT_TRUE(is_infinity(P));
2198
2199 // After standardization, coords should still be (0, 0)
2200 element_ct standardized = P.get_standard_form();
2201 EXPECT_TRUE(is_infinity(standardized));
2202 EXPECT_EQ(fq(standardized.x().get_value().lo), fq(0));
2203 EXPECT_EQ(fq(standardized.y().get_value().lo), fq(0));
2204
2206 }
2207
2208 // Test auto-detection of infinity in 2-argument constructor
2210 {
2212
2213 // Create element with (0, 0) coordinates - should auto-detect as infinity
2214 auto x_zero = element_ct::BaseField::from_witness(&builder, fq(0));
2215 auto y_zero = element_ct::BaseField::from_witness(&builder, fq(0));
2216
2217 element_ct point(x_zero, y_zero);
2218
2219 EXPECT_TRUE(is_infinity(point));
2220
2222 }
2223
2224 // Test scalar multiplication edge cases with infinity
2226 {
2228
2229 // Case 1: 0 * P = infinity
2230 {
2231 affine_element input(element::random_element());
2232 element_ct P = element_ct::from_witness(&builder, input);
2233 scalar_ct zero = scalar_ct::from_witness(&builder, fr(0));
2234
2235 element_ct result = P * zero;
2236 EXPECT_TRUE(is_infinity(result));
2237 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2238 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2239 }
2240
2241 // Case 2: k * infinity = infinity
2242 {
2243 element_ct inf = element_ct::constant_infinity(&builder);
2244 fr scalar_val = fr::random_element();
2245 scalar_ct k = scalar_ct::from_witness(&builder, scalar_val);
2246
2247 element_ct result = inf * k;
2248 EXPECT_TRUE(is_infinity(result));
2249 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2250 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2251 }
2252
2253 // Case 3: 0 * infinity = infinity
2254 {
2255 element_ct inf = element_ct::constant_infinity(&builder);
2256 scalar_ct zero = scalar_ct::from_witness(&builder, fr(0));
2257
2258 element_ct result = inf * zero;
2259 EXPECT_TRUE(is_infinity(result));
2260 }
2261
2263 }
2264
2265 // Test batch_mul where result cancels to infinity
2267 {
2269
2270 // P*a + Q*b + P*(-a) + Q*(-b) = infinity
2271 affine_element P(element::random_element());
2272 affine_element Q(element::random_element());
2275
2276 std::vector<element_ct> points = {
2277 element_ct::from_witness(&builder, P),
2278 element_ct::from_witness(&builder, Q),
2279 element_ct::from_witness(&builder, P),
2280 element_ct::from_witness(&builder, Q),
2281 };
2282
2283 std::vector<scalar_ct> scalars = { scalar_ct::from_witness(&builder, a),
2284 scalar_ct::from_witness(&builder, b),
2285 scalar_ct::from_witness(&builder, -a),
2286 scalar_ct::from_witness(&builder, -b) };
2287
2288 element_ct result = element_ct::batch_mul(points, scalars, 0, true);
2289
2290 EXPECT_TRUE(is_infinity(result));
2291 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2292 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2293
2295 }
2296
2297 // Test addition with constant infinity
2299 {
2301
2302 // P + constant_infinity = P
2303 affine_element input(element::random_element());
2304 element_ct P = element_ct::from_witness(&builder, input);
2305 element_ct const_inf = element_ct::constant_infinity(&builder); // This is a constant
2306
2307 element_ct result = P + const_inf;
2308
2309 EXPECT_FALSE(is_infinity(result));
2310 EXPECT_EQ(fq(result.x().get_value().lo), input.x);
2311 EXPECT_EQ(fq(result.y().get_value().lo), input.y);
2312
2313 // constant_infinity + P = P
2314 element_ct result2 = const_inf + P;
2315 EXPECT_FALSE(is_infinity(result2));
2316 EXPECT_EQ(fq(result2.x().get_value().lo), input.x);
2317 EXPECT_EQ(fq(result2.y().get_value().lo), input.y);
2318
2320 }
2321
2322 // Test that witness infinity points (created via operations) work correctly
2324 {
2326
2327 // Create infinity as P - P (witness-based infinity)
2328 affine_element input(element::random_element());
2329 element_ct P = element_ct::from_witness(&builder, input);
2330 element_ct witness_inf = P - P;
2331
2332 // Use this witness infinity in operations
2333 affine_element input2(element::random_element());
2334 element_ct Q = element_ct::from_witness(&builder, input2);
2335
2336 // Q + witness_inf = Q
2337 element_ct result = Q + witness_inf;
2338 EXPECT_EQ(fq(result.x().get_value().lo), input2.x);
2339 EXPECT_EQ(fq(result.y().get_value().lo), input2.y);
2340
2341 // witness_inf + Q = Q
2342 element_ct result2 = witness_inf + Q;
2343 EXPECT_EQ(fq(result2.x().get_value().lo), input2.x);
2344 EXPECT_EQ(fq(result2.y().get_value().lo), input2.y);
2345
2347 }
2348};
2349
2350// bn254 with ultra arithmetisation where scalar field is native field, base field is non-native field (bigfield)
2353
2354// bn254 with ultra arithmetisation where both scalar and base fields are non-native fields
2357 true>;
2358
2359// bn254 with mega arithmetisation where scalar field is native field, base field is non-native field
2362
2363// secp256r1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2366 false>;
2367
2368// secp256k1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2371 false>;
2372
2373using TestTypes = testing::Types<bn254_with_ultra,
2378
2380
2381TYPED_TEST(stdlib_biggroup, validate_on_curve)
2382{
2384 // Goblin points do not implement validate on curve
2385 if constexpr (!HasGoblinBuilder<TypeParam>) {
2386 using Builder = TestFixture::Builder;
2387 using element_ct = TestFixture::element_ct;
2388 using Fq = TestFixture::Curve::BaseField;
2389 using FqNative = TestFixture::Curve::BaseFieldNative;
2390 using GroupNative = TestFixture::Curve::GroupNative;
2391
2393 auto [native_point, witness_point] = TestFixture::get_random_witness_point(&builder);
2394
2395 // Valid point
2396 Fq expected_zero = witness_point.validate_on_curve("biggroup::validate_on_curve", false);
2397 expected_zero.assert_equal(Fq::zero());
2398 EXPECT_EQ(expected_zero.get_value(), static_cast<uint512_t>(FqNative::zero()));
2399
2400 // Invalid point
2401 Fq random_x = Fq::from_witness(&builder, FqNative::random_element());
2402 Fq random_y = Fq::from_witness(&builder, FqNative::random_element());
2403 element_ct invalid_point(random_x, random_y, /*assert_on_curve*/ false);
2404 Fq expected_non_zero = invalid_point.validate_on_curve("biggroup::validate_on_curve", false);
2405 Fq expected_value = -random_y.sqr() + random_x.pow(3) + Fq(uint256_t(GroupNative::curve_b));
2406 if constexpr (GroupNative::has_a) {
2407 expected_value += random_x * Fq(uint256_t(GroupNative::curve_a));
2408 }
2409 expected_non_zero.assert_equal(expected_value);
2410
2411 // Reduce the value to remove constants
2412 expected_non_zero.self_reduce();
2413 expected_value.self_reduce();
2414 EXPECT_EQ(expected_non_zero.get_value(), expected_value.get_value());
2415
2416 TestFixture::EXPECT_CIRCUIT_CORRECTNESS(builder);
2417
2418 // Check that the circuit fails if validate_on_curve is called with default parameters
2419 [[maybe_unused]] Fq _ = invalid_point.validate_on_curve();
2420 TestFixture::EXPECT_CIRCUIT_CORRECTNESS(builder, false);
2421 }
2422}
2423
2425{
2426 TestFixture::test_basic_tag_logic();
2427}
2428
2429TYPED_TEST(stdlib_biggroup, assert_coordinates_in_field)
2430{
2431 TestFixture::test_assert_coordinates_in_field();
2432}
2433
2434// Addition tests
2436{
2437 TestFixture::test_add();
2438}
2439TYPED_TEST(stdlib_biggroup, add_with_constants)
2440{
2441 TestFixture::test_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2442 TestFixture::test_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2443 TestFixture::test_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2444}
2445TYPED_TEST(stdlib_biggroup, add_points_at_infinity)
2446{
2447 TestFixture::test_add_points_at_infinity();
2448}
2449TYPED_TEST(stdlib_biggroup, standard_form_of_point_at_infinity)
2450{
2451 TestFixture::test_standard_form_of_point_at_infinity();
2452}
2453
2454// Subtraction tests
2456{
2457 TestFixture::test_sub();
2458}
2459TYPED_TEST(stdlib_biggroup, sub_with_constants)
2460{
2461 TestFixture::test_sub(InputType::WITNESS, InputType::CONSTANT); // w - c
2462 TestFixture::test_sub(InputType::CONSTANT, InputType::WITNESS); // c - w
2463 TestFixture::test_sub(InputType::CONSTANT, InputType::CONSTANT); // c - c
2464}
2465TYPED_TEST(stdlib_biggroup, sub_points_at_infinity)
2466{
2467 TestFixture::test_sub_points_at_infinity();
2468}
2470{
2471 TestFixture::test_dbl();
2472}
2473TYPED_TEST(stdlib_biggroup, dbl_with_constant)
2474{
2475 TestFixture::test_dbl(InputType::CONSTANT); // dbl(c)
2476}
2477TYPED_TEST(stdlib_biggroup, dbl_with_infinity)
2478{
2479 TestFixture::test_dbl_with_infinity();
2480}
2482{
2483 if constexpr (HasGoblinBuilder<TypeParam>) {
2484 GTEST_SKIP() << "mega builder does not support this edge case";
2485 } else {
2486 TestFixture::test_dbl_with_y_zero();
2487 }
2488}
2490{
2491 TestFixture::test_add_equals_dbl();
2492}
2493TYPED_TEST(stdlib_biggroup, sub_neg_equals_double)
2494{
2495 TestFixture::test_sub_neg_equals_double();
2496}
2497
2498// Test chain_add
2500{
2501 if constexpr (HasGoblinBuilder<TypeParam>) {
2502 GTEST_SKIP() << "mega builder does not implement chain_add function";
2503 } else {
2504 TestFixture::test_chain_add();
2505 };
2506}
2507HEAVY_TYPED_TEST(stdlib_biggroup, chain_add_with_constants)
2508{
2509 if constexpr (HasGoblinBuilder<TypeParam>) {
2510 GTEST_SKIP() << "mega builder does not implement chain_add function";
2511 } else {
2512 TestFixture::test_chain_add(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2513 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2514 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2515 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2516 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2517 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2518 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2519 }
2520}
2521
2522// Test multiple_montgomery_ladder
2523HEAVY_TYPED_TEST(stdlib_biggroup, multiple_montgomery_ladder)
2524{
2525
2526 if constexpr (HasGoblinBuilder<TypeParam>) {
2527 GTEST_SKIP() << "mega builder does not implement multiple_montgomery_ladder function";
2528 } else {
2529 TestFixture::test_multiple_montgomery_ladder();
2530 };
2531}
2532
2533// Test normalize
2535{
2536 TestFixture::test_normalize();
2537}
2538TYPED_TEST(stdlib_biggroup, normalize_constant)
2539{
2540 TestFixture::test_normalize(InputType::CONSTANT);
2541}
2542
2543// Test reduce
2545{
2546 TestFixture::test_reduce();
2547}
2549{
2550 TestFixture::test_reduce(InputType::CONSTANT);
2551}
2552
2553// Test unary negation
2555{
2556 TestFixture::test_unary_negate(InputType::WITNESS);
2557}
2558
2559TYPED_TEST(stdlib_biggroup, unary_negate_with_constants)
2560{
2561 TestFixture::test_unary_negate(InputType::CONSTANT);
2562}
2563
2564// Test operator+=
2566{
2567 TestFixture::test_add_assign(InputType::WITNESS, InputType::WITNESS);
2568}
2569
2570TYPED_TEST(stdlib_biggroup, add_assign_with_constants)
2571{
2572 TestFixture::test_add_assign(InputType::WITNESS, InputType::CONSTANT); // w += c
2573 TestFixture::test_add_assign(InputType::CONSTANT, InputType::WITNESS); // c += w
2574}
2575
2576// Test operator-=
2578{
2579 TestFixture::test_sub_assign(InputType::WITNESS, InputType::WITNESS);
2580}
2581TYPED_TEST(stdlib_biggroup, sub_assign_with_constants)
2582{
2583 TestFixture::test_sub_assign(InputType::WITNESS, InputType::CONSTANT); // w -= c
2584 TestFixture::test_sub_assign(InputType::CONSTANT, InputType::WITNESS); // c -= w
2585}
2586// Test checked_unconditional_add
2587TYPED_TEST(stdlib_biggroup, checked_unconditional_add)
2588{
2589 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::WITNESS);
2590}
2591TYPED_TEST(stdlib_biggroup, checked_unconditional_add_with_constants)
2592{
2593 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2594 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2595 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2596}
2597// Test checked_unconditional_subtract
2598TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract)
2599{
2600 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::WITNESS);
2601}
2602TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract_with_constants)
2603{
2604 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::CONSTANT); // w - c
2605 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::WITNESS); // c - w
2606 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::CONSTANT); // c - c
2607}
2608// Test checked_unconditional_add_sub
2609TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub)
2610{
2611 TestFixture::test_checked_unconditional_add_sub();
2612}
2613TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub_with_constants)
2614{
2615 TestFixture::test_checked_unconditional_add_sub(InputType::WITNESS, InputType::CONSTANT); // w, c
2616 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::WITNESS); // c, w
2617 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::CONSTANT); // c, c
2618}
2619// Test conditional_negate
2620TYPED_TEST(stdlib_biggroup, conditional_negate)
2621{
2622 TestFixture::test_conditional_negate();
2623}
2624TYPED_TEST(stdlib_biggroup, conditional_negate_with_constants)
2625{
2626 TestFixture::test_conditional_negate(InputType::WITNESS, InputType::CONSTANT); // w, c
2627 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::WITNESS); // c, w
2628 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::CONSTANT); // c, c
2629}
2630// Test conditional_select
2631TYPED_TEST(stdlib_biggroup, conditional_select)
2632{
2633 TestFixture::test_conditional_select();
2634}
2635TYPED_TEST(stdlib_biggroup, conditional_select_with_constants)
2636{
2637 TestFixture::test_conditional_select(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2638 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2639 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2640 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2641 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2642 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2643 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2644}
2645TYPED_TEST(stdlib_biggroup, incomplete_assert_equal)
2646{
2647 TestFixture::test_incomplete_assert_equal();
2648}
2649TYPED_TEST(stdlib_biggroup, incomplete_assert_equal_fails)
2650{
2651 TestFixture::test_incomplete_assert_equal_failure();
2652}
2653
2655{
2656 if constexpr (!HasGoblinBuilder<TypeParam>) {
2657 size_t num_repetitions = 1;
2658 for (size_t i = 0; i < num_repetitions; i++) {
2659 TestFixture::test_compute_naf();
2660 }
2661 } else {
2662 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2663 }
2664}
2665
2667{
2668 if constexpr (!HasGoblinBuilder<TypeParam>) {
2669 TestFixture::test_compute_naf_zero();
2670 } else {
2671 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2672 }
2673}
2674
2675HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf_overflow_lower_half)
2676{
2677 if constexpr (!HasGoblinBuilder<TypeParam>) {
2678 TestFixture::test_compute_naf_overflow_lower_half();
2679 } else {
2680 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2681 }
2682}
2683
2685{
2686 TestFixture::test_mul();
2687}
2689{
2690 TestFixture::test_mul(InputType::WITNESS, InputType::CONSTANT); // w * c
2691 TestFixture::test_mul(InputType::CONSTANT, InputType::WITNESS); // c * w
2692 TestFixture::test_mul(InputType::CONSTANT, InputType::CONSTANT); // c * c
2693}
2695{
2696 TestFixture::test_mul_edge_cases();
2697}
2698HEAVY_TYPED_TEST(stdlib_biggroup, mul_edge_cases_with_constants)
2699{
2700 TestFixture::test_mul_edge_cases(InputType::WITNESS, InputType::CONSTANT); // w * c
2701 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::WITNESS); // c * w
2702 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::CONSTANT); // c * c
2703}
2704
2705HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_with_bit_lengths)
2706{
2707 if constexpr (HasGoblinBuilder<TypeParam>) {
2708 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2709 } else {
2710 TestFixture::test_short_scalar_mul_with_bit_lengths();
2711 }
2712}
2713
2714HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_infinity)
2715{
2716 if constexpr (HasGoblinBuilder<TypeParam>) {
2717 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2718 } else {
2719 TestFixture::test_short_scalar_mul_infinity();
2720 }
2721}
2722
2723// Batch multiplication tests
2724// 1 point - Base case only
2726{
2727 TestFixture::test_helper_batch_mul(1);
2728}
2729
2730// 2 points - Base case + flag variations + one constant mix
2732{
2733 TestFixture::test_helper_batch_mul(2);
2734}
2735HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars)
2736{
2737 TestFixture::test_helper_batch_mul(2, true); // short_scalars
2738}
2739HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_with_edgecases)
2740{
2741 TestFixture::test_helper_batch_mul(2, false, true); // short_scalars, with_edgecases
2742}
2743HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars_with_edgecases)
2744{
2745 TestFixture::test_helper_batch_mul(2, true, true); // short_scalars, with_edgecases
2746}
2747HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_mixed_constants)
2748{
2749 TestFixture::test_helper_batch_mul({ InputType::WITNESS, InputType::CONSTANT },
2751}
2752
2753// 3 points - Base case only
2755{
2756 TestFixture::test_helper_batch_mul(3);
2757}
2758
2759// 4 points - Base case only
2761{
2762 TestFixture::test_helper_batch_mul(4);
2763}
2764
2765// 5 points - Base case + edge case + short scalar + mixed constant
2767{
2768 TestFixture::test_helper_batch_mul(5);
2769}
2770HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_with_edgecases)
2771{
2772 TestFixture::test_helper_batch_mul(5, false, true); // short_scalars, with_edgecases
2773}
2774HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars)
2775{
2776 TestFixture::test_helper_batch_mul(5, true); // short_scalars
2777}
2778HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars_with_edgecases)
2779{
2780 TestFixture::test_helper_batch_mul(5, true, true); // short_scalars, with_edgecases
2781}
2788
2789// 6 points - Base case only
2791{
2792 TestFixture::test_helper_batch_mul(6);
2793}
2794
2796{
2797 TestFixture::test_twin_mul();
2798}
2799
2800HEAVY_TYPED_TEST(stdlib_biggroup, twin_mul_with_infinity)
2801{
2802 TestFixture::test_twin_mul_with_infinity();
2803}
2804
2805HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators)
2806{
2807 TestFixture::test_batch_mul_linearly_dependent_generators();
2808}
2809
2810HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators_failure)
2811{
2812 if constexpr (HasGoblinBuilder<TypeParam>) {
2813 GTEST_SKIP() << "this failure test is designed for ultra builder only";
2814 } else {
2815 TestFixture::test_batch_mul_linearly_dependent_generators_failure();
2816 }
2817}
2818
2820{
2821 TestFixture::test_one();
2822}
2823
2825{
2826 TestFixture::test_batch_mul();
2827}
2828
2829HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edgecase_equivalence)
2830{
2831 TestFixture::test_batch_mul_edgecase_equivalence();
2832}
2833HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set1)
2834{
2835 TestFixture::test_batch_mul_edge_case_set1();
2836}
2837
2838HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set2)
2839{
2840 TestFixture::test_batch_mul_edge_case_set2();
2841}
2842
2843// Batch mul edge case tests
2844HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_infinity)
2845{
2846 TestFixture::test_batch_mul_all_infinity();
2847}
2848
2849HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_zero_scalars)
2850{
2851 TestFixture::test_batch_mul_all_zero_scalars();
2852}
2853
2854HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_zero_scalars)
2855{
2856 TestFixture::test_batch_mul_mixed_zero_scalars();
2857}
2858
2859HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_infinity)
2860{
2861 TestFixture::test_batch_mul_mixed_infinity();
2862}
2863
2864HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_cancellation)
2865{
2866 TestFixture::test_batch_mul_cancellation();
2867}
2868
2869HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_constant_witness)
2870{
2871 TestFixture::test_batch_mul_mixed_constant_witness();
2872}
2873
2874HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_large_number_of_points)
2875{
2876 TestFixture::test_batch_mul_large_number_of_points();
2877}
2878
2879// Point at Infinity Edge Case Tests
2880TYPED_TEST(stdlib_biggroup, infinity_canonical_representation)
2881{
2882 TestFixture::test_infinity_canonical_representation();
2883}
2884
2885TYPED_TEST(stdlib_biggroup, infinity_chained_operations)
2886{
2887 TestFixture::test_infinity_chained_operations();
2888}
2889
2890TYPED_TEST(stdlib_biggroup, conditional_select_with_infinity)
2891{
2892 TestFixture::test_conditional_select_with_infinity();
2893}
2894
2895TYPED_TEST(stdlib_biggroup, conditional_negate_with_infinity)
2896{
2897 TestFixture::test_conditional_negate_with_infinity();
2898}
2899
2900TYPED_TEST(stdlib_biggroup, get_standard_form_normalizes_infinity)
2901{
2902 TestFixture::test_get_standard_form_normalizes_infinity();
2903}
2904
2905TYPED_TEST(stdlib_biggroup, infinity_auto_detection_in_constructor)
2906{
2907 TestFixture::test_infinity_auto_detection_in_constructor();
2908}
2909
2910HEAVY_TYPED_TEST(stdlib_biggroup, scalar_mul_infinity_edge_cases)
2911{
2912 TestFixture::test_scalar_mul_infinity_edge_cases();
2913}
2914
2915HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_complete_cancellation)
2916{
2917 TestFixture::test_batch_mul_complete_cancellation();
2918}
2919
2920TYPED_TEST(stdlib_biggroup, add_constant_infinity)
2921{
2922 TestFixture::test_add_constant_infinity();
2923}
2924
2925TYPED_TEST(stdlib_biggroup, witness_infinity_from_operations)
2926{
2927 TestFixture::test_witness_infinity_from_operations();
2928}
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
InputType
TestType< stdlib::secp256r1< bb::UltraCircuitBuilder >, stdlib::secp256r1< bb::UltraCircuitBuilder >::ScalarField, false > secp256r1_with_ultra
TestType< stdlib::bn254< bb::UltraCircuitBuilder >, stdlib::bn254< bb::UltraCircuitBuilder >::ScalarField, false > bn254_with_ultra
TestType< stdlib::bn254< bb::UltraCircuitBuilder >, bb::stdlib::bigfield< bb::UltraCircuitBuilder, bb::Bn254FrParams >, true > bn254_with_ultra_scalar_bigfield
InputType
TestType< stdlib::bn254< bb::MegaCircuitBuilder >, stdlib::bn254< bb::MegaCircuitBuilder >::ScalarField, false > bn254_with_mega
constexpr InputType operator!(InputType type)
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
BB_INLINE constexpr void self_set_infinity() noexcept
group_elements::affine_element< Fq, Fr, Params > affine_element
Definition group.hpp:42
static constexpr element one
Definition group.hpp:46
group_elements::element< Fq, Fr, Params > element
Definition group.hpp:41
virtual uint8_t get_random_uint8()=0
virtual uint256_t get_random_uint256()=0
constexpr uint64_t get_msb() const
Implements boolean logic in-circuit.
Definition bool.hpp:60
static auto checked_unconditional_add_sub(const element< C, Fq, Fr, G > &elem1, const element< C, Fq, Fr, G > &elem2)
Definition biggroup.hpp:971
static field_t from_witness(Builder *ctx, const bb::fr &input)
Definition field.hpp:466
static void test_checked_unconditional_add_sub(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
static void test_sub_points_at_infinity()
static void test_sub_neg_equals_double()
static void test_helper_batch_mul(std::vector< InputType > point_types, std::vector< InputType > scalar_types, const bool short_scalars=false, const bool with_edgecases=false)
static void test_conditional_negate(InputType point_type=InputType::WITNESS, InputType predicate_type=InputType::WITNESS)
static void test_batch_mul_edgecase_equivalence()
static void test_one()
static void test_reduce(InputType point_type=InputType::WITNESS)
static void test_twin_mul()
static void test_witness_infinity_from_operations()
static void test_add_points_at_infinity()
static void test_chain_add(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS, InputType c_type=InputType::WITNESS)
static void test_conditional_negate_with_infinity()
static void test_compute_naf()
typename g1::element element
static void test_multiple_montgomery_ladder()
static void test_batch_mul_cancellation()
static void test_add_constant_infinity()
static void test_dbl_with_infinity()
static std::pair< affine_element, element_ct > get_random_constant_point(Builder *builder)
static void test_compute_naf_zero()
static void test_mul(InputType scalar_type=InputType::WITNESS, InputType point_type=InputType::WITNESS)
static void test_batch_mul_mixed_infinity()
typename Curve::ScalarFieldNative fr
static void test_batch_mul_edge_case_set2()
static std::pair< fr, scalar_ct > get_random_constant_scalar(Builder *builder, bool even=false)
static void test_get_standard_form_normalizes_infinity()
typename TestType::element_ct element_ct
static void test_assert_coordinates_in_field()
static std::pair< affine_element, element_ct > get_random_witness_point(Builder *builder)
static void test_infinity_auto_detection_in_constructor()
static void test_mul_edge_cases(InputType scalar_type=InputType::WITNESS, InputType point_type=InputType::WITNESS)
typename g1::affine_element affine_element
typename TestType::Curve Curve
static std::pair< fr, scalar_ct > get_random_witness_scalar(Builder *builder, bool even=false)
static void test_batch_mul_linearly_dependent_generators()
static void test_conditional_select(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS, InputType predicate_type=InputType::WITNESS)
static void test_basic_tag_logic()
static void test_add(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
typename Curve::Builder Builder
static void test_conditional_select_with_infinity()
static void test_incomplete_assert_equal()
static void test_batch_mul_mixed_constant_witness()
static void test_twin_mul_with_infinity()
static void test_unary_negate(InputType a_type=InputType::WITNESS)
typename TestType::scalar_ct scalar_ct
stdlib::bool_t< Builder > bool_ct
static std::pair< fr, scalar_ct > get_random_scalar(Builder *builder, InputType type, bool even=false)
static void test_batch_mul_edge_case_set1()
static void test_checked_unconditional_subtract(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
static void test_short_scalar_mul_with_bit_lengths()
static void test_short_scalar_mul_infinity()
static void test_dbl(InputType a_type=InputType::WITNESS)
static void test_normalize(InputType point_type=InputType::WITNESS)
static void test_infinity_chained_operations()
static void test_incomplete_assert_equal_failure()
static bool is_infinity(const element_ct &e)
static std::pair< fr, scalar_ct > get_random_short_scalar(Builder *builder, InputType type, size_t num_bits)
stdlib::witness_t< Builder > witness_ct
static void test_standard_form_of_point_at_infinity()
Check that converting a point at infinity into standard form ensures the coordinates are zeroes.
typename Curve::GroupNative g1
static void test_scalar_mul_infinity_edge_cases()
typename Curve::BaseFieldNative fq
static void test_batch_mul_mixed_zero_scalars()
static void test_add_assign(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
static std::pair< affine_element, element_ct > get_random_point(Builder *builder, InputType type)
static void test_batch_mul_large_number_of_points()
static void test_dbl_with_y_zero()
static void test_sub_assign(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
static void test_batch_mul()
static void test_batch_mul_all_zero_scalars()
static void test_compute_naf_overflow_lower_half()
static void test_batch_mul_complete_cancellation()
static void test_add_equals_dbl()
static void test_helper_batch_mul(size_t num_points, const bool short_scalars=false, const bool with_edgecases=false)
static void test_sub(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
static void test_batch_mul_linearly_dependent_generators_failure()
static constexpr auto EXPECT_CIRCUIT_CORRECTNESS
static void test_infinity_canonical_representation()
static void test_batch_mul_all_infinity()
static void test_checked_unconditional_add(InputType a_type=InputType::WITNESS, InputType b_type=InputType::WITNESS)
#define info(...)
Definition log.hpp:93
void benchmark_info(Args...)
Info used to store circuit statistics during CI/CD with concrete structure. Writes straight to log.
Definition log.hpp:121
AluTraceBuilder builder
Definition alu.test.cpp:124
FF a
FF b
bool expected_result
uint8_t const size_t length
Definition data_store.hpp:9
numeric::RNG & engine
uintx< uint256_t > uint512_t
Definition uintx.hpp:306
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:217
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
Inner sum(Cont< Inner, Args... > const &in)
Definition container.hpp:70
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
testing::Types< VKTestParams< UltraFlavor, stdlib::recursion::honk::DefaultIO< UltraCircuitBuilder > >, VKTestParams< UltraFlavor, stdlib::recursion::honk::RollupIO >, VKTestParams< UltraKeccakFlavor, stdlib::recursion::honk::DefaultIO< UltraCircuitBuilder > >, VKTestParams< MegaFlavor, stdlib::recursion::honk::DefaultIO< MegaCircuitBuilder > > > TestTypes
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
#define STANDARD_TESTING_TAGS
bb::stdlib::element< typename Curve::Builder, typename Curve::BaseField, ScalarField_, typename Curve::GroupNative > bigfield_element
std::conditional_t< use_bigfield, bigfield_element, typename Curve::Group > element_ct
ScalarField_ scalar_ct
static constexpr uint256_t modulus
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field sqr() const noexcept
BB_INLINE constexpr field reduce() const noexcept
reduce once, i.e., if the value is bigger than the modulus, subtract off the modulus once.
static constexpr field zero()
#define HEAVY_TYPED_TEST(x, y)
Definition test.hpp:11
curve::BN254::BaseField Fq