Skip to main content

zinc_utils/field/
monty.rs

1use crypto_primitives::{
2    FromWithConfig, IntoWithConfig, PrimeField, crypto_bigint_monty::MontyField,
3    crypto_bigint_uint::Uint,
4};
5use std::ops::MulAssign;
6
7use crate::{
8    from_ref::FromRef, inner_transparent_field::InnerTransparentField, mul_by_scalar::MulByScalar,
9    projectable_to_field::ProjectableToField,
10};
11
12impl<const LIMBS: usize> MulByScalar<&Self> for MontyField<LIMBS> {
13    #[allow(clippy::arithmetic_side_effects)] // False alert
14    fn mul_by_scalar<const CHECK: bool>(&self, rhs: &Self) -> Option<Self> {
15        // Field operations cannot overflow
16        Some(self * rhs)
17    }
18}
19
20impl<const LIMBS: usize> FromRef<Self> for MontyField<LIMBS> {
21    fn from_ref(value: &Self) -> Self {
22        value.clone()
23    }
24}
25
26impl<T, const LIMBS: usize> ProjectableToField<MontyField<LIMBS>> for T
27where
28    MontyField<LIMBS>: for<'a> FromWithConfig<&'a T>,
29{
30    fn prepare_projection(
31        sampled_value: &MontyField<LIMBS>,
32    ) -> impl Fn(&Self) -> MontyField<LIMBS> + Send + Sync + 'static {
33        let config = sampled_value.cfg().clone();
34        move |value: &T| value.into_with_cfg(&config)
35    }
36}
37
38impl<const LIMBS: usize> InnerTransparentField for MontyField<LIMBS> {
39    fn add_inner(lhs: &Self::Inner, rhs: &Self::Inner, config: &Self::Config) -> Self::Inner {
40        Uint::new(
41            lhs.inner()
42                .add_mod(rhs.inner(), config.modulus().as_nz_ref()),
43        )
44    }
45
46    fn sub_inner(lhs: &Self::Inner, rhs: &Self::Inner, config: &Self::Config) -> Self::Inner {
47        Uint::new(
48            lhs.inner()
49                .sub_mod(rhs.inner(), config.modulus().as_nz_ref()),
50        )
51    }
52
53    fn mul_assign_by_inner(&mut self, rhs: &Self::Inner) {
54        let rhs: Self = Self::new_unchecked(*rhs, self.cfg());
55
56        self.mul_assign(rhs);
57    }
58}
59
60#[cfg(test)]
61#[allow(
62    clippy::arithmetic_side_effects,
63    clippy::cast_possible_truncation,
64    clippy::cast_possible_wrap
65)]
66mod prop_tests {
67    use crypto_bigint::U256;
68    use crypto_primitives::{
69        FromWithConfig, IntoWithConfig, PrimeField,
70        crypto_bigint_monty::{F256, MontyField},
71        crypto_bigint_uint::Uint,
72    };
73    use proptest::prelude::*;
74
75    use crate::inner_transparent_field::InnerTransparentField;
76
77    const LIMBS: usize = 4;
78    const MODULUS: &str = "00dca94d8a1ecce3b6e8755d8999787d0524d8ca1ea755e7af84fb646fa31f27";
79    type F = F256;
80
81    fn get_dyn_config(hex_modulus: &str) -> <MontyField<LIMBS> as PrimeField>::Config {
82        let modulus = Uint::new(
83            crypto_bigint::Uint::<LIMBS>::from_str_radix_vartime(hex_modulus, 16).unwrap(),
84        );
85        MontyField::make_cfg(&modulus).expect("Failed to create field config")
86    }
87
88    fn any_u128() -> impl Strategy<Value = u128> {
89        any::<u128>()
90    }
91    fn any_i128() -> impl Strategy<Value = i128> {
92        any::<i128>()
93    }
94    fn any_bool() -> impl Strategy<Value = bool> {
95        any::<bool>()
96    }
97
98    proptest! {
99        #[test]
100        fn prop_from_unsigned_matches_sum_of_bits(x in any_u128()) {
101            let cfg = get_dyn_config(MODULUS);
102            let f: F = x.into_with_cfg(&cfg);
103            let mut acc: F = F::zero_with_cfg(&cfg);
104            for i in 0..128 {
105                if (x >> i) & 1 == 1 { acc += F::from_with_cfg(1u64, &cfg) * F::from_with_cfg(1u64 << i.min(63), &cfg); }
106            }
107            let u = Uint::<{ U256::LIMBS }>::from(x);
108            let g2: F = u.into_with_cfg(&cfg);
109            prop_assert_eq!(f, g2);
110        }
111
112        #[test]
113        fn prop_from_signed_is_neg_of_abs_when_negative(x in any_i128()) {
114            let cfg = get_dyn_config(MODULUS);
115            let f: F = x.into_with_cfg(&cfg);
116            let abs = x.unsigned_abs();
117            let g_abs = abs.into_with_cfg(&cfg);
118            if x < 0 {
119                prop_assert_eq!(f + g_abs, F::zero_with_cfg(&cfg));
120            } else {
121                prop_assert_eq!(f, g_abs);
122            }
123        }
124
125        #[test]
126        fn prop_from_bool_is_identity(b in any_bool()) {
127            let cfg = get_dyn_config(MODULUS);
128            let f: F = b.into_with_cfg(&cfg);
129            prop_assert_eq!(f, if b { F::one_with_cfg(&cfg) } else { F::zero_with_cfg(&cfg) });
130        }
131
132        #[test]
133        fn prop_from_uint_roundtrip_through_uint(x in any_u128()) {
134            let cfg = get_dyn_config(MODULUS);
135            let u: Uint<LIMBS> = Uint::from(x);
136            let g_from_uint: F = u.into_with_cfg(&cfg);
137            let g_direct: F = x.into_with_cfg(&cfg);
138            prop_assert_eq!(g_from_uint, g_direct);
139        }
140
141        #[test]
142        fn prop_inner_ops_match_normal_ops((x, y) in any::<(u128, u128)>()) {
143            let cfg = get_dyn_config(MODULUS);
144            let a: F = x.into_with_cfg(&cfg);
145            let b: F = y.into_with_cfg(&cfg);
146            prop_assert_eq!(F::add_inner(a.inner(), b.inner(), &cfg), (a.clone() + b.clone()).into_inner());
147            prop_assert_eq!(F::sub_inner(a.inner(), b.inner(), &cfg), (a.clone() - b.clone()).into_inner());
148
149            let mut res = a.clone();
150            res.mul_assign_by_inner(b.inner());
151            prop_assert_eq!(res, a * b);
152        }
153    }
154}