diff --git a/benches/boxed_uint.rs b/benches/boxed_uint.rs index 8654f5353..15d5df8e0 100644 --- a/benches/boxed_uint.rs +++ b/benches/boxed_uint.rs @@ -232,6 +232,42 @@ fn bench_division(c: &mut Criterion) { ); }); + group.bench_function("boxed_div_exact", |b| { + b.iter_batched( + || { + ( + BoxedUint::max(UINT_BITS), + NonZero::new(BoxedUint::random_bits_with_precision( + &mut rng, + UINT_BITS / 2, + UINT_BITS, + )) + .unwrap(), + ) + }, + |(x, y)| black_box(x.div_exact(&y)), + BatchSize::SmallInput, + ); + }); + + group.bench_function("boxed_div_exact_vartime", |b| { + b.iter_batched( + || { + ( + BoxedUint::max(UINT_BITS), + NonZero::new(BoxedUint::random_bits_with_precision( + &mut rng, + UINT_BITS / 2, + UINT_BITS, + )) + .unwrap(), + ) + }, + |(x, y)| black_box(x.div_exact_vartime(&y)), + BatchSize::SmallInput, + ); + }); + group.finish(); } diff --git a/benches/uint.rs b/benches/uint.rs index 532a59e33..10108bec3 100644 --- a/benches/uint.rs +++ b/benches/uint.rs @@ -393,6 +393,44 @@ fn bench_division(c: &mut Criterion) { ); }); + group.bench_function("div exact, U256/U128", |b| { + b.iter_batched( + || { + ( + U256::random_from_rng(&mut rng), + NonZero::::random_from_rng(&mut rng), + ) + }, + |(x, y)| x.div_exact(&y), + BatchSize::SmallInput, + ); + }); + + group.bench_function("div exact, U256/U128 (in U256)", |b| { + b.iter_batched( + || { + let x = U256::random_from_rng(&mut rng); + let y_half = U128::random_from_rng(&mut rng); + let y: U256 = (y_half, U128::ZERO).into(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| x.div_exact(&y), + BatchSize::SmallInput, + ); + }); + + group.bench_function("div exact, U256/U128 (in U512)", |b| { + b.iter_batched( + || { + let x = U256::random_from_rng(&mut rng); + let y: U512 = U128::random_from_rng(&mut rng).resize(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| x.div_exact(&y), + BatchSize::SmallInput, + ); + }); + group.bench_function("div/rem_vartime, U256/U128, full size", |b| { b.iter_batched( || { @@ -405,6 +443,18 @@ fn bench_division(c: &mut Criterion) { ); }); + group.bench_function("div exact vartime, U256/U128, full size", |b| { + b.iter_batched( + || { + let x = U256::random_from_rng(&mut rng); + let y = U256::from((NonZero::::random_from_rng(&mut rng).get(), U128::ZERO)); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| x.div_exact_vartime(&y), + BatchSize::SmallInput, + ); + }); + group.bench_function("rem, U256/U128", |b| { b.iter_batched( || { @@ -510,6 +560,19 @@ fn bench_division(c: &mut Criterion) { ); }); + group.bench_function("div exact vartime, U256/Limb, full size", |b| { + b.iter_batched( + || { + let x = U256::random_from_rng(&mut rng); + let y_small = Limb::random_from_rng(&mut rng); + let y = U256::from_word(y_small.0); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| x.div_exact_vartime(&y), + BatchSize::SmallInput, + ); + }); + group.bench_function("div/rem, U256/Limb, single limb", |b| { b.iter_batched( || { diff --git a/src/limb.rs b/src/limb.rs index c48961278..eb65e9141 100644 --- a/src/limb.rs +++ b/src/limb.rs @@ -13,6 +13,7 @@ mod div; mod encoding; mod from; mod gcd; +mod invert_mod; mod mul; mod neg; mod shl; diff --git a/src/limb/div.rs b/src/limb/div.rs index 39eb44372..cc0d95eee 100644 --- a/src/limb/div.rs +++ b/src/limb/div.rs @@ -42,6 +42,30 @@ impl Limb { let rem = self.div_rem(rhs_nz).1; CtOption::new(rem, is_nz) } + + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + #[must_use] + pub const fn div_exact(&self, rhs: NonZero) -> CtOption { + let mut quo = *self; + let mut div = rhs.get_copy(); + let exact = UintRef::new_mut(slice::from_mut(&mut quo)) + .div_exact(UintRef::new_mut(slice::from_mut(&mut div))); + CtOption::new(quo, exact) + } + + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + /// + /// This is variable-time only with respect to `rhs`. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect to `self`. + #[must_use] + pub const fn div_exact_vartime(&self, rhs: NonZero) -> CtOption { + let mut quo = *self; + let mut div = rhs.get_copy(); + let exact = UintRef::new_mut(slice::from_mut(&mut quo)) + .div_exact_vartime(UintRef::new_mut(slice::from_mut(&mut div))); + CtOption::new(quo, exact) + } } impl CheckedDiv for Limb { @@ -285,6 +309,17 @@ mod tests { let n = Limb::from_u32(0xffff_ffff); let d = NonZero::new(Limb::from_u32(0xfffe)).expect("ensured non-zero"); assert_eq!(n.div_rem(d), (Limb::from_u32(0x10002), Limb::from_u32(0x3))); + + assert_eq!(n.div_exact(d).into_option(), None); + assert_eq!(n.div_exact_vartime(d).into_option(), None); + + let d = NonZero::new(Limb::from_u32(0xffff)).expect("ensured non-zero"); + assert_eq!(n.div_rem(d), (Limb::from_u32(0x10001), Limb::from_u32(0))); + assert_eq!(n.div_exact(d).into_option(), Some(Limb::from_u32(0x10001))); + assert_eq!( + n.div_exact_vartime(d).into_option(), + Some(Limb::from_u32(0x10001)) + ); } #[test] diff --git a/src/limb/invert_mod.rs b/src/limb/invert_mod.rs new file mode 100644 index 000000000..8ed04341d --- /dev/null +++ b/src/limb/invert_mod.rs @@ -0,0 +1,17 @@ +use super::Limb; +use crate::{Odd, primitives}; + +impl Odd { + /// Returns the multiplicative inverse of the argument modulo 2^N, where + /// 2^N is the capacity of a [`Limb`]. + pub(crate) const fn multiplicative_inverse(self) -> Limb { + cpubits::cpubits! { + 32 => { + Limb(primitives::u32_invert_odd(self.as_ref().0)) + } + 64 => { + Limb(primitives::u64_invert_odd(self.as_ref().0)) + } + } + } +} diff --git a/src/primitives.rs b/src/primitives.rs index 3d2d1828a..31117b2a8 100644 --- a/src/primitives.rs +++ b/src/primitives.rs @@ -94,6 +94,43 @@ pub(crate) const fn u32_bits(n: u32) -> u32 { u32::BITS - n.leading_zeros() } +cpubits::cpubits! { + 32 => { + /// Returns the multiplicative inverse of the argument modulo 2^32. + /// + /// For correct results, the input `value` must be odd. + #[must_use] + pub(crate) const fn u32_invert_odd(value: u32) -> u32 { + debug_assert!(value & 1 == 1, "value must be odd"); + let x = value.wrapping_mul(3) ^ 2; + let y = 1u32.wrapping_sub(x.wrapping_mul(value)); + let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); + let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); + x.wrapping_mul(y.wrapping_add(1)) + } + } +} + +/// Returns the multiplicative inverse of the argument modulo 2^64. The implementation is based +/// on the Hurchalla's method for computing the multiplicative inverse modulo a power of two, and +/// is essentially an optimized Newton iteration. +/// +/// For correct results, the input `value` must be odd. +/// +/// For better understanding the implementation, the following paper is recommended: +/// J. Hurchalla, "An Improved Integer Multiplicative Inverse (modulo 2^w)", +/// +#[must_use] +pub(crate) const fn u64_invert_odd(value: u64) -> u64 { + debug_assert!(value & 1 == 1, "value must be odd"); + let x = value.wrapping_mul(3) ^ 2; + let y = 1u64.wrapping_sub(x.wrapping_mul(value)); + let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); + let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); + let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); + x.wrapping_mul(y.wrapping_add(1)) +} + #[cfg(test)] mod tests { use super::{u32_max, u32_min, u32_rem}; @@ -133,4 +170,17 @@ mod tests { assert_eq!(u32_rem(7, 5), 2); assert_eq!(u32_rem(101, 5), 1); } + + cpubits::cpubits! { + 32 => { + #[test] + fn test_u32_invert_odd() { + use super::u32_invert_odd; + + assert_eq!(u32_invert_odd(1), 1); + assert_eq!(u32_invert_odd(5).wrapping_mul(5), 1); + assert_eq!(u32_invert_odd(u32::MAX).wrapping_mul(u32::MAX), 1); + } + } + } } diff --git a/src/uint/boxed/div.rs b/src/uint/boxed/div.rs index aeeecd02e..61419325f 100644 --- a/src/uint/boxed/div.rs +++ b/src/uint/boxed/div.rs @@ -63,6 +63,15 @@ impl BoxedUint { rem } + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + #[must_use] + pub fn div_exact(&self, rhs: &NonZero) -> CtOption { + let mut quo = self.clone(); + let mut div = rhs.to_unsigned().get(); + let exact = quo.as_mut_uint_ref().div_exact(div.as_mut_uint_ref()); + CtOption::new(quo, exact) + } + /// Computes self / rhs, returns the quotient and remainder. /// /// Variable-time with respect to `rhs` @@ -121,6 +130,20 @@ impl BoxedUint { rem } + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + #[must_use] + pub fn div_exact_vartime( + &self, + rhs: &NonZero, + ) -> CtOption { + let mut quo = self.clone(); + let mut div = rhs.to_unsigned().get(); + let exact = quo + .as_mut_uint_ref() + .div_exact_vartime(div.as_mut_uint_ref()); + CtOption::new(quo, exact) + } + /// Wrapped division is just normal division i.e. `self` / `rhs` /// There’s no way wrapping could ever happen. /// diff --git a/src/uint/div.rs b/src/uint/div.rs index b31d280d7..0cb654421 100644 --- a/src/uint/div.rs +++ b/src/uint/div.rs @@ -69,6 +69,36 @@ impl Uint { (x, y) } + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + #[must_use] + pub const fn div_exact( + &self, + rhs: &NonZero>, + ) -> CtOption { + let mut quo = *self; + let mut div = rhs.get_copy(); + let exact = quo.as_mut_uint_ref().div_exact(div.as_mut_uint_ref()); + CtOption::new(quo, exact) + } + + /// Exactly divides `self` by `rhs`, returning `CtOption::none()` if `self` is not divisible by `rhs`. + /// + /// This is variable-time only with respect to `rhs`. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect to `self`. + #[must_use] + pub const fn div_exact_vartime( + &self, + rhs: &NonZero>, + ) -> CtOption { + let mut quo = *self; + let mut div = rhs.get_copy(); + let exact = quo + .as_mut_uint_ref() + .div_exact_vartime(div.as_mut_uint_ref()); + CtOption::new(quo, exact) + } + /// Computes self / rhs, assigning the quotient to `self` and returning the remainder. #[must_use] pub(crate) fn div_rem_assign(&mut self, rhs: NonZero) -> Rhs { @@ -538,6 +568,10 @@ mod tests { let (q, r) = lhs.div_rem_vartime(&rhs); assert_eq!(U256::from(*e), q); assert_eq!(U256::from(*ee), r); + let q = lhs.div_exact(&rhs).into_option(); + assert_eq!(if *ee == 0 { Some(U256::from(*e)) } else { None }, q); + let q = lhs.div_exact_vartime(&rhs).into_option(); + assert_eq!(if *ee == 0 { Some(U256::from(*e)) } else { None }, q); } } @@ -562,6 +596,10 @@ mod tests { assert_eq!(q, num); let (q, _) = n.div_rem_vartime(&den); assert_eq!(q, num); + let q = n.div_exact(&den).into_option(); + assert_eq!(q, Some(num)); + let q = n.div_exact_vartime(&den).into_option(); + assert_eq!(q, Some(num)); } } } diff --git a/src/uint/ref_type/div.rs b/src/uint/ref_type/div.rs index f291c5e02..8ec101fd1 100644 --- a/src/uint/ref_type/div.rs +++ b/src/uint/ref_type/div.rs @@ -5,7 +5,7 @@ use super::UintRef; use crate::{ - Choice, Limb, NonZero, + Choice, Limb, NonZero, Odd, div_limb::{Reciprocal, div2by1, div3by2}, primitives::u32_min, word, @@ -18,7 +18,6 @@ impl UintRef { /// # Panics /// If the divisor is zero. #[inline(always)] - #[allow(clippy::integer_division_remainder_used, reason = "needs triage")] pub(crate) const fn div_rem(&mut self, rhs: &mut Self) { let (x, y) = (self, rhs); @@ -38,7 +37,7 @@ impl UintRef { y.unbounded_shl_assign(yz); // Shift the dividend to align the words - let lshift = yz % Limb::BITS; + let lshift = yz & (Limb::BITS - 1); let x_hi = x.shl_assign_limb(lshift); Self::div_rem_shifted(x, x_hi, y, ywords); @@ -99,7 +98,6 @@ impl UintRef { /// # Panics /// If the divisor is zero. #[inline(always)] - #[allow(clippy::integer_division_remainder_used, reason = "needs triage")] pub(crate) const fn rem_wide(x_lower_upper: (&mut Self, &mut Self), rhs: &mut Self) { let (x_lo, x) = x_lower_upper; let y = rhs; @@ -122,7 +120,7 @@ impl UintRef { y.unbounded_shl_assign(yz); // Shift the dividend to align the words - let lshift = yz % Limb::BITS; + let lshift = yz & (Limb::BITS - 1); let x_lo_carry = x_lo.shl_assign_limb(lshift); let x_hi = x.shl_assign_limb(lshift); x.limbs[0] = x.limbs[0].bitor(x_lo_carry); @@ -599,4 +597,156 @@ impl UintRef { (_, hi.0) = div2by1(self.limbs[0].shl(lshift).0, hi.0, reciprocal); hi.shr(lshift) } + + /// Exactly divides `self` by `rhs`, returning `Choice::FALSE` if `self` is not divisible by `rhs`. + /// + /// If the division is not exact then `self` is left in an indeterminate state. + /// The divisor `rhs` is right-shifted to produce an odd integer. + /// + /// # Panics + /// If the divisor is zero. + #[inline(always)] + pub(crate) const fn div_exact(&mut self, rhs: &mut UintRef) -> Choice { + let x_prec = self.bits_precision(); + let y_bits = rhs.bits(); + assert!(y_bits > 0, "zero divisor"); + + let tz = rhs.trailing_zeros(); + // Track whether there are more zeros in the divisor than bits in the dividend + let excess_z = Choice::from_u32_lt(x_prec, tz); + let tz = excess_z.select_u32(tz, x_prec); + + // Shift the divisor such that it is odd + rhs.shr_assign(tz); + + // Check that the dividend evenly divides by 2^tz, and shift it to match the divisor + let div2s_exact = self.ensure_trailing_zeros(tz).and(excess_z.not()); + self.shr_assign(tz); + + let y = Odd::new_ref_unchecked(rhs); + let y_inv = y.invert_mod_limb(); + let ywords = (y_bits - tz).div_ceil(Limb::BITS); + let is_exact = Self::div_exact_odd_with_inverse(self, y, y_inv, ywords, Choice::FALSE) + .and(div2s_exact); + + // Restore the divisor + rhs.shl_assign(tz); + + is_exact + } + + /// Exactly divides `self` by `rhs`, returning `Choice::FALSE` if `self` is not divisible by `rhs`. + /// + /// The quotient is left in `self`, but is not guaranteed to be in a usable state unless + /// the return value is `Choice::TRUE`. + /// + /// # Panics + /// If the divisor is zero. + #[inline(always)] + pub(crate) const fn div_exact_vartime(&mut self, rhs: &mut UintRef) -> Choice { + let x_prec = self.bits_precision(); + let y_bits = rhs.bits_vartime(); + assert!(y_bits > 0, "zero divisor"); + + let tz = rhs.trailing_zeros_vartime(); + if tz > x_prec { + // The divisor exceeds the dividend precision. Short circuit based on public + // information (the divisor and the input size in limbs) + return Choice::FALSE; + } + // Reduce the divisor to its populated limbs and shift it such that it is odd + let rhs = rhs.leading_mut(y_bits.div_ceil(Limb::BITS) as usize); + rhs.unbounded_shr_assign_vartime(tz); + + // Check that the dividend evenly divides by 2^tz, and shift it to match the divisor + let div2s_exact = self.ensure_trailing_zeros(tz); + self.unbounded_shr_assign_vartime(tz); + + let ywords = (y_bits - tz).div_ceil(Limb::BITS); + let y = Odd::new_ref_unchecked(rhs.leading(ywords as usize)); + let y_inv = y.invert_mod_limb(); + let is_exact = + Self::div_exact_odd_with_inverse(self, y, y_inv, ywords, Choice::TRUE).and(div2s_exact); + + // Restore the divisor + rhs.shl_assign(tz); + + is_exact + } + + /// Exactly divides `x` by `y`, returning `Choice::FALSE` if `x` is not divisible by `y`. + /// + /// The quotient is left in `self`, but is not guaranteed to be in a usable state unless + /// the return value is `Choice::TRUE`. + /// + /// This method performs a Hensel division, subtracting from the least significant bits + /// and calculating an LSB quotient and remainder `(q,r) s.t. x = qy + r•2^N`. When the remainder + /// is zero, the calculated quotient corresponds to the traditional division quotient. + /// + /// For constant-time operation, this acts as if the divisor `y` is as small as one limb, + /// performing loops without updates to the quotient for larger divisors when vartime operation + /// is not specified. + #[allow(clippy::cast_possible_truncation)] + #[inline(always)] + const fn div_exact_odd_with_inverse( + x: &mut UintRef, + y: &Odd, + y_inv: Limb, + ywords: u32, + vartime: Choice, + ) -> Choice { + let xc = x.nlimbs(); + let y = y.as_ref(); + let mut meta_carry = Limb::ZERO; + let mut xi = 0; + + while xi < xc { + let y_remain = u32_min((xc - xi) as u32, y.nlimbs() as u32); + // This loop is a no-op once there are fewer words remaining than the size of the divisor + let done = Choice::from_u32_lt(y_remain, ywords); + if vartime.and(done).to_bool_vartime() { + // Set the upper limbs to zero + x.trailing_mut(xi).fill(Limb::ZERO); + break; + } + + // Compute the quotient limb that will clear the low dividend limb + let quo = Limb::select(x.limbs[xi].wrapping_mul(y_inv), Limb::ZERO, done); + x.limbs[xi] = quo; + + let (_, mut carry) = quo.widening_mul(y.limbs[0]); + let mut sub; + let mut borrow = Limb::ZERO; + let mut yi = 1; + + while yi < y_remain as usize { + (sub, carry) = quo.carrying_mul_add(y.limbs[yi], Limb::ZERO, carry); + (x.limbs[xi + yi], borrow) = x.limbs[xi + yi].borrowing_sub(sub, borrow); + yi += 1; + } + + meta_carry = meta_carry.wrapping_add(carry); + + if xi + yi < xc { + (x.limbs[xi + yi], borrow) = x.limbs[xi + yi].borrowing_sub(meta_carry, borrow); + meta_carry = borrow.wrapping_neg(); + } else { + meta_carry = meta_carry.wrapping_sub(borrow); + } + + xi += 1; + } + + meta_carry.is_zero() + } + + // Check that `self` can be cleanly divided by 2^zs: the bottom zs bits are zero. + #[inline(always)] + const fn ensure_trailing_zeros(&self, zs: u32) -> Choice { + let z_words = (zs >> Limb::LOG2_BITS) as usize; + let z_bits = zs & (Limb::BITS - 1); + self.leading(z_words) + .is_zero() + .and(self.limbs[z_words].restrict_bits(z_bits).is_zero()) + } } diff --git a/src/uint/ref_type/invert_mod.rs b/src/uint/ref_type/invert_mod.rs index 659ea26e4..8f6460e74 100644 --- a/src/uint/ref_type/invert_mod.rs +++ b/src/uint/ref_type/invert_mod.rs @@ -1,25 +1,19 @@ use super::UintRef; -use crate::Odd; +use crate::{Limb, Odd, primitives}; impl Odd { - /// Returns the multiplicative inverse of the argument modulo 2^64. The implementation is based - /// on the Hurchalla's method for computing the multiplicative inverse modulo a power of two. - /// - /// For better understanding the implementation, the following paper is recommended: - /// J. Hurchalla, "An Improved Integer Multiplicative Inverse (modulo 2^w)", - /// - /// - /// Variable time with respect to the number of words in `value`, however that number will be - /// fixed for a given integer size. + /// Returns the multiplicative inverse of the argument modulo 2^N, where 2^N + /// is the capacity of a [`Limb`]. + #[must_use] + pub(crate) const fn invert_mod_limb(&self) -> Limb { + Odd::new_unchecked(self.as_ref().limbs[0]).multiplicative_inverse() + } + + /// Returns the multiplicative inverse of the argument modulo 2^64. #[must_use] pub const fn invert_mod_u64(&self) -> u64 { let value = self.as_ref().lowest_u64(); - let x = value.wrapping_mul(3) ^ 2; - let y = 1u64.wrapping_sub(x.wrapping_mul(value)); - let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); - let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); - let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y)); - x.wrapping_mul(y.wrapping_add(1)) + primitives::u64_invert_odd(value) } } diff --git a/tests/boxed_uint.rs b/tests/boxed_uint.rs index 5d4771e88..8398ecf89 100644 --- a/tests/boxed_uint.rs +++ b/tests/boxed_uint.rs @@ -13,7 +13,7 @@ use crypto_bigint::{ use num_bigint::BigUint; use num_integer::Integer as _; use num_modular::ModularUnaryOps; -use num_traits::identities::One; +use num_traits::{Zero, identities::One}; use proptest::prelude::*; #[allow(clippy::cast_possible_truncation)] @@ -119,28 +119,34 @@ proptest! { let a_bi = to_biguint(&a); let b_bi = to_biguint(&b); - let expected_quotient = &a_bi / &b_bi; - let expected_remainder = a_bi % b_bi; + let (expected_quotient, expected_remainder) = a_bi.div_rem(&b_bi); - let (actual_quotient, actual_remainder) = a.div_rem(&NonZero::new(b).unwrap()); + let div = NonZero::new(b).unwrap(); + let (actual_quotient, actual_remainder) = a.div_rem(&div); prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient)); prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder)); + + let (quotient_vartime, remainder_vartime) = a.div_rem_vartime(&div); + prop_assert_eq!(actual_quotient, quotient_vartime); + prop_assert_eq!(actual_remainder, remainder_vartime); } #[test] - fn div_rem_vartime((a, mut b) in uint_pair()) { + fn div_exact((a, mut b) in uint_pair()) { if b.is_zero().into() { b = b.wrapping_add(Limb::ONE); } let a_bi = to_biguint(&a); let b_bi = to_biguint(&b); - let expected_quotient = &a_bi / &b_bi; - let expected_remainder = a_bi % b_bi; - - let (actual_quotient, actual_remainder) = a.div_rem_vartime(&NonZero::new(b).unwrap()); - prop_assert_eq!(expected_quotient, to_biguint(&actual_quotient)); - prop_assert_eq!(expected_remainder, to_biguint(&actual_remainder)); + let (expected_quotient, expected_remainder) = a_bi.div_rem(&b_bi); + let expected = if expected_remainder.is_zero() { Some(expected_quotient) } else { None }; + + let div = NonZero::new(b).unwrap(); + let actual = a.div_exact(&div).into_option(); + prop_assert_eq!(&expected, &actual.as_ref().map(to_biguint)); + let actual_vartime = a.div_exact_vartime(&div).into_option(); + prop_assert_eq!(expected, actual_vartime.as_ref().map(to_biguint)); } #[test] diff --git a/tests/uint.rs b/tests/uint.rs index 25d8a4894..f82285aea 100644 --- a/tests/uint.rs +++ b/tests/uint.rs @@ -315,6 +315,22 @@ proptest! { } } + #[test] + fn div_exact(a in uint(), b in uint()) { + let a_bi = to_biguint(&a); + let b_bi = to_biguint(&b); + + if !b_bi.is_zero() { + let (q, r) = a_bi.div_rem(&b_bi); + let expected = if r.is_zero() { Some(to_uint(q)) } else { None }; + let b_nz = NonZero::new(b).unwrap(); + let actual = a.div_exact(&b_nz).into_option(); + prop_assert_eq!(expected, actual); + let actual_vartime = a.div_exact_vartime(&b_nz).into_option(); + prop_assert_eq!(expected, actual_vartime); + } + } + #[test] fn rem_wide(a in uint(), b in uint(), c in uint()) { let ab_bi = to_biguint(&a) * to_biguint(&b);