Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
prime_field.test.cpp
Go to the documentation of this file.
1
24#include <gtest/gtest.h>
25
26using namespace bb;
27
28namespace {
30
31// Helper to create a field element whose internal (Montgomery) representation has exactly
32// the given limbs. This is done by constructing from the value and then calling
33// from_montgomery_form() to make the limbs directly represent the desired pattern.
34// Also verifies that the internal representation matches the expected limbs (on non-WASM only,
35// since WASM uses different internal representation during computation).
36template <typename F> F create_element_with_limbs(uint64_t l0, uint64_t l1, uint64_t l2, uint64_t l3)
37{
38 uint256_t val{ l0, l1, l2, l3 };
39 F result(val);
40 result.self_from_montgomery_form_reduced();
41
42// On WASM, the internal Montgomery multiplication uses 29-bit limbs and may produce
43// different (but equivalent) representations. Skip limb verification on WASM.
44#if defined(__SIZEOF_INT128__) && !defined(__wasm__)
45 // Verify the internal representation matches expected limbs
46 EXPECT_EQ(result.data[0], l0);
47 EXPECT_EQ(result.data[1], l1);
48 EXPECT_EQ(result.data[2], l2);
49 EXPECT_EQ(result.data[3], l3);
50#endif
51
52 return result;
53}
54} // namespace
55
56// Type-parameterized test fixture for prime fields
57template <typename F> class PrimeFieldTest : public ::testing::Test {
58 public:
59 // Helper to get a random field element as uint256_t (for reference calculations)
61 {
63 while (res >= F::modulus) {
64 res -= F::modulus;
65 }
66 return res;
67 }
68};
69
70// Register all prime field types
71using PrimeFieldTypes = ::testing::Types<bb::fq, bb::fr, secp256k1::fq, secp256k1::fr, secp256r1::fq, secp256r1::fr>;
72
73// Fields where sqrt() works correctly
74using SqrtFieldTypes = ::testing::Types<bb::fq, bb::fr, secp256k1::fq, secp256r1::fq>;
75
76// Fields that have cube_root_of_unity() defined
77using CubeRootFieldTypes = ::testing::Types<bb::fq, bb::fr, secp256k1::fq, secp256k1::fr>;
78
79// Fields whose modulus is 256 bits.
80using TwoFiftySixBitFieldTypes = ::testing::Types<secp256k1::fq, secp256k1::fr>;
81
82// Fields whose modulus is 254 bits (BN254 fq and fr).
83using TwoFiftyFourBitFieldTypes = ::testing::Types<bb::fq, bb::fr>;
84
86
87template <typename> class PrimeFieldSqrtTest : public ::testing::Test {};
89
90template <typename> class PrimeFieldCubeRootTest : public ::testing::Test {};
92
93template <typename> class PrimeFieldTwoFiftySixTest : public ::testing::Test {};
95
96template <typename> class PrimeFieldTwoFiftyFourTest : public ::testing::Test {};
98// ================================
99// Compile-time Tests (Prime Field Specific)
100// ================================
101
102TYPED_TEST(PrimeFieldTest, CompileTimeEquality)
103{
104 using F = TypeParam;
105
106 constexpr F a{ 0x01, 0x02, 0x03, 0x04 };
107 constexpr F b{ 0x01, 0x02, 0x03, 0x04 };
108
109 constexpr F c{ 0x01, 0x02, 0x03, 0x05 };
110 constexpr F d{ 0x01, 0x02, 0x04, 0x04 };
111 constexpr F e{ 0x01, 0x03, 0x03, 0x04 };
112 constexpr F f{ 0x02, 0x02, 0x03, 0x04 };
113 static_assert(a == b);
114 static_assert(!(a == c));
115 static_assert(!(a == d));
116 static_assert(!(a == e));
117 static_assert(!(a == f));
118}
119
120TYPED_TEST(PrimeFieldTest, IsZeroOnModulusForm)
121{
122 using F = TypeParam;
123
124 F modulus_form{ F::modulus.data[0], F::modulus.data[1], F::modulus.data[2], F::modulus.data[3] };
125 EXPECT_TRUE(modulus_form.is_zero());
126
127 F prefix_match{ F::modulus.data[0], F::modulus.data[1], F::modulus.data[2], F::modulus.data[3] - 1 };
128 EXPECT_FALSE(prefix_match.is_zero());
129
130 F first_limb_only{ F::modulus.data[0], 0, 0, 0 };
131 EXPECT_FALSE(first_limb_only.is_zero());
132}
133
134TYPED_TEST(PrimeFieldTest, CompileTimeSmallAddSubMul)
135{
136 using F = TypeParam;
137
138 constexpr F a{ 0x01, 0x02, 0x03, 0x04 };
139 constexpr F b{ 0x05, 0x06, 0x07, 0x08 };
140
141 // Just verify these operations are constexpr and produce consistent results
142 constexpr F sum = a + b;
143 constexpr F diff = a - b;
144 constexpr F prod = a * b;
145 constexpr F sq = a.sqr();
146
147 // Verify at runtime that constexpr results match runtime results
148 EXPECT_EQ(sum, a + b);
149 EXPECT_EQ(diff, a - b);
150 EXPECT_EQ(prod, a * b);
151 EXPECT_EQ(sq, a.sqr());
152}
153
154TYPED_TEST(PrimeFieldTest, CompileTimeUint256Conversion)
155{
156 using F = TypeParam;
157
158 constexpr uint256_t a{ 0x1111, 0x2222, 0x3333, 0x4444 };
159 constexpr F b(a);
160 constexpr uint256_t c = b;
161 static_assert(a == c || a == c - F::modulus);
162}
163
164// ================================
165// uint256_t Arithmetic Verification
166// ================================
167
168TYPED_TEST(PrimeFieldTest, AdditionModular)
169{
170 using F = TypeParam;
171
172 uint256_t a_raw = TestFixture::get_random_element_raw();
173 uint256_t b_raw = TestFixture::get_random_element_raw();
174
175 F a(a_raw);
176 F b(b_raw);
177 F c = a + b;
178
179 uint512_t expected_512 = uint512_t(a_raw) + uint512_t(b_raw);
180 uint256_t expected = (expected_512 % uint512_t(F::modulus)).lo;
181
182 EXPECT_EQ(uint256_t(c), expected);
183}
184
185TYPED_TEST(PrimeFieldTest, SubtractionModular)
186{
187 using F = TypeParam;
188
189 uint256_t a_raw = TestFixture::get_random_element_raw();
190 uint256_t b_raw = TestFixture::get_random_element_raw();
191
192 F a(a_raw);
193 F b(b_raw);
194 F c = a - b;
195
196 uint512_t expected_512 = uint512_t(a_raw) + uint512_t(F::modulus) - uint512_t(b_raw);
197 uint256_t expected = (expected_512 % uint512_t(F::modulus)).lo;
198
199 EXPECT_EQ(uint256_t(c), expected);
200}
201
202TYPED_TEST(PrimeFieldTest, MultiplicationModular)
203{
204 using F = TypeParam;
205
206 uint256_t a_raw = TestFixture::get_random_element_raw();
207 uint256_t b_raw = TestFixture::get_random_element_raw();
208
209 F a(a_raw);
210 F b(b_raw);
211 F c = a * b;
212
213 uint512_t c_512 = uint512_t(a_raw) * uint512_t(b_raw);
214 uint256_t expected = (c_512 % uint512_t(F::modulus)).lo;
215
216 EXPECT_EQ(uint256_t(c), expected);
217}
218
219TYPED_TEST(PrimeFieldTest, SquaringModular)
220{
221 using F = TypeParam;
222
223 uint256_t a_raw = TestFixture::get_random_element_raw();
224
225 F a(a_raw);
226 F c = a.sqr();
227
228 uint512_t c_512 = uint512_t(a_raw) * uint512_t(a_raw);
229 uint256_t expected = (c_512 % uint512_t(F::modulus)).lo;
230
231 EXPECT_EQ(uint256_t(c), expected);
232}
233
234TYPED_TEST(PrimeFieldTest, Uint256Roundtrip)
235{
236 using F = TypeParam;
237
238 uint256_t original = TestFixture::get_random_element_raw();
239 F field_element(original);
240 uint256_t recovered(field_element);
241
242 EXPECT_EQ(original, recovered);
243}
244
245// ================================
246// Montgomery Form (Prime Field Specific)
247// ================================
248
249TYPED_TEST(PrimeFieldTest, MontgomeryRoundtrip)
250{
251 using F = TypeParam;
252
253 F a = F::random_element();
255 EXPECT_EQ(a, b);
256}
257
258// ================================
259// Square Root
260// ================================
261
263{
264 using F = TypeParam;
265
266 F one = F::one();
267 auto [is_sqr, root] = one.sqrt();
268
269 EXPECT_TRUE(is_sqr);
270 EXPECT_EQ(root.sqr(), one);
271}
272
274{
275 using F = TypeParam;
276
277 F a = F::random_element();
278 F a_sqr = a.sqr();
279 auto [is_sqr, root] = a_sqr.sqrt();
280
281 EXPECT_TRUE(is_sqr);
282 EXPECT_EQ(root.sqr(), a_sqr);
283 EXPECT_TRUE((root == a) || (root == -a));
284}
285
286// ================================
287// Cube Root of Unity
288// ================================
289
291{
292 using F = TypeParam;
293
294 // lambda^3 = 1, so (lambda * x)^3 = x^3
295 F x = F::random_element();
296 F lambda = F::cube_root_of_unity();
297 F lambda_x = x * lambda;
298
299 F x_cubed = x * x * x;
300 F lambda_x_cubed = lambda_x * lambda_x * lambda_x;
301
302 EXPECT_EQ(x_cubed, lambda_x_cubed);
303}
304
305// ================================
306// Exponentiation
307// ================================
308
309TYPED_TEST(PrimeFieldTest, PowZeroExponent)
310{
311 using F = TypeParam;
312
313 // a^0 = 1 for any non-zero a
314 F a = F::random_element();
315 EXPECT_EQ(a.pow(uint256_t(0)), F::one());
316}
317
319{
320 using F = TypeParam;
321
322 F a = F::random_element();
323 EXPECT_EQ(a.pow(uint256_t(1)), a);
324}
325
327{
328 using F = TypeParam;
329
330 F a = F::random_element();
331 EXPECT_EQ(a.pow(uint256_t(2)), a * a);
332}
333
335{
336 using F = TypeParam;
337
338 F a = F::random_element();
339 EXPECT_EQ(a.pow(uint256_t(3)), a * a * a);
340}
341
342// ================================
343// Batch Invert (only implemented for prime fields)
344// ================================
345
347{
348 using F = TypeParam;
349 constexpr size_t batch_size = 10;
350
351 std::vector<F> elements(batch_size);
352 std::vector<F> inverses(batch_size);
353
354 for (size_t i = 0; i < batch_size; ++i) {
355 elements[i] = F::random_element();
356 inverses[i] = elements[i];
357 }
358
359 F::batch_invert(&inverses[0], batch_size);
360
361 for (size_t i = 0; i < batch_size; ++i) {
362 F product = elements[i] * inverses[i];
363 product = product.reduce_once().reduce_once();
364 EXPECT_EQ(product, F::one());
365 }
366}
367
368// ================================
369// Increment Operators
370// ================================
371
373{
374 using F = TypeParam;
375
376 F a = F::random_element();
377 F a_copy = a;
378
379 a += 2;
380 F expected = a_copy + F(2);
381 EXPECT_EQ(a, expected);
382
383 a += 3;
384 expected = a_copy + F(5);
385 EXPECT_EQ(a, expected);
386}
387
388TYPED_TEST(PrimeFieldTest, PrefixIncrement)
389{
390 using F = TypeParam;
391
392 F a = F::random_element();
393 F a_before = a;
394 F b = ++a;
395
396 // Prefix increment returns the new value
397 EXPECT_EQ(b, a);
398 EXPECT_EQ(a, a_before + F(1));
399}
400
401TYPED_TEST(PrimeFieldTest, PostfixIncrement)
402{
403 using F = TypeParam;
404
405 F a = F::random_element();
406 F a_old = a;
407 F b = a++;
408
409 // Postfix increment returns the old value
410 EXPECT_EQ(b, a_old);
411 EXPECT_EQ(a, a_old + F(1));
412}
413
414// ================================
415// Internal Representation Tests for Big Fields
416// ================================
417
418// Shows that the raw limbs of an addition can have a result which is not automatically reduced, even when we are in the
419// 256-bit field range.
420TYPED_TEST(PrimeFieldTwoFiftySixTest, AddYieldsLimbsBiggerThanModulus)
421{
422 using F = TypeParam;
423
424 auto small_number = 10;
425 F small_field_elt = F(small_number).from_montgomery_form();
426 uint256_t small_number_from_limbs(
427 small_field_elt.data[0], small_field_elt.data[1], small_field_elt.data[2], small_field_elt.data[3]);
428 uint256_t big_number = uint256_t(F::modulus) - 1;
429 F big_field_elt = F(big_number).from_montgomery_form();
430 uint256_t big_number_from_limbs(
431 big_field_elt.data[0], big_field_elt.data[1], big_field_elt.data[2], big_field_elt.data[3]);
432
433 // make sure that the limbs combine to the number. (this is not immediate because we internall represent via
434 // Montgomery form. Here, it is guaranteed because we call `.from_montgomery_form()`).
435 EXPECT_EQ(small_number_from_limbs, small_number);
436 EXPECT_EQ(big_number, big_number_from_limbs);
437
438 F result = small_field_elt + big_field_elt;
439
440 // Extract the raw limbs from result
441 uint256_t result_from_limbs(result.data[0], result.data[1], result.data[2], result.data[3]);
442
443 // Check if the uint256_t from limbs is >= p
444 bool is_gte_modulus = result_from_limbs >= F::modulus;
445 EXPECT_EQ(is_gte_modulus, true);
446}
447
448// ================================
449// Single Non-Zero Limb Edge Cases
450// ================================
451// These tests verify correctness when the internal Montgomery representation has only one
452// non-zero limb, which can expose carry propagation bugs in Montgomery multiplication and
453// other operations.
454
455TYPED_TEST(PrimeFieldTest, SingleLimbArithmetic)
456{
457 using F = TypeParam;
458
459 // Test values for each limb position (only one limb non-zero at a time)
460 // For limb 3, use modulus.data[3] / 2 to stay below modulus
461 constexpr std::array<uint64_t, 4> limb_values = {
462 0xDEADBEEFCAFEBABEULL, 0xFEDCBA9876543210ULL, 0x123456789ABCDEF0ULL, F::modulus.data[3] / 2
463 };
464
465 for (size_t limb_idx = 0; limb_idx < 4; ++limb_idx) {
466 std::array<uint64_t, 4> limbs = { 0, 0, 0, 0 };
467 limbs[limb_idx] = limb_values[limb_idx];
468
469 F a = create_element_with_limbs<F>(limbs[0], limbs[1], limbs[2], limbs[3]);
470 F b = F::random_element();
471 uint256_t a_val = uint256_t(a);
472 uint256_t b_val = uint256_t(b);
473
474 // Test add
475 F sum = a + b;
476 uint512_t expected_sum = (uint512_t(a_val) + uint512_t(b_val)) % uint512_t(F::modulus);
477 EXPECT_EQ(uint256_t(sum), expected_sum.lo) << "Add failed for limb " << limb_idx;
478
479 // Test sub
480 F diff = a - b;
481 uint512_t expected_diff = (uint512_t(a_val) + uint512_t(F::modulus) - uint512_t(b_val)) % uint512_t(F::modulus);
482 EXPECT_EQ(uint256_t(diff), expected_diff.lo) << "Sub failed for limb " << limb_idx;
483
484 // Test mul
485 F prod = a * b;
486 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(b_val)) % uint512_t(F::modulus);
487 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Mul failed for limb " << limb_idx;
488
489 // Test sqr
490 F sq = a.sqr();
491 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
492 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for limb " << limb_idx;
493 }
494}
495
496// Test multiplication of two single-limb values (different limbs)
497TYPED_TEST(PrimeFieldTest, CrossLimbMultiplication)
498{
499 using F = TypeParam;
500
501 // Create elements with single non-zero limbs at different positions
502 // For limb 3, use modulus.data[3] / 2 to stay below modulus
503 constexpr std::array<std::array<uint64_t, 4>, 4> limb_patterns = { { { { 0xABCDEF0123456789ULL, 0, 0, 0 } },
504 { { 0, 0x9876543210FEDCBAULL, 0, 0 } },
505 { { 0, 0, 0x1111222233334444ULL, 0 } },
506 { { 0, 0, 0, F::modulus.data[3] / 2 } } } };
507
508 // Build field elements and their uint256_t values
509 std::array<F, 4> elems;
511 for (size_t i = 0; i < 4; ++i) {
512 elems[i] = create_element_with_limbs<F>(
513 limb_patterns[i][0], limb_patterns[i][1], limb_patterns[i][2], limb_patterns[i][3]);
514 vals[i] = uint256_t(elems[i]);
515 }
516
517 // Test all pairwise multiplications
518 for (size_t i = 0; i < 4; ++i) {
519 for (size_t j = 0; j < 4; ++j) {
520 F prod = elems[i] * elems[j];
521 uint512_t expected_prod = (uint512_t(vals[i]) * uint512_t(vals[j])) % uint512_t(F::modulus);
522 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Failed for limb " << i << " * limb " << j;
523 }
524 }
525}
526
527// ================================
528// Edge Cases Near Modulus Boundary
529// ================================
530
531// Test multiplication and squaring of values near the modulus
532TYPED_TEST(PrimeFieldTest, NearModulusMultiplication)
533{
534 using F = TypeParam;
535
536 for (uint64_t offset = 1; offset <= 10; ++offset) {
537 uint256_t val = F::modulus - offset;
538 F a(val);
539 uint256_t a_val = uint256_t(a);
540
541 // Test squaring
542 F sq = a.sqr();
543 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
544 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for (p - " << offset << ")^2";
545
546 // Test a * (a + 1)
547 F a_plus_one = a + F(1);
548 uint256_t a_plus_one_val = uint256_t(a_plus_one);
549 F prod = a * a_plus_one;
550 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(a_plus_one_val)) % uint512_t(F::modulus);
551 EXPECT_EQ(uint256_t(prod), expected_prod.lo)
552 << "Mul failed for (p - " << offset << ") * (p - " << offset << " + 1)";
553 }
554}
555
556// ================================
557// Boundary Tests
558// ================================
559// Test arithmetic with internal representations near the representation boundary.
560// - 254-bit fields (modulus.data[3] < MODULUS_TOP_LIMB_LARGE_THRESHOLD): boundary is 2p
561// - 256-bit fields (modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD): boundary is 2^256 - 1
562
563TYPED_TEST(PrimeFieldTest, BoundaryArithmetic)
564{
565 using F = TypeParam;
566 constexpr std::array<uint64_t, 3> offsets = { 1, 2, 3 };
567
568 for (uint64_t offset : offsets) {
569 F a;
570 if constexpr (F::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
571 // 256-bit fields: construct element with internal representation near 2^256 - offset.
572 // (p - offset) + (2^256 - p) = 2^256 - offset, which has field value -offset.
573 uint256_t two_256_minus_p = uint256_t(0) - F::modulus;
574 F two_256_minus_p_elt(two_256_minus_p);
575 two_256_minus_p_elt.self_from_montgomery_form_reduced();
576 F p_minus_offset(F::modulus - offset);
577 p_minus_offset.self_from_montgomery_form_reduced();
578 a = p_minus_offset + two_256_minus_p_elt;
579
580 // Verify internal representation is 2^256 - offset
581 if (offset == 1) {
582 for (size_t i = 0; i < 4; ++i) {
583 EXPECT_EQ(a.data[i], 0xFFFFFFFFFFFFFFFFULL);
584 }
585 }
586 } else {
587 // 254-bit fields: construct element with internal representation near 2p - (offset + 1).
588 // (p - 1) + (p - offset) = 2p - (offset + 1), which has field value -(offset + 1).
589 F p_minus_one(F::modulus - 1);
590 p_minus_one.self_from_montgomery_form_reduced();
591 F p_minus_offset(F::modulus - offset);
592 p_minus_offset.self_from_montgomery_form_reduced();
593 a = p_minus_one + p_minus_offset;
594 }
595
596 uint256_t a_val = uint256_t(a);
597 F b = F::random_element();
598
599 // Test all operations
600 F sum = a + b;
601 uint512_t expected_sum = (uint512_t(a_val) + uint512_t(uint256_t(b))) % uint512_t(F::modulus);
602 EXPECT_EQ(uint256_t(sum), expected_sum.lo) << "Add failed for offset " << offset;
603
604 F diff = a - b;
605 uint512_t expected_diff =
606 (uint512_t(a_val) + uint512_t(F::modulus) - uint512_t(uint256_t(b))) % uint512_t(F::modulus);
607 EXPECT_EQ(uint256_t(diff), expected_diff.lo) << "Sub failed for offset " << offset;
608
609 F prod = a * b;
610 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(uint256_t(b))) % uint512_t(F::modulus);
611 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Mul failed for offset " << offset;
612
613 F sq = a.sqr();
614 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
615 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for offset " << offset;
616 }
617}
618
619// ================================
620// Serialization
621// ================================
622
624{
625 using F = TypeParam;
626
627 F a = F::random_element();
628 auto [actual, expected] = msgpack_roundtrip(a);
629 EXPECT_EQ(actual, expected);
630}
631
632// This test requires exception support; in WASM builds (BB_NO_EXCEPTIONS),
633// throw_or_abort calls abort() instead of throwing, so EXPECT_THROW cannot work.
634#ifndef BB_NO_EXCEPTIONS
635TYPED_TEST(PrimeFieldTest, MsgpackRejectsNonCanonical)
636{
637 using F = TypeParam;
638
639 // Use a small value so that (value + modulus) is guaranteed to fit in 256 bits,
640 // even for fields with moduli close to 2^256 (e.g., secp256k1, secp256r1).
641 F a = F(1);
642 msgpack::sbuffer buffer;
643 msgpack::pack(buffer, a);
644
645 // Find the 32-byte binary payload inside the msgpack buffer.
646 // pack_bin(32) produces: 0xc4 0x20 <32 bytes>
647 uint8_t* buf = reinterpret_cast<uint8_t*>(buffer.data());
648 size_t buf_size = buffer.size();
649 uint8_t* field_bytes = nullptr;
650 for (size_t i = 0; i + 33 <= buf_size; i++) {
651 if (buf[i] == 0xc4 && buf[i + 1] == 0x20) {
652 field_bytes = &buf[i + 2];
653 break;
654 }
655 }
656 ASSERT_NE(field_bytes, nullptr) << "Could not find 32-byte bin payload in msgpack buffer";
657
658 // Replace the payload with (1 + modulus), a non-canonical encoding of 1.
659 uint256_t non_canonical = uint256_t(1) + F::modulus;
660 for (int i = 31; i >= 0; i--) {
661 field_bytes[i] = static_cast<uint8_t>(non_canonical.data[0] & 0xFF);
662 non_canonical >>= 8;
663 }
664
665 // Deserializing the mutated buffer must throw
666 EXPECT_THROW(
667 {
668 msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
669 F result;
670 oh.get().convert(result);
671 },
672 std::runtime_error);
673}
674#else
675TYPED_TEST(PrimeFieldTest, MsgpackRejectsNonCanonical)
676{
677 GTEST_SKIP() << "Skipping: throw_or_abort calls abort() when BB_NO_EXCEPTIONS is defined";
678}
679#endif
680
681// ================================
682// Montgomery Form Conversion Tests (254-bit fields)
683// ================================
684
688TYPED_TEST(PrimeFieldTwoFiftyFourTest, FromMontgomeryFormNoReductionNeeded)
689{
690 using F = TypeParam;
691 constexpr uint256_t two_p = F::modulus + F::modulus;
692
693 // Test 1: Random elements
694 for (size_t i = 0; i < 100; i++) {
695 F a = F::random_element();
696
697 // Get internal limbs before conversion
698 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
699
700 // Verify input is in [0, 2p) range (coarse representation)
701 ASSERT_LT(a_internal, two_p) << "Input not in coarse form";
702
703 F result = a.from_montgomery_form();
704
705 // Check result is already in [0, 2p) range
706 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
707 EXPECT_LT(result_internal, two_p) << "Result of from_montgomery_form exceeds [0, 2p) range";
708 }
709
710 // Test 2: Edge cases - elements near the boundary 2p
711 // Construct elements with internal representation near 2p - 1
712 for (uint64_t offset = 1; offset <= 10; offset++) {
713 // Create element with internal representation = (2p - offset)
714 // We do this by adding (p - 1) + (p - offset + 1) = 2p - offset
715 F p_minus_one(F::modulus - 1);
716 p_minus_one.self_from_montgomery_form();
717 F p_minus_offset_plus_one(F::modulus - offset + 1);
718 p_minus_offset_plus_one.self_from_montgomery_form();
719 F a = p_minus_one + p_minus_offset_plus_one;
720
721 // Verify we have internal representation near 2p
722 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
723 ASSERT_LT(a_internal, two_p) << "Test setup: element not in valid range";
724
725 F result = a.from_montgomery_form();
726
727 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
728 EXPECT_LT(result_internal, two_p)
729 << "Edge case: Result of from_montgomery_form exceeds [0, 2p) for offset " << offset;
730 }
731}
732
736TYPED_TEST(PrimeFieldTwoFiftyFourTest, ToMontgomeryFormNoReductionNeeded)
737{
738 using F = TypeParam;
739 constexpr uint256_t two_p = F::modulus + F::modulus;
740
741 // Test 1: Random elements
742 for (size_t i = 0; i < 100; i++) {
743 F a = F::random_element();
744
745 // Get internal limbs before conversion
746 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
747
748 // Verify input is in [0, 2p) range (coarse representation)
749 ASSERT_LT(a_internal, two_p) << "Input not in coarse form";
750
751 F result = a.to_montgomery_form();
752
753 // Check result is already in [0, 2p) range
754 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
755 EXPECT_LT(result_internal, two_p) << "Result of to_montgomery_form exceeds [0, 2p) range";
756 }
757
758 // Test 2: Edge cases - elements near the boundary 2p
759 // Construct elements with internal representation near 2p - 1
760 for (uint64_t offset = 1; offset <= 10; offset++) {
761 // Create element with internal representation = (2p - offset)
762 // We do this by adding (p - 1) + (p - offset + 1) = 2p - offset
763 F p_minus_one(F::modulus - 1);
764 p_minus_one.self_from_montgomery_form();
765 F p_minus_offset_plus_one(F::modulus - offset + 1);
766 p_minus_offset_plus_one.self_from_montgomery_form();
767 F a = p_minus_one + p_minus_offset_plus_one;
768
769 // Verify we have internal representation near 2p
770 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
771 ASSERT_LT(a_internal, two_p) << "Test setup: element not in valid range";
772
773 F result = a.to_montgomery_form();
774
775 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
776 EXPECT_LT(result_internal, two_p)
777 << "Edge case: Result of to_montgomery_form exceeds [0, 2p) for offset " << offset;
778 }
779}
static uint256_t get_random_element_raw()
virtual uint256_t get_random_uint256()=0
FF a
FF b
numeric::RNG & engine
ssize_t offset
Definition engine.cpp:62
std::unique_ptr< uint8_t[]> buffer
Definition engine.cpp:60
uintx< uint256_t > uint512_t
Definition uintx.hpp:309
RNG & get_debug_randomness(bool reset, std::uint_fast64_t seed)
Definition engine.cpp:245
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
Inner sum(Cont< Inner, Args... > const &in)
Definition container.hpp:70
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
::testing::Types< bb::fq, bb::fr > TwoFiftyFourBitFieldTypes
::testing::Types< bb::fq, bb::fr, secp256k1::fq, secp256r1::fq > SqrtFieldTypes
::testing::Types< secp256k1::fq, secp256k1::fr > TwoFiftySixBitFieldTypes
::testing::Types< bb::fq, bb::fr, secp256k1::fq, secp256k1::fr, secp256r1::fq, secp256r1::fr > PrimeFieldTypes
::testing::Types< bb::fq, bb::fr, secp256k1::fq, secp256k1::fr > CubeRootFieldTypes
BB_INLINE constexpr field to_montgomery_form() const noexcept
BB_INLINE constexpr field pow(const uint256_t &exponent) const noexcept
BB_INLINE constexpr field sqr() const noexcept
constexpr std::pair< bool, field > sqrt() const noexcept
Compute square root of the field element.
BB_INLINE constexpr field from_montgomery_form() const noexcept
std::pair< T, T > msgpack_roundtrip(const T &object)