-
Notifications
You must be signed in to change notification settings - Fork 119
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a `Limb` newtype for the integer type which represents a limb for a given CPU word size. This allows us to provide much more consistent properties around what represents a "limb", as well as define functions directly on the limb type, which makes it much easier to define e.g. carry/borrow chains.
- Loading branch information
Showing
18 changed files
with
906 additions
and
307 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,76 @@ | ||
//! Limbs are smaller integers into which a big integer is subdivided. | ||
// TODO(tarcieri): `Limb` newtype? | ||
//! Limb newtype. | ||
|
||
/// Big integers are modeled as an array of smaller integers called "limbs". | ||
#[cfg(target_pointer_width = "32")] | ||
pub type Limb = u32; | ||
mod add; | ||
mod cmp; | ||
mod encoding; | ||
mod from; | ||
mod mul; | ||
mod sub; | ||
|
||
/// Big integers are modeled as an array of smaller integers called "limbs". | ||
#[cfg(target_pointer_width = "64")] | ||
pub type Limb = u64; | ||
use core::fmt; | ||
use subtle::{Choice, ConditionallySelectable}; | ||
|
||
/// Computes `a + b + carry`, returning the result along with the new carry. | ||
/// 32-bit version. | ||
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))] | ||
compile_error!("this crate builds on 32-bit and 64-bit platforms only"); | ||
|
||
/// Inner integer type. | ||
// TODO(tarcieri): expose this? | ||
#[cfg(target_pointer_width = "32")] | ||
#[inline(always)] | ||
pub(crate) const fn adc(a: Limb, b: Limb, carry: Limb) -> (Limb, Limb) { | ||
let ret = (a as u64) + (b as u64) + (carry as u64); | ||
(ret as u32, (ret >> 32) as u32) | ||
} | ||
pub(crate) type Inner = u32; | ||
#[cfg(target_pointer_width = "64")] | ||
pub(crate) type Inner = u64; | ||
|
||
/// Computes `a + b + carry`, returning the result along with the new carry. | ||
/// 64-bit version. | ||
/// Wide integer type: double the width of [`Inner`]. | ||
#[cfg(target_pointer_width = "32")] | ||
pub(crate) type Wide = u64; | ||
#[cfg(target_pointer_width = "64")] | ||
#[inline(always)] | ||
pub(crate) const fn adc(a: Limb, b: Limb, carry: Limb) -> (Limb, Limb) { | ||
let ret = (a as u128) + (b as u128) + (carry as u128); | ||
(ret as u64, (ret >> 64) as u64) | ||
pub(crate) type Wide = u128; | ||
|
||
/// Big integers are represented as an array of smaller CPU word-size integers | ||
/// called "limbs". | ||
#[derive(Copy, Clone, Debug, Default)] | ||
#[repr(transparent)] | ||
pub struct Limb(pub(crate) Inner); | ||
|
||
impl Limb { | ||
/// The value `0`. | ||
pub const ZERO: Self = Limb(0); | ||
|
||
/// The value `1`. | ||
pub const ONE: Self = Limb(1); | ||
|
||
/// Maximum value this [`Limb`] can express. | ||
pub const MAX: Self = Limb(Inner::MAX); | ||
} | ||
|
||
/// Computes `a - (b + borrow)`, returning the result along with the new borrow. | ||
/// 32-bit version. | ||
#[cfg(target_pointer_width = "32")] | ||
#[inline(always)] | ||
pub const fn sbb(a: Limb, b: Limb, borrow: Limb) -> (Limb, Limb) { | ||
let ret = (a as u64).wrapping_sub((b as u64) + ((borrow >> 31) as u64)); | ||
(ret as u32, (ret >> 32) as u32) | ||
impl ConditionallySelectable for Limb { | ||
#[inline] | ||
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { | ||
Limb(Inner::conditional_select(&a.0, &b.0, choice)) | ||
} | ||
} | ||
|
||
/// Computes `a - (b + borrow)`, returning the result along with the new borrow. | ||
/// 64-bit version. | ||
#[cfg(target_pointer_width = "64")] | ||
#[inline(always)] | ||
pub const fn sbb(a: Limb, b: Limb, borrow: Limb) -> (Limb, Limb) { | ||
let ret = (a as u128).wrapping_sub((b as u128) + ((borrow >> 63) as u128)); | ||
(ret as u64, (ret >> 64) as u64) | ||
impl fmt::Display for Limb { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
fmt::UpperHex::fmt(self, f) | ||
} | ||
} | ||
|
||
/// Computes `a + (b * c) + carry`, returning the result along with the new carry. | ||
/// 32-bit version. | ||
#[cfg(target_pointer_width = "32")] | ||
#[inline(always)] | ||
pub(crate) const fn mac(a: Limb, b: Limb, c: Limb, carry: Limb) -> (Limb, Limb) { | ||
let ret = (a as u64) + ((b as u64) * (c as u64)) + (carry as u64); | ||
(ret as u32, (ret >> 32) as u32) | ||
impl fmt::LowerHex for Limb { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
fmt::LowerHex::fmt(&self.0, f) | ||
} | ||
} | ||
|
||
/// Computes `a + (b * c) + carry`, returning the result along with the new carry. | ||
/// 64-bit version. | ||
#[cfg(target_pointer_width = "64")] | ||
#[inline(always)] | ||
pub(crate) const fn mac(a: Limb, b: Limb, c: Limb, carry: Limb) -> (Limb, Limb) { | ||
let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128); | ||
(ret as u64, (ret >> 64) as u64) | ||
impl fmt::UpperHex for Limb { | ||
#[inline] | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
fmt::UpperHex::fmt(&self.0, f) | ||
} | ||
} | ||
|
||
#[cfg(feature = "zeroize")] | ||
#[cfg_attr(docsrs, doc(cfg(feature = "zeroize")))] | ||
impl zeroize::DefaultIsZeroes for Limb {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
//! Limb addition | ||
|
||
use super::{Inner, Limb, Wide}; | ||
use crate::{Encoding, Wrapping}; | ||
use core::ops::{Add, AddAssign}; | ||
use subtle::CtOption; | ||
|
||
impl Limb { | ||
/// Computes `self + rhs + carry`, returning the result along with the new carry. | ||
#[inline(always)] | ||
pub const fn adc(self, rhs: Limb, carry: Limb) -> (Limb, Limb) { | ||
let a = self.0 as Wide; | ||
let b = rhs.0 as Wide; | ||
let carry = carry.0 as Wide; | ||
let ret = a + b + carry; | ||
(Limb(ret as Inner), Limb((ret >> Self::BIT_SIZE) as Inner)) | ||
} | ||
|
||
/// Perform wrapping addition, discarding overflow. | ||
#[inline(always)] | ||
pub const fn wrapping_add(&self, rhs: Self) -> Self { | ||
Limb(self.0.wrapping_add(rhs.0)) | ||
} | ||
|
||
/// Perform checked addition, returning a [`CtOption`] which `is_some` only | ||
/// if the operation did not overflow. | ||
#[inline] | ||
pub fn checked_add(&self, rhs: Self) -> CtOption<Self> { | ||
let (result, carry) = self.adc(rhs, Limb::ZERO); | ||
CtOption::new(result, carry.is_zero()) | ||
} | ||
} | ||
|
||
impl Add for Wrapping<Limb> { | ||
type Output = Self; | ||
|
||
fn add(self, rhs: Self) -> Wrapping<Limb> { | ||
Wrapping(self.0.wrapping_add(rhs.0)) | ||
} | ||
} | ||
|
||
impl Add<&Wrapping<Limb>> for Wrapping<Limb> { | ||
type Output = Wrapping<Limb>; | ||
|
||
fn add(self, rhs: &Wrapping<Limb>) -> Wrapping<Limb> { | ||
Wrapping(self.0.wrapping_add(rhs.0)) | ||
} | ||
} | ||
|
||
impl Add<Wrapping<Limb>> for &Wrapping<Limb> { | ||
type Output = Wrapping<Limb>; | ||
|
||
fn add(self, rhs: Wrapping<Limb>) -> Wrapping<Limb> { | ||
Wrapping(self.0.wrapping_add(rhs.0)) | ||
} | ||
} | ||
|
||
impl Add<&Wrapping<Limb>> for &Wrapping<Limb> { | ||
type Output = Wrapping<Limb>; | ||
|
||
fn add(self, rhs: &Wrapping<Limb>) -> Wrapping<Limb> { | ||
Wrapping(self.0.wrapping_add(rhs.0)) | ||
} | ||
} | ||
|
||
impl AddAssign for Wrapping<Limb> { | ||
fn add_assign(&mut self, other: Self) { | ||
*self = *self + other; | ||
} | ||
} | ||
|
||
impl AddAssign<&Wrapping<Limb>> for Wrapping<Limb> { | ||
fn add_assign(&mut self, other: &Self) { | ||
*self = *self + other; | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::Limb; | ||
|
||
#[test] | ||
fn adc_no_carry() { | ||
let (res, carry) = Limb::ZERO.adc(Limb::ONE, Limb::ZERO); | ||
assert_eq!(res, Limb::ONE); | ||
assert_eq!(carry, Limb::ZERO); | ||
} | ||
|
||
#[test] | ||
fn adc_with_carry() { | ||
let (res, carry) = Limb::MAX.adc(Limb::ONE, Limb::ZERO); | ||
assert_eq!(res, Limb::ZERO); | ||
assert_eq!(carry, Limb::ONE); | ||
} | ||
|
||
#[test] | ||
fn wrapping_add_no_carry() { | ||
assert_eq!(Limb::ZERO.wrapping_add(Limb::ONE), Limb::ONE); | ||
} | ||
|
||
#[test] | ||
fn wrapping_add_with_carry() { | ||
assert_eq!(Limb::MAX.wrapping_add(Limb::ONE), Limb::ZERO); | ||
} | ||
|
||
#[test] | ||
fn checked_add_ok() { | ||
let result = Limb::ZERO.checked_add(Limb::ONE); | ||
assert_eq!(result.unwrap(), Limb::ONE); | ||
} | ||
|
||
#[test] | ||
fn checked_add_overflow() { | ||
let result = Limb::MAX.checked_add(Limb::ONE); | ||
assert!(!bool::from(result.is_some())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
//! Limb comparisons | ||
|
||
use super::Limb; | ||
use core::cmp::Ordering; | ||
use subtle::{Choice, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess}; | ||
|
||
impl Limb { | ||
/// Is this limb equal to zero? | ||
#[inline] | ||
pub fn is_zero(&self) -> Choice { | ||
self.ct_eq(&Self::ZERO) | ||
} | ||
|
||
/// Perform a comparison of the inner value in variable-time. | ||
/// | ||
/// Note that the [`PartialOrd`] and [`Ord`] impls wrap constant-time | ||
/// comparisons using the `subtle` crate. | ||
pub fn cmp_vartime(&self, other: &Self) -> Ordering { | ||
self.0.cmp(&other.0) | ||
} | ||
} | ||
|
||
impl ConstantTimeEq for Limb { | ||
#[inline] | ||
fn ct_eq(&self, other: &Self) -> Choice { | ||
self.0.ct_eq(&other.0) | ||
} | ||
} | ||
|
||
impl ConstantTimeGreater for Limb { | ||
#[inline] | ||
fn ct_gt(&self, other: &Self) -> Choice { | ||
self.0.ct_gt(&other.0) | ||
} | ||
} | ||
|
||
impl ConstantTimeLess for Limb { | ||
#[inline] | ||
fn ct_lt(&self, other: &Self) -> Choice { | ||
self.0.ct_lt(&other.0) | ||
} | ||
} | ||
|
||
impl Eq for Limb {} | ||
|
||
impl Ord for Limb { | ||
fn cmp(&self, other: &Self) -> Ordering { | ||
let mut n = 0i8; | ||
n -= self.ct_lt(other).unwrap_u8() as i8; | ||
n += self.ct_gt(other).unwrap_u8() as i8; | ||
|
||
match n { | ||
-1 => Ordering::Less, | ||
1 => Ordering::Greater, | ||
_ => { | ||
debug_assert_eq!(n, 0); | ||
debug_assert!(bool::from(self.ct_eq(other))); | ||
Ordering::Equal | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl PartialOrd for Limb { | ||
#[inline] | ||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | ||
Some(self.cmp(other)) | ||
} | ||
} | ||
|
||
impl PartialEq for Limb { | ||
#[inline] | ||
fn eq(&self, other: &Self) -> bool { | ||
self.ct_eq(other).into() | ||
} | ||
} |
Oops, something went wrong.