Skip to main content

crypto_bigint/uint/
mod_symbol.rs

1//! Support for computing modular symbols.
2
3use crate::{JacobiSymbol, Odd, Uint};
4
5impl<const LIMBS: usize> Uint<LIMBS> {
6    /// Compute the Jacobi symbol `(self|rhs)`.
7    ///
8    /// For prime `rhs`, this corresponds to the Legendre symbol and
9    /// indicates whether `self` is quadratic residue modulo `rhs`.
10    #[must_use]
11    #[allow(clippy::cast_possible_truncation)]
12    pub const fn jacobi_symbol<const RHS_LIMBS: usize>(
13        &self,
14        rhs: &Odd<Uint<RHS_LIMBS>>,
15    ) -> JacobiSymbol {
16        if LIMBS > RHS_LIMBS {
17            // Ensure `a` is reduced modulo `b` and operate on the smallest limb size
18            let a = self.rem(rhs.as_nz_ref());
19            return a.jacobi_symbol(rhs);
20        }
21
22        let (gcd, jacobi_neg) = rhs.classic_bingcd_(&self.resize());
23
24        // The sign of the Jacobi symbol is represented by jacobi_neg. We select 0 as the
25        // symbol when the GCD is not one, otherwise 1 or -1.
26        let jacobi = (jacobi_neg as i8 * -2 + 1) as i64;
27        JacobiSymbol::from_i8(Uint::eq(gcd.as_ref(), &Uint::ONE).select_i64(0, jacobi) as i8)
28    }
29
30    /// Compute the Jacobi symbol `(self|rhs)`.
31    ///
32    /// For prime `rhs`, this corresponds to the Legendre symbol and
33    /// indicates whether `self` is quadratic residue modulo `rhs`.
34    ///
35    /// This method executes in variable-time for both inputs.
36    #[must_use]
37    #[allow(clippy::cast_possible_truncation)]
38    pub const fn jacobi_symbol_vartime<const RHS_LIMBS: usize>(
39        &self,
40        rhs: &Odd<Uint<RHS_LIMBS>>,
41    ) -> JacobiSymbol {
42        if LIMBS > RHS_LIMBS {
43            // Ensure `a` is reduced modulo `b` and operate on the smallest limb size
44            let a = self.rem_vartime(rhs.as_nz_ref());
45            return a.jacobi_symbol_vartime(rhs);
46        } else if RHS_LIMBS > LIMBS {
47            if self.is_zero_vartime() {
48                return if rhs.as_ref().cmp_vartime(&Uint::ONE).is_eq() {
49                    JacobiSymbol::One
50                } else {
51                    JacobiSymbol::Zero
52                };
53            } else {
54                // Perform an initial swap and reduction and operate on the smallest limb size
55                let mut jacobi_neg = 0;
56
57                let tail = self.trailing_zeros_vartime();
58                if tail & 1 == 1 {
59                    // (2a|b) = -(a|b) iff b = ±3 mod 8
60                    // b is always odd, so we ignore the lower bit and check that bits 2 and 3 are '01' or '10'
61                    let b_lo = rhs.as_ref().limbs[0].0;
62                    jacobi_neg ^= ((b_lo >> 1) ^ (b_lo >> 2)) & 1;
63                }
64
65                // (b|a) = -(a|b) iff a = b = 3 mod 4 (quadratic reciprocity)
66                let b = self
67                    .shr_vartime(tail)
68                    .to_odd()
69                    .expect_copied("ensured non-zero");
70                let a_b_mod_4 = b.as_ref().limbs[0].0 & rhs.as_ref().limbs[0].0 & 3;
71                jacobi_neg ^= a_b_mod_4 & (a_b_mod_4 >> 1) & 1;
72
73                // (a, b) = (b % a, a)
74                let a = rhs.as_ref().rem_vartime(b.as_nz_ref());
75                let jacobi_symbol = a.jacobi_symbol_vartime(&b);
76                return if jacobi_neg == 1 {
77                    jacobi_symbol.neg()
78                } else {
79                    jacobi_symbol
80                };
81            }
82        }
83
84        let (gcd, jacobi_neg) = rhs.classic_bingcd_vartime_(&self.resize());
85        JacobiSymbol::from_i8(if gcd.as_ref().cmp_vartime(&Uint::ONE).is_eq() {
86            jacobi_neg as i8 * -2 + 1
87        } else {
88            0
89        })
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use crate::{JacobiSymbol, U256};
96
97    #[test]
98    fn jacobi_quad_residue() {
99        // Two semiprimes with no common factors, and
100        // f is quadratic residue modulo g
101        let f = U256::from(59u32 * 67);
102        let g = U256::from(61u32 * 71).to_odd().unwrap();
103        let res = f.jacobi_symbol(&g);
104        let res_small = f.resize::<1>().jacobi_symbol(&g);
105        let res_large = f.jacobi_symbol(&g.resize::<1>());
106        let res_vartime = f.jacobi_symbol_vartime(&g);
107        let res_vartime_small = f.resize::<1>().jacobi_symbol_vartime(&g);
108        let res_vartime_large = f.jacobi_symbol_vartime(&g.resize::<1>());
109        assert_eq!(res, JacobiSymbol::One);
110        assert_eq!(res, res_small);
111        assert_eq!(res, res_large);
112        assert_eq!(res, res_vartime);
113        assert_eq!(res, res_vartime_small);
114        assert_eq!(res, res_vartime_large);
115    }
116
117    #[test]
118    fn jacobi_non_quad_residue() {
119        // f and g have no common factors, but
120        // f is not quadratic residue modulo g
121        let f = U256::from(59u32 * 67 + 2);
122        let g = U256::from(61u32 * 71).to_odd().unwrap();
123        let res = f.jacobi_symbol(&g);
124        let res_small = f.resize::<1>().jacobi_symbol(&g);
125        let res_large = f.jacobi_symbol(&g.resize::<1>());
126        let res_vartime = f.jacobi_symbol_vartime(&g);
127        let res_vartime_small = f.resize::<1>().jacobi_symbol_vartime(&g);
128        let res_vartime_large = f.jacobi_symbol_vartime(&g.resize::<1>());
129        assert_eq!(res, JacobiSymbol::MinusOne);
130        assert_eq!(res, res_small);
131        assert_eq!(res, res_large);
132        assert_eq!(res, res_vartime);
133        assert_eq!(res, res_vartime_small);
134        assert_eq!(res, res_vartime_large);
135    }
136
137    #[test]
138    fn jacobi_non_coprime() {
139        let f = U256::from(4391633u32);
140        let g = U256::from(2022161u32).to_odd().unwrap();
141        let res = f.jacobi_symbol(&g);
142        let res_small = f.resize::<1>().jacobi_symbol(&g);
143        let res_large = f.jacobi_symbol(&g.resize::<1>());
144        let res_vartime = f.jacobi_symbol_vartime(&g);
145        let res_vartime_small = f.resize::<1>().jacobi_symbol_vartime(&g);
146        let res_vartime_large = f.jacobi_symbol_vartime(&g.resize::<1>());
147        assert_eq!(res, JacobiSymbol::Zero);
148        assert_eq!(res, res_small);
149        assert_eq!(res, res_large);
150        assert_eq!(res, res_vartime);
151        assert_eq!(res, res_vartime_small);
152        assert_eq!(res, res_vartime_large);
153    }
154
155    #[test]
156    fn jacobi_zero() {
157        let f = U256::ZERO;
158        let g = U256::ONE.to_odd().unwrap();
159        let res = f.jacobi_symbol(&g);
160        let res_small = f.resize::<1>().jacobi_symbol(&g);
161        let res_large = f.jacobi_symbol(&g.resize::<1>());
162        let res_vartime = f.jacobi_symbol_vartime(&g);
163        let res_vartime_small = f.resize::<1>().jacobi_symbol_vartime(&g);
164        let res_vartime_large = f.jacobi_symbol_vartime(&g.resize::<1>());
165        assert_eq!(res, JacobiSymbol::One);
166        assert_eq!(res, res_small);
167        assert_eq!(res, res_large);
168        assert_eq!(res, res_vartime);
169        assert_eq!(res, res_vartime_small);
170        assert_eq!(res, res_vartime_large);
171    }
172
173    #[test]
174    fn jacobi_one() {
175        let f = U256::ONE;
176        let g = U256::ONE.to_odd().unwrap();
177        let res = f.jacobi_symbol(&g);
178        let res_small = f.resize::<1>().jacobi_symbol(&g);
179        let res_large = f.jacobi_symbol(&g.resize::<1>());
180        let res_vartime = f.jacobi_symbol_vartime(&g);
181        let res_vartime_small = f.resize::<1>().jacobi_symbol_vartime(&g);
182        let res_vartime_large = f.jacobi_symbol_vartime(&g.resize::<1>());
183        assert_eq!(res, JacobiSymbol::One);
184        assert_eq!(res, res_small);
185        assert_eq!(res, res_large);
186        assert_eq!(res, res_vartime);
187        assert_eq!(res, res_vartime_small);
188        assert_eq!(res, res_vartime_large);
189    }
190
191    #[test]
192    // test from issue #1295 - variations in only the middle bits can trip up optimized binary GCD method
193    fn jacobi_edge() {
194        use crate::{Odd, U256};
195
196        assert_eq!(
197            U256::from_be_hex("0000000000000002108DEAFCB180F023912BEED0186CEEAD593A8507B7DA4E9B")
198                .jacobi_symbol(&Odd::<U256>::from_be_hex(
199                    "0000000000000002108DEAFCB180F023912BEED0186CEEED593A8507B7DA4E9B",
200                )),
201            JacobiSymbol::MinusOne
202        );
203    }
204}