lambdaworks_math/field/fields/fft_friendly/
babybear_u32.rs

1use crate::field::{
2    fields::u32_montgomery_backend_prime_field::U32MontgomeryBackendPrimeField, traits::IsFFTField,
3};
4
5// Babybear Prime p = 2^31 - 2^27 + 1 = 0x78000001 = 2013265921
6pub type Babybear31PrimeField = U32MontgomeryBackendPrimeField<2013265921>;
7
8// p = 2^31 - 2^27 + 1 = 2^27 * (2^4-1) + 1, then
9// there is a gruop in the field of order 2^27.
10// Since we want to have margin to be able to define a bigger group (blow-up group),
11// we define TWO_ADICITY as 24 (so the blow-up factor can be 2^3 = 8).
12// A two-adic primitive root of unity is 21^(2^24) because
13// 21^(2^24)=1 mod 2013265921.
14// In the future we should allow this with cuda feature, and just dispatch it to the CPU until the implementation is done
15#[cfg(not(feature = "cuda"))]
16impl IsFFTField for Babybear31PrimeField {
17    const TWO_ADICITY: u64 = 24;
18
19    const TWO_ADIC_PRIMITVE_ROOT_OF_UNITY: Self::BaseType = 21;
20
21    fn field_name() -> &'static str {
22        "babybear31"
23    }
24}
25
26#[cfg(test)]
27mod tests {
28    use super::*;
29    mod test_babybear_31_ops {
30        use super::*;
31        use crate::{
32            errors::CreationError,
33            field::{element::FieldElement, errors::FieldError, traits::IsPrimeField},
34            traits::ByteConversion,
35        };
36        type FE = FieldElement<Babybear31PrimeField>;
37
38        #[test]
39        fn two_plus_one_is_three() {
40            let a = FE::from(2);
41            let b = FE::one();
42            let res = FE::from(3);
43
44            assert_eq!(a + b, res)
45        }
46
47        #[test]
48        fn one_minus_two_is_minus_one() {
49            let a = FE::from(2);
50            let b = FE::one();
51            let res = FE::from(2013265920);
52            assert_eq!(b - a, res)
53        }
54
55        #[test]
56        fn mul_by_zero_is_zero() {
57            let a = FE::from(2);
58            let b = FE::zero();
59            assert_eq!(a * b, b)
60        }
61
62        #[test]
63        fn neg_zero_is_zero() {
64            let zero = FE::from(0);
65
66            assert_eq!(-&zero, zero);
67        }
68
69        #[test]
70        fn doubling() {
71            assert_eq!(FE::from(2).double(), FE::from(2) + FE::from(2),);
72        }
73
74        const ORDER: usize = 2013265921;
75
76        #[test]
77        fn order_is_0() {
78            assert_eq!(FE::from((ORDER - 1) as u64) + FE::from(1), FE::from(0));
79        }
80
81        #[test]
82        fn when_comparing_13_and_13_they_are_equal() {
83            let a: FE = FE::from(13);
84            let b: FE = FE::from(13);
85            assert_eq!(a, b);
86        }
87
88        #[test]
89        fn when_comparing_13_and_8_they_are_different() {
90            let a: FE = FE::from(13);
91            let b: FE = FE::from(8);
92            assert_ne!(a, b);
93        }
94
95        #[test]
96        fn mul_neutral_element() {
97            let a: FE = FE::from(1);
98            let b: FE = FE::from(2);
99            assert_eq!(a * b, FE::from(2));
100        }
101
102        #[test]
103        fn mul_2_3_is_6() {
104            let a: FE = FE::from(2);
105            let b: FE = FE::from(3);
106            assert_eq!(a * b, FE::from(6));
107        }
108
109        #[test]
110        fn mul_order_minus_1() {
111            let a: FE = FE::from((ORDER - 1) as u64);
112            let b: FE = FE::from((ORDER - 1) as u64);
113            assert_eq!(a * b, FE::from(1));
114        }
115
116        #[test]
117        fn inv_0_error() {
118            let result = FE::from(0).inv();
119            assert!(matches!(result, Err(FieldError::InvZeroError)))
120        }
121
122        #[test]
123        fn inv_2_mul_2_is_1() {
124            let a: FE = FE::from(2);
125            assert_eq!(a * a.inv().unwrap(), FE::from(1));
126        }
127
128        #[test]
129        fn square_2_is_4() {
130            assert_eq!(FE::from(2).square(), FE::from(4))
131        }
132
133        #[test]
134        fn pow_2_3_is_8() {
135            assert_eq!(FE::from(2).pow(3_u64), FE::from(8))
136        }
137
138        #[test]
139        fn pow_p_minus_1() {
140            assert_eq!(FE::from(2).pow(ORDER - 1), FE::from(1))
141        }
142
143        #[test]
144        fn div_1() {
145            assert_eq!((FE::from(2) / FE::from(1)).unwrap(), FE::from(2))
146        }
147
148        #[test]
149        fn div_4_2() {
150            assert_eq!((FE::from(4) / FE::from(2)).unwrap(), FE::from(2))
151        }
152
153        #[test]
154        fn two_plus_its_additive_inv_is_0() {
155            let two = FE::from(2);
156
157            assert_eq!(two + (-&two), FE::from(0))
158        }
159
160        #[test]
161        fn four_minus_three_is_1() {
162            let four = FE::from(4);
163            let three = FE::from(3);
164
165            assert_eq!(four - three, FE::from(1))
166        }
167
168        #[test]
169        fn zero_minus_1_is_order_minus_1() {
170            let zero = FE::from(0);
171            let one = FE::from(1);
172
173            assert_eq!(zero - one, FE::from((ORDER - 1) as u64))
174        }
175
176        #[test]
177        fn babybear_uses_31_bits() {
178            assert_eq!(Babybear31PrimeField::field_bit_size(), 31);
179        }
180
181        #[test]
182        fn montgomery_backend_prime_field_compute_mu_parameter() {
183            let mu_expected: u32 = 2281701377;
184            assert_eq!(Babybear31PrimeField::MU, mu_expected);
185        }
186
187        #[test]
188        fn montgomery_backend_prime_field_compute_r2_parameter() {
189            let r2_expected: u32 = 1172168163;
190            assert_eq!(Babybear31PrimeField::R2, r2_expected);
191        }
192
193        #[test]
194        #[cfg(feature = "alloc")]
195        fn from_hex_bigger_than_u64_returns_error() {
196            let x = FE::from_hex("5f103b0bd4397d4df560eb559f38353f80eeb6");
197            assert!(matches!(x, Err(CreationError::InvalidHexString)))
198        }
199
200        #[test]
201        #[cfg(feature = "alloc")]
202        fn to_bytes_from_bytes_be_is_the_identity() {
203            let x = FE::from_hex("5f103b").unwrap();
204            assert_eq!(FE::from_bytes_be(&x.to_bytes_be()).unwrap(), x);
205        }
206
207        #[test]
208        #[cfg(feature = "alloc")]
209        fn from_bytes_to_bytes_be_is_the_identity() {
210            let bytes = [0, 0, 0, 1];
211            assert_eq!(FE::from_bytes_be(&bytes).unwrap().to_bytes_be(), bytes);
212        }
213
214        #[test]
215        #[cfg(feature = "alloc")]
216        fn to_bytes_from_bytes_le_is_the_identity() {
217            let x = FE::from_hex("5f103b").unwrap();
218            assert_eq!(FE::from_bytes_le(&x.to_bytes_le()).unwrap(), x);
219        }
220
221        #[test]
222        #[cfg(feature = "alloc")]
223        fn from_bytes_to_bytes_le_is_the_identity_4_bytes() {
224            let bytes = [1, 0, 0, 0];
225            assert_eq!(FE::from_bytes_le(&bytes).unwrap().to_bytes_le(), bytes);
226        }
227
228        #[test]
229        #[cfg(feature = "alloc")]
230        fn byte_serialization_for_a_number_matches_with_byte_conversion_implementation_le() {
231            let element = FE::from_hex("0123456701234567").unwrap();
232            let bytes = element.to_bytes_le();
233            let expected_bytes: [u8; 4] = ByteConversion::to_bytes_le(&element).try_into().unwrap();
234            assert_eq!(bytes, expected_bytes);
235        }
236
237        #[test]
238        #[cfg(feature = "alloc")]
239        fn byte_serialization_for_a_number_matches_with_byte_conversion_implementation_be() {
240            let element = FE::from_hex("0123456701234567").unwrap();
241            let bytes = element.to_bytes_be();
242            let expected_bytes: [u8; 4] = ByteConversion::to_bytes_be(&element).try_into().unwrap();
243            assert_eq!(bytes, expected_bytes);
244        }
245
246        #[test]
247        fn byte_serialization_and_deserialization_works_le() {
248            let element = FE::from_hex("0x7654321076543210").unwrap();
249            let bytes = element.to_bytes_le();
250            let from_bytes = FE::from_bytes_le(&bytes).unwrap();
251            assert_eq!(element, from_bytes);
252        }
253
254        #[test]
255        fn byte_serialization_and_deserialization_works_be() {
256            let element = FE::from_hex("7654321076543210").unwrap();
257            let bytes = element.to_bytes_be();
258            let from_bytes = FE::from_bytes_be(&bytes).unwrap();
259            assert_eq!(element, from_bytes);
260        }
261    }
262
263    #[cfg(all(feature = "std", not(feature = "instruments")))]
264    mod test_babybear_31_fft {
265        use super::*;
266        #[cfg(not(feature = "cuda"))]
267        use crate::fft::cpu::roots_of_unity::{
268            get_powers_of_primitive_root, get_powers_of_primitive_root_coset,
269        };
270        use crate::field::element::FieldElement;
271        #[cfg(not(feature = "cuda"))]
272        use crate::field::traits::{IsFFTField, RootsConfig};
273        use crate::polynomial::Polynomial;
274        use proptest::{collection, prelude::*, std_facade::Vec};
275
276        #[cfg(not(feature = "cuda"))]
277        fn gen_fft_and_naive_evaluation<F: IsFFTField>(
278            poly: Polynomial<FieldElement<F>>,
279        ) -> (Vec<FieldElement<F>>, Vec<FieldElement<F>>) {
280            let len = poly.coeff_len().next_power_of_two();
281            let order = len.trailing_zeros();
282            let twiddles =
283                get_powers_of_primitive_root(order.into(), len, RootsConfig::Natural).unwrap();
284
285            let fft_eval = Polynomial::evaluate_fft::<F>(&poly, 1, None).unwrap();
286            let naive_eval = poly.evaluate_slice(&twiddles);
287
288            (fft_eval, naive_eval)
289        }
290
291        #[cfg(not(feature = "cuda"))]
292        fn gen_fft_coset_and_naive_evaluation<F: IsFFTField>(
293            poly: Polynomial<FieldElement<F>>,
294            offset: FieldElement<F>,
295            blowup_factor: usize,
296        ) -> (Vec<FieldElement<F>>, Vec<FieldElement<F>>) {
297            let len = poly.coeff_len().next_power_of_two();
298            let order = (len * blowup_factor).trailing_zeros();
299            let twiddles =
300                get_powers_of_primitive_root_coset(order.into(), len * blowup_factor, &offset)
301                    .unwrap();
302
303            let fft_eval =
304                Polynomial::evaluate_offset_fft::<F>(&poly, blowup_factor, None, &offset).unwrap();
305            let naive_eval = poly.evaluate_slice(&twiddles);
306
307            (fft_eval, naive_eval)
308        }
309
310        #[cfg(not(feature = "cuda"))]
311        fn gen_fft_and_naive_interpolate<F: IsFFTField>(
312            fft_evals: &[FieldElement<F>],
313        ) -> (Polynomial<FieldElement<F>>, Polynomial<FieldElement<F>>) {
314            let order = fft_evals.len().trailing_zeros() as u64;
315            let twiddles =
316                get_powers_of_primitive_root(order, 1 << order, RootsConfig::Natural).unwrap();
317
318            let naive_poly = Polynomial::interpolate(&twiddles, fft_evals).unwrap();
319            let fft_poly = Polynomial::interpolate_fft::<F>(fft_evals).unwrap();
320
321            (fft_poly, naive_poly)
322        }
323
324        #[cfg(not(feature = "cuda"))]
325        fn gen_fft_and_naive_coset_interpolate<F: IsFFTField>(
326            fft_evals: &[FieldElement<F>],
327            offset: &FieldElement<F>,
328        ) -> (Polynomial<FieldElement<F>>, Polynomial<FieldElement<F>>) {
329            let order = fft_evals.len().trailing_zeros() as u64;
330            let twiddles = get_powers_of_primitive_root_coset(order, 1 << order, offset).unwrap();
331
332            let naive_poly = Polynomial::interpolate(&twiddles, fft_evals).unwrap();
333            let fft_poly = Polynomial::interpolate_offset_fft(fft_evals, offset).unwrap();
334
335            (fft_poly, naive_poly)
336        }
337
338        #[cfg(not(feature = "cuda"))]
339        fn gen_fft_interpolate_and_evaluate<F: IsFFTField>(
340            poly: Polynomial<FieldElement<F>>,
341        ) -> (Polynomial<FieldElement<F>>, Polynomial<FieldElement<F>>) {
342            let eval = Polynomial::evaluate_fft::<F>(&poly, 1, None).unwrap();
343            let new_poly = Polynomial::interpolate_fft::<F>(&eval).unwrap();
344
345            (poly, new_poly)
346        }
347
348        prop_compose! {
349            fn powers_of_two(max_exp: u8)(exp in 1..max_exp) -> usize { 1 << exp }
350            // max_exp cannot be multiple of the bits that represent a usize, generally 64 or 32.
351            // also it can't exceed the test field's two-adicity.
352        }
353        prop_compose! {
354            fn field_element()(num in any::<u64>().prop_filter("Avoid null coefficients", |x| x != &0)) -> FieldElement<Babybear31PrimeField> {
355                FieldElement::<Babybear31PrimeField>::from(num)
356            }
357        }
358        prop_compose! {
359            fn offset()(num in any::<u64>(), factor in any::<u64>()) -> FieldElement<Babybear31PrimeField> { FieldElement::<Babybear31PrimeField>::from(num).pow(factor) }
360        }
361        prop_compose! {
362            fn field_vec(max_exp: u8)(vec in collection::vec(field_element(), 0..1 << max_exp)) -> Vec<FieldElement<Babybear31PrimeField>> {
363                vec
364            }
365        }
366        prop_compose! {
367            fn non_power_of_two_sized_field_vec(max_exp: u8)(vec in collection::vec(field_element(), 2..1<<max_exp).prop_filter("Avoid polynomials of size power of two", |vec| !vec.len().is_power_of_two())) -> Vec<FieldElement<Babybear31PrimeField>> {
368                vec
369            }
370        }
371        prop_compose! {
372            fn poly(max_exp: u8)(coeffs in field_vec(max_exp)) -> Polynomial<FieldElement<Babybear31PrimeField>> {
373                Polynomial::new(&coeffs)
374            }
375        }
376        prop_compose! {
377            fn poly_with_non_power_of_two_coeffs(max_exp: u8)(coeffs in non_power_of_two_sized_field_vec(max_exp)) -> Polynomial<FieldElement<Babybear31PrimeField>> {
378                Polynomial::new(&coeffs)
379            }
380        }
381
382        proptest! {
383            // Property-based test that ensures FFT eval. gives same result as a naive polynomial evaluation.
384            #[test]
385            #[cfg(not(feature = "cuda"))]
386            fn test_fft_matches_naive_evaluation(poly in poly(8)) {
387                let (fft_eval, naive_eval) = gen_fft_and_naive_evaluation(poly);
388                prop_assert_eq!(fft_eval, naive_eval);
389            }
390
391            // Property-based test that ensures FFT eval. with coset gives same result as a naive polynomial evaluation.
392            #[test]
393            #[cfg(not(feature = "cuda"))]
394            fn test_fft_coset_matches_naive_evaluation(poly in poly(4), offset in offset(), blowup_factor in powers_of_two(4)) {
395                let (fft_eval, naive_eval) = gen_fft_coset_and_naive_evaluation(poly, offset, blowup_factor);
396                prop_assert_eq!(fft_eval, naive_eval);
397            }
398
399            // Property-based test that ensures FFT interpolation is the same as naive..
400            #[test]
401            #[cfg(not(feature = "cuda"))]
402            fn test_fft_interpolate_matches_naive(fft_evals in field_vec(4)
403                                                           .prop_filter("Avoid polynomials of size not power of two",
404                                                                        |evals| evals.len().is_power_of_two())) {
405                let (fft_poly, naive_poly) = gen_fft_and_naive_interpolate(&fft_evals);
406                prop_assert_eq!(fft_poly, naive_poly);
407            }
408
409            // Property-based test that ensures FFT interpolation with an offset is the same as naive.
410            #[test]
411            #[cfg(not(feature = "cuda"))]
412            fn test_fft_interpolate_coset_matches_naive(offset in offset(), fft_evals in field_vec(4)
413                                                           .prop_filter("Avoid polynomials of size not power of two",
414                                                                        |evals| evals.len().is_power_of_two())) {
415                let (fft_poly, naive_poly) = gen_fft_and_naive_coset_interpolate(&fft_evals, &offset);
416                prop_assert_eq!(fft_poly, naive_poly);
417            }
418
419            // Property-based test that ensures interpolation is the inverse operation of evaluation.
420            #[test]
421            #[cfg(not(feature = "cuda"))]
422            fn test_fft_interpolate_is_inverse_of_evaluate(
423                poly in poly(4).prop_filter("Avoid non pows of two", |poly| poly.coeff_len().is_power_of_two())) {
424                let (poly, new_poly) = gen_fft_interpolate_and_evaluate(poly);
425                prop_assert_eq!(poly, new_poly);
426            }
427        }
428    }
429}