pub(crate) use crypto_bigint::JacobiSymbol;
use crypto_bigint::{Limb, NonZero as CTNonZero, Odd, Unsigned, Word};
fn apply_reduce_numerator(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word) {
let p_mod_8 = p & 7;
let s = a.trailing_zeros();
let j = if (s & 1) == 1 && (p_mod_8 == 3 || p_mod_8 == 5) {
-j
} else {
j
};
(j, a >> s)
}
fn reduce_numerator_long<T>(j: JacobiSymbol, a: Word, p: &T) -> (JacobiSymbol, Word)
where
T: Unsigned,
{
apply_reduce_numerator(j, a, p.as_limbs()[0].0)
}
fn reduce_numerator_short(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word) {
apply_reduce_numerator(j, a, p)
}
fn apply_swap(j: JacobiSymbol, a: Word, p: Word) -> JacobiSymbol {
if a & 3 == 1 || p & 3 == 1 { j } else { -j }
}
fn swap_long<T: Unsigned>(j: JacobiSymbol, a: Word, p: &Odd<T>) -> (JacobiSymbol, &Odd<T>, Word) {
let j = apply_swap(j, a, p.as_ref().as_limbs()[0].0);
(j, p, a)
}
fn swap_short(j: JacobiSymbol, a: Word, p: Word) -> (JacobiSymbol, Word, Word) {
let j = apply_swap(j, a, p);
(j, p, a)
}
pub(crate) fn jacobi_symbol_vartime<T>(abs_a: Word, a_is_negative: bool, p_long: &Odd<T>) -> JacobiSymbol
where
T: Unsigned,
{
let result = JacobiSymbol::One;
let result = if a_is_negative && p_long.as_ref().as_limbs()[0].0 & 3 == 3 {
-result
} else {
result
};
if abs_a == 1 || p_long.as_ref() == &T::one_like(p_long) {
return result;
}
let a_limb = Limb::from(abs_a);
let (result, a, p): (JacobiSymbol, Word, Word) = if p_long.bits_vartime() <= Limb::BITS {
let a = a_limb.0;
let p = p_long.as_ref().as_limbs()[0].0;
(result, a % p, p)
} else {
let (result, a) = reduce_numerator_long(result, a_limb.0, p_long.as_ref());
if a == 1 {
return result;
}
let (result, a_long, p) = swap_long(result, a, p_long);
let a = a_long.rem_limb(CTNonZero::new(Limb::from(p)).expect("divisor should be non-zero here"));
(result, a.0, p)
};
let mut result = result;
let mut a = a;
let mut p = p;
loop {
if a == 0 {
return JacobiSymbol::Zero;
}
(result, a) = reduce_numerator_short(result, a, p);
if a == 1 {
return result;
}
(result, a, p) = swap_short(result, a, p);
a %= p;
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use crypto_bigint::{Odd, U128, Word};
use num_bigint::{BigInt, Sign};
use num_modular::ModularSymbols;
use proptest::prelude::*;
use super::{JacobiSymbol, jacobi_symbol_vartime};
#[test]
fn jacobi_symbol_derived_traits() {
assert_eq!(format!("{:?}", JacobiSymbol::One), "One");
assert_eq!(JacobiSymbol::One, JacobiSymbol::One);
assert!(JacobiSymbol::One != JacobiSymbol::MinusOne);
assert_eq!(JacobiSymbol::One.clone(), JacobiSymbol::One);
}
#[test]
fn jacobi_symbol_neg_zero() {
assert_eq!(-JacobiSymbol::Zero, JacobiSymbol::Zero);
}
fn jacobi_symbol_ref(a: Word, a_is_negative: bool, p: &U128) -> JacobiSymbol {
let mut a_bi = BigInt::from(a);
if a_is_negative {
a_bi = -a_bi;
}
let p_bi = BigInt::from_bytes_be(Sign::Plus, p.to_be_bytes().as_ref());
let j = a_bi.jacobi(&p_bi);
if j == 1 {
JacobiSymbol::One
} else if j == -1 {
JacobiSymbol::MinusOne
} else {
JacobiSymbol::Zero
}
}
#[test]
fn small_values() {
for a in 0..31 {
for a_is_negative in [true, false] {
for p in (1u32..31).step_by(2) {
let p_long = Odd::new(U128::from(p)).unwrap();
let j_ref = jacobi_symbol_ref(a, a_is_negative, &p_long);
let j = jacobi_symbol_vartime(a, a_is_negative, &p_long);
assert_eq!(j, j_ref);
}
}
}
}
#[test]
fn big_values() {
let a = 2147483647; let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); assert_eq!(jacobi_symbol_vartime(a, false, &p), JacobiSymbol::Zero);
assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::Zero);
let a = 659456; let p = Odd::new(U128::from_be_hex("ffffffffffffffffffffffffffffff5f")).unwrap(); assert_eq!(jacobi_symbol_vartime(a, false, &p), JacobiSymbol::One);
assert_eq!(jacobi_symbol_ref(a, false, &p), JacobiSymbol::One);
let a = 2147483648;
let a_is_negative = true;
let p = Odd::new(U128::from_be_hex("000000007ffffffeffffffe28000003b")).unwrap(); assert_eq!(jacobi_symbol_vartime(a, a_is_negative, &p), JacobiSymbol::One);
assert_eq!(jacobi_symbol_ref(a, a_is_negative, &p), JacobiSymbol::One);
}
prop_compose! {
fn odd_uint()(bytes in any::<[u8; 16]>()) -> Odd<U128> {
Odd::new(U128::from_le_slice(&bytes) | U128::ONE).unwrap()
}
}
proptest! {
#[test]
fn fuzzy(abs_a in any::<Word>(), a_is_negative in any::<bool>(), p in odd_uint()) {
let j_ref = jacobi_symbol_ref(abs_a, a_is_negative, &p);
let j = jacobi_symbol_vartime(abs_a, a_is_negative, &p);
assert_eq!(j, j_ref);
}
}
}