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, bool _use_bigfield = false> struct TestType {
40 public:
41 using Curve = _Curve;
42 static const bool use_bigfield = _use_bigfield;
43 using element_ct =
44 typename std::conditional<_use_bigfield, typename Curve::g1_bigfr_ct, typename Curve::Group>::type;
45 // the field of scalars acting on element_ct
46 using scalar_ct =
47 typename std::conditional<_use_bigfield, typename Curve::bigfr_ct, typename Curve::ScalarField>::type;
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 // Define masking scalar (128 bits)
1387 const auto get_128_bit_scalar = []() {
1388 uint256_t scalar_u256(0, 0, 0, 0);
1389 scalar_u256.data[0] = engine.get_random_uint64();
1390 scalar_u256.data[1] = engine.get_random_uint64();
1391 fr scalar(scalar_u256);
1392 return scalar;
1393 };
1394 fr masking_scalar = get_128_bit_scalar();
1395 scalar_ct masking_scalar_ct = scalar_ct::from_witness(&builder, masking_scalar);
1396 element_ct c = element_ct::batch_mul(points,
1397 scalars,
1398 /*max_num_bits*/ 128,
1399 /*with_edgecases*/ true,
1400 /*masking_scalar*/ masking_scalar_ct);
1401 element input_e = (element(input_P_a) * scalar_a);
1402 element input_f = (element(input_P_b) * scalar_b);
1403 element input_g = (element(input_P_c) * scalar_c);
1404
1405 affine_element expected(input_e + input_f + input_g);
1406 fq c_x_result(c.x().get_value().lo);
1407 fq c_y_result(c.y().get_value().lo);
1408
1409 EXPECT_EQ(c_x_result, expected.x);
1410 EXPECT_EQ(c_y_result, expected.y);
1411
1413 }
1414
1416 {
1418 affine_element input_P(element::random_element());
1419
1420 affine_element input_P_a = affine_element(element(input_P) + element(input_P)); // 2P
1421 affine_element input_P_b = affine_element(element(input_P_a) + element(input_P)); // 3P
1422 affine_element input_P_c = affine_element(element(input_P_a) + element(input_P_b)); // 5P
1423 std::vector<affine_element> input_points = { input_P_a, input_P_b, input_P_c };
1424
1425 // Choose scalars similar to the previous test
1426 fr scalar_a(11);
1427 fr scalar_b(14);
1428 fr scalar_c(6);
1429 std::vector<fr> input_scalars = { scalar_a, scalar_b, scalar_c };
1430
1431 std::vector<scalar_ct> scalars;
1433 for (size_t i = 0; i < 3; ++i) {
1434 const element_ct point = element_ct::from_witness(&builder, input_points[i]);
1435 points.emplace_back(point);
1436
1437 const scalar_ct scalar = scalar_ct::from_witness(&builder, input_scalars[i]);
1438 scalars.emplace_back(scalar);
1439 }
1440
1441 // with_edgecases = false should fail due to linearly dependent points
1442 // This will fail only while using ultra builder
1443 element_ct::batch_mul(points, scalars, /*max_num_bits*/ 4, /*with_edgecases*/ false);
1444
1446 EXPECT_EQ(builder.err(), "bigfield: prime limb diff is zero, but expected non-zero");
1447 }
1448
1449 static void test_one()
1450 {
1452 size_t num_repetitions = 1;
1453 for (size_t i = 0; i < num_repetitions; ++i) {
1454 fr scalar_a(fr::random_element());
1455 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
1456 scalar_a -= fr(1); // skew bit is 1
1457 }
1458 element_ct P_a = element_ct::one(&builder);
1459 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
1460 element_ct c = P_a * x_a;
1461
1462 affine_element expected(g1::one * scalar_a);
1463 fq c_x_result(c.x().get_value().lo);
1464 fq c_y_result(c.y().get_value().lo);
1465
1466 EXPECT_EQ(c_x_result, expected.x);
1467 EXPECT_EQ(c_y_result, expected.y);
1468 }
1469
1471 }
1472
1473 // Overload: defaults to all WITNESS types for given num_points
1474 static void test_helper_batch_mul(size_t num_points,
1475 const bool short_scalars = false,
1476 const bool with_edgecases = false)
1477 {
1478 std::vector<InputType> point_types(num_points, InputType::WITNESS);
1479 std::vector<InputType> scalar_types(num_points, InputType::WITNESS);
1480 test_helper_batch_mul(point_types, scalar_types, short_scalars, with_edgecases);
1481 }
1482
1484 std::vector<InputType> scalar_types,
1485 const bool short_scalars = false,
1486 const bool with_edgecases = false)
1487 {
1489
1490 const size_t num_points = point_types.size();
1492 std::vector<fr> scalars;
1493 std::vector<element_ct> circuit_points;
1494 std::vector<scalar_ct> circuit_scalars;
1495
1496 for (size_t i = 0; i < num_points; ++i) {
1497 // Generate scalars
1498 if (short_scalars) {
1499 auto [input_scalar, x] = get_random_short_scalar(&builder, scalar_types[i], /*num_bits*/ 128);
1500 scalars.push_back(input_scalar);
1501 circuit_scalars.push_back(x);
1502 } else {
1503 auto [input_scalar, x] = get_random_scalar(&builder, scalar_types[i], /*even*/ true);
1504 scalars.push_back(input_scalar);
1505 circuit_scalars.push_back(x);
1506 }
1507
1508 // Generate points
1509 auto [input_point, P] = get_random_point(&builder, point_types[i]);
1510 points.push_back(input_point);
1511 circuit_points.push_back(P);
1512 }
1513
1514 // Define masking scalar (128 bits) if with_edgecases is true
1515 const auto get_128_bit_scalar = []() {
1516 uint256_t scalar_u256(0, 0, 0, 0);
1517 scalar_u256.data[0] = engine.get_random_uint64();
1518 scalar_u256.data[1] = engine.get_random_uint64();
1519 fr scalar(scalar_u256);
1520 return scalar;
1521 };
1522 fr masking_scalar = with_edgecases ? get_128_bit_scalar() : fr(1);
1523 scalar_ct masking_scalar_ct =
1524 with_edgecases ? scalar_ct::from_witness(&builder, masking_scalar) : scalar_ct(&builder, fr(1));
1525
1526 element_ct result_point = element_ct::batch_mul(
1527 circuit_points, circuit_scalars, /*max_num_bits=*/0, with_edgecases, masking_scalar_ct);
1528
1529 element expected_point = g1::one;
1530 expected_point.self_set_infinity();
1531 for (size_t i = 0; i < num_points; ++i) {
1532 expected_point += (element(points[i]) * scalars[i]);
1533 }
1534
1535 expected_point = expected_point.normalize();
1536 fq result_x(result_point.x().get_value().lo);
1537 fq result_y(result_point.y().get_value().lo);
1538
1539 EXPECT_EQ(result_x, expected_point.x);
1540 EXPECT_EQ(result_y, expected_point.y);
1541
1543 }
1544
1545 static void test_batch_mul()
1546 {
1547 const size_t num_points = 5;
1550 std::vector<fr> scalars;
1551 for (size_t i = 0; i < num_points; ++i) {
1552 points.push_back(affine_element(element::random_element()));
1553 scalars.push_back(fr::random_element());
1554 }
1555
1556 std::vector<element_ct> circuit_points;
1557 std::vector<scalar_ct> circuit_scalars;
1558 for (size_t i = 0; i < num_points; ++i) {
1559 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1560 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1561 }
1562
1563 element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars);
1564
1565 element expected_point = g1::one;
1566 expected_point.self_set_infinity();
1567 for (size_t i = 0; i < num_points; ++i) {
1568 expected_point += (element(points[i]) * scalars[i]);
1569 }
1570
1571 expected_point = expected_point.normalize();
1572 fq result_x(result_point.x().get_value().lo);
1573 fq result_y(result_point.y().get_value().lo);
1574
1575 EXPECT_EQ(result_x, expected_point.x);
1576 EXPECT_EQ(result_y, expected_point.y);
1577
1579 }
1580
1582 {
1583 const size_t num_points = 5;
1586 std::vector<fr> scalars;
1587 for (size_t i = 0; i < num_points; ++i) {
1588 points.push_back(affine_element(element::random_element()));
1589 scalars.push_back(fr::random_element());
1590 }
1591
1592 std::vector<element_ct> circuit_points;
1593 std::vector<scalar_ct> circuit_scalars;
1594 for (size_t i = 0; i < num_points; ++i) {
1595 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1596 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1597 }
1598
1599 element_ct result_point2 =
1600 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1601
1602 element expected_point = g1::one;
1603 expected_point.self_set_infinity();
1604 for (size_t i = 0; i < num_points; ++i) {
1605 expected_point += (element(points[i]) * scalars[i]);
1606 }
1607
1608 expected_point = expected_point.normalize();
1609
1610 fq result2_x(result_point2.x().get_value().lo);
1611 fq result2_y(result_point2.y().get_value().lo);
1612
1613 EXPECT_EQ(result2_x, expected_point.x);
1614 EXPECT_EQ(result2_y, expected_point.y);
1615
1617 }
1618
1620 {
1621 const auto test_repeated_points = [](const uint32_t num_points) {
1622 // batch P + ... + P = m*P
1623 info("num points: ", num_points);
1625 std::vector<fr> scalars;
1626 for (size_t idx = 0; idx < num_points; idx++) {
1627 points.push_back(affine_element::one());
1628 scalars.push_back(1);
1629 }
1630
1632 ASSERT_EQ(points.size(), scalars.size());
1633
1634 std::vector<element_ct> circuit_points;
1635 std::vector<scalar_ct> circuit_scalars;
1636 for (size_t i = 0; i < num_points; ++i) {
1637 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1638 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1639 }
1640 element_ct result_point =
1641 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1642
1643 auto expected_point = element::infinity();
1644 for (const auto& point : points) {
1645 expected_point += point;
1646 }
1647 expected_point = expected_point.normalize();
1648
1649 fq result_x(result_point.x().get_value().lo);
1650 fq result_y(result_point.y().get_value().lo);
1651
1652 EXPECT_EQ(result_x, expected_point.x);
1653 EXPECT_EQ(result_y, expected_point.y);
1654
1656 };
1657 test_repeated_points(2);
1658 test_repeated_points(3);
1659 test_repeated_points(4);
1660 test_repeated_points(5);
1661 test_repeated_points(6);
1662 test_repeated_points(7);
1663 }
1665 {
1666 {
1667 // batch oo + P = P
1669 points.push_back(affine_element::infinity());
1670 points.push_back(affine_element(element::random_element()));
1671 std::vector<fr> scalars;
1672 scalars.push_back(1);
1673 scalars.push_back(1);
1674
1676 ASSERT_EQ(points.size(), scalars.size());
1677 const size_t num_points = points.size();
1678
1679 std::vector<element_ct> circuit_points;
1680 std::vector<scalar_ct> circuit_scalars;
1681 for (size_t i = 0; i < num_points; ++i) {
1682 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1683 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1684 }
1685
1686 element_ct result_point =
1687 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1688
1689 element expected_point = points[1];
1690 expected_point = expected_point.normalize();
1691
1692 fq result_x(result_point.x().get_value().lo);
1693 fq result_y(result_point.y().get_value().lo);
1694
1695 EXPECT_EQ(result_x, expected_point.x);
1696 EXPECT_EQ(result_y, expected_point.y);
1697
1699 }
1700 {
1701 // batch 0 * P1 + P2 = P2
1703 points.push_back(affine_element(element::random_element()));
1704 points.push_back(affine_element(element::random_element()));
1705 std::vector<fr> scalars;
1706 scalars.push_back(0);
1707 scalars.push_back(1);
1708
1710 ASSERT_EQ(points.size(), scalars.size());
1711 const size_t num_points = points.size();
1712
1713 std::vector<element_ct> circuit_points;
1714 std::vector<scalar_ct> circuit_scalars;
1715 for (size_t i = 0; i < num_points; ++i) {
1716 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1717 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1718 }
1719
1720 element_ct result_point =
1721 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1722
1723 element expected_point = points[1];
1724 expected_point = expected_point.normalize();
1725
1726 fq result_x(result_point.x().get_value().lo);
1727 fq result_y(result_point.y().get_value().lo);
1728
1729 EXPECT_EQ(result_x, expected_point.x);
1730 EXPECT_EQ(result_y, expected_point.y);
1731
1733 }
1734 }
1735
1736 // Test batch_mul with all points at infinity
1738 {
1741 std::vector<fr> scalars;
1742
1743 for (size_t i = 0; i < 5; ++i) {
1744 points.push_back(affine_element::infinity());
1745 scalars.push_back(fr::random_element());
1746 }
1747
1748 std::vector<element_ct> circuit_points;
1749 std::vector<scalar_ct> circuit_scalars;
1750
1751 for (size_t i = 0; i < points.size(); ++i) {
1752 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1753 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1754 }
1755
1756 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1757
1758 // Result should be point at infinity
1759 EXPECT_TRUE(is_infinity(result));
1761 }
1762
1763 // Test batch_mul with all zero scalars
1765 {
1768 std::vector<fr> scalars;
1769
1770 for (size_t i = 0; i < 5; ++i) {
1771 points.push_back(affine_element(element::random_element()));
1772 scalars.push_back(fr::zero());
1773 }
1774
1775 std::vector<element_ct> circuit_points;
1776 std::vector<scalar_ct> circuit_scalars;
1777
1778 for (size_t i = 0; i < points.size(); ++i) {
1779 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1780 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1781 }
1782
1783 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1784
1785 // Result should be point at infinity
1786 EXPECT_TRUE(is_infinity(result));
1788 }
1789
1790 // Test batch_mul with mixed zero and non-zero scalars
1792 {
1795 std::vector<fr> scalars;
1796
1797 for (size_t i = 0; i < 6; ++i) {
1798 points.push_back(affine_element(element::random_element()));
1799 // Alternate between zero and non-zero scalars
1800 scalars.push_back((i % 2 == 0) ? fr::zero() : fr::random_element());
1801 }
1802
1803 std::vector<element_ct> circuit_points;
1804 std::vector<scalar_ct> circuit_scalars;
1805
1806 for (size_t i = 0; i < points.size(); ++i) {
1807 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1808 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1809 }
1810
1811 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1812
1813 // Compute expected result
1814 element expected = element::infinity();
1815 for (size_t i = 0; i < points.size(); ++i) {
1816 expected += (element(points[i]) * scalars[i]);
1817 }
1818 affine_element expected_affine = affine_element(expected);
1819
1820 uint256_t result_x = result.x().get_value().lo;
1821 uint256_t result_y = result.y().get_value().lo;
1822
1823 EXPECT_EQ(fq(result_x), expected_affine.x);
1824 EXPECT_EQ(fq(result_y), expected_affine.y);
1825
1827 }
1828
1829 // Test batch_mul with mixed infinity and valid points
1831 {
1834 std::vector<fr> scalars;
1835
1836 for (size_t i = 0; i < 6; ++i) {
1837 // Alternate between infinity and valid points
1838 points.push_back((i % 2 == 0) ? affine_element::infinity() : affine_element(element::random_element()));
1839 scalars.push_back(fr::random_element());
1840 }
1841
1842 std::vector<element_ct> circuit_points;
1843 std::vector<scalar_ct> circuit_scalars;
1844
1845 for (size_t i = 0; i < points.size(); ++i) {
1846 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1847 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1848 }
1849
1850 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1851
1852 // Compute expected result
1853 element expected = element::infinity();
1854 for (size_t i = 0; i < points.size(); ++i) {
1855 if (!points[i].is_point_at_infinity()) {
1856 expected += (element(points[i]) * scalars[i]);
1857 }
1858 }
1859 affine_element expected_affine = affine_element(expected);
1860
1861 uint256_t result_x = result.x().get_value().lo;
1862 uint256_t result_y = result.y().get_value().lo;
1863
1864 EXPECT_EQ(fq(result_x), expected_affine.x);
1865 EXPECT_EQ(fq(result_y), expected_affine.y);
1866
1868 }
1869
1870 // Test batch_mul with points that cancel out
1872 {
1875 std::vector<fr> scalars;
1876
1877 // Add P and -P with same scalar
1878 affine_element P(element::random_element());
1880 fr scalar = fr::random_element();
1881
1882 points.push_back(P);
1883 scalars.push_back(scalar);
1884 points.push_back(neg_P);
1885 scalars.push_back(scalar);
1886
1887 // Add some other points to make it non-trivial
1888 for (size_t i = 0; i < 3; ++i) {
1889 points.push_back(affine_element(element::random_element()));
1890 scalars.push_back(fr::random_element());
1891 }
1892
1893 std::vector<element_ct> circuit_points;
1894 std::vector<scalar_ct> circuit_scalars;
1895
1896 for (size_t i = 0; i < points.size(); ++i) {
1897 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1898 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1899 }
1900
1901 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1902
1903 // Compute expected result
1904 element expected = element::infinity();
1905 for (size_t i = 0; i < points.size(); ++i) {
1906 expected += (element(points[i]) * scalars[i]);
1907 }
1908 affine_element expected_affine = affine_element(expected);
1909
1910 uint256_t result_x = result.x().get_value().lo;
1911 uint256_t result_y = result.y().get_value().lo;
1912
1913 EXPECT_EQ(fq(result_x), expected_affine.x);
1914 EXPECT_EQ(fq(result_y), expected_affine.y);
1915
1917 }
1918
1919 // Test batch_mul with constant and witness points mixed
1921 {
1923 std::vector<affine_element> points_native;
1924 std::vector<fr> scalars_native;
1925 std::vector<element_ct> circuit_points;
1926 std::vector<scalar_ct> circuit_scalars;
1927
1928 // Add constant-constant points
1929 for (size_t i = 0; i < 3; ++i) {
1930 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
1931 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
1932 points_native.push_back(point);
1933 scalars_native.push_back(scalar);
1934 circuit_points.push_back(point_ct); // Constant
1935 circuit_scalars.push_back(scalar_ct); // Constant
1936 }
1937
1938 // Add witness-witness points
1939 for (size_t i = 0; i < 3; ++i) {
1940 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
1941 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
1942 points_native.push_back(point);
1943 scalars_native.push_back(scalar);
1944 circuit_points.push_back(point_ct); // Witness
1945 circuit_scalars.push_back(scalar_ct); // Witness
1946 }
1947
1948 // Add constant-witness points
1949 for (size_t i = 0; i < 4; ++i) {
1950 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
1951 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
1952 points_native.push_back(point);
1953 scalars_native.push_back(scalar);
1954 circuit_points.push_back(element_ct(point.x, point.y)); // Constant
1955 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalar)); // Witness
1956 }
1957
1958 // Add witness-constant points
1959 for (size_t i = 0; i < 4; ++i) {
1960 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
1961 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
1962 points_native.push_back(point);
1963 scalars_native.push_back(scalar);
1964 circuit_points.push_back(point_ct); // Witness
1965 circuit_scalars.push_back(scalar_ct); // Constant
1966 }
1967
1968 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
1969
1970 // Compute expected result
1971 element expected = element::infinity();
1972 for (size_t i = 0; i < points_native.size(); ++i) {
1973 expected += (element(points_native[i]) * scalars_native[i]);
1974 }
1975 affine_element expected_affine = affine_element(expected);
1976
1977 uint256_t result_x = result.x().get_value().lo;
1978 uint256_t result_y = result.y().get_value().lo;
1979
1980 EXPECT_EQ(fq(result_x), expected_affine.x);
1981 EXPECT_EQ(fq(result_y), expected_affine.y);
1982
1984 }
1985
1986 // Test batch_mul with large number of points (stress test)
1988 {
1991 std::vector<fr> scalars;
1992 constexpr size_t num_points = 20;
1993
1994 for (size_t i = 0; i < num_points; ++i) {
1995 points.push_back(affine_element(element::random_element()));
1996 scalars.push_back(fr::random_element());
1997 }
1998
1999 std::vector<element_ct> circuit_points;
2000 std::vector<scalar_ct> circuit_scalars;
2001
2002 for (size_t i = 0; i < points.size(); ++i) {
2003 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2004 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2005 }
2006
2007 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
2008
2009 // Compute expected result
2010 element expected = element::infinity();
2011 for (size_t i = 0; i < points.size(); ++i) {
2012 expected += (element(points[i]) * scalars[i]);
2013 }
2014 affine_element expected_affine = affine_element(expected);
2015
2016 uint256_t result_x = result.x().get_value().lo;
2017 uint256_t result_y = result.y().get_value().lo;
2018
2019 EXPECT_EQ(fq(result_x), expected_affine.x);
2020 EXPECT_EQ(fq(result_y), expected_affine.y);
2021
2023 }
2024
2025 // Test that infinity representation is canonical (x=0, y=0) after all operations
2027 {
2029
2030 // Case 1: constant_infinity() returns canonical form
2031 {
2032 element_ct inf = element_ct::constant_infinity(&builder);
2033 EXPECT_TRUE(is_infinity(inf));
2034 // Verify coordinates are (0, 0)
2035 EXPECT_EQ(fq(inf.x().get_value().lo), fq(0));
2036 EXPECT_EQ(fq(inf.y().get_value().lo), fq(0));
2037 }
2038
2039 // Case 2: P + (-P) = infinity with canonical coords
2040 {
2041 affine_element input(element::random_element());
2042 element_ct P = element_ct::from_witness(&builder, input);
2043 element_ct neg_P = -P;
2044 element_ct result = P + neg_P;
2045
2046 EXPECT_TRUE(is_infinity(result));
2047 // After standardization, coordinates should be (0, 0)
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 3: P - P = infinity with canonical coords
2053 {
2054 affine_element input(element::random_element());
2055 element_ct P = element_ct::from_witness(&builder, input);
2056 element_ct result = P - P;
2057
2058 EXPECT_TRUE(is_infinity(result));
2059 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2060 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2061 }
2062
2063 // Case 4: infinity + infinity = infinity with canonical coords
2064 {
2065 element_ct inf1 = element_ct::constant_infinity(&builder);
2066 element_ct inf2 = element_ct::constant_infinity(&builder);
2067 element_ct result = inf1 + inf2;
2068
2069 EXPECT_TRUE(is_infinity(result));
2070 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2071 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2072 }
2073
2074 // Case 5: 2 * infinity = infinity with canonical coords
2075 {
2076 element_ct inf = element_ct::constant_infinity(&builder);
2077 element_ct result = inf.dbl();
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
2085 }
2086
2087 // Test chained operations involving infinity
2089 {
2091
2092 // (a + infinity) - a = infinity
2093 {
2094 affine_element input(element::random_element());
2095 element_ct a = element_ct::from_witness(&builder, input);
2096 element_ct inf = element_ct::constant_infinity(&builder);
2097
2098 element_ct temp = a + inf;
2099 element_ct result = temp - a;
2100
2101 EXPECT_TRUE(is_infinity(result));
2102 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2103 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2104 }
2105
2106 // a + (b - b) = a
2107 {
2108 affine_element input_a(element::random_element());
2109 affine_element input_b(element::random_element());
2110 element_ct a = element_ct::from_witness(&builder, input_a);
2111 element_ct b = element_ct::from_witness(&builder, input_b);
2112
2113 element_ct zero = b - b; // Should be infinity
2114 element_ct result = a + zero;
2115
2116 // Result should equal a
2117 EXPECT_EQ(fq(result.x().get_value().lo), input_a.x);
2118 EXPECT_EQ(fq(result.y().get_value().lo), input_a.y);
2119 EXPECT_FALSE(is_infinity(result));
2120 }
2121
2122 // (infinity - infinity) + a = a
2123 {
2124 affine_element input(element::random_element());
2125 element_ct a = element_ct::from_witness(&builder, input);
2126 element_ct inf1 = element_ct::constant_infinity(&builder);
2127 element_ct inf2 = element_ct::constant_infinity(&builder);
2128
2129 element_ct zero = inf1 - inf2;
2130 element_ct result = zero + a;
2131
2132 EXPECT_EQ(fq(result.x().get_value().lo), input.x);
2133 EXPECT_EQ(fq(result.y().get_value().lo), input.y);
2134 }
2135
2137 }
2138
2139 // Test conditional_select with infinity points
2141 {
2143
2144 affine_element input_a(element::random_element());
2145 element_ct a = element_ct::from_witness(&builder, input_a);
2146 element_ct inf = element_ct::constant_infinity(&builder);
2147
2148 // Case 1: Select finite point when predicate is false
2149 {
2150 bool_ct pred(witness_ct(&builder, false));
2151 element_ct result = a.conditional_select(inf, pred);
2152
2153 EXPECT_FALSE(is_infinity(result));
2154 EXPECT_EQ(fq(result.x().get_value().lo), input_a.x);
2155 EXPECT_EQ(fq(result.y().get_value().lo), input_a.y);
2156 }
2157
2158 // Case 2: Select infinity when predicate is true
2159 {
2160 bool_ct pred(witness_ct(&builder, true));
2161 element_ct result = a.conditional_select(inf, pred);
2162
2163 EXPECT_TRUE(is_infinity(result));
2164 }
2165
2166 // Case 3: Select between two infinity points
2167 {
2168 element_ct inf2 = element_ct::constant_infinity(&builder);
2169 bool_ct pred(witness_ct(&builder, true));
2170 element_ct result = inf.conditional_select(inf2, pred);
2171
2172 EXPECT_TRUE(is_infinity(result));
2173 }
2174
2176 }
2177
2178 // Test conditional_negate with infinity
2180 {
2182
2183 element_ct inf = element_ct::constant_infinity(&builder);
2184
2185 // Negating infinity should still be infinity
2186 {
2187 bool_ct pred(witness_ct(&builder, true));
2188 element_ct result = inf.conditional_negate(pred);
2189
2190 EXPECT_TRUE(is_infinity(result));
2191 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2192 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2193 }
2194
2195 // Not negating infinity should still be infinity
2196 {
2197 bool_ct pred(witness_ct(&builder, false));
2198 element_ct result = inf.conditional_negate(pred);
2199
2200 EXPECT_TRUE(is_infinity(result));
2201 }
2202
2204 }
2205
2206 // Test get_standard_form preserves canonical infinity representation
2208 {
2210
2211 // Use constant_infinity() factory to create canonical infinity with (0, 0) coordinates
2212 // Note: We no longer support non-canonical infinity representations (points with
2213 // random coords but is_infinity=true) through the public API
2214 element_ct P = element_ct::constant_infinity(&builder);
2215
2216 // Canonical infinity has (0, 0) coordinates
2217 EXPECT_EQ(fq(P.x().get_value().lo), fq(0));
2218 EXPECT_EQ(fq(P.y().get_value().lo), fq(0));
2219 EXPECT_TRUE(is_infinity(P));
2220
2221 // After standardization, coords should still be (0, 0)
2222 element_ct standardized = P.get_standard_form();
2223 EXPECT_TRUE(is_infinity(standardized));
2224 EXPECT_EQ(fq(standardized.x().get_value().lo), fq(0));
2225 EXPECT_EQ(fq(standardized.y().get_value().lo), fq(0));
2226
2228 }
2229
2230 // Test auto-detection of infinity in 2-argument constructor
2232 {
2234
2235 // Create element with (0, 0) coordinates - should auto-detect as infinity
2236 auto x_zero = element_ct::BaseField::from_witness(&builder, fq(0));
2237 auto y_zero = element_ct::BaseField::from_witness(&builder, fq(0));
2238
2239 element_ct point(x_zero, y_zero);
2240
2241 EXPECT_TRUE(is_infinity(point));
2242
2244 }
2245
2246 // Test scalar multiplication edge cases with infinity
2248 {
2250
2251 // Case 1: 0 * P = infinity
2252 {
2253 affine_element input(element::random_element());
2254 element_ct P = element_ct::from_witness(&builder, input);
2255 scalar_ct zero = scalar_ct::from_witness(&builder, fr(0));
2256
2257 element_ct result = P * zero;
2258 EXPECT_TRUE(is_infinity(result));
2259 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2260 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2261 }
2262
2263 // Case 2: k * infinity = infinity
2264 {
2265 element_ct inf = element_ct::constant_infinity(&builder);
2266 fr scalar_val = fr::random_element();
2267 scalar_ct k = scalar_ct::from_witness(&builder, scalar_val);
2268
2269 element_ct result = inf * k;
2270 EXPECT_TRUE(is_infinity(result));
2271 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2272 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2273 }
2274
2275 // Case 3: 0 * infinity = infinity
2276 {
2277 element_ct inf = element_ct::constant_infinity(&builder);
2278 scalar_ct zero = scalar_ct::from_witness(&builder, fr(0));
2279
2280 element_ct result = inf * zero;
2281 EXPECT_TRUE(is_infinity(result));
2282 }
2283
2285 }
2286
2287 // Test batch_mul where result cancels to infinity
2289 {
2291
2292 // P*a + Q*b + P*(-a) + Q*(-b) = infinity
2293 affine_element P(element::random_element());
2294 affine_element Q(element::random_element());
2297
2298 std::vector<element_ct> points = {
2299 element_ct::from_witness(&builder, P),
2300 element_ct::from_witness(&builder, Q),
2301 element_ct::from_witness(&builder, P),
2302 element_ct::from_witness(&builder, Q),
2303 };
2304
2305 std::vector<scalar_ct> scalars = { scalar_ct::from_witness(&builder, a),
2306 scalar_ct::from_witness(&builder, b),
2307 scalar_ct::from_witness(&builder, -a),
2308 scalar_ct::from_witness(&builder, -b) };
2309
2310 element_ct result = element_ct::batch_mul(points, scalars, 0, true);
2311
2312 EXPECT_TRUE(is_infinity(result));
2313 EXPECT_EQ(fq(result.x().get_value().lo), fq(0));
2314 EXPECT_EQ(fq(result.y().get_value().lo), fq(0));
2315
2317 }
2318
2319 // Test addition with constant infinity
2321 {
2323
2324 // P + constant_infinity = P
2325 affine_element input(element::random_element());
2326 element_ct P = element_ct::from_witness(&builder, input);
2327 element_ct const_inf = element_ct::constant_infinity(&builder); // This is a constant
2328
2329 element_ct result = P + const_inf;
2330
2331 EXPECT_FALSE(is_infinity(result));
2332 EXPECT_EQ(fq(result.x().get_value().lo), input.x);
2333 EXPECT_EQ(fq(result.y().get_value().lo), input.y);
2334
2335 // constant_infinity + P = P
2336 element_ct result2 = const_inf + P;
2337 EXPECT_FALSE(is_infinity(result2));
2338 EXPECT_EQ(fq(result2.x().get_value().lo), input.x);
2339 EXPECT_EQ(fq(result2.y().get_value().lo), input.y);
2340
2342 }
2343
2344 // Test that witness infinity points (created via operations) work correctly
2346 {
2348
2349 // Create infinity as P - P (witness-based infinity)
2350 affine_element input(element::random_element());
2351 element_ct P = element_ct::from_witness(&builder, input);
2352 element_ct witness_inf = P - P;
2353
2354 // Use this witness infinity in operations
2355 affine_element input2(element::random_element());
2356 element_ct Q = element_ct::from_witness(&builder, input2);
2357
2358 // Q + witness_inf = Q
2359 element_ct result = Q + witness_inf;
2360 EXPECT_EQ(fq(result.x().get_value().lo), input2.x);
2361 EXPECT_EQ(fq(result.y().get_value().lo), input2.y);
2362
2363 // witness_inf + Q = Q
2364 element_ct result2 = witness_inf + Q;
2365 EXPECT_EQ(fq(result2.x().get_value().lo), input2.x);
2366 EXPECT_EQ(fq(result2.y().get_value().lo), input2.y);
2367
2369 }
2370};
2371
2372// bn254 with ultra arithmetisation where scalar field is native field, base field is non-native field (bigfield)
2374
2375// bn254 with ultra arithmetisation where both scalar and base fields are non-native fields
2378
2379// bn254 with mega arithmetisation where scalar field is native field, base field is non-native field
2381
2382// secp256r1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2385
2386// secp256k1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2389
2390using TestTypes = testing::Types<bn254_with_ultra,
2395
2397
2398TYPED_TEST(stdlib_biggroup, validate_on_curve)
2399{
2401 // Goblin points do not implement validate on curve
2402 if constexpr (!HasGoblinBuilder<TypeParam>) {
2403 using Builder = TestFixture::Builder;
2404 using element_ct = TestFixture::element_ct;
2405 using Fq = TestFixture::Curve::BaseField;
2406 using FqNative = TestFixture::Curve::BaseFieldNative;
2407 using GroupNative = TestFixture::Curve::GroupNative;
2408
2410 auto [native_point, witness_point] = TestFixture::get_random_witness_point(&builder);
2411
2412 // Valid point
2413 Fq expected_zero = witness_point.validate_on_curve("biggroup::validate_on_curve", false);
2414 expected_zero.assert_equal(Fq::zero());
2415 EXPECT_EQ(expected_zero.get_value(), static_cast<uint512_t>(FqNative::zero()));
2416
2417 // Invalid point
2418 Fq random_x = Fq::from_witness(&builder, FqNative::random_element());
2419 Fq random_y = Fq::from_witness(&builder, FqNative::random_element());
2420 element_ct invalid_point(random_x, random_y, /*assert_on_curve*/ false);
2421 Fq expected_non_zero = invalid_point.validate_on_curve("biggroup::validate_on_curve", false);
2422 Fq expected_value = -random_y.sqr() + random_x.pow(3) + Fq(uint256_t(GroupNative::curve_b));
2423 if constexpr (GroupNative::has_a) {
2424 expected_value += random_x * Fq(uint256_t(GroupNative::curve_a));
2425 }
2426 expected_non_zero.assert_equal(expected_value);
2427
2428 // Reduce the value to remove constants
2429 expected_non_zero.self_reduce();
2430 expected_value.self_reduce();
2431 EXPECT_EQ(expected_non_zero.get_value(), expected_value.get_value());
2432
2433 TestFixture::EXPECT_CIRCUIT_CORRECTNESS(builder);
2434
2435 // Check that the circuit fails if validate_on_curve is called with default parameters
2436 [[maybe_unused]] Fq _ = invalid_point.validate_on_curve();
2437 TestFixture::EXPECT_CIRCUIT_CORRECTNESS(builder, false);
2438 }
2439}
2440
2442{
2443 TestFixture::test_basic_tag_logic();
2444}
2445
2446TYPED_TEST(stdlib_biggroup, assert_coordinates_in_field)
2447{
2448 TestFixture::test_assert_coordinates_in_field();
2449}
2450
2451// Addition tests
2453{
2454 TestFixture::test_add();
2455}
2456TYPED_TEST(stdlib_biggroup, add_with_constants)
2457{
2458 TestFixture::test_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2459 TestFixture::test_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2460 TestFixture::test_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2461}
2462TYPED_TEST(stdlib_biggroup, add_points_at_infinity)
2463{
2464 TestFixture::test_add_points_at_infinity();
2465}
2466TYPED_TEST(stdlib_biggroup, standard_form_of_point_at_infinity)
2467{
2468 TestFixture::test_standard_form_of_point_at_infinity();
2469}
2470
2471// Subtraction tests
2473{
2474 TestFixture::test_sub();
2475}
2476TYPED_TEST(stdlib_biggroup, sub_with_constants)
2477{
2478 TestFixture::test_sub(InputType::WITNESS, InputType::CONSTANT); // w - c
2479 TestFixture::test_sub(InputType::CONSTANT, InputType::WITNESS); // c - w
2480 TestFixture::test_sub(InputType::CONSTANT, InputType::CONSTANT); // c - c
2481}
2482TYPED_TEST(stdlib_biggroup, sub_points_at_infinity)
2483{
2484 TestFixture::test_sub_points_at_infinity();
2485}
2487{
2488 TestFixture::test_dbl();
2489}
2490TYPED_TEST(stdlib_biggroup, dbl_with_constant)
2491{
2492 TestFixture::test_dbl(InputType::CONSTANT); // dbl(c)
2493}
2494TYPED_TEST(stdlib_biggroup, dbl_with_infinity)
2495{
2496 TestFixture::test_dbl_with_infinity();
2497}
2499{
2500 if constexpr (HasGoblinBuilder<TypeParam>) {
2501 GTEST_SKIP() << "mega builder does not support this edge case";
2502 } else {
2503 TestFixture::test_dbl_with_y_zero();
2504 }
2505}
2507{
2508 TestFixture::test_add_equals_dbl();
2509}
2510TYPED_TEST(stdlib_biggroup, sub_neg_equals_double)
2511{
2512 TestFixture::test_sub_neg_equals_double();
2513}
2514
2515// Test chain_add
2517{
2518 if constexpr (HasGoblinBuilder<TypeParam>) {
2519 GTEST_SKIP() << "mega builder does not implement chain_add function";
2520 } else {
2521 TestFixture::test_chain_add();
2522 };
2523}
2524HEAVY_TYPED_TEST(stdlib_biggroup, chain_add_with_constants)
2525{
2526 if constexpr (HasGoblinBuilder<TypeParam>) {
2527 GTEST_SKIP() << "mega builder does not implement chain_add function";
2528 } else {
2529 TestFixture::test_chain_add(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2530 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2531 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2532 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2533 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2534 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2535 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2536 }
2537}
2538
2539// Test multiple_montgomery_ladder
2540HEAVY_TYPED_TEST(stdlib_biggroup, multiple_montgomery_ladder)
2541{
2542
2543 if constexpr (HasGoblinBuilder<TypeParam>) {
2544 GTEST_SKIP() << "mega builder does not implement multiple_montgomery_ladder function";
2545 } else {
2546 TestFixture::test_multiple_montgomery_ladder();
2547 };
2548}
2549
2550// Test normalize
2552{
2553 TestFixture::test_normalize();
2554}
2555TYPED_TEST(stdlib_biggroup, normalize_constant)
2556{
2557 TestFixture::test_normalize(InputType::CONSTANT);
2558}
2559
2560// Test reduce
2562{
2563 TestFixture::test_reduce();
2564}
2566{
2567 TestFixture::test_reduce(InputType::CONSTANT);
2568}
2569
2570// Test unary negation
2572{
2573 TestFixture::test_unary_negate(InputType::WITNESS);
2574}
2575
2576TYPED_TEST(stdlib_biggroup, unary_negate_with_constants)
2577{
2578 TestFixture::test_unary_negate(InputType::CONSTANT);
2579}
2580
2581// Test operator+=
2583{
2584 TestFixture::test_add_assign(InputType::WITNESS, InputType::WITNESS);
2585}
2586
2587TYPED_TEST(stdlib_biggroup, add_assign_with_constants)
2588{
2589 TestFixture::test_add_assign(InputType::WITNESS, InputType::CONSTANT); // w += c
2590 TestFixture::test_add_assign(InputType::CONSTANT, InputType::WITNESS); // c += w
2591}
2592
2593// Test operator-=
2595{
2596 TestFixture::test_sub_assign(InputType::WITNESS, InputType::WITNESS);
2597}
2598TYPED_TEST(stdlib_biggroup, sub_assign_with_constants)
2599{
2600 TestFixture::test_sub_assign(InputType::WITNESS, InputType::CONSTANT); // w -= c
2601 TestFixture::test_sub_assign(InputType::CONSTANT, InputType::WITNESS); // c -= w
2602}
2603// Test checked_unconditional_add
2604TYPED_TEST(stdlib_biggroup, checked_unconditional_add)
2605{
2606 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::WITNESS);
2607}
2608TYPED_TEST(stdlib_biggroup, checked_unconditional_add_with_constants)
2609{
2610 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2611 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2612 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2613}
2614// Test checked_unconditional_subtract
2615TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract)
2616{
2617 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::WITNESS);
2618}
2619TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract_with_constants)
2620{
2621 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::CONSTANT); // w - c
2622 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::WITNESS); // c - w
2623 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::CONSTANT); // c - c
2624}
2625// Test checked_unconditional_add_sub
2626TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub)
2627{
2628 TestFixture::test_checked_unconditional_add_sub();
2629}
2630TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub_with_constants)
2631{
2632 TestFixture::test_checked_unconditional_add_sub(InputType::WITNESS, InputType::CONSTANT); // w, c
2633 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::WITNESS); // c, w
2634 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::CONSTANT); // c, c
2635}
2636// Test conditional_negate
2637TYPED_TEST(stdlib_biggroup, conditional_negate)
2638{
2639 TestFixture::test_conditional_negate();
2640}
2641TYPED_TEST(stdlib_biggroup, conditional_negate_with_constants)
2642{
2643 TestFixture::test_conditional_negate(InputType::WITNESS, InputType::CONSTANT); // w, c
2644 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::WITNESS); // c, w
2645 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::CONSTANT); // c, c
2646}
2647// Test conditional_select
2648TYPED_TEST(stdlib_biggroup, conditional_select)
2649{
2650 TestFixture::test_conditional_select();
2651}
2652TYPED_TEST(stdlib_biggroup, conditional_select_with_constants)
2653{
2654 TestFixture::test_conditional_select(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2655 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2656 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2657 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2658 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2659 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2660 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2661}
2662TYPED_TEST(stdlib_biggroup, incomplete_assert_equal)
2663{
2664 TestFixture::test_incomplete_assert_equal();
2665}
2666TYPED_TEST(stdlib_biggroup, incomplete_assert_equal_fails)
2667{
2668 TestFixture::test_incomplete_assert_equal_failure();
2669}
2670
2672{
2673 if constexpr (!HasGoblinBuilder<TypeParam>) {
2674 size_t num_repetitions = 1;
2675 for (size_t i = 0; i < num_repetitions; i++) {
2676 TestFixture::test_compute_naf();
2677 }
2678 } else {
2679 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2680 }
2681}
2682
2684{
2685 if constexpr (!HasGoblinBuilder<TypeParam>) {
2686 TestFixture::test_compute_naf_zero();
2687 } else {
2688 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2689 }
2690}
2691
2692HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf_overflow_lower_half)
2693{
2694 if constexpr (!HasGoblinBuilder<TypeParam>) {
2695 TestFixture::test_compute_naf_overflow_lower_half();
2696 } else {
2697 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2698 }
2699}
2700
2702{
2703 TestFixture::test_mul();
2704}
2706{
2707 TestFixture::test_mul(InputType::WITNESS, InputType::CONSTANT); // w * c
2708 TestFixture::test_mul(InputType::CONSTANT, InputType::WITNESS); // c * w
2709 TestFixture::test_mul(InputType::CONSTANT, InputType::CONSTANT); // c * c
2710}
2712{
2713 TestFixture::test_mul_edge_cases();
2714}
2715HEAVY_TYPED_TEST(stdlib_biggroup, mul_edge_cases_with_constants)
2716{
2717 TestFixture::test_mul_edge_cases(InputType::WITNESS, InputType::CONSTANT); // w * c
2718 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::WITNESS); // c * w
2719 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::CONSTANT); // c * c
2720}
2721
2722HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_with_bit_lengths)
2723{
2724 if constexpr (HasGoblinBuilder<TypeParam>) {
2725 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2726 } else {
2727 TestFixture::test_short_scalar_mul_with_bit_lengths();
2728 }
2729}
2730
2731HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_infinity)
2732{
2733 if constexpr (HasGoblinBuilder<TypeParam>) {
2734 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2735 } else {
2736 TestFixture::test_short_scalar_mul_infinity();
2737 }
2738}
2739
2740// Batch multiplication tests
2741// 1 point - Base case only
2743{
2744 TestFixture::test_helper_batch_mul(1);
2745}
2746
2747// 2 points - Base case + flag variations + one constant mix
2749{
2750 TestFixture::test_helper_batch_mul(2);
2751}
2752HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars)
2753{
2754 TestFixture::test_helper_batch_mul(2, true); // short_scalars
2755}
2756HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_with_edgecases)
2757{
2758 TestFixture::test_helper_batch_mul(2, false, true); // short_scalars, with_edgecases
2759}
2760HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars_with_edgecases)
2761{
2762 TestFixture::test_helper_batch_mul(2, true, true); // short_scalars, with_edgecases
2763}
2764HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_mixed_constants)
2765{
2766 TestFixture::test_helper_batch_mul({ InputType::WITNESS, InputType::CONSTANT },
2768}
2769
2770// 3 points - Base case only
2772{
2773 TestFixture::test_helper_batch_mul(3);
2774}
2775
2776// 4 points - Base case only
2778{
2779 TestFixture::test_helper_batch_mul(4);
2780}
2781
2782// 5 points - Base case + edge case + short scalar + mixed constant
2784{
2785 TestFixture::test_helper_batch_mul(5);
2786}
2787HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_with_edgecases)
2788{
2789 TestFixture::test_helper_batch_mul(5, false, true); // short_scalars, with_edgecases
2790}
2791HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars)
2792{
2793 TestFixture::test_helper_batch_mul(5, true); // short_scalars
2794}
2795HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars_with_edgecases)
2796{
2797 TestFixture::test_helper_batch_mul(5, true, true); // short_scalars, with_edgecases
2798}
2805
2806// 6 points - Base case only
2808{
2809 TestFixture::test_helper_batch_mul(6);
2810}
2811
2813{
2814 TestFixture::test_twin_mul();
2815}
2816
2817HEAVY_TYPED_TEST(stdlib_biggroup, twin_mul_with_infinity)
2818{
2819 TestFixture::test_twin_mul_with_infinity();
2820}
2821
2822HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators)
2823{
2824 TestFixture::test_batch_mul_linearly_dependent_generators();
2825}
2826
2827HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators_failure)
2828{
2829 if constexpr (HasGoblinBuilder<TypeParam>) {
2830 GTEST_SKIP() << "this failure test is designed for ultra builder only";
2831 } else {
2832 TestFixture::test_batch_mul_linearly_dependent_generators_failure();
2833 }
2834}
2835
2837{
2838 TestFixture::test_one();
2839}
2840
2842{
2843 TestFixture::test_batch_mul();
2844}
2845
2846HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edgecase_equivalence)
2847{
2848 TestFixture::test_batch_mul_edgecase_equivalence();
2849}
2850HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set1)
2851{
2852 TestFixture::test_batch_mul_edge_case_set1();
2853}
2854
2855HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set2)
2856{
2857 TestFixture::test_batch_mul_edge_case_set2();
2858}
2859
2860// Batch mul edge case tests
2861HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_infinity)
2862{
2863 TestFixture::test_batch_mul_all_infinity();
2864}
2865
2866HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_zero_scalars)
2867{
2868 TestFixture::test_batch_mul_all_zero_scalars();
2869}
2870
2871HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_zero_scalars)
2872{
2873 TestFixture::test_batch_mul_mixed_zero_scalars();
2874}
2875
2876HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_infinity)
2877{
2878 TestFixture::test_batch_mul_mixed_infinity();
2879}
2880
2881HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_cancellation)
2882{
2883 TestFixture::test_batch_mul_cancellation();
2884}
2885
2886HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_constant_witness)
2887{
2888 TestFixture::test_batch_mul_mixed_constant_witness();
2889}
2890
2891HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_large_number_of_points)
2892{
2893 TestFixture::test_batch_mul_large_number_of_points();
2894}
2895
2896// Point at Infinity Edge Case Tests
2897TYPED_TEST(stdlib_biggroup, infinity_canonical_representation)
2898{
2899 TestFixture::test_infinity_canonical_representation();
2900}
2901
2902TYPED_TEST(stdlib_biggroup, infinity_chained_operations)
2903{
2904 TestFixture::test_infinity_chained_operations();
2905}
2906
2907TYPED_TEST(stdlib_biggroup, conditional_select_with_infinity)
2908{
2909 TestFixture::test_conditional_select_with_infinity();
2910}
2911
2912TYPED_TEST(stdlib_biggroup, conditional_negate_with_infinity)
2913{
2914 TestFixture::test_conditional_negate_with_infinity();
2915}
2916
2917TYPED_TEST(stdlib_biggroup, get_standard_form_normalizes_infinity)
2918{
2919 TestFixture::test_get_standard_form_normalizes_infinity();
2920}
2921
2922TYPED_TEST(stdlib_biggroup, infinity_auto_detection_in_constructor)
2923{
2924 TestFixture::test_infinity_auto_detection_in_constructor();
2925}
2926
2927HEAVY_TYPED_TEST(stdlib_biggroup, scalar_mul_infinity_edge_cases)
2928{
2929 TestFixture::test_scalar_mul_infinity_edge_cases();
2930}
2931
2932HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_complete_cancellation)
2933{
2934 TestFixture::test_batch_mul_complete_cancellation();
2935}
2936
2937TYPED_TEST(stdlib_biggroup, add_constant_infinity)
2938{
2939 TestFixture::test_add_constant_infinity();
2940}
2941
2942TYPED_TEST(stdlib_biggroup, witness_infinity_from_operations)
2943{
2944 TestFixture::test_witness_infinity_from_operations();
2945}
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
#define BB_DISABLE_ASSERTS()
Definition assert.hpp:33
InputType
stdlib_biggroup< TestType< stdlib::bn254< bb::UltraCircuitBuilder >, false > > bn254_with_ultra
InputType
stdlib_biggroup< TestType< stdlib::bn254< bb::UltraCircuitBuilder >, true > > bn254_with_ultra_scalar_bigfield
stdlib_biggroup< TestType< stdlib::secp256r1< bb::UltraCircuitBuilder >, true > > secp256r1_with_ultra
constexpr InputType operator!(InputType type)
stdlib_biggroup< TestType< stdlib::bn254< bb::MegaCircuitBuilder >, false > > bn254_with_mega
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 uint64_t get_random_uint64()=0
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:975
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
typename std::conditional< _use_bigfield, typename Curve::g1_bigfr_ct, typename Curve::Group >::type element_ct
typename std::conditional< _use_bigfield, typename Curve::bigfr_ct, typename Curve::ScalarField >::type scalar_ct
static const bool use_bigfield
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