Skip to content

Commit

Permalink
Add ConstantTimeSelect and ConstantTimeClone traits
Browse files Browse the repository at this point in the history
`ConstantTimeSelect` is intended as a replacement to
`ConditionallySelectable`, which is preserved but deprecated. It
replaces the previous `Copy` bound with a bound on a new
`ConstantTimeClone` marker trait, which allows the trait to be impl'd
for heap-allocated types.

No existing impls of `ConditionallySelectable` have been removed,
however a blanket impl of `ConstantTimeSelect` for
`T: ConditionallySelectable` has been added, allowing the two traits to
interoperate and for `ConstantTimeSelect` to work on all types which
currently impl `ConditionallySelectable`.

`ConstantTimeClone` likewise has a blanket impl for all types which impl
`Copy`.

`CtOption`'s combinator methods have been changed to bound on
`ConstantTimeSelect` which unlocks using them with heap-allocated types,
which otherwise is a major limitation. In theory these changes are all
backwards compatible due to the blanket impl, which should allow all
types which previously worked to continue to do so.

Closes dalek-cryptography#63, dalek-cryptography#94
  • Loading branch information
tarcieri committed Nov 29, 2023
1 parent 6b6a81a commit 3a8e7ca
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 54 deletions.
151 changes: 125 additions & 26 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,11 @@ impl From<u8> for Choice {
}
}

/// Marker trait for types whose [`Clone`] impl operates in constant-time.
pub trait ConstantTimeClone: Clone {}

impl<T: Copy> ConstantTimeClone for T {}

/// An `Eq`-like trait that produces a `Choice` instead of a `bool`.
///
/// # Example
Expand Down Expand Up @@ -397,6 +402,89 @@ impl ConstantTimeEq for cmp::Ordering {
///
/// This trait also provides generic implementations of conditional
/// assignment and conditional swaps.
pub trait ConstantTimeSelect: ConstantTimeClone {
/// Select `a` or `b` according to `choice`.
///
/// # Returns
///
/// * `a` if `choice == Choice(0)`;
/// * `b` if `choice == Choice(1)`.
///
/// This function should execute in constant time.
///
/// # Example
///
/// ```
/// use subtle::ConstantTimeSelect;
/// #
/// # fn main() {
/// let x: u8 = 13;
/// let y: u8 = 42;
///
/// let z = u8::ct_select(&x, &y, 0.into());
/// assert_eq!(z, x);
/// let z = u8::ct_select(&x, &y, 1.into());
/// assert_eq!(z, y);
/// # }
/// ```
fn ct_select(a: &Self, b: &Self, choice: Choice) -> Self;

/// Conditionally assign `other` to `self`, according to `choice`.
///
/// This function should execute in constant time.
///
/// # Example
///
/// ```
/// use subtle::ConstantTimeSelect;
/// #
/// # fn main() {
/// let mut x: u8 = 13;
/// let mut y: u8 = 42;
///
/// x.ct_assign(&y, 0.into());
/// assert_eq!(x, 13);
/// x.ct_assign(&y, 1.into());
/// assert_eq!(x, 42);
/// # }
/// ```
#[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.
///
/// This function should execute in constant time.
///
/// # Example
///
/// ```
/// use subtle::ConstantTimeSelect;
/// #
/// # fn main() {
/// let mut x: u8 = 13;
/// let mut y: u8 = 42;
///
/// u8::ct_swap(&mut x, &mut y, 0.into());
/// assert_eq!(x, 13);
/// assert_eq!(y, 42);
/// u8::ct_swap(&mut x, &mut y, 1.into());
/// assert_eq!(x, 42);
/// assert_eq!(y, 13);
/// # }
/// ```
#[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);
}
}

/// Deprecated legacy equivalent of [`ConstantTimeSelect`]: please migrate to the new trait.
#[deprecated(since = "2.6.0", note = "use ConstantTimeSelect instead")]
pub trait ConditionallySelectable: Copy {
/// Select `a` or `b` according to `choice`.
///
Expand All @@ -422,7 +510,6 @@ pub trait ConditionallySelectable: Copy {
/// assert_eq!(z, y);
/// # }
/// ```
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self;

/// Conditionally assign `other` to `self`, according to `choice`.
Expand Down Expand Up @@ -479,6 +566,24 @@ pub trait ConditionallySelectable: Copy {
}
}

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

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

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

macro_rules! to_signed_int {
(u8) => {
i8
Expand Down Expand Up @@ -514,6 +619,7 @@ macro_rules! to_signed_int {

macro_rules! generate_integer_conditional_select {
($($t:tt)*) => ($(
#[allow(deprecated)]
impl ConditionallySelectable for $t {
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Expand Down Expand Up @@ -559,6 +665,7 @@ generate_integer_conditional_select!(u128 i128);
///
/// Given this, it's possible to operate on orderings as if they're integers,
/// which allows leveraging conditional masking for predication.
#[allow(deprecated)]
impl ConditionallySelectable for cmp::Ordering {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
let a = *a as i8;
Expand All @@ -571,6 +678,7 @@ impl ConditionallySelectable for cmp::Ordering {
}
}

#[allow(deprecated)]
impl ConditionallySelectable for Choice {
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
Expand All @@ -579,6 +687,7 @@ impl ConditionallySelectable for Choice {
}

#[cfg(feature = "const-generics")]
#[allow(deprecated)]
impl<T, const N: usize> ConditionallySelectable for [T; N]
where
T: ConditionallySelectable,
Expand Down Expand Up @@ -613,6 +722,7 @@ pub trait ConditionallyNegatable {
fn conditional_negate(&mut self, choice: Choice);
}

#[allow(deprecated)]
impl<T> ConditionallyNegatable for T
where
T: ConditionallySelectable,
Expand Down Expand Up @@ -710,9 +820,9 @@ impl<T> CtOption<T> {
#[inline]
pub fn unwrap_or(self, def: T) -> T
where
T: ConditionallySelectable,
T: ConstantTimeSelect,
{
T::conditional_select(&def, &self.value, self.is_some)
T::ct_select(&def, &self.value, self.is_some)
}

/// This returns the underlying value if it is `Some`
Expand All @@ -723,10 +833,10 @@ impl<T> CtOption<T> {
#[inline]
pub fn unwrap_or_else<F>(self, f: F) -> T
where
T: ConditionallySelectable,
T: ConstantTimeSelect,
F: FnOnce() -> T,
{
T::conditional_select(&f(), &self.value, self.is_some)
T::ct_select(&f(), &self.value, self.is_some)
}

/// Returns a true `Choice` if this value is `Some`.
Expand All @@ -752,17 +862,9 @@ impl<T> CtOption<T> {
#[inline]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
F: FnOnce(T) -> U,
{
CtOption::new(
f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
)),
self.is_some,
)
CtOption::new(f(self.value), self.is_some)
}

/// Returns a `None` value if the option is `None`, otherwise
Expand All @@ -775,34 +877,31 @@ impl<T> CtOption<T> {
#[inline]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
T: Default + ConditionallySelectable,
F: FnOnce(T) -> CtOption<U>,
{
let mut tmp = f(T::conditional_select(
&T::default(),
&self.value,
self.is_some,
));
tmp.is_some &= self.is_some;

tmp
let mut ret = f(self.value);
ret.is_some &= self.is_some;
ret
}

/// Returns `self` if it contains a value, and otherwise returns the result of
/// calling `f`. The provided function `f` is always called.
#[inline]
pub fn or_else<F>(self, f: F) -> CtOption<T>
where
T: ConditionallySelectable,
T: ConstantTimeSelect,
F: FnOnce() -> CtOption<T>,
{
let is_none = self.is_none();
let mut is_some = self.is_some();
let f = f();

Self::conditional_select(&self, &f, is_none)
let value = T::ct_select(&f.value, &self.value, is_some);
is_some |= f.is_some();
CtOption::new(value, is_some)
}
}

#[allow(deprecated)]
impl<T: ConditionallySelectable> ConditionallySelectable for CtOption<T> {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
CtOption::new(
Expand Down
56 changes: 28 additions & 28 deletions tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,78 +32,78 @@ fn slices_equal() {
}

#[test]
fn conditional_assign_i32() {
fn ct_assign_i32() {
let mut a: i32 = 5;
let b: i32 = 13;

a.conditional_assign(&b, 0.into());
a.ct_assign(&b, 0.into());
assert_eq!(a, 5);
a.conditional_assign(&b, 1.into());
a.ct_assign(&b, 1.into());
assert_eq!(a, 13);
}

#[test]
fn conditional_assign_i64() {
fn ct_assign_i64() {
let mut c: i64 = 2343249123;
let d: i64 = 8723884895;

c.conditional_assign(&d, 0.into());
c.ct_assign(&d, 0.into());
assert_eq!(c, 2343249123);
c.conditional_assign(&d, 1.into());
c.ct_assign(&d, 1.into());
assert_eq!(c, 8723884895);
}

macro_rules! generate_integer_conditional_select_tests {
macro_rules! generate_integer_ct_select_tests {
($($t:ty)*) => ($(
let x: $t = 0; // all 0 bits
let y: $t = !0; // all 1 bits

assert_eq!(<$t>::conditional_select(&x, &y, 0.into()), 0);
assert_eq!(<$t>::conditional_select(&x, &y, 1.into()), y);
assert_eq!(<$t>::ct_select(&x, &y, 0.into()), 0);
assert_eq!(<$t>::ct_select(&x, &y, 1.into()), y);

let mut z = x;
let mut w = y;

<$t>::conditional_swap(&mut z, &mut w, 0.into());
<$t>::ct_swap(&mut z, &mut w, 0.into());
assert_eq!(z, x);
assert_eq!(w, y);
<$t>::conditional_swap(&mut z, &mut w, 1.into());
<$t>::ct_swap(&mut z, &mut w, 1.into());
assert_eq!(z, y);
assert_eq!(w, x);

z.conditional_assign(&x, 1.into());
w.conditional_assign(&y, 0.into());
z.ct_assign(&x, 1.into());
w.ct_assign(&y, 0.into());
assert_eq!(z, x);
assert_eq!(w, x);
)*)
}

#[test]
fn integer_conditional_select() {
generate_integer_conditional_select_tests!(u8 u16 u32 u64);
generate_integer_conditional_select_tests!(i8 i16 i32 i64);
fn integer_ct_select() {
generate_integer_ct_select_tests!(u8 u16 u32 u64);
generate_integer_ct_select_tests!(i8 i16 i32 i64);
#[cfg(feature = "i128")]
generate_integer_conditional_select_tests!(i128 u128);
generate_integer_ct_select_tests!(i128 u128);
}

#[test]
fn custom_conditional_select_i16() {
fn custom_ct_select_i16() {
let x: i16 = 257;
let y: i16 = 514;

assert_eq!(i16::conditional_select(&x, &y, 0.into()), 257);
assert_eq!(i16::conditional_select(&x, &y, 1.into()), 514);
assert_eq!(i16::ct_select(&x, &y, 0.into()), 257);
assert_eq!(i16::ct_select(&x, &y, 1.into()), 514);
}

#[test]
fn ordering_conditional_select() {
fn ordering_ct_select() {
assert_eq!(
cmp::Ordering::conditional_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 0.into()),
cmp::Ordering::ct_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 0.into()),
cmp::Ordering::Less
);

assert_eq!(
cmp::Ordering::conditional_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 1.into()),
cmp::Ordering::ct_select(&cmp::Ordering::Less, &cmp::Ordering::Greater, 1.into()),
cmp::Ordering::Greater
);
}
Expand Down Expand Up @@ -143,14 +143,14 @@ fn choice_into_bool() {
}

#[test]
fn conditional_select_choice() {
fn ct_select_choice() {
let t = Choice::from(1);
let f = Choice::from(0);

assert_eq!(bool::from(Choice::conditional_select(&t, &f, f)), true);
assert_eq!(bool::from(Choice::conditional_select(&t, &f, t)), false);
assert_eq!(bool::from(Choice::conditional_select(&f, &t, f)), false);
assert_eq!(bool::from(Choice::conditional_select(&f, &t, t)), true);
assert_eq!(bool::from(Choice::ct_select(&t, &f, f)), true);
assert_eq!(bool::from(Choice::ct_select(&t, &f, t)), false);
assert_eq!(bool::from(Choice::ct_select(&f, &t, f)), false);
assert_eq!(bool::from(Choice::ct_select(&f, &t, t)), true);
}

#[test]
Expand Down

0 comments on commit 3a8e7ca

Please sign in to comment.