Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

crypto-bigint: Limb newtype #499

Merged
merged 1 commit into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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()
}
}