diff --git a/src/int.rs b/src/int.rs new file mode 100644 index 000000000..fdbcd6cc8 --- /dev/null +++ b/src/int.rs @@ -0,0 +1,167 @@ +//! Stack-allocated big signed integers. + +use num_traits::Zero; +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; + +use crate::{Limb, Uint}; + +mod add; +mod cmp; +mod div; +mod mul; +mod neg; +mod sub; + +#[derive(Copy, Clone, Hash, Debug, PartialEq)] +pub struct Int { + // TODO(erik): replace with Choice once I've figured how to const-construct those. + is_negative: bool, + magnitude: Uint, +} + +impl Int { + /// The value `0`. + pub const ZERO: Self = Self { + is_negative: false, + magnitude: Uint::ZERO, + }; + + /// The value `1`. + pub const ONE: Self = Self { + is_negative: false, + magnitude: Uint::ONE, + }; + + /// The value `-1`. + pub const MINUS_ONE: Self = Self { + is_negative: true, + magnitude: Uint::ONE, + }; + + /// Maximum value this [`Uint`] can express. + pub const MAX: Self = Self { + is_negative: false, + magnitude: Uint::MAX, + }; + + /// Smallest value this [`Uint`] can express. + // Note: keep in mind that when `LIMBS = 0`, the minimal value we can express is zero. + pub const MIN: Self = Self { + is_negative: LIMBS != 0, + magnitude: Uint::MAX, + }; + + /// The number of limbs used on this platform. + pub const LIMBS: usize = LIMBS; + + /// Const-friendly [`Int`] constructor. + pub const fn new(is_negative: bool, limbs: [Limb; LIMBS]) -> Self { + Self { + is_negative, + magnitude: Uint::new(limbs), + } + } + + /// Const-friendly [`Int`] constructor, using a `Uint`. + pub const fn new_from_uint(is_negative: bool, magnitude: Uint) -> Self { + Self { + is_negative, + magnitude, + } + } + + /// Whether this [`Int`] is negative + pub fn is_negative(&self) -> Choice { + Choice::from(u8::from(self.is_negative)) + } + + /// Whether this [`Int`] is zero + pub fn is_zero(&self) -> bool { + self.magnitude.is_zero() + } +} + +impl ConditionallySelectable for Int { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let magnitude = Uint::conditional_select(&a.magnitude, &b.magnitude, choice); + let is_negative = Choice::conditional_select(&a.is_negative(), &b.is_negative(), choice); + + Self { + is_negative: is_negative.unwrap_u8() == 1, + magnitude, + } + } +} + +impl Default for Int { + fn default() -> Self { + Self::ZERO + } +} + +impl Zero for Int { + fn zero() -> Self { + Self::ZERO + } + + fn is_zero(&self) -> bool { + self.ct_eq(&Self::ZERO).into() + } +} + +#[cfg(target_pointer_width = "64")] +#[allow(dead_code)] +type I128 = Int<2>; + +#[cfg(target_pointer_width = "32")] +#[allow(dead_code)] +type I128 = Int<4>; + +#[cfg(test)] +#[allow(clippy::unwrap_used)] +mod tests { + use crate::{ + int::{Int, I128}, + Uint, U128, + }; + + #[test] + fn zero() { + let n = I128::ZERO; + assert!(!n.is_negative); + assert_eq!(n.magnitude, U128::ZERO); + } + + #[test] + fn one() { + let n = I128::ONE; + assert!(!n.is_negative); + assert_eq!(n.magnitude, U128::ONE); + } + + #[test] + fn minus_one() { + let n = I128::MINUS_ONE; + assert!(n.is_negative); + assert_eq!(n.magnitude, U128::ONE); + } + + #[test] + fn min() { + let n = I128::MIN; + assert!(n.is_negative); + assert_eq!(n.magnitude, U128::MAX); + + // Deal with case that LIMBS = 0 + let n = Int::<0>::MIN; + assert!(!n.is_negative); + assert_eq!(n.magnitude, Uint::<0>::MAX); // dirty trick; MAX is zero, while ZERO panics. + } + + #[test] + fn max() { + let n = I128::MAX; + assert!(!n.is_negative); + assert_eq!(n.magnitude, U128::MAX); + } +} diff --git a/src/int/add.rs b/src/int/add.rs new file mode 100644 index 000000000..fac0fde4a --- /dev/null +++ b/src/int/add.rs @@ -0,0 +1,186 @@ +//! [`Int`] addition operations. + +use core::ops::{Add, AddAssign}; + +use subtle::{Choice, ConstantTimeEq, CtOption}; + +use crate::int::Int; +use crate::{Checked, CheckedAdd, CheckedSub, ConstantTimeSelect}; + +impl Int { + /// Add two [`Int`]s, checking for overflow. + fn checked_adc(&self, rhs: &Self) -> CtOption { + // Step 1. Select the element with the largest magnitude to be the lhs. + let (lhs, rhs) = Int::abs_max_min(self, rhs); + + // Step 2. Add/subtract the magnitudes of the two sides to/from each other + let magnitude_add = lhs.magnitude.checked_add(&rhs.magnitude); + let magnitude_sub = lhs.magnitude.checked_sub(&rhs.magnitude); + + // Step 3. Select the correct magnitude. + // magnitude_sub is used when the signs of the two elements are not the same. + let different_signs = lhs.is_negative().ct_ne(&rhs.is_negative()); + let magnitude = CtOption::ct_select(&magnitude_add, &magnitude_sub, different_signs); + + // Step 4. Determine the sign of the result. + // This is always the same as the sign of the self (since it assumed to have the + // larger magnitude), except when the sum is zero. + let sum_is_zero = different_signs & lhs.magnitude.ct_eq(&rhs.magnitude); + let is_negative = lhs.is_negative() & !sum_is_zero; + + magnitude.and_then(|magnitude| { + CtOption::new( + Self { + is_negative: is_negative.unwrap_u8() == 1, + magnitude, + }, + Choice::from(1), + ) + }) + } +} + +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 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 { + /// Add two [`Int`]s, checking for overflow. + fn checked_add(&self, rhs: &Self) -> CtOption { + self.checked_adc(rhs) + } +} + +#[cfg(test)] +mod tests { + use crate::int::I128; + use crate::{CheckedAdd, U128}; + + #[test] + fn checked_add() { + let max_minus_one = U128::MAX.wrapping_sub(&U128::ONE); + let two = 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(), I128::new_from_uint(true, max_minus_one)); + + let result = I128::MIN.checked_add(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + + // 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(), I128::new_from_uint(true, two)); + + 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(), I128::new_from_uint(false, 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(), I128::new_from_uint(true, max_minus_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(), I128::new_from_uint(false, 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::ZERO); + + let result = I128::MAX.checked_add(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::new_from_uint(false, 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 000000000..19cf5854a --- /dev/null +++ b/src/int/cmp.rs @@ -0,0 +1,35 @@ +//! [`Int`] comparisons. +//! +//! By default, these are all constant-time and use the `subtle` crate. + +use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeLess}; + +use crate::int::Int; +use crate::Uint; + +impl Int { + /// Given two [`Int`]s, return a tuple with the larger magnitude first. + /// + /// When both have the same magnitude, they are returned in the same order + /// as they were provided. + pub(crate) fn abs_max_min(&self, rhs: &Self) -> (Self, Self) { + let swap = self.magnitude.ct_lt(&rhs.magnitude); + let greatest = Int::conditional_select(self, rhs, swap); + let smallest = Int::conditional_select(rhs, self, swap); + (greatest, smallest) + } + + #[inline] + /// TODO: make this a const function? + pub(crate) fn eq(lhs: &Self, rhs: &Self) -> Choice { + Choice::from(Uint::eq(&lhs.magnitude, &rhs.magnitude)) + & Choice::ct_eq(&lhs.is_negative(), &rhs.is_negative()) + } +} + +impl ConstantTimeEq for Int { + #[inline] + fn ct_eq(&self, other: &Self) -> Choice { + Int::eq(self, other) + } +} diff --git a/src/int/div.rs b/src/int/div.rs new file mode 100644 index 000000000..d876fb96a --- /dev/null +++ b/src/int/div.rs @@ -0,0 +1,21 @@ +//! [`Int`] division operations. + +use subtle::{Choice, ConstantTimeEq, CtOption}; + +use crate::int::Int; +use crate::{CheckedDiv, Uint}; + +impl CheckedDiv for Int { + fn checked_div(&self, rhs: &Self) -> CtOption { + self.magnitude + .checked_div(&rhs.magnitude) + .and_then(|magnitude| { + let res_is_zero = magnitude.ct_eq(&Uint::ZERO); + let is_negative = (self.is_negative() ^ rhs.is_negative()) & !res_is_zero; + CtOption::new( + Int::new_from_uint(is_negative.into(), magnitude), + Choice::from(1u8), + ) + }) + } +} diff --git a/src/int/mul.rs b/src/int/mul.rs new file mode 100644 index 000000000..b63383441 --- /dev/null +++ b/src/int/mul.rs @@ -0,0 +1,15 @@ +//! [`Int`] multiplication operations. + +use subtle::{ConstantTimeEq, CtOption}; + +use crate::int::Int; +use crate::{CheckedMul, Uint, Zero}; + +impl CheckedMul> for Int { + #[inline] + fn checked_mul(&self, rhs: &Int) -> CtOption { + let (lo, hi) = self.magnitude.split_mul(&rhs.magnitude); + let is_negative = (self.is_negative() ^ rhs.is_negative()) & lo.ct_ne(&Uint::ZERO); + CtOption::new(Int::new_from_uint(is_negative.into(), lo), hi.is_zero()) + } +} diff --git a/src/int/neg.rs b/src/int/neg.rs new file mode 100644 index 000000000..68e0b30a1 --- /dev/null +++ b/src/int/neg.rs @@ -0,0 +1,21 @@ +//! [`Int`] negation operation. + +use crate::int::Int; +use crate::WrappingNeg; + +impl Int { + /// Perform wrapping negation. + pub fn wrapping_neg(&self) -> Self { + Self { + is_negative: !self.is_negative & !self.is_zero(), + magnitude: self.magnitude, + } + } +} + +impl WrappingNeg for Int { + #[inline] + fn wrapping_neg(&self) -> Self { + self.wrapping_neg() + } +} diff --git a/src/int/sub.rs b/src/int/sub.rs new file mode 100644 index 000000000..4e127f5f4 --- /dev/null +++ b/src/int/sub.rs @@ -0,0 +1,88 @@ +//! [`Int`] subtraction operations. + +use core::ops::{Sub, SubAssign}; + +use subtle::CtOption; + +use crate::int::Int; +use crate::{Checked, CheckedAdd, CheckedSub}; + +impl CheckedSub for Int { + fn checked_sub(&self, rhs: &Self) -> CtOption { + self.checked_add(&rhs.wrapping_neg()) + } +} + +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 Int { + fn sub_assign(&mut self, other: Self) { + *self -= &other + } +} + +impl SubAssign<&Int> for Int { + 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; + } +} + +#[cfg(test)] +mod tests { + use crate::int::I128; + use crate::CheckedSub; + + #[test] + fn checked_sub_ok() { + let result = I128::MIN.checked_sub(&I128::MIN); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MINUS_ONE.checked_sub(&I128::MINUS_ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ZERO.checked_sub(&I128::ZERO); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::ONE.checked_sub(&I128::ONE); + assert_eq!(result.unwrap(), I128::ZERO); + + let result = I128::MAX.checked_sub(&I128::MAX); + assert_eq!(result.unwrap(), I128::ZERO); + } + + #[test] + fn checked_sub_overflow() { + let result = I128::MIN.checked_sub(&I128::ONE); + assert!(bool::from(result.is_none())); + + let result = I128::MAX.checked_sub(&I128::MINUS_ONE); + assert!(bool::from(result.is_none())); + } +} diff --git a/src/lib.rs b/src/lib.rs index cdfab0abb..c9f94b8ae 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -164,6 +164,7 @@ pub mod modular; mod array; mod checked; mod const_choice; +mod int; mod limb; mod non_zero; mod odd;