From 3a8e7ca84abf78507456dbabd0a091ccb137d2fa Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Wed, 29 Nov 2023 14:29:48 -0700 Subject: [PATCH] Add `ConstantTimeSelect` and `ConstantTimeClone` traits `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 #63, #94 --- src/lib.rs | 151 ++++++++++++++++++++++++++++++++++++++++++--------- tests/mod.rs | 56 +++++++++---------- 2 files changed, 153 insertions(+), 54 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 795eade..11a5905 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -258,6 +258,11 @@ impl From for Choice { } } +/// Marker trait for types whose [`Clone`] impl operates in constant-time. +pub trait ConstantTimeClone: Clone {} + +impl ConstantTimeClone for T {} + /// An `Eq`-like trait that produces a `Choice` instead of a `bool`. /// /// # Example @@ -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`. /// @@ -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`. @@ -479,6 +566,24 @@ pub trait ConditionallySelectable: Copy { } } +#[allow(deprecated)] +impl 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 @@ -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 { @@ -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; @@ -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 { @@ -579,6 +687,7 @@ impl ConditionallySelectable for Choice { } #[cfg(feature = "const-generics")] +#[allow(deprecated)] impl ConditionallySelectable for [T; N] where T: ConditionallySelectable, @@ -613,6 +722,7 @@ pub trait ConditionallyNegatable { fn conditional_negate(&mut self, choice: Choice); } +#[allow(deprecated)] impl ConditionallyNegatable for T where T: ConditionallySelectable, @@ -710,9 +820,9 @@ impl CtOption { #[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` @@ -723,10 +833,10 @@ impl CtOption { #[inline] pub fn unwrap_or_else(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`. @@ -752,17 +862,9 @@ impl CtOption { #[inline] pub fn map(self, f: F) -> CtOption 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 @@ -775,17 +877,11 @@ impl CtOption { #[inline] pub fn and_then(self, f: F) -> CtOption where - T: Default + ConditionallySelectable, F: FnOnce(T) -> CtOption, { - 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 @@ -793,16 +889,19 @@ impl CtOption { #[inline] pub fn or_else(self, f: F) -> CtOption where - T: ConditionallySelectable, + T: ConstantTimeSelect, F: FnOnce() -> CtOption, { - 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 ConditionallySelectable for CtOption { fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { CtOption::new( diff --git a/tests/mod.rs b/tests/mod.rs index f6b3982..dd71eab 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -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 ); } @@ -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]