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// Regression for the WASM coarse-Montgomery footgun behind the fused IPA fold (#330) abort:
165// from_montgomery_form() is only coarse-reduced ([0, 2p)) and returns value+p on WASM's 29-bit-limb
166// Montgomery backend, so reading its raw limbs misreads a short value as full-width. The reduced
167// variant must return the canonical ([0, p)) value on every platform. This deterministically fails on
168// WASM if a limb-level caller reads from_montgomery_form()'s coarse limbs, and passes everywhere once
169// from_montgomery_form_reduced() is used instead.
170TYPED_TEST(PrimeFieldTest, ReducedLimbsAreCanonicalForShortValues)
171{
172 using F = TypeParam;
173
174 for (size_t i = 0; i < 256; ++i) {
175 // A 127-bit value, as produced by the fused fold's challenge inputs.
176 const F seed = F::random_element().from_montgomery_form_reduced();
177 const uint256_t value(seed.data[0], seed.data[1] & 0x7FFFFFFFFFFFFFFFULL, 0, 0);
178
179 const F reduced = F(value).from_montgomery_form_reduced();
180 EXPECT_EQ(reduced.data[0], value.data[0]);
181 EXPECT_EQ(reduced.data[1], value.data[1]);
182 EXPECT_TRUE(((reduced.data[2] | reduced.data[3]) == 0) && ((reduced.data[1] >> 63) == 0))
183 << "from_montgomery_form_reduced() must be canonical (< 2^127) for a 127-bit value";
184 }
185}
186
187// ================================
188// uint256_t Arithmetic Verification
189// ================================
190
191TYPED_TEST(PrimeFieldTest, AdditionModular)
192{
193 using F = TypeParam;
194
195 uint256_t a_raw = TestFixture::get_random_element_raw();
196 uint256_t b_raw = TestFixture::get_random_element_raw();
197
198 F a(a_raw);
199 F b(b_raw);
200 F c = a + b;
201
202 uint512_t expected_512 = uint512_t(a_raw) + uint512_t(b_raw);
203 uint256_t expected = (expected_512 % uint512_t(F::modulus)).lo;
204
205 EXPECT_EQ(uint256_t(c), expected);
206}
207
208TYPED_TEST(PrimeFieldTest, SubtractionModular)
209{
210 using F = TypeParam;
211
212 uint256_t a_raw = TestFixture::get_random_element_raw();
213 uint256_t b_raw = TestFixture::get_random_element_raw();
214
215 F a(a_raw);
216 F b(b_raw);
217 F c = a - b;
218
219 uint512_t expected_512 = uint512_t(a_raw) + uint512_t(F::modulus) - uint512_t(b_raw);
220 uint256_t expected = (expected_512 % uint512_t(F::modulus)).lo;
221
222 EXPECT_EQ(uint256_t(c), expected);
223}
224
225TYPED_TEST(PrimeFieldTest, MultiplicationModular)
226{
227 using F = TypeParam;
228
229 uint256_t a_raw = TestFixture::get_random_element_raw();
230 uint256_t b_raw = TestFixture::get_random_element_raw();
231
232 F a(a_raw);
233 F b(b_raw);
234 F c = a * b;
235
236 uint512_t c_512 = uint512_t(a_raw) * uint512_t(b_raw);
237 uint256_t expected = (c_512 % uint512_t(F::modulus)).lo;
238
239 EXPECT_EQ(uint256_t(c), expected);
240}
241
242TYPED_TEST(PrimeFieldTest, SquaringModular)
243{
244 using F = TypeParam;
245
246 uint256_t a_raw = TestFixture::get_random_element_raw();
247
248 F a(a_raw);
249 F c = a.sqr();
250
251 uint512_t c_512 = uint512_t(a_raw) * uint512_t(a_raw);
252 uint256_t expected = (c_512 % uint512_t(F::modulus)).lo;
253
254 EXPECT_EQ(uint256_t(c), expected);
255}
256
257TYPED_TEST(PrimeFieldTest, Uint256Roundtrip)
258{
259 using F = TypeParam;
260
261 uint256_t original = TestFixture::get_random_element_raw();
262 F field_element(original);
263 uint256_t recovered(field_element);
264
265 EXPECT_EQ(original, recovered);
266}
267
268// ================================
269// Montgomery Form (Prime Field Specific)
270// ================================
271
272TYPED_TEST(PrimeFieldTest, MontgomeryRoundtrip)
273{
274 using F = TypeParam;
275
276 F a = F::random_element();
278 EXPECT_EQ(a, b);
279}
280
281// ================================
282// Square Root
283// ================================
284
286{
287 using F = TypeParam;
288
289 F one = F::one();
290 auto [is_sqr, root] = one.sqrt();
291
292 EXPECT_TRUE(is_sqr);
293 EXPECT_EQ(root.sqr(), one);
294}
295
297{
298 using F = TypeParam;
299
300 F a = F::random_element();
301 F a_sqr = a.sqr();
302 auto [is_sqr, root] = a_sqr.sqrt();
303
304 EXPECT_TRUE(is_sqr);
305 EXPECT_EQ(root.sqr(), a_sqr);
306 EXPECT_TRUE((root == a) || (root == -a));
307}
308
309// ================================
310// Cube Root of Unity
311// ================================
312
314{
315 using F = TypeParam;
316
317 // lambda^3 = 1, so (lambda * x)^3 = x^3
318 F x = F::random_element();
319 F lambda = F::cube_root_of_unity();
320 F lambda_x = x * lambda;
321
322 F x_cubed = x * x * x;
323 F lambda_x_cubed = lambda_x * lambda_x * lambda_x;
324
325 EXPECT_EQ(x_cubed, lambda_x_cubed);
326}
327
328// ================================
329// Exponentiation
330// ================================
331
332TYPED_TEST(PrimeFieldTest, PowZeroExponent)
333{
334 using F = TypeParam;
335
336 // a^0 = 1 for any non-zero a
337 F a = F::random_element();
338 EXPECT_EQ(a.pow(uint256_t(0)), F::one());
339}
340
342{
343 using F = TypeParam;
344
345 F a = F::random_element();
346 EXPECT_EQ(a.pow(uint256_t(1)), a);
347}
348
350{
351 using F = TypeParam;
352
353 F a = F::random_element();
354 EXPECT_EQ(a.pow(uint256_t(2)), a * a);
355}
356
358{
359 using F = TypeParam;
360
361 F a = F::random_element();
362 EXPECT_EQ(a.pow(uint256_t(3)), a * a * a);
363}
364
365// ================================
366// Batch Invert (only implemented for prime fields)
367// ================================
368
370{
371 using F = TypeParam;
372 constexpr size_t batch_size = 10;
373
374 std::vector<F> elements(batch_size);
375 std::vector<F> inverses(batch_size);
376
377 for (size_t i = 0; i < batch_size; ++i) {
378 elements[i] = F::random_element();
379 inverses[i] = elements[i];
380 }
381
382 F::batch_invert(&inverses[0], batch_size);
383
384 for (size_t i = 0; i < batch_size; ++i) {
385 F product = elements[i] * inverses[i];
386 product = product.reduce_once().reduce_once();
387 EXPECT_EQ(product, F::one());
388 }
389}
390
391// ================================
392// Increment Operators
393// ================================
394
396{
397 using F = TypeParam;
398
399 F a = F::random_element();
400 F a_copy = a;
401
402 a += 2;
403 F expected = a_copy + F(2);
404 EXPECT_EQ(a, expected);
405
406 a += 3;
407 expected = a_copy + F(5);
408 EXPECT_EQ(a, expected);
409}
410
411TYPED_TEST(PrimeFieldTest, PrefixIncrement)
412{
413 using F = TypeParam;
414
415 F a = F::random_element();
416 F a_before = a;
417 F b = ++a;
418
419 // Prefix increment returns the new value
420 EXPECT_EQ(b, a);
421 EXPECT_EQ(a, a_before + F(1));
422}
423
424TYPED_TEST(PrimeFieldTest, PostfixIncrement)
425{
426 using F = TypeParam;
427
428 F a = F::random_element();
429 F a_old = a;
430 F b = a++;
431
432 // Postfix increment returns the old value
433 EXPECT_EQ(b, a_old);
434 EXPECT_EQ(a, a_old + F(1));
435}
436
437// ================================
438// Internal Representation Tests for Big Fields
439// ================================
440
441// Shows that the raw limbs of an addition can have a result which is not automatically reduced, even when we are in the
442// 256-bit field range.
443TYPED_TEST(PrimeFieldTwoFiftySixTest, AddYieldsLimbsBiggerThanModulus)
444{
445 using F = TypeParam;
446
447 auto small_number = 10;
448 F small_field_elt = F(small_number).from_montgomery_form();
449 uint256_t small_number_from_limbs(
450 small_field_elt.data[0], small_field_elt.data[1], small_field_elt.data[2], small_field_elt.data[3]);
451 uint256_t big_number = uint256_t(F::modulus) - 1;
452 F big_field_elt = F(big_number).from_montgomery_form();
453 uint256_t big_number_from_limbs(
454 big_field_elt.data[0], big_field_elt.data[1], big_field_elt.data[2], big_field_elt.data[3]);
455
456 // make sure that the limbs combine to the number. (this is not immediate because we internall represent via
457 // Montgomery form. Here, it is guaranteed because we call `.from_montgomery_form()`).
458 EXPECT_EQ(small_number_from_limbs, small_number);
459 EXPECT_EQ(big_number, big_number_from_limbs);
460
461 F result = small_field_elt + big_field_elt;
462
463 // Extract the raw limbs from result
464 uint256_t result_from_limbs(result.data[0], result.data[1], result.data[2], result.data[3]);
465
466 // Check if the uint256_t from limbs is >= p
467 bool is_gte_modulus = result_from_limbs >= F::modulus;
468 EXPECT_EQ(is_gte_modulus, true);
469}
470
471// ================================
472// Single Non-Zero Limb Edge Cases
473// ================================
474// These tests verify correctness when the internal Montgomery representation has only one
475// non-zero limb, which can expose carry propagation bugs in Montgomery multiplication and
476// other operations.
477
478TYPED_TEST(PrimeFieldTest, SingleLimbArithmetic)
479{
480 using F = TypeParam;
481
482 // Test values for each limb position (only one limb non-zero at a time)
483 // For limb 3, use modulus.data[3] / 2 to stay below modulus
484 constexpr std::array<uint64_t, 4> limb_values = {
485 0xDEADBEEFCAFEBABEULL, 0xFEDCBA9876543210ULL, 0x123456789ABCDEF0ULL, F::modulus.data[3] / 2
486 };
487
488 for (size_t limb_idx = 0; limb_idx < 4; ++limb_idx) {
489 std::array<uint64_t, 4> limbs = { 0, 0, 0, 0 };
490 limbs[limb_idx] = limb_values[limb_idx];
491
492 F a = create_element_with_limbs<F>(limbs[0], limbs[1], limbs[2], limbs[3]);
493 F b = F::random_element();
494 uint256_t a_val = uint256_t(a);
495 uint256_t b_val = uint256_t(b);
496
497 // Test add
498 F sum = a + b;
499 uint512_t expected_sum = (uint512_t(a_val) + uint512_t(b_val)) % uint512_t(F::modulus);
500 EXPECT_EQ(uint256_t(sum), expected_sum.lo) << "Add failed for limb " << limb_idx;
501
502 // Test sub
503 F diff = a - b;
504 uint512_t expected_diff = (uint512_t(a_val) + uint512_t(F::modulus) - uint512_t(b_val)) % uint512_t(F::modulus);
505 EXPECT_EQ(uint256_t(diff), expected_diff.lo) << "Sub failed for limb " << limb_idx;
506
507 // Test mul
508 F prod = a * b;
509 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(b_val)) % uint512_t(F::modulus);
510 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Mul failed for limb " << limb_idx;
511
512 // Test sqr
513 F sq = a.sqr();
514 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
515 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for limb " << limb_idx;
516 }
517}
518
519// Test multiplication of two single-limb values (different limbs)
520TYPED_TEST(PrimeFieldTest, CrossLimbMultiplication)
521{
522 using F = TypeParam;
523
524 // Create elements with single non-zero limbs at different positions
525 // For limb 3, use modulus.data[3] / 2 to stay below modulus
526 constexpr std::array<std::array<uint64_t, 4>, 4> limb_patterns = { { { { 0xABCDEF0123456789ULL, 0, 0, 0 } },
527 { { 0, 0x9876543210FEDCBAULL, 0, 0 } },
528 { { 0, 0, 0x1111222233334444ULL, 0 } },
529 { { 0, 0, 0, F::modulus.data[3] / 2 } } } };
530
531 // Build field elements and their uint256_t values
532 std::array<F, 4> elems;
534 for (size_t i = 0; i < 4; ++i) {
535 elems[i] = create_element_with_limbs<F>(
536 limb_patterns[i][0], limb_patterns[i][1], limb_patterns[i][2], limb_patterns[i][3]);
537 vals[i] = uint256_t(elems[i]);
538 }
539
540 // Test all pairwise multiplications
541 for (size_t i = 0; i < 4; ++i) {
542 for (size_t j = 0; j < 4; ++j) {
543 F prod = elems[i] * elems[j];
544 uint512_t expected_prod = (uint512_t(vals[i]) * uint512_t(vals[j])) % uint512_t(F::modulus);
545 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Failed for limb " << i << " * limb " << j;
546 }
547 }
548}
549
550// ================================
551// Edge Cases Near Modulus Boundary
552// ================================
553
554// Test multiplication and squaring of values near the modulus
555TYPED_TEST(PrimeFieldTest, NearModulusMultiplication)
556{
557 using F = TypeParam;
558
559 for (uint64_t offset = 1; offset <= 10; ++offset) {
560 uint256_t val = F::modulus - offset;
561 F a(val);
562 uint256_t a_val = uint256_t(a);
563
564 // Test squaring
565 F sq = a.sqr();
566 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
567 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for (p - " << offset << ")^2";
568
569 // Test a * (a + 1)
570 F a_plus_one = a + F(1);
571 uint256_t a_plus_one_val = uint256_t(a_plus_one);
572 F prod = a * a_plus_one;
573 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(a_plus_one_val)) % uint512_t(F::modulus);
574 EXPECT_EQ(uint256_t(prod), expected_prod.lo)
575 << "Mul failed for (p - " << offset << ") * (p - " << offset << " + 1)";
576 }
577}
578
579// ================================
580// Boundary Tests
581// ================================
582// Test arithmetic with internal representations near the representation boundary.
583// - 254-bit fields (modulus.data[3] < MODULUS_TOP_LIMB_LARGE_THRESHOLD): boundary is 2p
584// - 256-bit fields (modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD): boundary is 2^256 - 1
585
586TYPED_TEST(PrimeFieldTest, BoundaryArithmetic)
587{
588 using F = TypeParam;
589 constexpr std::array<uint64_t, 3> offsets = { 1, 2, 3 };
590
591 for (uint64_t offset : offsets) {
592 F a;
593 if constexpr (F::modulus.data[3] >= MODULUS_TOP_LIMB_LARGE_THRESHOLD) {
594 // 256-bit fields: construct element with internal representation near 2^256 - offset.
595 // (p - offset) + (2^256 - p) = 2^256 - offset, which has field value -offset.
596 uint256_t two_256_minus_p = uint256_t(0) - F::modulus;
597 F two_256_minus_p_elt(two_256_minus_p);
598 two_256_minus_p_elt.self_from_montgomery_form_reduced();
599 F p_minus_offset(F::modulus - offset);
600 p_minus_offset.self_from_montgomery_form_reduced();
601 a = p_minus_offset + two_256_minus_p_elt;
602
603 // Verify internal representation is 2^256 - offset
604 if (offset == 1) {
605 for (size_t i = 0; i < 4; ++i) {
606 EXPECT_EQ(a.data[i], 0xFFFFFFFFFFFFFFFFULL);
607 }
608 }
609 } else {
610 // 254-bit fields: construct element with internal representation near 2p - (offset + 1).
611 // (p - 1) + (p - offset) = 2p - (offset + 1), which has field value -(offset + 1).
612 F p_minus_one(F::modulus - 1);
613 p_minus_one.self_from_montgomery_form_reduced();
614 F p_minus_offset(F::modulus - offset);
615 p_minus_offset.self_from_montgomery_form_reduced();
616 a = p_minus_one + p_minus_offset;
617 }
618
619 uint256_t a_val = uint256_t(a);
620 F b = F::random_element();
621
622 // Test all operations
623 F sum = a + b;
624 uint512_t expected_sum = (uint512_t(a_val) + uint512_t(uint256_t(b))) % uint512_t(F::modulus);
625 EXPECT_EQ(uint256_t(sum), expected_sum.lo) << "Add failed for offset " << offset;
626
627 F diff = a - b;
628 uint512_t expected_diff =
629 (uint512_t(a_val) + uint512_t(F::modulus) - uint512_t(uint256_t(b))) % uint512_t(F::modulus);
630 EXPECT_EQ(uint256_t(diff), expected_diff.lo) << "Sub failed for offset " << offset;
631
632 F prod = a * b;
633 uint512_t expected_prod = (uint512_t(a_val) * uint512_t(uint256_t(b))) % uint512_t(F::modulus);
634 EXPECT_EQ(uint256_t(prod), expected_prod.lo) << "Mul failed for offset " << offset;
635
636 F sq = a.sqr();
637 uint512_t expected_sq = (uint512_t(a_val) * uint512_t(a_val)) % uint512_t(F::modulus);
638 EXPECT_EQ(uint256_t(sq), expected_sq.lo) << "Sqr failed for offset " << offset;
639 }
640}
641
642// ================================
643// Serialization
644// ================================
645
647{
648 using F = TypeParam;
649
650 F a = F::random_element();
651 auto [actual, expected] = msgpack_roundtrip(a);
652 EXPECT_EQ(actual, expected);
653}
654
655// This test requires exception support; in WASM builds (BB_NO_EXCEPTIONS),
656// throw_or_abort calls abort() instead of throwing, so EXPECT_THROW cannot work.
657#ifndef BB_NO_EXCEPTIONS
658TYPED_TEST(PrimeFieldTest, MsgpackRejectsNonCanonical)
659{
660 using F = TypeParam;
661
662 // Use a small value so that (value + modulus) is guaranteed to fit in 256 bits,
663 // even for fields with moduli close to 2^256 (e.g., secp256k1, secp256r1).
664 F a = F(1);
665 msgpack::sbuffer buffer;
666 msgpack::pack(buffer, a);
667
668 // Find the 32-byte binary payload inside the msgpack buffer.
669 // pack_bin(32) produces: 0xc4 0x20 <32 bytes>
670 uint8_t* buf = reinterpret_cast<uint8_t*>(buffer.data());
671 size_t buf_size = buffer.size();
672 uint8_t* field_bytes = nullptr;
673 for (size_t i = 0; i + 33 <= buf_size; i++) {
674 if (buf[i] == 0xc4 && buf[i + 1] == 0x20) {
675 field_bytes = &buf[i + 2];
676 break;
677 }
678 }
679 ASSERT_NE(field_bytes, nullptr) << "Could not find 32-byte bin payload in msgpack buffer";
680
681 // Replace the payload with (1 + modulus), a non-canonical encoding of 1.
682 uint256_t non_canonical = uint256_t(1) + F::modulus;
683 for (int i = 31; i >= 0; i--) {
684 field_bytes[i] = static_cast<uint8_t>(non_canonical.data[0] & 0xFF);
685 non_canonical >>= 8;
686 }
687
688 // Deserializing the mutated buffer must throw
689 EXPECT_THROW(
690 {
691 msgpack::object_handle oh = msgpack::unpack(buffer.data(), buffer.size());
692 F result;
693 oh.get().convert(result);
694 },
695 std::runtime_error);
696}
697#else
698TYPED_TEST(PrimeFieldTest, MsgpackRejectsNonCanonical)
699{
700 GTEST_SKIP() << "Skipping: throw_or_abort calls abort() when BB_NO_EXCEPTIONS is defined";
701}
702#endif
703
704// ================================
705// Montgomery Form Conversion Tests (254-bit fields)
706// ================================
707
711TYPED_TEST(PrimeFieldTwoFiftyFourTest, FromMontgomeryFormNoReductionNeeded)
712{
713 using F = TypeParam;
714 constexpr uint256_t two_p = F::modulus + F::modulus;
715
716 // Test 1: Random elements
717 for (size_t i = 0; i < 100; i++) {
718 F a = F::random_element();
719
720 // Get internal limbs before conversion
721 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
722
723 // Verify input is in [0, 2p) range (coarse representation)
724 ASSERT_LT(a_internal, two_p) << "Input not in coarse form";
725
727
728 // Check result is already in [0, 2p) range
729 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
730 EXPECT_LT(result_internal, two_p) << "Result of from_montgomery_form exceeds [0, 2p) range";
731 }
732
733 // Test 2: Edge cases - elements near the boundary 2p
734 // Construct elements with internal representation near 2p - 1
735 for (uint64_t offset = 1; offset <= 10; offset++) {
736 // Create element with internal representation = (2p - offset)
737 // We do this by adding (p - 1) + (p - offset + 1) = 2p - offset
738 F p_minus_one(F::modulus - 1);
739 p_minus_one.self_from_montgomery_form();
740 F p_minus_offset_plus_one(F::modulus - offset + 1);
741 p_minus_offset_plus_one.self_from_montgomery_form();
742 F a = p_minus_one + p_minus_offset_plus_one;
743
744 // Verify we have internal representation near 2p
745 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
746 ASSERT_LT(a_internal, two_p) << "Test setup: element not in valid range";
747
749
750 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
751 EXPECT_LT(result_internal, two_p)
752 << "Edge case: Result of from_montgomery_form exceeds [0, 2p) for offset " << offset;
753 }
754}
755
759TYPED_TEST(PrimeFieldTwoFiftyFourTest, ToMontgomeryFormNoReductionNeeded)
760{
761 using F = TypeParam;
762 constexpr uint256_t two_p = F::modulus + F::modulus;
763
764 // Test 1: Random elements
765 for (size_t i = 0; i < 100; i++) {
766 F a = F::random_element();
767
768 // Get internal limbs before conversion
769 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
770
771 // Verify input is in [0, 2p) range (coarse representation)
772 ASSERT_LT(a_internal, two_p) << "Input not in coarse form";
773
775
776 // Check result is already in [0, 2p) range
777 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
778 EXPECT_LT(result_internal, two_p) << "Result of to_montgomery_form exceeds [0, 2p) range";
779 }
780
781 // Test 2: Edge cases - elements near the boundary 2p
782 // Construct elements with internal representation near 2p - 1
783 for (uint64_t offset = 1; offset <= 10; offset++) {
784 // Create element with internal representation = (2p - offset)
785 // We do this by adding (p - 1) + (p - offset + 1) = 2p - offset
786 F p_minus_one(F::modulus - 1);
787 p_minus_one.self_from_montgomery_form();
788 F p_minus_offset_plus_one(F::modulus - offset + 1);
789 p_minus_offset_plus_one.self_from_montgomery_form();
790 F a = p_minus_one + p_minus_offset_plus_one;
791
792 // Verify we have internal representation near 2p
793 uint256_t a_internal(a.data[0], a.data[1], a.data[2], a.data[3]);
794 ASSERT_LT(a_internal, two_p) << "Test setup: element not in valid range";
795
797
798 uint256_t result_internal(result.data[0], result.data[1], result.data[2], result.data[3]);
799 EXPECT_LT(result_internal, two_p)
800 << "Edge case: Result of to_montgomery_form exceeds [0, 2p) for offset " << offset;
801 }
802}
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
Field get(size_t i) const noexcept
BB_INLINE constexpr field from_montgomery_form_reduced() const noexcept
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)
VectorField result