Skip to content

Commit

Permalink
crypto-bigint: Limb newtype
Browse files Browse the repository at this point in the history
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
tarcieri committed Jun 22, 2021
1 parent a52c85e commit 5a76a4c
Show file tree
Hide file tree
Showing 18 changed files with 914 additions and 307 deletions.
11 changes: 10 additions & 1 deletion crypto-bigint/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions crypto-bigint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ pub use {
generic_array::{self, typenum::consts},
};

#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
compile_error!("this crate builds on 32-bit and 64-bit platforms only");

/// Number of bytes in a [`Limb`].
#[cfg(target_pointer_width = "32")]
pub const LIMB_BYTES: usize = 4;
Expand Down
110 changes: 61 additions & 49 deletions crypto-bigint/src/limb.rs
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 {}
117 changes: 117 additions & 0 deletions crypto-bigint/src/limb/add.rs
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()));
}
}
76 changes: 76 additions & 0 deletions crypto-bigint/src/limb/cmp.rs
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()
}
}

0 comments on commit 5a76a4c

Please sign in to comment.