Skip to content

Commit

Permalink
Add ConstantTimeSelect trait (#454)
Browse files Browse the repository at this point in the history
We can't impl `subtle::ConditionallySelectable` for `Boxed*` types due
to a bound on `Copy`.

I've proposed various ways to try to fix this upstream, e.g.
dalek-cryptography/subtle#118, from which the `ConstantTimeSelect` trait
has been extracted. It provides the same API as
`ConditionallySelectable` but without the `Copy` bound.

A blanket impl of `ConstantTimeSelect` for all `ConditionallySelectable`
types means that all stack allocated types can continue to use
`ConditionallySelectable` and will receive an impl of
`ConstantTimeSelect` as well.

A bound on `ConstantTimeSelect` has been added to the `Integer` trait as
well, allowing it to be used on all `*Uint` types in this crate with
`Integer` as the trait abstraction.
  • Loading branch information
tarcieri authored Dec 17, 2023
1 parent 3e571c3 commit e7bcff7
Show file tree
Hide file tree
Showing 9 changed files with 81 additions and 164 deletions.
6 changes: 3 additions & 3 deletions src/modular/boxed_residue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::{
reduction::{montgomery_reduction_boxed, montgomery_reduction_boxed_mut},
Retrieve,
};
use crate::{BoxedUint, Integer, Limb, NonZero, Word};
use crate::{BoxedUint, ConstantTimeSelect, Integer, Limb, NonZero, Word};
use subtle::CtOption;

#[cfg(feature = "std")]
Expand Down Expand Up @@ -49,7 +49,7 @@ impl BoxedResidueParams {

// Use a surrogate value of `1` in case a modulus of `0` is passed.
// This will be rejected by the `is_odd` check above, which will fail and return `None`.
let modulus_nz = NonZero::new(BoxedUint::conditional_select(
let modulus_nz = NonZero::new(BoxedUint::ct_select(
&modulus,
&BoxedUint::one_with_precision(bits_precision),
modulus.is_zero(),
Expand Down Expand Up @@ -82,7 +82,7 @@ impl BoxedResidueParams {

// Use a surrogate value of `1` in case a modulus of `0` is passed.
// This will be rejected by the `is_odd` check above, which will fail and return `None`.
let modulus_nz = NonZero::new(BoxedUint::conditional_select(
let modulus_nz = NonZero::new(BoxedUint::ct_select(
&modulus,
&BoxedUint::one_with_precision(bits_precision),
modulus.is_zero(),
Expand Down
4 changes: 2 additions & 2 deletions src/modular/boxed_residue/pow.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Modular exponentiation support for [`BoxedResidue`].

use super::{mul::MontgomeryMultiplier, BoxedResidue};
use crate::{BoxedUint, Limb, PowBoundedExp, Word};
use crate::{BoxedUint, ConstantTimeSelect, Limb, PowBoundedExp, Word};
use alloc::vec::Vec;
use subtle::{ConstantTimeEq, ConstantTimeLess};

Expand Down Expand Up @@ -103,7 +103,7 @@ fn pow_montgomery_form(
// Constant-time lookup in the array of powers
power.limbs.copy_from_slice(&powers[0].limbs);
for i in 1..(1 << WINDOW) {
power.conditional_assign(&powers[i as usize], i.ct_eq(&idx));
power.ct_assign(&powers[i as usize], i.ct_eq(&idx));
}

multiplier.mul_amm_assign(&mut z, &power);
Expand Down
49 changes: 47 additions & 2 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,51 @@ pub trait Bounded {
const BYTES: usize;
}

/// Trait for types which are conditionally selectable in constant time, similar to (and blanket impl'd for) `subtle`'s
/// [`ConditionallySelectable`] trait, but without the `Copy` bound which allows it to be impl'd for heap allocated
/// types such as `BoxedUint`.
///
/// It also provides generic implementations of conditional assignment and conditional swaps.
pub trait ConstantTimeSelect: Clone {
/// Select `a` or `b` according to `choice`.
///
/// # Returns
/// - `a` if `choice == Choice(0)`;
/// - `b` if `choice == Choice(1)`.
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self;

/// Conditionally assign `other` to `self`, according to `choice`.
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
*self = Self::ct_select(self, other, choice);
}

/// Conditionally swap `self` and `other` if `choice == 1`; otherwise, reassign both unto themselves.
#[inline]
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
let t: Self = a.clone();
a.ct_assign(b, choice);
b.ct_assign(&t, choice);
}
}

impl<T: ConditionallySelectable> ConstantTimeSelect for T {
#[inline(always)]
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self {
T::conditional_select(a, b, choice)
}

#[inline(always)]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.conditional_assign(other, choice)
}

#[inline(always)]
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
T::conditional_swap(a, b, choice)
}
}

/// Integer trait: represents common functionality of integer types provided by this crate.
pub trait Integer:
'static
Expand All @@ -55,10 +100,10 @@ pub trait Integer:
+ CheckedMul
+ CheckedDiv
+ Clone
// + ConditionallySelectable (see dalek-cryptography/subtle#94)
+ ConstantTimeEq
+ ConstantTimeGreater
+ ConstantTimeLess
+ ConstantTimeSelect
+ Debug
+ Default
+ Div<NonZero<Self>, Output = Self>
Expand Down Expand Up @@ -113,7 +158,7 @@ pub trait Integer:
fn bytes_precision(&self) -> usize;

/// Calculate the number of leading zeros in the binary representation of this number.
fn leading_zeros(&self) -> u32 {
fn leading_zeros(&self) -> u32 {
self.bits_precision() - self.bits()
}

Expand Down
152 changes: 12 additions & 140 deletions src/uint/boxed/ct.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
//! Constant-time helper functions.

use super::BoxedUint;
use crate::Limb;
use subtle::{Choice, ConditionallySelectable, CtOption};
use crate::{ConstantTimeSelect, Limb};
use subtle::{Choice, ConditionallySelectable};

impl BoxedUint {
/// Conditionally select `a` or `b` in constant time depending on [`Choice`].
///
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
/// this is an inherent function instead.
///
/// Panics if `a` and `b` don't have the same precision.
pub fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound
impl ConstantTimeSelect for BoxedUint {
#[inline]
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self {
debug_assert_eq!(a.bits_precision(), b.bits_precision());
let mut limbs = vec![Limb::ZERO; a.nlimbs()].into_boxed_slice();

Expand All @@ -22,161 +18,37 @@ impl BoxedUint {
Self { limbs }
}

/// Conditionally assign `other` to `self`, according to `choice`.
///
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
/// this is an inherent function instead.
///
/// Panics if `a` and `b` don't have the same precision.
#[inline]
pub fn conditional_assign(&mut self, other: &Self, choice: Choice) {
fn ct_assign(&mut self, other: &Self, choice: Choice) {
debug_assert_eq!(self.bits_precision(), other.bits_precision());

for i in 0..self.nlimbs() {
self.limbs[i].conditional_assign(&other.limbs[i], choice);
}
}

/// Conditionally swap `self` and `other` if `choice == 1`; otherwise,
/// reassign both unto themselves.
///
/// NOTE: can't impl `subtle`'s [`ConditionallySelectable`] trait due to its `Copy` bound, so
/// this is an inherent function instead.
///
/// Panics if `a` and `b` don't have the same precision.
#[inline]
pub fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) {
fn ct_swap(a: &mut Self, b: &mut Self, choice: Choice) {
debug_assert_eq!(a.bits_precision(), b.bits_precision());

for i in 0..a.nlimbs() {
Limb::conditional_swap(&mut a.limbs[i], &mut b.limbs[i], choice);
}
}

/// Conditional `map`: workaround which provides a [`CtOption::map`]-like API.
///
/// Ensures both functions are called regardless of whether the first returns some/none with an
/// argument whose precision matches `self`. Note this still requires branching on the
/// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can
/// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94).
///
/// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds.
pub fn conditional_map<C, F, T>(&self, condition: C, f: F) -> CtOption<T>
where
C: Fn(&Self) -> CtOption<Self>,
F: Fn(Self) -> T,
{
let conditional_val = condition(self);
let is_some = conditional_val.is_some();

let placeholder = Self::zero_with_precision(self.bits_precision());
let value = Option::<Self>::from(conditional_val).unwrap_or(placeholder);
debug_assert_eq!(self.bits_precision(), value.bits_precision());
CtOption::new(f(value), is_some)
}

/// Conditional `and_then`: workaround which provides a [`CtOption::and_then`]-like API.
///
/// Ensures both functions are called regardless of whether the first returns some/none with an
/// argument whose precision matches `self`. Note this still requires branching on the
/// intermediate [`CtOption`] value and therefore isn't fully constant time, but the best we can
/// do without upstream changes to `subtle` (see dalek-cryptography/subtle#94).
///
/// Workaround due to `Copy` in [`ConditionallySelectable`] supertrait bounds.
pub fn conditional_and_then<C, F>(&self, condition: C, f: F) -> CtOption<Self>
where
C: Fn(&Self) -> CtOption<Self>,
F: Fn(Self) -> CtOption<Self>,
{
let conditional_val = condition(self);
let mut is_some = conditional_val.is_some();

let placeholder = Self::zero_with_precision(self.bits_precision());
let value = Option::<Self>::from(conditional_val).unwrap_or(placeholder);
debug_assert_eq!(self.bits_precision(), value.bits_precision());

let conditional_val = f(value);
is_some &= conditional_val.is_some();

let placeholder = Self::zero_with_precision(self.bits_precision());
let value = Option::from(conditional_val).unwrap_or(placeholder);
debug_assert_eq!(self.bits_precision(), value.bits_precision());

CtOption::new(value, is_some)
}
}

#[cfg(test)]
mod tests {
use super::BoxedUint;
use subtle::{Choice, CtOption};
use crate::ConstantTimeSelect;
use subtle::Choice;

#[test]
fn conditional_select() {
let a = BoxedUint::zero_with_precision(128);
let b = BoxedUint::max(128);

assert_eq!(a, BoxedUint::conditional_select(&a, &b, Choice::from(0)));
assert_eq!(b, BoxedUint::conditional_select(&a, &b, Choice::from(1)));
}

#[test]
fn conditional_map_some() {
let n = BoxedUint::one();

let ret = n
.conditional_map(
|n| CtOption::new(n.clone(), 1u8.into()),
|n| n.wrapping_add(&BoxedUint::one()),
)
.unwrap();

assert_eq!(ret, BoxedUint::from(2u8));
assert_eq!(a, BoxedUint::ct_select(&a, &b, Choice::from(0)));
assert_eq!(b, BoxedUint::ct_select(&a, &b, Choice::from(1)));
}

#[test]
fn conditional_map_none() {
let n = BoxedUint::one();

let ret = n.conditional_map(
|n| CtOption::new(n.clone(), 0u8.into()),
|n| n.wrapping_add(&BoxedUint::one()),
);

assert!(bool::from(ret.is_none()));
}

#[test]
fn conditional_and_then_all_some() {
let n = BoxedUint::one();

let ret = n
.conditional_and_then(
|n| CtOption::new(n.clone(), 1u8.into()),
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), 1u8.into()),
)
.unwrap();

assert_eq!(ret, BoxedUint::from(2u8));
}

macro_rules! conditional_and_then_none_test {
($name:ident, $a:expr, $b:expr) => {
#[test]
fn $name() {
let n = BoxedUint::one();

let ret = n.conditional_and_then(
|n| CtOption::new(n.clone(), $a.into()),
|n| CtOption::new(n.wrapping_add(&BoxedUint::one()), $b.into()),
);

assert!(bool::from(ret.is_none()));
}
};
}

conditional_and_then_none_test!(conditional_and_then_none_some, 0, 1);
conditional_and_then_none_test!(conditional_and_then_some_none, 1, 0);
conditional_and_then_none_test!(conditional_and_then_none_none, 0, 0);
}
14 changes: 7 additions & 7 deletions src/uint/boxed/div.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! [`BoxedUint`] division operations.

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

Expand Down Expand Up @@ -42,7 +42,7 @@ impl BoxedUint {

loop {
let (r, borrow) = rem.sbb(&c, Limb::ZERO);
rem = Self::conditional_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO));
rem = Self::ct_select(&r, &rem, !borrow.ct_eq(&Limb::ZERO));
if bd == 0 {
break rem;
}
Expand Down Expand Up @@ -84,9 +84,9 @@ impl BoxedUint {

loop {
let (mut r, borrow) = rem.sbb(&c, Limb::ZERO);
rem.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
rem.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
r = quo.bitor(&Self::one());
quo.conditional_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
quo.ct_assign(&r, !(Choice::from((borrow.0 & 1) as u8) | done));
if i == 0 {
break;
}
Expand All @@ -95,7 +95,7 @@ impl BoxedUint {
// aren't modified further (but do the remaining iterations anyway to be constant-time)
done = i.ct_lt(&mb);
c.shr1_assign();
quo.conditional_assign(&quo.shl1(), !done);
quo.ct_assign(&quo.shl1(), !done);
}

(quo, rem)
Expand All @@ -117,9 +117,9 @@ impl BoxedUint {
loop {
let (mut r, borrow) = remainder.sbb(&c, Limb::ZERO);
let borrow = Choice::from(borrow.0 as u8 & 1);
remainder = Self::conditional_select(&r, &remainder, borrow);
remainder = Self::ct_select(&r, &remainder, borrow);
r = &quotient | Self::one();
quotient = Self::conditional_select(&r, &quotient, borrow);
quotient = Self::ct_select(&r, &quotient, borrow);
if bd == 0 {
break;
}
Expand Down
8 changes: 4 additions & 4 deletions src/uint/boxed/inv_mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! [`BoxedUint`] modular inverse (i.e. reciprocal) operations.

use crate::{BoxedUint, Integer};
use crate::{BoxedUint, ConstantTimeSelect, Integer};
use subtle::{Choice, ConstantTimeEq, ConstantTimeLess, CtOption};

impl BoxedUint {
Expand Down Expand Up @@ -56,7 +56,7 @@ impl BoxedUint {
let x_i = b.limbs[0].0 & 1;
let x_i_choice = Choice::from(x_i as u8);
// b_{i+1} = (b_i - a * X_i) / 2
b = Self::conditional_select(&b, &b.wrapping_sub(self), x_i_choice).shr1();
b = Self::ct_select(&b, &b.wrapping_sub(self), x_i_choice).shr1();

// Store the X_i bit in the result (x = x | (1 << X_i))
// Don't change the result in dummy iterations.
Expand Down Expand Up @@ -115,13 +115,13 @@ impl BoxedUint {
// Set `self -= b` if `self` is odd.
let swap = a.conditional_sbb_assign(&b, self_odd);
// Set `b += self` if `swap` is true.
b = Self::conditional_select(&b, &b.wrapping_add(&a), swap);
b = Self::ct_select(&b, &b.wrapping_add(&a), swap);
// Negate `self` if `swap` is true.
a = a.conditional_wrapping_neg(swap);

let mut new_u = u.clone();
let mut new_v = v.clone();
Self::conditional_swap(&mut new_u, &mut new_v, swap);
Self::ct_swap(&mut new_u, &mut new_v, swap);
let cy = new_u.conditional_sbb_assign(&new_v, self_odd);
let cyy = new_u.conditional_adc_assign(modulus, cy);
debug_assert!(bool::from(cy.ct_eq(&cyy)));
Expand Down
Loading

0 comments on commit e7bcff7

Please sign in to comment.