From 4bc098cb7a3ac7feb259417233fb7b2d7d875998 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 23 Sep 2024 18:00:20 +0200 Subject: [PATCH 01/87] Create `Int` core struct --- src/int.rs | 337 ++++++++++++++++++++++++++++++++++++++++++++ src/int/encoding.rs | 9 ++ src/lib.rs | 2 + 3 files changed, 348 insertions(+) create mode 100644 src/int.rs create mode 100644 src/int/encoding.rs diff --git a/src/int.rs b/src/int.rs new file mode 100644 index 00000000..7c22887a --- /dev/null +++ b/src/int.rs @@ -0,0 +1,337 @@ +use core::{fmt, ops::Not}; + +use subtle::{Choice, ConditionallySelectable, CtOption}; + +use crate::{Bounded, ConstCtOption, Limb, NonZero, Odd, Uint, Word}; + +mod encoding; + +/// Stack-allocated big _signed_ integer. +/// See [`Uint`] for _unsigned_ integers. +/// +/// Created as a [`Uint`] newtype. +#[derive(Copy, Clone, PartialEq, Hash)] +pub struct Int(Uint); + +impl Int { + /// The value `0`. + pub const ZERO: Self = Self(Uint::ZERO); // Bit sequence (be): 0000....0000 + + /// The value `1`. + pub const ONE: Self = Self(Uint::ONE); // Bit sequence (be): 0000....0001 + + /// The value `-1` + pub const MINUS_ONE: Self = Self::FULL_MASK; // Bit sequence (be): 1111....1111 + + /// Smallest value this [`Int`] can express. + pub const MIN: Self = Self(Uint::MAX.bitxor(&Uint::MAX.shr(1u32))); // Bit sequence (be): 1000....0000 + + /// Maximum value this [`Int`] can express. + pub const MAX: Self = Self(Uint::MAX.shr(1u32)); // Bit sequence (be): 0111....1111 + + /// Bit mask for the sign bit of this [`Int`]. + pub const SIGN_BIT_MASK: Self = Self::MIN; // Bit sequence (be): 1000....0000 + + /// All-one bit mask. + pub const FULL_MASK: Self = Self(Uint::MAX); // Bit sequence (be): 1111...1111 + + /// Total size of the represented integer in bits. + pub const BITS: u32 = Uint::::BITS; + + /// Total size of the represented integer in bytes. + pub const BYTES: usize = Uint::::BYTES; + + /// The number of limbs used on this platform. + pub const LIMBS: usize = LIMBS; + + /// Const-friendly [`Int`] constructor. + pub const fn new(limbs: [Limb; LIMBS]) -> Self { + Self(Uint::new(limbs)) + } + + /// Create an [`Int`] from an array of [`Word`]s (i.e. word-sized unsigned + /// integers). + #[inline] + pub const fn from_words(arr: [Word; LIMBS]) -> Self { + Self(Uint::from_words(arr)) + } + + /// Create an array of [`Word`]s (i.e. word-sized unsigned integers) from + /// an [`Int`]. + #[inline] + pub const fn to_words(self) -> [Word; LIMBS] { + self.0.to_words() + } + + /// Borrow the inner limbs as an array of [`Word`]s. + pub const fn as_words(&self) -> &[Word; LIMBS] { + self.0.as_words() + } + + /// Borrow the inner limbs as a mutable array of [`Word`]s. + pub fn as_words_mut(&mut self) -> &mut [Word; LIMBS] { + self.0.as_words_mut() + } + + /// Borrow the limbs of this [`Int`]. + pub const fn as_limbs(&self) -> &[Limb; LIMBS] { + self.0.as_limbs() + } + + /// Borrow the limbs of this [`Int`] mutably. + pub fn as_limbs_mut(&mut self) -> &mut [Limb; LIMBS] { + self.0.as_limbs_mut() + } + + /// Convert this [`Int`] into its inner limbs. + pub const fn to_limbs(self) -> [Limb; LIMBS] { + self.0.to_limbs() + } + + /// Convert to a [`NonZero>`]. + /// + /// Returns some if the original value is non-zero, and false otherwise. + pub const fn to_nz(self) -> ConstCtOption> { + ConstCtOption::new(NonZero(self), self.0.is_nonzero()) + } + + /// Convert to a [`Odd>`]. + /// + /// Returns some if the original value is odd, and false otherwise. + pub const fn to_odd(self) -> ConstCtOption> { + ConstCtOption::new(Odd(self), self.0.is_odd()) + } + + /// Compute the sign bit of this [`Int`]. + /// + /// Returns some if the original value is odd, and false otherwise. + pub const fn sign_bit(&self) -> Word { + self.0.bitand(&Self::SIGN_BIT_MASK.0).to_words()[LIMBS - 1] >> (Word::BITS - 1) + } + + /// View the data in this type as an [`Uint`] instead. + pub const fn as_uint(&self) -> Uint { + self.0 + } + + /// Whether this [`Int`] is equal to Self::MIN. + pub fn is_minimal(&self) -> Choice { + Choice::from((self == &Self::MIN) as u8) + } + + /// Whether this [`Int`] is equal to Self::MIN. + pub fn is_maximal(&self) -> Choice { + Choice::from((self == &Self::MAX) as u8) + } + + /// Map this [`Int`] to `-self` if possible. + pub fn negated(&self) -> CtOption> { + CtOption::new( + Self(self.0.bitxor(&Self::FULL_MASK.0).wrapping_add(&Self::ONE.0)), + self.is_minimal().not(), + ) + } +} + +impl AsRef<[Word; LIMBS]> for Int { + fn as_ref(&self) -> &[Word; LIMBS] { + self.as_words() + } +} + +impl AsMut<[Word; LIMBS]> for Int { + fn as_mut(&mut self) -> &mut [Word; LIMBS] { + self.as_words_mut() + } +} + +impl AsRef<[Limb]> for Int { + fn as_ref(&self) -> &[Limb] { + self.as_limbs() + } +} + +impl AsMut<[Limb]> for Int { + fn as_mut(&mut self) -> &mut [Limb] { + self.as_limbs_mut() + } +} + +impl ConditionallySelectable for Int { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + Self(Uint::conditional_select(&a.0, &b.0, choice)) + } +} + +impl Bounded for Int { + const BITS: u32 = Self::BITS; + const BYTES: usize = Self::BYTES; +} + +// TODO: impl Constants + +impl Default for Int { + fn default() -> Self { + Self::ZERO + } +} + +// TODO: impl FixedInteger + +// TODO: impl Integer + +// TODO: impl ConstZero + +// TODO: impl num_traits::One + +// TODO: impl num_traits::One + +impl fmt::Debug for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Int(0x{self:X})") + } +} + +impl fmt::Binary for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } +} + +impl fmt::Display for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(self, f) + } +} + +impl fmt::LowerHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl fmt::UpperHex for Int { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use subtle::ConditionallySelectable; + + use crate::Int; + + type I128 = Int<2>; + + #[cfg(target_pointer_width = "64")] + #[test] + fn as_words() { + let n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + assert_eq!(n.as_words(), &[0xCCCCCCCCDDDDDDDD, 0xAAAAAAAABBBBBBBB]); + } + + #[cfg(target_pointer_width = "64")] + #[test] + fn as_words_mut() { + let mut n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + assert_eq!(n.as_words_mut(), &[0xCCCCCCCCDDDDDDDD, 0xAAAAAAAABBBBBBBB]); + } + + #[cfg(feature = "alloc")] + #[test] + fn debug() { + let n = I128::from_be_hex("AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"); + + assert_eq!( + format!("{:?}", n), + "Int(0xAAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD)" + ); + } + + #[cfg(feature = "alloc")] + #[test] + fn display() { + let hex = "AAAAAAAABBBBBBBBCCCCCCCCDDDDDDDD"; + let n = I128::from_be_hex(hex); + + use alloc::string::ToString; + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB0000000000000000"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB00000000DDDDDDDD"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + + let hex = "AAAAAAAABBBBBBBB0CCCCCCCDDDDDDDD"; + let n = I128::from_be_hex(hex); + assert_eq!(hex, n.to_string()); + } + + #[test] + fn conditional_select() { + let a = I128::from_be_hex("00002222444466668888AAAACCCCEEEE"); + let b = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + + let select_0 = I128::conditional_select(&a, &b, 0.into()); + assert_eq!(a, select_0); + + let select_1 = I128::conditional_select(&a, &b, 1.into()); + assert_eq!(b, select_1); + } + + #[test] + fn sign_bit() { + assert_eq!(I128::MIN.sign_bit(), 1u32.into()); + assert_eq!(I128::MINUS_ONE.sign_bit(), 1u32.into()); + assert_eq!(I128::ZERO.sign_bit(), 0u32.into()); + assert_eq!(I128::ONE.sign_bit(), 0u32.into()); + assert_eq!(I128::MAX.sign_bit(), 0u32.into()); + + let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_negative.sign_bit(), 1u32.into()); + + let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_positive.sign_bit(), 0u32.into()); + } + + #[test] + fn is_minimal() { + let min = I128::from_be_hex("80000000000000000000000000000000"); + assert_eq!(min.is_minimal().unwrap_u8(), 1u8); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + assert_eq!(random.is_minimal().unwrap_u8(), 0u8); + } + + #[test] + fn is_maximal() { + let max = I128::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + assert_eq!(max.is_maximal().unwrap_u8(), 1u8); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + assert_eq!(random.is_maximal().unwrap_u8(), 0u8); + } + + #[test] + fn negated() { + assert_eq!(I128::MIN.negated().is_none().unwrap_u8(), 1u8); + assert_eq!(I128::MINUS_ONE.negated().unwrap(), I128::ONE); + assert_eq!(I128::ZERO.negated().unwrap(), I128::ZERO); + assert_eq!(I128::ONE.negated().unwrap(), I128::MINUS_ONE); + assert_eq!( + I128::MAX.negated().unwrap(), + I128::from_be_hex("80000000000000000000000000000001") + ); + + let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); + assert_eq!(negative.negated().unwrap(), positive); + assert_eq!(positive.negated().unwrap(), negative); + assert_eq!(positive.negated().unwrap().negated().unwrap(), positive); + } +} diff --git a/src/int/encoding.rs b/src/int/encoding.rs new file mode 100644 index 00000000..9683d007 --- /dev/null +++ b/src/int/encoding.rs @@ -0,0 +1,9 @@ +//! Const-friendly decoding/encoding operations for [`Int`]. + +use crate::{Int, Uint}; + +impl Int { + pub const fn from_be_hex(hex: &str) -> Self { + Self(Uint::from_be_hex(hex)) + } +} diff --git a/src/lib.rs b/src/lib.rs index cdfab0ab..0e8390ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -165,6 +165,7 @@ mod array; mod checked; mod const_choice; mod limb; +mod int; mod non_zero; mod odd; mod primitives; @@ -176,6 +177,7 @@ pub use crate::{ checked::Checked, const_choice::{ConstChoice, ConstCtOption}, limb::{Limb, WideWord, Word}, + int::*, non_zero::NonZero, odd::Odd, traits::*, From 31e35772511e5fc1e6a91aa4e9a56a2585884e71 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 15 Oct 2024 16:10:57 +0200 Subject: [PATCH 02/87] Introduce `Int::add` --- src/int.rs | 2 + src/int/add.rs | 203 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+) create mode 100644 src/int/add.rs diff --git a/src/int.rs b/src/int.rs index 7c22887a..3bc5e8f6 100644 --- a/src/int.rs +++ b/src/int.rs @@ -5,6 +5,8 @@ use subtle::{Choice, ConditionallySelectable, CtOption}; use crate::{Bounded, ConstCtOption, Limb, NonZero, Odd, Uint, Word}; mod encoding; +mod add; +mod mul; /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. diff --git a/src/int/add.rs b/src/int/add.rs new file mode 100644 index 00000000..251cb267 --- /dev/null +++ b/src/int/add.rs @@ -0,0 +1,203 @@ +//! [`Int`] addition operations. + +use core::ops::{Add, AddAssign}; +use num_traits::WrappingAdd; +use subtle::{ConstantTimeEq, CtOption}; +use crate::{Checked, CheckedAdd, Int, Wrapping}; + +impl Int { + + /// Perform checked addition. + pub fn checked_add_(&self, rhs: &Self) -> CtOption { + // Step 1. add operands + let res = Self(self.0.wrapping_add(&rhs.0)); + + // Step 2. check whether overflow happened. + // Note: + // - overflow can only happen when the inputs have the same sign, and then + // - overflow occurs if and only if the result has the opposite sign of both inputs. + // + // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) + let self_msb = self.is_negative(); + let overflow= self_msb.ct_eq(&rhs.is_negative()) & self_msb.ct_ne(&res.is_negative()); + + // Step 3. Construct result + CtOption::new( + res, + !overflow, + ) + } + + /// Perform wrapping addition, discarding overflow. + pub const fn wrapping_add(&self, rhs: &Self) -> Self { + Self(self.0.wrapping_add(&rhs.0)) + } +} + + +impl Add for Int { + type Output = Self; + + fn add(self, rhs: Self) -> Self { + self.add(&rhs) + } +} + +impl Add<&Int> for Int { + type Output = Self; + + fn add(self, rhs: &Self) -> Self { + self.checked_add(rhs) + .expect("attempted to add with overflow") + } +} + +impl AddAssign for Int { + fn add_assign(&mut self, other: Self) { + *self += &other; + } +} + +impl AddAssign<&Int> for Int { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl AddAssign for Wrapping> { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl AddAssign<&Wrapping>> for Wrapping> { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl AddAssign for Checked> { + fn add_assign(&mut self, other: Self) { + *self = *self + other; + } +} + +impl AddAssign<&Checked>> for Checked> { + fn add_assign(&mut self, other: &Self) { + *self = *self + other; + } +} + +impl CheckedAdd for Int { + fn checked_add(&self, rhs: &Self) -> CtOption { + self.checked_add_(rhs) + } +} + +impl WrappingAdd for Int { + fn wrapping_add(&self, v: &Self) -> Self { + self.wrapping_add(v) + } +} + +#[cfg(test)] +mod tests { + + #[cfg(test)] + mod tests { + use crate::{CheckedAdd, Int, U128}; + use crate::int::I128; + + #[test] + fn checked_add() { + let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; + let two = Int{ 0: U128::from(2u32) }; + + // lhs = MIN + + let result = I128::MIN.checked_add(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_add(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MIN.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_add(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), two.negated().unwrap()); + + let result = I128::MINUS_ONE.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), max_minus_one); + + // lhs = 0 + + let result = I128::ZERO.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::ZERO.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ZERO.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ZERO.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), I128::MAX); + + // lhs = 1 + + let result = I128::ONE.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::ONE.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_add(&I128::ONE); + assert_eq!(result.unwrap(), two); + + let result = I128::ONE.checked_add(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = MAX + + let result = I128::MAX.checked_add(&I128::MIN); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MAX.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), max_minus_one); + + let result = I128::MAX.checked_add(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_add(&I128::ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_add(&I128::MAX); + assert!(bool::from(result.is_none())); + } + } +} \ No newline at end of file From cfb1ec4e3f2d94de07cc6f7a3433db7d2805a609 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 15 Oct 2024 17:42:29 +0200 Subject: [PATCH 03/87] Introduce `Int::mul` --- src/int.rs | 119 +++++++++++++++++++++++--------- src/int/add.rs | 6 +- src/int/mul.rs | 179 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 270 insertions(+), 34 deletions(-) create mode 100644 src/int/mul.rs diff --git a/src/int.rs b/src/int.rs index 3bc5e8f6..02460d6c 100644 --- a/src/int.rs +++ b/src/int.rs @@ -1,8 +1,8 @@ -use core::{fmt, ops::Not}; +use core::fmt; -use subtle::{Choice, ConditionallySelectable, CtOption}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; -use crate::{Bounded, ConstCtOption, Limb, NonZero, Odd, Uint, Word}; +use crate::{Bounded, ConstantTimeSelect, ConstChoice, ConstCtOption, Limb, NonZero, Odd, Uint, Word}; mod encoding; mod add; @@ -51,6 +51,15 @@ impl Int { Self(Uint::new(limbs)) } + /// Construct new [`Int`] from a sign and magnitude. + /// Returns `None` when the magnitude does not fit in an [`Int`]. + pub fn new_from_sign_and_magnitude(is_negative: Choice, magnitude: Uint) -> CtOption { + CtOption::new( + Self(magnitude).negate_if_unsafe(is_negative).0, + !magnitude.ct_gt(&Int::MAX.0) | is_negative & magnitude.ct_eq(&Int::MIN.0), + ) + } + /// Create an [`Int`] from an array of [`Word`]s (i.e. word-sized unsigned /// integers). #[inline] @@ -104,11 +113,9 @@ impl Int { ConstCtOption::new(Odd(self), self.0.is_odd()) } - /// Compute the sign bit of this [`Int`]. - /// - /// Returns some if the original value is odd, and false otherwise. - pub const fn sign_bit(&self) -> Word { - self.0.bitand(&Self::SIGN_BIT_MASK.0).to_words()[LIMBS - 1] >> (Word::BITS - 1) + /// Get the sign bit of this [`Int`] as `Choice`. + pub fn sign_bit(&self) -> Choice { + Choice::from((self.0.to_words()[LIMBS - 1] >> (Word::BITS - 1)) as u8) } /// View the data in this type as an [`Uint`] instead. @@ -116,23 +123,69 @@ impl Int { self.0 } - /// Whether this [`Int`] is equal to Self::MIN. + /// Whether this [`Int`] is equal to `Self::MIN`. pub fn is_minimal(&self) -> Choice { Choice::from((self == &Self::MIN) as u8) } - /// Whether this [`Int`] is equal to Self::MIN. + /// Whether this [`Int`] is equal to `Self::MAX`. pub fn is_maximal(&self) -> Choice { Choice::from((self == &Self::MAX) as u8) } + /// Perform the two's complement "negate" operation on this [`Int`]: + /// map `self` to `(self ^ 1111...1111) + 0000...0001` + /// + /// Returns + /// - the result, as well as + /// - whether the addition overflowed (indicating `self` is zero). + /// + /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. + #[inline] + fn negate_unsafe(&self) -> (Self, Choice) { + let inverted = self.0.bitxor(&Self::FULL_MASK.0); + let (res, carry) = inverted.adc(&Uint::ONE, Limb::ZERO); + let is_zero = ConstChoice::from_word_lsb(carry.0).into(); + (Self(res), is_zero) + } + + /// Perform the [two's complement "negate" operation](Int::negate_unsafe) on this [`Int`] + /// if `negate` is truthy. + /// + /// Returns + /// - the result, as well as + /// - whether the addition overflowed (indicating `self` is zero). + /// + /// Warning: this operation is unsafe; when `self == Self::MIN` and `negate` is truthy, + /// the negation fails. + #[inline] + fn negate_if_unsafe(&self, negate: Choice) -> (Int, Choice) { + let (negated, is_zero) = self.negate_unsafe(); + (Self(Uint::ct_select(&self.0, &negated.0, negate)), is_zero) + } + /// Map this [`Int`] to `-self` if possible. - pub fn negated(&self) -> CtOption> { + /// + /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive + /// equivalent of that. + pub fn neg(&self) -> CtOption { CtOption::new( - Self(self.0.bitxor(&Self::FULL_MASK.0).wrapping_add(&Self::ONE.0)), - self.is_minimal().not(), + self.negate_unsafe().0, + !self.is_minimal() ) } + + /// The sign and magnitude of this [`Int`], as well as whether it is zero. + pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { + let sign = self.sign_bit(); + let (magnitude, is_zero) = self.negate_if_unsafe(sign); + (sign, magnitude.0, is_zero) + } + + /// The magnitude of this [`Int`]. + pub fn magnitude(&self) -> Uint { + self.sign_magnitude_is_zero().1 + } } impl AsRef<[Word; LIMBS]> for Int { @@ -218,14 +271,18 @@ impl fmt::UpperHex for Int { } } +#[cfg(target_pointer_width = "64")] +type I128 = Int<2>; + +#[cfg(target_pointer_width = "32")] +type I128 = Int<4>; + #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use subtle::ConditionallySelectable; - use crate::Int; - - type I128 = Int<2>; + use crate::int::I128; #[cfg(target_pointer_width = "64")] #[test] @@ -288,17 +345,17 @@ mod tests { #[test] fn sign_bit() { - assert_eq!(I128::MIN.sign_bit(), 1u32.into()); - assert_eq!(I128::MINUS_ONE.sign_bit(), 1u32.into()); - assert_eq!(I128::ZERO.sign_bit(), 0u32.into()); - assert_eq!(I128::ONE.sign_bit(), 0u32.into()); - assert_eq!(I128::MAX.sign_bit(), 0u32.into()); + assert_eq!(I128::MIN.sign_bit().unwrap_u8(), 1u8); + assert_eq!(I128::MINUS_ONE.sign_bit().unwrap_u8(), 1u8); + assert_eq!(I128::ZERO.sign_bit().unwrap_u8(), 0u8); + assert_eq!(I128::ONE.sign_bit().unwrap_u8(), 0u8); + assert_eq!(I128::MAX.sign_bit().unwrap_u8(), 0u8); let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.sign_bit(), 1u32.into()); + assert_eq!(random_negative.sign_bit().unwrap_u8(), 1u8); let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.sign_bit(), 0u32.into()); + assert_eq!(random_positive.sign_bit().unwrap_u8(), 0u8); } #[test] @@ -321,19 +378,19 @@ mod tests { #[test] fn negated() { - assert_eq!(I128::MIN.negated().is_none().unwrap_u8(), 1u8); - assert_eq!(I128::MINUS_ONE.negated().unwrap(), I128::ONE); - assert_eq!(I128::ZERO.negated().unwrap(), I128::ZERO); - assert_eq!(I128::ONE.negated().unwrap(), I128::MINUS_ONE); + assert_eq!(I128::MIN.neg().is_none().unwrap_u8(), 1u8); + assert_eq!(I128::MINUS_ONE.neg().unwrap(), I128::ONE); + assert_eq!(I128::ZERO.neg().unwrap(), I128::ZERO); + assert_eq!(I128::ONE.neg().unwrap(), I128::MINUS_ONE); assert_eq!( - I128::MAX.negated().unwrap(), + I128::MAX.neg().unwrap(), I128::from_be_hex("80000000000000000000000000000001") ); let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); - assert_eq!(negative.negated().unwrap(), positive); - assert_eq!(positive.negated().unwrap(), negative); - assert_eq!(positive.negated().unwrap().negated().unwrap(), positive); + assert_eq!(negative.neg().unwrap(), positive); + assert_eq!(positive.neg().unwrap(), negative); + assert_eq!(positive.neg().unwrap().neg().unwrap(), positive); } } diff --git a/src/int/add.rs b/src/int/add.rs index 251cb267..f4be8ee5 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -18,8 +18,8 @@ impl Int { // - overflow occurs if and only if the result has the opposite sign of both inputs. // // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) - let self_msb = self.is_negative(); - let overflow= self_msb.ct_eq(&rhs.is_negative()) & self_msb.ct_ne(&res.is_negative()); + let self_msb = self.sign_bit(); + let overflow= self_msb.ct_eq(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); // Step 3. Construct result CtOption::new( @@ -137,7 +137,7 @@ mod tests { assert!(bool::from(result.is_none())); let result = I128::MINUS_ONE.checked_add(&I128::MINUS_ONE); - assert_eq!(result.unwrap(), two.negated().unwrap()); + assert_eq!(result.unwrap(), two.neg().unwrap()); let result = I128::MINUS_ONE.checked_add(&I128::ZERO); assert_eq!(result.unwrap(), I128::MINUS_ONE); diff --git a/src/int/mul.rs b/src/int/mul.rs new file mode 100644 index 00000000..f632051d --- /dev/null +++ b/src/int/mul.rs @@ -0,0 +1,179 @@ +//! [`Int`] multiplication operations. + +use core::ops::{Mul, MulAssign}; +use subtle::{Choice, ConstantTimeEq, CtOption}; + +use crate::{Checked, CheckedMul, Int, Uint, Zero}; + +impl Int { + /// Compute "wide" multiplication as a 3-tuple `(lo, hi, sgn)`. + /// The `(lo, hi)` components contain the _magnitude of the product_, with sizes + /// correspond to the sizes of the operands; the `sgn` indicates the sign of the result. + pub fn split_mul( + &self, + rhs: &Int, + ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, Choice) { + // Step 1: split operands into signs, magnitudes and whether they are zero. + let (lhs_sgn, lhs_mag, lhs_is_zero) = self.sign_magnitude_is_zero(); + let (rhs_sgn, rhs_mag, rhs_is_zero) = rhs.sign_magnitude_is_zero(); + + // Step 2: multiply the magnitudes + let (lo, hi) = lhs_mag.split_mul(&rhs_mag); + + // Step 3: construct the sign of the result + let is_negative = !lhs_is_zero & !rhs_is_zero & lhs_sgn.ct_ne(&rhs_sgn); + + (lo, hi, is_negative) + } +} + +impl CheckedMul> for Int { + #[inline] + fn checked_mul(&self, rhs: &Int) -> CtOption { + let (lo, hi, is_negative) = self.split_mul(rhs); + let val = Self::new_from_sign_and_magnitude(is_negative, lo); + val.and_then(|int| { CtOption::new(int, hi.is_zero()) }) + } +} + +impl Mul> for Int { + type Output = Int; + + fn mul(self, rhs: Int) -> Self { + self.mul(&rhs) + } +} + +impl Mul<&Int> for Int { + type Output = Int; + + fn mul(self, rhs: &Int) -> Self { + (&self).mul(rhs) + } +} + +impl Mul> for &Int { + type Output = Int; + + fn mul(self, rhs: Int) -> Self::Output { + self.mul(&rhs) + } +} + +impl Mul<&Int> for &Int { + type Output = Int; + + fn mul(self, rhs: &Int) -> Self::Output { + self.checked_mul(rhs) + .expect("attempted to multiply with overflow") + } +} + +impl MulAssign>> for Checked> { + fn mul_assign(&mut self, other: Checked>) { + *self = *self * other; + } +} + +impl MulAssign<&Checked>> for Checked> { + fn mul_assign(&mut self, other: &Checked>) { + *self = *self * other; + } +} + +#[cfg(test)] +mod tests { + use crate::CheckedMul; + use crate::int::{I128, Int}; + + #[test] + fn test_checked_mul() { + let min_plus_one = Int { 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + + // lhs = min + + let result = I128::MIN.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_mul(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MIN.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_mul(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MINUS_ONE.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_one); + + // lhs = 0 + + let result = I128::ZERO.checked_mul(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 1 + + let result = I128::ONE.checked_mul(&I128::MIN); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::ONE.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ONE.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_mul(&I128::MAX); + assert_eq!(result.unwrap(), I128::MAX); + + // lhs = max + + let result = I128::MAX.checked_mul(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_mul(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MAX.checked_mul(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MAX.checked_mul(&I128::ONE); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_mul(&I128::MAX); + assert!(bool::from(result.is_none())); + + } +} From 01a07f7cfafbbac3bc601ab32dcf4e8f03a3c955 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 15 Oct 2024 17:56:58 +0200 Subject: [PATCH 04/87] Introduce `Int::div` --- src/int.rs | 1 + src/int/div.rs | 118 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) create mode 100644 src/int/div.rs diff --git a/src/int.rs b/src/int.rs index 02460d6c..30b7ec58 100644 --- a/src/int.rs +++ b/src/int.rs @@ -7,6 +7,7 @@ use crate::{Bounded, ConstantTimeSelect, ConstChoice, ConstCtOption, Limb, NonZe mod encoding; mod add; mod mul; +mod div; /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. diff --git a/src/int/div.rs b/src/int/div.rs new file mode 100644 index 00000000..584b3d9c --- /dev/null +++ b/src/int/div.rs @@ -0,0 +1,118 @@ +//! [`Int`] division operations. + +use subtle::{ConstantTimeEq, CtOption}; +use crate::Int; + +impl Int { + + /// Perform checked division, returning a [`CtOption`] which `is_some` + /// only if the rhs != 0 + pub fn checked_div(&self, rhs: &Self) -> CtOption { + // Step 1: split operands into signs, magnitudes and whether they are zero. + let (lhs_sgn, lhs_mag, lhs_is_zero) = self.sign_magnitude_is_zero(); + let (rhs_sgn, rhs_mag, ..) = rhs.sign_magnitude_is_zero(); + + // Step 2: divide the magnitudes + lhs_mag.checked_div(&rhs_mag).and_then(| magnitude | { + // Step 3: construct the sign of the result + let is_negative = !lhs_is_zero & lhs_sgn.ct_ne(&rhs_sgn); + Self::new_from_sign_and_magnitude(is_negative, magnitude) + }) + } +} + + +#[cfg(test)] +mod tests { + use crate::int::{I128, Int}; + + #[test] + fn test_checked_div() { + let min_plus_one = Int { 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + + // lhs = min + + let result = I128::MIN.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MIN.checked_div(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::MINUS_ONE.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MINUS_ONE.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 0 + + let result = I128::ZERO.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::ZERO.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = 1 + + let result = I128::ONE.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ONE.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::ONE.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // lhs = max + + let result = I128::MAX.checked_div(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MAX.checked_div(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MAX.checked_div(&I128::ZERO); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_div(&I128::ONE); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_div(&I128::MAX); + assert_eq!(result.unwrap(), I128::ONE); + } +} \ No newline at end of file From 000c0077aa318dbce5c19327033fd5c094cd6bae Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 09:15:35 +0200 Subject: [PATCH 05/87] Implement `Div` for `Int` --- src/int/div.rs | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/int/div.rs b/src/int/div.rs index 584b3d9c..8eaa836b 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -1,7 +1,10 @@ //! [`Int`] division operations. +use core::ops::{Div, DivAssign}; + use subtle::{ConstantTimeEq, CtOption}; -use crate::Int; + +use crate::{CheckedDiv, Int, NonZero}; impl Int { @@ -13,7 +16,7 @@ impl Int { let (rhs_sgn, rhs_mag, ..) = rhs.sign_magnitude_is_zero(); // Step 2: divide the magnitudes - lhs_mag.checked_div(&rhs_mag).and_then(| magnitude | { + lhs_mag.checked_div(&rhs_mag).and_then(|magnitude| { // Step 3: construct the sign of the result let is_negative = !lhs_is_zero & lhs_sgn.ct_ne(&rhs_sgn); Self::new_from_sign_and_magnitude(is_negative, magnitude) @@ -21,6 +24,44 @@ impl Int { } } +impl CheckedDiv for Int { + fn checked_div(&self, rhs: &Int) -> CtOption { + self.checked_div(rhs) + } +} + +impl Div<&NonZero>> for &Int { + type Output = CtOption>; + + fn div(self, rhs: &NonZero>) -> Self::Output { + *self / *rhs + } +} + +impl Div<&NonZero>> for Int { + type Output = CtOption>; + + fn div(self, rhs: &NonZero>) -> Self::Output { + self / *rhs + } +} + +impl Div>> for &Int { + type Output = CtOption>; + + fn div(self, rhs: NonZero>) -> Self::Output { + *self / rhs + } +} + +impl Div>> for Int { + type Output = CtOption>; + + fn div(self, rhs: NonZero>) -> Self::Output { + self.checked_div(&rhs) + } +} + #[cfg(test)] mod tests { From 39b08bbb9e0f5961a6bf3c85ee4ed9c6db1d43aa Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 09:16:07 +0200 Subject: [PATCH 06/87] Clean up --- src/int/add.rs | 6 +++--- src/int/div.rs | 2 +- src/int/mul.rs | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/int/add.rs b/src/int/add.rs index f4be8ee5..bec567b9 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -8,7 +8,7 @@ use crate::{Checked, CheckedAdd, Int, Wrapping}; impl Int { /// Perform checked addition. - pub fn checked_add_(&self, rhs: &Self) -> CtOption { + fn checked_add_internal(&self, rhs: &Self) -> CtOption { // Step 1. add operands let res = Self(self.0.wrapping_add(&rhs.0)); @@ -17,7 +17,7 @@ impl Int { // - overflow can only happen when the inputs have the same sign, and then // - overflow occurs if and only if the result has the opposite sign of both inputs. // - // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) + // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) let self_msb = self.sign_bit(); let overflow= self_msb.ct_eq(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); @@ -90,7 +90,7 @@ impl AddAssign<&Checked>> for Checked> impl CheckedAdd for Int { fn checked_add(&self, rhs: &Self) -> CtOption { - self.checked_add_(rhs) + self.checked_add_internal(rhs) } } diff --git a/src/int/div.rs b/src/int/div.rs index 8eaa836b..5e0a8502 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -1,6 +1,6 @@ //! [`Int`] division operations. -use core::ops::{Div, DivAssign}; +use core::ops::Div; use subtle::{ConstantTimeEq, CtOption}; diff --git a/src/int/mul.rs b/src/int/mul.rs index f632051d..5c864793 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -174,6 +174,5 @@ mod tests { let result = I128::MAX.checked_mul(&I128::MAX); assert!(bool::from(result.is_none())); - } } From 7b0fbab5d0deff99cf7be2f0a7da7d3cd4133b41 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 09:48:09 +0200 Subject: [PATCH 07/87] Implement `Sub` for `Int` --- src/int.rs | 1 + src/int/sub.rs | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 src/int/sub.rs diff --git a/src/int.rs b/src/int.rs index 30b7ec58..f9463f19 100644 --- a/src/int.rs +++ b/src/int.rs @@ -8,6 +8,7 @@ mod encoding; mod add; mod mul; mod div; +mod sub; /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. diff --git a/src/int/sub.rs b/src/int/sub.rs new file mode 100644 index 00000000..f3e613c7 --- /dev/null +++ b/src/int/sub.rs @@ -0,0 +1,161 @@ +//! [`Int`] subtraction operations. + +use core::ops::{Sub, SubAssign}; +use subtle::{ConstantTimeEq, CtOption}; +use crate::{Checked, CheckedSub, Int}; + +impl CheckedSub for Int { + fn checked_sub(&self, rhs: &Self) -> CtOption { + // Step 1. subtract operands + let res = Self(self.0.wrapping_sub(&rhs.0)); + + // Step 2. check whether underflow happened. + // Note: + // - underflow can only happen when the inputs have opposing signs, and then + // - underflow occurs if and only if the result and the lhs have opposing signs. + // + // We can thus express the overflow flag as: (self.msb != rhs.msb) & (self.msb != res.msb) + let self_msb = self.sign_bit(); + let underflow = self_msb.ct_ne(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); + + // Step 3. Construct result + CtOption::new( + res, + !underflow, + ) + } +} + +impl Sub for Int { + type Output = Self; + + fn sub(self, rhs: Self) -> Self { + self.sub(&rhs) + } +} + +impl Sub<&Int> for Int { + type Output = Self; + + fn sub(self, rhs: &Self) -> Self { + self.checked_sub(rhs) + .expect("attempted to subtract with underflow") + } +} + + +impl SubAssign for Checked> { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl SubAssign<&Checked>> for Checked> { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + + +#[cfg(test)] +mod tests { + + #[cfg(test)] + mod tests { + use crate::{CheckedSub, Int, U128}; + use crate::int::I128; + + #[test] + fn checked_sub() { + let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; + let two = Int{ 0: U128::from(2u32) }; + let min_plus_two = Int{ 0: I128::MIN.0.wrapping_add(&two.0) }; + + // lhs = MIN + + let result = I128::MIN.checked_sub(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MIN.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), min_plus_one); + + let result = I128::MIN.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MIN); + + let result = I128::MIN.checked_sub(&I128::ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MIN.checked_sub(&I128::MAX); + assert!(bool::from(result.is_none())); + + // lhs = -1 + + let result = I128::MINUS_ONE.checked_sub(&I128::MIN); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MINUS_ONE.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::MINUS_ONE.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), two.neg().unwrap()); + + let result = I128::MINUS_ONE.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), I128::MIN); + + // lhs = 0 + + let result = I128::ZERO.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::ZERO.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ZERO.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), I128::MINUS_ONE); + + let result = I128::ZERO.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_one); + + // lhs = 1 + + let result = I128::ONE.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::ONE.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), two); + + let result = I128::ONE.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ONE); + + let result = I128::ONE.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), min_plus_two); + + // lhs = MAX + + let result = I128::MAX.checked_sub(&I128::MIN); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_sub(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::MAX); + + let result = I128::MAX.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), max_minus_one); + + let result = I128::MAX.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + } + } +} \ No newline at end of file From 66b46390dcfcde495592aa762c2757a232ad28a0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 09:48:19 +0200 Subject: [PATCH 08/87] Fix typo in `Uint::sub` --- src/uint/sub.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uint/sub.rs b/src/uint/sub.rs index a6ac2bf2..561b9f0c 100644 --- a/src/uint/sub.rs +++ b/src/uint/sub.rs @@ -1,4 +1,4 @@ -//! [`Uint`] addition operations. +//! [`Uint`] subtraction operations. use super::Uint; use crate::{Checked, CheckedSub, ConstChoice, Limb, Wrapping, WrappingSub, Zero}; From 02d9d5c812b8f647b613bd3d3fe80c0fe21d7a07 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 09:58:03 +0200 Subject: [PATCH 09/87] Implement `WrappingSub` for `Int` --- src/int/sub.rs | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/int/sub.rs b/src/int/sub.rs index f3e613c7..501108ef 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -1,8 +1,9 @@ //! [`Int`] subtraction operations. use core::ops::{Sub, SubAssign}; +use num_traits::WrappingSub; use subtle::{ConstantTimeEq, CtOption}; -use crate::{Checked, CheckedSub, Int}; +use crate::{Checked, CheckedSub, Int, Wrapping}; impl CheckedSub for Int { fn checked_sub(&self, rhs: &Self) -> CtOption { @@ -43,6 +44,17 @@ impl Sub<&Int> for Int { } } +impl SubAssign for Wrapping> { + fn sub_assign(&mut self, other: Self) { + *self = *self - other; + } +} + +impl SubAssign<&Wrapping>> for Wrapping> { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} impl SubAssign for Checked> { fn sub_assign(&mut self, other: Self) { @@ -56,12 +68,18 @@ impl SubAssign<&Checked>> for Checked> } } +impl WrappingSub for Int { + fn wrapping_sub(&self, v: &Self) -> Self { + Self(self.0.wrapping_sub(&v.0)) + } +} #[cfg(test)] mod tests { #[cfg(test)] mod tests { + use num_traits::WrappingSub; use crate::{CheckedSub, Int, U128}; use crate::int::I128; @@ -157,5 +175,24 @@ mod tests { let result = I128::MAX.checked_sub(&I128::MAX); assert_eq!(result.unwrap(), I128::ZERO); } + + #[test] + fn wrapping_sub() { + let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + let two = Int{ 0: U128::from(2u32) }; + let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; + + // + sub - + let result = I128::ONE.wrapping_sub(&I128::MIN); + assert_eq!(result, min_plus_one); + + // 0 sub - + let result = I128::ZERO.wrapping_sub(&I128::MIN); + assert_eq!(result, I128::MIN); + + // - sub + + let result = I128::MIN.wrapping_sub(&two); + assert_eq!(result, max_minus_one); + } } } \ No newline at end of file From e614cd59d25619e51df6bda5695a374a2345f5ca Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:27:49 +0200 Subject: [PATCH 10/87] Implement `cmp` for `Int` --- src/int.rs | 8 +++- src/int/cmp.rs | 108 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/int/cmp.rs diff --git a/src/int.rs b/src/int.rs index f9463f19..e046ccf0 100644 --- a/src/int.rs +++ b/src/int.rs @@ -9,12 +9,13 @@ mod add; mod mul; mod div; mod sub; +mod cmp; /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. /// /// Created as a [`Uint`] newtype. -#[derive(Copy, Clone, PartialEq, Hash)] +#[derive(Copy, Clone, Hash)] pub struct Int(Uint); impl Int { @@ -188,6 +189,11 @@ impl Int { pub fn magnitude(&self) -> Uint { self.sign_magnitude_is_zero().1 } + + /// Invert the most significant bit (msb) of this [`Int`] + const fn invert_msb(&self) -> Self { + Self(self.0.bitxor(&Self::SIGN_BIT_MASK.0)) + } } impl AsRef<[Word; LIMBS]> for Int { diff --git a/src/int/cmp.rs b/src/int/cmp.rs new file mode 100644 index 00000000..d1bda140 --- /dev/null +++ b/src/int/cmp.rs @@ -0,0 +1,108 @@ +//! [`Int`] comparisons. +//! +//! By default, these are all constant-time and use the `subtle` crate. +#![allow(dead_code)] + +use core::cmp::Ordering; +use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; +use crate::{ConstChoice, Int, Uint}; + + +impl Int { + /// Return `b` if `c` is truthy, otherwise return `a`. + #[inline] + pub(crate) const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { + Self(Uint::select(&a.0, &b.0, c)) + } + + /// Returns the truthy value if `self`!=0 or the falsy value otherwise. + #[inline] + pub(crate) const fn is_nonzero(&self) -> ConstChoice { + Uint::is_nonzero(&self.0) + } + + /// Returns the truthy value if `self` is odd or the falsy value otherwise. + pub(crate) const fn is_odd(&self) -> ConstChoice { + Uint::is_odd(&self.0) + } + + /// Returns the truthy value if `self == rhs` or the falsy value otherwise. + #[inline] + pub(crate) const fn eq(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::eq(&lhs.0, &rhs.0) + } + + /// Returns the truthy value if `self < rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn lt(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::lt(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the truthy value if `self > rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { + Uint::gt(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the ordering between `self` and `rhs` as an i8. + /// Values correspond to the Ordering enum: + /// -1 is Less + /// 0 is Equal + /// 1 is Greater + #[inline] + pub(crate) const fn cmp(lhs: &Self, rhs: &Self) -> i8 { + Uint::cmp(&lhs.invert_msb().0, &rhs.invert_msb().0) + } + + /// Returns the Ordering between `self` and `rhs` in variable time. + pub const fn cmp_vartime(&self, rhs: &Self) -> Ordering { + self.invert_msb().0.cmp_vartime(&rhs.invert_msb().0) + } +} + + +impl ConstantTimeEq for Int { + #[inline] + fn ct_eq(&self, other: &Self) -> Choice { + Int::eq(self, other).into() + } +} + +impl ConstantTimeGreater for Int { + #[inline] + fn ct_gt(&self, other: &Self) -> Choice { + Int::gt(self, other).into() + } +} + +impl ConstantTimeLess for Int { + #[inline] + fn ct_lt(&self, other: &Self) -> Choice { + Int::lt(self, other).into() + } +} + +impl Eq for Int {} + +impl Ord for Int { + fn cmp(&self, other: &Self) -> Ordering { + let c = Self::cmp(self, other); + match c { + -1 => Ordering::Less, + 0 => Ordering::Equal, + _ => Ordering::Greater, + } + } +} + +impl PartialOrd for Int { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Int { + fn eq(&self, other: &Self) -> bool { + self.ct_eq(other).into() + } +} \ No newline at end of file From da13a4f22b0d4bef68f8dbaf8ba573448bc140c2 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:28:18 +0200 Subject: [PATCH 11/87] Implement `rand` for `Int` --- src/int.rs | 1 + src/int/rand.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/int/rand.rs diff --git a/src/int.rs b/src/int.rs index e046ccf0..047bf83d 100644 --- a/src/int.rs +++ b/src/int.rs @@ -9,6 +9,7 @@ mod add; mod mul; mod div; mod sub; +mod rand; mod cmp; /// Stack-allocated big _signed_ integer. diff --git a/src/int/rand.rs b/src/int/rand.rs new file mode 100644 index 00000000..8c988ee9 --- /dev/null +++ b/src/int/rand.rs @@ -0,0 +1,108 @@ +//! Random number generator support + +use super::Uint; +use crate::{Int, Random, RandomBits, RandomBitsError}; +use rand_core::CryptoRngCore; + +impl Random for Int { + /// Generate a cryptographically secure random [`Int`]. + fn random(rng: &mut impl CryptoRngCore) -> Self { + Self(Uint::random(rng)) + } +} + +impl RandomBits for Int { + fn try_random_bits( + rng: &mut impl CryptoRngCore, + bit_length: u32, + ) -> Result { + Self::try_random_bits_with_precision(rng, bit_length, Self::BITS) + } + + fn try_random_bits_with_precision( + rng: &mut impl CryptoRngCore, + bit_length: u32, + bits_precision: u32, + ) -> Result { + Uint::try_random_bits_with_precision(rng, bit_length, bits_precision) + .map(|val| Self(val)) + } +} + +#[cfg(test)] +mod tests { + use crate::{Limb, NonZero, RandomBits, RandomMod, U256}; + use rand_core::SeedableRng; + + #[test] + fn random_mod() { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); + + // Ensure `random_mod` runs in a reasonable amount of time + let modulus = NonZero::new(U256::from(42u8)).unwrap(); + let res = U256::random_mod(&mut rng, &modulus); + + // Check that the value is in range + assert!(res < U256::from(42u8)); + + // Ensure `random_mod` runs in a reasonable amount of time + // when the modulus is larger than 1 limb + let modulus = NonZero::new(U256::from(0x10000000000000001u128)).unwrap(); + let res = U256::random_mod(&mut rng, &modulus); + + // Check that the value is in range + assert!(res < U256::from(0x10000000000000001u128)); + } + + #[test] + fn random_bits() { + let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); + + let lower_bound = 16; + + // Full length of the integer + let bit_length = U256::BITS; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert!(res > (U256::ONE << (bit_length - lower_bound))); + } + + // A multiple of limb size + let bit_length = U256::BITS - Limb::BITS; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert!(res > (U256::ONE << (bit_length - lower_bound))); + assert!(res < (U256::ONE << bit_length)); + } + + // A multiple of 8 + let bit_length = U256::BITS - Limb::BITS - 8; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert!(res > (U256::ONE << (bit_length - lower_bound))); + assert!(res < (U256::ONE << bit_length)); + } + + // Not a multiple of 8 + let bit_length = U256::BITS - Limb::BITS - 8 - 3; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert!(res > (U256::ONE << (bit_length - lower_bound))); + assert!(res < (U256::ONE << bit_length)); + } + + // One incomplete limb + let bit_length = 7; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert!(res < (U256::ONE << bit_length)); + } + + // Zero bits + let bit_length = 0; + for _ in 0..10 { + let res = U256::random_bits(&mut rng, bit_length); + assert_eq!(res, U256::ZERO); + } + } +} From 69b72366236c967399028de980be5190c82910a6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:29:54 +0200 Subject: [PATCH 12/87] Implement `expand` for `Int` --- src/int.rs | 1 + src/int/expand.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 src/int/expand.rs diff --git a/src/int.rs b/src/int.rs index 047bf83d..235a52e8 100644 --- a/src/int.rs +++ b/src/int.rs @@ -10,6 +10,7 @@ mod mul; mod div; mod sub; mod rand; +mod expand; mod cmp; /// Stack-allocated big _signed_ integer. diff --git a/src/int/expand.rs b/src/int/expand.rs new file mode 100644 index 00000000..a5230ed6 --- /dev/null +++ b/src/int/expand.rs @@ -0,0 +1,17 @@ +use crate::{ConstantTimeSelect, Int, Uint}; + +impl Int { + /// Expand the representation of `self` to an `Int`. + /// Assumes `T >= LIMBS`; panics otherwise. + #[inline(always)] + pub fn expand(&self) -> Int { + assert!(T >= LIMBS); + let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.sign_bit()); + let mut i = 0; + while i < T { + res.limbs[i] = self.0.limbs[i]; + i += 1; + } + Int::{ 0: res } + } +} \ No newline at end of file From 20cdb5f1b6d8f4617054b1578918ed04e37d2ae6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:32:15 +0200 Subject: [PATCH 13/87] Update `Int::encoding` --- src/int/encoding.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/uint.rs | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/int/encoding.rs b/src/int/encoding.rs index 9683d007..59321efc 100644 --- a/src/int/encoding.rs +++ b/src/int/encoding.rs @@ -3,7 +3,50 @@ use crate::{Int, Uint}; impl Int { + /// Create a new [`Int`] from the provided big endian bytes. + /// + /// See [`Uint::from_be_slice`] for more details. + pub const fn from_be_slice(bytes: &[u8]) -> Self { + Self(Uint::from_be_slice(bytes)) + } + + /// Create a new [`Int`] from the provided big endian hex string. + /// + /// Panics if the hex is malformed or not zero-padded accordingly for the size. + /// + /// See [`Uint::from_be_hex`] for more details. pub const fn from_be_hex(hex: &str) -> Self { Self(Uint::from_be_hex(hex)) } + + /// Create a new [`Int`] from the provided little endian bytes. + /// + /// See [`Uint::from_le_slice`] for more details. + pub const fn from_le_slice(bytes: &[u8]) -> Self { + Self(Uint::from_le_slice(bytes)) + } + + /// Create a new [`Int`] from the provided little endian hex string. + /// + /// Panics if the hex is malformed or not zero-padded accordingly for the size. + /// + /// See [`Uint::from_le_hex`] for more details. + pub const fn from_le_hex(hex: &str) -> Self { + Self(Uint::from_le_hex(hex)) + } +} + +/// Encode an [`Int`] to a big endian byte array of the given size. +pub(crate) const fn int_to_be_bytes( + int: &Int, +) -> [u8; BYTES] { + crate::uint::encoding::uint_to_be_bytes(&int.0) +} + + +/// Encode an [`Int`] to a little endian byte array of the given size. +pub(crate) const fn int_to_le_bytes( + int: &Int, +) -> [u8; BYTES] { + crate::uint::encoding::uint_to_le_bytes(&int.0) } diff --git a/src/uint.rs b/src/uint.rs index 44a1853d..d166f9b6 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -16,7 +16,7 @@ mod cmp; mod concat; mod div; pub(crate) mod div_limb; -mod encoding; +pub(crate) mod encoding; mod from; mod gcd; mod inv_mod; From 4ab45f5e18ed0e958d045cb56a8756ba55074cd3 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:32:42 +0200 Subject: [PATCH 14/87] Add `Int` aliases --- src/int.rs | 49 ++++++++++++++++++++++++++++++++++++++--------- src/int/macros.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/int/macros.rs diff --git a/src/int.rs b/src/int.rs index 235a52e8..f17ec912 100644 --- a/src/int.rs +++ b/src/int.rs @@ -1,8 +1,7 @@ -use core::fmt; - -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; +//! Stack-allocated big signed integers. -use crate::{Bounded, ConstantTimeSelect, ConstChoice, ConstCtOption, Limb, NonZero, Odd, Uint, Word}; +#[macro_use] +mod macros; mod encoding; mod add; @@ -13,6 +12,12 @@ mod rand; mod expand; mod cmp; +use core::fmt; +use num_traits::ConstZero; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; + +use crate::{Bounded, ConstantTimeSelect, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; + /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. /// @@ -281,11 +286,37 @@ impl fmt::UpperHex for Int { } } -#[cfg(target_pointer_width = "64")] -type I128 = Int<2>; - -#[cfg(target_pointer_width = "32")] -type I128 = Int<4>; +impl_int_aliases! { + (I64, 64, "64-bit"), + (I128, 128, "128-bit"), + (I192, 192, "192-bit"), + (I256, 256, "256-bit"), + (I320, 320, "320-bit"), + (I384, 384, "384-bit"), + (I448, 448, "448-bit"), + (I512, 512, "512-bit"), + (I576, 576, "576-bit"), + (I640, 640, "640-bit"), + (I704, 704, "704-bit"), + (I768, 768, "768-bit"), + (I832, 832, "832-bit"), + (I896, 896, "896-bit"), + (I960, 960, "960-bit"), + (I1024, 1024, "1024-bit"), + (I1280, 1280, "1280-bit"), + (I1536, 1536, "1536-bit"), + (I1792, 1792, "1792-bit"), + (I2048, 2048, "2048-bit"), + (I3072, 3072, "3072-bit"), + (I3584, 3584, "3584-bit"), + (I4096, 4096, "4096-bit"), + (I4224, 4224, "4224-bit"), + (I4352, 4352, "4352-bit"), + (I6144, 6144, "6144-bit"), + (I8192, 8192, "8192-bit"), + (I16384, 16384, "16384-bit"), + (I32768, 32768, "32768-bit") +} #[cfg(test)] #[allow(clippy::unwrap_used)] diff --git a/src/int/macros.rs b/src/int/macros.rs new file mode 100644 index 00000000..9d0be0f1 --- /dev/null +++ b/src/int/macros.rs @@ -0,0 +1,48 @@ +//! Macros used to define traits on aliases of `Int`. + +// TODO(tarcieri): use `generic_const_exprs` when stable to make generic around bits. +macro_rules! impl_int_aliases { + ($(($name:ident, $bits:expr, $doc:expr)),+) => { + $( + #[doc = $doc] + #[doc="unsigned big integer."] + pub type $name = Int<{ nlimbs!($bits) }>; + + impl $name { + /// Serialize as big endian bytes. + pub const fn to_be_bytes(&self) -> [u8; $bits / 8] { + encoding::int_to_be_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) + } + + /// Serialize as little endian bytes. + pub const fn to_le_bytes(&self) -> [u8; $bits / 8] { + encoding::int_to_le_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) + } + } + + impl Encoding for $name { + type Repr = [u8; $bits / 8]; + + #[inline] + fn from_be_bytes(bytes: Self::Repr) -> Self { + Self::from_be_slice(&bytes) + } + + #[inline] + fn from_le_bytes(bytes: Self::Repr) -> Self { + Self::from_le_slice(&bytes) + } + + #[inline] + fn to_be_bytes(&self) -> Self::Repr { + encoding::int_to_be_bytes(self) + } + + #[inline] + fn to_le_bytes(&self) -> Self::Repr { + encoding::int_to_le_bytes(self) + } + } + )+ + }; +} \ No newline at end of file From 57000c5cff512a6ba75ad19b528c41409b027d59 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:34:17 +0200 Subject: [PATCH 15/87] Impl some more `Int` traits --- src/int.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/int.rs b/src/int.rs index f17ec912..fba75e76 100644 --- a/src/int.rs +++ b/src/int.rs @@ -250,11 +250,30 @@ impl Default for Int { // TODO: impl Integer -// TODO: impl ConstZero +impl ConstZero for Int { + const ZERO: Self = Self::ZERO; +} + +impl num_traits::Zero for Int { + fn zero() -> Self { + Self::ZERO + } -// TODO: impl num_traits::One + fn is_zero(&self) -> bool { + self.0.ct_eq(&Self::ZERO.0).into() + } +} + +impl num_traits::One for Int { + fn one() -> Self { + Self::ONE + } + + fn is_one(&self) -> bool { + self.0.ct_eq(&Self::ONE.0).into() + } +} -// TODO: impl num_traits::One impl fmt::Debug for Int { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { From e29e4258b89b64a7a7456923c51c526d08abd15e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:40:53 +0200 Subject: [PATCH 16/87] Add `Int` benchmarks --- benches/int.rs | 192 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 benches/int.rs diff --git a/benches/int.rs b/benches/int.rs new file mode 100644 index 00000000..c39491cc --- /dev/null +++ b/benches/int.rs @@ -0,0 +1,192 @@ +use std::ops::{Add, Div, Sub}; +use criterion::{BatchSize, black_box, Criterion, criterion_group, criterion_main}; +use rand_core::OsRng; +use crypto_bigint::{I1024, I128, I2048, I256, I4096, I512, NonZero, Random}; + +fn bench_mul(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("split_mul, I128xI128", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I256xI256", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I4096xI4096", |b| { + b.iter_batched( + || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + +fn bench_div(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("div, I256/I128, full size", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I128::random(&mut OsRng).expand::<{I256::LIMBS}>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I512/I256, full size", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I256::random(&mut OsRng).expand::<{I512::LIMBS}>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I4096/I2048, full size", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I2048::random(&mut OsRng).expand::<{I4096::LIMBS}>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +fn bench_add(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("add, I128+I128, full size", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I256+I256, full size", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I256::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I1024+I1024, full size", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I1024::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I4096+I4096, full size", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.add(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +fn bench_sub(c: &mut Criterion) { + let mut group = c.benchmark_group("wrapping ops"); + + group.bench_function("sub, I128-I128, full size", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I256-I256, full size", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I256::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I1024-I1024, full size", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I1024::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I4096-I4096, full size", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_mul, + bench_div, + bench_add, + bench_sub, +); + +criterion_main!(benches); \ No newline at end of file From 869a8e7a5a9f75a1c14b00721b9d83ffb58dab12 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:52:54 +0200 Subject: [PATCH 17/87] Remove superfluous tests --- src/int/rand.rs | 78 ------------------------------------------------- 1 file changed, 78 deletions(-) diff --git a/src/int/rand.rs b/src/int/rand.rs index 8c988ee9..b672a0d1 100644 --- a/src/int/rand.rs +++ b/src/int/rand.rs @@ -28,81 +28,3 @@ impl RandomBits for Int { .map(|val| Self(val)) } } - -#[cfg(test)] -mod tests { - use crate::{Limb, NonZero, RandomBits, RandomMod, U256}; - use rand_core::SeedableRng; - - #[test] - fn random_mod() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); - - // Ensure `random_mod` runs in a reasonable amount of time - let modulus = NonZero::new(U256::from(42u8)).unwrap(); - let res = U256::random_mod(&mut rng, &modulus); - - // Check that the value is in range - assert!(res < U256::from(42u8)); - - // Ensure `random_mod` runs in a reasonable amount of time - // when the modulus is larger than 1 limb - let modulus = NonZero::new(U256::from(0x10000000000000001u128)).unwrap(); - let res = U256::random_mod(&mut rng, &modulus); - - // Check that the value is in range - assert!(res < U256::from(0x10000000000000001u128)); - } - - #[test] - fn random_bits() { - let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(1); - - let lower_bound = 16; - - // Full length of the integer - let bit_length = U256::BITS; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert!(res > (U256::ONE << (bit_length - lower_bound))); - } - - // A multiple of limb size - let bit_length = U256::BITS - Limb::BITS; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert!(res > (U256::ONE << (bit_length - lower_bound))); - assert!(res < (U256::ONE << bit_length)); - } - - // A multiple of 8 - let bit_length = U256::BITS - Limb::BITS - 8; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert!(res > (U256::ONE << (bit_length - lower_bound))); - assert!(res < (U256::ONE << bit_length)); - } - - // Not a multiple of 8 - let bit_length = U256::BITS - Limb::BITS - 8 - 3; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert!(res > (U256::ONE << (bit_length - lower_bound))); - assert!(res < (U256::ONE << bit_length)); - } - - // One incomplete limb - let bit_length = 7; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert!(res < (U256::ONE << bit_length)); - } - - // Zero bits - let bit_length = 0; - for _ in 0..10 { - let res = U256::random_bits(&mut rng, bit_length); - assert_eq!(res, U256::ZERO); - } - } -} From f6ef7edf3918ed19b58e9cd358c9a992786dce07 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 11:54:05 +0200 Subject: [PATCH 18/87] Fix fmt --- benches/int.rs | 22 +++++++--------- src/int.rs | 38 +++++++++++++++------------ src/int/add.rs | 27 ++++++++++--------- src/int/cmp.rs | 6 ++--- src/int/div.rs | 10 +++---- src/int/encoding.rs | 1 - src/int/expand.rs | 4 +-- src/int/macros.rs | 2 +- src/int/mul.rs | 9 ++++--- src/int/rand.rs | 9 ++++--- src/int/sub.rs | 40 +++++++++++++++++++--------- src/lib.rs | 64 +++++++++++++++++++++------------------------ 12 files changed, 124 insertions(+), 108 deletions(-) diff --git a/benches/int.rs b/benches/int.rs index c39491cc..b48c3d55 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -1,7 +1,9 @@ use std::ops::{Add, Div, Sub}; -use criterion::{BatchSize, black_box, Criterion, criterion_group, criterion_main}; + +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use rand_core::OsRng; -use crypto_bigint::{I1024, I128, I2048, I256, I4096, I512, NonZero, Random}; + +use crypto_bigint::{NonZero, Random, I1024, I128, I2048, I256, I4096, I512}; fn bench_mul(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); @@ -38,7 +40,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I256::random(&mut OsRng); - let y = I128::random(&mut OsRng).expand::<{I256::LIMBS}>(); + let y = I128::random(&mut OsRng).expand::<{ I256::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -50,7 +52,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I512::random(&mut OsRng); - let y = I256::random(&mut OsRng).expand::<{I512::LIMBS}>(); + let y = I256::random(&mut OsRng).expand::<{ I512::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -62,7 +64,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I4096::random(&mut OsRng); - let y = I2048::random(&mut OsRng).expand::<{I4096::LIMBS}>(); + let y = I2048::random(&mut OsRng).expand::<{ I4096::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -181,12 +183,6 @@ fn bench_sub(c: &mut Criterion) { group.finish(); } -criterion_group!( - benches, - bench_mul, - bench_div, - bench_add, - bench_sub, -); +criterion_group!(benches, bench_mul, bench_div, bench_add, bench_sub,); -criterion_main!(benches); \ No newline at end of file +criterion_main!(benches); diff --git a/src/int.rs b/src/int.rs index fba75e76..c204a7a1 100644 --- a/src/int.rs +++ b/src/int.rs @@ -1,27 +1,32 @@ //! Stack-allocated big signed integers. +use core::fmt; + +use num_traits::ConstZero; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; + +use crate::{ + Bounded, ConstChoice, ConstCtOption, ConstantTimeSelect, Encoding, Limb, NonZero, Odd, Uint, + Word, +}; + #[macro_use] mod macros; -mod encoding; mod add; -mod mul; +mod cmp; mod div; -mod sub; -mod rand; +mod encoding; mod expand; -mod cmp; - -use core::fmt; -use num_traits::ConstZero; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; - -use crate::{Bounded, ConstantTimeSelect, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; +mod mul; +mod rand; +mod sub; /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. /// /// Created as a [`Uint`] newtype. +#[allow(clippy::derived_hash_with_manual_eq)] #[derive(Copy, Clone, Hash)] pub struct Int(Uint); @@ -63,7 +68,10 @@ impl Int { /// Construct new [`Int`] from a sign and magnitude. /// Returns `None` when the magnitude does not fit in an [`Int`]. - pub fn new_from_sign_and_magnitude(is_negative: Choice, magnitude: Uint) -> CtOption { + pub fn new_from_sign_and_magnitude( + is_negative: Choice, + magnitude: Uint, + ) -> CtOption { CtOption::new( Self(magnitude).negate_if_unsafe(is_negative).0, !magnitude.ct_gt(&Int::MAX.0) | is_negative & magnitude.ct_eq(&Int::MIN.0), @@ -179,10 +187,7 @@ impl Int { /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive /// equivalent of that. pub fn neg(&self) -> CtOption { - CtOption::new( - self.negate_unsafe().0, - !self.is_minimal() - ) + CtOption::new(self.negate_unsafe().0, !self.is_minimal()) } /// The sign and magnitude of this [`Int`], as well as whether it is zero. @@ -274,7 +279,6 @@ impl num_traits::One for Int { } } - impl fmt::Debug for Int { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Int(0x{self:X})") diff --git a/src/int/add.rs b/src/int/add.rs index bec567b9..ada7933f 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -1,12 +1,13 @@ //! [`Int`] addition operations. use core::ops::{Add, AddAssign}; + use num_traits::WrappingAdd; use subtle::{ConstantTimeEq, CtOption}; + use crate::{Checked, CheckedAdd, Int, Wrapping}; impl Int { - /// Perform checked addition. fn checked_add_internal(&self, rhs: &Self) -> CtOption { // Step 1. add operands @@ -19,13 +20,10 @@ impl Int { // // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) let self_msb = self.sign_bit(); - let overflow= self_msb.ct_eq(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); + let overflow = self_msb.ct_eq(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); // Step 3. Construct result - CtOption::new( - res, - !overflow, - ) + CtOption::new(res, !overflow) } /// Perform wrapping addition, discarding overflow. @@ -34,7 +32,6 @@ impl Int { } } - impl Add for Int { type Output = Self; @@ -105,14 +102,20 @@ mod tests { #[cfg(test)] mod tests { - use crate::{CheckedAdd, Int, U128}; use crate::int::I128; + use crate::{CheckedAdd, Int, U128}; #[test] fn checked_add() { - let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; - let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; - let two = Int{ 0: U128::from(2u32) }; + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let max_minus_one = Int { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + let two = Int { + 0: U128::from(2u32), + }; // lhs = MIN @@ -200,4 +203,4 @@ mod tests { assert!(bool::from(result.is_none())); } } -} \ No newline at end of file +} diff --git a/src/int/cmp.rs b/src/int/cmp.rs index d1bda140..f640430a 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -4,9 +4,10 @@ #![allow(dead_code)] use core::cmp::Ordering; + use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; -use crate::{ConstChoice, Int, Uint}; +use crate::{ConstChoice, Int, Uint}; impl Int { /// Return `b` if `c` is truthy, otherwise return `a`. @@ -60,7 +61,6 @@ impl Int { } } - impl ConstantTimeEq for Int { #[inline] fn ct_eq(&self, other: &Self) -> Choice { @@ -105,4 +105,4 @@ impl PartialEq for Int { fn eq(&self, other: &Self) -> bool { self.ct_eq(other).into() } -} \ No newline at end of file +} diff --git a/src/int/div.rs b/src/int/div.rs index 5e0a8502..b1aac2f0 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -7,7 +7,6 @@ use subtle::{ConstantTimeEq, CtOption}; use crate::{CheckedDiv, Int, NonZero}; impl Int { - /// Perform checked division, returning a [`CtOption`] which `is_some` /// only if the rhs != 0 pub fn checked_div(&self, rhs: &Self) -> CtOption { @@ -62,14 +61,15 @@ impl Div>> for Int { } } - #[cfg(test)] mod tests { - use crate::int::{I128, Int}; + use crate::int::{Int, I128}; #[test] fn test_checked_div() { - let min_plus_one = Int { 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; // lhs = min @@ -156,4 +156,4 @@ mod tests { let result = I128::MAX.checked_div(&I128::MAX); assert_eq!(result.unwrap(), I128::ONE); } -} \ No newline at end of file +} diff --git a/src/int/encoding.rs b/src/int/encoding.rs index 59321efc..9e66e789 100644 --- a/src/int/encoding.rs +++ b/src/int/encoding.rs @@ -43,7 +43,6 @@ pub(crate) const fn int_to_be_bytes( crate::uint::encoding::uint_to_be_bytes(&int.0) } - /// Encode an [`Int`] to a little endian byte array of the given size. pub(crate) const fn int_to_le_bytes( int: &Int, diff --git a/src/int/expand.rs b/src/int/expand.rs index a5230ed6..683832be 100644 --- a/src/int/expand.rs +++ b/src/int/expand.rs @@ -12,6 +12,6 @@ impl Int { res.limbs[i] = self.0.limbs[i]; i += 1; } - Int::{ 0: res } + Int::(res) } -} \ No newline at end of file +} diff --git a/src/int/macros.rs b/src/int/macros.rs index 9d0be0f1..eea4527a 100644 --- a/src/int/macros.rs +++ b/src/int/macros.rs @@ -45,4 +45,4 @@ macro_rules! impl_int_aliases { } )+ }; -} \ No newline at end of file +} diff --git a/src/int/mul.rs b/src/int/mul.rs index 5c864793..869aabc3 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -1,6 +1,7 @@ //! [`Int`] multiplication operations. use core::ops::{Mul, MulAssign}; + use subtle::{Choice, ConstantTimeEq, CtOption}; use crate::{Checked, CheckedMul, Int, Uint, Zero}; @@ -32,7 +33,7 @@ impl CheckedMul> for fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); let val = Self::new_from_sign_and_magnitude(is_negative, lo); - val.and_then(|int| { CtOption::new(int, hi.is_zero()) }) + val.and_then(|int| CtOption::new(int, hi.is_zero())) } } @@ -83,12 +84,14 @@ impl MulAssign<&Checked>> for Checked> #[cfg(test)] mod tests { + use crate::int::{Int, I128}; use crate::CheckedMul; - use crate::int::{I128, Int}; #[test] fn test_checked_mul() { - let min_plus_one = Int { 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; // lhs = min diff --git a/src/int/rand.rs b/src/int/rand.rs index b672a0d1..37e25f40 100644 --- a/src/int/rand.rs +++ b/src/int/rand.rs @@ -1,9 +1,11 @@ //! Random number generator support -use super::Uint; -use crate::{Int, Random, RandomBits, RandomBitsError}; use rand_core::CryptoRngCore; +use crate::{Int, Random, RandomBits, RandomBitsError}; + +use super::Uint; + impl Random for Int { /// Generate a cryptographically secure random [`Int`]. fn random(rng: &mut impl CryptoRngCore) -> Self { @@ -24,7 +26,6 @@ impl RandomBits for Int { bit_length: u32, bits_precision: u32, ) -> Result { - Uint::try_random_bits_with_precision(rng, bit_length, bits_precision) - .map(|val| Self(val)) + Uint::try_random_bits_with_precision(rng, bit_length, bits_precision).map(Self) } } diff --git a/src/int/sub.rs b/src/int/sub.rs index 501108ef..cd9647d9 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -1,8 +1,10 @@ //! [`Int`] subtraction operations. use core::ops::{Sub, SubAssign}; + use num_traits::WrappingSub; use subtle::{ConstantTimeEq, CtOption}; + use crate::{Checked, CheckedSub, Int, Wrapping}; impl CheckedSub for Int { @@ -20,10 +22,7 @@ impl CheckedSub for Int { let underflow = self_msb.ct_ne(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); // Step 3. Construct result - CtOption::new( - res, - !underflow, - ) + CtOption::new(res, !underflow) } } @@ -80,15 +79,24 @@ mod tests { #[cfg(test)] mod tests { use num_traits::WrappingSub; - use crate::{CheckedSub, Int, U128}; + use crate::int::I128; + use crate::{CheckedSub, Int, U128}; #[test] fn checked_sub() { - let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; - let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; - let two = Int{ 0: U128::from(2u32) }; - let min_plus_two = Int{ 0: I128::MIN.0.wrapping_add(&two.0) }; + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let max_minus_one = Int { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + let two = Int { + 0: U128::from(2u32), + }; + let min_plus_two = Int { + 0: I128::MIN.0.wrapping_add(&two.0), + }; // lhs = MIN @@ -178,9 +186,15 @@ mod tests { #[test] fn wrapping_sub() { - let min_plus_one = Int{ 0: I128::MIN.0.wrapping_add(&I128::ONE.0) }; - let two = Int{ 0: U128::from(2u32) }; - let max_minus_one = Int{ 0: I128::MAX.0.wrapping_sub(&I128::ONE.0) }; + let min_plus_one = Int { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let two = Int { + 0: U128::from(2u32), + }; + let max_minus_one = Int { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; // + sub - let result = I128::ONE.wrapping_sub(&I128::MIN); @@ -195,4 +209,4 @@ mod tests { assert_eq!(result, max_minus_one); } } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 0e8390ad..4f7534b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,29 +155,27 @@ #[macro_use] extern crate alloc; -#[macro_use] -mod macros; - -pub mod modular; +#[cfg(feature = "rand_core")] +pub use rand_core; +#[cfg(feature = "rlp")] +pub use rlp; +pub use subtle; +#[cfg(feature = "zeroize")] +pub use zeroize; #[cfg(feature = "hybrid-array")] -mod array; -mod checked; -mod const_choice; -mod limb; -mod int; -mod non_zero; -mod odd; -mod primitives; -mod traits; -mod uint; -mod wrapping; +pub use { + crate::array::{ArrayDecoding, ArrayEncoding, ByteArray}, + hybrid_array::{self, typenum::consts}, +}; +#[cfg(feature = "alloc")] +pub use crate::uint::boxed::BoxedUint; pub use crate::{ checked::Checked, const_choice::{ConstChoice, ConstCtOption}, - limb::{Limb, WideWord, Word}, int::*, + limb::{Limb, WideWord, Word}, non_zero::NonZero, odd::Odd, traits::*, @@ -185,30 +183,28 @@ pub use crate::{ uint::*, wrapping::Wrapping, }; -pub use subtle; - -#[cfg(feature = "alloc")] -pub use crate::uint::boxed::BoxedUint; - -#[cfg(feature = "hybrid-array")] -pub use { - crate::array::{ArrayDecoding, ArrayEncoding, ByteArray}, - hybrid_array::{self, typenum::consts}, -}; -#[cfg(feature = "rand_core")] -pub use rand_core; +#[macro_use] +mod macros; -#[cfg(feature = "rlp")] -pub use rlp; +pub mod modular; -#[cfg(feature = "zeroize")] -pub use zeroize; +#[cfg(feature = "hybrid-array")] +mod array; +mod checked; +mod const_choice; +mod int; +mod limb; +mod non_zero; +mod odd; +mod primitives; +mod traits; +mod uint; +mod wrapping; /// Import prelude for this crate: includes important traits. pub mod prelude { - pub use crate::traits::*; - #[cfg(feature = "hybrid-array")] pub use crate::array::{ArrayDecoding, ArrayEncoding}; + pub use crate::traits::*; } From c610b08535d4220ad5c45b33ef8974b9df70c049 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 12:03:18 +0200 Subject: [PATCH 19/87] Introduce `Int::new_from_uint` --- src/int.rs | 7 +++++++ src/int/expand.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/int.rs b/src/int.rs index c204a7a1..031a4fd9 100644 --- a/src/int.rs +++ b/src/int.rs @@ -66,6 +66,13 @@ impl Int { Self(Uint::new(limbs)) } + /// Const-friendly [`Int`] constructor. + /// Note: interprets the `value` as an `Int`. For a proper conversion, + /// see [`Int::new_from_sign_and_magnitude`]. + pub const fn new_from_uint(value: Uint) -> Self { + Self(value) + } + /// Construct new [`Int`] from a sign and magnitude. /// Returns `None` when the magnitude does not fit in an [`Int`]. pub fn new_from_sign_and_magnitude( diff --git a/src/int/expand.rs b/src/int/expand.rs index 683832be..9fa31ce1 100644 --- a/src/int/expand.rs +++ b/src/int/expand.rs @@ -12,6 +12,6 @@ impl Int { res.limbs[i] = self.0.limbs[i]; i += 1; } - Int::(res) + Int::new_from_uint(res) } } From 0669c1b0a132b09d7f1ac2a096b4ec3e22a0cbf6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 12:05:32 +0200 Subject: [PATCH 20/87] Feature lock `Int::rand` --- src/int.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/int.rs b/src/int.rs index 031a4fd9..3f0c6489 100644 --- a/src/int.rs +++ b/src/int.rs @@ -19,9 +19,11 @@ mod div; mod encoding; mod expand; mod mul; -mod rand; mod sub; +#[cfg(feature = "rand_core")] +mod rand; + /// Stack-allocated big _signed_ integer. /// See [`Uint`] for _unsigned_ integers. /// From d7029e2fd42c13fbd7a0bab355b4a319ff55a036 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 12:24:48 +0200 Subject: [PATCH 21/87] Add `Int::cmp` tests --- src/int/cmp.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index f640430a..7019140c 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -106,3 +106,70 @@ impl PartialEq for Int { self.ct_eq(other).into() } } + +#[cfg(test)] +mod tests { + use crate::{I128, U128}; + + #[test] + fn test_is_nonzero() { + assert_eq!(I128::MAX.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::ONE.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::ZERO.is_nonzero().to_u8(), 0u8); + assert_eq!(I128::MINUS_ONE.is_nonzero().to_u8(), 1u8); + assert_eq!(I128::MAX.is_nonzero().to_u8(), 1u8); + } + + #[test] + fn test_is_odd() { + let two = I128::new_from_uint(U128::from(2u32)); + assert_eq!(I128::MAX.is_odd().to_u8(), 1u8); + assert_eq!(two.is_odd().to_u8(), 0u8); + assert_eq!(I128::ONE.is_odd().to_u8(), 1u8); + assert_eq!(I128::ZERO.is_odd().to_u8(), 0u8); + assert_eq!(I128::MINUS_ONE.is_odd().to_u8(), 1u8); + assert_eq!(two.neg().unwrap().is_odd().to_u8(), 0u8); + assert_eq!(I128::MAX.is_odd().to_u8(), 1u8); + } + + #[test] + fn test_eq() { + assert_eq!(I128::ZERO, I128::ONE.wrapping_add(&I128::MINUS_ONE)); + assert_eq!(I128::ONE, I128::ZERO.wrapping_add(&I128::ONE)); + assert_ne!(I128::ONE, I128::ZERO); + } + + #[test] + fn test_gt() { + // x > y + assert!(I128::MAX > I128::ONE); + assert!(I128::ONE > I128::ZERO); + assert!(I128::ZERO > I128::MINUS_ONE); + assert!(I128::MINUS_ONE > I128::MIN); + assert!(I128::MAX > I128::MIN); + + // a !> a + assert_ne!(true, I128::MAX > I128::MAX); + assert_ne!(true, I128::ONE > I128::ONE); + assert_ne!(true, I128::ZERO > I128::ZERO); + assert_ne!(true, I128::MINUS_ONE > I128::MINUS_ONE); + assert_ne!(true, I128::MIN > I128::MIN); + } + + #[test] + fn test_lt() { + // x < y + assert!(I128::ONE < I128::MAX); + assert!(I128::ZERO < I128::ONE); + assert!(I128::MINUS_ONE < I128::ZERO); + assert!(I128::MIN < I128::MINUS_ONE); + assert!(I128::MIN < I128::MAX); + + // a !< a + assert_ne!(true, I128::MAX < I128::MAX); + assert_ne!(true, I128::ONE < I128::ONE); + assert_ne!(true, I128::ZERO < I128::ZERO); + assert_ne!(true, I128::MINUS_ONE < I128::MINUS_ONE); + assert_ne!(true, I128::MIN < I128::MIN); + } +} From a653375e25f377d0d4047d2e2ae6164c6c51efb8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 12:34:28 +0200 Subject: [PATCH 22/87] Fix `Int::expand` bug --- src/int/expand.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/int/expand.rs b/src/int/expand.rs index 9fa31ce1..906986bf 100644 --- a/src/int/expand.rs +++ b/src/int/expand.rs @@ -8,7 +8,7 @@ impl Int { assert!(T >= LIMBS); let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.sign_bit()); let mut i = 0; - while i < T { + while i < LIMBS { res.limbs[i] = self.0.limbs[i]; i += 1; } From 57f65f8af59c8e496554520e00c8ceaf3279fc35 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 12:34:42 +0200 Subject: [PATCH 23/87] Attach `Int` benchmark --- Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9caf1377..95bfde87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,3 +76,7 @@ harness = false [[bench]] name = "uint" harness = false + +[[bench]] +name = "int" +harness = false From b23483da960a8476b95ab3372166bbac51b2bda5 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 13:00:59 +0200 Subject: [PATCH 24/87] Improve `Int` benchmarks --- benches/int.rs | 131 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 114 insertions(+), 17 deletions(-) diff --git a/benches/int.rs b/benches/int.rs index b48c3d55..442728f5 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -1,6 +1,7 @@ -use std::ops::{Add, Div, Sub}; +use std::ops::Div; use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; +use num_traits::WrappingSub; use rand_core::OsRng; use crypto_bigint::{NonZero, Random, I1024, I128, I2048, I256, I4096, I512}; @@ -24,6 +25,30 @@ fn bench_mul(c: &mut Criterion) { ) }); + group.bench_function("split_mul, I512xI512", |b| { + b.iter_batched( + || (I512::random(&mut OsRng), I512::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I1024xI1024", |b| { + b.iter_batched( + || (I1024::random(&mut OsRng), I1024::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("split_mul, I2048xI2048", |b| { + b.iter_batched( + || (I2048::random(&mut OsRng), I2048::random(&mut OsRng)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); + group.bench_function("split_mul, I4096xI4096", |b| { b.iter_batched( || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), @@ -60,6 +85,30 @@ fn bench_div(c: &mut Criterion) { ) }); + group.bench_function("div, I1024/I512, full size", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I512::random(&mut OsRng).expand::<{ I1024::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I2048/I1024, full size", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I1024::random(&mut OsRng).expand::<{ I2048::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + group.bench_function("div, I4096/I2048, full size", |b| { b.iter_batched( || { @@ -78,50 +127,74 @@ fn bench_div(c: &mut Criterion) { fn bench_add(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); - group.bench_function("add, I128+I128, full size", |b| { + group.bench_function("add, I128+I128", |b| { b.iter_batched( || { let x = I128::random(&mut OsRng); let y = I128::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.add(&y)), + |(x, y)| black_box(x.wrapping_add(&y)), BatchSize::SmallInput, ) }); - group.bench_function("add, I256+I256, full size", |b| { + group.bench_function("add, I256+I256", |b| { b.iter_batched( || { let x = I256::random(&mut OsRng); let y = I256::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.add(&y)), + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I512+I512", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I512::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), BatchSize::SmallInput, ) }); - group.bench_function("add, I1024+I1024, full size", |b| { + group.bench_function("add, I1024+I1024", |b| { b.iter_batched( || { let x = I1024::random(&mut OsRng); let y = I1024::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.add(&y)), + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("add, I2048+I2048", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I2048::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), BatchSize::SmallInput, ) }); - group.bench_function("add, I4096+I4096, full size", |b| { + group.bench_function("add, I4096+I4096", |b| { b.iter_batched( || { let x = I4096::random(&mut OsRng); let y = I4096::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.add(&y)), + |(x, y)| black_box(x.wrapping_add(&y)), BatchSize::SmallInput, ) }); @@ -132,50 +205,74 @@ fn bench_add(c: &mut Criterion) { fn bench_sub(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); - group.bench_function("sub, I128-I128, full size", |b| { + group.bench_function("sub, I128-I128", |b| { b.iter_batched( || { let x = I128::random(&mut OsRng); let y = I128::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.sub(&y)), + |(x, y)| black_box(x.wrapping_sub(&y)), BatchSize::SmallInput, ) }); - group.bench_function("sub, I256-I256, full size", |b| { + group.bench_function("sub, I256-I256", |b| { b.iter_batched( || { let x = I256::random(&mut OsRng); let y = I256::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.sub(&y)), + |(x, y)| black_box(x.wrapping_sub(&y)), BatchSize::SmallInput, ) }); - group.bench_function("sub, I1024-I1024, full size", |b| { + group.bench_function("sub, I512-I512", |b| { + b.iter_batched( + || { + let x = I512::random(&mut OsRng); + let y = I512::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I1024-I1024", |b| { b.iter_batched( || { let x = I1024::random(&mut OsRng); let y = I1024::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.sub(&y)), + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("sub, I2048-I2048", |b| { + b.iter_batched( + || { + let x = I2048::random(&mut OsRng); + let y = I2048::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), BatchSize::SmallInput, ) }); - group.bench_function("sub, I4096-I4096, full size", |b| { + group.bench_function("sub, I4096-I4096", |b| { b.iter_batched( || { let x = I4096::random(&mut OsRng); let y = I4096::random(&mut OsRng); (x, y) }, - |(x, y)| black_box(x.sub(&y)), + |(x, y)| black_box(x.wrapping_sub(&y)), BatchSize::SmallInput, ) }); From 30b4676fef53255aec9183d82fea2218ebf2ea88 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 14:45:45 +0200 Subject: [PATCH 25/87] Speed up `Int::negate_unsafe` --- src/int.rs | 13 +++++++------ src/uint/neg.rs | 7 ++++++- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/int.rs b/src/int.rs index 3f0c6489..7b2bff79 100644 --- a/src/int.rs +++ b/src/int.rs @@ -169,11 +169,9 @@ impl Int { /// /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. #[inline] - fn negate_unsafe(&self) -> (Self, Choice) { - let inverted = self.0.bitxor(&Self::FULL_MASK.0); - let (res, carry) = inverted.adc(&Uint::ONE, Limb::ZERO); - let is_zero = ConstChoice::from_word_lsb(carry.0).into(); - (Self(res), is_zero) + pub const fn negate_unsafe(&self) -> (Self, ConstChoice) { + let (val, carry) = self.0.wrapping_negc(); + (Self(val), ConstChoice::from_word_lsb(carry)) } /// Perform the [two's complement "negate" operation](Int::negate_unsafe) on this [`Int`] @@ -188,7 +186,10 @@ impl Int { #[inline] fn negate_if_unsafe(&self, negate: Choice) -> (Int, Choice) { let (negated, is_zero) = self.negate_unsafe(); - (Self(Uint::ct_select(&self.0, &negated.0, negate)), is_zero) + ( + Self(Uint::ct_select(&self.0, &negated.0, negate)), + is_zero.into(), + ) } /// Map this [`Int`] to `-self` if possible. diff --git a/src/uint/neg.rs b/src/uint/neg.rs index e734f1fb..053454cf 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -3,6 +3,11 @@ use crate::{Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { + self.wrapping_negc().0 + } + + /// Perform wrapping negation; returns the resulting carry. + pub const fn wrapping_negc(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; @@ -12,7 +17,7 @@ impl Uint { carry = r >> Limb::BITS; i += 1; } - Uint::new(ret) + (Uint::new(ret), carry as Word) } } From 580e0a53232dd00e16c51a060a733affad234e37 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 14:49:13 +0200 Subject: [PATCH 26/87] Improve `Int::sign_bit` --- src/const_choice.rs | 10 +++++++++- src/int.rs | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index 524a3525..d8abc596 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -49,6 +49,13 @@ impl ConstChoice { Self(value.wrapping_neg()) } + /// Returns the truthy value if the most significant bit of `value` is `1`, + /// and the falsy value if it equals `0`. + #[inline] + pub(crate) const fn from_word_msb(value: Word) -> Self { + Self::from_word_lsb(value >> (Word::BITS - 1)) + } + /// Returns the truthy value if `value == 1`, and the falsy value if `value == 0`. /// Panics for other values. #[inline] @@ -450,9 +457,10 @@ impl #[cfg(test)] mod tests { - use super::ConstChoice; use crate::{WideWord, Word}; + use super::ConstChoice; + #[test] fn from_u64_lsb() { assert_eq!(ConstChoice::from_u64_lsb(0), ConstChoice::FALSE); diff --git a/src/int.rs b/src/int.rs index 7b2bff79..aa062d31 100644 --- a/src/int.rs +++ b/src/int.rs @@ -142,7 +142,7 @@ impl Int { /// Get the sign bit of this [`Int`] as `Choice`. pub fn sign_bit(&self) -> Choice { - Choice::from((self.0.to_words()[LIMBS - 1] >> (Word::BITS - 1)) as u8) + ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]).into() } /// View the data in this type as an [`Uint`] instead. @@ -202,7 +202,7 @@ impl Int { /// The sign and magnitude of this [`Int`], as well as whether it is zero. pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { - let sign = self.sign_bit(); + let sign = self.sign_bit().into(); let (magnitude, is_zero) = self.negate_if_unsafe(sign); (sign, magnitude.0, is_zero) } From 220d3292952591a8eb187b3de047c22fb2890491 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 16 Oct 2024 14:52:33 +0200 Subject: [PATCH 27/87] Remove useless conversion --- src/int.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/int.rs b/src/int.rs index aa062d31..b0377802 100644 --- a/src/int.rs +++ b/src/int.rs @@ -202,7 +202,7 @@ impl Int { /// The sign and magnitude of this [`Int`], as well as whether it is zero. pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { - let sign = self.sign_bit().into(); + let sign = self.sign_bit(); let (magnitude, is_zero) = self.negate_if_unsafe(sign); (sign, magnitude.0, is_zero) } From c7623078765210facef71614ed6791f69882a0d1 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:23:09 +0200 Subject: [PATCH 28/87] Fix `Int::as_uint` return type --- src/int.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/int.rs b/src/int.rs index b0377802..e5fbd62c 100644 --- a/src/int.rs +++ b/src/int.rs @@ -145,9 +145,9 @@ impl Int { ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]).into() } - /// View the data in this type as an [`Uint`] instead. - pub const fn as_uint(&self) -> Uint { - self.0 + /// Interpret the data in this type as a [`Uint`] instead. + pub const fn as_uint(&self) -> &Uint { + &self.0 } /// Whether this [`Int`] is equal to `Self::MIN`. From 98075bbcb9cf68898dfb5e3fc1b1d604010399d5 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:24:39 +0200 Subject: [PATCH 29/87] Rename `Uint::wrapping_negc` to `wrapping_neg_with_carry` --- src/int.rs | 2 +- src/uint/neg.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/int.rs b/src/int.rs index e5fbd62c..e596d81c 100644 --- a/src/int.rs +++ b/src/int.rs @@ -170,7 +170,7 @@ impl Int { /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. #[inline] pub const fn negate_unsafe(&self) -> (Self, ConstChoice) { - let (val, carry) = self.0.wrapping_negc(); + let (val, carry) = self.0.wrapping_neg_with_carry(); (Self(val), ConstChoice::from_word_lsb(carry)) } diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 053454cf..aef06753 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -3,11 +3,11 @@ use crate::{Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { - self.wrapping_negc().0 + self.wrapping_neg_with_carry().0 } - /// Perform wrapping negation; returns the resulting carry. - pub const fn wrapping_negc(&self) -> (Self, Word) { + /// Perform wrapping negation; also returns the resulting carry. + pub const fn wrapping_neg_with_carry(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; From a064fea1a161bb1fa9b8785b4c07b473c3d50156 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:35:22 +0200 Subject: [PATCH 30/87] Make `Int::sign_bit` `const` --- src/int.rs | 23 ++++++++++++----------- src/int/add.rs | 7 ++++--- src/int/expand.rs | 2 +- src/int/sub.rs | 7 ++++--- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/int.rs b/src/int.rs index e596d81c..67003ffe 100644 --- a/src/int.rs +++ b/src/int.rs @@ -140,9 +140,9 @@ impl Int { ConstCtOption::new(Odd(self), self.0.is_odd()) } - /// Get the sign bit of this [`Int`] as `Choice`. - pub fn sign_bit(&self) -> Choice { - ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]).into() + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn sign_bit(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) } /// Interpret the data in this type as a [`Uint`] instead. @@ -202,7 +202,7 @@ impl Int { /// The sign and magnitude of this [`Int`], as well as whether it is zero. pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { - let sign = self.sign_bit(); + let sign = self.sign_bit().into(); let (magnitude, is_zero) = self.negate_if_unsafe(sign); (sign, magnitude.0, is_zero) } @@ -357,6 +357,7 @@ mod tests { use subtle::ConditionallySelectable; use crate::int::I128; + use crate::ConstChoice; #[cfg(target_pointer_width = "64")] #[test] @@ -419,17 +420,17 @@ mod tests { #[test] fn sign_bit() { - assert_eq!(I128::MIN.sign_bit().unwrap_u8(), 1u8); - assert_eq!(I128::MINUS_ONE.sign_bit().unwrap_u8(), 1u8); - assert_eq!(I128::ZERO.sign_bit().unwrap_u8(), 0u8); - assert_eq!(I128::ONE.sign_bit().unwrap_u8(), 0u8); - assert_eq!(I128::MAX.sign_bit().unwrap_u8(), 0u8); + assert_eq!(I128::MIN.sign_bit(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.sign_bit(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.sign_bit(), ConstChoice::FALSE); + assert_eq!(I128::ONE.sign_bit(), ConstChoice::FALSE); + assert_eq!(I128::MAX.sign_bit(), ConstChoice::FALSE); let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.sign_bit().unwrap_u8(), 1u8); + assert_eq!(random_negative.sign_bit(), ConstChoice::TRUE); let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.sign_bit().unwrap_u8(), 0u8); + assert_eq!(random_positive.sign_bit(), ConstChoice::FALSE); } #[test] diff --git a/src/int/add.rs b/src/int/add.rs index ada7933f..79f1cd30 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -3,7 +3,7 @@ use core::ops::{Add, AddAssign}; use num_traits::WrappingAdd; -use subtle::{ConstantTimeEq, CtOption}; +use subtle::{Choice, ConstantTimeEq, CtOption}; use crate::{Checked, CheckedAdd, Int, Wrapping}; @@ -19,8 +19,9 @@ impl Int { // - overflow occurs if and only if the result has the opposite sign of both inputs. // // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) - let self_msb = self.sign_bit(); - let overflow = self_msb.ct_eq(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); + let self_msb: Choice = self.sign_bit().into(); + let overflow = + self_msb.ct_eq(&rhs.sign_bit().into()) & self_msb.ct_ne(&res.sign_bit().into()); // Step 3. Construct result CtOption::new(res, !overflow) diff --git a/src/int/expand.rs b/src/int/expand.rs index 906986bf..2e2f1568 100644 --- a/src/int/expand.rs +++ b/src/int/expand.rs @@ -6,7 +6,7 @@ impl Int { #[inline(always)] pub fn expand(&self) -> Int { assert!(T >= LIMBS); - let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.sign_bit()); + let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.sign_bit().into()); let mut i = 0; while i < LIMBS { res.limbs[i] = self.0.limbs[i]; diff --git a/src/int/sub.rs b/src/int/sub.rs index cd9647d9..1076f3b8 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -3,7 +3,7 @@ use core::ops::{Sub, SubAssign}; use num_traits::WrappingSub; -use subtle::{ConstantTimeEq, CtOption}; +use subtle::{Choice, ConstantTimeEq, CtOption}; use crate::{Checked, CheckedSub, Int, Wrapping}; @@ -18,8 +18,9 @@ impl CheckedSub for Int { // - underflow occurs if and only if the result and the lhs have opposing signs. // // We can thus express the overflow flag as: (self.msb != rhs.msb) & (self.msb != res.msb) - let self_msb = self.sign_bit(); - let underflow = self_msb.ct_ne(&rhs.sign_bit()) & self_msb.ct_ne(&res.sign_bit()); + let self_msb: Choice = self.sign_bit().into(); + let underflow = + self_msb.ct_ne(&rhs.sign_bit().into()) & self_msb.ct_ne(&res.sign_bit().into()); // Step 3. Construct result CtOption::new(res, !underflow) From 8bcf5efa68ec036c383257fc1b7e00e0c71bace8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:37:36 +0200 Subject: [PATCH 31/87] Rename `Int::sign_bit` to `Int::is_negative` --- src/int.rs | 18 +++++++++--------- src/int/add.rs | 4 ++-- src/int/expand.rs | 2 +- src/int/sub.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/int.rs b/src/int.rs index 67003ffe..5eaf4251 100644 --- a/src/int.rs +++ b/src/int.rs @@ -141,7 +141,7 @@ impl Int { } /// Whether this [`Int`] is negative, as a `ConstChoice`. - pub const fn sign_bit(&self) -> ConstChoice { + pub const fn is_negative(&self) -> ConstChoice { ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) } @@ -202,7 +202,7 @@ impl Int { /// The sign and magnitude of this [`Int`], as well as whether it is zero. pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { - let sign = self.sign_bit().into(); + let sign = self.is_negative().into(); let (magnitude, is_zero) = self.negate_if_unsafe(sign); (sign, magnitude.0, is_zero) } @@ -420,17 +420,17 @@ mod tests { #[test] fn sign_bit() { - assert_eq!(I128::MIN.sign_bit(), ConstChoice::TRUE); - assert_eq!(I128::MINUS_ONE.sign_bit(), ConstChoice::TRUE); - assert_eq!(I128::ZERO.sign_bit(), ConstChoice::FALSE); - assert_eq!(I128::ONE.sign_bit(), ConstChoice::FALSE); - assert_eq!(I128::MAX.sign_bit(), ConstChoice::FALSE); + assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.sign_bit(), ConstChoice::TRUE); + assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.sign_bit(), ConstChoice::FALSE); + assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); } #[test] diff --git a/src/int/add.rs b/src/int/add.rs index 79f1cd30..9cb372ed 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -19,9 +19,9 @@ impl Int { // - overflow occurs if and only if the result has the opposite sign of both inputs. // // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) - let self_msb: Choice = self.sign_bit().into(); + let self_msb: Choice = self.is_negative().into(); let overflow = - self_msb.ct_eq(&rhs.sign_bit().into()) & self_msb.ct_ne(&res.sign_bit().into()); + self_msb.ct_eq(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); // Step 3. Construct result CtOption::new(res, !overflow) diff --git a/src/int/expand.rs b/src/int/expand.rs index 2e2f1568..0a061282 100644 --- a/src/int/expand.rs +++ b/src/int/expand.rs @@ -6,7 +6,7 @@ impl Int { #[inline(always)] pub fn expand(&self) -> Int { assert!(T >= LIMBS); - let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.sign_bit().into()); + let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.is_negative().into()); let mut i = 0; while i < LIMBS { res.limbs[i] = self.0.limbs[i]; diff --git a/src/int/sub.rs b/src/int/sub.rs index 1076f3b8..b8ec1f05 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -18,9 +18,9 @@ impl CheckedSub for Int { // - underflow occurs if and only if the result and the lhs have opposing signs. // // We can thus express the overflow flag as: (self.msb != rhs.msb) & (self.msb != res.msb) - let self_msb: Choice = self.sign_bit().into(); + let self_msb: Choice = self.is_negative().into(); let underflow = - self_msb.ct_ne(&rhs.sign_bit().into()) & self_msb.ct_ne(&res.sign_bit().into()); + self_msb.ct_ne(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); // Step 3. Construct result CtOption::new(res, !underflow) From 8e598b66b56a9228b50b39e44b3d723e928f4e37 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:46:48 +0200 Subject: [PATCH 32/87] Move `Int`'s negation operations to `int::neg` --- src/int.rs | 80 +------------------------------------------- src/int/neg.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 79 deletions(-) create mode 100644 src/int/neg.rs diff --git a/src/int.rs b/src/int.rs index 5eaf4251..29ccdc32 100644 --- a/src/int.rs +++ b/src/int.rs @@ -19,6 +19,7 @@ mod div; mod encoding; mod expand; mod mul; +mod neg; mod sub; #[cfg(feature = "rand_core")] @@ -140,11 +141,6 @@ impl Int { ConstCtOption::new(Odd(self), self.0.is_odd()) } - /// Whether this [`Int`] is negative, as a `ConstChoice`. - pub const fn is_negative(&self) -> ConstChoice { - ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) - } - /// Interpret the data in this type as a [`Uint`] instead. pub const fn as_uint(&self) -> &Uint { &self.0 @@ -160,46 +156,6 @@ impl Int { Choice::from((self == &Self::MAX) as u8) } - /// Perform the two's complement "negate" operation on this [`Int`]: - /// map `self` to `(self ^ 1111...1111) + 0000...0001` - /// - /// Returns - /// - the result, as well as - /// - whether the addition overflowed (indicating `self` is zero). - /// - /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. - #[inline] - pub const fn negate_unsafe(&self) -> (Self, ConstChoice) { - let (val, carry) = self.0.wrapping_neg_with_carry(); - (Self(val), ConstChoice::from_word_lsb(carry)) - } - - /// Perform the [two's complement "negate" operation](Int::negate_unsafe) on this [`Int`] - /// if `negate` is truthy. - /// - /// Returns - /// - the result, as well as - /// - whether the addition overflowed (indicating `self` is zero). - /// - /// Warning: this operation is unsafe; when `self == Self::MIN` and `negate` is truthy, - /// the negation fails. - #[inline] - fn negate_if_unsafe(&self, negate: Choice) -> (Int, Choice) { - let (negated, is_zero) = self.negate_unsafe(); - ( - Self(Uint::ct_select(&self.0, &negated.0, negate)), - is_zero.into(), - ) - } - - /// Map this [`Int`] to `-self` if possible. - /// - /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive - /// equivalent of that. - pub fn neg(&self) -> CtOption { - CtOption::new(self.negate_unsafe().0, !self.is_minimal()) - } - /// The sign and magnitude of this [`Int`], as well as whether it is zero. pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { let sign = self.is_negative().into(); @@ -357,7 +313,6 @@ mod tests { use subtle::ConditionallySelectable; use crate::int::I128; - use crate::ConstChoice; #[cfg(target_pointer_width = "64")] #[test] @@ -418,21 +373,6 @@ mod tests { assert_eq!(b, select_1); } - #[test] - fn sign_bit() { - assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); - - let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); - - let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); - } - #[test] fn is_minimal() { let min = I128::from_be_hex("80000000000000000000000000000000"); @@ -450,22 +390,4 @@ mod tests { let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); assert_eq!(random.is_maximal().unwrap_u8(), 0u8); } - - #[test] - fn negated() { - assert_eq!(I128::MIN.neg().is_none().unwrap_u8(), 1u8); - assert_eq!(I128::MINUS_ONE.neg().unwrap(), I128::ONE); - assert_eq!(I128::ZERO.neg().unwrap(), I128::ZERO); - assert_eq!(I128::ONE.neg().unwrap(), I128::MINUS_ONE); - assert_eq!( - I128::MAX.neg().unwrap(), - I128::from_be_hex("80000000000000000000000000000001") - ); - - let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); - assert_eq!(negative.neg().unwrap(), positive); - assert_eq!(positive.neg().unwrap(), negative); - assert_eq!(positive.neg().unwrap().neg().unwrap(), positive); - } } diff --git a/src/int/neg.rs b/src/int/neg.rs new file mode 100644 index 00000000..bb41b799 --- /dev/null +++ b/src/int/neg.rs @@ -0,0 +1,90 @@ +//! [`Int`] negation-related operations. + +use subtle::{Choice, CtOption}; + +use crate::{ConstChoice, ConstantTimeSelect, Int, Uint}; + +impl Int { + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn is_negative(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) + } + + /// Perform the two's complement "negate" operation on this [`Int`]: + /// map `self` to `(self ^ 1111...1111) + 0000...0001` + /// + /// Returns + /// - the result, as well as + /// - whether the addition overflowed (indicating `self` is zero). + /// + /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. + #[inline] + pub(crate) const fn negate_unsafe(&self) -> (Self, ConstChoice) { + let (val, carry) = self.0.wrapping_neg_with_carry(); + (Self(val), ConstChoice::from_word_lsb(carry)) + } + + /// Perform the [two's complement "negate" operation](Int::negate_unsafe) on this [`Int`] + /// if `negate` is truthy. + /// + /// Returns + /// - the result, as well as + /// - whether the addition overflowed (indicating `self` is zero). + /// + /// Warning: this operation is unsafe; when `self == Self::MIN` and `negate` is truthy, + /// the negation fails. + #[inline] + pub(crate) fn negate_if_unsafe(&self, negate: Choice) -> (Int, Choice) { + let (negated, is_zero) = self.negate_unsafe(); + ( + Self(Uint::ct_select(&self.0, &negated.0, negate)), + is_zero.into(), + ) + } + + /// Map this [`Int`] to `-self`. + /// + /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive + /// equivalent of that. + pub fn neg(&self) -> CtOption { + CtOption::new(self.negate_unsafe().0, !self.is_minimal()) + } +} + +#[cfg(test)] +mod tests { + use crate::{ConstChoice, I128}; + + #[test] + fn is_negative() { + assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); + + let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); + + let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); + } + + #[test] + fn neg() { + assert_eq!(I128::MIN.neg().is_none().unwrap_u8(), 1u8); + assert_eq!(I128::MINUS_ONE.neg().unwrap(), I128::ONE); + assert_eq!(I128::ZERO.neg().unwrap(), I128::ZERO); + assert_eq!(I128::ONE.neg().unwrap(), I128::MINUS_ONE); + assert_eq!( + I128::MAX.neg().unwrap(), + I128::from_be_hex("80000000000000000000000000000001") + ); + + let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); + assert_eq!(negative.neg().unwrap(), positive); + assert_eq!(positive.neg().unwrap(), negative); + assert_eq!(positive.neg().unwrap().neg().unwrap(), positive); + } +} From 22582a219ad698a7ecda3025736e6bb23c5bf30b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 10:59:56 +0200 Subject: [PATCH 33/87] Expand `int::neg` tests --- src/int/neg.rs | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/int/neg.rs b/src/int/neg.rs index bb41b799..4ae46e01 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -70,6 +70,83 @@ mod tests { assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); } + #[test] + fn negate_unsafe() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let (res, is_zero) = I128::MIN.negate_unsafe(); + assert!(!is_zero.to_bool_vartime()); + assert_eq!(res, I128::MIN); + + let (res, is_zero) = I128::MINUS_ONE.negate_unsafe(); + assert!(!is_zero.to_bool_vartime()); + assert_eq!(res, I128::ONE); + + let (res, is_zero) = I128::ZERO.negate_unsafe(); + assert!(is_zero.to_bool_vartime()); + assert_eq!(res, I128::ZERO); + + let (res, is_zero) = I128::ONE.negate_unsafe(); + assert!(!is_zero.to_bool_vartime()); + assert_eq!(res, I128::MINUS_ONE); + + let (res, is_zero) = I128::MAX.negate_unsafe(); + assert!(!is_zero.to_bool_vartime()); + assert_eq!(res, min_plus_one); + } + + #[test] + fn negate_if_unsafe() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let do_negate = ConstChoice::TRUE.into(); + let do_not_negate = ConstChoice::FALSE.into(); + + let (res, is_zero) = I128::MIN.negate_if_unsafe(do_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::MIN); + + let (res, is_zero) = I128::MIN.negate_if_unsafe(do_not_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::MIN); + + let (res, is_zero) = I128::MINUS_ONE.negate_if_unsafe(do_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::ONE); + + let (res, is_zero) = I128::MINUS_ONE.negate_if_unsafe(do_not_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::MINUS_ONE); + + let (res, is_zero) = I128::ZERO.negate_if_unsafe(do_negate); + assert_eq!(is_zero.unwrap_u8(), 1u8); + assert_eq!(res, I128::ZERO); + + let (res, is_zero) = I128::ZERO.negate_if_unsafe(do_not_negate); + assert_eq!(is_zero.unwrap_u8(), 1u8); + assert_eq!(res, I128::ZERO); + + let (res, is_zero) = I128::ONE.negate_if_unsafe(do_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::MINUS_ONE); + + let (res, is_zero) = I128::ONE.negate_if_unsafe(do_not_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::ONE); + + let (res, is_zero) = I128::MAX.negate_if_unsafe(do_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, min_plus_one); + + let (res, is_zero) = I128::MAX.negate_if_unsafe(do_not_negate); + assert_eq!(is_zero.unwrap_u8(), 0u8); + assert_eq!(res, I128::MAX); + } + #[test] fn neg() { assert_eq!(I128::MIN.neg().is_none().unwrap_u8(), 1u8); From 74b377b371037b948bffdc37acc7d39115b2a0cb Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 11:00:06 +0200 Subject: [PATCH 34/87] Some cleaning up --- src/int.rs | 5 +---- src/int/add.rs | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/int.rs b/src/int.rs index 29ccdc32..d24849d0 100644 --- a/src/int.rs +++ b/src/int.rs @@ -5,10 +5,7 @@ use core::fmt; use num_traits::ConstZero; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; -use crate::{ - Bounded, ConstChoice, ConstCtOption, ConstantTimeSelect, Encoding, Limb, NonZero, Odd, Uint, - Word, -}; +use crate::{Bounded, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; #[macro_use] mod macros; diff --git a/src/int/add.rs b/src/int/add.rs index 9cb372ed..377cb4b8 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -104,17 +104,17 @@ mod tests { #[cfg(test)] mod tests { use crate::int::I128; - use crate::{CheckedAdd, Int, U128}; + use crate::{CheckedAdd, U128}; #[test] fn checked_add() { - let min_plus_one = Int { + let min_plus_one = I128 { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; - let max_minus_one = Int { + let max_minus_one = I128 { 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), }; - let two = Int { + let two = I128 { 0: U128::from(2u32), }; From 8fad5d48d097885d138ffc402b27bd2c247175b4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 11:52:32 +0200 Subject: [PATCH 35/87] Introduce `ConstChoice::as_word_mask` --- src/const_choice.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/const_choice.rs b/src/const_choice.rs index d8abc596..90ebd169 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -33,6 +33,18 @@ impl ConstChoice { self.0 } + #[inline] + #[cfg(target_pointer_width = "32")] + pub(crate) const fn as_word_mask(&self) -> Word { + self.as_u32_mask() + } + + #[inline] + #[cfg(target_pointer_width = "64")] + pub(crate) const fn as_word_mask(&self) -> Word { + self.as_u64_mask() + } + /// Returns the truthy value if `value == Word::MAX`, and the falsy value if `value == 0`. /// Panics for other values. #[inline] From d8a6a917aacf1ed49f99871b808be2f5e9e96dee Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 11:55:29 +0200 Subject: [PATCH 36/87] Introduce `Uint::wrapping_neg_if` --- src/uint/neg.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/uint/neg.rs b/src/uint/neg.rs index aef06753..9563bf8b 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -1,4 +1,6 @@ -use crate::{Limb, Uint, WideWord, Word, WrappingNeg}; +use num_traits::ConstOne; + +use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. @@ -19,6 +21,22 @@ impl Uint { } (Uint::new(ret), carry as Word) } + + /// Perform wrapping negation, if `negate` is truthy. + /// Also returns the resulting carry. + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { + let mut ret = [Limb::ZERO; LIMBS]; + let mut i = 0; + let mask = negate.as_word_mask() as WideWord; + let mut carry = mask & WideWord::ONE; + while i < LIMBS { + let r = ((self.limbs[i].0 as WideWord) ^ mask) + carry; + ret[i] = Limb(r as Word); + carry = r >> Limb::BITS; + i += 1; + } + Uint::new(ret) + } } impl WrappingNeg for Uint { From 2033850cc5166f525a28beda82c50a079912b03d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:08:14 +0200 Subject: [PATCH 37/87] Refactor `Int::negate_if_unsafe`, make `split_mul` `const` in the process. --- src/int.rs | 24 +++++++++--------- src/int/div.rs | 24 ++++++++++-------- src/int/mul.rs | 29 +++++++++++++--------- src/int/neg.rs | 66 ++++++++++++++------------------------------------ 4 files changed, 62 insertions(+), 81 deletions(-) diff --git a/src/int.rs b/src/int.rs index d24849d0..fd7227d7 100644 --- a/src/int.rs +++ b/src/int.rs @@ -5,7 +5,7 @@ use core::fmt; use num_traits::ConstZero; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; -use crate::{Bounded, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; +use crate::{Bounded, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; #[macro_use] mod macros; @@ -76,12 +76,13 @@ impl Int { /// Construct new [`Int`] from a sign and magnitude. /// Returns `None` when the magnitude does not fit in an [`Int`]. pub fn new_from_sign_and_magnitude( - is_negative: Choice, + is_negative: ConstChoice, magnitude: Uint, ) -> CtOption { CtOption::new( - Self(magnitude).negate_if_unsafe(is_negative).0, - !magnitude.ct_gt(&Int::MAX.0) | is_negative & magnitude.ct_eq(&Int::MIN.0), + Self(magnitude).negate_if_unsafe(is_negative), + !magnitude.ct_gt(&Int::MAX.0) + | Choice::from(is_negative) & magnitude.ct_eq(&Int::MIN.0), ) } @@ -153,16 +154,17 @@ impl Int { Choice::from((self == &Self::MAX) as u8) } - /// The sign and magnitude of this [`Int`], as well as whether it is zero. - pub fn sign_magnitude_is_zero(&self) -> (Choice, Uint, Choice) { - let sign = self.is_negative().into(); - let (magnitude, is_zero) = self.negate_if_unsafe(sign); - (sign, magnitude.0, is_zero) + /// The sign and magnitude of this [`Int`]. + pub const fn sign_and_magnitude(&self) -> (ConstChoice, Uint) { + let sign = self.is_negative(); + // Note: this negate_if is safe to use, since we are negating based on self.is_negative() + let magnitude = self.negate_if_unsafe(sign); + (sign, magnitude.0) } /// The magnitude of this [`Int`]. - pub fn magnitude(&self) -> Uint { - self.sign_magnitude_is_zero().1 + pub const fn magnitude(&self) -> Uint { + self.sign_and_magnitude().1 } /// Invert the most significant bit (msb) of this [`Int`] diff --git a/src/int/div.rs b/src/int/div.rs index b1aac2f0..bcf6468b 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -2,7 +2,7 @@ use core::ops::Div; -use subtle::{ConstantTimeEq, CtOption}; +use subtle::CtOption; use crate::{CheckedDiv, Int, NonZero}; @@ -11,15 +11,19 @@ impl Int { /// only if the rhs != 0 pub fn checked_div(&self, rhs: &Self) -> CtOption { // Step 1: split operands into signs, magnitudes and whether they are zero. - let (lhs_sgn, lhs_mag, lhs_is_zero) = self.sign_magnitude_is_zero(); - let (rhs_sgn, rhs_mag, ..) = rhs.sign_magnitude_is_zero(); - - // Step 2: divide the magnitudes - lhs_mag.checked_div(&rhs_mag).and_then(|magnitude| { - // Step 3: construct the sign of the result - let is_negative = !lhs_is_zero & lhs_sgn.ct_ne(&rhs_sgn); - Self::new_from_sign_and_magnitude(is_negative, magnitude) - }) + let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); + let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + + // Step 2. Determine if the result should be negated. + // This should be done if and only if lhs and rhs have opposing signs. + // Note: if the lhs is zero, the resulting magnitude will also be zero. Negating zero, + // however, still yields zero, so having a truthy `negate_result` in that scenario is OK. + let negate_result = lhs_sgn.xor(rhs_sgn); + + // Step 3. Construct result + lhs_mag + .checked_div(&rhs_mag) + .and_then(|magnitude| Self::new_from_sign_and_magnitude(negate_result, magnitude)) } } diff --git a/src/int/mul.rs b/src/int/mul.rs index 869aabc3..534a33c8 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -2,29 +2,34 @@ use core::ops::{Mul, MulAssign}; -use subtle::{Choice, ConstantTimeEq, CtOption}; +use subtle::CtOption; -use crate::{Checked, CheckedMul, Int, Uint, Zero}; +use crate::{Checked, CheckedMul, ConstChoice, Int, Uint, Zero}; impl Int { - /// Compute "wide" multiplication as a 3-tuple `(lo, hi, sgn)`. + /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. /// The `(lo, hi)` components contain the _magnitude of the product_, with sizes - /// correspond to the sizes of the operands; the `sgn` indicates the sign of the result. - pub fn split_mul( + /// corresponding to the sizes of the operands; `negate` indicates whether the result should be + /// negated when converted from `Uint` to `Int`. Note: even if `negate` is truthy, the magnitude + /// might be zero! + pub const fn split_mul( &self, rhs: &Int, - ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, Choice) { - // Step 1: split operands into signs, magnitudes and whether they are zero. - let (lhs_sgn, lhs_mag, lhs_is_zero) = self.sign_magnitude_is_zero(); - let (rhs_sgn, rhs_mag, rhs_is_zero) = rhs.sign_magnitude_is_zero(); + ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, ConstChoice) { + // Step 1: split operands into their signs and magnitudes. + let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); + let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); // Step 2: multiply the magnitudes let (lo, hi) = lhs_mag.split_mul(&rhs_mag); - // Step 3: construct the sign of the result - let is_negative = !lhs_is_zero & !rhs_is_zero & lhs_sgn.ct_ne(&rhs_sgn); + // Step 3. Determine if the result should be negated. + // This should be done if and only if lhs and rhs have opposing signs. + // Note: if either operand is zero, the resulting magnitude will also be zero. Negating + // zero, however, still yields zero, so having a truthy `negate` in that scenario is OK. + let negate = lhs_sgn.xor(rhs_sgn); - (lo, hi, is_negative) + (lo, hi, negate) } } diff --git a/src/int/neg.rs b/src/int/neg.rs index 4ae46e01..43c34314 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -1,8 +1,8 @@ //! [`Int`] negation-related operations. -use subtle::{Choice, CtOption}; +use subtle::CtOption; -use crate::{ConstChoice, ConstantTimeSelect, Int, Uint}; +use crate::{ConstChoice, Int}; impl Int { /// Whether this [`Int`] is negative, as a `ConstChoice`. @@ -34,12 +34,8 @@ impl Int { /// Warning: this operation is unsafe; when `self == Self::MIN` and `negate` is truthy, /// the negation fails. #[inline] - pub(crate) fn negate_if_unsafe(&self, negate: Choice) -> (Int, Choice) { - let (negated, is_zero) = self.negate_unsafe(); - ( - Self(Uint::ct_select(&self.0, &negated.0, negate)), - is_zero.into(), - ) + pub(crate) const fn negate_if_unsafe(&self, negate: ConstChoice) -> Int { + Self(self.0.wrapping_neg_if(negate)) } /// Map this [`Int`] to `-self`. @@ -104,47 +100,21 @@ mod tests { }; let do_negate = ConstChoice::TRUE.into(); - let do_not_negate = ConstChoice::FALSE.into(); - - let (res, is_zero) = I128::MIN.negate_if_unsafe(do_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::MIN); - - let (res, is_zero) = I128::MIN.negate_if_unsafe(do_not_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::MIN); - - let (res, is_zero) = I128::MINUS_ONE.negate_if_unsafe(do_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::ONE); - - let (res, is_zero) = I128::MINUS_ONE.negate_if_unsafe(do_not_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::MINUS_ONE); - - let (res, is_zero) = I128::ZERO.negate_if_unsafe(do_negate); - assert_eq!(is_zero.unwrap_u8(), 1u8); - assert_eq!(res, I128::ZERO); + assert_eq!(I128::MIN.negate_if_unsafe(do_negate), I128::MIN); + assert_eq!(I128::MINUS_ONE.negate_if_unsafe(do_negate), I128::ONE); + assert_eq!(I128::ZERO.negate_if_unsafe(do_negate), I128::ZERO); + assert_eq!(I128::ONE.negate_if_unsafe(do_negate), I128::MINUS_ONE); + assert_eq!(I128::MAX.negate_if_unsafe(do_negate), min_plus_one); - let (res, is_zero) = I128::ZERO.negate_if_unsafe(do_not_negate); - assert_eq!(is_zero.unwrap_u8(), 1u8); - assert_eq!(res, I128::ZERO); - - let (res, is_zero) = I128::ONE.negate_if_unsafe(do_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::MINUS_ONE); - - let (res, is_zero) = I128::ONE.negate_if_unsafe(do_not_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::ONE); - - let (res, is_zero) = I128::MAX.negate_if_unsafe(do_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, min_plus_one); - - let (res, is_zero) = I128::MAX.negate_if_unsafe(do_not_negate); - assert_eq!(is_zero.unwrap_u8(), 0u8); - assert_eq!(res, I128::MAX); + let do_not_negate = ConstChoice::FALSE.into(); + assert_eq!(I128::MIN.negate_if_unsafe(do_not_negate), I128::MIN); + assert_eq!( + I128::MINUS_ONE.negate_if_unsafe(do_not_negate), + I128::MINUS_ONE + ); + assert_eq!(I128::ZERO.negate_if_unsafe(do_not_negate), I128::ZERO); + assert_eq!(I128::ONE.negate_if_unsafe(do_not_negate), I128::ONE); + assert_eq!(I128::MAX.negate_if_unsafe(do_not_negate), I128::MAX); } #[test] From cff448c36f2bdb10b062d3b07b06178da6f815b4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:16:59 +0200 Subject: [PATCH 38/87] Make `Int::new_from_sign_and_magnitude` `const` --- src/int.rs | 13 +++++++------ src/int/div.rs | 6 +++--- src/int/mul.rs | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/int.rs b/src/int.rs index fd7227d7..915c0a78 100644 --- a/src/int.rs +++ b/src/int.rs @@ -3,7 +3,7 @@ use core::fmt; use num_traits::ConstZero; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, CtOption}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{Bounded, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; @@ -75,14 +75,15 @@ impl Int { /// Construct new [`Int`] from a sign and magnitude. /// Returns `None` when the magnitude does not fit in an [`Int`]. - pub fn new_from_sign_and_magnitude( + pub const fn new_from_sign_and_magnitude( is_negative: ConstChoice, magnitude: Uint, - ) -> CtOption { - CtOption::new( + ) -> ConstCtOption { + ConstCtOption::new( Self(magnitude).negate_if_unsafe(is_negative), - !magnitude.ct_gt(&Int::MAX.0) - | Choice::from(is_negative) & magnitude.ct_eq(&Int::MIN.0), + Uint::gt(&magnitude, &Int::MAX.0) + .not() + .or(is_negative.and(Uint::eq(&magnitude, &Int::MIN.0))), ) } diff --git a/src/int/div.rs b/src/int/div.rs index bcf6468b..b7dc3a6b 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -21,9 +21,9 @@ impl Int { let negate_result = lhs_sgn.xor(rhs_sgn); // Step 3. Construct result - lhs_mag - .checked_div(&rhs_mag) - .and_then(|magnitude| Self::new_from_sign_and_magnitude(negate_result, magnitude)) + lhs_mag.checked_div(&rhs_mag).and_then(|magnitude| { + Self::new_from_sign_and_magnitude(negate_result, magnitude).into() + }) } } diff --git a/src/int/mul.rs b/src/int/mul.rs index 534a33c8..4edd2bfd 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -38,7 +38,7 @@ impl CheckedMul> for fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); let val = Self::new_from_sign_and_magnitude(is_negative, lo); - val.and_then(|int| CtOption::new(int, hi.is_zero())) + CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } From d3264329184fb6a693d6d5efb0713328d30e1d86 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:22:11 +0200 Subject: [PATCH 39/87] Make `Int::checked_add_internal` `const` --- src/int/add.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/int/add.rs b/src/int/add.rs index 377cb4b8..bb9d2e4a 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -3,13 +3,13 @@ use core::ops::{Add, AddAssign}; use num_traits::WrappingAdd; -use subtle::{Choice, ConstantTimeEq, CtOption}; +use subtle::CtOption; -use crate::{Checked, CheckedAdd, Int, Wrapping}; +use crate::{Checked, CheckedAdd, ConstCtOption, Int, Wrapping}; impl Int { /// Perform checked addition. - fn checked_add_internal(&self, rhs: &Self) -> CtOption { + pub const fn checked_add_internal(&self, rhs: &Self) -> ConstCtOption { // Step 1. add operands let res = Self(self.0.wrapping_add(&rhs.0)); @@ -19,12 +19,14 @@ impl Int { // - overflow occurs if and only if the result has the opposite sign of both inputs. // // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) - let self_msb: Choice = self.is_negative().into(); - let overflow = - self_msb.ct_eq(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); + let self_msb = self.is_negative(); + let overflow = self_msb + .xor(rhs.is_negative()) + .not() + .and(self_msb.xor(res.is_negative())); // Step 3. Construct result - CtOption::new(res, !overflow) + ConstCtOption::new(res, overflow.not()) } /// Perform wrapping addition, discarding overflow. @@ -88,7 +90,7 @@ impl AddAssign<&Checked>> for Checked> impl CheckedAdd for Int { fn checked_add(&self, rhs: &Self) -> CtOption { - self.checked_add_internal(rhs) + self.checked_add_internal(rhs).into() } } From cb9967f7ec4fd217d18e5e63d9ace0f9f2f4885e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:23:30 +0200 Subject: [PATCH 40/87] Rename `Int::checked_add_internal` to `Int::checked_add` --- src/int/add.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/int/add.rs b/src/int/add.rs index bb9d2e4a..2981f482 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -9,7 +9,7 @@ use crate::{Checked, CheckedAdd, ConstCtOption, Int, Wrapping}; impl Int { /// Perform checked addition. - pub const fn checked_add_internal(&self, rhs: &Self) -> ConstCtOption { + pub const fn checked_add(&self, rhs: &Self) -> ConstCtOption { // Step 1. add operands let res = Self(self.0.wrapping_add(&rhs.0)); @@ -47,8 +47,7 @@ impl Add<&Int> for Int { type Output = Self; fn add(self, rhs: &Self) -> Self { - self.checked_add(rhs) - .expect("attempted to add with overflow") + CtOption::from(self.checked_add(rhs)).expect("attempted to add with overflow") } } @@ -90,7 +89,7 @@ impl AddAssign<&Checked>> for Checked> impl CheckedAdd for Int { fn checked_add(&self, rhs: &Self) -> CtOption { - self.checked_add_internal(rhs).into() + self.checked_add(rhs).into() } } @@ -106,7 +105,7 @@ mod tests { #[cfg(test)] mod tests { use crate::int::I128; - use crate::{CheckedAdd, U128}; + use crate::U128; #[test] fn checked_add() { From 0a7155bea2dbefe4c6b053cadf5a951bd87b9bf9 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:30:26 +0200 Subject: [PATCH 41/87] Refactor `Int::expand` to `Int::resize` --- benches/int.rs | 10 +++++----- src/int.rs | 2 +- src/int/expand.rs | 17 ----------------- src/int/resize.rs | 10 ++++++++++ 4 files changed, 16 insertions(+), 23 deletions(-) delete mode 100644 src/int/expand.rs create mode 100644 src/int/resize.rs diff --git a/benches/int.rs b/benches/int.rs index 442728f5..4108c46f 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -65,7 +65,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I256::random(&mut OsRng); - let y = I128::random(&mut OsRng).expand::<{ I256::LIMBS }>(); + let y = I128::random(&mut OsRng).resize::<{ I256::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -77,7 +77,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I512::random(&mut OsRng); - let y = I256::random(&mut OsRng).expand::<{ I512::LIMBS }>(); + let y = I256::random(&mut OsRng).resize::<{ I512::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -89,7 +89,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I1024::random(&mut OsRng); - let y = I512::random(&mut OsRng).expand::<{ I1024::LIMBS }>(); + let y = I512::random(&mut OsRng).resize::<{ I1024::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -101,7 +101,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I2048::random(&mut OsRng); - let y = I1024::random(&mut OsRng).expand::<{ I2048::LIMBS }>(); + let y = I1024::random(&mut OsRng).resize::<{ I2048::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), @@ -113,7 +113,7 @@ fn bench_div(c: &mut Criterion) { b.iter_batched( || { let x = I4096::random(&mut OsRng); - let y = I2048::random(&mut OsRng).expand::<{ I4096::LIMBS }>(); + let y = I2048::random(&mut OsRng).resize::<{ I4096::LIMBS }>(); (x, NonZero::new(y).unwrap()) }, |(x, y)| black_box(x.div(&y)), diff --git a/src/int.rs b/src/int.rs index 915c0a78..daebe227 100644 --- a/src/int.rs +++ b/src/int.rs @@ -14,9 +14,9 @@ mod add; mod cmp; mod div; mod encoding; -mod expand; mod mul; mod neg; +mod resize; mod sub; #[cfg(feature = "rand_core")] diff --git a/src/int/expand.rs b/src/int/expand.rs deleted file mode 100644 index 0a061282..00000000 --- a/src/int/expand.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::{ConstantTimeSelect, Int, Uint}; - -impl Int { - /// Expand the representation of `self` to an `Int`. - /// Assumes `T >= LIMBS`; panics otherwise. - #[inline(always)] - pub fn expand(&self) -> Int { - assert!(T >= LIMBS); - let mut res = Uint::ct_select(&Int::ZERO.0, &Int::FULL_MASK.0, self.is_negative().into()); - let mut i = 0; - while i < LIMBS { - res.limbs[i] = self.0.limbs[i]; - i += 1; - } - Int::new_from_uint(res) - } -} diff --git a/src/int/resize.rs b/src/int/resize.rs new file mode 100644 index 00000000..a6222717 --- /dev/null +++ b/src/int/resize.rs @@ -0,0 +1,10 @@ +use crate::Int; + +impl Int { + /// Resize the representation of `self` to an `Int`. + /// Warning: this operation may lead to loss of information. + #[inline(always)] + pub const fn resize(&self) -> Int { + Int::new_from_uint(self.0.resize::()) + } +} From 9d7b1d9a67fd5486172dd8abda8b4adf5a7233b8 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:36:33 +0200 Subject: [PATCH 42/87] Rename `Uint::wrapping_neg_with_carry` to `Uint::negc` --- src/int/neg.rs | 2 +- src/uint/neg.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/int/neg.rs b/src/int/neg.rs index 43c34314..c243e313 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -20,7 +20,7 @@ impl Int { /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. #[inline] pub(crate) const fn negate_unsafe(&self) -> (Self, ConstChoice) { - let (val, carry) = self.0.wrapping_neg_with_carry(); + let (val, carry) = self.0.negc(); (Self(val), ConstChoice::from_word_lsb(carry)) } diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 9563bf8b..18eb528a 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -5,11 +5,11 @@ use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { - self.wrapping_neg_with_carry().0 + self.negc().0 } - /// Perform wrapping negation; also returns the resulting carry. - pub const fn wrapping_neg_with_carry(&self) -> (Self, Word) { + /// Perform negation; additionally return the carry. + pub const fn negc(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; @@ -22,8 +22,7 @@ impl Uint { (Uint::new(ret), carry as Word) } - /// Perform wrapping negation, if `negate` is truthy. - /// Also returns the resulting carry. + /// Perform negation, if `negate` is truthy. pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { let mut ret = [Limb::ZERO; LIMBS]; let mut i = 0; From 93f9a45ccb4c1b62081e7dd35288640be3c35ebf Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:44:56 +0200 Subject: [PATCH 43/87] Rename `Int`'s negation functions. --- src/int.rs | 4 +-- src/int/neg.rs | 74 ++++++++++++++++++++++++-------------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/int.rs b/src/int.rs index daebe227..f01db05d 100644 --- a/src/int.rs +++ b/src/int.rs @@ -80,7 +80,7 @@ impl Int { magnitude: Uint, ) -> ConstCtOption { ConstCtOption::new( - Self(magnitude).negate_if_unsafe(is_negative), + Self(magnitude).wrapping_neg_if(is_negative), Uint::gt(&magnitude, &Int::MAX.0) .not() .or(is_negative.and(Uint::eq(&magnitude, &Int::MIN.0))), @@ -159,7 +159,7 @@ impl Int { pub const fn sign_and_magnitude(&self) -> (ConstChoice, Uint) { let sign = self.is_negative(); // Note: this negate_if is safe to use, since we are negating based on self.is_negative() - let magnitude = self.negate_if_unsafe(sign); + let magnitude = self.wrapping_neg_if(sign); (sign, magnitude.0) } diff --git a/src/int/neg.rs b/src/int/neg.rs index c243e313..afefc527 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -2,7 +2,7 @@ use subtle::CtOption; -use crate::{ConstChoice, Int}; +use crate::{ConstChoice, Int, Word}; impl Int { /// Whether this [`Int`] is negative, as a `ConstChoice`. @@ -11,30 +11,24 @@ impl Int { } /// Perform the two's complement "negate" operation on this [`Int`]: - /// map `self` to `(self ^ 1111...1111) + 0000...0001` + /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. /// - /// Returns - /// - the result, as well as - /// - whether the addition overflowed (indicating `self` is zero). + /// Note: a non-zero carry indicates `self == Self::ZERO`. /// - /// Warning: this operation is unsafe; when `self == Self::MIN`, the negation fails. + /// Warning: this operation is unsafe to use as negation; the negation is incorrect when + /// `self == Self::MIN`. #[inline] - pub(crate) const fn negate_unsafe(&self) -> (Self, ConstChoice) { + pub(crate) const fn negc(&self) -> (Self, Word) { let (val, carry) = self.0.negc(); - (Self(val), ConstChoice::from_word_lsb(carry)) + (Self(val), carry) } - /// Perform the [two's complement "negate" operation](Int::negate_unsafe) on this [`Int`] - /// if `negate` is truthy. + /// Wrapping negate this [`Int`] if `negate` is truthy; otherwise do nothing. /// - /// Returns - /// - the result, as well as - /// - whether the addition overflowed (indicating `self` is zero). - /// - /// Warning: this operation is unsafe; when `self == Self::MIN` and `negate` is truthy, - /// the negation fails. + /// Warning: this operation is unsafe to use as negation; the result is incorrect when + /// `self == Self::MIN` and `negate` is truthy. #[inline] - pub(crate) const fn negate_if_unsafe(&self, negate: ConstChoice) -> Int { + pub(crate) const fn wrapping_neg_if(&self, negate: ConstChoice) -> Int { Self(self.0.wrapping_neg_if(negate)) } @@ -43,13 +37,15 @@ impl Int { /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive /// equivalent of that. pub fn neg(&self) -> CtOption { - CtOption::new(self.negate_unsafe().0, !self.is_minimal()) + CtOption::new(self.negc().0, !self.is_minimal()) } } #[cfg(test)] mod tests { - use crate::{ConstChoice, I128}; + use num_traits::{ConstOne, ConstZero}; + + use crate::{ConstChoice, Word, I128}; #[test] fn is_negative() { @@ -72,24 +68,24 @@ mod tests { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; - let (res, is_zero) = I128::MIN.negate_unsafe(); - assert!(!is_zero.to_bool_vartime()); + let (res, carry) = I128::MIN.negc(); + assert_eq!(carry, Word::ZERO); assert_eq!(res, I128::MIN); - let (res, is_zero) = I128::MINUS_ONE.negate_unsafe(); - assert!(!is_zero.to_bool_vartime()); + let (res, carry) = I128::MINUS_ONE.negc(); + assert_eq!(carry, Word::ZERO); assert_eq!(res, I128::ONE); - let (res, is_zero) = I128::ZERO.negate_unsafe(); - assert!(is_zero.to_bool_vartime()); + let (res, carry) = I128::ZERO.negc(); + assert_eq!(carry, Word::ONE); assert_eq!(res, I128::ZERO); - let (res, is_zero) = I128::ONE.negate_unsafe(); - assert!(!is_zero.to_bool_vartime()); + let (res, carry) = I128::ONE.negc(); + assert_eq!(carry, Word::ZERO); assert_eq!(res, I128::MINUS_ONE); - let (res, is_zero) = I128::MAX.negate_unsafe(); - assert!(!is_zero.to_bool_vartime()); + let (res, carry) = I128::MAX.negc(); + assert_eq!(carry, Word::ZERO); assert_eq!(res, min_plus_one); } @@ -100,21 +96,21 @@ mod tests { }; let do_negate = ConstChoice::TRUE.into(); - assert_eq!(I128::MIN.negate_if_unsafe(do_negate), I128::MIN); - assert_eq!(I128::MINUS_ONE.negate_if_unsafe(do_negate), I128::ONE); - assert_eq!(I128::ZERO.negate_if_unsafe(do_negate), I128::ZERO); - assert_eq!(I128::ONE.negate_if_unsafe(do_negate), I128::MINUS_ONE); - assert_eq!(I128::MAX.negate_if_unsafe(do_negate), min_plus_one); + assert_eq!(I128::MIN.wrapping_neg_if(do_negate), I128::MIN); + assert_eq!(I128::MINUS_ONE.wrapping_neg_if(do_negate), I128::ONE); + assert_eq!(I128::ZERO.wrapping_neg_if(do_negate), I128::ZERO); + assert_eq!(I128::ONE.wrapping_neg_if(do_negate), I128::MINUS_ONE); + assert_eq!(I128::MAX.wrapping_neg_if(do_negate), min_plus_one); let do_not_negate = ConstChoice::FALSE.into(); - assert_eq!(I128::MIN.negate_if_unsafe(do_not_negate), I128::MIN); + assert_eq!(I128::MIN.wrapping_neg_if(do_not_negate), I128::MIN); assert_eq!( - I128::MINUS_ONE.negate_if_unsafe(do_not_negate), + I128::MINUS_ONE.wrapping_neg_if(do_not_negate), I128::MINUS_ONE ); - assert_eq!(I128::ZERO.negate_if_unsafe(do_not_negate), I128::ZERO); - assert_eq!(I128::ONE.negate_if_unsafe(do_not_negate), I128::ONE); - assert_eq!(I128::MAX.negate_if_unsafe(do_not_negate), I128::MAX); + assert_eq!(I128::ZERO.wrapping_neg_if(do_not_negate), I128::ZERO); + assert_eq!(I128::ONE.wrapping_neg_if(do_not_negate), I128::ONE); + assert_eq!(I128::MAX.wrapping_neg_if(do_not_negate), I128::MAX); } #[test] From 0f2009fe5339fcd6f03d0ca66d5bc8d277d20d09 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:46:27 +0200 Subject: [PATCH 44/87] Rename `Int::is_minimal` to `Int::is_min` --- src/int.rs | 9 +++++---- src/int/neg.rs | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/int.rs b/src/int.rs index f01db05d..a432e092 100644 --- a/src/int.rs +++ b/src/int.rs @@ -146,8 +146,8 @@ impl Int { } /// Whether this [`Int`] is equal to `Self::MIN`. - pub fn is_minimal(&self) -> Choice { - Choice::from((self == &Self::MIN) as u8) + pub fn is_min(&self) -> ConstChoice { + Self::eq(&self, &Self::MIN) } /// Whether this [`Int`] is equal to `Self::MAX`. @@ -313,6 +313,7 @@ mod tests { use subtle::ConditionallySelectable; use crate::int::I128; + use crate::ConstChoice; #[cfg(target_pointer_width = "64")] #[test] @@ -376,10 +377,10 @@ mod tests { #[test] fn is_minimal() { let min = I128::from_be_hex("80000000000000000000000000000000"); - assert_eq!(min.is_minimal().unwrap_u8(), 1u8); + assert_eq!(min.is_min(), ConstChoice::TRUE); let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); - assert_eq!(random.is_minimal().unwrap_u8(), 0u8); + assert_eq!(random.is_min(), ConstChoice::FALSE); } #[test] diff --git a/src/int/neg.rs b/src/int/neg.rs index afefc527..7e34f4d3 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -37,7 +37,7 @@ impl Int { /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive /// equivalent of that. pub fn neg(&self) -> CtOption { - CtOption::new(self.negc().0, !self.is_minimal()) + CtOption::new(self.negc().0, self.is_min().not().into()) } } From e182fafcff6234cce72ecfe9cbe4863d8c6e24bc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:48:57 +0200 Subject: [PATCH 45/87] Rename `Int::is_maximal` to `Int::is_max` --- src/int.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/int.rs b/src/int.rs index a432e092..e4dcf332 100644 --- a/src/int.rs +++ b/src/int.rs @@ -151,8 +151,8 @@ impl Int { } /// Whether this [`Int`] is equal to `Self::MAX`. - pub fn is_maximal(&self) -> Choice { - Choice::from((self == &Self::MAX) as u8) + pub fn is_max(&self) -> ConstChoice { + Self::eq(&self, &Self::MAX) } /// The sign and magnitude of this [`Int`]. @@ -386,9 +386,9 @@ mod tests { #[test] fn is_maximal() { let max = I128::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); - assert_eq!(max.is_maximal().unwrap_u8(), 1u8); + assert_eq!(max.is_max(), ConstChoice::TRUE); let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); - assert_eq!(random.is_maximal().unwrap_u8(), 0u8); + assert_eq!(random.is_max(), ConstChoice::FALSE); } } From 2fa8bc1c9a776f07bd4a6ea128efea4f2c73733f Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 12:53:10 +0200 Subject: [PATCH 46/87] Rename `Int::SIGN_BIT_MASK` to `Int::SIGN_MASK` --- src/int.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index e4dcf332..d8aef583 100644 --- a/src/int.rs +++ b/src/int.rs @@ -47,7 +47,7 @@ impl Int { pub const MAX: Self = Self(Uint::MAX.shr(1u32)); // Bit sequence (be): 0111....1111 /// Bit mask for the sign bit of this [`Int`]. - pub const SIGN_BIT_MASK: Self = Self::MIN; // Bit sequence (be): 1000....0000 + pub const SIGN_MASK: Self = Self::MIN; // Bit sequence (be): 1000....0000 /// All-one bit mask. pub const FULL_MASK: Self = Self(Uint::MAX); // Bit sequence (be): 1111...1111 @@ -170,7 +170,7 @@ impl Int { /// Invert the most significant bit (msb) of this [`Int`] const fn invert_msb(&self) -> Self { - Self(self.0.bitxor(&Self::SIGN_BIT_MASK.0)) + Self(self.0.bitxor(&Self::SIGN_MASK.0)) } } From 6050c7ec51bddd5a474553b231f2c6b4d8c5fc40 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 13:00:05 +0200 Subject: [PATCH 47/87] Satisfy clippy --- src/int.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index d8aef583..8ca63b98 100644 --- a/src/int.rs +++ b/src/int.rs @@ -147,12 +147,12 @@ impl Int { /// Whether this [`Int`] is equal to `Self::MIN`. pub fn is_min(&self) -> ConstChoice { - Self::eq(&self, &Self::MIN) + Self::eq(self, &Self::MIN) } /// Whether this [`Int`] is equal to `Self::MAX`. pub fn is_max(&self) -> ConstChoice { - Self::eq(&self, &Self::MAX) + Self::eq(self, &Self::MAX) } /// The sign and magnitude of this [`Int`]. From e9ad737defbf6bf2d91a11f81643f872c0291962 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 13:14:05 +0200 Subject: [PATCH 48/87] Fix carry on `Uint::negc` --- src/int/neg.rs | 16 ++++++++-------- src/uint/neg.rs | 27 +++++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/int/neg.rs b/src/int/neg.rs index 7e34f4d3..e6a8cc80 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -13,7 +13,7 @@ impl Int { /// Perform the two's complement "negate" operation on this [`Int`]: /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. /// - /// Note: a non-zero carry indicates `self == Self::ZERO`. + /// Note: a zero carry indicates `self == Self::ZERO`. /// /// Warning: this operation is unsafe to use as negation; the negation is incorrect when /// `self == Self::MIN`. @@ -43,7 +43,7 @@ impl Int { #[cfg(test)] mod tests { - use num_traits::{ConstOne, ConstZero}; + use num_traits::ConstZero; use crate::{ConstChoice, Word, I128}; @@ -63,29 +63,29 @@ mod tests { } #[test] - fn negate_unsafe() { + fn negc() { let min_plus_one = I128 { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; let (res, carry) = I128::MIN.negc(); - assert_eq!(carry, Word::ZERO); + assert_eq!(carry, Word::MAX); assert_eq!(res, I128::MIN); let (res, carry) = I128::MINUS_ONE.negc(); - assert_eq!(carry, Word::ZERO); + assert_eq!(carry, Word::MAX); assert_eq!(res, I128::ONE); let (res, carry) = I128::ZERO.negc(); - assert_eq!(carry, Word::ONE); + assert_eq!(carry, Word::ZERO); assert_eq!(res, I128::ZERO); let (res, carry) = I128::ONE.negc(); - assert_eq!(carry, Word::ZERO); + assert_eq!(carry, Word::MAX); assert_eq!(res, I128::MINUS_ONE); let (res, carry) = I128::MAX.negc(); - assert_eq!(carry, Word::ZERO); + assert_eq!(carry, Word::MAX); assert_eq!(res, min_plus_one); } diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 18eb528a..4e96db4f 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -9,6 +9,7 @@ impl Uint { } /// Perform negation; additionally return the carry. + /// Note: the carry equals `Word::ZERO` when `self == Self::ZERO`, and `Word::MAX` otherwise. pub const fn negc(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; @@ -19,7 +20,7 @@ impl Uint { carry = r >> Limb::BITS; i += 1; } - (Uint::new(ret), carry as Word) + (Uint::new(ret), carry.wrapping_add(!0) as Word) } /// Perform negation, if `negate` is truthy. @@ -47,7 +48,9 @@ impl WrappingNeg for Uint { #[cfg(test)] mod tests { - use crate::U256; + use num_traits::ConstZero; + + use crate::{ConstChoice, Word, U256}; #[test] fn wrapping_neg() { @@ -62,4 +65,24 @@ mod tests { U256::from_u64(42).saturating_sub(&U256::ONE).not() ); } + + #[test] + fn negc() { + assert_eq!(U256::ZERO.negc(), (U256::ZERO, Word::ZERO)); + assert_eq!(U256::ONE.negc(), (U256::MAX, Word::MAX)); + assert_eq!(U256::MAX.negc(), (U256::ONE, Word::MAX)); + } + + #[test] + fn wrapping_neg_if() { + let negate = ConstChoice::TRUE; + assert_eq!(U256::ZERO.wrapping_neg_if(negate), U256::ZERO); + assert_eq!(U256::ONE.wrapping_neg_if(negate), U256::MAX); + assert_eq!(U256::MAX.wrapping_neg_if(negate), U256::ONE); + + let do_not_negate = ConstChoice::FALSE; + assert_eq!(U256::ZERO.wrapping_neg_if(do_not_negate), U256::ZERO); + assert_eq!(U256::ONE.wrapping_neg_if(do_not_negate), U256::ONE); + assert_eq!(U256::MAX.wrapping_neg_if(do_not_negate), U256::MAX); + } } From 6bffaedf604d87df630f0debbdcec34452b4c78b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 16:02:47 +0200 Subject: [PATCH 49/87] Impl serde for `Int` --- src/int.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/int.rs b/src/int.rs index 8ca63b98..703f67e4 100644 --- a/src/int.rs +++ b/src/int.rs @@ -3,6 +3,8 @@ use core::fmt; use num_traits::ConstZero; +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use crate::{Bounded, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; @@ -275,6 +277,32 @@ impl fmt::UpperHex for Int { } } +#[cfg(feature = "serde")] +impl<'de, const LIMBS: usize> Deserialize<'de> for Int +where + Uint: Encoding, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Self(Uint::deserialize(deserializer)?)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Int +where + Uint: Encoding, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.0.serialize(serializer) + } +} + impl_int_aliases! { (I64, 64, "64-bit"), (I128, 128, "128-bit"), @@ -314,6 +342,8 @@ mod tests { use crate::int::I128; use crate::ConstChoice; + #[cfg(feature = "serde")] + use crate::U128; #[cfg(target_pointer_width = "64")] #[test] @@ -391,4 +421,26 @@ mod tests { let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); assert_eq!(random.is_max(), ConstChoice::FALSE); } + + #[cfg(feature = "serde")] + #[test] + fn serde() { + const TEST: I128 = I128::new_from_uint(U128::from_u64(0x0011223344556677)); + + let serialized = bincode::serialize(&TEST).unwrap(); + let deserialized: I128 = bincode::deserialize(&serialized).unwrap(); + + assert_eq!(TEST, deserialized); + } + + #[cfg(feature = "serde")] + #[test] + fn serde_owned() { + const TEST: I128 = I128::new_from_uint(U128::from_u64(0x0011223344556677)); + + let serialized = bincode::serialize(&TEST).unwrap(); + let deserialized: I128 = bincode::deserialize_from(serialized.as_slice()).unwrap(); + + assert_eq!(TEST, deserialized); + } } From 33b94cf17a71af7c4280d2e1253361368fc7f374 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 16:04:49 +0200 Subject: [PATCH 50/87] Fix serde for `Int` --- src/int.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index 703f67e4..979e6b90 100644 --- a/src/int.rs +++ b/src/int.rs @@ -280,7 +280,7 @@ impl fmt::UpperHex for Int { #[cfg(feature = "serde")] impl<'de, const LIMBS: usize> Deserialize<'de> for Int where - Uint: Encoding, + Int: Encoding, { fn deserialize(deserializer: D) -> Result where @@ -293,7 +293,7 @@ where #[cfg(feature = "serde")] impl Serialize for Int where - Uint: Encoding, + Int: Encoding, { fn serialize(&self, serializer: S) -> Result where From 7c707993e21e58b54ff90901d0cb3cbed85055dc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Thu, 17 Oct 2024 16:23:45 +0200 Subject: [PATCH 51/87] Fix serde for `Int` attempt 2 --- src/int.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index 979e6b90..4933fff0 100644 --- a/src/int.rs +++ b/src/int.rs @@ -286,7 +286,9 @@ where where D: Deserializer<'de>, { - Ok(Self(Uint::deserialize(deserializer)?)) + let mut buffer = Self::ZERO.to_le_bytes(); + serdect::array::deserialize_hex_or_bin(buffer.as_mut(), deserializer)?; + Ok(Self::from_le_bytes(buffer)) } } @@ -299,7 +301,7 @@ where where S: Serializer, { - self.0.serialize(serializer) + serdect::array::serialize_hex_lower_or_bin(&Encoding::to_le_bytes(self), serializer) } } From c7642e5262340b893c72e957f5f4fa9330a70fdb Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 11:41:26 +0200 Subject: [PATCH 52/87] Implement `Int::from` --- src/int.rs | 1 + src/int/from.rs | 141 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 src/int/from.rs diff --git a/src/int.rs b/src/int.rs index 4933fff0..a440c3ef 100644 --- a/src/int.rs +++ b/src/int.rs @@ -16,6 +16,7 @@ mod add; mod cmp; mod div; mod encoding; +mod from; mod mul; mod neg; mod resize; diff --git a/src/int/from.rs b/src/int/from.rs new file mode 100644 index 00000000..47de0da4 --- /dev/null +++ b/src/int/from.rs @@ -0,0 +1,141 @@ +//! `From`-like conversions for [`Int`]. + +use crate::{Int, Limb, Uint, I128, I64}; + +impl Int { + /// Create a [`Int`] from an `i8` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i8(n: i8) -> Self { + Self(Uint::from_u8(n as u8)) + } + + /// Create a [`Int`] from an `i16` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i16(n: i16) -> Self { + Self(Uint::from_u16(n as u16)) + } + + /// Create a [`Int`] from an `i32` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i32(n: i32) -> Self { + Self(Uint::from_u32(n as u32)) + } + + /// Create a [`Int`] from an `i64` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + #[cfg(target_pointer_width = "32")] + pub const fn from_i64(n: i64) -> Self { + Self(Uint::from_u64(n as u64)) + } + + /// Create a [`Int`] from an `i64` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + #[cfg(target_pointer_width = "64")] + pub const fn from_i64(n: i64) -> Self { + Self(Uint::from_u64(n as u64)) + } + + /// Create a [`Int`] from an `i128` (const-friendly) + // TODO(tarcieri): replace with `const impl From` when stable + pub const fn from_i128(n: i128) -> Self { + Self(Uint::from_u128(n as u128)) + } +} + +impl From for Int { + fn from(n: i8) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i8(n) + } +} + +impl From for Int { + fn from(n: i16) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i16(n) + } +} + +impl From for Int { + fn from(n: i32) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS > 0, "limbs must be non-zero"); + Self::from_i32(n) + } +} + +impl From for Int { + fn from(n: i64) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS >= 8 / Limb::BYTES, "not enough limbs"); + Self::from_i64(n) + } +} + +impl From for Int { + fn from(n: i128) -> Self { + // TODO(tarcieri): const where clause when possible + debug_assert!(LIMBS >= 16 / Limb::BYTES, "not enough limbs"); + Self::from_i128(n) + } +} + +impl From for i64 { + fn from(n: I64) -> i64 { + u64::from(n.0) as i64 + } +} + +impl From for i128 { + fn from(n: I128) -> i128 { + u128::from(n.0) as i128 + } +} + +impl From<&Int> for Int { + fn from(num: &Int) -> Int { + num.resize() + } +} + +#[cfg(test)] +mod tests { + #[cfg(target_pointer_width = "64")] + use crate::I128 as IntEx; + #[cfg(target_pointer_width = "32")] + use crate::I64 as IntEx; + use crate::{Limb, I128}; + + #[test] + fn from_i8() { + let n = IntEx::from(42i8); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + } + + #[test] + fn from_i16() { + let n = IntEx::from(42i16); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + } + + #[test] + fn from_i32() { + let n = IntEx::from(42i32); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + } + + #[test] + fn from_i64() { + let n = IntEx::from(42i64); + assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + } + + #[test] + fn from_i128() { + let n = I128::from(42i128); + assert_eq!(&n.as_limbs()[..2], &[Limb(42), Limb(0)]); + assert_eq!(i128::from(n), 42i128); + } +} From 080c6c88eeaaed9d3f01953ca3a1657edd5e61ae Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 13:32:48 +0200 Subject: [PATCH 53/87] Fix `Int::resize` bug --- src/int/resize.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/int/resize.rs b/src/int/resize.rs index a6222717..5a7a5957 100644 --- a/src/int/resize.rs +++ b/src/int/resize.rs @@ -1,10 +1,50 @@ -use crate::Int; +use crate::{Int, Limb, Uint}; impl Int { /// Resize the representation of `self` to an `Int`. /// Warning: this operation may lead to loss of information. #[inline(always)] pub const fn resize(&self) -> Int { - Int::new_from_uint(self.0.resize::()) + let mut limbs = [Limb::select(Limb::ZERO, Limb::MAX, self.is_negative()); T]; + let mut i = 0; + let dim = if T < LIMBS { T } else { LIMBS }; + while i < dim { + limbs[i] = self.0.limbs[i]; + i += 1; + } + Int { 0: Uint { limbs } } + } +} + +#[cfg(test)] +mod tests { + use num_traits::WrappingSub; + + use crate::{I128, I256}; + + #[test] + fn scale_down() { + assert_eq!(I256::MIN.resize::<{ I128::LIMBS }>(), I128::ZERO); + assert_eq!(I256::MINUS_ONE.resize::<{ I128::LIMBS }>(), I128::MINUS_ONE); + assert_eq!(I256::ZERO.resize::<{ I128::LIMBS }>(), I128::ZERO); + assert_eq!(I256::ONE.resize::<{ I128::LIMBS }>(), I128::ONE); + assert_eq!(I256::MAX.resize::<{ I128::LIMBS }>(), I128::MINUS_ONE); + } + + #[test] + fn scale_up() { + assert_eq!( + I128::MIN.resize::<{ I256::LIMBS }>(), + I256::ZERO.wrapping_sub(&I256 { + 0: I128::MIN.0.resize() + }) + ); + assert_eq!(I128::MINUS_ONE.resize::<{ I256::LIMBS }>(), I256::MINUS_ONE); + assert_eq!(I128::ZERO.resize::<{ I256::LIMBS }>(), I256::ZERO); + assert_eq!(I128::ONE.resize::<{ I256::LIMBS }>(), I256::ONE); + assert_eq!( + I128::MAX.resize::<{ I256::LIMBS }>(), + I256::new_from_uint(I128::MAX.0.resize()) + ); } } From d53a0ef50fe393172255034e0aeef743a7b3a60a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 13:59:43 +0200 Subject: [PATCH 54/87] Fix `Int::from` --- src/int/from.rs | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/int/from.rs b/src/int/from.rs index 47de0da4..48d7272c 100644 --- a/src/int/from.rs +++ b/src/int/from.rs @@ -1,44 +1,55 @@ //! `From`-like conversions for [`Int`]. -use crate::{Int, Limb, Uint, I128, I64}; +use crate::{Int, Limb, Uint, Word, I128, I64}; impl Int { /// Create a [`Int`] from an `i8` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i8(n: i8) -> Self { - Self(Uint::from_u8(n as u8)) + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() } /// Create a [`Int`] from an `i16` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i16(n: i16) -> Self { - Self(Uint::from_u16(n as u16)) + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() } /// Create a [`Int`] from an `i32` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable - pub const fn from_i32(n: i32) -> Self { - Self(Uint::from_u32(n as u32)) + pub fn from_i32(n: i32) -> Self { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() } /// Create a [`Int`] from an `i64` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable #[cfg(target_pointer_width = "32")] pub const fn from_i64(n: i64) -> Self { - Self(Uint::from_u64(n as u64)) + assert!(LIMBS >= 2, "number of limbs must be two or greater"); + let hi = (n >> 32) as u32; + let is_negative = ConstChoice::from_word_msb(hi); + let limb = Limb::select(Limb::ZERO, Limb::MAX, is_negative); + let mut limbs = [limb; LIMBS]; + limbs[0].0 = (n & 0xFFFFFFFF) as u32; + limbs[0].0 = hi; + Self(Uint::new(limbs)) } /// Create a [`Int`] from an `i64` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable #[cfg(target_pointer_width = "64")] pub const fn from_i64(n: i64) -> Self { - Self(Uint::from_u64(n as u64)) + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() } /// Create a [`Int`] from an `i128` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i128(n: i128) -> Self { - Self(Uint::from_u128(n as u128)) + Int::new_from_uint(Uint::<{ I128::LIMBS }>::from_u128(n as u128)).resize() } } @@ -112,24 +123,32 @@ mod tests { fn from_i8() { let n = IntEx::from(42i8); assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i8); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); } #[test] fn from_i16() { let n = IntEx::from(42i16); assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i16); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); } #[test] fn from_i32() { let n = IntEx::from(42i32); assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i32); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); } #[test] fn from_i64() { let n = IntEx::from(42i64); assert_eq!(n.as_limbs(), &[Limb(42), Limb(0)]); + let n = IntEx::from(-42i64); + assert_eq!(n.as_limbs(), &[Limb::MAX - Limb::from(41u32), Limb::MAX]); } #[test] @@ -137,5 +156,8 @@ mod tests { let n = I128::from(42i128); assert_eq!(&n.as_limbs()[..2], &[Limb(42), Limb(0)]); assert_eq!(i128::from(n), 42i128); + let n = I128::from(-42i128); + assert_eq!(&n.as_limbs()[..2], &[Limb::MAX - Limb(41), Limb::MAX]); + assert_eq!(i128::from(n), -42i128); } } From 8947396781f27c8ab849626a49bea49e1fbfd20b Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 15:24:54 +0200 Subject: [PATCH 55/87] Impl `Int::div_floor` and `Int::div_mod_floor` --- src/int.rs | 9 ++- src/int/div.rs | 167 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 166 insertions(+), 10 deletions(-) diff --git a/src/int.rs b/src/int.rs index a440c3ef..ffbdb6a3 100644 --- a/src/int.rs +++ b/src/int.rs @@ -7,7 +7,9 @@ use num_traits::ConstZero; use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use crate::{Bounded, ConstChoice, ConstCtOption, Encoding, Limb, NonZero, Odd, Uint, Word}; +use crate::{ + Bounded, ConstChoice, ConstCtOption, Constants, Encoding, Limb, NonZero, Odd, Uint, Word, +}; #[macro_use] mod macros; @@ -212,7 +214,10 @@ impl Bounded for Int { const BYTES: usize = Self::BYTES; } -// TODO: impl Constants +impl Constants for Int { + const ONE: Self = Self::ONE; + const MAX: Self = Self::MAX; +} impl Default for Int { fn default() -> Self { diff --git a/src/int/div.rs b/src/int/div.rs index b7dc3a6b..06305454 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -2,14 +2,19 @@ use core::ops::Div; -use subtle::CtOption; +use subtle::{Choice, CtOption}; -use crate::{CheckedDiv, Int, NonZero}; +use crate::{CheckedDiv, ConstChoice, Int, NonZero, Uint}; impl Int { - /// Perform checked division, returning a [`CtOption`] which `is_some` - /// only if the rhs != 0 - pub fn checked_div(&self, rhs: &Self) -> CtOption { + #[inline] + /// Base checked_div_mod operation. + /// Given `(a, b)` computes the quotient and remainder of the absolute + /// Note: this operation rounds towards zero, truncating any fractional part of the exact result. + fn checked_div_mod( + &self, + rhs: &NonZero, + ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice) { // Step 1: split operands into signs, magnitudes and whether they are zero. let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); @@ -18,11 +23,111 @@ impl Int { // This should be done if and only if lhs and rhs have opposing signs. // Note: if the lhs is zero, the resulting magnitude will also be zero. Negating zero, // however, still yields zero, so having a truthy `negate_result` in that scenario is OK. - let negate_result = lhs_sgn.xor(rhs_sgn); + let opposing_signs = lhs_sgn.xor(rhs_sgn); // Step 3. Construct result - lhs_mag.checked_div(&rhs_mag).and_then(|magnitude| { - Self::new_from_sign_and_magnitude(negate_result, magnitude).into() + // safe to unwrap since rhs is NonZero. + let (quotient, remainder) = lhs_mag.div_rem(&NonZero::new(rhs_mag).unwrap()); + + (quotient, remainder, opposing_signs) + } + + /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. + /// Note: this operation rounds towards zero, truncating any fractional part of the exact result. + pub fn checked_div(&self, rhs: &Self) -> CtOption { + NonZero::new(*rhs).and_then(|rhs| { + let (quotient, _, opposing_signs) = self.checked_div_mod(&rhs); + Self::new_from_sign_and_magnitude(opposing_signs, quotient).into() + }) + } + + /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. + /// Note: this operation rounds down. + /// + /// Example: + /// ``` + /// use crypto_bigint::I128; + /// assert_eq!( + /// I128::from(8).checked_div_floor(&I128::from(3)).unwrap(), + /// I128::from(2) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_floor(&I128::from(3)).unwrap(), + /// I128::from(-3) + /// ); + /// assert_eq!( + /// I128::from(8).checked_div_floor(&I128::from(-3)).unwrap(), + /// I128::from(-3) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_floor(&I128::from(-3)).unwrap(), + /// I128::from(2) + /// ) + /// ``` + pub fn checked_div_floor(&self, rhs: &Self) -> CtOption { + NonZero::new(*rhs).and_then(|rhs| { + let (quotient, remainder, opposing_signs) = self.checked_div_mod(&rhs); + + // Increase the quotient by one when lhs and rhs have opposing signs and there + // is a non-zero remainder. + let increment_quotient = remainder.is_nonzero().and(opposing_signs); + let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); + let quotient = Uint::select("ient, "ient_sub_one, increment_quotient); + + Self::new_from_sign_and_magnitude(opposing_signs, quotient).into() + }) + } + + /// Perform checked division and mod, returning a [`CtOption`] which `is_some` only + /// if the rhs != 0. + /// Note: this operation rounds down. + /// + /// Example: + /// ``` + /// use crypto_bigint::I128; + /// assert_eq!( + /// I128::from(8).checked_div_mod_floor(&I128::from(3)).unwrap(), + /// (I128::from(2), I128::from(2)) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_mod_floor(&I128::from(3)).unwrap(), + /// (I128::from(-3), I128::from(-1)) + /// ); + /// assert_eq!( + /// I128::from(8).checked_div_mod_floor(&I128::from(-3)).unwrap(), + /// (I128::from(-3), I128::from(-1)) + /// ); + /// assert_eq!( + /// I128::from(-8).checked_div_mod_floor(&I128::from(-3)).unwrap(), + /// (I128::from(2), I128::from(2)) + /// ); + /// ``` + pub fn checked_div_mod_floor(&self, rhs: &Self) -> CtOption<(Self, Self)> { + let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); + let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let opposing_signs = lhs_sgn.xor(rhs_sgn); + NonZero::new(rhs_mag).and_then(|rhs_mag| { + let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); + + // Increase the quotient by one when lhs and rhs have opposing signs and there + // is a non-zero remainder. + let modify = remainder.is_nonzero().and(opposing_signs); + let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); + let quotient = Uint::select("ient, "ient_sub_one, modify); + + // Invert the remainder and add one to remainder when lhs and rhs have opposing signs + // and the remainder is non-zero. + let inv_remainder = rhs_mag.wrapping_sub(&remainder); + let remainder = Uint::select(&remainder, &inv_remainder, modify); + + CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, quotient)).and_then( + |quotient| { + CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, remainder)) + .and_then(|remainder| { + CtOption::new((quotient, remainder), Choice::from(1u8)) + }) + }, + ) }) } } @@ -160,4 +265,50 @@ mod tests { let result = I128::MAX.checked_div(&I128::MAX); assert_eq!(result.unwrap(), I128::ONE); } + + #[test] + fn test_checked_div_floor() { + assert_eq!( + I128::from(8).checked_div_floor(&I128::from(3)).unwrap(), + I128::from(2) + ); + assert_eq!( + I128::from(-8).checked_div_floor(&I128::from(3)).unwrap(), + I128::from(-3) + ); + assert_eq!( + I128::from(8).checked_div_floor(&I128::from(-3)).unwrap(), + I128::from(-3) + ); + assert_eq!( + I128::from(-8).checked_div_floor(&I128::from(-3)).unwrap(), + I128::from(2) + ); + } + + #[test] + fn test_checked_div_mod_floor() { + assert_eq!( + I128::from(8).checked_div_mod_floor(&I128::from(3)).unwrap(), + (I128::from(2), I128::from(2)) + ); + assert_eq!( + I128::from(-8) + .checked_div_mod_floor(&I128::from(3)) + .unwrap(), + (I128::from(-3), I128::from(-1)) + ); + assert_eq!( + I128::from(8) + .checked_div_mod_floor(&I128::from(-3)) + .unwrap(), + (I128::from(-3), I128::from(-1)) + ); + assert_eq!( + I128::from(-8) + .checked_div_mod_floor(&I128::from(-3)) + .unwrap(), + (I128::from(2), I128::from(2)) + ); + } } From 35fe35d7ff47ae28efa77a911f4a6e0eaee501c5 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 16:09:13 +0200 Subject: [PATCH 56/87] Fix problems --- src/int/from.rs | 2 ++ src/int/resize.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/int/from.rs b/src/int/from.rs index 48d7272c..0d084489 100644 --- a/src/int/from.rs +++ b/src/int/from.rs @@ -1,5 +1,7 @@ //! `From`-like conversions for [`Int`]. +#[cfg(target_pointer_width = "32")] +use crate::ConstChoice; use crate::{Int, Limb, Uint, Word, I128, I64}; impl Int { diff --git a/src/int/resize.rs b/src/int/resize.rs index 5a7a5957..bc30cdbe 100644 --- a/src/int/resize.rs +++ b/src/int/resize.rs @@ -12,7 +12,7 @@ impl Int { limbs[i] = self.0.limbs[i]; i += 1; } - Int { 0: Uint { limbs } } + Int::new_from_uint(Uint { limbs }) } } From 8ec01c3b33d263467a5958fba5db77db8335963a Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 16:18:51 +0200 Subject: [PATCH 57/87] Attempt to fix from_i64 for x86 platform --- src/int/from.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/int/from.rs b/src/int/from.rs index 0d084489..4c8ceecf 100644 --- a/src/int/from.rs +++ b/src/int/from.rs @@ -30,14 +30,7 @@ impl Int { // TODO(tarcieri): replace with `const impl From` when stable #[cfg(target_pointer_width = "32")] pub const fn from_i64(n: i64) -> Self { - assert!(LIMBS >= 2, "number of limbs must be two or greater"); - let hi = (n >> 32) as u32; - let is_negative = ConstChoice::from_word_msb(hi); - let limb = Limb::select(Limb::ZERO, Limb::MAX, is_negative); - let mut limbs = [limb; LIMBS]; - limbs[0].0 = (n & 0xFFFFFFFF) as u32; - limbs[0].0 = hi; - Self(Uint::new(limbs)) + Int::new_from_uint(Uint::<{ I64::LIMBS }>::from_i64(n as u64)).resize() } /// Create a [`Int`] from an `i64` (const-friendly) From 3378cbb324ccb9dbd5d3f787fec3b4055b6e5352 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Fri, 18 Oct 2024 16:24:50 +0200 Subject: [PATCH 58/87] Attempt #2 --- src/int/from.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/int/from.rs b/src/int/from.rs index 4c8ceecf..6d105b2a 100644 --- a/src/int/from.rs +++ b/src/int/from.rs @@ -1,7 +1,5 @@ //! `From`-like conversions for [`Int`]. -#[cfg(target_pointer_width = "32")] -use crate::ConstChoice; use crate::{Int, Limb, Uint, Word, I128, I64}; impl Int { @@ -30,7 +28,7 @@ impl Int { // TODO(tarcieri): replace with `const impl From` when stable #[cfg(target_pointer_width = "32")] pub const fn from_i64(n: i64) -> Self { - Int::new_from_uint(Uint::<{ I64::LIMBS }>::from_i64(n as u64)).resize() + Int::new_from_uint(Uint::<{ I64::LIMBS }>::from_u64(n as u64)).resize() } /// Create a [`Int`] from an `i64` (const-friendly) From d2e56923541796826926c658bf04c011a35d56ea Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 21 Oct 2024 11:44:24 +0200 Subject: [PATCH 59/87] Introduce `lte` and `gte` for `Uint` --- src/uint/cmp.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index eeb9a554..22572229 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -2,11 +2,14 @@ //! //! By default these are all constant-time and use the `subtle` crate. -use super::Uint; -use crate::{ConstChoice, Limb}; use core::cmp::Ordering; + use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; +use crate::{ConstChoice, Limb}; + +use super::Uint; + impl Uint { /// Return `b` if `c` is truthy, otherwise return `a`. #[inline] @@ -64,6 +67,12 @@ impl Uint { ConstChoice::from_word_mask(borrow.0) } + /// Returns the truthy value if `self <= rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn lte(lhs: &Self, rhs: &Self) -> ConstChoice { + Self::gt(lhs, rhs).not() + } + /// Returns the truthy value if `self > rhs` and the falsy value otherwise. #[inline] pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { @@ -71,6 +80,12 @@ impl Uint { ConstChoice::from_word_mask(borrow.0) } + /// Returns the truthy value if `self >= rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn gte(lhs: &Self, rhs: &Self) -> ConstChoice { + Self::lt(lhs, rhs).not() + } + /// Returns the ordering between `self` and `rhs` as an i8. /// Values correspond to the Ordering enum: /// -1 is Less @@ -160,10 +175,12 @@ impl PartialEq for Uint { #[cfg(test)] mod tests { - use crate::{Integer, Zero, U128}; use core::cmp::Ordering; + use subtle::{ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; + use crate::{Integer, Uint, Zero, U128}; + #[test] fn is_zero() { assert!(bool::from(U128::ZERO.is_zero())); @@ -214,6 +231,25 @@ mod tests { assert!(!bool::from(b.ct_gt(&c))); } + #[test] + fn gte() { + let a = U128::ZERO; + let b = U128::ONE; + let c = U128::MAX; + + assert!(bool::from(Uint::gte(&b, &a))); + assert!(bool::from(Uint::gte(&c, &a))); + assert!(bool::from(Uint::gte(&c, &b))); + + assert!(bool::from(Uint::gte(&a, &a))); + assert!(bool::from(Uint::gte(&b, &b))); + assert!(bool::from(Uint::gte(&c, &c))); + + assert!(!bool::from(Uint::gte(&a, &b))); + assert!(!bool::from(Uint::gte(&a, &c))); + assert!(!bool::from(Uint::gte(&b, &c))); + } + #[test] fn ct_lt() { let a = U128::ZERO; @@ -233,6 +269,25 @@ mod tests { assert!(!bool::from(c.ct_lt(&b))); } + #[test] + fn lte() { + let a = U128::ZERO; + let b = U128::ONE; + let c = U128::MAX; + + assert!(bool::from(Uint::lte(&a, &b))); + assert!(bool::from(Uint::lte(&a, &c))); + assert!(bool::from(Uint::lte(&b, &c))); + + assert!(bool::from(Uint::lte(&a, &a))); + assert!(bool::from(Uint::lte(&b, &b))); + assert!(bool::from(Uint::lte(&c, &c))); + + assert!(!bool::from(Uint::lte(&b, &a))); + assert!(!bool::from(Uint::lte(&c, &a))); + assert!(!bool::from(Uint::lte(&c, &b))); + } + #[test] fn cmp() { let a = U128::ZERO; From 6e0410997f67d0903e5e5b49971dac968e1d6527 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 21 Oct 2024 11:52:01 +0200 Subject: [PATCH 60/87] Introduce `lte` and `gte` for `Int` --- src/int/cmp.rs | 92 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index 7019140c..851a14ea 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -39,12 +39,24 @@ impl Int { Uint::lt(&lhs.invert_msb().0, &rhs.invert_msb().0) } + /// Returns the truthy value if `self <= rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn lte(lhs: &Self, rhs: &Self) -> ConstChoice { + Self::gt(lhs, rhs).not() + } + /// Returns the truthy value if `self > rhs` and the falsy value otherwise. #[inline] pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { Uint::gt(&lhs.invert_msb().0, &rhs.invert_msb().0) } + /// Returns the truthy value if `self >= rhs` and the falsy value otherwise. + #[inline] + pub(crate) const fn gte(lhs: &Self, rhs: &Self) -> ConstChoice { + Self::lt(lhs, rhs).not() + } + /// Returns the ordering between `self` and `rhs` as an i8. /// Values correspond to the Ordering enum: /// -1 is Less @@ -109,7 +121,9 @@ impl PartialEq for Int { #[cfg(test)] mod tests { - use crate::{I128, U128}; + use subtle::{ConstantTimeGreater, ConstantTimeLess}; + + use crate::{Int, I128, U128}; #[test] fn test_is_nonzero() { @@ -172,4 +186,80 @@ mod tests { assert_ne!(true, I128::MINUS_ONE < I128::MINUS_ONE); assert_ne!(true, I128::MIN < I128::MIN); } + + #[test] + fn ct_gt() { + let a = I128::MIN; + let b = I128::ZERO; + let c = I128::MAX; + + assert!(bool::from(b.ct_gt(&a))); + assert!(bool::from(c.ct_gt(&a))); + assert!(bool::from(c.ct_gt(&b))); + + assert!(!bool::from(a.ct_gt(&a))); + assert!(!bool::from(b.ct_gt(&b))); + assert!(!bool::from(c.ct_gt(&c))); + + assert!(!bool::from(a.ct_gt(&b))); + assert!(!bool::from(a.ct_gt(&c))); + assert!(!bool::from(b.ct_gt(&c))); + } + + #[test] + fn gte() { + let a = I128::MIN; + let b = I128::ZERO; + let c = I128::MAX; + + assert!(bool::from(Int::gte(&b, &a))); + assert!(bool::from(Int::gte(&c, &a))); + assert!(bool::from(Int::gte(&c, &b))); + + assert!(bool::from(Int::gte(&a, &a))); + assert!(bool::from(Int::gte(&b, &b))); + assert!(bool::from(Int::gte(&c, &c))); + + assert!(!bool::from(Int::gte(&a, &b))); + assert!(!bool::from(Int::gte(&a, &c))); + assert!(!bool::from(Int::gte(&b, &c))); + } + + #[test] + fn ct_lt() { + let a = I128::ZERO; + let b = I128::ONE; + let c = I128::MAX; + + assert!(bool::from(a.ct_lt(&b))); + assert!(bool::from(a.ct_lt(&c))); + assert!(bool::from(b.ct_lt(&c))); + + assert!(!bool::from(a.ct_lt(&a))); + assert!(!bool::from(b.ct_lt(&b))); + assert!(!bool::from(c.ct_lt(&c))); + + assert!(!bool::from(b.ct_lt(&a))); + assert!(!bool::from(c.ct_lt(&a))); + assert!(!bool::from(c.ct_lt(&b))); + } + + #[test] + fn lte() { + let a = I128::ZERO; + let b = I128::ONE; + let c = I128::MAX; + + assert!(bool::from(Int::lte(&a, &b))); + assert!(bool::from(Int::lte(&a, &c))); + assert!(bool::from(Int::lte(&b, &c))); + + assert!(bool::from(Int::lte(&a, &a))); + assert!(bool::from(Int::lte(&b, &b))); + assert!(bool::from(Int::lte(&c, &c))); + + assert!(!bool::from(Int::lte(&b, &a))); + assert!(!bool::from(Int::lte(&c, &a))); + assert!(!bool::from(Int::lte(&c, &b))); + } } From 34290de5de124426fab423b1d09f2f87f9cdd14e Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 21 Oct 2024 12:55:57 +0200 Subject: [PATCH 61/87] Update `Int::div_rem` --- src/const_choice.rs | 5 +++ src/int/div.rs | 75 ++++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index 90ebd169..f3f2ad77 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -206,6 +206,11 @@ impl ConstChoice { Self(self.0 ^ other.0) } + #[inline] + pub(crate) const fn ne(&self, other: Self) -> Self { + Self::xor(self, other) + } + /// Return `b` if `self` is truthy, otherwise return `a`. #[inline] pub(crate) const fn select_word(&self, a: Word, b: Word) -> Word { diff --git a/src/int/div.rs b/src/int/div.rs index 06305454..a892131b 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -4,41 +4,64 @@ use core::ops::Div; use subtle::{Choice, CtOption}; -use crate::{CheckedDiv, ConstChoice, Int, NonZero, Uint}; +use crate::{CheckedDiv, ConstChoice, ConstCtOption, Int, NonZero, Uint}; impl Int { #[inline] - /// Base checked_div_mod operation. - /// Given `(a, b)` computes the quotient and remainder of the absolute - /// Note: this operation rounds towards zero, truncating any fractional part of the exact result. - fn checked_div_mod( + /// Base div_rem operation. + /// Given `(a, b)`, computes the quotient and remainder of their absolute values. Furthermore, + /// returns the signs of `a` and `b`. + fn div_rem_base( &self, rhs: &NonZero, - ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice) { - // Step 1: split operands into signs, magnitudes and whether they are zero. + ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { + // Step 1: split operands into signs and magnitudes. let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let (rhs_sgn, rhs_mag) = rhs.0.sign_and_magnitude(); - // Step 2. Determine if the result should be negated. - // This should be done if and only if lhs and rhs have opposing signs. - // Note: if the lhs is zero, the resulting magnitude will also be zero. Negating zero, - // however, still yields zero, so having a truthy `negate_result` in that scenario is OK. - let opposing_signs = lhs_sgn.xor(rhs_sgn); - - // Step 3. Construct result + // Step 2. Divide magnitudes // safe to unwrap since rhs is NonZero. let (quotient, remainder) = lhs_mag.div_rem(&NonZero::new(rhs_mag).unwrap()); - (quotient, remainder, opposing_signs) + (quotient, remainder, lhs_sgn, rhs_sgn) + } + + /// + /// Example: + /// ``` + /// use crypto_bigint::{I128, NonZero}; + /// let (quotient, remainder) = I128::from(8).checked_div_rem(&I128::from(3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(2)); + /// assert_eq!(remainder.unwrap(), I128::from(2)); + /// + /// let (quotient, remainder) = I128::from(-8).checked_div_rem(&I128::from(3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(-2)); + /// assert_eq!(remainder.unwrap(), I128::from(-2)); + /// + /// let (quotient, remainder) = I128::from(8).checked_div_rem(&I128::from(-3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(-2)); + /// assert_eq!(remainder.unwrap(), I128::from(2)); + /// + /// let (quotient, remainder) = I128::from(-8).checked_div_rem(&I128::from(-3).to_nz().unwrap()); + /// assert_eq!(quotient.unwrap(), I128::from(2)); + /// assert_eq!(remainder.unwrap(), I128::from(-2)); + /// ``` + pub fn checked_div_rem( + &self, + rhs: &NonZero, + ) -> (ConstCtOption, ConstCtOption) { + let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(rhs); + let opposing_signs = lhs_sgn.ne(rhs_sgn); + ( + Self::new_from_sign_and_magnitude(opposing_signs, quotient), + Self::new_from_sign_and_magnitude(lhs_sgn, remainder), + ) } /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. /// Note: this operation rounds towards zero, truncating any fractional part of the exact result. pub fn checked_div(&self, rhs: &Self) -> CtOption { - NonZero::new(*rhs).and_then(|rhs| { - let (quotient, _, opposing_signs) = self.checked_div_mod(&rhs); - Self::new_from_sign_and_magnitude(opposing_signs, quotient).into() - }) + NonZero::new(*rhs).and_then(|rhs| self.checked_div_rem(&rhs).0.into()) } /// Perform checked division, returning a [`CtOption`] which `is_some` only if the rhs != 0. @@ -66,10 +89,12 @@ impl Int { /// ``` pub fn checked_div_floor(&self, rhs: &Self) -> CtOption { NonZero::new(*rhs).and_then(|rhs| { - let (quotient, remainder, opposing_signs) = self.checked_div_mod(&rhs); + let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(&rhs); - // Increase the quotient by one when lhs and rhs have opposing signs and there - // is a non-zero remainder. + // Increment the quotient when + // - lhs and rhs have opposing signs, and + // - there is a non-zero remainder. + let opposing_signs = lhs_sgn.ne(rhs_sgn); let increment_quotient = remainder.is_nonzero().and(opposing_signs); let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); let quotient = Uint::select("ient, "ient_sub_one, increment_quotient); @@ -134,7 +159,7 @@ impl Int { impl CheckedDiv for Int { fn checked_div(&self, rhs: &Int) -> CtOption { - self.checked_div(rhs) + self.checked_div(rhs).into() } } @@ -166,7 +191,7 @@ impl Div>> for Int { type Output = CtOption>; fn div(self, rhs: NonZero>) -> Self::Output { - self.checked_div(&rhs) + self.checked_div(&rhs).into() } } From cb67265c11824ef708239b88f9624e505e11c287 Mon Sep 17 00:00:00 2001 From: Mateusz Kramarczyk Date: Thu, 17 Oct 2024 21:56:44 +0200 Subject: [PATCH 62/87] Add widening_mul, group absolute value operations, reformat. --- .gitignore | 2 + src/int.rs | 30 +------------ src/int/div.rs | 4 +- src/int/mul.rs | 112 +++++++++++++++++++++++++++++++++++++++++++++--- src/int/neg.rs | 23 +--------- src/int/sign.rs | 75 ++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 58 deletions(-) create mode 100644 src/int/sign.rs diff --git a/.gitignore b/.gitignore index ea7e3f81..5ea04312 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ target proptest-regressions + +.idea/ diff --git a/src/int.rs b/src/int.rs index ffbdb6a3..0b338551 100644 --- a/src/int.rs +++ b/src/int.rs @@ -22,6 +22,7 @@ mod from; mod mul; mod neg; mod resize; +mod sign; mod sub; #[cfg(feature = "rand_core")] @@ -73,25 +74,11 @@ impl Int { /// Const-friendly [`Int`] constructor. /// Note: interprets the `value` as an `Int`. For a proper conversion, - /// see [`Int::new_from_sign_and_magnitude`]. + /// see [`Int::new_from_abs_sign`]. pub const fn new_from_uint(value: Uint) -> Self { Self(value) } - /// Construct new [`Int`] from a sign and magnitude. - /// Returns `None` when the magnitude does not fit in an [`Int`]. - pub const fn new_from_sign_and_magnitude( - is_negative: ConstChoice, - magnitude: Uint, - ) -> ConstCtOption { - ConstCtOption::new( - Self(magnitude).wrapping_neg_if(is_negative), - Uint::gt(&magnitude, &Int::MAX.0) - .not() - .or(is_negative.and(Uint::eq(&magnitude, &Int::MIN.0))), - ) - } - /// Create an [`Int`] from an array of [`Word`]s (i.e. word-sized unsigned /// integers). #[inline] @@ -160,19 +147,6 @@ impl Int { Self::eq(self, &Self::MAX) } - /// The sign and magnitude of this [`Int`]. - pub const fn sign_and_magnitude(&self) -> (ConstChoice, Uint) { - let sign = self.is_negative(); - // Note: this negate_if is safe to use, since we are negating based on self.is_negative() - let magnitude = self.wrapping_neg_if(sign); - (sign, magnitude.0) - } - - /// The magnitude of this [`Int`]. - pub const fn magnitude(&self) -> Uint { - self.sign_and_magnitude().1 - } - /// Invert the most significant bit (msb) of this [`Int`] const fn invert_msb(&self) -> Self { Self(self.0.bitxor(&Self::SIGN_MASK.0)) diff --git a/src/int/div.rs b/src/int/div.rs index a892131b..9c6e8cb4 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -16,8 +16,8 @@ impl Int { rhs: &NonZero, ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { // Step 1: split operands into signs and magnitudes. - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.0.sign_and_magnitude(); + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.0.abs_sign(); // Step 2. Divide magnitudes // safe to unwrap since rhs is NonZero. diff --git a/src/int/mul.rs b/src/int/mul.rs index 4edd2bfd..623517d6 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConstChoice, Int, Uint, Zero}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, WideningMul, Zero}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. @@ -17,11 +17,11 @@ impl Int { rhs: &Int, ) -> (Uint<{ LIMBS }>, Uint<{ RHS_LIMBS }>, ConstChoice) { // Step 1: split operands into their signs and magnitudes. - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let (lhs_abs, lhs_sgn) = self.abs_sign(); + let (rhs_abs, rhs_sgn) = rhs.abs_sign(); // Step 2: multiply the magnitudes - let (lo, hi) = lhs_mag.split_mul(&rhs_mag); + let (lo, hi) = lhs_abs.split_mul(&rhs_abs); // Step 3. Determine if the result should be negated. // This should be done if and only if lhs and rhs have opposing signs. @@ -31,13 +31,30 @@ impl Int { (lo, hi, negate) } + + /// Multiply `self` by `rhs`, returning a concatenated "wide" result. + pub const fn widening_mul( + &self, + rhs: &Int, + ) -> Int + where + Uint: ConcatMixed, MixedOutput = Uint>, + { + let (lhs_abs, lhs_sign) = self.abs_sign(); + let (rhs_abs, rhs_sign) = rhs.abs_sign(); + let product_abs = lhs_abs.widening_mul(&rhs_abs); + let product_sign = lhs_sign.xor(rhs_sign); + + // always fits + Int::new_from_uint(product_abs.wrapping_neg_if(product_sign)) + } } impl CheckedMul> for Int { #[inline] fn checked_mul(&self, rhs: &Int) -> CtOption { let (lo, hi, is_negative) = self.split_mul(rhs); - let val = Self::new_from_sign_and_magnitude(is_negative, lo); + let val = Self::new_from_abs_sign(lo, is_negative); CtOption::from(val).and_then(|int| CtOption::new(int, hi.is_zero())) } } @@ -87,10 +104,24 @@ impl MulAssign<&Checked>> for Checked> } } +// TODO(lleoha): unfortunately we cannot satisfy this (yet!). +// impl +// WideningMul> for Int +// where +// Uint: ConcatMixed, MixedOutput = Uint>, +// { +// type Output = Int; +// +// #[inline] +// fn widening_mul(&self, rhs: Int) -> Self::Output { +// self.widening_mul(&rhs) +// } +// } + #[cfg(test)] mod tests { use crate::int::{Int, I128}; - use crate::CheckedMul; + use crate::{CheckedMul, I256}; #[test] fn test_checked_mul() { @@ -183,4 +214,73 @@ mod tests { let result = I128::MAX.checked_mul(&I128::MAX); assert!(bool::from(result.is_none())); } + + #[test] + fn test_widening_mul() { + assert_eq!( + I128::MIN.widening_mul(&I128::MIN), + I256::from_be_hex("4000000000000000000000000000000000000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MIN.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MIN.widening_mul(&I128::ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!( + I128::MIN.widening_mul(&I128::MAX), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MIN), + I256::from_be_hex("0000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::MINUS_ONE), I256::ONE); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::MINUS_ONE.widening_mul(&I128::ONE), I256::MINUS_ONE); + assert_eq!( + I128::MINUS_ONE.widening_mul(&I128::MAX), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + + assert_eq!(I128::ZERO.widening_mul(&I128::MIN), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MINUS_ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::ONE), I256::ZERO); + assert_eq!(I128::ZERO.widening_mul(&I128::MAX), I256::ZERO); + + assert_eq!( + I128::ONE.widening_mul(&I128::MIN), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000000") + ); + assert_eq!(I128::ONE.widening_mul(&I128::MINUS_ONE), I256::MINUS_ONE); + assert_eq!(I128::ONE.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!(I128::ONE.widening_mul(&I128::ONE), I256::ONE); + assert_eq!( + I128::ONE.widening_mul(&I128::MAX), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + + assert_eq!( + I128::MAX.widening_mul(&I128::MIN), + I256::from_be_hex("C000000000000000000000000000000080000000000000000000000000000000") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MINUS_ONE), + I256::from_be_hex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF80000000000000000000000000000001") + ); + assert_eq!(I128::MAX.widening_mul(&I128::ZERO), I256::ZERO); + assert_eq!( + I128::MAX.widening_mul(&I128::ONE), + I256::from_be_hex("000000000000000000000000000000007FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") + ); + assert_eq!( + I128::MAX.widening_mul(&I128::MAX), + I256::from_be_hex("3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000000000000000000000000001") + ); + } } diff --git a/src/int/neg.rs b/src/int/neg.rs index e6a8cc80..701e6cef 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -5,11 +5,6 @@ use subtle::CtOption; use crate::{ConstChoice, Int, Word}; impl Int { - /// Whether this [`Int`] is negative, as a `ConstChoice`. - pub const fn is_negative(&self) -> ConstChoice { - ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) - } - /// Perform the two's complement "negate" operation on this [`Int`]: /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. /// @@ -43,24 +38,8 @@ impl Int { #[cfg(test)] mod tests { - use num_traits::ConstZero; - use crate::{ConstChoice, Word, I128}; - - #[test] - fn is_negative() { - assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); - assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); - assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); - - let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); - - let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); - assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); - } + use num_traits::ConstZero; #[test] fn negc() { diff --git a/src/int/sign.rs b/src/int/sign.rs new file mode 100644 index 00000000..cf48010f --- /dev/null +++ b/src/int/sign.rs @@ -0,0 +1,75 @@ +use crate::{ConstChoice, ConstCtOption, Int, Uint}; + +impl Int { + /// Construct new [`Int`] from a sign and magnitude. + /// Returns `None` when the magnitude does not fit in an [`Int`]. + pub const fn new_from_abs_sign( + abs: Uint, + is_negative: ConstChoice, + ) -> ConstCtOption { + let magnitude = Self(abs).wrapping_neg_if(is_negative); + let fits = Uint::gt(&abs, &Int::MAX.0) + .not() + .or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); + ConstCtOption::new(magnitude, fits) + } + + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn is_negative(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) + } + + /// Whether this [`Int`] is positive, as a `ConstChoice`. + pub const fn is_positive(&self) -> ConstChoice { + Int::is_negative(self).not().and(Int::is_nonzero(self)) + } + + /// The sign and magnitude of this [`Int`]. + pub const fn abs_sign(&self) -> (Uint, ConstChoice) { + let sign = self.is_negative(); + // Note: this negate_if is safe to use, since we are negating based on self.is_negative() + let abs = self.wrapping_neg_if(sign); + (abs.0, sign) + } + + /// The magnitude of this [`Int`]. + pub const fn abs(&self) -> Uint { + self.abs_sign().0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::I128; + + #[test] + fn is_negative() { + assert_eq!(I128::MIN.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.is_negative(), ConstChoice::TRUE); + assert_eq!(I128::ZERO.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_negative(), ConstChoice::FALSE); + assert_eq!(I128::MAX.is_negative(), ConstChoice::FALSE); + + let random_negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_negative.is_negative(), ConstChoice::TRUE); + + let random_positive = I128::from_be_hex("71113333555577779999BBBBDDDDFFFF"); + assert_eq!(random_positive.is_negative(), ConstChoice::FALSE); + } + + #[test] + fn is_positive() { + assert_eq!(I128::MIN.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::MINUS_ONE.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ZERO.is_positive(), ConstChoice::FALSE); + assert_eq!(I128::ONE.is_positive(), ConstChoice::TRUE); + assert_eq!(I128::MAX.is_positive(), ConstChoice::TRUE); + + let random_negative = I128::from_be_hex("deadbeefcafebabedeadbeefcafebabe"); + assert_eq!(random_negative.is_positive(), ConstChoice::FALSE); + + let random_positive = I128::from_be_hex("0badc0dedeadc0decafebabedeadcafe"); + assert_eq!(random_positive.is_positive(), ConstChoice::TRUE); + } +} From 3773bb79e036f7a58ae9e62e76ed7bf1f8de128f Mon Sep 17 00:00:00 2001 From: Mateusz Kramarczyk Date: Fri, 18 Oct 2024 18:46:40 +0200 Subject: [PATCH 63/87] bench --- benches/int.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/benches/int.rs b/benches/int.rs index 4108c46f..15d90094 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -58,6 +58,58 @@ fn bench_mul(c: &mut Criterion) { }); } +fn bench_widening_mul(c: &mut Criterion) { + let mut group = c.benchmark_group("widening ops"); + + group.bench_function("widening_mul, I128xI128", |b| { + b.iter_batched( + || (I128::random(&mut OsRng), I128::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I256xI256", |b| { + b.iter_batched( + || (I256::random(&mut OsRng), I256::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I512xI512", |b| { + b.iter_batched( + || (I512::random(&mut OsRng), I512::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I1024xI1024", |b| { + b.iter_batched( + || (I1024::random(&mut OsRng), I1024::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I2048xI2048", |b| { + b.iter_batched( + || (I2048::random(&mut OsRng), I2048::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("widening_mul, I4096xI4096", |b| { + b.iter_batched( + || (I4096::random(&mut OsRng), I4096::random(&mut OsRng)), + |(x, y)| black_box(x.widening_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + fn bench_div(c: &mut Criterion) { let mut group = c.benchmark_group("wrapping ops"); @@ -280,6 +332,6 @@ fn bench_sub(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_mul, bench_div, bench_add, bench_sub,); +criterion_group!(benches, bench_mul, bench_widening_mul, bench_div, bench_add, bench_sub,); criterion_main!(benches); From e218afa2359fd9222089c6018aadc169d6525419 Mon Sep 17 00:00:00 2001 From: Mateusz Kramarczyk Date: Fri, 18 Oct 2024 18:53:45 +0200 Subject: [PATCH 64/87] Address review remarks, add benchmarks. --- benches/int.rs | 9 ++++++++- src/int/div.rs | 18 +++++++----------- src/int/mul.rs | 2 +- src/int/sign.rs | 4 ++-- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/benches/int.rs b/benches/int.rs index 15d90094..466eb6b7 100644 --- a/benches/int.rs +++ b/benches/int.rs @@ -332,6 +332,13 @@ fn bench_sub(c: &mut Criterion) { group.finish(); } -criterion_group!(benches, bench_mul, bench_widening_mul, bench_div, bench_add, bench_sub,); +criterion_group!( + benches, + bench_mul, + bench_widening_mul, + bench_div, + bench_add, + bench_sub, +); criterion_main!(benches); diff --git a/src/int/div.rs b/src/int/div.rs index 9c6e8cb4..fddeabde 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -99,7 +99,7 @@ impl Int { let quotient_sub_one = quotient.wrapping_add(&Uint::ONE); let quotient = Uint::select("ient, "ient_sub_one, increment_quotient); - Self::new_from_sign_and_magnitude(opposing_signs, quotient).into() + Self::new_from_abs_sign(quotient, opposing_signs).into() }) } @@ -128,8 +128,8 @@ impl Int { /// ); /// ``` pub fn checked_div_mod_floor(&self, rhs: &Self) -> CtOption<(Self, Self)> { - let (lhs_sgn, lhs_mag) = self.sign_and_magnitude(); - let (rhs_sgn, rhs_mag) = rhs.sign_and_magnitude(); + let (lhs_mag, lhs_sgn) = self.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.abs_sign(); let opposing_signs = lhs_sgn.xor(rhs_sgn); NonZero::new(rhs_mag).and_then(|rhs_mag| { let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); @@ -145,14 +145,10 @@ impl Int { let inv_remainder = rhs_mag.wrapping_sub(&remainder); let remainder = Uint::select(&remainder, &inv_remainder, modify); - CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, quotient)).and_then( - |quotient| { - CtOption::from(Int::new_from_sign_and_magnitude(opposing_signs, remainder)) - .and_then(|remainder| { - CtOption::new((quotient, remainder), Choice::from(1u8)) - }) - }, - ) + CtOption::from(Int::new_from_abs_sign(quotient, opposing_signs)).and_then(|quotient| { + CtOption::from(Int::new_from_abs_sign(remainder, opposing_signs)) + .and_then(|remainder| CtOption::new((quotient, remainder), Choice::from(1u8))) + }) }) } } diff --git a/src/int/mul.rs b/src/int/mul.rs index 623517d6..9e692c40 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -4,7 +4,7 @@ use core::ops::{Mul, MulAssign}; use subtle::CtOption; -use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, WideningMul, Zero}; +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; impl Int { /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. diff --git a/src/int/sign.rs b/src/int/sign.rs index cf48010f..60aec750 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,8 +1,8 @@ use crate::{ConstChoice, ConstCtOption, Int, Uint}; impl Int { - /// Construct new [`Int`] from a sign and magnitude. - /// Returns `None` when the magnitude does not fit in an [`Int`]. + /// Construct new [`Int`] from an absolute value and sign. + /// Returns `None` when the absolute value does not fit in an [`Int`]. pub const fn new_from_abs_sign( abs: Uint, is_negative: ConstChoice, From e61537ff664010726a51f85ca98a89bf99664207 Mon Sep 17 00:00:00 2001 From: Erik <159244975+erik-3milabs@users.noreply.github.com> Date: Mon, 21 Oct 2024 11:40:01 +0200 Subject: [PATCH 65/87] Update src/int/sign.rs --- src/int/sign.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/int/sign.rs b/src/int/sign.rs index 60aec750..71e2c743 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -21,7 +21,7 @@ impl Int { /// Whether this [`Int`] is positive, as a `ConstChoice`. pub const fn is_positive(&self) -> ConstChoice { - Int::is_negative(self).not().and(Int::is_nonzero(self)) + self.is_negative().not().and(self.is_nonzero()) } /// The sign and magnitude of this [`Int`]. From 2624a8a8f97b36eba952629f5d3916729f9a1302 Mon Sep 17 00:00:00 2001 From: Mateusz Kramarczyk Date: Mon, 21 Oct 2024 17:14:28 +0200 Subject: [PATCH 66/87] Refine. --- src/int/div.rs | 10 +++++----- src/int/sign.rs | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/int/div.rs b/src/int/div.rs index fddeabde..41d84c9a 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -11,7 +11,7 @@ impl Int { /// Base div_rem operation. /// Given `(a, b)`, computes the quotient and remainder of their absolute values. Furthermore, /// returns the signs of `a` and `b`. - fn div_rem_base( + const fn div_rem_base( &self, rhs: &NonZero, ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { @@ -21,7 +21,7 @@ impl Int { // Step 2. Divide magnitudes // safe to unwrap since rhs is NonZero. - let (quotient, remainder) = lhs_mag.div_rem(&NonZero::new(rhs_mag).unwrap()); + let (quotient, remainder) = lhs_mag.div_rem(&NonZero::>::new_unwrap(rhs_mag)); (quotient, remainder, lhs_sgn, rhs_sgn) } @@ -46,15 +46,15 @@ impl Int { /// assert_eq!(quotient.unwrap(), I128::from(2)); /// assert_eq!(remainder.unwrap(), I128::from(-2)); /// ``` - pub fn checked_div_rem( + pub const fn checked_div_rem( &self, rhs: &NonZero, ) -> (ConstCtOption, ConstCtOption) { let (quotient, remainder, lhs_sgn, rhs_sgn) = self.div_rem_base(rhs); let opposing_signs = lhs_sgn.ne(rhs_sgn); ( - Self::new_from_sign_and_magnitude(opposing_signs, quotient), - Self::new_from_sign_and_magnitude(lhs_sgn, remainder), + Self::new_from_abs_sign(quotient, opposing_signs), + Self::new_from_abs_sign(remainder, lhs_sgn), ) } diff --git a/src/int/sign.rs b/src/int/sign.rs index 71e2c743..1052746f 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,6 +1,19 @@ -use crate::{ConstChoice, ConstCtOption, Int, Uint}; +use num_traits::ConstZero; +use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; impl Int { + /// Returns the word of most significant [`Limb`]. + /// For the generative case where the number of limbs is zero, + /// zeroed word is returned (which is semantically correct). + /// This method leaks the limb length of the value, which is also OK. + const fn most_significant_word(&self) -> Word { + if Self::LIMBS == 0 { + Word::ZERO + } else { + self.0.to_words()[LIMBS - 1] + } + } + /// Construct new [`Int`] from an absolute value and sign. /// Returns `None` when the absolute value does not fit in an [`Int`]. pub const fn new_from_abs_sign( @@ -8,15 +21,14 @@ impl Int { is_negative: ConstChoice, ) -> ConstCtOption { let magnitude = Self(abs).wrapping_neg_if(is_negative); - let fits = Uint::gt(&abs, &Int::MAX.0) - .not() + let fits = Uint::lte(&abs, &Int::MAX.0) .or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); ConstCtOption::new(magnitude, fits) } /// Whether this [`Int`] is negative, as a `ConstChoice`. pub const fn is_negative(&self) -> ConstChoice { - ConstChoice::from_word_msb(self.0.to_words()[LIMBS - 1]) + ConstChoice::from_word_msb(self.most_significant_word()) } /// Whether this [`Int`] is positive, as a `ConstChoice`. From 23f1f4ae656c7b44401a89df9f58caf68d50f550 Mon Sep 17 00:00:00 2001 From: Mateusz Kramarczyk Date: Mon, 21 Oct 2024 17:17:44 +0200 Subject: [PATCH 67/87] reformat --- src/int/sign.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/int/sign.rs b/src/int/sign.rs index 1052746f..e8bd3ed0 100644 --- a/src/int/sign.rs +++ b/src/int/sign.rs @@ -1,5 +1,5 @@ -use num_traits::ConstZero; use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; +use num_traits::ConstZero; impl Int { /// Returns the word of most significant [`Limb`]. @@ -21,8 +21,7 @@ impl Int { is_negative: ConstChoice, ) -> ConstCtOption { let magnitude = Self(abs).wrapping_neg_if(is_negative); - let fits = Uint::lte(&abs, &Int::MAX.0) - .or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); + let fits = Uint::lte(&abs, &Int::MAX.0).or(is_negative.and(Uint::eq(&abs, &Int::MIN.0))); ConstCtOption::new(magnitude, fits) } From 6543016914ad25e15105276f9ec927ae482fb4dc Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 22 Oct 2024 11:24:32 +0200 Subject: [PATCH 68/87] Deprecate `Uint::gte` --- src/uint/cmp.rs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index 22572229..a71532c7 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -80,12 +80,6 @@ impl Uint { ConstChoice::from_word_mask(borrow.0) } - /// Returns the truthy value if `self >= rhs` and the falsy value otherwise. - #[inline] - pub(crate) const fn gte(lhs: &Self, rhs: &Self) -> ConstChoice { - Self::lt(lhs, rhs).not() - } - /// Returns the ordering between `self` and `rhs` as an i8. /// Values correspond to the Ordering enum: /// -1 is Less @@ -231,25 +225,6 @@ mod tests { assert!(!bool::from(b.ct_gt(&c))); } - #[test] - fn gte() { - let a = U128::ZERO; - let b = U128::ONE; - let c = U128::MAX; - - assert!(bool::from(Uint::gte(&b, &a))); - assert!(bool::from(Uint::gte(&c, &a))); - assert!(bool::from(Uint::gte(&c, &b))); - - assert!(bool::from(Uint::gte(&a, &a))); - assert!(bool::from(Uint::gte(&b, &b))); - assert!(bool::from(Uint::gte(&c, &c))); - - assert!(!bool::from(Uint::gte(&a, &b))); - assert!(!bool::from(Uint::gte(&a, &c))); - assert!(!bool::from(Uint::gte(&b, &c))); - } - #[test] fn ct_lt() { let a = U128::ZERO; From 51f269a1bdaae2cf9e1929dc3a1e283b4a7f0c12 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 22 Oct 2024 11:25:00 +0200 Subject: [PATCH 69/87] Clean `Int::div` --- src/int/div.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int/div.rs b/src/int/div.rs index 41d84c9a..197dd7c2 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -155,7 +155,7 @@ impl Int { impl CheckedDiv for Int { fn checked_div(&self, rhs: &Int) -> CtOption { - self.checked_div(rhs).into() + self.checked_div(rhs) } } @@ -187,7 +187,7 @@ impl Div>> for Int { type Output = CtOption>; fn div(self, rhs: NonZero>) -> Self::Output { - self.checked_div(&rhs).into() + self.checked_div(&rhs) } } From fd858767385a07e685268043f8f9ef41f29a5c42 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:19:20 +0100 Subject: [PATCH 70/87] Clean .gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 5ea04312..ea7e3f81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ target proptest-regressions - -.idea/ From 62bd6765d4ff87372f95d77cd7e032ead1362e2c Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:26:24 +0100 Subject: [PATCH 71/87] Update `cmp` comments --- src/int/cmp.rs | 2 +- src/uint/cmp.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index 851a14ea..fb49a9be 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -1,6 +1,6 @@ //! [`Int`] comparisons. //! -//! By default, these are all constant-time and use the `subtle` crate. +//! By default, these are all constant-time. #![allow(dead_code)] use core::cmp::Ordering; diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index a71532c7..453f8d11 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -1,6 +1,6 @@ //! [`Uint`] comparisons. //! -//! By default these are all constant-time and use the `subtle` crate. +//! By default, these are all constant-time. use core::cmp::Ordering; From 62764a7284e67c3a82ec62605295a58ecec66c54 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:26:50 +0100 Subject: [PATCH 72/87] Remove `dead_code` in `int::cmp` --- src/int/cmp.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index fb49a9be..374f1d47 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -1,7 +1,6 @@ //! [`Int`] comparisons. //! //! By default, these are all constant-time. -#![allow(dead_code)] use core::cmp::Ordering; @@ -10,12 +9,6 @@ use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; use crate::{ConstChoice, Int, Uint}; impl Int { - /// Return `b` if `c` is truthy, otherwise return `a`. - #[inline] - pub(crate) const fn select(a: &Self, b: &Self, c: ConstChoice) -> Self { - Self(Uint::select(&a.0, &b.0, c)) - } - /// Returns the truthy value if `self`!=0 or the falsy value otherwise. #[inline] pub(crate) const fn is_nonzero(&self) -> ConstChoice { From badae254f8f96543c54aaddf96cb464767730622 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:27:55 +0100 Subject: [PATCH 73/87] Fix `int::div::div_rem_base` docstring --- src/int/div.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/int/div.rs b/src/int/div.rs index 197dd7c2..164419db 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -9,6 +9,7 @@ use crate::{CheckedDiv, ConstChoice, ConstCtOption, Int, NonZero, Uint}; impl Int { #[inline] /// Base div_rem operation. + /// /// Given `(a, b)`, computes the quotient and remainder of their absolute values. Furthermore, /// returns the signs of `a` and `b`. const fn div_rem_base( From 76be4dc295d43b4a9eb34a5cd2b81cc2df8fa9b6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:42:50 +0100 Subject: [PATCH 74/87] Introduce `NonZero::>::abs_sign` --- src/int/div.rs | 4 ++-- src/non_zero.rs | 43 +++++++++++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/int/div.rs b/src/int/div.rs index 164419db..28c6164b 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -18,11 +18,11 @@ impl Int { ) -> (Uint<{ LIMBS }>, Uint<{ LIMBS }>, ConstChoice, ConstChoice) { // Step 1: split operands into signs and magnitudes. let (lhs_mag, lhs_sgn) = self.abs_sign(); - let (rhs_mag, rhs_sgn) = rhs.0.abs_sign(); + let (rhs_mag, rhs_sgn) = rhs.abs_sign(); // Step 2. Divide magnitudes // safe to unwrap since rhs is NonZero. - let (quotient, remainder) = lhs_mag.div_rem(&NonZero::>::new_unwrap(rhs_mag)); + let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); (quotient, remainder, lhs_sgn, rhs_sgn) } diff --git a/src/non_zero.rs b/src/non_zero.rs index 3f280b30..42aa2e99 100644 --- a/src/non_zero.rs +++ b/src/non_zero.rs @@ -1,24 +1,24 @@ //! Wrapper type for non-zero integers. -use crate::{Bounded, Constants, Encoding, Limb, Uint, Zero}; use core::{ fmt, num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8}, ops::Deref, }; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; - -#[cfg(feature = "hybrid-array")] -use crate::{ArrayEncoding, ByteArray}; - -#[cfg(feature = "rand_core")] -use {crate::Random, rand_core::CryptoRngCore}; #[cfg(feature = "serde")] use serdect::serde::{ de::{Error, Unexpected}, Deserialize, Deserializer, Serialize, Serializer, }; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; + +#[cfg(feature = "rand_core")] +use {crate::Random, rand_core::CryptoRngCore}; + +#[cfg(feature = "hybrid-array")] +use crate::{ArrayEncoding, ByteArray}; +use crate::{Bounded, ConstChoice, Constants, Encoding, Int, Limb, Uint, Zero}; /// Wrapper type for non-zero integers. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -170,6 +170,15 @@ impl NonZero> { } } +impl NonZero> { + /// Convert a [`NonZero`] to its sign and [`NonZero`] magnitude. + pub const fn abs_sign(&self) -> (NonZero>, ConstChoice) { + let (abs, sign) = self.0.abs_sign(); + // Note: a NonZero always has a non-zero magnitude, so it is safe to unwrap. + (NonZero::>::new_unwrap(abs), sign) + } +} + #[cfg(feature = "hybrid-array")] impl NonZero where @@ -381,12 +390,26 @@ impl zeroize::Zeroize for NonZero { } } +#[cfg(test)] +mod tests { + use crate::{ConstChoice, I128, U128}; + + #[test] + fn int_abs_sign() { + let x = I128::from(-55).to_nz().unwrap(); + let (abs, sgn) = x.abs_sign(); + assert_eq!(abs, U128::from(55u32).to_nz().unwrap()); + assert_eq!(sgn, ConstChoice::TRUE); + } +} + #[cfg(all(test, feature = "serde"))] #[allow(clippy::unwrap_used)] -mod tests { - use crate::{NonZero, U64}; +mod tests_serde { use bincode::ErrorKind; + use crate::{NonZero, U64}; + #[test] fn serde() { let test = From 2260fa77e53b2aa3cafed90a2c33a844f2c418c4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:44:59 +0100 Subject: [PATCH 75/87] Rename `negc` to `carrying_neg` --- src/int/neg.rs | 21 +++++++++++---------- src/uint/neg.rs | 12 ++++++------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/int/neg.rs b/src/int/neg.rs index 701e6cef..7625d520 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -13,8 +13,8 @@ impl Int { /// Warning: this operation is unsafe to use as negation; the negation is incorrect when /// `self == Self::MIN`. #[inline] - pub(crate) const fn negc(&self) -> (Self, Word) { - let (val, carry) = self.0.negc(); + pub(crate) const fn carrying_neg(&self) -> (Self, Word) { + let (val, carry) = self.0.carrying_neg(); (Self(val), carry) } @@ -32,38 +32,39 @@ impl Int { /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive /// equivalent of that. pub fn neg(&self) -> CtOption { - CtOption::new(self.negc().0, self.is_min().not().into()) + CtOption::new(self.carrying_neg().0, self.is_min().not().into()) } } #[cfg(test)] mod tests { - use crate::{ConstChoice, Word, I128}; use num_traits::ConstZero; + use crate::{ConstChoice, Word, I128}; + #[test] - fn negc() { + fn carrying_neg() { let min_plus_one = I128 { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; - let (res, carry) = I128::MIN.negc(); + let (res, carry) = I128::MIN.carrying_neg(); assert_eq!(carry, Word::MAX); assert_eq!(res, I128::MIN); - let (res, carry) = I128::MINUS_ONE.negc(); + let (res, carry) = I128::MINUS_ONE.carrying_neg(); assert_eq!(carry, Word::MAX); assert_eq!(res, I128::ONE); - let (res, carry) = I128::ZERO.negc(); + let (res, carry) = I128::ZERO.carrying_neg(); assert_eq!(carry, Word::ZERO); assert_eq!(res, I128::ZERO); - let (res, carry) = I128::ONE.negc(); + let (res, carry) = I128::ONE.carrying_neg(); assert_eq!(carry, Word::MAX); assert_eq!(res, I128::MINUS_ONE); - let (res, carry) = I128::MAX.negc(); + let (res, carry) = I128::MAX.carrying_neg(); assert_eq!(carry, Word::MAX); assert_eq!(res, min_plus_one); } diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 4e96db4f..9c1a7bf3 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -5,12 +5,12 @@ use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { - self.negc().0 + self.carrying_neg().0 } /// Perform negation; additionally return the carry. /// Note: the carry equals `Word::ZERO` when `self == Self::ZERO`, and `Word::MAX` otherwise. - pub const fn negc(&self) -> (Self, Word) { + pub const fn carrying_neg(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; @@ -67,10 +67,10 @@ mod tests { } #[test] - fn negc() { - assert_eq!(U256::ZERO.negc(), (U256::ZERO, Word::ZERO)); - assert_eq!(U256::ONE.negc(), (U256::MAX, Word::MAX)); - assert_eq!(U256::MAX.negc(), (U256::ONE, Word::MAX)); + fn carrying_neg() { + assert_eq!(U256::ZERO.carrying_neg(), (U256::ZERO, Word::ZERO)); + assert_eq!(U256::ONE.carrying_neg(), (U256::MAX, Word::MAX)); + assert_eq!(U256::MAX.carrying_neg(), (U256::ONE, Word::MAX)); } #[test] From dd9161738b4ae4a2609e9dffed764d46f29e3e30 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 09:52:52 +0100 Subject: [PATCH 76/87] Deprecate more `int::cmp` dead code. --- src/int/cmp.rs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index 374f1d47..8edfb6ca 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -15,11 +15,6 @@ impl Int { Uint::is_nonzero(&self.0) } - /// Returns the truthy value if `self` is odd or the falsy value otherwise. - pub(crate) const fn is_odd(&self) -> ConstChoice { - Uint::is_odd(&self.0) - } - /// Returns the truthy value if `self == rhs` or the falsy value otherwise. #[inline] pub(crate) const fn eq(lhs: &Self, rhs: &Self) -> ConstChoice { @@ -32,24 +27,12 @@ impl Int { Uint::lt(&lhs.invert_msb().0, &rhs.invert_msb().0) } - /// Returns the truthy value if `self <= rhs` and the falsy value otherwise. - #[inline] - pub(crate) const fn lte(lhs: &Self, rhs: &Self) -> ConstChoice { - Self::gt(lhs, rhs).not() - } - /// Returns the truthy value if `self > rhs` and the falsy value otherwise. #[inline] pub(crate) const fn gt(lhs: &Self, rhs: &Self) -> ConstChoice { Uint::gt(&lhs.invert_msb().0, &rhs.invert_msb().0) } - /// Returns the truthy value if `self >= rhs` and the falsy value otherwise. - #[inline] - pub(crate) const fn gte(lhs: &Self, rhs: &Self) -> ConstChoice { - Self::lt(lhs, rhs).not() - } - /// Returns the ordering between `self` and `rhs` as an i8. /// Values correspond to the Ordering enum: /// -1 is Less From e53a26104a186ed89205b15dbb6bb7e4760253ae Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 10:40:01 +0100 Subject: [PATCH 77/87] Remove dead `int::cmp` tests --- src/int/cmp.rs | 52 +------------------------------------------------- 1 file changed, 1 insertion(+), 51 deletions(-) diff --git a/src/int/cmp.rs b/src/int/cmp.rs index 8edfb6ca..eeee0c6f 100644 --- a/src/int/cmp.rs +++ b/src/int/cmp.rs @@ -99,7 +99,7 @@ impl PartialEq for Int { mod tests { use subtle::{ConstantTimeGreater, ConstantTimeLess}; - use crate::{Int, I128, U128}; + use crate::I128; #[test] fn test_is_nonzero() { @@ -110,18 +110,6 @@ mod tests { assert_eq!(I128::MAX.is_nonzero().to_u8(), 1u8); } - #[test] - fn test_is_odd() { - let two = I128::new_from_uint(U128::from(2u32)); - assert_eq!(I128::MAX.is_odd().to_u8(), 1u8); - assert_eq!(two.is_odd().to_u8(), 0u8); - assert_eq!(I128::ONE.is_odd().to_u8(), 1u8); - assert_eq!(I128::ZERO.is_odd().to_u8(), 0u8); - assert_eq!(I128::MINUS_ONE.is_odd().to_u8(), 1u8); - assert_eq!(two.neg().unwrap().is_odd().to_u8(), 0u8); - assert_eq!(I128::MAX.is_odd().to_u8(), 1u8); - } - #[test] fn test_eq() { assert_eq!(I128::ZERO, I128::ONE.wrapping_add(&I128::MINUS_ONE)); @@ -182,25 +170,6 @@ mod tests { assert!(!bool::from(b.ct_gt(&c))); } - #[test] - fn gte() { - let a = I128::MIN; - let b = I128::ZERO; - let c = I128::MAX; - - assert!(bool::from(Int::gte(&b, &a))); - assert!(bool::from(Int::gte(&c, &a))); - assert!(bool::from(Int::gte(&c, &b))); - - assert!(bool::from(Int::gte(&a, &a))); - assert!(bool::from(Int::gte(&b, &b))); - assert!(bool::from(Int::gte(&c, &c))); - - assert!(!bool::from(Int::gte(&a, &b))); - assert!(!bool::from(Int::gte(&a, &c))); - assert!(!bool::from(Int::gte(&b, &c))); - } - #[test] fn ct_lt() { let a = I128::ZERO; @@ -219,23 +188,4 @@ mod tests { assert!(!bool::from(c.ct_lt(&a))); assert!(!bool::from(c.ct_lt(&b))); } - - #[test] - fn lte() { - let a = I128::ZERO; - let b = I128::ONE; - let c = I128::MAX; - - assert!(bool::from(Int::lte(&a, &b))); - assert!(bool::from(Int::lte(&a, &c))); - assert!(bool::from(Int::lte(&b, &c))); - - assert!(bool::from(Int::lte(&a, &a))); - assert!(bool::from(Int::lte(&b, &b))); - assert!(bool::from(Int::lte(&c, &c))); - - assert!(!bool::from(Int::lte(&b, &a))); - assert!(!bool::from(Int::lte(&c, &a))); - assert!(!bool::from(Int::lte(&c, &b))); - } } From 7c3ae9e967e65f4cd5761c986456dd6745a95018 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 10:51:06 +0100 Subject: [PATCH 78/87] Simplify `uint::wrapping_neg_if` implementation, preventing potential timing leak. --- src/const_choice.rs | 12 ------------ src/uint/neg.rs | 14 +------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index f3f2ad77..212b2172 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -33,18 +33,6 @@ impl ConstChoice { self.0 } - #[inline] - #[cfg(target_pointer_width = "32")] - pub(crate) const fn as_word_mask(&self) -> Word { - self.as_u32_mask() - } - - #[inline] - #[cfg(target_pointer_width = "64")] - pub(crate) const fn as_word_mask(&self) -> Word { - self.as_u64_mask() - } - /// Returns the truthy value if `value == Word::MAX`, and the falsy value if `value == 0`. /// Panics for other values. #[inline] diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 9c1a7bf3..58552504 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -1,5 +1,3 @@ -use num_traits::ConstOne; - use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { @@ -25,17 +23,7 @@ impl Uint { /// Perform negation, if `negate` is truthy. pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { - let mut ret = [Limb::ZERO; LIMBS]; - let mut i = 0; - let mask = negate.as_word_mask() as WideWord; - let mut carry = mask & WideWord::ONE; - while i < LIMBS { - let r = ((self.limbs[i].0 as WideWord) ^ mask) + carry; - ret[i] = Limb(r as Word); - carry = r >> Limb::BITS; - i += 1; - } - Uint::new(ret) + Uint::select(self, &self.wrapping_neg(), negate) } } From 0be0e571675d586a01ef1aa5d3f080c84584bbd0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 13:56:54 +0100 Subject: [PATCH 79/87] Refactor `int::new_from_uint`; make it crate-restricted. --- src/int.rs | 26 ++++++++++++++++++-------- src/int/from.rs | 14 ++++++++------ src/int/mul.rs | 2 +- src/int/resize.rs | 4 ++-- src/uint.rs | 45 +++++++++++++++++++++++++-------------------- 5 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/int.rs b/src/int.rs index 0b338551..2201d648 100644 --- a/src/int.rs +++ b/src/int.rs @@ -73,9 +73,11 @@ impl Int { } /// Const-friendly [`Int`] constructor. - /// Note: interprets the `value` as an `Int`. For a proper conversion, - /// see [`Int::new_from_abs_sign`]. - pub const fn new_from_uint(value: Uint) -> Self { + /// + /// Reinterprets the bits of a value of type [`Uint`] as an [`Int`]. + /// For a proper conversion, see [`Int::new_from_abs_sign`]; + /// the public interface of this function is available at [`Uint::as_int`]. + pub(crate) const fn from_bits(value: Uint) -> Self { Self(value) } @@ -133,8 +135,8 @@ impl Int { } /// Interpret the data in this type as a [`Uint`] instead. - pub const fn as_uint(&self) -> &Uint { - &self.0 + pub const fn as_uint(&self) -> Uint { + self.0 } /// Whether this [`Int`] is equal to `Self::MIN`. @@ -324,7 +326,6 @@ mod tests { use crate::int::I128; use crate::ConstChoice; - #[cfg(feature = "serde")] use crate::U128; #[cfg(target_pointer_width = "64")] @@ -404,10 +405,19 @@ mod tests { assert_eq!(random.is_max(), ConstChoice::FALSE); } + #[test] + fn as_uint() { + assert_eq!(I128::MIN.as_uint(), U128::ONE << 127); + assert_eq!(I128::MINUS_ONE.as_uint(), U128::MAX); + assert_eq!(I128::ZERO.as_uint(), U128::ZERO); + assert_eq!(I128::ONE.as_uint(), U128::ONE); + assert_eq!(I128::MAX.as_uint(), U128::MAX >> 1); + } + #[cfg(feature = "serde")] #[test] fn serde() { - const TEST: I128 = I128::new_from_uint(U128::from_u64(0x0011223344556677)); + const TEST: I128 = I128::from_i64(0x0011223344556677i64); let serialized = bincode::serialize(&TEST).unwrap(); let deserialized: I128 = bincode::deserialize(&serialized).unwrap(); @@ -418,7 +428,7 @@ mod tests { #[cfg(feature = "serde")] #[test] fn serde_owned() { - const TEST: I128 = I128::new_from_uint(U128::from_u64(0x0011223344556677)); + const TEST: I128 = I128::from_i64(0x0011223344556677i64); let serialized = bincode::serialize(&TEST).unwrap(); let deserialized: I128 = bincode::deserialize_from(serialized.as_slice()).unwrap(); diff --git a/src/int/from.rs b/src/int/from.rs index 6d105b2a..e387f9d8 100644 --- a/src/int/from.rs +++ b/src/int/from.rs @@ -7,28 +7,28 @@ impl Int { // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i8(n: i8) -> Self { assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() + Uint::new([Limb(n as Word)]).as_int().resize() } /// Create a [`Int`] from an `i16` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i16(n: i16) -> Self { assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() + Uint::new([Limb(n as Word)]).as_int().resize() } /// Create a [`Int`] from an `i32` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub fn from_i32(n: i32) -> Self { assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() + Uint::new([Limb(n as Word)]).as_int().resize() } /// Create a [`Int`] from an `i64` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable #[cfg(target_pointer_width = "32")] pub const fn from_i64(n: i64) -> Self { - Int::new_from_uint(Uint::<{ I64::LIMBS }>::from_u64(n as u64)).resize() + Uint::<{ I64::LIMBS }>::from_u64(n as u64).as_int().resize() } /// Create a [`Int`] from an `i64` (const-friendly) @@ -36,13 +36,15 @@ impl Int { #[cfg(target_pointer_width = "64")] pub const fn from_i64(n: i64) -> Self { assert!(LIMBS >= 1, "number of limbs must be greater than zero"); - Int::new_from_uint(Uint::new([Limb(n as Word)])).resize() + Uint::new([Limb(n as Word)]).as_int().resize() } /// Create a [`Int`] from an `i128` (const-friendly) // TODO(tarcieri): replace with `const impl From` when stable pub const fn from_i128(n: i128) -> Self { - Int::new_from_uint(Uint::<{ I128::LIMBS }>::from_u128(n as u128)).resize() + Uint::<{ I128::LIMBS }>::from_u128(n as u128) + .as_int() + .resize() } } diff --git a/src/int/mul.rs b/src/int/mul.rs index 9e692c40..ec3f95a7 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -46,7 +46,7 @@ impl Int { let product_sign = lhs_sign.xor(rhs_sign); // always fits - Int::new_from_uint(product_abs.wrapping_neg_if(product_sign)) + Int::from_bits(product_abs.wrapping_neg_if(product_sign)) } } diff --git a/src/int/resize.rs b/src/int/resize.rs index bc30cdbe..4aed2899 100644 --- a/src/int/resize.rs +++ b/src/int/resize.rs @@ -12,7 +12,7 @@ impl Int { limbs[i] = self.0.limbs[i]; i += 1; } - Int::new_from_uint(Uint { limbs }) + Uint { limbs }.as_int() } } @@ -44,7 +44,7 @@ mod tests { assert_eq!(I128::ONE.resize::<{ I256::LIMBS }>(), I256::ONE); assert_eq!( I128::MAX.resize::<{ I256::LIMBS }>(), - I256::new_from_uint(I128::MAX.0.resize()) + I128::MAX.0.resize().as_int() ); } } diff --git a/src/uint.rs b/src/uint.rs index d166f9b6..8a75aaed 100644 --- a/src/uint.rs +++ b/src/uint.rs @@ -2,6 +2,23 @@ #![allow(clippy::needless_range_loop, clippy::many_single_char_names)] +use core::fmt; + +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; +#[cfg(feature = "zeroize")] +use zeroize::DefaultIsZeroes; + +#[cfg(feature = "extra-sizes")] +pub use extra_sizes::*; + +use crate::{ + modular::{MontyForm, SafeGcdInverter}, + Bounded, ConstCtOption, ConstZero, Constants, Encoding, FixedInteger, Int, Integer, Limb, + NonZero, Odd, PrecomputeInverter, PrecomputeInverterWithAdjuster, Word, +}; + #[macro_use] mod macros; @@ -39,20 +56,6 @@ pub(crate) mod boxed; #[cfg(feature = "rand_core")] mod rand; -use crate::{ - modular::{MontyForm, SafeGcdInverter}, - Bounded, ConstCtOption, ConstZero, Constants, Encoding, FixedInteger, Integer, Limb, NonZero, - Odd, PrecomputeInverter, PrecomputeInverterWithAdjuster, Word, -}; -use core::fmt; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - -#[cfg(feature = "serde")] -use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; - -#[cfg(feature = "zeroize")] -use zeroize::DefaultIsZeroes; - /// Stack-allocated big unsigned integer. /// /// Generic over the given number of `LIMBS` @@ -184,6 +187,11 @@ impl Uint { pub const fn to_odd(self) -> ConstCtOption> { ConstCtOption::new(Odd(self), self.is_odd()) } + + /// Interpret the data in this type as an [`Int`] instead. + pub const fn as_int(&self) -> Int { + Int::from_bits(*self) + } } impl AsRef<[Word; LIMBS]> for Uint { @@ -447,20 +455,17 @@ impl_uint_concat_split_mixed! { #[cfg(feature = "extra-sizes")] mod extra_sizes; -#[cfg(feature = "extra-sizes")] -pub use extra_sizes::*; - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { - use crate::{Encoding, U128}; - use subtle::ConditionallySelectable; - #[cfg(feature = "alloc")] use alloc::format; + use subtle::ConditionallySelectable; + #[cfg(feature = "serde")] use crate::U64; + use crate::{Encoding, U128}; #[cfg(target_pointer_width = "64")] #[test] From 92cf876c483141d37f46312e6268f9b348995ae4 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 14:01:19 +0100 Subject: [PATCH 80/87] Make `Int::as_uint` return a ref. --- src/int.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int.rs b/src/int.rs index 2201d648..6c1e37ae 100644 --- a/src/int.rs +++ b/src/int.rs @@ -135,8 +135,8 @@ impl Int { } /// Interpret the data in this type as a [`Uint`] instead. - pub const fn as_uint(&self) -> Uint { - self.0 + pub const fn as_uint(&self) -> &Uint { + &self.0 } /// Whether this [`Int`] is equal to `Self::MIN`. From 2c8af9c894bc2cfd76fda6182f665bcc1b5ce6ee Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Mon, 28 Oct 2024 15:40:53 +0100 Subject: [PATCH 81/87] Fix broken tests --- src/int.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/int.rs b/src/int.rs index 6c1e37ae..e10b1b0f 100644 --- a/src/int.rs +++ b/src/int.rs @@ -407,11 +407,11 @@ mod tests { #[test] fn as_uint() { - assert_eq!(I128::MIN.as_uint(), U128::ONE << 127); - assert_eq!(I128::MINUS_ONE.as_uint(), U128::MAX); - assert_eq!(I128::ZERO.as_uint(), U128::ZERO); - assert_eq!(I128::ONE.as_uint(), U128::ONE); - assert_eq!(I128::MAX.as_uint(), U128::MAX >> 1); + assert_eq!(*I128::MIN.as_uint(), U128::ONE << 127); + assert_eq!(*I128::MINUS_ONE.as_uint(), U128::MAX); + assert_eq!(*I128::ZERO.as_uint(), U128::ZERO); + assert_eq!(*I128::ONE.as_uint(), U128::ONE); + assert_eq!(*I128::MAX.as_uint(), U128::MAX >> 1); } #[cfg(feature = "serde")] From 84466dd303632ef57194248a73f2af07e5235887 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 29 Oct 2024 10:30:54 +0100 Subject: [PATCH 82/87] yank `Int::Encoding`; save it for a later PR --- src/int.rs | 68 ++++----------------------------------------- src/int/add.rs | 3 +- src/int/div.rs | 2 +- src/int/encoding.rs | 37 ------------------------ src/int/macros.rs | 48 -------------------------------- src/int/mul.rs | 3 +- src/int/sub.rs | 3 +- src/int/types.rs | 51 ++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + 9 files changed, 61 insertions(+), 155 deletions(-) delete mode 100644 src/int/macros.rs create mode 100644 src/int/types.rs diff --git a/src/int.rs b/src/int.rs index e10b1b0f..400f88dd 100644 --- a/src/int.rs +++ b/src/int.rs @@ -7,12 +7,9 @@ use num_traits::ConstZero; use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; -use crate::{ - Bounded, ConstChoice, ConstCtOption, Constants, Encoding, Limb, NonZero, Odd, Uint, Word, -}; - -#[macro_use] -mod macros; +#[cfg(feature = "serde")] +use crate::Encoding; +use crate::{Bounded, ConstChoice, ConstCtOption, Constants, Limb, NonZero, Odd, Uint, Word}; mod add; mod cmp; @@ -24,6 +21,7 @@ mod neg; mod resize; mod sign; mod sub; +pub(crate) mod types; #[cfg(feature = "rand_core")] mod rand; @@ -287,46 +285,12 @@ where } } -impl_int_aliases! { - (I64, 64, "64-bit"), - (I128, 128, "128-bit"), - (I192, 192, "192-bit"), - (I256, 256, "256-bit"), - (I320, 320, "320-bit"), - (I384, 384, "384-bit"), - (I448, 448, "448-bit"), - (I512, 512, "512-bit"), - (I576, 576, "576-bit"), - (I640, 640, "640-bit"), - (I704, 704, "704-bit"), - (I768, 768, "768-bit"), - (I832, 832, "832-bit"), - (I896, 896, "896-bit"), - (I960, 960, "960-bit"), - (I1024, 1024, "1024-bit"), - (I1280, 1280, "1280-bit"), - (I1536, 1536, "1536-bit"), - (I1792, 1792, "1792-bit"), - (I2048, 2048, "2048-bit"), - (I3072, 3072, "3072-bit"), - (I3584, 3584, "3584-bit"), - (I4096, 4096, "4096-bit"), - (I4224, 4224, "4224-bit"), - (I4352, 4352, "4352-bit"), - (I6144, 6144, "6144-bit"), - (I8192, 8192, "8192-bit"), - (I16384, 16384, "16384-bit"), - (I32768, 32768, "32768-bit") -} - #[cfg(test)] #[allow(clippy::unwrap_used)] mod tests { use subtle::ConditionallySelectable; - use crate::int::I128; - use crate::ConstChoice; - use crate::U128; + use crate::{ConstChoice, I128, U128}; #[cfg(target_pointer_width = "64")] #[test] @@ -413,26 +377,4 @@ mod tests { assert_eq!(*I128::ONE.as_uint(), U128::ONE); assert_eq!(*I128::MAX.as_uint(), U128::MAX >> 1); } - - #[cfg(feature = "serde")] - #[test] - fn serde() { - const TEST: I128 = I128::from_i64(0x0011223344556677i64); - - let serialized = bincode::serialize(&TEST).unwrap(); - let deserialized: I128 = bincode::deserialize(&serialized).unwrap(); - - assert_eq!(TEST, deserialized); - } - - #[cfg(feature = "serde")] - #[test] - fn serde_owned() { - const TEST: I128 = I128::from_i64(0x0011223344556677i64); - - let serialized = bincode::serialize(&TEST).unwrap(); - let deserialized: I128 = bincode::deserialize_from(serialized.as_slice()).unwrap(); - - assert_eq!(TEST, deserialized); - } } diff --git a/src/int/add.rs b/src/int/add.rs index 2981f482..2ce3699f 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -104,8 +104,7 @@ mod tests { #[cfg(test)] mod tests { - use crate::int::I128; - use crate::U128; + use crate::{I128, U128}; #[test] fn checked_add() { diff --git a/src/int/div.rs b/src/int/div.rs index 28c6164b..86aec783 100644 --- a/src/int/div.rs +++ b/src/int/div.rs @@ -194,7 +194,7 @@ impl Div>> for Int { #[cfg(test)] mod tests { - use crate::int::{Int, I128}; + use crate::{Int, I128}; #[test] fn test_checked_div() { diff --git a/src/int/encoding.rs b/src/int/encoding.rs index 9e66e789..1bbe30c8 100644 --- a/src/int/encoding.rs +++ b/src/int/encoding.rs @@ -3,13 +3,6 @@ use crate::{Int, Uint}; impl Int { - /// Create a new [`Int`] from the provided big endian bytes. - /// - /// See [`Uint::from_be_slice`] for more details. - pub const fn from_be_slice(bytes: &[u8]) -> Self { - Self(Uint::from_be_slice(bytes)) - } - /// Create a new [`Int`] from the provided big endian hex string. /// /// Panics if the hex is malformed or not zero-padded accordingly for the size. @@ -18,34 +11,4 @@ impl Int { pub const fn from_be_hex(hex: &str) -> Self { Self(Uint::from_be_hex(hex)) } - - /// Create a new [`Int`] from the provided little endian bytes. - /// - /// See [`Uint::from_le_slice`] for more details. - pub const fn from_le_slice(bytes: &[u8]) -> Self { - Self(Uint::from_le_slice(bytes)) - } - - /// Create a new [`Int`] from the provided little endian hex string. - /// - /// Panics if the hex is malformed or not zero-padded accordingly for the size. - /// - /// See [`Uint::from_le_hex`] for more details. - pub const fn from_le_hex(hex: &str) -> Self { - Self(Uint::from_le_hex(hex)) - } -} - -/// Encode an [`Int`] to a big endian byte array of the given size. -pub(crate) const fn int_to_be_bytes( - int: &Int, -) -> [u8; BYTES] { - crate::uint::encoding::uint_to_be_bytes(&int.0) -} - -/// Encode an [`Int`] to a little endian byte array of the given size. -pub(crate) const fn int_to_le_bytes( - int: &Int, -) -> [u8; BYTES] { - crate::uint::encoding::uint_to_le_bytes(&int.0) } diff --git a/src/int/macros.rs b/src/int/macros.rs deleted file mode 100644 index eea4527a..00000000 --- a/src/int/macros.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Macros used to define traits on aliases of `Int`. - -// TODO(tarcieri): use `generic_const_exprs` when stable to make generic around bits. -macro_rules! impl_int_aliases { - ($(($name:ident, $bits:expr, $doc:expr)),+) => { - $( - #[doc = $doc] - #[doc="unsigned big integer."] - pub type $name = Int<{ nlimbs!($bits) }>; - - impl $name { - /// Serialize as big endian bytes. - pub const fn to_be_bytes(&self) -> [u8; $bits / 8] { - encoding::int_to_be_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) - } - - /// Serialize as little endian bytes. - pub const fn to_le_bytes(&self) -> [u8; $bits / 8] { - encoding::int_to_le_bytes::<{ nlimbs!($bits) }, { $bits / 8 }>(self) - } - } - - impl Encoding for $name { - type Repr = [u8; $bits / 8]; - - #[inline] - fn from_be_bytes(bytes: Self::Repr) -> Self { - Self::from_be_slice(&bytes) - } - - #[inline] - fn from_le_bytes(bytes: Self::Repr) -> Self { - Self::from_le_slice(&bytes) - } - - #[inline] - fn to_be_bytes(&self) -> Self::Repr { - encoding::int_to_be_bytes(self) - } - - #[inline] - fn to_le_bytes(&self) -> Self::Repr { - encoding::int_to_le_bytes(self) - } - } - )+ - }; -} diff --git a/src/int/mul.rs b/src/int/mul.rs index ec3f95a7..a36944b7 100644 --- a/src/int/mul.rs +++ b/src/int/mul.rs @@ -120,8 +120,7 @@ impl MulAssign<&Checked>> for Checked> #[cfg(test)] mod tests { - use crate::int::{Int, I128}; - use crate::{CheckedMul, I256}; + use crate::{CheckedMul, Int, I128, I256}; #[test] fn test_checked_mul() { diff --git a/src/int/sub.rs b/src/int/sub.rs index b8ec1f05..888ee4a9 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -81,8 +81,7 @@ mod tests { mod tests { use num_traits::WrappingSub; - use crate::int::I128; - use crate::{CheckedSub, Int, U128}; + use crate::{CheckedSub, Int, I128, U128}; #[test] fn checked_sub() { diff --git a/src/int/types.rs b/src/int/types.rs new file mode 100644 index 00000000..95ac194c --- /dev/null +++ b/src/int/types.rs @@ -0,0 +1,51 @@ +///! Selection of [`Int`] types. +/// todo: replace with macro implementation once serde is set up. +use crate::Int; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I64 = Int<1>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I128 = Int<2>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I256 = Int<4>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I512 = Int<8>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I1024 = Int<16>; + +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I2048 = Int<32>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I64 = Int<2>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I128 = Int<4>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I256 = Int<8>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I512 = Int<16>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I1024 = Int<32>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I2048 = Int<64>; diff --git a/src/lib.rs b/src/lib.rs index 4f7534b9..10675b12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -174,6 +174,7 @@ pub use crate::uint::boxed::BoxedUint; pub use crate::{ checked::Checked, const_choice::{ConstChoice, ConstCtOption}, + int::types::*, int::*, limb::{Limb, WideWord, Word}, non_zero::NonZero, From 7a667f6238c7b2a9c687258c001986a1734665c6 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 29 Oct 2024 10:51:37 +0100 Subject: [PATCH 83/87] Fix clippy --- src/int/types.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/int/types.rs b/src/int/types.rs index 95ac194c..1056cf4e 100644 --- a/src/int/types.rs +++ b/src/int/types.rs @@ -1,5 +1,6 @@ -///! Selection of [`Int`] types. -/// todo: replace with macro implementation once serde is set up. +//! Selection of [`Int`] types. +//! todo: replace with macro implementation once serde is set up. + use crate::Int; #[cfg(target_pointer_width = "64")] From 006720793d2cce720792fc2bdd21e9560596b96d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 29 Oct 2024 11:17:15 +0100 Subject: [PATCH 84/87] Add missing type --- src/int/types.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/int/types.rs b/src/int/types.rs index 1056cf4e..0488dd49 100644 --- a/src/int/types.rs +++ b/src/int/types.rs @@ -27,6 +27,10 @@ pub type I1024 = Int<16>; /// Signed bit integer. pub type I2048 = Int<32>; +#[cfg(target_pointer_width = "64")] +/// Signed bit integer. +pub type I4096 = Int<64>; + #[cfg(target_pointer_width = "32")] /// Signed bit integer. pub type I64 = Int<2>; @@ -50,3 +54,7 @@ pub type I1024 = Int<32>; #[cfg(target_pointer_width = "32")] /// Signed bit integer. pub type I2048 = Int<64>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I4096 = Int<128>; From 7688826c7773b85dcc5bef204b074ba1b947d2a7 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 29 Oct 2024 11:25:49 +0100 Subject: [PATCH 85/87] Fix `carrying_neg` docstring --- src/int/neg.rs | 2 +- src/uint/neg.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/int/neg.rs b/src/int/neg.rs index 7625d520..b1c37c67 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -8,7 +8,7 @@ impl Int { /// Perform the two's complement "negate" operation on this [`Int`]: /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. /// - /// Note: a zero carry indicates `self == Self::ZERO`. + /// Note: a non-zero carry indicates `self == Self::ZERO`. /// /// Warning: this operation is unsafe to use as negation; the negation is incorrect when /// `self == Self::MIN`. diff --git a/src/uint/neg.rs b/src/uint/neg.rs index 58552504..ee70bab7 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -7,7 +7,7 @@ impl Uint { } /// Perform negation; additionally return the carry. - /// Note: the carry equals `Word::ZERO` when `self == Self::ZERO`, and `Word::MAX` otherwise. + /// Note: the carry equals `Word::MAX` when `self == Self::ZERO`, and `Word::ZERO` otherwise. pub const fn carrying_neg(&self) -> (Self, Word) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; From 0719d316c950b1bba598e863a5cb9dbc24c6e1d0 Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Tue, 29 Oct 2024 11:26:09 +0100 Subject: [PATCH 86/87] Make `Int::neg` const --- src/int.rs | 2 +- src/int/neg.rs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/int.rs b/src/int.rs index 400f88dd..85833cfc 100644 --- a/src/int.rs +++ b/src/int.rs @@ -138,7 +138,7 @@ impl Int { } /// Whether this [`Int`] is equal to `Self::MIN`. - pub fn is_min(&self) -> ConstChoice { + pub const fn is_min(&self) -> ConstChoice { Self::eq(self, &Self::MIN) } diff --git a/src/int/neg.rs b/src/int/neg.rs index b1c37c67..badd7b3c 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -1,8 +1,6 @@ //! [`Int`] negation-related operations. -use subtle::CtOption; - -use crate::{ConstChoice, Int, Word}; +use crate::{ConstChoice, ConstCtOption, Int, Word}; impl Int { /// Perform the two's complement "negate" operation on this [`Int`]: @@ -31,8 +29,8 @@ impl Int { /// /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive /// equivalent of that. - pub fn neg(&self) -> CtOption { - CtOption::new(self.carrying_neg().0, self.is_min().not().into()) + pub const fn neg(&self) -> ConstCtOption { + ConstCtOption::new(self.carrying_neg().0, self.is_min().not()) } } @@ -95,7 +93,7 @@ mod tests { #[test] fn neg() { - assert_eq!(I128::MIN.neg().is_none().unwrap_u8(), 1u8); + assert_eq!(I128::MIN.neg().is_none(), ConstChoice::TRUE); assert_eq!(I128::MINUS_ONE.neg().unwrap(), I128::ONE); assert_eq!(I128::ZERO.neg().unwrap(), I128::ZERO); assert_eq!(I128::ONE.neg().unwrap(), I128::MINUS_ONE); From 223a79edaffb6dc620bebc7fb1c0a85fbcf32d3d Mon Sep 17 00:00:00 2001 From: Erik Takke Date: Wed, 30 Oct 2024 16:47:15 +0100 Subject: [PATCH 87/87] Update negation operations --- src/const_choice.rs | 5 +++ src/int/add.rs | 23 +++++++----- src/int/neg.rs | 90 ++++++++++++++++++++++++--------------------- src/int/sub.rs | 2 +- src/uint/neg.rs | 22 +++++------ 5 files changed, 79 insertions(+), 63 deletions(-) diff --git a/src/const_choice.rs b/src/const_choice.rs index 212b2172..8b5d4340 100644 --- a/src/const_choice.rs +++ b/src/const_choice.rs @@ -199,6 +199,11 @@ impl ConstChoice { Self::xor(self, other) } + #[inline] + pub(crate) const fn eq(&self, other: Self) -> Self { + Self::ne(self, other).not() + } + /// Return `b` if `self` is truthy, otherwise return `a`. #[inline] pub(crate) const fn select_word(&self, a: Word, b: Word) -> Word { diff --git a/src/int/add.rs b/src/int/add.rs index 2ce3699f..6212c319 100644 --- a/src/int/add.rs +++ b/src/int/add.rs @@ -5,15 +5,22 @@ use core::ops::{Add, AddAssign}; use num_traits::WrappingAdd; use subtle::CtOption; -use crate::{Checked, CheckedAdd, ConstCtOption, Int, Wrapping}; +use crate::{Checked, CheckedAdd, ConstChoice, ConstCtOption, Int, Wrapping}; impl Int { - /// Perform checked addition. + /// Perform checked addition. Returns `none` when the addition overflowed. pub const fn checked_add(&self, rhs: &Self) -> ConstCtOption { + let (value, overflow) = self.overflowing_add(rhs); + ConstCtOption::new(value, overflow.not()) + } + + /// Perform `self + rhs`, returns the result, as well as a flag indicating whether the + /// addition overflowed. + pub const fn overflowing_add(&self, rhs: &Self) -> (Self, ConstChoice) { // Step 1. add operands let res = Self(self.0.wrapping_add(&rhs.0)); - // Step 2. check whether overflow happened. + // Step 2. determine whether overflow happened. // Note: // - overflow can only happen when the inputs have the same sign, and then // - overflow occurs if and only if the result has the opposite sign of both inputs. @@ -21,12 +28,10 @@ impl Int { // We can thus express the overflow flag as: (self.msb == rhs.msb) & (self.msb != res.msb) let self_msb = self.is_negative(); let overflow = self_msb - .xor(rhs.is_negative()) - .not() - .and(self_msb.xor(res.is_negative())); + .eq(rhs.is_negative()) + .and(self_msb.ne(res.is_negative())); - // Step 3. Construct result - ConstCtOption::new(res, overflow.not()) + (res, overflow) } /// Perform wrapping addition, discarding overflow. @@ -141,7 +146,7 @@ mod tests { assert!(bool::from(result.is_none())); let result = I128::MINUS_ONE.checked_add(&I128::MINUS_ONE); - assert_eq!(result.unwrap(), two.neg().unwrap()); + assert_eq!(result.unwrap(), two.checked_neg().unwrap()); let result = I128::MINUS_ONE.checked_add(&I128::ZERO); assert_eq!(result.unwrap(), I128::MINUS_ONE); diff --git a/src/int/neg.rs b/src/int/neg.rs index badd7b3c..20c3b13d 100644 --- a/src/int/neg.rs +++ b/src/int/neg.rs @@ -1,74 +1,77 @@ //! [`Int`] negation-related operations. -use crate::{ConstChoice, ConstCtOption, Int, Word}; +use crate::{ConstChoice, ConstCtOption, Int, Uint}; impl Int { - /// Perform the two's complement "negate" operation on this [`Int`]: - /// map `self` to `(self ^ 1111...1111) + 0000...0001` and return the carry. + /// Map this [`Int`] to its two's-complement negation: + /// map `self` to `(self ^ 1111...1111) + 0000...0001`. /// - /// Note: a non-zero carry indicates `self == Self::ZERO`. + /// Returns the negation, as well as whether the operation overflowed. + /// The operation overflows when attempting to negate [`Int::MIN`]; the positive counterpart + /// of this value cannot be represented. + pub const fn overflowing_neg(&self) -> (Self, ConstChoice) { + Self(self.0.bitxor(&Uint::MAX)).overflowing_add(&Int::ONE) + } + + /// Wrapping negate this [`Int`]. /// - /// Warning: this operation is unsafe to use as negation; the negation is incorrect when - /// `self == Self::MIN`. - #[inline] - pub(crate) const fn carrying_neg(&self) -> (Self, Word) { - let (val, carry) = self.0.carrying_neg(); - (Self(val), carry) + /// Warning: this operation maps [`Int::MIN`] to itself, since the positive counterpart of this + /// value cannot be represented. + pub const fn wrapping_neg(&self) -> Self { + self.overflowing_neg().0 } /// Wrapping negate this [`Int`] if `negate` is truthy; otherwise do nothing. /// - /// Warning: this operation is unsafe to use as negation; the result is incorrect when - /// `self == Self::MIN` and `negate` is truthy. - #[inline] - pub(crate) const fn wrapping_neg_if(&self, negate: ConstChoice) -> Int { + /// Warning: this operation maps [`Int::MIN`] to itself, since the positive counterpart of this + /// value cannot be represented. + pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Int { Self(self.0.wrapping_neg_if(negate)) } - /// Map this [`Int`] to `-self`. + /// Negate this [`Int`]. /// - /// Yields `None` when `self == Self::MIN`, since an [`Int`] cannot represent the positive - /// equivalent of that. - pub const fn neg(&self) -> ConstCtOption { - ConstCtOption::new(self.carrying_neg().0, self.is_min().not()) + /// Yields `None` when `self == Self::MIN`, since the positive counterpart of this value cannot + /// be represented. + pub const fn checked_neg(&self) -> ConstCtOption { + let (value, overflow) = self.overflowing_neg(); + ConstCtOption::new(value, overflow.not()) } } #[cfg(test)] mod tests { - use num_traits::ConstZero; - - use crate::{ConstChoice, Word, I128}; + use crate::{ConstChoice, I128}; #[test] - fn carrying_neg() { + fn overflowing_neg() { let min_plus_one = I128 { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; - let (res, carry) = I128::MIN.carrying_neg(); - assert_eq!(carry, Word::MAX); + let (res, overflow) = I128::MIN.overflowing_neg(); assert_eq!(res, I128::MIN); + assert_eq!(overflow, ConstChoice::TRUE); - let (res, carry) = I128::MINUS_ONE.carrying_neg(); - assert_eq!(carry, Word::MAX); + let (res, overflow) = I128::MINUS_ONE.overflowing_neg(); assert_eq!(res, I128::ONE); + assert_eq!(overflow, ConstChoice::FALSE); - let (res, carry) = I128::ZERO.carrying_neg(); - assert_eq!(carry, Word::ZERO); + let (res, overflow) = I128::ZERO.overflowing_neg(); assert_eq!(res, I128::ZERO); + assert_eq!(overflow, ConstChoice::FALSE); - let (res, carry) = I128::ONE.carrying_neg(); - assert_eq!(carry, Word::MAX); + let (res, overflow) = I128::ONE.overflowing_neg(); assert_eq!(res, I128::MINUS_ONE); + assert_eq!(overflow, ConstChoice::FALSE); - let (res, carry) = I128::MAX.carrying_neg(); - assert_eq!(carry, Word::MAX); + let (res, overflow) = I128::MAX.overflowing_neg(); assert_eq!(res, min_plus_one); + assert_eq!(overflow, ConstChoice::FALSE); } #[test] - fn negate_if_unsafe() { + fn wrapping_neg_if() { let min_plus_one = I128 { 0: I128::MIN.0.wrapping_add(&I128::ONE.0), }; @@ -93,19 +96,22 @@ mod tests { #[test] fn neg() { - assert_eq!(I128::MIN.neg().is_none(), ConstChoice::TRUE); - assert_eq!(I128::MINUS_ONE.neg().unwrap(), I128::ONE); - assert_eq!(I128::ZERO.neg().unwrap(), I128::ZERO); - assert_eq!(I128::ONE.neg().unwrap(), I128::MINUS_ONE); + assert_eq!(I128::MIN.checked_neg().is_none(), ConstChoice::TRUE); + assert_eq!(I128::MINUS_ONE.checked_neg().unwrap(), I128::ONE); + assert_eq!(I128::ZERO.checked_neg().unwrap(), I128::ZERO); + assert_eq!(I128::ONE.checked_neg().unwrap(), I128::MINUS_ONE); assert_eq!( - I128::MAX.neg().unwrap(), + I128::MAX.checked_neg().unwrap(), I128::from_be_hex("80000000000000000000000000000001") ); let negative = I128::from_be_hex("91113333555577779999BBBBDDDDFFFF"); let positive = I128::from_be_hex("6EEECCCCAAAA88886666444422220001"); - assert_eq!(negative.neg().unwrap(), positive); - assert_eq!(positive.neg().unwrap(), negative); - assert_eq!(positive.neg().unwrap().neg().unwrap(), positive); + assert_eq!(negative.checked_neg().unwrap(), positive); + assert_eq!(positive.checked_neg().unwrap(), negative); + assert_eq!( + positive.checked_neg().unwrap().checked_neg().unwrap(), + positive + ); } } diff --git a/src/int/sub.rs b/src/int/sub.rs index 888ee4a9..1cb1d73b 100644 --- a/src/int/sub.rs +++ b/src/int/sub.rs @@ -127,7 +127,7 @@ mod tests { assert_eq!(result.unwrap(), I128::MINUS_ONE); let result = I128::MINUS_ONE.checked_sub(&I128::ONE); - assert_eq!(result.unwrap(), two.neg().unwrap()); + assert_eq!(result.unwrap(), two.checked_neg().unwrap()); let result = I128::MINUS_ONE.checked_sub(&I128::MAX); assert_eq!(result.unwrap(), I128::MIN); diff --git a/src/uint/neg.rs b/src/uint/neg.rs index ee70bab7..2852ce8f 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -6,9 +6,11 @@ impl Uint { self.carrying_neg().0 } - /// Perform negation; additionally return the carry. - /// Note: the carry equals `Word::MAX` when `self == Self::ZERO`, and `Word::ZERO` otherwise. - pub const fn carrying_neg(&self) -> (Self, Word) { + /// Perform negation: map `self` to `(self ^ 1111...1111) + 0000...0001`. + /// Additionally return whether the carry flag is set on the addition. + /// + /// Note: the carry is set if and only if `self == Self::ZERO`. + pub const fn carrying_neg(&self) -> (Self, ConstChoice) { let mut ret = [Limb::ZERO; LIMBS]; let mut carry = 1; let mut i = 0; @@ -18,10 +20,10 @@ impl Uint { carry = r >> Limb::BITS; i += 1; } - (Uint::new(ret), carry.wrapping_add(!0) as Word) + (Uint::new(ret), ConstChoice::from_word_lsb(carry as Word)) } - /// Perform negation, if `negate` is truthy. + /// Perform wrapping negation, if `negate` is truthy. Otherwise, return `self`. pub const fn wrapping_neg_if(&self, negate: ConstChoice) -> Self { Uint::select(self, &self.wrapping_neg(), negate) } @@ -36,9 +38,7 @@ impl WrappingNeg for Uint { #[cfg(test)] mod tests { - use num_traits::ConstZero; - - use crate::{ConstChoice, Word, U256}; + use crate::{ConstChoice, U256}; #[test] fn wrapping_neg() { @@ -56,9 +56,9 @@ mod tests { #[test] fn carrying_neg() { - assert_eq!(U256::ZERO.carrying_neg(), (U256::ZERO, Word::ZERO)); - assert_eq!(U256::ONE.carrying_neg(), (U256::MAX, Word::MAX)); - assert_eq!(U256::MAX.carrying_neg(), (U256::ONE, Word::MAX)); + assert_eq!(U256::ZERO.carrying_neg(), (U256::ZERO, ConstChoice::TRUE)); + assert_eq!(U256::ONE.carrying_neg(), (U256::MAX, ConstChoice::FALSE)); + assert_eq!(U256::MAX.carrying_neg(), (U256::ONE, ConstChoice::FALSE)); } #[test]