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 diff --git a/benches/int.rs b/benches/int.rs new file mode 100644 index 00000000..466eb6b7 --- /dev/null +++ b/benches/int.rs @@ -0,0 +1,344 @@ +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}; + +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, 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)), + |(x, y)| black_box(x.split_mul(&y)), + BatchSize::SmallInput, + ) + }); +} + +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"); + + group.bench_function("div, I256/I128, full size", |b| { + b.iter_batched( + || { + let x = I256::random(&mut OsRng); + let y = I128::random(&mut OsRng).resize::<{ 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).resize::<{ I512::LIMBS }>(); + (x, NonZero::new(y).unwrap()) + }, + |(x, y)| black_box(x.div(&y)), + BatchSize::SmallInput, + ) + }); + + group.bench_function("div, I1024/I512, full size", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I512::random(&mut OsRng).resize::<{ 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).resize::<{ 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( + || { + let x = I4096::random(&mut OsRng); + let y = I2048::random(&mut OsRng).resize::<{ 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", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_add(&y)), + BatchSize::SmallInput, + ) + }); + + 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.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", |b| { + b.iter_batched( + || { + let x = I1024::random(&mut OsRng); + let y = I1024::random(&mut OsRng); + (x, 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", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_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", |b| { + b.iter_batched( + || { + let x = I128::random(&mut OsRng); + let y = I128::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + 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.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + 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.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", |b| { + b.iter_batched( + || { + let x = I4096::random(&mut OsRng); + let y = I4096::random(&mut OsRng); + (x, y) + }, + |(x, y)| black_box(x.wrapping_sub(&y)), + BatchSize::SmallInput, + ) + }); + + group.finish(); +} + +criterion_group!( + benches, + bench_mul, + bench_widening_mul, + bench_div, + bench_add, + bench_sub, +); + +criterion_main!(benches); diff --git a/src/const_choice.rs b/src/const_choice.rs index 524a3525..8b5d4340 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] @@ -187,6 +194,16 @@ impl ConstChoice { Self(self.0 ^ other.0) } + #[inline] + pub(crate) const fn ne(&self, other: Self) -> Self { + 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 { @@ -450,9 +467,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 new file mode 100644 index 00000000..85833cfc --- /dev/null +++ b/src/int.rs @@ -0,0 +1,380 @@ +//! Stack-allocated big signed integers. + +use core::fmt; + +use num_traits::ConstZero; +#[cfg(feature = "serde")] +use serdect::serde::{Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +#[cfg(feature = "serde")] +use crate::Encoding; +use crate::{Bounded, ConstChoice, ConstCtOption, Constants, Limb, NonZero, Odd, Uint, Word}; + +mod add; +mod cmp; +mod div; +mod encoding; +mod from; +mod mul; +mod neg; +mod resize; +mod sign; +mod sub; +pub(crate) mod types; + +#[cfg(feature = "rand_core")] +mod rand; + +/// 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); + +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_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)) + } + + /// Const-friendly [`Int`] constructor. + /// + /// 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) + } + + /// 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()) + } + + /// 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`. + pub const fn is_min(&self) -> ConstChoice { + Self::eq(self, &Self::MIN) + } + + /// Whether this [`Int`] is equal to `Self::MAX`. + pub fn is_max(&self) -> ConstChoice { + Self::eq(self, &Self::MAX) + } + + /// Invert the most significant bit (msb) of this [`Int`] + const fn invert_msb(&self) -> Self { + Self(self.0.bitxor(&Self::SIGN_MASK.0)) + } +} + +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; +} + +impl Constants for Int { + const ONE: Self = Self::ONE; + const MAX: Self = Self::MAX; +} + +impl Default for Int { + fn default() -> Self { + Self::ZERO + } +} + +// TODO: impl FixedInteger + +// TODO: impl Integer + +impl ConstZero for Int { + const ZERO: Self = Self::ZERO; +} + +impl num_traits::Zero for Int { + fn zero() -> Self { + Self::ZERO + } + + 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() + } +} + +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(feature = "serde")] +impl<'de, const LIMBS: usize> Deserialize<'de> for Int +where + Int: Encoding, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let mut buffer = Self::ZERO.to_le_bytes(); + serdect::array::deserialize_hex_or_bin(buffer.as_mut(), deserializer)?; + Ok(Self::from_le_bytes(buffer)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Int +where + Int: Encoding, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serdect::array::serialize_hex_lower_or_bin(&Encoding::to_le_bytes(self), serializer) + } +} + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use subtle::ConditionallySelectable; + + use crate::{ConstChoice, I128, U128}; + + #[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 is_minimal() { + let min = I128::from_be_hex("80000000000000000000000000000000"); + assert_eq!(min.is_min(), ConstChoice::TRUE); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + assert_eq!(random.is_min(), ConstChoice::FALSE); + } + + #[test] + fn is_maximal() { + let max = I128::from_be_hex("7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + assert_eq!(max.is_max(), ConstChoice::TRUE); + + let random = I128::from_be_hex("11113333555577779999BBBBDDDDFFFF"); + 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); + } +} diff --git a/src/int/add.rs b/src/int/add.rs new file mode 100644 index 00000000..6212c319 --- /dev/null +++ b/src/int/add.rs @@ -0,0 +1,212 @@ +//! [`Int`] addition operations. + +use core::ops::{Add, AddAssign}; + +use num_traits::WrappingAdd; +use subtle::CtOption; + +use crate::{Checked, CheckedAdd, ConstChoice, ConstCtOption, Int, Wrapping}; + +impl Int { + /// 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. 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. + // + // 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 + .eq(rhs.is_negative()) + .and(self_msb.ne(res.is_negative())); + + (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 { + CtOption::from(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).into() + } +} + +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::{I128, U128}; + + #[test] + fn checked_add() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + let max_minus_one = I128 { + 0: I128::MAX.0.wrapping_sub(&I128::ONE.0), + }; + let two = I128 { + 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.checked_neg().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())); + } + } +} diff --git a/src/int/cmp.rs b/src/int/cmp.rs new file mode 100644 index 00000000..eeee0c6f --- /dev/null +++ b/src/int/cmp.rs @@ -0,0 +1,191 @@ +//! [`Int`] comparisons. +//! +//! By default, these are all constant-time. + +use core::cmp::Ordering; + +use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; + +use crate::{ConstChoice, Int, Uint}; + +impl Int { + /// 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 == 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() + } +} + +#[cfg(test)] +mod tests { + use subtle::{ConstantTimeGreater, ConstantTimeLess}; + + use crate::I128; + + #[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_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); + } + + #[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 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))); + } +} diff --git a/src/int/div.rs b/src/int/div.rs new file mode 100644 index 00000000..86aec783 --- /dev/null +++ b/src/int/div.rs @@ -0,0 +1,336 @@ +//! [`Int`] division operations. + +use core::ops::Div; + +use subtle::{Choice, CtOption}; + +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( + &self, + rhs: &NonZero, + ) -> (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.abs_sign(); + + // Step 2. Divide magnitudes + // safe to unwrap since rhs is NonZero. + let (quotient, remainder) = lhs_mag.div_rem(&rhs_mag); + + (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 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_abs_sign(quotient, opposing_signs), + Self::new_from_abs_sign(remainder, lhs_sgn), + ) + } + + /// 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| self.checked_div_rem(&rhs).0.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, lhs_sgn, rhs_sgn) = self.div_rem_base(&rhs); + + // 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); + + Self::new_from_abs_sign(quotient, opposing_signs).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_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); + + // 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_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))) + }) + }) + } +} + +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 { + use crate::{Int, I128}; + + #[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); + } + + #[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)) + ); + } +} diff --git a/src/int/encoding.rs b/src/int/encoding.rs new file mode 100644 index 00000000..1bbe30c8 --- /dev/null +++ b/src/int/encoding.rs @@ -0,0 +1,14 @@ +//! Const-friendly decoding/encoding operations for [`Int`]. + +use crate::{Int, Uint}; + +impl Int { + /// 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)) + } +} diff --git a/src/int/from.rs b/src/int/from.rs new file mode 100644 index 00000000..e387f9d8 --- /dev/null +++ b/src/int/from.rs @@ -0,0 +1,158 @@ +//! `From`-like conversions for [`Int`]. + +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 { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + 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"); + 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"); + 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 { + Uint::<{ I64::LIMBS }>::from_u64(n as u64).as_int().resize() + } + + /// 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 { + assert!(LIMBS >= 1, "number of limbs must be greater than zero"); + 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 { + Uint::<{ I128::LIMBS }>::from_u128(n as u128) + .as_int() + .resize() + } +} + +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)]); + 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] + fn from_i128() { + 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); + } +} diff --git a/src/int/mul.rs b/src/int/mul.rs new file mode 100644 index 00000000..a36944b7 --- /dev/null +++ b/src/int/mul.rs @@ -0,0 +1,285 @@ +//! [`Int`] multiplication operations. + +use core::ops::{Mul, MulAssign}; + +use subtle::CtOption; + +use crate::{Checked, CheckedMul, ConcatMixed, ConstChoice, Int, Uint, Zero}; + +impl Int { + /// Compute "wide" multiplication as a 3-tuple `(lo, hi, negate)`. + /// The `(lo, hi)` components contain the _magnitude of the product_, with sizes + /// 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 }>, ConstChoice) { + // Step 1: split operands into their signs and magnitudes. + 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_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. + // 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, 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::from_bits(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_abs_sign(lo, is_negative); + CtOption::from(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; + } +} + +// 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::{CheckedMul, Int, I128, I256}; + + #[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())); + } + + #[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 new file mode 100644 index 00000000..20c3b13d --- /dev/null +++ b/src/int/neg.rs @@ -0,0 +1,117 @@ +//! [`Int`] negation-related operations. + +use crate::{ConstChoice, ConstCtOption, Int, Uint}; + +impl Int { + /// Map this [`Int`] to its two's-complement negation: + /// map `self` to `(self ^ 1111...1111) + 0000...0001`. + /// + /// 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 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 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)) + } + + /// Negate this [`Int`]. + /// + /// 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 crate::{ConstChoice, I128}; + + #[test] + fn overflowing_neg() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let (res, overflow) = I128::MIN.overflowing_neg(); + assert_eq!(res, I128::MIN); + assert_eq!(overflow, ConstChoice::TRUE); + + let (res, overflow) = I128::MINUS_ONE.overflowing_neg(); + assert_eq!(res, I128::ONE); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::ZERO.overflowing_neg(); + assert_eq!(res, I128::ZERO); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::ONE.overflowing_neg(); + assert_eq!(res, I128::MINUS_ONE); + assert_eq!(overflow, ConstChoice::FALSE); + + let (res, overflow) = I128::MAX.overflowing_neg(); + assert_eq!(res, min_plus_one); + assert_eq!(overflow, ConstChoice::FALSE); + } + + #[test] + fn wrapping_neg_if() { + let min_plus_one = I128 { + 0: I128::MIN.0.wrapping_add(&I128::ONE.0), + }; + + let do_negate = ConstChoice::TRUE.into(); + 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.wrapping_neg_if(do_not_negate), I128::MIN); + assert_eq!( + I128::MINUS_ONE.wrapping_neg_if(do_not_negate), + I128::MINUS_ONE + ); + 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] + fn neg() { + 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.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.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/rand.rs b/src/int/rand.rs new file mode 100644 index 00000000..37e25f40 --- /dev/null +++ b/src/int/rand.rs @@ -0,0 +1,31 @@ +//! Random number generator support + +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 { + 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(Self) + } +} diff --git a/src/int/resize.rs b/src/int/resize.rs new file mode 100644 index 00000000..4aed2899 --- /dev/null +++ b/src/int/resize.rs @@ -0,0 +1,50 @@ +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 { + 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; + } + Uint { limbs }.as_int() + } +} + +#[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 }>(), + I128::MAX.0.resize().as_int() + ); + } +} diff --git a/src/int/sign.rs b/src/int/sign.rs new file mode 100644 index 00000000..e8bd3ed0 --- /dev/null +++ b/src/int/sign.rs @@ -0,0 +1,86 @@ +use crate::{ConstChoice, ConstCtOption, Int, Uint, Word}; +use num_traits::ConstZero; + +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( + abs: Uint, + 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))); + ConstCtOption::new(magnitude, fits) + } + + /// Whether this [`Int`] is negative, as a `ConstChoice`. + pub const fn is_negative(&self) -> ConstChoice { + ConstChoice::from_word_msb(self.most_significant_word()) + } + + /// Whether this [`Int`] is positive, as a `ConstChoice`. + pub const fn is_positive(&self) -> ConstChoice { + self.is_negative().not().and(self.is_nonzero()) + } + + /// 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); + } +} diff --git a/src/int/sub.rs b/src/int/sub.rs new file mode 100644 index 00000000..1cb1d73b --- /dev/null +++ b/src/int/sub.rs @@ -0,0 +1,212 @@ +//! [`Int`] subtraction operations. + +use core::ops::{Sub, SubAssign}; + +use num_traits::WrappingSub; +use subtle::{Choice, ConstantTimeEq, CtOption}; + +use crate::{Checked, CheckedSub, Int, Wrapping}; + +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: Choice = self.is_negative().into(); + let underflow = + self_msb.ct_ne(&rhs.is_negative().into()) & self_msb.ct_ne(&res.is_negative().into()); + + // 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 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) { + *self = *self - other; + } +} + +impl SubAssign<&Checked>> for Checked> { + fn sub_assign(&mut self, other: &Self) { + *self = *self - other; + } +} + +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, I128, 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), + }; + + // 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.checked_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); + } + + #[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); + } + } +} diff --git a/src/int/types.rs b/src/int/types.rs new file mode 100644 index 00000000..0488dd49 --- /dev/null +++ b/src/int/types.rs @@ -0,0 +1,60 @@ +//! 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 = "64")] +/// Signed bit integer. +pub type I4096 = Int<64>; + +#[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>; + +#[cfg(target_pointer_width = "32")] +/// Signed bit integer. +pub type I4096 = Int<128>; diff --git a/src/lib.rs b/src/lib.rs index cdfab0ab..10675b12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,26 +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 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}, + int::types::*, + int::*, limb::{Limb, WideWord, Word}, non_zero::NonZero, odd::Odd, @@ -183,30 +184,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::*; } 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 = diff --git a/src/uint.rs b/src/uint.rs index 44a1853d..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; @@ -16,7 +33,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; @@ -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] diff --git a/src/uint/cmp.rs b/src/uint/cmp.rs index eeb9a554..453f8d11 100644 --- a/src/uint/cmp.rs +++ b/src/uint/cmp.rs @@ -1,12 +1,15 @@ //! [`Uint`] comparisons. //! -//! By default these are all constant-time and use the `subtle` crate. +//! By default, these are all constant-time. -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 { @@ -160,10 +169,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())); @@ -233,6 +244,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; diff --git a/src/uint/neg.rs b/src/uint/neg.rs index e734f1fb..2852ce8f 100644 --- a/src/uint/neg.rs +++ b/src/uint/neg.rs @@ -1,8 +1,16 @@ -use crate::{Limb, Uint, WideWord, Word, WrappingNeg}; +use crate::{ConstChoice, Limb, Uint, WideWord, Word, WrappingNeg}; impl Uint { /// Perform wrapping negation. pub const fn wrapping_neg(&self) -> Self { + self.carrying_neg().0 + } + + /// 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; @@ -12,7 +20,12 @@ impl Uint { carry = r >> Limb::BITS; i += 1; } - Uint::new(ret) + (Uint::new(ret), ConstChoice::from_word_lsb(carry as Word)) + } + + /// 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) } } @@ -25,7 +38,7 @@ impl WrappingNeg for Uint { #[cfg(test)] mod tests { - use crate::U256; + use crate::{ConstChoice, U256}; #[test] fn wrapping_neg() { @@ -40,4 +53,24 @@ mod tests { U256::from_u64(42).saturating_sub(&U256::ONE).not() ); } + + #[test] + fn carrying_neg() { + 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] + 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); + } } 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};