a/math/numbers/
quadratic.rs1use super::prime::jacobi as jacobi_1;
4use super::{ByteOrder, Number};
5use core::cmp::Ordering;
6
7impl Number {
8 #[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 #[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#[cold]
87#[inline(never)]
88#[track_caller]
89fn odd_positive() -> ! {
90 panic!("the modulus must be odd and positive")
91}
92
93fn 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
107fn 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 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
156fn 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
181fn trim(x: &mut Vec<u64>) {
183 while x.last() == Some(&0) {
184 x.pop();
185 }
186}
187
188fn 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
213fn 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
228fn 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
235fn 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}