Skip to main content

a/math/numbers/
quadratic.rs

1//! The Jacobi symbol and square roots modulo an odd prime.
2
3use super::prime::jacobi as jacobi_1;
4use super::{ByteOrder, Number};
5use core::cmp::Ordering;
6
7impl Number {
8    /// The Jacobi symbol `(self / n)` for an odd positive `n`: 0 when the two have a common
9    /// factor, otherwise 1 or -1. For a prime `n` it is the Legendre symbol, 1 for a non-zero
10    /// square modulo `n` and -1 for a non-square; for a composite `n`, 1 does not make `self` a
11    /// square, though -1 still rules it out.
12    ///
13    /// # Panics
14    ///
15    /// Panics if `n` is even, zero or negative.
16    ///
17    /// # Examples
18    ///
19    /// ```
20    /// use a::math::numbers::{ByteOrder, Number};
21    ///
22    /// let n = Number::new_from_u8(7, ByteOrder::BigEndian);
23    /// // 2 = 3^2 modulo 7; 3 is not a square modulo 7; 14 is a multiple of 7.
24    /// assert_eq!(Number::new_from_u8(2, ByteOrder::BigEndian).jacobi(&n), 1);
25    /// assert_eq!(Number::new_from_u8(3, ByteOrder::BigEndian).jacobi(&n), -1);
26    /// assert_eq!(Number::new_from_u8(14, ByteOrder::BigEndian).jacobi(&n), 0);
27    /// ```
28    #[track_caller]
29    pub fn jacobi(&self, n: &Number) -> i32 {
30        if n.negative || !n.bit(0) {
31            odd_positive();
32        }
33        let a = limbs(&self.rem_euclid(n));
34        let n = limbs(n);
35        if n.len() == 1 {
36            return jacobi_1(a.first().copied().unwrap_or(0), n[0]);
37        }
38        jacobi_long(a, n)
39    }
40
41    /// A square root of `self` modulo the odd prime `p`: the smaller of the two, or `None` when
42    /// `self` is not a square modulo `p`. Zero is its own root. The result is in the byte order
43    /// of `self`.
44    ///
45    /// The result is checked, so a composite `p` never gives a wrong root, though it may give
46    /// `None` for a number that has one.
47    ///
48    /// Like [`pow_mod`](Number::pow_mod), which it is built on, it is not constant-time.
49    ///
50    /// # Panics
51    ///
52    /// Panics if `p` is even, zero or negative.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use a::math::numbers::{ByteOrder, Number};
58    ///
59    /// let p = Number::new_from_u8(13, ByteOrder::BigEndian);
60    /// // 6^2 = 36 = 10 modulo 13, and 7 = 13 - 6 is the other root.
61    /// let r = Number::new_from_u8(10, ByteOrder::BigEndian).sqrt_mod(&p).unwrap();
62    /// assert_eq!(r.value, [6]);
63    /// assert!(Number::new_from_u8(5, ByteOrder::BigEndian).sqrt_mod(&p).is_none());
64    /// ```
65    #[track_caller]
66    pub fn sqrt_mod(&self, p: &Number) -> Option<Number> {
67        if p.negative || !p.bit(0) {
68            odd_positive();
69        }
70        let order = self.byte_order;
71        let a = with_order(self.rem_euclid(p), order);
72        if a.is_zero() {
73            return Some(a);
74        }
75        if a.jacobi(p) != 1 {
76            return None;
77        }
78        let r = tonelli_shanks(&a, &with_order(p.clone(), order))?;
79        let other = p - &r;
80        let r = if other < r { other } else { r };
81        Some(with_order(r, order))
82    }
83}
84
85/// Panics for a modulus the symbol is not defined for.
86#[cold]
87#[inline(never)]
88#[track_caller]
89fn odd_positive() -> ! {
90    panic!("the modulus must be odd and positive")
91}
92
93/// `x` in the byte order `order`.
94fn with_order(x: Number, order: ByteOrder) -> Number {
95    if x.byte_order == order {
96        return x;
97    }
98    let mut value = x.value;
99    value.reverse();
100    Number {
101        value,
102        negative: x.negative,
103        byte_order: order,
104    }
105}
106
107/// A root of the non-zero square `a` below `p`, by Tonelli and Shanks: with `p - 1 = q 2^s`,
108/// `r = a^((q + 1) / 2)` has `r^2 = a b` for `b = a^q`, whose order divides `2^s`, and each step
109/// multiplies `r` and `b` by powers of a generator `c` of the group of order `2^s` until `b` is 1.
110/// For `s = 1` it is the single power `a^((p + 1) / 4)`.
111fn tonelli_shanks(a: &Number, p: &Number) -> Option<Number> {
112    let one = Number::new_from_u8(1, a.byte_order);
113    let p1 = p - &one;
114    let s = p1.trailing_zeros()? as usize;
115    let q = &p1 >> s;
116    let half = (&q - &one) >> 1usize;
117    let x = a.pow_mod(&half, p);
118    let mut r = (a * &x).rem_euclid(p);
119    let mut b = (&r * &x).rem_euclid(p);
120    if s > 1 && b != one {
121        let mut z = Number::new_from_u8(2, a.byte_order);
122        loop {
123            match z.jacobi(p) {
124                -1 => break,
125                // A factor of `p`, which is composite, and maybe the square of a number, whose
126                // symbols are never -1.
127                0 => return None,
128                _ => z = &z + &one,
129            }
130        }
131        let mut c = z.pow_mod(&q, p);
132        let mut m = s;
133        while b != one {
134            let mut t = 0;
135            let mut b2 = b.clone();
136            while b2 != one {
137                b2 = (&b2 * &b2).rem_euclid(p);
138                t += 1;
139                if t == m {
140                    return None;
141                }
142            }
143            let mut g = c;
144            for _ in 0..m - t - 1 {
145                g = (&g * &g).rem_euclid(p);
146            }
147            r = (&r * &g).rem_euclid(p);
148            c = (&g * &g).rem_euclid(p);
149            b = (&b * &c).rem_euclid(p);
150            m = t;
151        }
152    }
153    ((&r * &r).rem_euclid(p) == *a).then_some(r)
154}
155
156/// The limbs of the magnitude of `x`, the least significant first, without zeros above it.
157fn limbs(x: &Number) -> Vec<u64> {
158    let mut l: Vec<u64> = if x.byte_order == ByteOrder::LittleEndian {
159        x.value
160            .chunks(8)
161            .map(|c| {
162                let mut b = [0; 8];
163                b[..c.len()].copy_from_slice(c);
164                u64::from_le_bytes(b)
165            })
166            .collect()
167    } else {
168        x.value
169            .rchunks(8)
170            .map(|c| {
171                let mut b = [0; 8];
172                b[8 - c.len()..].copy_from_slice(c);
173                u64::from_be_bytes(b)
174            })
175            .collect()
176    };
177    trim(&mut l);
178    l
179}
180
181/// Drops the zero limbs at the top.
182fn trim(x: &mut Vec<u64>) {
183    while x.last() == Some(&0) {
184        x.pop();
185    }
186}
187
188/// The Jacobi symbol `(a / n)` for `a < n`, `n` odd, by the binary method of [`jacobi_1`] on limbs.
189fn jacobi_long(mut a: Vec<u64>, mut n: Vec<u64>) -> i32 {
190    let mut t = 1;
191    loop {
192        trim(&mut a);
193        if a.is_empty() {
194            return if n == [1] { t } else { 0 };
195        }
196        if a.len() == 1 && n.len() == 1 {
197            return t * jacobi_1(a[0], n[0]);
198        }
199        let z = shift_out_zeros(&mut a);
200        if z & 1 == 1 && matches!(n[0] & 7, 3 | 5) {
201            t = -t;
202        }
203        if compare(&a, &n) == Ordering::Less {
204            if a[0] & n[0] & 2 != 0 {
205                t = -t;
206            }
207            core::mem::swap(&mut a, &mut n);
208        }
209        subtract(&mut a, &n);
210    }
211}
212
213/// Divides the non-zero `x` by the largest power of two that divides it, and gives the exponent.
214fn shift_out_zeros(x: &mut Vec<u64>) -> u64 {
215    let whole = x.iter().take_while(|&&l| l == 0).count();
216    x.drain(..whole);
217    let bits = x[0].trailing_zeros();
218    if bits != 0 {
219        for i in 0..x.len() {
220            let above = x.get(i + 1).copied().unwrap_or(0);
221            x[i] = (x[i] >> bits) | (above << (64 - bits));
222        }
223    }
224    trim(x);
225    whole as u64 * 64 + u64::from(bits)
226}
227
228/// The order of `a` and `b`, without zeros at the top.
229fn compare(a: &[u64], b: &[u64]) -> Ordering {
230    a.len()
231        .cmp(&b.len())
232        .then_with(|| a.iter().rev().cmp(b.iter().rev()))
233}
234
235/// `a -= b`, for `a >= b`.
236fn subtract(a: &mut [u64], b: &[u64]) {
237    let mut borrow = false;
238    for (i, x) in a.iter_mut().enumerate() {
239        let y = b.get(i).copied().unwrap_or(0);
240        let (d, b1) = x.overflowing_sub(y);
241        let (d, b2) = d.overflowing_sub(u64::from(borrow));
242        *x = d;
243        borrow = b1 | b2;
244    }
245}