Skip to content

Commit

Permalink
Expand integer trait (#489)
Browse files Browse the repository at this point in the history
  • Loading branch information
xuganyu96 authored Dec 21, 2023
1 parent 871b645 commit f9ef5aa
Show file tree
Hide file tree
Showing 14 changed files with 668 additions and 8 deletions.
16 changes: 15 additions & 1 deletion src/const_choice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use subtle::{Choice, CtOption};

use crate::{modular::BernsteinYangInverter, NonZero, Odd, Uint, Word};
use crate::{modular::BernsteinYangInverter, Limb, NonZero, Odd, Uint, Word};

/// A boolean value returned by constant-time `const fn`s.
// TODO: should be replaced by `subtle::Choice` or `CtOption`
Expand Down Expand Up @@ -319,6 +319,20 @@ impl<const LIMBS: usize> ConstCtOption<Odd<Uint<LIMBS>>> {
}
}

impl ConstCtOption<NonZero<Limb>> {
/// Returns the contained value, consuming the `self` value.
///
/// # Panics
///
/// Panics if the value is none with a custom panic message provided by
/// `msg`.
#[inline]
pub const fn expect(self, msg: &str) -> NonZero<Limb> {
assert!(self.is_some.is_true_vartime(), "{}", msg);
self.value
}
}

impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
ConstCtOption<BernsteinYangInverter<SAT_LIMBS, UNSAT_LIMBS>>
{
Expand Down
37 changes: 36 additions & 1 deletion src/modular/boxed_monty_form.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//! Implements `BoxedMontyForm`s, supporting modular arithmetic with a modulus whose size and value
//! is chosen at runtime.

mod add;
Expand All @@ -9,6 +8,7 @@ mod pow;
mod sub;

use super::{
div_by_2,
reduction::{montgomery_reduction_boxed, montgomery_reduction_boxed_mut},
Retrieve,
};
Expand Down Expand Up @@ -219,6 +219,18 @@ impl BoxedMontyForm {
debug_assert!(self.montgomery_form < self.params.modulus);
self.montgomery_form.clone()
}

/// Performs the modular division by 2, that is for given `x` returns `y`
/// such that `y * 2 = x mod p`. This means:
/// - if `x` is even, returns `x / 2`,
/// - if `x` is odd, returns `(x + p) / 2`
/// (since the modulus `p` in Montgomery form is always odd, this divides entirely).
pub fn div_by_2(&self) -> Self {
Self {
montgomery_form: div_by_2::div_by_2_boxed(&self.montgomery_form, &self.params.modulus),
params: self.params.clone(),
}
}
}

impl Retrieve for BoxedMontyForm {
Expand Down Expand Up @@ -258,3 +270,26 @@ fn convert_to_montgomery(integer: &mut BoxedUint, params: &BoxedMontyParams) {
#[cfg(feature = "zeroize")]
product.zeroize();
}

#[cfg(test)]
mod tests {
use super::{BoxedMontyForm, BoxedMontyParams, BoxedUint, Odd};

#[test]
fn new_params_with_valid_modulus() {
let modulus = Odd::new(BoxedUint::from(3u8)).unwrap();
BoxedMontyParams::new(modulus);
}

#[test]
fn div_by_2() {
let modulus = Odd::new(BoxedUint::from(9u8)).unwrap();
let params = BoxedMontyParams::new(modulus);
let zero = BoxedMontyForm::zero(params.clone());
let one = BoxedMontyForm::one(params.clone());
let two = one.add(&one);

assert_eq!(zero.div_by_2(), zero);
assert_eq!(one.div_by_2().mul(&two), one);
}
}
18 changes: 18 additions & 0 deletions src/modular/div_by_2.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::Uint;
#[cfg(feature = "alloc")]
use crate::{BoxedUint, ConstantTimeSelect};

pub(crate) fn div_by_2<const LIMBS: usize>(a: &Uint<LIMBS>, modulus: &Uint<LIMBS>) -> Uint<LIMBS> {
// We are looking for such `x` that `x * 2 = y mod modulus`,
Expand Down Expand Up @@ -28,3 +30,19 @@ pub(crate) fn div_by_2<const LIMBS: usize>(a: &Uint<LIMBS>, modulus: &Uint<LIMBS

Uint::<LIMBS>::select(&if_even, &if_odd, is_odd)
}

#[cfg(feature = "alloc")]
pub(crate) fn div_by_2_boxed(a: &BoxedUint, modulus: &BoxedUint) -> BoxedUint {
debug_assert_eq!(a.bits_precision(), modulus.bits_precision());

let (mut half, is_odd) = a.shr1_with_carry();
let half_modulus = modulus.shr1();

let if_odd = half
.wrapping_add(&half_modulus)
.wrapping_add(&BoxedUint::one_with_precision(a.bits_precision()));

half.ct_assign(&if_odd, is_odd);

half
}
24 changes: 24 additions & 0 deletions src/odd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,27 @@ impl Odd<BoxedUint> {
Odd(ret)
}
}

#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use super::BoxedUint;
use super::{Odd, Uint};

#[test]
fn not_odd_numbers() {
let zero = Odd::new(Uint::<4>::ZERO);
assert!(bool::from(zero.is_none()));
let two = Odd::new(Uint::<4>::from(2u8));
assert!(bool::from(two.is_none()));
}

#[cfg(feature = "alloc")]
#[test]
fn not_odd_numbers_boxed() {
let zero = Odd::new(BoxedUint::zero());
assert!(bool::from(zero.is_none()));
let two = Odd::new(BoxedUint::from(2u8));
assert!(bool::from(two.is_none()));
}
}
10 changes: 10 additions & 0 deletions src/uint/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod bits;
mod cmp;
mod ct;
mod div;
mod div_limb;
pub(crate) mod encoding;
mod from;
mod inv_mod;
Expand All @@ -19,6 +20,7 @@ mod neg;
mod neg_mod;
mod shl;
mod shr;
mod sqrt;
mod sub;
mod sub_mod;

Expand Down Expand Up @@ -92,6 +94,14 @@ impl BoxedUint {
.fold(Choice::from(1), |acc, limb| acc & limb.is_zero())
}

/// Is this [`BoxedUint`] not equal to zero?
pub fn is_nonzero(&self) -> Choice {
// TODO: why not just !self.is_zero()?
self.limbs
.iter()
.fold(Choice::from(0), |acc, limb| acc | limb.is_nonzero().into())
}

/// Is this [`BoxedUint`] equal to one?
pub fn is_one(&self) -> Choice {
let mut iter = self.limbs.iter();
Expand Down
106 changes: 105 additions & 1 deletion src/uint/boxed/bits.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Bit manipulation functions.

use crate::{BoxedUint, Limb, Zero};
use crate::{BoxedUint, ConstChoice, Limb, Zero};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};

impl BoxedUint {
Expand All @@ -24,6 +24,11 @@ impl BoxedUint {
Limb::BITS * n - leading_zeros
}

/// `floor(log2(self.bits_precision()))`.
pub(crate) fn log2_bits(&self) -> u32 {
u32::BITS - self.bits_precision().leading_zeros() - 1
}

/// Calculate the number of bits needed to represent this number in variable-time with respect
/// to `self`.
pub fn bits_vartime(&self) -> u32 {
Expand All @@ -36,6 +41,18 @@ impl BoxedUint {
Limb::BITS * (i as u32 + 1) - limb.leading_zeros()
}

/// Returns `true` if the bit at position `index` is set, `false` otherwise.
///
/// # Remarks
/// This operation is variable time with respect to `index` only.
pub fn bit_vartime(&self, index: u32) -> bool {
if index >= self.bits_precision() {
false
} else {
(self.limbs[(index / Limb::BITS) as usize].0 >> (index % Limb::BITS)) & 1 == 1
}
}

/// Get the precision of this [`BoxedUint`] in bits.
pub fn bits_precision(&self) -> u32 {
self.limbs.len() as u32 * Limb::BITS
Expand All @@ -55,6 +72,45 @@ impl BoxedUint {
count
}

/// Calculate the number of trailing ones in the binary representation of this number.
pub fn trailing_ones(&self) -> u32 {
let limbs = self.as_limbs();

let mut count = 0;
let mut i = 0;
let mut nonmax_limb_not_encountered = ConstChoice::TRUE;
while i < limbs.len() {
let l = limbs[i];
let z = l.trailing_ones();
count += nonmax_limb_not_encountered.if_true_u32(z);
nonmax_limb_not_encountered =
nonmax_limb_not_encountered.and(ConstChoice::from_word_eq(l.0, Limb::MAX.0));
i += 1;
}

count
}

/// Calculate the number of trailing ones in the binary representation of this number,
/// variable time in `self`.
pub fn trailing_ones_vartime(&self) -> u32 {
let limbs = self.as_limbs();

let mut count = 0;
let mut i = 0;
while i < limbs.len() {
let l = limbs[i];
let z = l.trailing_ones();
count += z;
if z != Limb::BITS {
break;
}
i += 1;
}

count
}

/// Sets the bit at `index` to 0 or 1 depending on the value of `bit_value`.
pub(crate) fn set_bit(&mut self, index: u32, bit_value: Choice) {
let limb_num = (index / Limb::BITS) as usize;
Expand Down Expand Up @@ -89,6 +145,18 @@ mod tests {
result
}

#[test]
fn bit_vartime() {
let u = uint_with_bits_at(&[16, 48, 112, 127, 255]);
assert!(!u.bit_vartime(0));
assert!(!u.bit_vartime(1));
assert!(u.bit_vartime(16));
assert!(u.bit_vartime(127));
assert!(u.bit_vartime(255));
assert!(!u.bit_vartime(256));
assert!(!u.bit_vartime(260));
}

#[test]
fn bits() {
assert_eq!(0, BoxedUint::zero().bits());
Expand Down Expand Up @@ -119,4 +187,40 @@ mod tests {
u.set_bit(150, Choice::from(0));
assert_eq!(u, uint_with_bits_at(&[16, 79]));
}

#[test]
fn trailing_ones() {
let u = !uint_with_bits_at(&[16, 79, 150]);
assert_eq!(u.trailing_ones(), 16);

let u = !uint_with_bits_at(&[79, 150]);
assert_eq!(u.trailing_ones(), 79);

let u = !uint_with_bits_at(&[150, 207]);
assert_eq!(u.trailing_ones(), 150);

let u = !uint_with_bits_at(&[0, 150, 207]);
assert_eq!(u.trailing_ones(), 0);

let u = !BoxedUint::zero_with_precision(256);
assert_eq!(u.trailing_ones(), 256);
}

#[test]
fn trailing_ones_vartime() {
let u = !uint_with_bits_at(&[16, 79, 150]);
assert_eq!(u.trailing_ones_vartime(), 16);

let u = !uint_with_bits_at(&[79, 150]);
assert_eq!(u.trailing_ones_vartime(), 79);

let u = !uint_with_bits_at(&[150, 207]);
assert_eq!(u.trailing_ones_vartime(), 150);

let u = !uint_with_bits_at(&[0, 150, 207]);
assert_eq!(u.trailing_ones_vartime(), 0);

let u = !BoxedUint::zero_with_precision(256);
assert_eq!(u.trailing_ones_vartime(), 256);
}
}
24 changes: 24 additions & 0 deletions src/uint/boxed/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,30 @@ use subtle::{
Choice, ConditionallySelectable, ConstantTimeEq, ConstantTimeGreater, ConstantTimeLess,
};

impl BoxedUint {
/// Returns the Ordering between `self` and `rhs` in variable time.
pub fn cmp_vartime(&self, rhs: &Self) -> Ordering {
debug_assert_eq!(self.limbs.len(), rhs.limbs.len());
let mut i = self.limbs.len() - 1;
loop {
// TODO: investigate if directly comparing limbs is faster than performing a
// subtraction between limbs
let (val, borrow) = self.limbs[i].sbb(rhs.limbs[i], Limb::ZERO);
if val.0 != 0 {
return if borrow.0 != 0 {
Ordering::Less
} else {
Ordering::Greater
};
}
if i == 0 {
return Ordering::Equal;
}
i -= 1;
}
}
}

impl ConstantTimeEq for BoxedUint {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
Expand Down
23 changes: 22 additions & 1 deletion src/uint/boxed/div.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
//! [`BoxedUint`] division operations.

use crate::{BoxedUint, CheckedDiv, ConstantTimeSelect, Limb, NonZero, Wrapping};
use crate::{
uint::boxed, BoxedUint, CheckedDiv, ConstantTimeSelect, Limb, NonZero, Reciprocal, Wrapping,
};
use core::ops::{Div, DivAssign, Rem, RemAssign};
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};

impl BoxedUint {
/// Computes `self` / `rhs` using a pre-made reciprocal,
/// returns the quotient (q) and remainder (r).
pub fn div_rem_limb_with_reciprocal(&self, reciprocal: &Reciprocal) -> (Self, Limb) {
boxed::div_limb::div_rem_limb_with_reciprocal(self, reciprocal)
}

/// Computes `self` / `rhs`, returns the quotient (q) and remainder (r).
pub fn div_rem_limb(&self, rhs: NonZero<Limb>) -> (Self, Limb) {
boxed::div_limb::div_rem_limb_with_reciprocal(self, &Reciprocal::new(rhs))
}

/// Computes self / rhs, returns the quotient, remainder.
pub fn div_rem(&self, rhs: &NonZero<Self>) -> (Self, Self) {
// Since `rhs` is nonzero, this should always hold.
Expand Down Expand Up @@ -61,6 +74,14 @@ impl BoxedUint {
self.div_rem(rhs).0
}

/// Wrapped division is just normal division i.e. `self` / `rhs`
///
/// There’s no way wrapping could ever happen.
/// This function exists, so that all operations are accounted for in the wrapping operations
pub fn wrapping_div_vartime(&self, rhs: &NonZero<Self>) -> Self {
self.div_rem_vartime(rhs).0
}

/// Perform checked division, returning a [`CtOption`] which `is_some`
/// only if the rhs != 0
pub fn checked_div(&self, rhs: &Self) -> CtOption<Self> {
Expand Down
Loading

0 comments on commit f9ef5aa

Please sign in to comment.