use core::num::NonZero;
use crypto_bigint::{Limb, NonZero as CTNonZero, Unsigned, Word};
pub(crate) fn gcd_vartime<T>(n: &T, m: NonZero<Word>) -> Word
where
T: Unsigned,
{
let m = m.get();
if n.is_zero().into() {
return m;
}
let (a, b): (Word, Word) = if n.bits() > Word::BITS {
let r = n.rem_limb(CTNonZero::new(Limb::from(m)).expect("divisor should be non-zero here"));
(m, r.0)
} else {
let n = n.as_limbs()[0].0;
if n > m { (n, m) } else { (m, n) }
};
binary_gcd(a, b)
}
fn binary_gcd(mut n: Word, mut m: Word) -> Word {
let i = n.trailing_zeros();
n >>= i;
let j = m.trailing_zeros();
m >>= j;
let k = core::cmp::min(i, j);
loop {
if n > m {
core::mem::swap(&mut n, &mut m);
}
m -= n;
if m == 0 {
return n << k;
}
m >>= m.trailing_zeros();
}
}
#[cfg(test)]
mod tests {
use core::num::NonZero;
use crypto_bigint::{U128, Word};
use num_bigint::BigUint;
use num_integer::Integer;
use proptest::prelude::*;
use super::gcd_vartime;
#[test]
fn corner_cases() {
assert_eq!(gcd_vartime(&U128::from(0u64), NonZero::new(5).unwrap()), 5);
assert_eq!(gcd_vartime(&U128::from(1u64), NonZero::new(11 * 13 * 19).unwrap()), 1);
assert_eq!(gcd_vartime(&U128::from(7u64 * 11 * 13), NonZero::new(1).unwrap()), 1);
assert_eq!(
gcd_vartime(&U128::from(7u64 * 11 * 13), NonZero::new(11 * 13 * 19).unwrap()),
11 * 13
);
}
prop_compose! {
fn uint()(bytes in any::<[u8; 16]>()) -> U128 {
U128::from_le_slice(&bytes) | U128::ONE
}
}
proptest! {
#[test]
fn fuzzy(m in any::<Word>(), n in uint()) {
if m == 0 {
return Ok(());
}
let m_bi = BigUint::from(m);
let n_bi = BigUint::from_bytes_be(n.to_be_bytes().as_ref());
let gcd_ref: Word = n_bi.gcd(&m_bi).try_into().unwrap();
let gcd_test = gcd_vartime(&n, NonZero::new(m).unwrap());
assert_eq!(gcd_test, gcd_ref);
}
}
}