From 213e1de5cfb33b1d42b84ef7cb645e0d51fbc982 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 26 Nov 2023 20:02:56 -0700 Subject: [PATCH] Add `BoxedUint::rem_vartime` --- src/uint/boxed.rs | 14 +++++++++++ src/uint/boxed/div.rs | 44 +++++++++++++++++++++++++++++++++++ tests/boxed_uint_proptests.rs | 26 +++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 src/uint/boxed/div.rs diff --git a/src/uint/boxed.rs b/src/uint/boxed.rs index 0587674cb..1ae76d76a 100644 --- a/src/uint/boxed.rs +++ b/src/uint/boxed.rs @@ -5,6 +5,7 @@ mod add_mod; mod bit_and; mod bits; mod cmp; +mod div; pub(crate) mod encoding; mod modular; mod mul; @@ -170,6 +171,19 @@ impl BoxedUint { Self { limbs } } + /// Widen this type's precision to the given number of bits. + /// + /// Panics if `bits_precision` is not a multiple of `Limb::BITS` or smaller than the current + /// precision. + pub fn widen(&self, bits_precision: usize) -> BoxedUint { + assert!(bits_precision % Limb::BITS == 0); + assert!(bits_precision >= self.bits_precision()); + + let mut ret = BoxedUint::zero_with_precision(bits_precision); + ret.limbs[..self.nlimbs()].copy_from_slice(&self.limbs); + ret + } + /// Perform a carry chain-like operation over the limbs of the inputs, /// constructing a result from the returned limbs and carry which is /// widened to the same width as the widest input. diff --git a/src/uint/boxed/div.rs b/src/uint/boxed/div.rs new file mode 100644 index 000000000..a38af763b --- /dev/null +++ b/src/uint/boxed/div.rs @@ -0,0 +1,44 @@ +//! [`BoxedUint`] division operations. + +use crate::{BoxedUint, Limb}; +use subtle::{ConstantTimeEq, CtOption}; + +impl BoxedUint { + /// Computes self % rhs, returns the remainder. + /// + /// Variable-time with respect to `rhs`. + /// + /// Panics if `self` and `rhs` have different precisions. + // TODO(tarcieri): make infallible by making `rhs` into `NonZero`; don't panic + pub fn rem_vartime(&self, rhs: &Self) -> CtOption { + debug_assert_eq!(self.nlimbs(), rhs.nlimbs()); + let mb = rhs.bits(); + let mut bd = self.bits_precision() - mb; + let mut rem = self.clone(); + let mut c = rhs.shl_vartime(bd); + + loop { + let (r, borrow) = rem.sbb(&c, Limb::ZERO); + rem = Self::conditional_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO)); + if bd == 0 { + break; + } + bd -= 1; + c = c.shr_vartime(1); + } + + CtOption::new(rem, !(mb as u32).ct_eq(&0)) + } +} + +#[cfg(test)] +mod tests { + use super::BoxedUint; + + #[test] + fn rem_vartime() { + let n = BoxedUint::from(0xFFEECCBBAA99887766u128); + let p = BoxedUint::from(997u128); + assert_eq!(BoxedUint::from(648u128), n.rem_vartime(&p).unwrap()); + } +} diff --git a/tests/boxed_uint_proptests.rs b/tests/boxed_uint_proptests.rs index 493bbd85b..4fcf0aeb3 100644 --- a/tests/boxed_uint_proptests.rs +++ b/tests/boxed_uint_proptests.rs @@ -19,6 +19,7 @@ fn to_uint(big_uint: BigUint) -> BoxedUint { } prop_compose! { + /// Generate a random `uint()`. fn uint()(mut bytes in any::>()) -> BoxedUint { let extra = bytes.len() % Limb::BYTES; let bytes_precision = bytes.len() - extra; @@ -26,6 +27,18 @@ prop_compose! { BoxedUint::from_be_slice(&bytes, bytes_precision * 8).unwrap() } } +prop_compose! { + /// Generate a pair of random uints with the same precision. + fn uint_pair()(mut a in uint(), mut b in uint()) -> (BoxedUint, BoxedUint) { + if a.bits_precision() > b.bits_precision() { + b = b.widen(a.bits_precision()); + } else if a.bits_precision() < b.bits_precision() { + a = a.widen(b.bits_precision()); + } + + (a, b) + } +} proptest! { #[test] @@ -58,4 +71,17 @@ proptest! { prop_assert_eq!(expected, to_biguint(&actual)); } + + #[test] + fn rem_vartime((a, b) in uint_pair()) { + if bool::from(!b.is_zero()) { + let a_bi = to_biguint(&a); + let b_bi = to_biguint(&b); + + let expected = a_bi % b_bi; + let actual = a.rem_vartime(&b).unwrap(); + + prop_assert_eq!(expected, to_biguint(&actual)); + } + } }