From 18241c208f91f4cae8bbaa9ac46a1fa473aa0266 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sat, 3 Aug 2024 16:09:42 -0600 Subject: [PATCH] [WIP] subtle: breaking changes for a hypothetical v3.0 This commit contains a minimal set of proposed breaking changes, as an alternative to trying to make additive changes which may be "subtly" breaking: - Add `ConstantTimeClone` trait and use it as `ConditionallySelectable`'s supertrait bound (alternative to #118): this makes it possible to implement `ConditionallySelectable` on heap-allocated types (fixes #94) - Remove `Default` bound on `CtOption::map` and `CtOption::and_then`: allows using these methods with types that don't impl `Default` and avoids potential timing sidechannels if there might be timing variability in `Default` types (fixes #63) --- Cargo.toml | 2 +- src/lib.rs | 53 +++++++++++++++++++++++++---------------------------- 2 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ae7361c..74582b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ name = "subtle" # - update html_root_url # - update README if necessary by semver # - if any updates were made to the README, also update the module documentation in src/lib.rs -version = "2.6.0" +version = "3.0.0-pre" edition = "2018" authors = ["Isis Lovecruft ", "Henry de Valence "] diff --git a/src/lib.rs b/src/lib.rs index 9fc143b..206057a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,7 @@ impl Choice { /// /// This function only exists as an **escape hatch** for the rare case /// where it's not possible to use one of the `subtle`-provided + /// where it's not possible to use one of the `subtle`-provided /// trait impls. /// /// **To convert a `Choice` to a `bool`, use the `From` implementation instead.** @@ -383,6 +384,11 @@ impl ConstantTimeEq for cmp::Ordering { } } +/// Marker trait for types whose [`Clone`] impl operates in constant-time. +pub trait ConstantTimeClone: Clone {} + +impl ConstantTimeClone for T {} + /// A type which can be conditionally selected in constant time. /// /// This trait also provides generic implementations of conditional @@ -390,7 +396,7 @@ impl ConstantTimeEq for cmp::Ordering { // // #[inline] is specified on these function prototypes to signify that they #[allow(unused_attributes)] // should be in the actual implementation -pub trait ConditionallySelectable: Copy { +pub trait ConditionallySelectable: ConstantTimeClone { /// Select `a` or `b` according to `choice`. /// /// # Returns @@ -467,7 +473,7 @@ pub trait ConditionallySelectable: Copy { /// ``` #[inline] fn conditional_swap(a: &mut Self, b: &mut Self, choice: Choice) { - let t: Self = *a; + let t: Self = a.clone(); a.conditional_assign(&b, choice); b.conditional_assign(&t, choice); } @@ -575,7 +581,7 @@ impl ConditionallySelectable for Choice { #[cfg(feature = "const-generics")] impl ConditionallySelectable for [T; N] where - T: ConditionallySelectable, + T: ConditionallySelectable + Copy, { #[inline] fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { @@ -741,49 +747,34 @@ impl CtOption { /// Returns a `None` value if the option is `None`, otherwise /// returns a `CtOption` enclosing the value of the provided closure. - /// The closure is given the enclosed value or, if the option is - /// `None`, it is provided a dummy value computed using - /// `Default::default()`. + /// The closure is given the enclosed value. /// /// This operates in constant time, because the provided closure /// is always called. #[inline] pub fn map(self, f: F) -> CtOption where - T: Default + ConditionallySelectable, + T: 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 /// returns the result of the provided closure. The closure is - /// given the enclosed value or, if the option is `None`, it - /// is provided a dummy value computed using `Default::default()`. + /// given the enclosed value. /// /// This operates in constant time, because the provided closure /// is always called. #[inline] pub fn and_then(self, f: F) -> CtOption where - T: Default + ConditionallySelectable, + T: 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 @@ -797,7 +788,10 @@ impl CtOption { let is_none = self.is_none(); let f = f(); - Self::conditional_select(&self, &f, is_none) + CtOption::new( + T::conditional_select(&self.value, &f.value, is_none), + Choice::conditional_select(&self.is_some, &f.is_some, is_none), + ) } /// Convert the `CtOption` wrapper into an `Option`, depending on whether @@ -817,7 +811,10 @@ impl CtOption { } } -impl ConditionallySelectable for CtOption { +impl ConditionallySelectable for CtOption +where + T: ConditionallySelectable + Copy +{ fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { CtOption::new( T::conditional_select(&a.value, &b.value, choice),