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 // Create a random point as a witness
75 {
76 affine_element point_native(element::random_element());
77 element_ct point_ct = element_ct::from_witness(builder, point_native);
78 return std::make_pair(point_native, point_ct);
79 }
80
81 // Create a random point as a constant
83 {
84 affine_element point_native(element::random_element());
85 // Create constant coordinates with builder context
86 using Fq = typename element_ct::BaseField;
87 Fq x_const(builder, uint256_t(point_native.x));
88 Fq y_const(builder, uint256_t(point_native.y));
89 element_ct point_ct(x_const, y_const);
90 return std::make_pair(point_native, point_ct);
91 }
92
93 // Create a random point based on InputType
101
102 // Create a random scalar as a witness
104 {
105 fr scalar_native = fr::random_element();
106 if (even && uint256_t(scalar_native).get_bit(0)) {
107 scalar_native -= fr(1); // make it even if it's odd
108 }
109 scalar_ct scalar_ct_val = scalar_ct::from_witness(builder, scalar_native);
110 return std::make_pair(scalar_native, scalar_ct_val);
111 }
112
113 // Create a random scalar as a constant
115 {
116 fr scalar_native = fr::random_element();
117 if (even && uint256_t(scalar_native).get_bit(0)) {
118 scalar_native -= fr(1); // make it even if it's odd
119 }
120 scalar_ct scalar_ct_val = scalar_ct(builder, scalar_native);
121 return std::make_pair(scalar_native, scalar_ct_val);
122 }
123
124 // Create a random scalar based on InputType
126 {
127 if (type == InputType::WITNESS) {
129 }
131 }
132
134 {
135 uint256_t scalar_u256 = engine.get_random_uint256();
136 scalar_u256 = scalar_u256 >> (256 - num_bits); // keep only the lower num_bits bits
137
138 fr scalar_native(scalar_u256);
139 scalar_ct scalar_ct_val;
140 if (type == InputType::WITNESS) {
141 scalar_ct_val = scalar_ct::from_witness(builder, scalar_native);
142 } else {
143 scalar_ct_val = scalar_ct(builder, scalar_native);
144 }
145 return std::make_pair(scalar_native, scalar_ct_val);
146 }
147
148 public:
150 {
152 affine_element input_a(element::random_element());
153
154 element_ct a = element_ct::from_witness(&builder, input_a);
155 a.set_origin_tag(next_submitted_value_origin_tag);
156 // Tag is preserved after being set
157 EXPECT_EQ(a.get_origin_tag(), next_submitted_value_origin_tag);
158
159 // Tags from members are merged
160 // Create field elements with specific tags before constructing the biggroup element
161 affine_element input_c(element::random_element());
162 auto x = element_ct::BaseField::from_witness(&builder, input_c.x);
163 auto y = element_ct::BaseField::from_witness(&builder, input_c.y);
164 auto pif = bool_ct(witness_ct(&builder, false));
165
166 // Set tags on the individual field elements
167 x.set_origin_tag(submitted_value_origin_tag);
168 y.set_origin_tag(challenge_origin_tag);
169 pif.set_origin_tag(next_challenge_tag);
170
171 // Construct biggroup element from pre-tagged field elements
172 element_ct c(x, y, pif);
173
174 // The tag of the biggroup element should be the union of all 3 member tags
175 EXPECT_EQ(c.get_origin_tag(), first_second_third_merged_tag);
176
177#ifndef NDEBUG
178 // Test that instant_death_tag on x coordinate propagates correctly
179 affine_element input_b(element::random_element());
180 auto x_death = element_ct::BaseField::from_witness(&builder, input_b.x);
181 auto y_normal = element_ct::BaseField::from_witness(&builder, input_b.y);
182 auto pif_normal = bool_ct(witness_ct(&builder, false));
183
184 x_death.set_origin_tag(instant_death_tag);
185 // Set constant tags on the other elements so they can be merged with instant_death_tag
186 y_normal.set_origin_tag(constant_tag);
187 pif_normal.set_origin_tag(constant_tag);
188
189 // Use assert_on_curve=false to avoid triggering instant_death during validate_on_curve()
190 element_ct b(x_death, y_normal, pif_normal, /*assert_on_curve=*/false);
191 // Working with instant death tagged element causes an exception
192 EXPECT_THROW(b + b, std::runtime_error);
193#endif
194 }
195
197 {
198 // Only test for non-goblin builders (goblin elements don't have assert_coordinates_in_field
199 // because coordinate checks are done in the ECCVM circuit)
200 if constexpr (!HasGoblinBuilder<TestType>) {
201 // Test 1: Valid coordinates should pass
202 {
204
205 // Test multiple random points to ensure assert_coordinates_in_field works correctly
206 for (size_t i = 0; i < 3; ++i) {
207 affine_element valid_point(element::random_element());
208 element_ct point = element_ct::from_witness(&builder, valid_point);
209
210 // This should not fail - coordinates are in field
211 point.assert_coordinates_in_field();
212 }
213
214 // Verify the circuit is correct
216 }
217
218 // Test 2: Invalid x coordinate should cause circuit to fail
219 {
221 affine_element valid_point(element::random_element());
222
223 // Create a bigfield element with x coordinate that will be out of range
224 // We do this by creating a valid witness but then manipulating the limb values
225 // to make them represent a value >= the modulus
226 auto x_coord = element_ct::BaseField::from_witness(&builder, valid_point.x);
227 auto y_coord = element_ct::BaseField::from_witness(&builder, valid_point.y);
228
229 // Manipulate the limbs to create an invalid value
230 // Set the highest limb to a very large value that would make the total >= modulus
231 x_coord.binary_basis_limbs[3].element = field_ct::from_witness(&builder, bb::fr(uint256_t(1) << 68));
232 x_coord.binary_basis_limbs[3].maximum_value = uint256_t(1) << 68;
233
234 // Skip curve check since we're intentionally creating an invalid point
235 element_ct point(x_coord, y_coord, bool_ct(witness_ct(&builder, false)), /*assert_on_curve=*/false);
236 point.assert_coordinates_in_field();
237
238 // Circuit should fail because x coordinate is out of field
240 }
241
242 // Test 3: Invalid y coordinate should cause circuit to fail
243 {
245 affine_element valid_point(element::random_element());
246
247 auto x_coord = element_ct::BaseField::from_witness(&builder, valid_point.x);
248 auto y_coord = element_ct::BaseField::from_witness(&builder, valid_point.y);
249
250 // Manipulate the limbs to create an invalid value
251 // Set the highest limb to a very large value that would make the total >= modulus
252 y_coord.binary_basis_limbs[3].element = field_ct::from_witness(&builder, bb::fr(uint256_t(1) << 68));
253 y_coord.binary_basis_limbs[3].maximum_value = uint256_t(1) << 68;
254
255 // Skip curve check since we're intentionally creating an invalid point
256 element_ct point(x_coord, y_coord, bool_ct(witness_ct(&builder, false)), /*assert_on_curve=*/false);
257 point.assert_coordinates_in_field();
258
259 // Circuit should fail because y coordinate is out of field
261 }
262 }
263 }
264
266 {
268 size_t num_repetitions = 10;
269 for (size_t i = 0; i < num_repetitions; ++i) {
270 auto [input_a, a] = get_random_point(&builder, a_type);
271 auto [input_b, b] = get_random_point(&builder, b_type);
272
273 // Set different tags in a and b
274 a.set_origin_tag(submitted_value_origin_tag);
275 b.set_origin_tag(challenge_origin_tag);
276
277 uint64_t before = builder.get_num_finalized_gates_inefficient();
278 element_ct c = a + b;
279 uint64_t after = builder.get_num_finalized_gates_inefficient();
280
281 // Check that the resulting tag is the union of inputs' tgs
282 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
283 if (i == num_repetitions - 1) {
284 benchmark_info(Builder::NAME_STRING, "Biggroup", "ADD", "Gate Count", after - before);
285 }
286
287 affine_element c_expected(element(input_a) + element(input_b));
288
289 uint256_t c_x_u256 = c.x().get_value().lo;
290 uint256_t c_y_u256 = c.y().get_value().lo;
291
292 fq c_x_result(c_x_u256);
293 fq c_y_result(c_y_u256);
294
295 EXPECT_EQ(c_x_result, c_expected.x);
296 EXPECT_EQ(c_y_result, c_expected.y);
297 }
298
300 }
301
303 {
305 size_t num_repetitions = 10;
306 for (size_t i = 0; i < num_repetitions; ++i) {
307 auto [input_a, a] = get_random_point(&builder, a_type);
308 auto [input_b, b] = get_random_point(&builder, b_type);
309
310 element_ct original_a = a;
311 a += b;
312
313 affine_element expected(element(input_a) + element(input_b));
314 uint256_t result_x = a.x().get_value().lo;
315 uint256_t result_y = a.y().get_value().lo;
316
317 EXPECT_EQ(fq(result_x), expected.x);
318 EXPECT_EQ(fq(result_y), expected.y);
319 }
321 }
322
324 {
326 size_t num_repetitions = 1;
327 for (size_t i = 0; i < num_repetitions; ++i) {
328 affine_element input_a(element::random_element());
329 affine_element input_b(element::random_element());
330 input_b.self_set_infinity();
331 element_ct a = element_ct::from_witness(&builder, input_a);
332
333 // create copy of a with different witness
334 element_ct a_alternate = element_ct::from_witness(&builder, input_a);
335 element_ct a_negated = element_ct::from_witness(&builder, -input_a);
336 element_ct b = element_ct::from_witness(&builder, input_b);
337
338 // Set different tags on all elements
339 a.set_origin_tag(submitted_value_origin_tag);
340 b.set_origin_tag(challenge_origin_tag);
341 a_alternate.set_origin_tag(next_challenge_tag);
342 // We can't use next_submitted_value tag here or it will break, so construct a tag manually
343 const auto second_round_challenge_tag =
344 OriginTag(/*parent_index=*/0, /*child_index=*/2, /*is_submitted=*/false);
345 a_negated.set_origin_tag(second_round_challenge_tag);
346
347 element_ct c = a + b;
348 element_ct d = b + a;
349 element_ct e = b + b;
350 element_ct f = a + a;
351 element_ct g = a + a_alternate;
352 element_ct h = a + a_negated;
353
354 // Check the resulting tags are correct unions of input tags
355 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
356 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
357 EXPECT_EQ(e.get_origin_tag(), challenge_origin_tag);
358 EXPECT_EQ(f.get_origin_tag(), submitted_value_origin_tag);
359 EXPECT_EQ(g.get_origin_tag(), first_and_third_merged_tag);
360 EXPECT_EQ(h.get_origin_tag(), OriginTag(submitted_value_origin_tag, second_round_challenge_tag));
361
362 affine_element c_expected = affine_element(element(input_a) + element(input_b));
363 affine_element d_expected = affine_element(element(input_b) + element(input_a));
364 affine_element e_expected = affine_element(element(input_b) + element(input_b));
365 affine_element f_expected = affine_element(element(input_a) + element(input_a));
366 affine_element g_expected = affine_element(element(input_a) + element(input_a));
367 affine_element h_expected = affine_element(element(input_a) + element(-input_a));
368
369 EXPECT_EQ(c.get_value(), c_expected);
370 EXPECT_EQ(d.get_value(), d_expected);
371 EXPECT_EQ(e.get_value(), e_expected);
372 EXPECT_EQ(f.get_value(), f_expected);
373 EXPECT_EQ(g.get_value(), g_expected);
374 EXPECT_EQ(h.get_value(), h_expected);
375 }
376
378 }
384 {
386 size_t num_repetitions = 5;
387 for (size_t i = 0; i < num_repetitions; ++i) {
388 // Check both constant and witness case
389 element_ct input_a(element::random_element());
390 element_ct input_b = element_ct::from_witness(&builder, element::random_element());
391 input_a.set_point_at_infinity(true);
392 input_b.set_point_at_infinity(true);
393
394 // Set tags
395 input_a.set_origin_tag(submitted_value_origin_tag);
396 input_b.set_origin_tag(challenge_origin_tag);
397
398 auto standard_a = input_a.get_standard_form();
399 auto standard_b = input_b.get_standard_form();
400
401 // Check that tags are preserved
402 EXPECT_EQ(standard_a.get_origin_tag(), submitted_value_origin_tag);
403 EXPECT_EQ(standard_b.get_origin_tag(), challenge_origin_tag);
404
405 EXPECT_EQ(standard_a.is_point_at_infinity().get_value(), true);
406 EXPECT_EQ(standard_b.is_point_at_infinity().get_value(), true);
407
408 fq standard_a_x = standard_a.x().get_value().lo;
409 fq standard_a_y = standard_a.y().get_value().lo;
410
411 fq standard_b_x = standard_b.x().get_value().lo;
412 fq standard_b_y = standard_b.y().get_value().lo;
413
414 EXPECT_EQ(standard_a_x, 0);
415 EXPECT_EQ(standard_a_y, 0);
416 EXPECT_EQ(standard_b_x, 0);
417 EXPECT_EQ(standard_b_y, 0);
418 }
419
421 }
422
424 {
426 size_t num_repetitions = 10;
427 for (size_t i = 0; i < num_repetitions; ++i) {
428 auto [input_a, a] = get_random_point(&builder, a_type);
429 auto [input_b, b] = get_random_point(&builder, b_type);
430
431 // Set tags
432 a.set_origin_tag(submitted_value_origin_tag);
433 b.set_origin_tag(challenge_origin_tag);
434
435 element_ct c = a - b;
436
437 // Check tags have merged
438 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
439
440 affine_element c_expected(element(input_a) - element(input_b));
441
442 uint256_t c_x_u256 = c.x().get_value().lo;
443 uint256_t c_y_u256 = c.y().get_value().lo;
444
445 fq c_x_result(c_x_u256);
446 fq c_y_result(c_y_u256);
447
448 EXPECT_EQ(c_x_result, c_expected.x);
449 EXPECT_EQ(c_y_result, c_expected.y);
450 }
451
453 }
454
456 {
458 size_t num_repetitions = 10;
459 for (size_t i = 0; i < num_repetitions; ++i) {
460 auto [input_a, a] = get_random_point(&builder, a_type);
461 auto [input_b, b] = get_random_point(&builder, b_type);
462
463 a -= b;
464
465 affine_element expected(element(input_a) - element(input_b));
466 uint256_t result_x = a.x().get_value().lo;
467 uint256_t result_y = a.y().get_value().lo;
468
469 EXPECT_EQ(fq(result_x), expected.x);
470 EXPECT_EQ(fq(result_y), expected.y);
471 }
473 }
474
476 {
478 size_t num_repetitions = 1;
479 for (size_t i = 0; i < num_repetitions; ++i) {
480 affine_element input_a(element::random_element());
481 affine_element input_b(element::random_element());
482 input_b.self_set_infinity();
483 element_ct a = element_ct::from_witness(&builder, input_a);
484
485 // create copy of a with different witness
486 element_ct a_alternate = element_ct::from_witness(&builder, input_a);
487 element_ct a_negated = element_ct::from_witness(&builder, -input_a);
488 element_ct b = element_ct::from_witness(&builder, input_b);
489
490 // Set different tags on all elements
491 a.set_origin_tag(submitted_value_origin_tag);
492 b.set_origin_tag(challenge_origin_tag);
493 a_alternate.set_origin_tag(next_challenge_tag);
494 // We can't use next_submitted_value tag here or it will break, so construct a tag manually
495 const auto second_round_challenge_tag =
496 OriginTag(/*parent_index=*/0, /*child_index=*/2, /*is_submitted=*/false);
497 a_negated.set_origin_tag(second_round_challenge_tag);
498
499 element_ct c = a - b;
500 element_ct d = b - a;
501 element_ct e = b - b;
502 element_ct f = a - a;
503 element_ct g = a - a_alternate;
504 element_ct h = a - a_negated;
505
506 // Check the resulting tags are correct unions of input tags
507 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
508 EXPECT_EQ(d.get_origin_tag(), first_two_merged_tag);
509 EXPECT_EQ(e.get_origin_tag(), challenge_origin_tag);
510 EXPECT_EQ(f.get_origin_tag(), submitted_value_origin_tag);
511 EXPECT_EQ(g.get_origin_tag(), first_and_third_merged_tag);
512 EXPECT_EQ(h.get_origin_tag(), OriginTag(submitted_value_origin_tag, second_round_challenge_tag));
513
514 affine_element c_expected = affine_element(element(input_a) - element(input_b));
515 affine_element d_expected = affine_element(element(input_b) - element(input_a));
516 affine_element e_expected = affine_element(element(input_b) - element(input_b));
517 affine_element f_expected = affine_element(element(input_a) - element(input_a));
518 affine_element g_expected = affine_element(element(input_a) - element(input_a));
519 affine_element h_expected = affine_element(element(input_a) - element(-input_a));
520
521 EXPECT_EQ(c.get_value(), c_expected);
522 EXPECT_EQ(d.get_value(), d_expected);
523 EXPECT_EQ(e.get_value(), e_expected);
524 EXPECT_EQ(f.get_value(), f_expected);
525 EXPECT_EQ(g.get_value(), g_expected);
526 EXPECT_EQ(h.get_value(), h_expected);
527 }
528
530 }
531
534 {
536 size_t num_repetitions = 10;
537 for (size_t i = 0; i < num_repetitions; ++i) {
538 auto [input_a, a] = get_random_point(&builder, a_type);
539 auto [input_b, b] = get_random_point(&builder, b_type);
540
541 element_ct result = a.checked_unconditional_add(b);
542
543 affine_element expected(element(input_a) + element(input_b));
544 uint256_t result_x = result.x().get_value().lo;
545 uint256_t result_y = result.y().get_value().lo;
546
547 EXPECT_EQ(fq(result_x), expected.x);
548 EXPECT_EQ(fq(result_y), expected.y);
549 }
551 }
552
555 {
557 size_t num_repetitions = 10;
558 for (size_t i = 0; i < num_repetitions; ++i) {
559 auto [input_a, a] = get_random_point(&builder, a_type);
560 auto [input_b, b] = get_random_point(&builder, b_type);
561
562 element_ct result = a.checked_unconditional_subtract(b);
563
564 affine_element expected(element(input_a) - element(input_b));
565 uint256_t result_x = result.x().get_value().lo;
566 uint256_t result_y = result.y().get_value().lo;
567
568 EXPECT_EQ(fq(result_x), expected.x);
569 EXPECT_EQ(fq(result_y), expected.y);
570 }
572 }
573
576 {
578 size_t num_repetitions = 10;
579 for (size_t i = 0; i < num_repetitions; ++i) {
580 const auto [input_a, a] = get_random_point(&builder, a_type);
581 const auto [input_b, b] = get_random_point(&builder, b_type);
582
583 // Since unchecked_unconditional_add_sub is private in biggroup, we test it via the element_test_accessor
585
586 affine_element expected_sum(element(input_a) + element(input_b));
587 affine_element expected_diff(element(input_a) - element(input_b));
588
589 uint256_t sum_x = sum.x().get_value().lo;
590 uint256_t sum_y = sum.y().get_value().lo;
591 uint256_t diff_x = diff.x().get_value().lo;
592 uint256_t diff_y = diff.y().get_value().lo;
593
594 EXPECT_EQ(fq(sum_x), expected_sum.x);
595 EXPECT_EQ(fq(sum_y), expected_sum.y);
596 EXPECT_EQ(fq(diff_x), expected_diff.x);
597 EXPECT_EQ(fq(diff_y), expected_diff.y);
598 }
600 }
601
603 {
605 size_t num_repetitions = 10;
606 for (size_t i = 0; i < num_repetitions; ++i) {
607 auto [input_a, a] = get_random_point(&builder, a_type);
608
609 a.set_origin_tag(submitted_value_origin_tag);
610
611 element_ct c = a.dbl();
612
613 // Check that the tag is preserved
614 EXPECT_EQ(c.get_origin_tag(), submitted_value_origin_tag);
615
616 affine_element c_expected(element(input_a).dbl());
617
618 uint256_t c_x_u256 = c.x().get_value().lo;
619 uint256_t c_y_u256 = c.y().get_value().lo;
620
621 fq c_x_result(c_x_u256);
622 fq c_y_result(c_y_u256);
623
624 EXPECT_EQ(c_x_result, c_expected.x);
625 EXPECT_EQ(c_y_result, c_expected.y);
626 }
628 }
629
631 {
633 {
634 // Case 1: Doubling point at infinity should return point at infinity
635 affine_element input_infinity(element::random_element());
636 input_infinity.self_set_infinity();
637 element_ct a_infinity = element_ct::from_witness(&builder, input_infinity);
638 a_infinity.set_origin_tag(submitted_value_origin_tag);
639
640 element_ct result_infinity = a_infinity.dbl();
641
642 // Check that the tag is preserved
643 EXPECT_EQ(result_infinity.get_origin_tag(), submitted_value_origin_tag);
644
645 // Result should be point at infinity
646 EXPECT_TRUE(result_infinity.is_point_at_infinity().get_value());
647 }
648 {
649 // Case 2: Doubling a normal point should not result in infinity
650 affine_element input_normal(element::random_element());
651 element_ct a_normal = element_ct::from_witness(&builder, input_normal);
652 a_normal.set_origin_tag(submitted_value_origin_tag);
653
654 element_ct result_normal = a_normal.dbl();
655
656 // Check that the tag is preserved
657 EXPECT_EQ(result_normal.get_origin_tag(), submitted_value_origin_tag);
658
659 // Result should not be point at infinity (with overwhelming probability)
660 EXPECT_FALSE(result_normal.is_point_at_infinity().get_value());
661
662 // Verify correctness
663 affine_element expected_normal(element(input_normal).dbl());
664 uint256_t result_x = result_normal.x().get_value().lo;
665 uint256_t result_y = result_normal.y().get_value().lo;
666 fq expected_x(result_x);
667 fq expected_y(result_y);
668 EXPECT_EQ(expected_x, expected_normal.x);
669 EXPECT_EQ(expected_y, expected_normal.y);
670 }
672 }
673
675 {
677
678 // For bn254 curve: y^2 = x^3 + 3
679 // We need a point where y = 0, which means x^3 = -3
680 // For most curves, there may not be a rational point with y = 0
681 // So we test the logic by creating a witness point with y = 0 explicitly
682 // Even if it's not on the curve, we can test the doubling logic
683 affine_element test_point(element::random_element());
684
685 // Create a point with y = 0 (may not be on curve, but tests the edge case)
686 auto x_coord = element_ct::BaseField::from_witness(&builder, test_point.x);
687 auto y_coord = element_ct::BaseField::from_witness(&builder, fq(0));
688 // Skip curve check since we're intentionally creating an invalid point to test edge case
689 element_ct a(x_coord, y_coord, bool_ct(witness_ct(&builder, false)), /*assert_on_curve=*/false);
690
691 a.set_origin_tag(submitted_value_origin_tag);
692
693 // With the new assertion, attempting to double a point with y = 0 should throw
694 // because for valid curves like bn254, y = 0 cannot occur on the curve
695 EXPECT_THROW_WITH_MESSAGE(a.dbl(), "Attempting to dbl a point with y = 0, not allowed.");
696 }
697
699 {
700 // Test that P + P equals P.dbl()
702 size_t num_repetitions = 5;
703 for (size_t i = 0; i < num_repetitions; ++i) {
704 auto [input_a, a] = get_random_point(&builder, InputType::WITNESS);
705
706 element_ct sum = a + a;
707 element_ct doubled = a.dbl();
708
709 // Results should match
710 uint256_t sum_x = sum.x().get_value().lo;
711 uint256_t sum_y = sum.y().get_value().lo;
712 uint256_t dbl_x = doubled.x().get_value().lo;
713 uint256_t dbl_y = doubled.y().get_value().lo;
714
715 EXPECT_EQ(fq(sum_x), fq(dbl_x));
716 EXPECT_EQ(fq(sum_y), fq(dbl_y));
717 EXPECT_EQ(sum.is_point_at_infinity().get_value(), doubled.is_point_at_infinity().get_value());
718 }
720 }
721
723 {
724 // Test that P - (-P) equals 2P
726 size_t num_repetitions = 5;
727 for (size_t i = 0; i < num_repetitions; ++i) {
728 auto [input_a, a] = get_random_point(&builder, InputType::WITNESS);
729
730 element_ct neg_a = -a;
731 element_ct result = a - neg_a;
732 element_ct expected = a.dbl();
733
734 // P - (-P) = P + P = 2P
735 uint256_t result_x = result.x().get_value().lo;
736 uint256_t result_y = result.y().get_value().lo;
737 uint256_t expected_x = expected.x().get_value().lo;
738 uint256_t expected_y = expected.y().get_value().lo;
739
740 EXPECT_EQ(fq(result_x), fq(expected_x));
741 EXPECT_EQ(fq(result_y), fq(expected_y));
742 }
744 }
745
749 {
751 size_t num_repetitions = 10;
752 for (size_t i = 0; i < num_repetitions; ++i) {
753
754 auto [input_a, a] = get_random_point(&builder, a_type);
755 auto [input_b, b] = get_random_point(&builder, b_type);
756 auto [input_c, c] = get_random_point(&builder, c_type);
757
758 auto acc = element_ct::chain_add_start(a, b);
759 auto acc_out = element_ct::chain_add(c, acc);
760 element_ct result = element_ct::chain_add_end(acc_out);
761
762 // Verify result
763 affine_element expected(element(input_a) + element(input_b) + element(input_c));
764 uint256_t result_x = result.x().get_value().lo;
765 uint256_t result_y = result.y().get_value().lo;
766 EXPECT_EQ(fq(result_x), expected.x);
767 EXPECT_EQ(fq(result_y), expected.y);
768
769 // Check intermediate values
770 auto lambda_prev = (input_b.y - input_a.y) / (input_b.x - input_a.x);
771 auto x3_prev = lambda_prev * lambda_prev - input_b.x - input_a.x;
772 auto y3_prev = lambda_prev * (input_a.x - x3_prev) - input_a.y;
773 auto lambda = (y3_prev - input_c.y) / (x3_prev - input_c.x);
774 auto x3 = lambda * lambda - x3_prev - input_c.x;
775
776 uint256_t x3_u256 = acc_out.x3_prev.get_value().lo;
777 uint256_t lambda_u256 = acc_out.lambda_prev.get_value().lo;
778
779 fq x3_result(x3_u256);
780 fq lambda_result(lambda_u256);
781
782 EXPECT_EQ(x3_result, x3);
783 EXPECT_EQ(lambda_result, lambda);
784 }
785
787 }
788
790 {
792 size_t num_repetitions = 10;
793 for (size_t i = 0; i < num_repetitions; ++i) {
794 affine_element acc_small(element::random_element());
795 element_ct acc_big = element_ct::from_witness(&builder, acc_small);
796
798 for (size_t j = 0; j < i; ++j) {
799 affine_element add_1_small_0(element::random_element());
800 element_ct add_1_big_0 = element_ct::from_witness(&builder, add_1_small_0);
801 affine_element add_2_small_0(element::random_element());
802 element_ct add_2_big_0 = element_ct::from_witness(&builder, add_2_small_0);
803 typename element_ct::chain_add_accumulator add_1 =
804 element_ct::chain_add_start(add_1_big_0, add_2_big_0);
805 to_add.emplace_back(add_1);
806 }
807 acc_big.multiple_montgomery_ladder(to_add);
808 }
809
811 }
812
814 {
816 size_t num_repetitions = 10;
817 for (size_t i = 0; i < num_repetitions; ++i) {
818 auto [input_a, a] = get_random_point(&builder, point_type);
819
820 element_ct normalized = a.normalize();
821
822 // Normalized should equal the original
823 uint256_t x_before = a.x().get_value().lo;
824 uint256_t y_before = a.y().get_value().lo;
825 uint256_t x_after = normalized.x().get_value().lo;
826 uint256_t y_after = normalized.y().get_value().lo;
827
828 EXPECT_EQ(fq(x_before), fq(x_after));
829 EXPECT_EQ(fq(y_before), fq(y_after));
830 }
832 }
833
834 static void test_reduce(InputType point_type = InputType::WITNESS)
835 {
837 size_t num_repetitions = 10;
838 for (size_t i = 0; i < num_repetitions; ++i) {
839 auto [input_a, a] = get_random_point(&builder, point_type);
840
841 element_ct reduced = a.reduce();
842
843 // Reduced should equal the original
844 uint256_t x_before = a.x().get_value().lo;
845 uint256_t y_before = a.y().get_value().lo;
846 uint256_t x_after = reduced.x().get_value().lo;
847 uint256_t y_after = reduced.y().get_value().lo;
848
849 EXPECT_EQ(fq(x_before), fq(x_after));
850 EXPECT_EQ(fq(y_before), fq(y_after));
851 }
853 }
854
856 {
858 auto [input_a, a] = get_random_point(&builder, a_type);
859
860 element_ct neg_a = -a;
861
862 affine_element expected = affine_element(-element(input_a));
863 uint512_t neg_x_u512 = uint512_t(neg_a.x().get_value()) % uint512_t(fq::modulus);
864 uint512_t neg_y_u512 = uint512_t(neg_a.y().get_value()) % uint512_t(fq::modulus);
865 uint256_t neg_x = neg_x_u512.lo;
866 uint256_t neg_y = neg_y_u512.lo;
867
868 EXPECT_EQ(fq(neg_x), expected.x);
869 EXPECT_EQ(fq(neg_y), expected.y);
870
872 }
873
875 InputType predicate_type = InputType::WITNESS)
876 {
878 size_t num_repetitions = 10;
879 for (size_t i = 0; i < num_repetitions; ++i) {
880 // Get random point
881 auto [input_a, a] = get_random_point(&builder, point_type);
882 a.set_origin_tag(submitted_value_origin_tag);
883
884 // Get random predicate
885 bool predicate_value = (engine.get_random_uint8() % 2) != 0;
886 bool_ct predicate = (predicate_type == InputType::WITNESS) ? bool_ct(witness_ct(&builder, predicate_value))
887 : bool_ct(predicate_value);
888 predicate.set_origin_tag(challenge_origin_tag);
889
890 element_ct c = a.conditional_negate(predicate);
891
892 // Check the resulting tag is preserved
893 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
894
895 affine_element c_expected = predicate_value ? affine_element(-element(input_a)) : input_a;
896 EXPECT_EQ(c.get_value(), c_expected);
897 }
899 }
900
903 InputType predicate_type = InputType::WITNESS)
904 {
906 size_t num_repetitions = 10;
907 for (size_t i = 0; i < num_repetitions; ++i) {
908 auto [input_a, a] = get_random_point(&builder, a_type);
909 auto [input_b, b] = get_random_point(&builder, b_type);
910
911 bool predicate_value = (engine.get_random_uint8() % 2) != 0;
912 bool_ct predicate = (predicate_type == InputType::WITNESS) ? bool_ct(witness_ct(&builder, predicate_value))
913 : bool_ct(predicate_value);
914
915 // Set different tags in a and b and the predicate
916 a.set_origin_tag(submitted_value_origin_tag);
917 b.set_origin_tag(challenge_origin_tag);
918 predicate.set_origin_tag(next_challenge_tag);
919
920 element_ct c = a.conditional_select(b, predicate);
921
922 // Check that the resulting tag is the union of inputs' tags
923 EXPECT_EQ(c.get_origin_tag(), first_second_third_merged_tag);
924
925 affine_element c_expected = predicate_value ? input_b : input_a;
926 EXPECT_EQ(c.get_value(), c_expected);
927 }
929 }
930
932 {
933 // Case 1: Should pass because the points are identical
934 {
936 size_t num_repetitions = 10;
937 for (size_t i = 0; i < num_repetitions; ++i) {
938 affine_element input_a(element::random_element());
939 element_ct a = element_ct::from_witness(&builder, input_a);
940 element_ct b = element_ct::from_witness(&builder, input_a);
941
942 // Set different tags in a and b
943 a.set_origin_tag(submitted_value_origin_tag);
944 b.set_origin_tag(challenge_origin_tag);
945
946 a.incomplete_assert_equal(b, "elements don't match");
947 }
949 }
950 // Case 2: Should pass because the points are identical and at infinity
951 {
953 size_t num_repetitions = 10;
954 for (size_t i = 0; i < num_repetitions; ++i) {
955 affine_element input_a(element::random_element());
956 element_ct a = element_ct::from_witness(&builder, input_a);
957 element_ct b = element_ct::from_witness(&builder, input_a);
958
959 // Set different tags in a and b
960 a.set_origin_tag(submitted_value_origin_tag);
961 b.set_origin_tag(challenge_origin_tag);
962
963 a.set_point_at_infinity(bool_ct(witness_ct(&builder, true)));
964 b.set_point_at_infinity(bool_ct(witness_ct(&builder, true)));
965
966 a.incomplete_assert_equal(b, "elements don't match");
967 }
969 }
970 // Case 3: Self-assertion (point equals itself)
971 {
973 affine_element input(element::random_element());
974 element_ct a = element_ct::from_witness(&builder, input);
975
976 a.incomplete_assert_equal(a, "self assertion test");
977
979 }
980 }
981
983 {
984 // Case 1: Should fail because the points are different
985 {
987 affine_element input_a(element::random_element());
988 affine_element input_b(element::random_element());
989 // Ensure inputs are different
990 while (input_a == input_b) {
991 input_b = element::random_element();
992 }
993 element_ct a = element_ct::from_witness(&builder, input_a);
994 element_ct b = element_ct::from_witness(&builder, input_b);
995
996 // Set different tags in a and b
997 a.set_origin_tag(submitted_value_origin_tag);
998 b.set_origin_tag(challenge_origin_tag);
999
1000 a.incomplete_assert_equal(b, "elements don't match");
1001
1002 // Circuit should fail (Circuit checker doesn't fail because it doesn't actually check copy constraints,
1003 // it only checks gate constraints)
1004 EXPECT_EQ(builder.failed(), true);
1005 EXPECT_EQ(builder.err(), "elements don't match (x coordinate)");
1006 }
1007 // Case 2: Should fail because the points have same x but different y
1008 {
1010 affine_element input_a(element::random_element());
1011
1012 // Create a point with the same x coordinate but different y
1013 // For an elliptic curve y^2 = x^3 + ax + b, if (x, y) is on the curve, then (x, -y) is also on the
1014 // curve
1015 affine_element input_b = input_a;
1016 input_b.y = -input_a.y; // Negate y to get a different point with same x
1017
1018 // Construct the circuit elements with same x but different y
1019 auto x_coord = element_ct::BaseField::from_witness(&builder, input_a.x);
1020 auto y_coord_a = element_ct::BaseField::from_witness(&builder, input_a.y);
1021 auto y_coord_b = element_ct::BaseField::from_witness(&builder, input_b.y);
1022
1023 element_ct a(x_coord, y_coord_a, bool_ct(witness_ct(&builder, false)));
1024 element_ct b(x_coord, y_coord_b, bool_ct(witness_ct(&builder, false)));
1025
1026 // Set different tags in a and b
1027 a.set_origin_tag(submitted_value_origin_tag);
1028 b.set_origin_tag(challenge_origin_tag);
1029
1030 a.incomplete_assert_equal(b, "elements don't match");
1031
1032 // Circuit should fail with y coordinate error
1033 EXPECT_EQ(builder.failed(), true);
1034 EXPECT_EQ(builder.err(), "elements don't match (y coordinate)");
1035 }
1036 // Case 3: Infinity flag mismatch (one point at infinity, one not)
1037 {
1039 affine_element input_a(element::random_element());
1040 affine_element input_b(element::random_element());
1041
1042 element_ct a = element_ct::from_witness(&builder, input_a);
1043 element_ct b = element_ct::from_witness(&builder, input_b);
1044
1045 // Set only one point at infinity
1046 a.set_point_at_infinity(bool_ct(witness_ct(&builder, true))); // at infinity
1047 b.set_point_at_infinity(bool_ct(witness_ct(&builder, false))); // not at infinity
1048
1049 a.incomplete_assert_equal(b, "infinity flag mismatch test");
1050
1051 EXPECT_EQ(builder.failed(), true);
1052 EXPECT_EQ(builder.err(), "infinity flag mismatch test (infinity flag)");
1053 }
1054 }
1055
1057 {
1059 // Check that two points at infinity with different x,y coords fail the equality check
1060 affine_element input_a(element::random_element());
1061 affine_element input_b(element::random_element());
1062
1063 // Ensure inputs are different
1064 while (input_a == input_b) {
1065 input_b = element::random_element();
1066 }
1067 element_ct a = element_ct::from_witness(&builder, input_a);
1068 element_ct b = element_ct::from_witness(&builder, input_b);
1069
1070 const bool_ct is_infinity = bool_ct(witness_ct(&builder, 1));
1071 a.set_point_at_infinity(is_infinity);
1072 b.set_point_at_infinity(is_infinity);
1073
1074 // Set different tags in a and b
1075 a.set_origin_tag(submitted_value_origin_tag);
1076 b.set_origin_tag(challenge_origin_tag);
1077
1078 a.incomplete_assert_equal(b, "points at infinity with different x,y should not be equal");
1079
1080 // Circuit should fail
1081 EXPECT_EQ(builder.failed(), true);
1082 EXPECT_EQ(builder.err(), "points at infinity with different x,y should not be equal (x coordinate)");
1083 }
1084
1085 static void test_compute_naf()
1086 {
1088 size_t max_num_bits = 254;
1089 for (size_t length = 2; length < max_num_bits; length += 1) {
1090
1091 fr scalar_val;
1092
1093 uint256_t scalar_raw = engine.get_random_uint256();
1094 scalar_raw = scalar_raw >> (256 - length);
1095
1096 scalar_val = fr(scalar_raw);
1097
1098 // We test non-zero scalars here
1099 if (scalar_val == fr(0)) {
1100 scalar_val += 1;
1101 };
1102 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1103 // Set tag for scalar
1104 scalar.set_origin_tag(submitted_value_origin_tag);
1105 auto naf = element_ct::compute_naf(scalar, length);
1106
1107 for (const auto& bit : naf) {
1108 // Check that the tag is propagated to bits
1109 EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag);
1110 }
1111 // scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1112 fr reconstructed_val(0);
1113 for (size_t i = 0; i < length; i++) {
1114 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1115 };
1116 reconstructed_val -= fr(naf[length].get_value());
1117 EXPECT_EQ(scalar_val, reconstructed_val);
1118 }
1119
1121 }
1122
1124 {
1126 size_t length = fr::modulus.get_msb() + 1;
1127
1128 // Our algorithm for input 0 outputs the NAF representation of r (the field modulus)
1129 fr scalar_val(0);
1130
1131 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1132
1133 // Set tag for scalar
1134 scalar.set_origin_tag(submitted_value_origin_tag);
1135 auto naf = element_ct::compute_naf(scalar, length);
1136
1137 for (const auto& bit : naf) {
1138 // Check that the tag is propagated to bits
1139 EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag);
1140 }
1141
1142 // scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1143 fr reconstructed_val(0);
1144 uint256_t reconstructed_u256(0);
1145 for (size_t i = 0; i < length; i++) {
1146 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1147 reconstructed_u256 +=
1148 (uint256_t(1) - uint256_t(2) * uint256_t(naf[i].get_value())) * (uint256_t(1) << (length - 1 - i));
1149 };
1150 reconstructed_val -= fr(naf[length].get_value());
1151 EXPECT_EQ(scalar_val, reconstructed_val);
1152 EXPECT_EQ(reconstructed_u256, uint256_t(fr::modulus));
1153
1155 }
1156
1158 {
1160
1161 // Create a scalar that is even (skew=1) and has least-significant 2L bits all 0 (L=68, 2L=136)
1162 // This causes overflow in negative_lo = skew + sum_{i=0}^{135} a'_{i+1} * 2^i = 1 + (2^136 - 1) = 2^136
1163 //
1164 // Scalar chosen such that least significant 136 bits are zero:
1165 fr scalar_native = fr::random_element();
1166 uint256_t scalar_raw = uint256_t(scalar_native);
1167 scalar_raw = (scalar_raw >> 136) << 136;
1168 fr scalar_val = fr(scalar_raw);
1169 scalar_ct scalar = scalar_ct::from_witness(&builder, scalar_val);
1170 scalar.set_origin_tag(submitted_value_origin_tag);
1171
1172 // Compute NAF with full field size
1173 const size_t length = fr::modulus.get_msb() + 1;
1174
1175 // This should not overflow with the fix in place
1176 auto naf = element_ct::compute_naf(scalar, length);
1177
1178 // Verify NAF correctness
1179 for (const auto& bit : naf) {
1180 EXPECT_EQ(bit.get_origin_tag(), submitted_value_origin_tag);
1181 }
1182
1183 // Reconstruct scalar from NAF: scalar = -naf[L] + \sum_{i=0}^{L-1}(1-2*naf[i]) 2^{L-1-i}
1184 fr reconstructed_val(0);
1185 for (size_t i = 0; i < length; i++) {
1186 reconstructed_val += (fr(1) - fr(2) * fr(naf[i].get_value())) * fr(uint256_t(1) << (length - 1 - i));
1187 }
1188 reconstructed_val -= fr(naf[length].get_value());
1189
1190 EXPECT_EQ(scalar_val, reconstructed_val);
1192 }
1193
1194 static void test_mul(InputType scalar_type = InputType::WITNESS, InputType point_type = InputType::WITNESS)
1195 {
1197 size_t num_repetitions = 1;
1198 for (size_t i = 0; i < num_repetitions; ++i) {
1199 auto [input, P] = get_random_point(&builder, point_type);
1200 auto [scalar, x] = get_random_scalar(&builder, scalar_type, /*even*/ true);
1201
1202 // Set input tags
1203 x.set_origin_tag(challenge_origin_tag);
1204 P.set_origin_tag(submitted_value_origin_tag);
1205
1206 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1207 element_ct c = P * x;
1208 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1209 affine_element c_expected(element(input) * scalar);
1210
1211 // Check the result of the multiplication has a tag that's the union of inputs' tags
1212 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1213 fq c_x_result(c.x().get_value().lo);
1214 fq c_y_result(c.y().get_value().lo);
1215
1216 EXPECT_EQ(c_x_result, c_expected.x);
1217 EXPECT_EQ(c_y_result, c_expected.y);
1218 }
1219
1221 }
1222
1224 InputType point_type = InputType::WITNESS)
1225 {
1227
1228 const auto run_mul_and_check = [&](element_ct& P, scalar_ct& x, const affine_element& expected) {
1229 // Set input tags
1230 x.set_origin_tag(challenge_origin_tag);
1231 P.set_origin_tag(submitted_value_origin_tag);
1232
1233 // Perform multiplication
1234 element_ct result = P * x;
1235
1236 // Check the result tag
1237 EXPECT_EQ(result.get_origin_tag(), first_two_merged_tag);
1238
1239 // Check if result is infinity
1240 bool result_is_inf = result.is_point_at_infinity().get_value();
1241 bool expected_is_inf = expected.is_point_at_infinity();
1242
1243 EXPECT_EQ(result_is_inf, expected_is_inf);
1244
1245 // If not infinity, check if the coordinates match
1246 if (!expected_is_inf) {
1247 uint256_t result_x = result.x().get_value().lo;
1248 uint256_t result_y = result.y().get_value().lo;
1249
1250 EXPECT_EQ(fq(result_x), expected.x);
1251 EXPECT_EQ(fq(result_y), expected.y);
1252 }
1253 };
1254
1255 // Case 1: P * 0 = ∞
1256 {
1257 auto [input, P] = get_random_point(&builder, point_type);
1258 scalar_ct x = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, fr(0))
1259 : scalar_ct(&builder, fr(0));
1260 affine_element expected_infinity = affine_element(element::infinity());
1261 run_mul_and_check(P, x, expected_infinity);
1262 }
1263 // Case 2: (∞) * k = ∞
1264 {
1265 auto [input, P] = get_random_point(&builder, point_type);
1266 if (point_type == InputType::CONSTANT) {
1267 P.set_point_at_infinity(bool_ct(true));
1268 } else {
1269 P.set_point_at_infinity(bool_ct(witness_ct(&builder, true)));
1270 }
1271
1272 // For mega circuit builder, we need to ensure the point at infinity is (0, 0)
1273 if constexpr (IsMegaBuilder<Builder>) {
1274 P = P.get_standard_form();
1275 }
1276
1277 auto [scalar, x] = get_random_scalar(&builder, scalar_type, /*even*/ true);
1278 affine_element expected_infinity = affine_element(element::infinity());
1279 run_mul_and_check(P, x, expected_infinity);
1280 }
1281 // Case 3: P * 1 = P
1282 {
1283 auto [input, P] = get_random_point(&builder, point_type);
1284 scalar_ct one = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, fr(1))
1285 : scalar_ct(&builder, fr(1));
1286 run_mul_and_check(P, one, input);
1287 }
1288 // Case 4: P * (-1) = -P
1289 {
1290 auto [input, P] = get_random_point(&builder, point_type);
1291 fr neg_one = -fr(1);
1292 scalar_ct neg_one_ct = (scalar_type == InputType::WITNESS) ? scalar_ct::from_witness(&builder, neg_one)
1293 : scalar_ct(&builder, neg_one);
1294 affine_element expected = affine_element(-element(input));
1295 run_mul_and_check(P, neg_one_ct, expected);
1296 }
1298 }
1299
1300 // Test short scalar mul with variable bit lengths.
1302 {
1304
1305 std::vector<size_t> test_lengths = { 2, 3, 10, 11, 31, 32, 63, 64, 127, 128, 252, 253 };
1306
1307 for (size_t i : test_lengths) {
1308 affine_element input(element::random_element());
1309 // Get a random 256 integer
1310 uint256_t scalar_raw = engine.get_random_uint256();
1311 // Produce a length =< i scalar.
1312 scalar_raw = scalar_raw >> (256 - i);
1313 fr scalar = fr(scalar_raw);
1314
1315 // Avoid multiplication by 0 that may occur when `i` is small
1316 if (scalar == fr(0)) {
1317 scalar += 1;
1318 };
1319
1320 element_ct P = element_ct::from_witness(&builder, input);
1321 scalar_ct x = scalar_ct::from_witness(&builder, scalar);
1322
1323 // Set input tags
1324 x.set_origin_tag(challenge_origin_tag);
1325 P.set_origin_tag(submitted_value_origin_tag);
1326
1327 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1328 // Multiply using specified scalar length
1329 element_ct c = P.scalar_mul(x, i);
1330 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1331 affine_element c_expected(element(input) * scalar);
1332
1333 // Check the result of the multiplication has a tag that's the union of inputs' tags
1334 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1335 fq c_x_result(c.x().get_value().lo);
1336 fq c_y_result(c.y().get_value().lo);
1337
1338 EXPECT_EQ(c_x_result, c_expected.x);
1339
1340 EXPECT_EQ(c_y_result, c_expected.y);
1341 }
1342
1344 }
1345
1347 {
1348 // We check that a point at infinity preserves `is_point_at_infinity()` flag after being multiplied against
1349 // a short scalar and also check that the number of gates in this case is more than the number of gates
1350 // spent on a finite point.
1351
1352 // Populate test points.
1353 std::vector<element> points(2);
1354
1355 points[0] = element::infinity();
1356 points[1] = element::random_element();
1357 // Containter for gate counts.
1358 std::vector<size_t> gates(2);
1359
1360 // We initialize this flag as `true`, because the first result is expected to be the point at infinity.
1361 bool expect_infinity = true;
1362
1363 for (auto [point, num_gates] : zip_view(points, gates)) {
1365
1366 const size_t max_num_bits = 128;
1367 // Get a random 256-bit integer
1368 uint256_t scalar_raw = engine.get_random_uint256();
1369 // Produce a length =< max_num_bits scalar.
1370 scalar_raw = scalar_raw >> (256 - max_num_bits);
1371 fr scalar = fr(scalar_raw);
1372
1373 element_ct P = element_ct::from_witness(&builder, point);
1374 scalar_ct x = scalar_ct::from_witness(&builder, scalar);
1375
1376 // Set input tags
1377 x.set_origin_tag(challenge_origin_tag);
1378 P.set_origin_tag(submitted_value_origin_tag);
1379
1380 std::cerr << "gates before mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1381 element_ct c = P.scalar_mul(x, max_num_bits);
1382 std::cerr << "builder aftr mul " << builder.get_num_finalized_gates_inefficient() << std::endl;
1383 num_gates = builder.get_num_finalized_gates_inefficient();
1384 // Check the result of the multiplication has a tag that's the union of inputs' tags
1385 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1386
1387 EXPECT_EQ(c.is_point_at_infinity().get_value(), expect_infinity);
1389 // The second point is finite, hence we flip the flag
1390 expect_infinity = false;
1391 }
1392 // Check that the numbers of gates are greater when multiplying by point at infinity,
1393 // because we transform (s * ∞) into (0 * G), and NAF representation of 0 ≡ NAF(r) which is 254 bits long.
1394 EXPECT_GT(gates[0], gates[1]);
1395 }
1396
1397 static void test_twin_mul()
1398 {
1400 size_t num_repetitions = 1;
1401 for (size_t i = 0; i < num_repetitions; ++i) {
1402 affine_element input_a(element::random_element());
1403 affine_element input_b(element::random_element());
1404 fr scalar_a(fr::random_element());
1405 fr scalar_b(fr::random_element());
1406 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
1407 scalar_a -= fr(1); // skew bit is 1
1408 }
1409 if ((uint256_t(scalar_b).get_bit(0) & 1) == 0) {
1410 scalar_b += fr(1); // skew bit is 0
1411 }
1412 element_ct P_a = element_ct::from_witness(&builder, input_a);
1413 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
1414 element_ct P_b = element_ct::from_witness(&builder, input_b);
1415 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b);
1416
1417 // Set tags
1418 P_a.set_origin_tag(submitted_value_origin_tag);
1419 x_a.set_origin_tag(challenge_origin_tag);
1420 P_b.set_origin_tag(next_submitted_value_origin_tag);
1421 x_b.set_origin_tag(next_challenge_tag);
1422
1423 element_ct c = element_ct::batch_mul({ P_a, P_b }, { x_a, x_b });
1424
1425 // Check that the resulting tag is a union of all tags
1426 EXPECT_EQ(c.get_origin_tag(), first_to_fourth_merged_tag);
1427 element input_c = (element(input_a) * scalar_a);
1428 element input_d = (element(input_b) * scalar_b);
1429 affine_element expected(input_c + input_d);
1430 fq c_x_result(c.x().get_value().lo);
1431 fq c_y_result(c.y().get_value().lo);
1432
1433 EXPECT_EQ(c_x_result, expected.x);
1434 EXPECT_EQ(c_y_result, expected.y);
1435 }
1437 }
1438
1440 {
1442 size_t num_repetitions = 1;
1443 for (size_t i = 0; i < num_repetitions; ++i) {
1444 affine_element input_a(element::random_element());
1445 affine_element input_b(element::random_element());
1446 input_b.self_set_infinity();
1447
1448 // Get two 128-bit scalars
1449 const size_t max_num_bits = 128;
1450 uint256_t scalar_raw_a = engine.get_random_uint256();
1451 scalar_raw_a = scalar_raw_a >> (256 - max_num_bits);
1452 fr scalar_a = fr(scalar_raw_a);
1453
1454 uint256_t scalar_raw_b = engine.get_random_uint256();
1455 scalar_raw_b = scalar_raw_b >> (256 - max_num_bits);
1456 fr scalar_b = fr(scalar_raw_b);
1457
1458 element_ct P_a = element_ct::from_witness(&builder, input_a); // A
1459 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a); // s_1 (128 bits)
1460 element_ct P_b = element_ct::from_witness(&builder, input_b); // ∞
1461 scalar_ct x_b = scalar_ct::from_witness(&builder, scalar_b); // s_2 (128 bits)
1462
1463 // Set tags
1464 P_a.set_origin_tag(submitted_value_origin_tag);
1465 x_a.set_origin_tag(challenge_origin_tag);
1466 P_b.set_origin_tag(next_submitted_value_origin_tag);
1467 x_b.set_origin_tag(next_challenge_tag);
1468
1469 element_ct c = element_ct::batch_mul({ P_a, P_b }, { x_a, x_b }, 128);
1470
1471 // Check that the resulting tag is a union of all tags
1472 EXPECT_EQ(c.get_origin_tag(), first_to_fourth_merged_tag);
1473 element input_c = (element(input_a) * scalar_a);
1474 element input_d = (element(input_b) * scalar_b);
1475 affine_element expected(input_c + input_d);
1476 fq c_x_result(c.x().get_value().lo);
1477 fq c_y_result(c.y().get_value().lo);
1478
1479 EXPECT_EQ(c_x_result, expected.x);
1480 EXPECT_EQ(c_y_result, expected.y);
1481 }
1483 }
1484
1486 {
1488 affine_element input_P(element::random_element());
1489
1490 affine_element input_P_a = affine_element(element(input_P) + element(input_P)); // 2P
1491 affine_element input_P_b = affine_element(element(input_P_a) + element(input_P)); // 3P
1492 affine_element input_P_c = affine_element(element(input_P_a) + element(input_P_b)); // 5P
1493 std::vector<affine_element> input_points = { input_P_a, input_P_b, input_P_c };
1494
1495 // Choose scalars such that their NAF representations are:
1496 // skew msd lsd
1497 // a: 0 [+1, +1, -1, +1] = -0 + 2^3 + 2^2 - 2^1 + 2^0 = 11
1498 // b: 1 [+1, +1, +1, +1] = -1 + 2^3 + 2^2 + 2^1 + 2^0 = 14
1499 // c: 1 [+1, -1, +1, +1] = -1 + 2^3 - 2^2 + 2^1 + 2^0 = 6
1500 fr scalar_a(11);
1501 fr scalar_b(14);
1502 fr scalar_c(6);
1503 std::vector<fr> input_scalars = { scalar_a, scalar_b, scalar_c };
1504
1505 OriginTag tag_union =
1506 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1507 std::vector<scalar_ct> scalars;
1509 for (size_t i = 0; i < 3; ++i) {
1510 const element_ct point = element_ct::from_witness(&builder, input_points[i]);
1511 point.set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1512 tag_union = OriginTag(tag_union, point.get_origin_tag());
1513
1514 const scalar_ct scalar = scalar_ct::from_witness(&builder, input_scalars[i]);
1515 scalar.set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1516 tag_union = OriginTag(tag_union, scalar.get_origin_tag());
1517
1518 scalars.emplace_back(scalar);
1519 points.emplace_back(point);
1520 }
1521
1522 // If with_edgecases = true, should handle linearly dependent points correctly
1523 // Define masking scalar (128 bits)
1524 const auto get_128_bit_scalar = []() {
1525 uint256_t scalar_u256(0, 0, 0, 0);
1526 scalar_u256.data[0] = engine.get_random_uint64();
1527 scalar_u256.data[1] = engine.get_random_uint64();
1528 fr scalar(scalar_u256);
1529 return scalar;
1530 };
1531 fr masking_scalar = get_128_bit_scalar();
1532 scalar_ct masking_scalar_ct = scalar_ct::from_witness(&builder, masking_scalar);
1533 element_ct c = element_ct::batch_mul(points,
1534 scalars,
1535 /*max_num_bits*/ 128,
1536 /*with_edgecases*/ true,
1537 /*masking_scalar*/ masking_scalar_ct);
1538
1539 // Check that the result tag is a union of inputs' tags
1540 EXPECT_EQ(c.get_origin_tag(), tag_union);
1541 element input_e = (element(input_P_a) * scalar_a);
1542 element input_f = (element(input_P_b) * scalar_b);
1543 element input_g = (element(input_P_c) * scalar_c);
1544
1545 affine_element expected(input_e + input_f + input_g);
1546 fq c_x_result(c.x().get_value().lo);
1547 fq c_y_result(c.y().get_value().lo);
1548
1549 EXPECT_EQ(c_x_result, expected.x);
1550 EXPECT_EQ(c_y_result, expected.y);
1551
1553 }
1554
1556 {
1558 affine_element input_P(element::random_element());
1559
1560 affine_element input_P_a = affine_element(element(input_P) + element(input_P)); // 2P
1561 affine_element input_P_b = affine_element(element(input_P_a) + element(input_P)); // 3P
1562 affine_element input_P_c = affine_element(element(input_P_a) + element(input_P_b)); // 5P
1563 std::vector<affine_element> input_points = { input_P_a, input_P_b, input_P_c };
1564
1565 // Choose scalars similar to the previous test
1566 fr scalar_a(11);
1567 fr scalar_b(14);
1568 fr scalar_c(6);
1569 std::vector<fr> input_scalars = { scalar_a, scalar_b, scalar_c };
1570
1571 std::vector<scalar_ct> scalars;
1573 for (size_t i = 0; i < 3; ++i) {
1574 const element_ct point = element_ct::from_witness(&builder, input_points[i]);
1575 points.emplace_back(point);
1576
1577 const scalar_ct scalar = scalar_ct::from_witness(&builder, input_scalars[i]);
1578 scalars.emplace_back(scalar);
1579 }
1580
1581 // with_edgecases = false should fail due to linearly dependent points
1582 // This will fail only while using ultra builder
1583 element_ct::batch_mul(points, scalars, /*max_num_bits*/ 4, /*with_edgecases*/ false);
1584
1586 EXPECT_EQ(builder.err(), "bigfield: prime limb diff is zero, but expected non-zero");
1587 }
1588
1589 static void test_one()
1590 {
1592 size_t num_repetitions = 1;
1593 for (size_t i = 0; i < num_repetitions; ++i) {
1594 fr scalar_a(fr::random_element());
1595 if ((uint256_t(scalar_a).get_bit(0) & 1) == 1) {
1596 scalar_a -= fr(1); // skew bit is 1
1597 }
1598 element_ct P_a = element_ct::one(&builder);
1599
1600 // Set origin tag for element to submitted value in round 0
1601 P_a.set_origin_tag(submitted_value_origin_tag);
1602 scalar_ct x_a = scalar_ct::from_witness(&builder, scalar_a);
1603
1604 // Set origin tag for scalar to challenge in round 0
1605 x_a.set_origin_tag(challenge_origin_tag);
1606 element_ct c = P_a * x_a;
1607
1608 // Check that the resulting tag is a union
1609 EXPECT_EQ(c.get_origin_tag(), first_two_merged_tag);
1610 affine_element expected(g1::one * scalar_a);
1611 fq c_x_result(c.x().get_value().lo);
1612 fq c_y_result(c.y().get_value().lo);
1613
1614 EXPECT_EQ(c_x_result, expected.x);
1615 EXPECT_EQ(c_y_result, expected.y);
1616 }
1617
1619 }
1620
1621 // Overload: defaults to all WITNESS types for given num_points
1622 static void test_helper_batch_mul(size_t num_points,
1623 const bool short_scalars = false,
1624 const bool with_edgecases = false)
1625 {
1626 std::vector<InputType> point_types(num_points, InputType::WITNESS);
1627 std::vector<InputType> scalar_types(num_points, InputType::WITNESS);
1628 test_helper_batch_mul(point_types, scalar_types, short_scalars, with_edgecases);
1629 }
1630
1632 std::vector<InputType> scalar_types,
1633 const bool short_scalars = false,
1634 const bool with_edgecases = false)
1635 {
1637
1638 const size_t num_points = point_types.size();
1640 std::vector<fr> scalars;
1641 std::vector<element_ct> circuit_points;
1642 std::vector<scalar_ct> circuit_scalars;
1643
1644 for (size_t i = 0; i < num_points; ++i) {
1645 // Generate scalars
1646 if (short_scalars) {
1647 auto [input_scalar, x] = get_random_short_scalar(&builder, scalar_types[i], /*num_bits*/ 128);
1648 scalars.push_back(input_scalar);
1649 circuit_scalars.push_back(x);
1650 } else {
1651 auto [input_scalar, x] = get_random_scalar(&builder, scalar_types[i], /*even*/ true);
1652 scalars.push_back(input_scalar);
1653 circuit_scalars.push_back(x);
1654 }
1655
1656 // Generate points
1657 auto [input_point, P] = get_random_point(&builder, point_types[i]);
1658 points.push_back(input_point);
1659 circuit_points.push_back(P);
1660 }
1661
1662 OriginTag tag_union =
1663 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1664 for (size_t i = 0; i < num_points; ++i) {
1665 // Set tag to submitted value tag at round i
1666 circuit_points[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1667 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1668
1669 // Set tag to challenge tag at round i
1670 circuit_scalars[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1671 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1672 }
1673
1674 // Define masking scalar (128 bits) if with_edgecases is true
1675 const auto get_128_bit_scalar = []() {
1676 uint256_t scalar_u256(0, 0, 0, 0);
1677 scalar_u256.data[0] = engine.get_random_uint64();
1678 scalar_u256.data[1] = engine.get_random_uint64();
1679 fr scalar(scalar_u256);
1680 return scalar;
1681 };
1682 fr masking_scalar = with_edgecases ? get_128_bit_scalar() : fr(1);
1683 scalar_ct masking_scalar_ct =
1684 with_edgecases ? scalar_ct::from_witness(&builder, masking_scalar) : scalar_ct(&builder, fr(1));
1685
1686 element_ct result_point = element_ct::batch_mul(
1687 circuit_points, circuit_scalars, /*max_num_bits=*/0, with_edgecases, masking_scalar_ct);
1688
1689 // Check the resulting tag is a union of inputs' tags
1690 EXPECT_EQ(result_point.get_origin_tag(), tag_union);
1691
1692 element expected_point = g1::one;
1693 expected_point.self_set_infinity();
1694 for (size_t i = 0; i < num_points; ++i) {
1695 expected_point += (element(points[i]) * scalars[i]);
1696 }
1697
1698 expected_point = expected_point.normalize();
1699 fq result_x(result_point.x().get_value().lo);
1700 fq result_y(result_point.y().get_value().lo);
1701
1702 EXPECT_EQ(result_x, expected_point.x);
1703 EXPECT_EQ(result_y, expected_point.y);
1704
1706 }
1707
1708 static void test_batch_mul()
1709 {
1710 const size_t num_points = 5;
1713 std::vector<fr> scalars;
1714 for (size_t i = 0; i < num_points; ++i) {
1715 points.push_back(affine_element(element::random_element()));
1716 scalars.push_back(fr::random_element());
1717 }
1718
1719 std::vector<element_ct> circuit_points;
1720 std::vector<scalar_ct> circuit_scalars;
1721 OriginTag tag_union =
1722 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1723 for (size_t i = 0; i < num_points; ++i) {
1724 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1725
1726 // Set tag to submitted value tag at round i
1727 circuit_points[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1728 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1729 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1730
1731 // Set tag to challenge tag at round i
1732 circuit_scalars[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1733 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1734 }
1735
1736 element_ct result_point = element_ct::batch_mul(circuit_points, circuit_scalars);
1737
1738 // Check the resulting tag is a union of inputs' tags
1739 EXPECT_EQ(result_point.get_origin_tag(), tag_union);
1740
1741 element expected_point = g1::one;
1742 expected_point.self_set_infinity();
1743 for (size_t i = 0; i < num_points; ++i) {
1744 expected_point += (element(points[i]) * scalars[i]);
1745 }
1746
1747 expected_point = expected_point.normalize();
1748 fq result_x(result_point.x().get_value().lo);
1749 fq result_y(result_point.y().get_value().lo);
1750
1751 EXPECT_EQ(result_x, expected_point.x);
1752 EXPECT_EQ(result_y, expected_point.y);
1753
1755 }
1756
1758 {
1759 const size_t num_points = 5;
1762 std::vector<fr> scalars;
1763 for (size_t i = 0; i < num_points; ++i) {
1764 points.push_back(affine_element(element::random_element()));
1765 scalars.push_back(fr::random_element());
1766 }
1767
1768 std::vector<element_ct> circuit_points;
1769 std::vector<scalar_ct> circuit_scalars;
1770
1771 OriginTag tag_union =
1772 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1773 for (size_t i = 0; i < num_points; ++i) {
1774 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1775
1776 // Set tag to submitted value tag at round i
1777 circuit_points[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1778 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1779 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1780
1781 // Set tag to challenge tag at round i
1782 circuit_scalars[i].set_origin_tag(OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1783 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1784 }
1785
1786 element_ct result_point2 =
1787 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1788
1789 // Check that the result tag is a union of inputs' tags
1790 EXPECT_EQ(result_point2.get_origin_tag(), tag_union);
1791 element expected_point = g1::one;
1792 expected_point.self_set_infinity();
1793 for (size_t i = 0; i < num_points; ++i) {
1794 expected_point += (element(points[i]) * scalars[i]);
1795 }
1796
1797 expected_point = expected_point.normalize();
1798
1799 fq result2_x(result_point2.x().get_value().lo);
1800 fq result2_y(result_point2.y().get_value().lo);
1801
1802 EXPECT_EQ(result2_x, expected_point.x);
1803 EXPECT_EQ(result2_y, expected_point.y);
1804
1806 }
1807
1809 {
1810 const auto test_repeated_points = [](const uint32_t num_points) {
1811 // batch P + ... + P = m*P
1812 info("num points: ", num_points);
1814 std::vector<fr> scalars;
1815 for (size_t idx = 0; idx < num_points; idx++) {
1816 points.push_back(affine_element::one());
1817 scalars.push_back(1);
1818 }
1819
1821 ASSERT_EQ(points.size(), scalars.size());
1822
1823 std::vector<element_ct> circuit_points;
1824 std::vector<scalar_ct> circuit_scalars;
1825
1826 OriginTag tag_union =
1827 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1828 for (size_t i = 0; i < num_points; ++i) {
1829 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1830
1831 // Set tag to submitted value tag at round i
1832 circuit_points[i].set_origin_tag(
1833 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1834 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1835 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1836
1837 // Set tag to challenge tag at round i
1838 circuit_scalars[i].set_origin_tag(
1839 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1840 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1841 }
1842 element_ct result_point =
1843 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1844
1845 // Check that the result tag is a union of inputs' tags
1846 EXPECT_EQ(result_point.get_origin_tag(), tag_union);
1847
1848 auto expected_point = element::infinity();
1849 for (const auto& point : points) {
1850 expected_point += point;
1851 }
1852 expected_point = expected_point.normalize();
1853
1854 fq result_x(result_point.x().get_value().lo);
1855 fq result_y(result_point.y().get_value().lo);
1856
1857 EXPECT_EQ(result_x, expected_point.x);
1858 EXPECT_EQ(result_y, expected_point.y);
1859
1861 };
1862 test_repeated_points(2);
1863 test_repeated_points(3);
1864 test_repeated_points(4);
1865 test_repeated_points(5);
1866 test_repeated_points(6);
1867 test_repeated_points(7);
1868 }
1870 {
1871 {
1872 // batch oo + P = P
1874 points.push_back(affine_element::infinity());
1875 points.push_back(affine_element(element::random_element()));
1876 std::vector<fr> scalars;
1877 scalars.push_back(1);
1878 scalars.push_back(1);
1879
1881 ASSERT_EQ(points.size(), scalars.size());
1882 const size_t num_points = points.size();
1883
1884 std::vector<element_ct> circuit_points;
1885 std::vector<scalar_ct> circuit_scalars;
1886
1887 OriginTag tag_union =
1888 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1889 for (size_t i = 0; i < num_points; ++i) {
1890 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1891
1892 // Set tag to submitted value tag at round i
1893 circuit_points[i].set_origin_tag(
1894 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1895 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1896 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1897
1898 // Set tag to challenge tag at round i
1899 circuit_scalars[i].set_origin_tag(
1900 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1901 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1902 }
1903
1904 element_ct result_point =
1905 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1906
1907 // Check that the result tag is a union of inputs' tags
1908 EXPECT_EQ(result_point.get_origin_tag(), tag_union);
1909
1910 element expected_point = points[1];
1911 expected_point = expected_point.normalize();
1912
1913 fq result_x(result_point.x().get_value().lo);
1914 fq result_y(result_point.y().get_value().lo);
1915
1916 EXPECT_EQ(result_x, expected_point.x);
1917 EXPECT_EQ(result_y, expected_point.y);
1918
1920 }
1921 {
1922 // batch 0 * P1 + P2 = P2
1924 points.push_back(affine_element(element::random_element()));
1925 points.push_back(affine_element(element::random_element()));
1926 std::vector<fr> scalars;
1927 scalars.push_back(0);
1928 scalars.push_back(1);
1929
1931 ASSERT_EQ(points.size(), scalars.size());
1932 const size_t num_points = points.size();
1933
1934 std::vector<element_ct> circuit_points;
1935 std::vector<scalar_ct> circuit_scalars;
1936 OriginTag tag_union =
1937 OriginTag::constant(); // Initialize as CONSTANT so merging with input tags works correctly
1938 for (size_t i = 0; i < num_points; ++i) {
1939 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1940
1941 // Set tag to submitted value tag at round i
1942 circuit_points[i].set_origin_tag(
1943 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/true));
1944 tag_union = OriginTag(tag_union, circuit_points[i].get_origin_tag());
1945 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1946
1947 // Set tag to challenge tag at round i
1948 circuit_scalars[i].set_origin_tag(
1949 OriginTag(/*parent_index=*/0, /*child_index=*/i, /*is_submitted=*/false));
1950 tag_union = OriginTag(tag_union, circuit_scalars[i].get_origin_tag());
1951 }
1952
1953 element_ct result_point =
1954 element_ct::batch_mul(circuit_points, circuit_scalars, /*max_num_bits=*/0, /*with_edgecases=*/true);
1955
1956 // Check that the result tag is a union of inputs' tags
1957 EXPECT_EQ(result_point.get_origin_tag(), tag_union);
1958
1959 element expected_point = points[1];
1960 expected_point = expected_point.normalize();
1961
1962 fq result_x(result_point.x().get_value().lo);
1963 fq result_y(result_point.y().get_value().lo);
1964
1965 EXPECT_EQ(result_x, expected_point.x);
1966 EXPECT_EQ(result_y, expected_point.y);
1967
1969 }
1970 }
1971
1972 // Test batch_mul with all points at infinity
1974 {
1977 std::vector<fr> scalars;
1978
1979 for (size_t i = 0; i < 5; ++i) {
1980 points.push_back(affine_element::infinity());
1981 scalars.push_back(fr::random_element());
1982 }
1983
1984 std::vector<element_ct> circuit_points;
1985 std::vector<scalar_ct> circuit_scalars;
1986
1987 for (size_t i = 0; i < points.size(); ++i) {
1988 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
1989 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
1990 }
1991
1992 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
1993
1994 // Result should be point at infinity
1995 EXPECT_TRUE(result.is_point_at_infinity().get_value());
1997 }
1998
1999 // Test batch_mul with all zero scalars
2001 {
2004 std::vector<fr> scalars;
2005
2006 for (size_t i = 0; i < 5; ++i) {
2007 points.push_back(affine_element(element::random_element()));
2008 scalars.push_back(fr::zero());
2009 }
2010
2011 std::vector<element_ct> circuit_points;
2012 std::vector<scalar_ct> circuit_scalars;
2013
2014 for (size_t i = 0; i < points.size(); ++i) {
2015 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2016 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2017 }
2018
2019 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
2020
2021 // Result should be point at infinity
2022 EXPECT_TRUE(result.is_point_at_infinity().get_value());
2024 }
2025
2026 // Test batch_mul with mixed zero and non-zero scalars
2028 {
2031 std::vector<fr> scalars;
2032
2033 for (size_t i = 0; i < 6; ++i) {
2034 points.push_back(affine_element(element::random_element()));
2035 // Alternate between zero and non-zero scalars
2036 scalars.push_back((i % 2 == 0) ? fr::zero() : fr::random_element());
2037 }
2038
2039 std::vector<element_ct> circuit_points;
2040 std::vector<scalar_ct> circuit_scalars;
2041
2042 for (size_t i = 0; i < points.size(); ++i) {
2043 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2044 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2045 }
2046
2047 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
2048
2049 // Compute expected result
2050 element expected = element::infinity();
2051 for (size_t i = 0; i < points.size(); ++i) {
2052 expected += (element(points[i]) * scalars[i]);
2053 }
2054 affine_element expected_affine = affine_element(expected);
2055
2056 uint256_t result_x = result.x().get_value().lo;
2057 uint256_t result_y = result.y().get_value().lo;
2058
2059 EXPECT_EQ(fq(result_x), expected_affine.x);
2060 EXPECT_EQ(fq(result_y), expected_affine.y);
2061
2063 }
2064
2065 // Test batch_mul with mixed infinity and valid points
2067 {
2070 std::vector<fr> scalars;
2071
2072 for (size_t i = 0; i < 6; ++i) {
2073 // Alternate between infinity and valid points
2074 points.push_back((i % 2 == 0) ? affine_element::infinity() : affine_element(element::random_element()));
2075 scalars.push_back(fr::random_element());
2076 }
2077
2078 std::vector<element_ct> circuit_points;
2079 std::vector<scalar_ct> circuit_scalars;
2080
2081 for (size_t i = 0; i < points.size(); ++i) {
2082 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2083 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2084 }
2085
2086 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
2087
2088 // Compute expected result
2089 element expected = element::infinity();
2090 for (size_t i = 0; i < points.size(); ++i) {
2091 if (!points[i].is_point_at_infinity()) {
2092 expected += (element(points[i]) * scalars[i]);
2093 }
2094 }
2095 affine_element expected_affine = affine_element(expected);
2096
2097 uint256_t result_x = result.x().get_value().lo;
2098 uint256_t result_y = result.y().get_value().lo;
2099
2100 EXPECT_EQ(fq(result_x), expected_affine.x);
2101 EXPECT_EQ(fq(result_y), expected_affine.y);
2102
2104 }
2105
2106 // Test batch_mul with points that cancel out
2108 {
2111 std::vector<fr> scalars;
2112
2113 // Add P and -P with same scalar
2114 affine_element P(element::random_element());
2116 fr scalar = fr::random_element();
2117
2118 points.push_back(P);
2119 scalars.push_back(scalar);
2120 points.push_back(neg_P);
2121 scalars.push_back(scalar);
2122
2123 // Add some other points to make it non-trivial
2124 for (size_t i = 0; i < 3; ++i) {
2125 points.push_back(affine_element(element::random_element()));
2126 scalars.push_back(fr::random_element());
2127 }
2128
2129 std::vector<element_ct> circuit_points;
2130 std::vector<scalar_ct> circuit_scalars;
2131
2132 for (size_t i = 0; i < points.size(); ++i) {
2133 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2134 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2135 }
2136
2137 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars, 0, true);
2138
2139 // Compute expected result
2140 element expected = element::infinity();
2141 for (size_t i = 0; i < points.size(); ++i) {
2142 expected += (element(points[i]) * scalars[i]);
2143 }
2144 affine_element expected_affine = affine_element(expected);
2145
2146 uint256_t result_x = result.x().get_value().lo;
2147 uint256_t result_y = result.y().get_value().lo;
2148
2149 EXPECT_EQ(fq(result_x), expected_affine.x);
2150 EXPECT_EQ(fq(result_y), expected_affine.y);
2151
2153 }
2154
2155 // Test batch_mul with constant and witness points mixed
2157 {
2159 std::vector<affine_element> points_native;
2160 std::vector<fr> scalars_native;
2161 std::vector<element_ct> circuit_points;
2162 std::vector<scalar_ct> circuit_scalars;
2163
2164 // Add constant-constant points
2165 for (size_t i = 0; i < 3; ++i) {
2166 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
2167 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
2168 points_native.push_back(point);
2169 scalars_native.push_back(scalar);
2170 circuit_points.push_back(point_ct); // Constant
2171 circuit_scalars.push_back(scalar_ct); // Constant
2172 }
2173
2174 // Add witness-witness points
2175 for (size_t i = 0; i < 3; ++i) {
2176 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
2177 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
2178 points_native.push_back(point);
2179 scalars_native.push_back(scalar);
2180 circuit_points.push_back(point_ct); // Witness
2181 circuit_scalars.push_back(scalar_ct); // Witness
2182 }
2183
2184 // Add constant-witness points
2185 for (size_t i = 0; i < 4; ++i) {
2186 const auto [point, point_ct] = get_random_point(&builder, InputType::CONSTANT);
2187 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::WITNESS);
2188 points_native.push_back(point);
2189 scalars_native.push_back(scalar);
2190 circuit_points.push_back(element_ct(point)); // Constant
2191 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalar)); // Witness
2192 }
2193
2194 // Add witness-constant points
2195 for (size_t i = 0; i < 4; ++i) {
2196 const auto [point, point_ct] = get_random_point(&builder, InputType::WITNESS);
2197 const auto [scalar, scalar_ct] = get_random_scalar(&builder, InputType::CONSTANT);
2198 points_native.push_back(point);
2199 scalars_native.push_back(scalar);
2200 circuit_points.push_back(point_ct); // Witness
2201 circuit_scalars.push_back(scalar_ct); // Constant
2202 }
2203
2204 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
2205
2206 // Compute expected result
2207 element expected = element::infinity();
2208 for (size_t i = 0; i < points_native.size(); ++i) {
2209 expected += (element(points_native[i]) * scalars_native[i]);
2210 }
2211 affine_element expected_affine = affine_element(expected);
2212
2213 uint256_t result_x = result.x().get_value().lo;
2214 uint256_t result_y = result.y().get_value().lo;
2215
2216 EXPECT_EQ(fq(result_x), expected_affine.x);
2217 EXPECT_EQ(fq(result_y), expected_affine.y);
2218
2220 }
2221
2222 // Test batch_mul with large number of points (stress test)
2224 {
2227 std::vector<fr> scalars;
2228 constexpr size_t num_points = 20;
2229
2230 for (size_t i = 0; i < num_points; ++i) {
2231 points.push_back(affine_element(element::random_element()));
2232 scalars.push_back(fr::random_element());
2233 }
2234
2235 std::vector<element_ct> circuit_points;
2236 std::vector<scalar_ct> circuit_scalars;
2237
2238 for (size_t i = 0; i < points.size(); ++i) {
2239 circuit_points.push_back(element_ct::from_witness(&builder, points[i]));
2240 circuit_scalars.push_back(scalar_ct::from_witness(&builder, scalars[i]));
2241 }
2242
2243 element_ct result = element_ct::batch_mul(circuit_points, circuit_scalars);
2244
2245 // Compute expected result
2246 element expected = element::infinity();
2247 for (size_t i = 0; i < points.size(); ++i) {
2248 expected += (element(points[i]) * scalars[i]);
2249 }
2250 affine_element expected_affine = affine_element(expected);
2251
2252 uint256_t result_x = result.x().get_value().lo;
2253 uint256_t result_y = result.y().get_value().lo;
2254
2255 EXPECT_EQ(fq(result_x), expected_affine.x);
2256 EXPECT_EQ(fq(result_y), expected_affine.y);
2257
2259 }
2260};
2261
2262// bn254 with ultra arithmetisation where scalar field is native field, base field is non-native field (bigfield)
2264
2265// bn254 with ultra arithmetisation where both scalar and base fields are non-native fields
2268
2269// bn254 with mega arithmetisation where scalar field is native field, base field is non-native field
2271
2272// secp256r1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2275
2276// secp256k1 with ultra arithmetisation where both scalar and base fields are (naturally) non-native fields
2279
2280using TestTypes = testing::Types<bn254_with_ultra,
2285
2287
2289{
2290 TestFixture::test_basic_tag_logic();
2291}
2292
2293TYPED_TEST(stdlib_biggroup, assert_coordinates_in_field)
2294{
2295 TestFixture::test_assert_coordinates_in_field();
2296}
2297
2298// Addition tests
2300{
2301 TestFixture::test_add();
2302}
2303TYPED_TEST(stdlib_biggroup, add_with_constants)
2304{
2305 TestFixture::test_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2306 TestFixture::test_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2307 TestFixture::test_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2308}
2309TYPED_TEST(stdlib_biggroup, add_points_at_infinity)
2310{
2311 TestFixture::test_add_points_at_infinity();
2312}
2313TYPED_TEST(stdlib_biggroup, standard_form_of_point_at_infinity)
2314{
2315 TestFixture::test_standard_form_of_point_at_infinity();
2316}
2317
2318// Subtraction tests
2320{
2321 TestFixture::test_sub();
2322}
2323TYPED_TEST(stdlib_biggroup, sub_with_constants)
2324{
2325 TestFixture::test_sub(InputType::WITNESS, InputType::CONSTANT); // w - c
2326 TestFixture::test_sub(InputType::CONSTANT, InputType::WITNESS); // c - w
2327 TestFixture::test_sub(InputType::CONSTANT, InputType::CONSTANT); // c - c
2328}
2329TYPED_TEST(stdlib_biggroup, sub_points_at_infinity)
2330{
2331 TestFixture::test_sub_points_at_infinity();
2332}
2334{
2335 TestFixture::test_dbl();
2336}
2337TYPED_TEST(stdlib_biggroup, dbl_with_constant)
2338{
2339 TestFixture::test_dbl(InputType::CONSTANT); // dbl(c)
2340}
2341TYPED_TEST(stdlib_biggroup, dbl_with_infinity)
2342{
2343 TestFixture::test_dbl_with_infinity();
2344}
2346{
2347 if constexpr (HasGoblinBuilder<TypeParam>) {
2348 GTEST_SKIP() << "mega builder does not support this edge case";
2349 } else {
2350 TestFixture::test_dbl_with_y_zero();
2351 }
2352}
2354{
2355 TestFixture::test_add_equals_dbl();
2356}
2357TYPED_TEST(stdlib_biggroup, sub_neg_equals_double)
2358{
2359 TestFixture::test_sub_neg_equals_double();
2360}
2361
2362// Test chain_add
2364{
2365 if constexpr (HasGoblinBuilder<TypeParam>) {
2366 GTEST_SKIP() << "mega builder does not implement chain_add function";
2367 } else {
2368 TestFixture::test_chain_add();
2369 };
2370}
2371HEAVY_TYPED_TEST(stdlib_biggroup, chain_add_with_constants)
2372{
2373 if constexpr (HasGoblinBuilder<TypeParam>) {
2374 GTEST_SKIP() << "mega builder does not implement chain_add function";
2375 } else {
2376 TestFixture::test_chain_add(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2377 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2378 TestFixture::test_chain_add(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2379 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2380 TestFixture::test_chain_add(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2381 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2382 TestFixture::test_chain_add(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2383 }
2384}
2385
2386// Test multiple_montgomery_ladder
2387HEAVY_TYPED_TEST(stdlib_biggroup, multiple_montgomery_ladder)
2388{
2389
2390 if constexpr (HasGoblinBuilder<TypeParam>) {
2391 GTEST_SKIP() << "mega builder does not implement multiple_montgomery_ladder function";
2392 } else {
2393 TestFixture::test_multiple_montgomery_ladder();
2394 };
2395}
2396
2397// Test normalize
2399{
2400 TestFixture::test_normalize();
2401}
2402TYPED_TEST(stdlib_biggroup, normalize_constant)
2403{
2404 TestFixture::test_normalize(InputType::CONSTANT);
2405}
2406
2407// Test reduce
2409{
2410 TestFixture::test_reduce();
2411}
2413{
2414 TestFixture::test_reduce(InputType::CONSTANT);
2415}
2416
2417// Test unary negation
2419{
2420 TestFixture::test_unary_negate(InputType::WITNESS);
2421}
2422
2423TYPED_TEST(stdlib_biggroup, unary_negate_with_constants)
2424{
2425 TestFixture::test_unary_negate(InputType::CONSTANT);
2426}
2427
2428// Test operator+=
2430{
2431 TestFixture::test_add_assign(InputType::WITNESS, InputType::WITNESS);
2432}
2433
2434TYPED_TEST(stdlib_biggroup, add_assign_with_constants)
2435{
2436 TestFixture::test_add_assign(InputType::WITNESS, InputType::CONSTANT); // w += c
2437 TestFixture::test_add_assign(InputType::CONSTANT, InputType::WITNESS); // c += w
2438}
2439
2440// Test operator-=
2442{
2443 TestFixture::test_sub_assign(InputType::WITNESS, InputType::WITNESS);
2444}
2445TYPED_TEST(stdlib_biggroup, sub_assign_with_constants)
2446{
2447 TestFixture::test_sub_assign(InputType::WITNESS, InputType::CONSTANT); // w -= c
2448 TestFixture::test_sub_assign(InputType::CONSTANT, InputType::WITNESS); // c -= w
2449}
2450// Test checked_unconditional_add
2451TYPED_TEST(stdlib_biggroup, checked_unconditional_add)
2452{
2453 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::WITNESS);
2454}
2455TYPED_TEST(stdlib_biggroup, checked_unconditional_add_with_constants)
2456{
2457 TestFixture::test_checked_unconditional_add(InputType::WITNESS, InputType::CONSTANT); // w + c
2458 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::WITNESS); // c + w
2459 TestFixture::test_checked_unconditional_add(InputType::CONSTANT, InputType::CONSTANT); // c + c
2460}
2461// Test checked_unconditional_subtract
2462TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract)
2463{
2464 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::WITNESS);
2465}
2466TYPED_TEST(stdlib_biggroup, checked_unconditional_subtract_with_constants)
2467{
2468 TestFixture::test_checked_unconditional_subtract(InputType::WITNESS, InputType::CONSTANT); // w - c
2469 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::WITNESS); // c - w
2470 TestFixture::test_checked_unconditional_subtract(InputType::CONSTANT, InputType::CONSTANT); // c - c
2471}
2472// Test checked_unconditional_add_sub
2473TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub)
2474{
2475 TestFixture::test_checked_unconditional_add_sub();
2476}
2477TYPED_TEST(stdlib_biggroup, checked_unconditional_add_sub_with_constants)
2478{
2479 TestFixture::test_checked_unconditional_add_sub(InputType::WITNESS, InputType::CONSTANT); // w, c
2480 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::WITNESS); // c, w
2481 TestFixture::test_checked_unconditional_add_sub(InputType::CONSTANT, InputType::CONSTANT); // c, c
2482}
2483// Test conditional_negate
2484TYPED_TEST(stdlib_biggroup, conditional_negate)
2485{
2486 TestFixture::test_conditional_negate();
2487}
2488TYPED_TEST(stdlib_biggroup, conditional_negate_with_constants)
2489{
2490 TestFixture::test_conditional_negate(InputType::WITNESS, InputType::CONSTANT); // w, c
2491 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::WITNESS); // c, w
2492 TestFixture::test_conditional_negate(InputType::CONSTANT, InputType::CONSTANT); // c, c
2493}
2494// Test conditional_select
2495TYPED_TEST(stdlib_biggroup, conditional_select)
2496{
2497 TestFixture::test_conditional_select();
2498}
2499TYPED_TEST(stdlib_biggroup, conditional_select_with_constants)
2500{
2501 TestFixture::test_conditional_select(InputType::WITNESS, InputType::WITNESS, InputType::CONSTANT); // w, w, c
2502 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::WITNESS); // w, c, w
2503 TestFixture::test_conditional_select(InputType::WITNESS, InputType::CONSTANT, InputType::CONSTANT); // w, c, c
2504 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::WITNESS); // c, w, w
2505 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::WITNESS); // c, c, w
2506 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::WITNESS, InputType::CONSTANT); // c, w, c
2507 TestFixture::test_conditional_select(InputType::CONSTANT, InputType::CONSTANT, InputType::CONSTANT); // c, c, c
2508}
2509TYPED_TEST(stdlib_biggroup, incomplete_assert_equal)
2510{
2511 TestFixture::test_incomplete_assert_equal();
2512}
2513TYPED_TEST(stdlib_biggroup, incomplete_assert_equal_fails)
2514{
2515 TestFixture::test_incomplete_assert_equal_failure();
2516}
2517TYPED_TEST(stdlib_biggroup, incomplete_assert_equal_edge_cases)
2518{
2519 TestFixture::test_incomplete_assert_equal_edge_cases();
2520}
2521
2523{
2524 if constexpr (!HasGoblinBuilder<TypeParam>) {
2525 size_t num_repetitions = 1;
2526 for (size_t i = 0; i < num_repetitions; i++) {
2527 TestFixture::test_compute_naf();
2528 }
2529 } else {
2530 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2531 }
2532}
2533
2535{
2536 if constexpr (!HasGoblinBuilder<TypeParam>) {
2537 TestFixture::test_compute_naf_zero();
2538 } else {
2539 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2540 }
2541}
2542
2543HEAVY_TYPED_TEST(stdlib_biggroup, compute_naf_overflow_lower_half)
2544{
2545 if constexpr (!HasGoblinBuilder<TypeParam>) {
2546 TestFixture::test_compute_naf_overflow_lower_half();
2547 } else {
2548 GTEST_SKIP() << "mega builder does not implement compute_naf function";
2549 }
2550}
2551
2553{
2554 TestFixture::test_mul();
2555}
2557{
2558 TestFixture::test_mul(InputType::WITNESS, InputType::CONSTANT); // w * c
2559 TestFixture::test_mul(InputType::CONSTANT, InputType::WITNESS); // c * w
2560 TestFixture::test_mul(InputType::CONSTANT, InputType::CONSTANT); // c * c
2561}
2563{
2564 TestFixture::test_mul_edge_cases();
2565}
2566HEAVY_TYPED_TEST(stdlib_biggroup, mul_edge_cases_with_constants)
2567{
2568 TestFixture::test_mul_edge_cases(InputType::WITNESS, InputType::CONSTANT); // w * c
2569 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::WITNESS); // c * w
2570 TestFixture::test_mul_edge_cases(InputType::CONSTANT, InputType::CONSTANT); // c * c
2571}
2572
2573HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_with_bit_lengths)
2574{
2575 if constexpr (HasGoblinBuilder<TypeParam>) {
2576 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2577 } else {
2578 TestFixture::test_short_scalar_mul_with_bit_lengths();
2579 }
2580}
2581
2582HEAVY_TYPED_TEST(stdlib_biggroup, short_scalar_mul_infinity)
2583{
2584 if constexpr (HasGoblinBuilder<TypeParam>) {
2585 GTEST_SKIP() << "mega builder does not implement scalar_mul function";
2586 } else {
2587 TestFixture::test_short_scalar_mul_infinity();
2588 }
2589}
2590
2591// Batch multiplication tests
2592// 1 point - Base case only
2594{
2595 TestFixture::test_helper_batch_mul(1);
2596}
2597
2598// 2 points - Base case + flag variations + one constant mix
2600{
2601 TestFixture::test_helper_batch_mul(2);
2602}
2603HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars)
2604{
2605 TestFixture::test_helper_batch_mul(2, true); // short_scalars
2606}
2607HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_with_edgecases)
2608{
2609 TestFixture::test_helper_batch_mul(2, false, true); // short_scalars, with_edgecases
2610}
2611HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_short_scalars_with_edgecases)
2612{
2613 TestFixture::test_helper_batch_mul(2, true, true); // short_scalars, with_edgecases
2614}
2615HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_twin_mixed_constants)
2616{
2617 TestFixture::test_helper_batch_mul({ InputType::WITNESS, InputType::CONSTANT },
2619}
2620
2621// 3 points - Base case only
2623{
2624 TestFixture::test_helper_batch_mul(3);
2625}
2626
2627// 4 points - Base case only
2629{
2630 TestFixture::test_helper_batch_mul(4);
2631}
2632
2633// 5 points - Base case + edge case + short scalar + mixed constant
2635{
2636 TestFixture::test_helper_batch_mul(5);
2637}
2638HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_with_edgecases)
2639{
2640 TestFixture::test_helper_batch_mul(5, false, true); // short_scalars, with_edgecases
2641}
2642HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars)
2643{
2644 TestFixture::test_helper_batch_mul(5, true); // short_scalars
2645}
2646HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_five_short_scalars_with_edgecases)
2647{
2648 TestFixture::test_helper_batch_mul(5, true, true); // short_scalars, with_edgecases
2649}
2656
2657// 6 points - Base case only
2659{
2660 TestFixture::test_helper_batch_mul(6);
2661}
2662
2664{
2665 TestFixture::test_twin_mul();
2666}
2667
2668HEAVY_TYPED_TEST(stdlib_biggroup, twin_mul_with_infinity)
2669{
2670 TestFixture::test_twin_mul_with_infinity();
2671}
2672
2673HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators)
2674{
2675 TestFixture::test_batch_mul_linearly_dependent_generators();
2676}
2677
2678HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_linearly_dependent_generators_failure)
2679{
2680 if constexpr (HasGoblinBuilder<TypeParam>) {
2681 GTEST_SKIP() << "this failure test is designed for ultra builder only";
2682 } else {
2683 TestFixture::test_batch_mul_linearly_dependent_generators_failure();
2684 }
2685}
2686
2688{
2689 TestFixture::test_one();
2690}
2691
2693{
2694 TestFixture::test_batch_mul();
2695}
2696
2697HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edgecase_equivalence)
2698{
2699 TestFixture::test_batch_mul_edgecase_equivalence();
2700}
2701HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set1)
2702{
2703 TestFixture::test_batch_mul_edge_case_set1();
2704}
2705
2706HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_edge_case_set2)
2707{
2708 TestFixture::test_batch_mul_edge_case_set2();
2709}
2710
2711// Batch mul edge case tests
2712HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_infinity)
2713{
2714 TestFixture::test_batch_mul_all_infinity();
2715}
2716
2717HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_all_zero_scalars)
2718{
2719 TestFixture::test_batch_mul_all_zero_scalars();
2720}
2721
2722HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_zero_scalars)
2723{
2724 TestFixture::test_batch_mul_mixed_zero_scalars();
2725}
2726
2727HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_infinity)
2728{
2729 TestFixture::test_batch_mul_mixed_infinity();
2730}
2731
2732HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_cancellation)
2733{
2734 TestFixture::test_batch_mul_cancellation();
2735}
2736
2737HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_mixed_constant_witness)
2738{
2739 TestFixture::test_batch_mul_mixed_constant_witness();
2740}
2741
2742HEAVY_TYPED_TEST(stdlib_biggroup, batch_mul_large_number_of_points)
2743{
2744 TestFixture::test_batch_mul_large_number_of_points();
2745}
#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.
constexpr void self_set_infinity() noexcept
static constexpr affine_element infinity()
static constexpr affine_element one() noexcept
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
void set_origin_tag(const OriginTag &new_tag) const
Definition bool.hpp:154
static auto checked_unconditional_add_sub(const element< C, Fq, Fr, G > &elem1, const element< C, Fq, Fr, G > &elem2)
Definition biggroup.hpp:940
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_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_compute_naf()
typename g1::element element
static void test_multiple_montgomery_ladder()
static void test_batch_mul_cancellation()
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)
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_mul_edge_cases(InputType scalar_type=InputType::WITNESS, InputType point_type=InputType::WITNESS)
typename g1::affine_element affine_element
typename TestType::Curve Curve
static void test_incomplete_assert_equal_edge_cases()
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_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_incomplete_assert_equal_failure()
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
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_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_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:307
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:200
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 OriginTag constant()
static constexpr uint256_t modulus
static field random_element(numeric::RNG *engine=nullptr) noexcept
BB_INLINE constexpr field reduce() const noexcept
static constexpr field zero()
#define HEAVY_TYPED_TEST(x, y)
Definition test.hpp:11