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