use super::prime::jacobi as jacobi_1;
use super::{ByteOrder, Number};
use core::cmp::Ordering;
impl Number {
#[track_caller]
pub fn jacobi(&self, n: &Number) -> i32 {
if n.negative || !n.bit(0) {
odd_positive();
}
let a = limbs(&self.rem_euclid(n));
let n = limbs(n);
if n.len() == 1 {
return jacobi_1(a.first().copied().unwrap_or(0), n[0]);
}
jacobi_long(a, n)
}
#[track_caller]
pub fn sqrt_mod(&self, p: &Number) -> Option<Number> {
if p.negative || !p.bit(0) {
odd_positive();
}
let order = self.byte_order;
let a = with_order(self.rem_euclid(p), order);
if a.is_zero() {
return Some(a);
}
if a.jacobi(p) != 1 {
return None;
}
let r = tonelli_shanks(&a, &with_order(p.clone(), order))?;
let other = p - &r;
let r = if other < r { other } else { r };
Some(with_order(r, order))
}
}
#[cold]
#[inline(never)]
#[track_caller]
fn odd_positive() -> ! {
panic!("the modulus must be odd and positive")
}
fn with_order(x: Number, order: ByteOrder) -> Number {
if x.byte_order == order {
return x;
}
let mut value = x.value;
value.reverse();
Number {
value,
negative: x.negative,
byte_order: order,
}
}
fn tonelli_shanks(a: &Number, p: &Number) -> Option<Number> {
let one = Number::new_from_u8(1, a.byte_order);
let p1 = p - &one;
let s = p1.trailing_zeros()? as usize;
let q = &p1 >> s;
let half = (&q - &one) >> 1usize;
let x = a.pow_mod(&half, p);
let mut r = (a * &x).rem_euclid(p);
let mut b = (&r * &x).rem_euclid(p);
if s > 1 && b != one {
let mut z = Number::new_from_u8(2, a.byte_order);
loop {
match z.jacobi(p) {
-1 => break,
0 => return None,
_ => z = &z + &one,
}
}
let mut c = z.pow_mod(&q, p);
let mut m = s;
while b != one {
let mut t = 0;
let mut b2 = b.clone();
while b2 != one {
b2 = (&b2 * &b2).rem_euclid(p);
t += 1;
if t == m {
return None;
}
}
let mut g = c;
for _ in 0..m - t - 1 {
g = (&g * &g).rem_euclid(p);
}
r = (&r * &g).rem_euclid(p);
c = (&g * &g).rem_euclid(p);
b = (&b * &c).rem_euclid(p);
m = t;
}
}
((&r * &r).rem_euclid(p) == *a).then_some(r)
}
fn limbs(x: &Number) -> Vec<u64> {
let mut l: Vec<u64> = if x.byte_order == ByteOrder::LittleEndian {
x.value
.chunks(8)
.map(|c| {
let mut b = [0; 8];
b[..c.len()].copy_from_slice(c);
u64::from_le_bytes(b)
})
.collect()
} else {
x.value
.rchunks(8)
.map(|c| {
let mut b = [0; 8];
b[8 - c.len()..].copy_from_slice(c);
u64::from_be_bytes(b)
})
.collect()
};
trim(&mut l);
l
}
fn trim(x: &mut Vec<u64>) {
while x.last() == Some(&0) {
x.pop();
}
}
fn jacobi_long(mut a: Vec<u64>, mut n: Vec<u64>) -> i32 {
let mut t = 1;
loop {
trim(&mut a);
if a.is_empty() {
return if n == [1] { t } else { 0 };
}
if a.len() == 1 && n.len() == 1 {
return t * jacobi_1(a[0], n[0]);
}
let z = shift_out_zeros(&mut a);
if z & 1 == 1 && matches!(n[0] & 7, 3 | 5) {
t = -t;
}
if compare(&a, &n) == Ordering::Less {
if a[0] & n[0] & 2 != 0 {
t = -t;
}
core::mem::swap(&mut a, &mut n);
}
subtract(&mut a, &n);
}
}
fn shift_out_zeros(x: &mut Vec<u64>) -> u64 {
let whole = x.iter().take_while(|&&l| l == 0).count();
x.drain(..whole);
let bits = x[0].trailing_zeros();
if bits != 0 {
for i in 0..x.len() {
let above = x.get(i + 1).copied().unwrap_or(0);
x[i] = (x[i] >> bits) | (above << (64 - bits));
}
}
trim(x);
whole as u64 * 64 + u64::from(bits)
}
fn compare(a: &[u64], b: &[u64]) -> Ordering {
a.len()
.cmp(&b.len())
.then_with(|| a.iter().rev().cmp(b.iter().rev()))
}
fn subtract(a: &mut [u64], b: &[u64]) {
let mut borrow = false;
for (i, x) in a.iter_mut().enumerate() {
let y = b.get(i).copied().unwrap_or(0);
let (d, b1) = x.overflowing_sub(y);
let (d, b2) = d.overflowing_sub(u64::from(borrow));
*x = d;
borrow = b1 | b2;
}
}