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