diff --git a/Cargo.lock b/Cargo.lock index e39846ffb..aecf9c344 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,6 +77,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.5.0" @@ -225,7 +231,7 @@ dependencies = [ "der", "generic-array", "hex-literal", - "num-bigint", + "num-bigint-dig", "num-integer", "num-traits", "proptest", @@ -350,6 +356,9 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -391,14 +400,20 @@ dependencies = [ ] [[package]] -name = "num-bigint" -version = "0.4.4" +name = "num-bigint-dig" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ - "autocfg", + "byteorder", + "lazy_static", + "libm", "num-integer", + "num-iter", "num-traits", + "rand", + "serde", + "smallvec", ] [[package]] @@ -411,6 +426,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.17" @@ -711,6 +737,18 @@ dependencies = [ "serde", ] +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "subtle" version = "2.5.0" diff --git a/Cargo.toml b/Cargo.toml index 3b7ec79e2..747177375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ zeroize = { version = "1", optional = true, default-features = false } bincode = "1" criterion = { version = "0.5", features = ["html_reports"] } hex-literal = "0.4" -num-bigint = "0.4" +num-bigint = { package = "num-bigint-dig", version = "0.8" } num-integer = "0.1" num-traits = "0.2" proptest = "1" diff --git a/src/uint/boxed.rs b/src/uint/boxed.rs index d3dbd4123..46716bb7e 100644 --- a/src/uint/boxed.rs +++ b/src/uint/boxed.rs @@ -10,6 +10,7 @@ mod div; pub(crate) mod encoding; mod inv_mod; mod mul; +mod neg; mod shl; mod shr; mod sub; @@ -18,7 +19,7 @@ mod sub_mod; use crate::{Limb, NonZero, Uint, Word, Zero, U128, U64}; use alloc::{boxed::Box, vec, vec::Vec}; use core::fmt; -use subtle::{Choice, ConditionallySelectable}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; #[cfg(feature = "zeroize")] use zeroize::Zeroize; @@ -85,6 +86,13 @@ impl BoxedUint { .fold(Choice::from(1), |acc, limb| acc & limb.is_zero()) } + /// Is this [`BoxedUint`] equal to one? + pub fn is_one(&self) -> Choice { + let mut iter = self.limbs.iter(); + let choice = iter.next().copied().unwrap_or(Limb::ZERO).ct_eq(&Limb::ONE); + iter.fold(choice, |acc, limb| acc & limb.is_zero()) + } + /// Is this integer value an odd number? /// /// # Returns @@ -197,6 +205,25 @@ impl BoxedUint { Self { limbs } } + /// Conditionally assign `other` to `self`, according to `choice`. + /// + /// This function should execute in constant time. + #[inline] + pub fn conditional_assign(&mut self, other: &Self, choice: Choice) { + *self = Self::conditional_select(self, other, choice); + } + + /// Conditionally swap `self` and `other` if `choice == 1`; otherwise, + /// reassign both unto themselves. + /// + /// This function should execute in constant time. + #[inline] + fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) { + let t = a.clone(); + a.conditional_assign(b, choice); + b.conditional_assign(&t, choice); + } + /// 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 diff --git a/src/uint/boxed/add.rs b/src/uint/boxed/add.rs index c8c196b23..d03d5c565 100644 --- a/src/uint/boxed/add.rs +++ b/src/uint/boxed/add.rs @@ -1,7 +1,7 @@ //! [`BoxedUint`] addition operations. use crate::{BoxedUint, CheckedAdd, Limb, Zero}; -use subtle::CtOption; +use subtle::{Choice, CtOption}; impl BoxedUint { /// Computes `a + b + carry`, returning the result along with the new carry. @@ -14,6 +14,18 @@ impl BoxedUint { pub fn wrapping_add(&self, rhs: &Self) -> Self { self.adc(rhs, Limb::ZERO).0 } + + /// Perform wrapping addition, returning the truthy value as the second element of the tuple + /// if an overflow has occurred. + pub(crate) fn conditional_wrapping_add(&self, rhs: &Self, choice: Choice) -> (Self, Choice) { + let actual_rhs = Self::conditional_select( + &Self::zero_with_precision(rhs.bits_precision()), + rhs, + choice, + ); + let (sum, carry) = self.adc(&actual_rhs, Limb::ZERO); + (sum, Choice::from((carry.0 & 1) as u8)) + } } impl CheckedAdd<&BoxedUint> for BoxedUint { diff --git a/src/uint/boxed/bits.rs b/src/uint/boxed/bits.rs index d07f66a75..3dd23371a 100644 --- a/src/uint/boxed/bits.rs +++ b/src/uint/boxed/bits.rs @@ -29,6 +29,20 @@ impl BoxedUint { self.limbs.len() * Limb::BITS } + /// Calculate the number of trailing zeros in the binary representation of this number. + pub fn trailing_zeros(&self) -> usize { + let mut count: Word = 0; + let mut nonzero_limb_not_encountered = Choice::from(1u8); + + for l in &*self.limbs { + let z = l.trailing_zeros() as Word; + count += Word::conditional_select(&0, &z, nonzero_limb_not_encountered); + nonzero_limb_not_encountered &= l.is_zero(); + } + + count as usize + } + /// Sets the bit at `index` to 0 or 1 depending on the value of `bit_value`. pub(crate) fn set_bit(&mut self, index: usize, bit_value: Choice) { let limb_num = index / Limb::BITS; diff --git a/src/uint/boxed/encoding.rs b/src/uint/boxed/encoding.rs index a2c7e8668..c2f396e8b 100644 --- a/src/uint/boxed/encoding.rs +++ b/src/uint/boxed/encoding.rs @@ -24,6 +24,10 @@ impl BoxedUint { /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this /// function will return [`DecodeError::InputSize`]. pub fn from_be_slice(bytes: &[u8], bits_precision: usize) -> Result { + if bytes.is_empty() && bits_precision == 0 { + return Ok(Self::zero()); + } + if bits_precision % Limb::BITS != 0 { return Err(DecodeError::Precision); } @@ -50,6 +54,10 @@ impl BoxedUint { /// If the length of `bytes` (when interpreted as bits) is larger than `bits_precision`, this /// function will return [`DecodeError::InputSize`]. pub fn from_le_slice(bytes: &[u8], bits_precision: usize) -> Result { + if bytes.is_empty() && bits_precision == 0 { + return Ok(Self::zero()); + } + if bits_precision % Limb::BITS != 0 { return Err(DecodeError::Precision); } diff --git a/src/uint/boxed/inv_mod.rs b/src/uint/boxed/inv_mod.rs index 01e58cd38..be40f909e 100644 --- a/src/uint/boxed/inv_mod.rs +++ b/src/uint/boxed/inv_mod.rs @@ -1,13 +1,46 @@ //! [`BoxedUint`] modular inverse (i.e. reciprocal) operations. use crate::{BoxedUint, Word}; -use subtle::{Choice, ConstantTimeLess}; +use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption}; impl BoxedUint { + /// Computes the multiplicative inverse of `self` mod `modulus`. + /// Returns `None` if an inverse does not exist. + pub fn inv_mod(&self, modulus: &Self) -> CtOption { + debug_assert_eq!(self.bits_precision(), modulus.bits_precision()); + + // Decompose `modulus = s * 2^k` where `s` is odd + let k = modulus.trailing_zeros(); + let s = modulus.shr(k); + + // Decompose `self` into RNS with moduli `2^k` and `s` and calculate the inverses. + // Using the fact that `(z^{-1} mod (m1 * m2)) mod m1 == z^{-1} mod m1` + let (a, a_is_some) = self.inv_odd_mod(&s); + let b = self.inv_mod2k(k); + // inverse modulo 2^k exists either if `k` is 0 or if `self` is odd. + let b_is_some = (k as Word).ct_eq(&0) | self.is_odd(); + + // Restore from RNS: + // self^{-1} = a mod s = b mod 2^k + // => self^{-1} = a + s * ((b - a) * s^(-1) mod 2^k) + // (essentially one step of the Garner's algorithm for recovery from RNS). + + let m_odd_inv = s.inv_mod2k(k); // `s` is odd, so this always exists + + // This part is mod 2^k + let mask = Self::one().shl(k).wrapping_sub(&Self::one()); + let t = (b.wrapping_sub(&a).wrapping_mul(&m_odd_inv)).bitand(&mask); + + // Will not overflow since `a <= s - 1`, `t <= 2^k - 1`, + // so `a + s * t <= s * 2^k - 1 == modulus - 1`. + let result = a.wrapping_add(&s.wrapping_mul(&t)); + CtOption::new(result, a_is_some & b_is_some) + } + /// Computes 1/`self` mod `2^k`. /// /// Conditions: `self` < 2^k and `self` must be odd - pub fn inv_mod2k(&self, k: usize) -> Self { + pub(crate) fn inv_mod2k(&self, k: usize) -> Self { // This is the same algorithm as in `inv_mod2k_vartime()`, // but made constant-time w.r.t `k` as well. @@ -33,6 +66,83 @@ impl BoxedUint { x } + + /// Computes the multiplicative inverse of `self` mod `modulus`, where `modulus` is odd. + /// Returns `None` if an inverse does not exist. + fn inv_odd_mod(&self, modulus: &Self) -> (Self, Choice) { + self.inv_odd_mod_bounded(modulus, self.bits_precision(), modulus.bits_precision()) + } + + /// Computes the multiplicative inverse of `self` mod `modulus`, where `modulus` is odd. + /// In other words `self^-1 mod modulus`. + /// + /// `bits` and `modulus_bits` are the bounds on the bit size + /// of `self` and `modulus`, respectively. + /// + /// (the inversion speed will be proportional to `bits + modulus_bits`). + /// The second element of the tuple is the truthy value if an inverse exists, + /// otherwise it is a falsy value. + /// + /// **Note:** variable time in `bits` and `modulus_bits`. + /// + /// The algorithm is the same as in GMP 6.2.1's `mpn_sec_invert`. + fn inv_odd_mod_bounded( + &self, + modulus: &Self, + bits: usize, + modulus_bits: usize, + ) -> (Self, Choice) { + debug_assert_eq!(self.bits_precision(), modulus.bits_precision()); + + let bits_precision = self.bits_precision(); + debug_assert!(bool::from(modulus.is_odd())); + + let mut a = self.clone(); + let mut u = Self::one_with_precision(bits_precision); + let mut v = Self::zero_with_precision(bits_precision); + let mut b = modulus.clone(); + + // `bit_size` can be anything >= `self.bits()` + `modulus.bits()`, setting to the minimum. + let bit_size = bits + modulus_bits; + + let mut m1hp = modulus.clone(); + let (m1hp_new, carry) = m1hp.shr_1(); + debug_assert!(bool::from(carry)); + m1hp = m1hp_new.wrapping_add(&Self::one_with_precision(bits_precision)); + + for _ in 0..bit_size { + debug_assert!(bool::from(b.is_odd())); + + let self_odd = a.is_odd(); + + // Set `self -= b` if `self` is odd. + let (new_a, swap) = a.conditional_wrapping_sub(&b, self_odd); + // Set `b += self` if `swap` is true. + b = Self::conditional_select(&b, &b.wrapping_add(&new_a), swap); + // Negate `self` if `swap` is true. + a = new_a.conditional_wrapping_neg(swap); + + let mut new_u = u.clone(); + let mut new_v = v.clone(); + Self::conditional_swap(&mut new_u, &mut new_v, swap); + let (new_u, cy) = new_u.conditional_wrapping_sub(&new_v, self_odd); + let (new_u, cyy) = new_u.conditional_wrapping_add(modulus, cy); + debug_assert!(bool::from(cy.ct_eq(&cyy))); + + let (new_a, overflow) = a.shr_1(); + debug_assert!(!bool::from(overflow)); + let (new_u, cy) = new_u.shr_1(); + let (new_u, cy) = new_u.conditional_wrapping_add(&m1hp, cy); + debug_assert!(!bool::from(cy)); + + a = new_a; + u = new_u; + v = new_v; + } + + debug_assert!(bool::from(a.is_zero())); + (v, b.is_one()) + } } #[cfg(test)] diff --git a/src/uint/boxed/mul.rs b/src/uint/boxed/mul.rs index 03d2c23a8..33f092011 100644 --- a/src/uint/boxed/mul.rs +++ b/src/uint/boxed/mul.rs @@ -27,6 +27,11 @@ impl BoxedUint { ret } + /// Perform wrapping multiplication, wrapping to the width of `self`. + pub fn wrapping_mul(&self, rhs: &Self) -> Self { + self.mul_wide(rhs).shorten(self.bits_precision()) + } + /// Multiply `self` by itself. pub fn square(&self) -> Self { // TODO(tarcieri): more optimized implementation diff --git a/src/uint/boxed/neg.rs b/src/uint/boxed/neg.rs new file mode 100644 index 000000000..62161bfe9 --- /dev/null +++ b/src/uint/boxed/neg.rs @@ -0,0 +1,53 @@ +//! [`BoxedUint`] negation operations. + +use crate::{BoxedUint, Limb, WideWord, Word, Wrapping}; +use core::ops::Neg; +use subtle::Choice; + +impl Neg for Wrapping { + type Output = Self; + + fn neg(self) -> Self::Output { + Self(self.0.wrapping_neg()) + } +} + +impl BoxedUint { + /// Negates based on `choice` by wrapping the integer. + pub(crate) fn conditional_wrapping_neg(&self, choice: Choice) -> BoxedUint { + Self::conditional_select(self, &self.wrapping_neg(), choice) + } + + /// Perform wrapping negation. + pub fn wrapping_neg(&self) -> Self { + let mut ret = vec![Limb::ZERO; self.nlimbs()]; + let mut carry = 1; + + for i in 0..self.nlimbs() { + let r = (!self.limbs[i].0 as WideWord) + carry; + ret[i] = Limb(r as Word); + carry = r >> Limb::BITS; + } + + ret.into() + } +} + +#[cfg(test)] +mod tests { + use crate::BoxedUint; + + #[test] + fn wrapping_neg() { + assert_eq!(BoxedUint::zero().wrapping_neg(), BoxedUint::zero()); + assert_eq!(BoxedUint::max(64).wrapping_neg(), BoxedUint::one()); + // assert_eq!( + // BoxedUint::from(13u64).wrapping_neg(), + // BoxedUint::from(13u64).not().saturating_add(&BoxedUint::one()) + // ); + // assert_eq!( + // BoxedUint::from(42u64).wrapping_neg(), + // BoxedUint::from(42u64).saturating_sub(&BoxedUint::one()).not() + // ); + } +} diff --git a/src/uint/boxed/shl.rs b/src/uint/boxed/shl.rs index 17da1afbb..f09d6a148 100644 --- a/src/uint/boxed/shl.rs +++ b/src/uint/boxed/shl.rs @@ -1,6 +1,8 @@ //! [`BoxedUint`] bitwise left shift operations. use crate::{BoxedUint, Limb, Word}; +use core::ops::{Shl, ShlAssign}; +use subtle::{Choice, ConstantTimeLess}; impl BoxedUint { /// Computes `self << shift` where `0 <= shift < Limb::BITS`, @@ -58,6 +60,61 @@ impl BoxedUint { let (new_lower, _carry) = (Self { limbs }).shl_limb(rem); new_lower } + + /// Computes `self << n`. + /// Returns zero if `n >= Self::BITS`. + pub fn shl(&self, shift: usize) -> Self { + let overflow = !(shift as Word).ct_lt(&(self.bits_precision() as Word)); + let shift = shift % self.bits_precision(); + let log2_bits = (usize::BITS - self.bits_precision().leading_zeros()) as usize; + let mut result = self.clone(); + + for i in 0..log2_bits { + let bit = Choice::from(((shift as Word >> i) & 1) as u8); + result = Self::conditional_select(&result, &result.shl_vartime(1 << i), bit); + } + + Self::conditional_select( + &result, + &Self::zero_with_precision(self.bits_precision()), + overflow, + ) + } +} + +impl Shl for BoxedUint { + type Output = BoxedUint; + + /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect + /// to `self`. + fn shl(self, rhs: usize) -> BoxedUint { + Self::shl(&self, rhs) + } +} + +impl Shl for &BoxedUint { + type Output = BoxedUint; + + /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect + /// to `self`. + fn shl(self, rhs: usize) -> BoxedUint { + self.shl(rhs) + } +} + +impl ShlAssign for BoxedUint { + /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect + /// to `self`. + fn shl_assign(&mut self, rhs: usize) { + // TODO(tarcieri): in-place implementation that avoids clone + *self = self.clone().shl(rhs) + } } #[cfg(test)] diff --git a/src/uint/boxed/shr.rs b/src/uint/boxed/shr.rs index 6bf60a28d..573c0f1ca 100644 --- a/src/uint/boxed/shr.rs +++ b/src/uint/boxed/shr.rs @@ -1,8 +1,35 @@ //! [`BoxedUint`] bitwise right shift operations. -use crate::{BoxedUint, Limb}; +use crate::{limb::HI_BIT, BoxedUint, Limb, Word}; +use core::ops::{Shr, ShrAssign}; +use subtle::{Choice, ConstantTimeLess}; impl BoxedUint { + /// Computes `self >> 1` in constant-time, returning [`CtChoice::TRUE`] if the overflowing bit + /// was set, and [`CtChoice::FALSE`] otherwise. + pub(crate) fn shr_1(&self) -> (Self, Choice) { + let nlimbs = self.nlimbs(); + let mut shifted_bits = vec![0; nlimbs]; + + for i in 0..nlimbs { + shifted_bits[i] = self.limbs[i].0 >> 1; + } + + let mut carry_bits = vec![0; nlimbs]; + for i in 0..nlimbs { + carry_bits[i] = self.limbs[i].0 << HI_BIT; + } + + let mut limbs = vec![Limb(0); nlimbs]; + for i in 0..(nlimbs - 1) { + limbs[i] = Limb(shifted_bits[i] | carry_bits[i + 1]); + } + limbs[nlimbs - 1] = Limb(shifted_bits[nlimbs - 1]); + + debug_assert!(carry_bits[nlimbs - 1] == 0 || carry_bits[nlimbs - 1] == (1 << HI_BIT)); + (limbs.into(), Choice::from((carry_bits[0] >> HI_BIT) as u8)) + } + /// Computes `self >> shift`. /// /// NOTE: this operation is variable time with respect to `shift` *ONLY*. @@ -42,6 +69,57 @@ impl BoxedUint { Self { limbs } } + + /// Computes `self << n`. + /// Returns zero if `n >= Self::BITS`. + pub fn shr(&self, shift: usize) -> Self { + let overflow = !(shift as Word).ct_lt(&(self.bits_precision() as Word)); + let shift = shift % self.bits_precision(); + let log2_bits = (usize::BITS - self.bits_precision().leading_zeros()) as usize; + let mut result = self.clone(); + + for i in 0..log2_bits { + let bit = Choice::from(((shift as Word >> i) & 1) as u8); + result = Self::conditional_select(&result, &result.shr_vartime(1 << i), bit); + } + + Self::conditional_select( + &result, + &Self::zero_with_precision(self.bits_precision()), + overflow, + ) + } +} + +impl Shr for BoxedUint { + type Output = BoxedUint; + + /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect + /// to `self`. + fn shr(self, rhs: usize) -> BoxedUint { + Self::shr(&self, rhs) + } +} + +impl Shr for &BoxedUint { + type Output = BoxedUint; + + /// NOTE: this operation is variable time with respect to `rhs` *ONLY*. + /// + /// When used with a fixed `rhs`, this function is constant-time with respect + /// to `self`. + fn shr(self, rhs: usize) -> BoxedUint { + self.shr(rhs) + } +} + +impl ShrAssign for BoxedUint { + fn shr_assign(&mut self, rhs: usize) { + // TODO(tarcieri): in-place implementation that avoids clone + *self = self.clone().shr(rhs); + } } #[cfg(test)] diff --git a/src/uint/boxed/sub.rs b/src/uint/boxed/sub.rs index 0a9520ac4..be895486c 100644 --- a/src/uint/boxed/sub.rs +++ b/src/uint/boxed/sub.rs @@ -1,7 +1,7 @@ //! [`BoxedUint`] subtraction operations. use crate::{BoxedUint, CheckedSub, Limb, Zero}; -use subtle::CtOption; +use subtle::{Choice, CtOption}; impl BoxedUint { /// Computes `a + b + carry`, returning the result along with the new carry. @@ -14,6 +14,18 @@ impl BoxedUint { pub fn wrapping_sub(&self, rhs: &Self) -> Self { self.sbb(rhs, Limb::ZERO).0 } + + /// Perform wrapping subtraction, returning the truthy value as the second element of the tuple + /// if an underflow has occurred. + pub(crate) fn conditional_wrapping_sub(&self, rhs: &Self, choice: Choice) -> (Self, Choice) { + let actual_rhs = Self::conditional_select( + &Self::zero_with_precision(rhs.bits_precision()), + rhs, + choice, + ); + let (res, borrow) = self.sbb(&actual_rhs, Limb::ZERO); + (res, Choice::from((borrow.0 & 1) as u8)) + } } impl CheckedSub<&BoxedUint> for BoxedUint { diff --git a/tests/boxed_uint_proptests.rs b/tests/boxed_uint_proptests.rs index 190757d74..b73973b11 100644 --- a/tests/boxed_uint_proptests.rs +++ b/tests/boxed_uint_proptests.rs @@ -3,7 +3,7 @@ #![cfg(feature = "alloc")] use crypto_bigint::{BoxedUint, CheckedAdd, Limb, NonZero}; -use num_bigint::BigUint; +use num_bigint::{BigUint, ModInverse}; use proptest::prelude::*; fn to_biguint(uint: &BoxedUint) -> BigUint { @@ -61,6 +61,28 @@ proptest! { } } + #[test] + fn mod_inv((mut a, mut b) in uint_pair()) { + if a.is_zero().into() { + a = BoxedUint::one_with_precision(b.bits_precision()); + } + + if b.is_zero().into() { + b = BoxedUint::one_with_precision(a.bits_precision()); + } + + let a_bi = to_biguint(&a); + let b_bi = to_biguint(&b); + let expected = a_bi.mod_inverse(b_bi); + let actual = Option::from(a.inv_mod(&b)); + + match (expected, actual) { + (Some(exp), Some(act)) => prop_assert_eq!(exp, to_biguint(&act).into()), + (None, None) => (), + (_, _) => panic!("disagreement on if modular inverse exists") + } + } + #[test] fn mul_wide(a in uint(), b in uint()) { let a_bi = to_biguint(&a); diff --git a/tests/uint_proptests.rs b/tests/uint_proptests.rs index cb4760b58..88a7ffc1b 100644 --- a/tests/uint_proptests.rs +++ b/tests/uint_proptests.rs @@ -53,7 +53,7 @@ proptest! { fn shl_vartime(a in uint(), shift in any::()) { let a_bi = to_biguint(&a); - let expected = to_uint(a_bi << shift); + let expected = to_uint(a_bi << shift.into()); let actual = a.shl_vartime(shift as usize); assert_eq!(expected, actual);