From 3819a4df050150db4028be50e7b599806378d4a8 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 14 Dec 2023 19:35:24 -0700 Subject: [PATCH] Impl `Wrapping*` traits from `num-traits` (#425) Until now we have used traits defined in this crate, primarily because many of these traits are predicates that return `bool` and in constant-time code we want those to return `Choice`/`CtOption`. `Wrapping*` is an example of the sort of trait we'd want to incorporate unchanged, and there are potentially others, so this adds `num-traits` as a hard dependency (previously our only hard dependency was `subtle`). Note that `num-traits` itself has no transitive dependencies (or rather, they're optional but not enabled). The `Wrapping*` traits have bounds on the corresponding op traits being impl'd with `Self` operands e.g. `WrappingAdd: Add` so this PR also adds impls of those traits. We've previously avoided these as in `std` they panic on overflow/underflow in debug builds and silently wrap in release builds. This PR always panics. This required some changes to the `Mul` impls which were conditional on `ConcatMixed` and implicitly widened. To accomodate impls which are always available and require no bounds (in order to allow us to impl `WideningMul`), and renames the following: - `Uint::mul` -> `Uint::widening_mul` - `Uint::mul_wide` -> `Uint::split_mul` --- Cargo.toml | 2 +- src/modular.rs | 6 +- src/modular/dyn_residue.rs | 2 +- src/modular/inv.rs | 2 +- src/modular/mul.rs | 2 +- src/modular/residue.rs | 2 +- src/traits.rs | 10 +++ src/uint/add.rs | 33 ++++++++-- src/uint/boxed/add.rs | 42 ++++++++++-- src/uint/boxed/mul.rs | 63 ++++++++++++++---- src/uint/boxed/mul_mod.rs | 2 +- src/uint/boxed/neg.rs | 8 ++- src/uint/boxed/shl.rs | 84 +++++++++++++++++------ src/uint/boxed/shr.rs | 96 +++++++++++++++++++-------- src/uint/boxed/sub.rs | 35 +++++++++- src/uint/concat.rs | 2 +- src/uint/div.rs | 6 +- src/uint/mul.rs | 132 ++++++++++++++++++------------------- src/uint/mul_mod.rs | 4 +- src/uint/neg.rs | 8 ++- src/uint/shl.rs | 62 ++++++++++++----- src/uint/shr.rs | 62 ++++++++++++----- src/uint/sub.rs | 25 ++++++- tests/uint_proptests.rs | 2 +- 24 files changed, 500 insertions(+), 192 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3b1a54ea..1526e604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ subtle = { version = "2.5", default-features = false } # optional dependencies der = { version = "0.7", optional = true, default-features = false } generic-array = { version = "0.14", optional = true } +num-traits = { version = "0.2", default-features = false } rand_core = { version = "0.6.4", optional = true } rlp = { version = "0.5", optional = true, default-features = false } serdect = { version = "0.2", optional = true, default-features = false } @@ -33,7 +34,6 @@ criterion = { version = "0.5", features = ["html_reports"] } hex-literal = "0.4" num-bigint = { package = "num-bigint-dig", version = "0.8" } num-integer = "0.1" -num-traits = "0.2" proptest = "1" rand_core = { version = "0.6", features = ["std"] } rand_chacha = "0.3" diff --git a/src/modular.rs b/src/modular.rs index eb2854be..0fc3ff35 100644 --- a/src/modular.rs +++ b/src/modular.rs @@ -135,7 +135,7 @@ mod tests { // Reducing xR should return x let x = U256::from_be_hex("44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56"); - let product = x.mul_wide(&Modulus2::R); + let product = x.split_mul(&Modulus2::R); assert_eq!( montgomery_reduction::<{ Modulus2::LIMBS }>( &product, @@ -151,10 +151,10 @@ mod tests { // Reducing xR^2 should return xR let x = U256::from_be_hex("44acf6b7e36c1342c2c5897204fe09504e1e2efb1a900377dbc4e7a6a133ec56"); - let product = x.mul_wide(&Modulus2::R2); + let product = x.split_mul(&Modulus2::R2); // Computing xR mod modulus without Montgomery reduction - let (lo, hi) = x.mul_wide(&Modulus2::R); + let (lo, hi) = x.split_mul(&Modulus2::R); let c = hi.concat(&lo); let red = c.rem(&NonZero::new(U256::ZERO.concat(&Modulus2::MODULUS)).unwrap()); let (hi, lo) = red.split(); diff --git a/src/modular/dyn_residue.rs b/src/modular/dyn_residue.rs index fadde5b7..324e7a7c 100644 --- a/src/modular/dyn_residue.rs +++ b/src/modular/dyn_residue.rs @@ -119,7 +119,7 @@ pub struct DynResidue { impl DynResidue { /// Instantiates a new `Residue` that represents this `integer` mod `MOD`. pub const fn new(integer: &Uint, residue_params: DynResidueParams) -> Self { - let product = integer.mul_wide(&residue_params.r2); + let product = integer.split_mul(&residue_params.r2); let montgomery_form = montgomery_reduction( &product, &residue_params.modulus, diff --git a/src/modular/inv.rs b/src/modular/inv.rs index e4daff19..c57438f9 100644 --- a/src/modular/inv.rs +++ b/src/modular/inv.rs @@ -8,7 +8,7 @@ pub const fn inv_montgomery_form( ) -> (Uint, ConstChoice) { let (inverse, is_some) = x.inv_odd_mod(modulus); ( - montgomery_reduction(&inverse.mul_wide(r3), modulus, mod_neg_inv), + montgomery_reduction(&inverse.split_mul(r3), modulus, mod_neg_inv), is_some, ) } diff --git a/src/modular/mul.rs b/src/modular/mul.rs index b84ceb5c..4cedd770 100644 --- a/src/modular/mul.rs +++ b/src/modular/mul.rs @@ -8,7 +8,7 @@ pub(crate) const fn mul_montgomery_form( modulus: &Uint, mod_neg_inv: Limb, ) -> Uint { - let product = a.mul_wide(b); + let product = a.split_mul(b); montgomery_reduction::(&product, modulus, mod_neg_inv) } diff --git a/src/modular/residue.rs b/src/modular/residue.rs index e58348f4..8d2092d5 100644 --- a/src/modular/residue.rs +++ b/src/modular/residue.rs @@ -84,7 +84,7 @@ impl, const LIMBS: usize> Residue { /// Internal helper function to generate a residue; this lets us cleanly wrap the constructors. const fn generate_residue(integer: &Uint) -> Self { - let product = integer.mul_wide(&MOD::R2); + let product = integer.split_mul(&MOD::R2); let montgomery_form = montgomery_reduction::(&product, &MOD::MODULUS.0, MOD::MOD_NEG_INV); diff --git a/src/traits.rs b/src/traits.rs index 042cd6e4..c012144e 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,5 +1,9 @@ //! Traits provided by this crate +pub use num_traits::{ + WrappingAdd, WrappingMul, WrappingNeg, WrappingShl, WrappingShr, WrappingSub, +}; + use crate::{Limb, NonZero}; use core::fmt::Debug; use core::ops::{ @@ -71,6 +75,12 @@ pub trait Integer: + ShrAssign + SubMod + Sync + + WrappingAdd + + WrappingSub + + WrappingMul + + WrappingNeg + + WrappingShl + + WrappingShr + Zero { /// The value `1`. diff --git a/src/uint/add.rs b/src/uint/add.rs index 402c48a6..0861993b 100644 --- a/src/uint/add.rs +++ b/src/uint/add.rs @@ -1,6 +1,6 @@ //! [`Uint`] addition operations. -use crate::{Checked, CheckedAdd, ConstChoice, Limb, Uint, Wrapping, Zero}; +use crate::{Checked, CheckedAdd, ConstChoice, Limb, Uint, Wrapping, WrappingAdd, Zero}; use core::ops::{Add, AddAssign}; use subtle::CtOption; @@ -45,12 +45,20 @@ impl Uint { } } -impl CheckedAdd<&Uint> for Uint { +impl Add for Uint { type Output = Self; - fn checked_add(&self, rhs: &Self) -> CtOption { - let (result, carry) = self.adc(rhs, Limb::ZERO); - CtOption::new(result, carry.is_zero()) + fn add(self, rhs: Self) -> Self { + self.add(&rhs) + } +} + +impl Add<&Uint> for Uint { + type Output = Self; + + fn add(self, rhs: &Self) -> Self { + self.checked_add(rhs) + .expect("attempted to add with overflow") } } @@ -154,6 +162,21 @@ impl AddAssign<&Checked>> for Checked CheckedAdd<&Uint> for Uint { + type Output = Self; + + fn checked_add(&self, rhs: &Self) -> CtOption { + let (result, carry) = self.adc(rhs, Limb::ZERO); + CtOption::new(result, carry.is_zero()) + } +} + +impl WrappingAdd for Uint { + fn wrapping_add(&self, v: &Self) -> Self { + self.wrapping_add(v) + } +} + #[cfg(test)] mod tests { use crate::{CheckedAdd, Limb, U128}; diff --git a/src/uint/boxed/add.rs b/src/uint/boxed/add.rs index cd5cb14f..28f145c3 100644 --- a/src/uint/boxed/add.rs +++ b/src/uint/boxed/add.rs @@ -1,8 +1,7 @@ //! [`BoxedUint`] addition operations. +use crate::{BoxedUint, CheckedAdd, Limb, Wrapping, WrappingAdd, Zero}; use core::ops::{Add, AddAssign}; - -use crate::{BoxedUint, CheckedAdd, Limb, Wrapping, Zero}; use subtle::{Choice, ConditionallySelectable, CtOption}; impl BoxedUint { @@ -51,12 +50,28 @@ impl BoxedUint { } } -impl CheckedAdd<&BoxedUint> for BoxedUint { +impl Add for BoxedUint { type Output = Self; - fn checked_add(&self, rhs: &Self) -> CtOption { - let (result, carry) = self.adc(rhs, Limb::ZERO); - CtOption::new(result, carry.is_zero()) + fn add(self, rhs: Self) -> Self { + self.add(&rhs) + } +} + +impl Add<&BoxedUint> for BoxedUint { + type Output = Self; + + fn add(self, rhs: &Self) -> Self { + Add::add(&self, rhs) + } +} + +impl Add<&BoxedUint> for &BoxedUint { + type Output = BoxedUint; + + fn add(self, rhs: &BoxedUint) -> BoxedUint { + self.checked_add(rhs) + .expect("attempted to add with overflow") } } @@ -104,6 +119,21 @@ impl AddAssign<&Wrapping> for Wrapping { } } +impl CheckedAdd<&BoxedUint> for BoxedUint { + type Output = Self; + + fn checked_add(&self, rhs: &Self) -> CtOption { + let (result, carry) = self.adc(rhs, Limb::ZERO); + CtOption::new(result, carry.is_zero()) + } +} + +impl WrappingAdd for BoxedUint { + fn wrapping_add(&self, v: &Self) -> Self { + self.wrapping_add(v) + } +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { diff --git a/src/uint/boxed/mul.rs b/src/uint/boxed/mul.rs index 1a747e53..c6411674 100644 --- a/src/uint/boxed/mul.rs +++ b/src/uint/boxed/mul.rs @@ -1,6 +1,8 @@ //! [`BoxedUint`] multiplication operations. -use crate::{uint::mul::mul_limbs, BoxedUint, CheckedMul, Limb, WideningMul, Wrapping, Zero}; +use crate::{ + uint::mul::mul_limbs, BoxedUint, CheckedMul, Limb, WideningMul, Wrapping, WrappingMul, Zero, +}; use core::ops::{Mul, MulAssign}; use subtle::{Choice, CtOption}; @@ -51,21 +53,36 @@ impl CheckedMul<&BoxedUint> for BoxedUint { } } -impl WideningMul for BoxedUint { - type Output = Self; +impl Mul for BoxedUint { + type Output = BoxedUint; - #[inline] - fn widening_mul(&self, rhs: BoxedUint) -> Self { - self.mul(&rhs) + fn mul(self, rhs: BoxedUint) -> Self { + BoxedUint::mul(&self, &rhs) } } -impl WideningMul<&BoxedUint> for BoxedUint { - type Output = Self; +impl Mul<&BoxedUint> for BoxedUint { + type Output = BoxedUint; - #[inline] - fn widening_mul(&self, rhs: &BoxedUint) -> Self { - self.mul(rhs) + fn mul(self, rhs: &BoxedUint) -> Self { + BoxedUint::mul(&self, rhs) + } +} + +impl Mul for &BoxedUint { + type Output = BoxedUint; + + fn mul(self, rhs: BoxedUint) -> Self::Output { + BoxedUint::mul(self, &rhs) + } +} + +impl Mul<&BoxedUint> for &BoxedUint { + type Output = BoxedUint; + + fn mul(self, rhs: &BoxedUint) -> Self::Output { + self.checked_mul(rhs) + .expect("attempted to multiply with overflow") } } @@ -113,6 +130,30 @@ impl MulAssign<&Wrapping> for Wrapping { } } +impl WideningMul for BoxedUint { + type Output = Self; + + #[inline] + fn widening_mul(&self, rhs: BoxedUint) -> Self { + self.mul(&rhs) + } +} + +impl WideningMul<&BoxedUint> for BoxedUint { + type Output = Self; + + #[inline] + fn widening_mul(&self, rhs: &BoxedUint) -> Self { + self.mul(rhs) + } +} + +impl WrappingMul for BoxedUint { + fn wrapping_mul(&self, v: &Self) -> Self { + self.wrapping_mul(v) + } +} + #[cfg(test)] mod tests { use crate::BoxedUint; diff --git a/src/uint/boxed/mul_mod.rs b/src/uint/boxed/mul_mod.rs index 32d3cf76..5c90631b 100644 --- a/src/uint/boxed/mul_mod.rs +++ b/src/uint/boxed/mul_mod.rs @@ -132,7 +132,7 @@ mod tests { assert!(c < **p, "not reduced: {} >= {} ", c, p); let expected = { - let (lo, hi) = a.mul_wide(&b); + let (lo, hi) = a.split_mul(&b); let mut prod = Uint::<{ 2 * $size }>::ZERO; prod.limbs[..$size].clone_from_slice(&lo.limbs); prod.limbs[$size..].clone_from_slice(&hi.limbs); diff --git a/src/uint/boxed/neg.rs b/src/uint/boxed/neg.rs index 62161bfe..10991e0f 100644 --- a/src/uint/boxed/neg.rs +++ b/src/uint/boxed/neg.rs @@ -1,6 +1,6 @@ //! [`BoxedUint`] negation operations. -use crate::{BoxedUint, Limb, WideWord, Word, Wrapping}; +use crate::{BoxedUint, Limb, WideWord, Word, Wrapping, WrappingNeg}; use core::ops::Neg; use subtle::Choice; @@ -33,6 +33,12 @@ impl BoxedUint { } } +impl WrappingNeg for BoxedUint { + fn wrapping_neg(&self) -> Self { + self.wrapping_neg() + } +} + #[cfg(test)] mod tests { use crate::BoxedUint; diff --git a/src/uint/boxed/shl.rs b/src/uint/boxed/shl.rs index 0aea2642..081b5559 100644 --- a/src/uint/boxed/shl.rs +++ b/src/uint/boxed/shl.rs @@ -1,10 +1,27 @@ //! [`BoxedUint`] bitwise left shift operations. -use crate::{BoxedUint, Limb, Zero}; +use crate::{BoxedUint, Limb, WrappingShl, Zero}; use core::ops::{Shl, ShlAssign}; use subtle::{Choice, ConstantTimeLess}; impl BoxedUint { + /// Computes `self << shift`. + /// + /// Panics if `shift >= Self::BITS`. + pub fn shl(&self, shift: u32) -> BoxedUint { + let (result, overflow) = self.overflowing_shl(shift); + assert!(!bool::from(overflow), "attempt to shift left with overflow"); + result + } + + /// Computes `self <<= shift`. + /// + /// Panics if `shift >= Self::BITS`. + pub fn shl_assign(&mut self, shift: u32) { + let overflow = self.overflowing_shl_assign(shift); + assert!(!bool::from(overflow), "attempt to shift left with overflow"); + } + /// Computes `self << shift`. /// /// Returns a zero and a truthy `Choice` if `shift >= self.bits_precision()`, @@ -42,6 +59,20 @@ impl BoxedUint { overflow } + /// Computes `self << shift` in a panic-free manner, masking off bits of `shift` which would cause the shift to + /// exceed the type's width. + pub fn wrapping_shl(&self, shift: u32) -> Self { + self.overflowing_shl(shift).0 + } + + /// Computes `self << shift` in variable-time in a panic-free manner, masking off bits of `shift` which would cause + /// the shift to exceed the type's width. + pub fn wrapping_shl_vartime(&self, shift: u32) -> Self { + let mut result = Self::zero_with_precision(self.bits_precision()); + let _ = self.shl_vartime_into(&mut result, shift); + result + } + /// Computes `self << shift` and writes the result into `dest`. /// Returns `None` if `shift >= self.bits_precision()`. /// @@ -113,28 +144,41 @@ impl BoxedUint { } } -impl Shl for BoxedUint { - type Output = BoxedUint; - - fn shl(self, shift: u32) -> BoxedUint { - <&BoxedUint as Shl>::shl(&self, shift) - } +macro_rules! impl_shl { + ($($shift:ty),+) => { + $( + impl Shl<$shift> for BoxedUint { + type Output = BoxedUint; + + #[inline] + fn shl(self, shift: $shift) -> BoxedUint { + <&Self>::shl(&self, shift) + } + } + + impl Shl<$shift> for &BoxedUint { + type Output = BoxedUint; + + #[inline] + fn shl(self, shift: $shift) -> BoxedUint { + BoxedUint::shl(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShlAssign<$shift> for BoxedUint { + fn shl_assign(&mut self, shift: $shift) { + BoxedUint::shl_assign(self, u32::try_from(shift).expect("invalid shift")) + } + } + )+ + }; } -impl Shl for &BoxedUint { - type Output = BoxedUint; - - fn shl(self, shift: u32) -> BoxedUint { - let (result, overflow) = self.overflowing_shl(shift); - assert!(!bool::from(overflow), "attempt to shift left with overflow"); - result - } -} +impl_shl!(i32, u32, usize); -impl ShlAssign for BoxedUint { - fn shl_assign(&mut self, shift: u32) { - let overflow = self.overflowing_shl_assign(shift); - assert!(!bool::from(overflow), "attempt to shift left with overflow"); +impl WrappingShl for BoxedUint { + fn wrapping_shl(&self, shift: u32) -> BoxedUint { + self.wrapping_shl(shift) } } diff --git a/src/uint/boxed/shr.rs b/src/uint/boxed/shr.rs index ffd293dd..01ad01b2 100644 --- a/src/uint/boxed/shr.rs +++ b/src/uint/boxed/shr.rs @@ -1,10 +1,33 @@ //! [`BoxedUint`] bitwise right shift operations. -use crate::{BoxedUint, Limb, Zero}; +use crate::{BoxedUint, Limb, WrappingShr, Zero}; use core::ops::{Shr, ShrAssign}; use subtle::{Choice, ConstantTimeLess}; impl BoxedUint { + /// Computes `self >> shift`. + /// + /// Panics if `shift >= Self::BITS`. + pub fn shr(&self, shift: u32) -> BoxedUint { + let (result, overflow) = self.overflowing_shr(shift); + assert!( + !bool::from(overflow), + "attempt to shift right with overflow" + ); + result + } + + /// Computes `self >>= shift`. + /// + /// Panics if `shift >= Self::BITS`. + pub fn shr_assign(&mut self, shift: u32) { + let overflow = self.overflowing_shr_assign(shift); + assert!( + !bool::from(overflow), + "attempt to shift right with overflow" + ); + } + /// Computes `self >> shift`. /// /// Returns a zero and a truthy `Choice` if `shift >= self.bits_precision()`, @@ -42,6 +65,20 @@ impl BoxedUint { overflow } + /// Computes `self >> shift` in a panic-free manner, masking off bits of `shift` which would cause the shift to + /// exceed the type's width. + pub fn wrapping_shr(&self, shift: u32) -> Self { + self.overflowing_shr(shift).0 + } + + /// Computes `self >> shift` in variable-time in a panic-free manner, masking off bits of `shift` which would cause + /// the shift to exceed the type's width. + pub fn wrapping_shr_vartime(&self, shift: u32) -> Self { + let mut result = Self::zero_with_precision(self.bits_precision()); + let _ = self.shr_vartime_into(&mut result, shift); + result + } + /// Computes `self >> shift`. /// Returns `None` if `shift >= self.bits_precision()`. /// @@ -117,34 +154,41 @@ impl BoxedUint { } } -impl Shr for BoxedUint { - type Output = BoxedUint; - - fn shr(self, shift: u32) -> BoxedUint { - <&BoxedUint as Shr>::shr(&self, shift) - } +macro_rules! impl_shr { + ($($shift:ty),+) => { + $( + impl Shr<$shift> for BoxedUint { + type Output = BoxedUint; + + #[inline] + fn shr(self, shift: $shift) -> BoxedUint { + <&Self>::shr(&self, shift) + } + } + + impl Shr<$shift> for &BoxedUint { + type Output = BoxedUint; + + #[inline] + fn shr(self, shift: $shift) -> BoxedUint { + BoxedUint::shr(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShrAssign<$shift> for BoxedUint { + fn shr_assign(&mut self, shift: $shift) { + BoxedUint::shr_assign(self, u32::try_from(shift).expect("invalid shift")) + } + } + )+ + }; } -impl Shr for &BoxedUint { - type Output = BoxedUint; - - fn shr(self, shift: u32) -> BoxedUint { - let (result, overflow) = self.overflowing_shr(shift); - assert!( - !bool::from(overflow), - "attempt to shift right with overflow" - ); - result - } -} +impl_shr!(i32, u32, usize); -impl ShrAssign for BoxedUint { - fn shr_assign(&mut self, shift: u32) { - let overflow = self.overflowing_shr_assign(shift); - assert!( - !bool::from(overflow), - "attempt to shift right with overflow" - ); +impl WrappingShr for BoxedUint { + fn wrapping_shr(&self, shift: u32) -> BoxedUint { + self.wrapping_shr(shift) } } diff --git a/src/uint/boxed/sub.rs b/src/uint/boxed/sub.rs index 12f13c29..dba042a4 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, Wrapping, Zero}; -use core::ops::SubAssign; +use crate::{BoxedUint, CheckedSub, Limb, Wrapping, WrappingSub, Zero}; +use core::ops::{Sub, SubAssign}; use subtle::{Choice, ConditionallySelectable, CtOption}; impl BoxedUint { @@ -59,6 +59,31 @@ impl CheckedSub<&BoxedUint> for BoxedUint { } } +impl Sub for BoxedUint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + self.sub(&rhs) + } +} + +impl Sub<&BoxedUint> for BoxedUint { + type Output = Self; + + fn sub(self, rhs: &Self) -> Self { + Sub::sub(&self, rhs) + } +} + +impl Sub<&BoxedUint> for &BoxedUint { + type Output = BoxedUint; + + fn sub(self, rhs: &BoxedUint) -> BoxedUint { + self.checked_sub(rhs) + .expect("attempted to subtract with underflow") + } +} + impl SubAssign> for Wrapping { fn sub_assign(&mut self, other: Wrapping) { self.0.sbb_assign(&other.0, Limb::ZERO); @@ -71,6 +96,12 @@ impl SubAssign<&Wrapping> for Wrapping { } } +impl WrappingSub for BoxedUint { + fn wrapping_sub(&self, v: &Self) -> Self { + self.wrapping_sub(v) + } +} + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { diff --git a/src/uint/concat.rs b/src/uint/concat.rs index dde5242a..3cc38d4b 100644 --- a/src/uint/concat.rs +++ b/src/uint/concat.rs @@ -61,7 +61,7 @@ mod tests { #[test] fn convert() { - let res: U128 = U64::ONE.mul_wide(&U64::ONE).into(); + let res: U128 = U64::ONE.split_mul(&U64::ONE).into(); assert_eq!(res, U128::ONE); let res: U128 = U64::ONE.square_wide().into(); diff --git a/src/uint/div.rs b/src/uint/div.rs index 2ceea32c..dbfbcc00 100644 --- a/src/uint/div.rs +++ b/src/uint/div.rs @@ -28,7 +28,7 @@ impl Uint { let mut rem = *self; let mut quo = Self::ZERO; // If there is overflow, it means `mb == 0`, so `rhs == 0`. - let (mut c, _overflow) = rhs.0.overflowing_shl(Self::BITS - mb); + let mut c = rhs.0.wrapping_shl(Self::BITS - mb); let mut i = Self::BITS; let mut done = ConstChoice::FALSE; @@ -64,7 +64,7 @@ impl Uint { let mut rem = *self; let mut quo = Self::ZERO; // If there is overflow, it means `mb == 0`, so `rhs == 0`. - let (mut c, _overflow) = rhs.0.overflowing_shl_vartime(bd); + let mut c = rhs.0.wrapping_shl_vartime(bd); loop { let (mut r, borrow) = rem.sbb(&c, Limb::ZERO); @@ -92,7 +92,7 @@ impl Uint { let mb = rhs.0.bits_vartime(); let mut bd = Self::BITS - mb; let mut rem = *self; - let (mut c, _overflow) = rhs.0.overflowing_shl_vartime(bd); + let mut c = rhs.0.wrapping_shl_vartime(bd); loop { let (r, borrow) = rem.sbb(&c, Limb::ZERO); diff --git a/src/uint/mul.rs b/src/uint/mul.rs index 5347ead9..c1c1033a 100644 --- a/src/uint/mul.rs +++ b/src/uint/mul.rs @@ -4,7 +4,7 @@ use crate::{ Checked, CheckedMul, Concat, ConcatMixed, Limb, Uint, WideWord, WideningMul, Word, Wrapping, - Zero, + WrappingMul, Zero, }; use core::ops::{Mul, MulAssign}; use subtle::CtOption; @@ -52,38 +52,37 @@ macro_rules! impl_schoolbook_multiplication { impl Uint { /// Multiply `self` by `rhs`, returning a concatenated "wide" result. - pub fn mul( + pub fn widening_mul( &self, rhs: &Uint, ) -> as ConcatMixed>::MixedOutput where Uint: ConcatMixed, { - let (lo, hi) = self.mul_wide(rhs); + let (lo, hi) = self.split_mul(rhs); hi.concat_mixed(&lo) } - /// Compute "wide" multiplication, with a product twice the size of the input. - /// - /// Returns a tuple containing the `(lo, hi)` components of the product. - pub const fn mul_wide(&self, rhs: &Uint) -> (Self, Uint) { + /// Compute "wide" multiplication as a 2-tuple containing the `(lo, hi)` components of the product, whose sizes + /// correspond to the sizes of the operands. + pub const fn split_mul(&self, rhs: &Uint) -> (Self, Uint) { let mut lo = Self::ZERO; let mut hi = Uint::::ZERO; impl_schoolbook_multiplication!(&self.limbs, &rhs.limbs, lo.limbs, hi.limbs); (lo, hi) } + /// Perform wrapping multiplication, discarding overflow. + pub const fn wrapping_mul(&self, rhs: &Uint) -> Self { + self.split_mul(rhs).0 + } + /// Perform saturating multiplication, returning `MAX` on overflow. pub const fn saturating_mul(&self, rhs: &Uint) -> Self { - let (res, overflow) = self.mul_wide(rhs); + let (res, overflow) = self.split_mul(rhs); Self::select(&res, &Self::MAX, overflow.is_nonzero()) } - /// Perform wrapping multiplication, discarding overflow. - pub const fn wrapping_mul(&self, rhs: &Uint) -> Self { - self.mul_wide(rhs).0 - } - /// Square self, returning a concatenated "wide" result. pub fn square(&self) -> ::Output where @@ -169,7 +168,7 @@ impl Uint { } impl CheckedMul> for Uint { - type Output = Self; + type Output = Uint; #[inline] fn checked_mul(&self, rhs: Uint) -> CtOption { @@ -178,36 +177,45 @@ impl CheckedMul> for Uint< } impl CheckedMul<&Uint> for Uint { - type Output = Self; + type Output = Uint; #[inline] fn checked_mul(&self, rhs: &Uint) -> CtOption { - let (lo, hi) = self.mul_wide(rhs); + let (lo, hi) = self.split_mul(rhs); CtOption::new(lo, hi.is_zero()) } } -impl WideningMul> for Uint -where - Uint: ConcatMixed, -{ - type Output = as ConcatMixed>::MixedOutput; +impl Mul> for Uint { + type Output = Uint; - #[inline] - fn widening_mul(&self, rhs: Uint) -> Self::Output { + fn mul(self, rhs: Uint) -> Self { self.mul(&rhs) } } -impl WideningMul<&Uint> for Uint -where - Uint: ConcatMixed, -{ - type Output = as ConcatMixed>::MixedOutput; +impl Mul<&Uint> for Uint { + type Output = Uint; - #[inline] - fn widening_mul(&self, rhs: &Uint) -> Self::Output { - self.mul(rhs) + fn mul(self, rhs: &Uint) -> Self { + (&self).mul(rhs) + } +} + +impl Mul> for &Uint { + type Output = Uint; + + fn mul(self, rhs: Uint) -> Self::Output { + self.mul(&rhs) + } +} + +impl Mul<&Uint> for &Uint { + type Output = Uint; + + fn mul(self, rhs: &Uint) -> Self::Output { + self.checked_mul(rhs) + .expect("attempted to multiply with overflow") } } @@ -317,47 +325,33 @@ impl MulAssign<&Checked>> } } -impl Mul> for Uint +impl WideningMul> for Uint where - Uint: ConcatMixed>, + Uint: ConcatMixed, { type Output = as ConcatMixed>::MixedOutput; - fn mul(self, other: Uint) -> Self::Output { - Uint::mul(&self, &other) + #[inline] + fn widening_mul(&self, rhs: Uint) -> Self::Output { + self.widening_mul(&rhs) } } -impl Mul<&Uint> for Uint +impl WideningMul<&Uint> for Uint where - Uint: ConcatMixed>, + Uint: ConcatMixed, { type Output = as ConcatMixed>::MixedOutput; - fn mul(self, other: &Uint) -> Self::Output { - Uint::mul(&self, other) - } -} - -impl Mul> for &Uint -where - Uint: ConcatMixed>, -{ - type Output = as ConcatMixed>>::MixedOutput; - - fn mul(self, other: Uint) -> Self::Output { - Uint::mul(self, &other) + #[inline] + fn widening_mul(&self, rhs: &Uint) -> Self::Output { + self.widening_mul(rhs) } } -impl Mul<&Uint> for &Uint -where - Uint: ConcatMixed>, -{ - type Output = as ConcatMixed>>::MixedOutput; - - fn mul(self, other: &Uint) -> Self::Output { - Uint::mul(self, other) +impl WrappingMul for Uint { + fn wrapping_mul(&self, v: &Self) -> Self { + self.wrapping_mul(v) } } @@ -375,10 +369,10 @@ mod tests { #[test] fn mul_wide_zero_and_one() { - assert_eq!(U64::ZERO.mul_wide(&U64::ZERO), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ZERO.mul_wide(&U64::ONE), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ONE.mul_wide(&U64::ZERO), (U64::ZERO, U64::ZERO)); - assert_eq!(U64::ONE.mul_wide(&U64::ONE), (U64::ONE, U64::ZERO)); + assert_eq!(U64::ZERO.split_mul(&U64::ZERO), (U64::ZERO, U64::ZERO)); + assert_eq!(U64::ZERO.split_mul(&U64::ONE), (U64::ZERO, U64::ZERO)); + assert_eq!(U64::ONE.split_mul(&U64::ZERO), (U64::ZERO, U64::ZERO)); + assert_eq!(U64::ONE.split_mul(&U64::ONE), (U64::ONE, U64::ZERO)); } #[test] @@ -387,7 +381,7 @@ mod tests { for &a_int in primes { for &b_int in primes { - let (lo, hi) = U64::from_u32(a_int).mul_wide(&U64::from_u32(b_int)); + let (lo, hi) = U64::from_u32(a_int).split_mul(&U64::from_u32(b_int)); let expected = U64::from_u64(a_int as u64 * b_int as u64); assert_eq!(lo, expected); assert!(bool::from(hi.is_zero())); @@ -397,14 +391,14 @@ mod tests { #[test] fn mul_concat_even() { - assert_eq!(U64::ZERO * U64::MAX, U128::ZERO); - assert_eq!(U64::MAX * U64::ZERO, U128::ZERO); + assert_eq!(U64::ZERO.widening_mul(&U64::MAX), U128::ZERO); + assert_eq!(U64::MAX.widening_mul(&U64::ZERO), U128::ZERO); assert_eq!( - U64::MAX * U64::MAX, + U64::MAX.widening_mul(&U64::MAX), U128::from_u128(0xfffffffffffffffe_0000000000000001) ); assert_eq!( - U64::ONE * U64::MAX, + U64::ONE.widening_mul(&U64::MAX), U128::from_u128(0x0000000000000000_ffffffffffffffff) ); } @@ -413,8 +407,8 @@ mod tests { fn mul_concat_mixed() { let a = U64::from_u64(0x0011223344556677); let b = U128::from_u128(0x8899aabbccddeeff_8899aabbccddeeff); - assert_eq!(a * b, U192::from(&a).saturating_mul(&b)); - assert_eq!(b * a, U192::from(&b).saturating_mul(&a)); + assert_eq!(a.widening_mul(&b), U192::from(&a).saturating_mul(&b)); + assert_eq!(b.widening_mul(&a), U192::from(&b).saturating_mul(&a)); } #[test] diff --git a/src/uint/mul_mod.rs b/src/uint/mul_mod.rs index a2cac75e..b6f38a28 100644 --- a/src/uint/mul_mod.rs +++ b/src/uint/mul_mod.rs @@ -43,7 +43,7 @@ impl Uint { return Self::from_word(reduced as Word); } - let (lo, hi) = self.mul_wide(rhs); + let (lo, hi) = self.split_mul(rhs); // Now use Algorithm 14.47 for the reduction let (lo, carry) = mac_by_limb(&lo, &hi, c, Limb::ZERO); @@ -134,7 +134,7 @@ mod tests { assert!(c < **p, "not reduced: {} >= {} ", c, p); let expected = { - let (lo, hi) = a.mul_wide(&b); + let (lo, hi) = a.split_mul(&b); let mut prod = Uint::<{ 2 * $size }>::ZERO; prod.limbs[..$size].clone_from_slice(&lo.limbs); prod.limbs[$size..].clone_from_slice(&hi.limbs); diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 6d3d1071..8a66faba 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -1,6 +1,6 @@ use core::ops::Neg; -use crate::{ConstChoice, Limb, Uint, WideWord, Word, Wrapping}; +use crate::{ConstChoice, Limb, Uint, WideWord, Word, Wrapping, WrappingNeg}; impl Neg for Wrapping> { type Output = Self; @@ -31,6 +31,12 @@ impl Uint { } } +impl WrappingNeg for Uint { + fn wrapping_neg(&self) -> Self { + self.wrapping_neg() + } +} + #[cfg(test)] mod tests { use crate::U256; diff --git a/src/uint/shl.rs b/src/uint/shl.rs index 03a5b6e1..ed972f18 100644 --- a/src/uint/shl.rs +++ b/src/uint/shl.rs @@ -1,6 +1,6 @@ //! [`Uint`] bitwise left shift operations. -use crate::{ConstChoice, Limb, Uint, Word}; +use crate::{ConstChoice, Limb, Uint, Word, WrappingShl}; use core::ops::{Shl, ShlAssign}; impl Uint { @@ -109,6 +109,18 @@ impl Uint { } } + /// Computes `self << shift` in a panic-free manner, masking off bits of `shift` which would cause the shift to + /// exceed the type's width. + pub const fn wrapping_shl(&self, shift: u32) -> Self { + self.overflowing_shl(shift).0 + } + + /// Computes `self << shift` in variable-time in a panic-free manner, masking off bits of `shift` which would cause + /// the shift to exceed the type's width. + pub const fn wrapping_shl_vartime(&self, shift: u32) -> Self { + self.overflowing_shl_vartime(shift).0 + } + /// Computes `self << shift` where `0 <= shift < Limb::BITS`, /// returning the result and the carry. #[inline(always)] @@ -156,25 +168,41 @@ impl Uint { } } -impl Shl for Uint { - type Output = Uint; - - fn shl(self, shift: u32) -> Uint { - <&Uint as Shl>::shl(&self, shift) - } +macro_rules! impl_shl { + ($($shift:ty),+) => { + $( + impl Shl<$shift> for Uint { + type Output = Uint; + + #[inline] + fn shl(self, shift: $shift) -> Uint { + <&Self>::shl(&self, shift) + } + } + + impl Shl<$shift> for &Uint { + type Output = Uint; + + #[inline] + fn shl(self, shift: $shift) -> Uint { + Uint::::shl(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShlAssign<$shift> for Uint { + fn shl_assign(&mut self, shift: $shift) { + *self = self.shl(shift) + } + } + )+ + }; } -impl Shl for &Uint { - type Output = Uint; - - fn shl(self, shift: u32) -> Uint { - Uint::::shl(self, shift) - } -} +impl_shl!(i32, u32, usize); -impl ShlAssign for Uint { - fn shl_assign(&mut self, shift: u32) { - *self = self.shl(shift) +impl WrappingShl for Uint { + fn wrapping_shl(&self, shift: u32) -> Uint { + self.wrapping_shl(shift) } } diff --git a/src/uint/shr.rs b/src/uint/shr.rs index 47f2f154..54ba0fd1 100644 --- a/src/uint/shr.rs +++ b/src/uint/shr.rs @@ -1,6 +1,6 @@ //! [`Uint`] bitwise right shift operations. -use crate::{ConstChoice, Limb, Uint}; +use crate::{ConstChoice, Limb, Uint, WrappingShr}; use core::ops::{Shr, ShrAssign}; impl Uint { @@ -108,6 +108,18 @@ impl Uint { } } + /// Computes `self >> shift` in a panic-free manner, masking off bits of `shift` which would cause the shift to + /// exceed the type's width. + pub const fn wrapping_shr(&self, shift: u32) -> Self { + self.overflowing_shr(shift).0 + } + + /// Computes `self >> shift` in variable-time in a panic-free manner, masking off bits of `shift` which would cause + /// the shift to exceed the type's width. + pub const fn wrapping_shr_vartime(&self, shift: u32) -> Self { + self.overflowing_shr_vartime(shift).0 + } + /// Computes `self >> 1` in constant-time. pub(crate) const fn shr1(&self) -> Self { self.shr1_with_carry().0 @@ -131,25 +143,41 @@ impl Uint { } } -impl Shr for Uint { - type Output = Uint; - - fn shr(self, shift: u32) -> Uint { - <&Uint as Shr>::shr(&self, shift) - } +macro_rules! impl_shr { + ($($shift:ty),+) => { + $( + impl Shr<$shift> for Uint { + type Output = Uint; + + #[inline] + fn shr(self, shift: $shift) -> Uint { + <&Self>::shr(&self, shift) + } + } + + impl Shr<$shift> for &Uint { + type Output = Uint; + + #[inline] + fn shr(self, shift: $shift) -> Uint { + Uint::::shr(self, u32::try_from(shift).expect("invalid shift")) + } + } + + impl ShrAssign<$shift> for Uint { + fn shr_assign(&mut self, shift: $shift) { + *self = self.shr(shift) + } + } + )+ + }; } -impl Shr for &Uint { - type Output = Uint; - - fn shr(self, shift: u32) -> Uint { - Uint::::shr(self, shift) - } -} +impl_shr!(i32, u32, usize); -impl ShrAssign for Uint { - fn shr_assign(&mut self, shift: u32) { - *self = self.shr(shift); +impl WrappingShr for Uint { + fn wrapping_shr(&self, shift: u32) -> Uint { + self.wrapping_shr(shift) } } diff --git a/src/uint/sub.rs b/src/uint/sub.rs index 5048e81a..5663c5ce 100644 --- a/src/uint/sub.rs +++ b/src/uint/sub.rs @@ -1,7 +1,7 @@ //! [`Uint`] addition operations. use super::Uint; -use crate::{Checked, CheckedSub, ConstChoice, Limb, Wrapping, Zero}; +use crate::{Checked, CheckedSub, ConstChoice, Limb, Wrapping, WrappingSub, Zero}; use core::ops::{Sub, SubAssign}; use subtle::CtOption; @@ -56,6 +56,23 @@ impl CheckedSub<&Uint> for Uint { } } +impl Sub for Uint { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + self.sub(&rhs) + } +} + +impl Sub<&Uint> for Uint { + type Output = Self; + + fn sub(self, rhs: &Self) -> Self { + self.checked_sub(rhs) + .expect("attempted to subtract with underflow") + } +} + impl Sub for Wrapping> { type Output = Self; @@ -156,6 +173,12 @@ impl SubAssign<&Checked>> for Checked WrappingSub for Uint { + fn wrapping_sub(&self, v: &Self) -> Self { + self.wrapping_sub(v) + } +} + #[cfg(test)] mod tests { use crate::{CheckedSub, Limb, U128}; diff --git a/tests/uint_proptests.rs b/tests/uint_proptests.rs index 2ccc3bd0..c09e406c 100644 --- a/tests/uint_proptests.rs +++ b/tests/uint_proptests.rs @@ -393,7 +393,7 @@ proptest! { #[test] fn residue_pow_bounded_exp(a in uint_mod_p(P), b in uint(), exponent_bits in any::()) { - let b_masked = b & (U256::ONE << exponent_bits.into()).wrapping_sub(&U256::ONE); + let b_masked = b & (U256::ONE << exponent_bits as u32).wrapping_sub(&U256::ONE); let a_bi = to_biguint(&a); let b_bi = to_biguint(&b_masked);