From a9d61c25ec14d2822a1f16bc4516b7dcce09cf1c Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:33:41 -0400 Subject: [PATCH 01/10] fix compiler warnings about unused #[inline] --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 143212b..9906ee6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ // -*- mode: rust; -*- // // This file is part of subtle, part of the dalek cryptography project. -// Copyright (c) 2016-2018 isis lovecruft, Henry de Valence +// Copyright (c) 2016-2022 isis lovecruft, Henry de Valence // See LICENSE for licensing information. // // Authors: From 8dc0d042bc664142c4c27c511ac6940bb4b136fd Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:34:11 -0400 Subject: [PATCH 02/10] spelling fix --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9906ee6..aae0349 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -703,7 +703,7 @@ macro_rules! generate_unsigned_integer_greater { /// /// # Note /// - /// This algoritm would also work for signed integers if we first + /// This algorithm would also work for signed integers if we first /// flip the top bit, e.g. `let x: u8 = x ^ 0x80`, etc. #[inline] fn ct_gt(&self, other: &$t_u) -> Choice { From 1955e495584c0e3285742389ea44c71da4c31fe8 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:34:28 -0400 Subject: [PATCH 03/10] rustfmt change --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index aae0349..c451310 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,7 +13,6 @@ #![deny(missing_docs)] #![doc(html_root_url = "https://docs.rs/subtle-ng/2.5.0")] - #[cfg(feature = "std")] #[macro_use] extern crate std; @@ -728,7 +727,7 @@ macro_rules! generate_unsigned_integer_greater { Choice::from((bit & 1) as u8) } } - } + }; } generate_unsigned_integer_greater!(u8, 8); From 61ac90df7b6e6538a5ecbc33ee7a646ce4ea3f07 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:35:28 -0400 Subject: [PATCH 04/10] remove trait bounds on the ConstantTimeLess trait declaration itself --- src/lib.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c451310..f4393b5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -739,7 +739,7 @@ generate_unsigned_integer_greater!(u128, 128); /// A type which can be compared in some manner and be determined to be less /// than another of the same type. -pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater { +pub trait ConstantTimeLess { /// Determine whether `self < other`. /// /// The bitwise-NOT of the return value of this function should be usable to @@ -775,15 +775,16 @@ pub trait ConstantTimeLess: ConstantTimeEq + ConstantTimeGreater { /// /// assert_eq!(x_lt_x.unwrap_u8(), 0); /// ``` + fn ct_lt(&self, other: &Self) -> Choice; +} + +impl ConstantTimeLess for T { + /// Default implementation for whether `self < other`. + /// + /// This function should execute in constant time. #[inline] fn ct_lt(&self, other: &Self) -> Choice { !self.ct_gt(other) & !self.ct_eq(other) } } -impl ConstantTimeLess for u8 {} -impl ConstantTimeLess for u16 {} -impl ConstantTimeLess for u32 {} -impl ConstantTimeLess for u64 {} -#[cfg(feature = "i128")] -impl ConstantTimeLess for u128 {} From eb31e200eee76756c1db3398a6b4fbd392dfbb6e Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 00:36:07 -0400 Subject: [PATCH 05/10] implement ConstantTime{Less,Greater} with shortlex too --- src/lib.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4393b5..9c55d4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -184,7 +184,12 @@ impl From for Choice { pub trait ConstantTimeEq { /// Determine if two items are equal. /// - /// The `ct_eq` function should execute in constant time. + /// The `ct_eq` function should execute in constant time. When collections of elements which may + /// have different lengths must be compared, a strategy such as [shortlex] may be used to avoid + /// leaking timing info based on the contents of the collection. This is how + /// `ConstantTimeEq` is implemented for slices. + /// + /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order /// /// # Returns /// @@ -198,9 +203,12 @@ impl ConstantTimeEq for [T] { /// /// # Note /// - /// This function short-circuits if the lengths of the input slices - /// are different. Otherwise, it should execute in time independent - /// of the slice contents. + /// This function short-circuits if the lengths of the input slices are different. Otherwise, + /// it should execute in time independent of the slice contents. When the slice lengths differ, + /// this implementation applies the [shortlex] ordering scheme, which sorts shorter slices + /// before longer slices without checking the contents. + /// + /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order /// /// Since arrays coerce to slices, this function works with fixed-size arrays: /// @@ -665,7 +673,12 @@ pub trait ConstantTimeGreater { /// The bitwise-NOT of the return value of this function should be usable to /// determine if `self <= other`. /// - /// This function should execute in constant time. + /// This function should execute in constant time. When collections of elements which may have + /// different lengths must be compared, a strategy such as [shortlex] may be used to avoid + /// leaking timing info based on the contents of the collection. This is how + /// `ConstantTimeGreater` is implemented for slices. + /// + /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order /// /// # Returns /// @@ -737,6 +750,59 @@ generate_unsigned_integer_greater!(u64, 64); #[cfg(feature = "i128")] generate_unsigned_integer_greater!(u128, 128); +impl ConstantTimeGreater for [T] { + /// Compare whether one slice of `ConstantTimeGreater` types is greater than another. + /// + /// # Note + /// + /// This function short-circuits if the lengths of the input slices are different. Otherwise, + /// it should execute in time independent of the slice contents. When the slice lengths differ, + /// this implementation applies the [shortlex] ordering scheme, which sorts shorter slices + /// before longer slices without checking the contents. + /// + /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order + /// + /// Since arrays coerce to slices, this function also works with fixed-size arrays: + /// + /// ``` + /// # use subtle_ng::ConstantTimeGreater; + /// # + /// let a: [u8; 8] = [0,1,2,3,4,5,6,7]; + /// let b: [u8; 8] = [0,1,2,3,0,1,2,3]; + /// + /// let a_gt_a = a.ct_gt(&a); + /// let a_gt_b = a.ct_gt(&b); + /// + /// assert_eq!(a_gt_a.unwrap_u8(), 0); + /// assert_eq!(a_gt_b.unwrap_u8(), 1); + /// ``` + #[inline] + fn ct_gt(&self, _rhs: &[T]) -> Choice { + let len = self.len(); + + // Short-circuit on the *lengths* of the slices, not their + // contents. + if len < _rhs.len() { + return Choice::from(0); + } + if len > _rhs.len() { + return Choice::from(1); + } + + // This loop shouldn't be shortcircuitable, since the compiler + // shouldn't be able to reason about the value of the `u8` + // unwrapped from the `ct_eq` and `ct_gt` results. + let mut still_at_least_eq: u8 = 1u8; + let mut was_gt: u8 = 0u8; + for (ai, bi) in self.iter().zip(_rhs.iter()) { + was_gt |= still_at_least_eq & ai.ct_gt(bi).unwrap_u8(); + still_at_least_eq &= ai.ct_eq(bi).unwrap_u8(); + } + + was_gt.into() + } +} + /// A type which can be compared in some manner and be determined to be less /// than another of the same type. pub trait ConstantTimeLess { @@ -748,7 +814,12 @@ pub trait ConstantTimeLess { /// A default implementation is provided and implemented for the unsigned /// integer types. /// - /// This function should execute in constant time. + /// This function should execute in constant time. When collections of elements which may have + /// different lengths must be compared, a strategy such as [shortlex] may be used to avoid + /// leaking timing info based on the contents of the collection. This is how `ConstantTimeLess` + /// is implemented for slices. + /// + /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order /// /// # Returns /// @@ -787,4 +858,3 @@ impl ConstantTimeLess for T { !self.ct_gt(other) & !self.ct_eq(other) } } - From 3cbf4801803c8f88476c727fadb504bc7a675636 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 04:07:38 -0400 Subject: [PATCH 06/10] improve comments on ct_gt impl --- src/lib.rs | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9c55d4f..b95fe62 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ extern crate std; #[cfg(test)] extern crate rand; +use core::cmp::Ordering; use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not}; use core::option::Option; @@ -780,21 +781,30 @@ impl ConstantTimeGreater for [T] { fn ct_gt(&self, _rhs: &[T]) -> Choice { let len = self.len(); - // Short-circuit on the *lengths* of the slices, not their - // contents. - if len < _rhs.len() { - return Choice::from(0); - } - if len > _rhs.len() { - return Choice::from(1); + // Short-circuit on the *lengths* of the slices, not their contents. Here we apply shortlex + // ordering, sorting shorter slices before longer ones. + match len.cmp(&_rhs.len()) { + Ordering::Equal => (), + Ordering::Less => { + return Choice::from(0); + } + Ordering::Greater => { + return Choice::from(1); + } } + // Whether all the pairs of elements from both slices have been equal for all + // previous iterations. + let mut still_at_least_eq: u8 = 1u8; + // Whether `self` is indeed greater than `other`. + let mut was_gt: u8 = 0u8; // This loop shouldn't be shortcircuitable, since the compiler // shouldn't be able to reason about the value of the `u8` // unwrapped from the `ct_eq` and `ct_gt` results. - let mut still_at_least_eq: u8 = 1u8; - let mut was_gt: u8 = 0u8; for (ai, bi) in self.iter().zip(_rhs.iter()) { + // If all the elements have been the same so far, check whether the next one is + // greater. If so, then `self` is indeed greater, and using |= will ensure if `was_gt` + // was ever set to 1, that it remains 1 after iterating through the rest. was_gt |= still_at_least_eq & ai.ct_gt(bi).unwrap_u8(); still_at_least_eq &= ai.ct_eq(bi).unwrap_u8(); } From a8c6eaf0f5e03bf3bb688f767c7a5ca2c3bd137a Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Thu, 30 Jun 2022 04:32:55 -0400 Subject: [PATCH 07/10] remove extraneous mentions of shortlex --- src/lib.rs | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b95fe62..e41ee8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,12 +185,7 @@ impl From for Choice { pub trait ConstantTimeEq { /// Determine if two items are equal. /// - /// The `ct_eq` function should execute in constant time. When collections of elements which may - /// have different lengths must be compared, a strategy such as [shortlex] may be used to avoid - /// leaking timing info based on the contents of the collection. This is how - /// `ConstantTimeEq` is implemented for slices. - /// - /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order + /// The `ct_eq` function should execute in constant time. /// /// # Returns /// @@ -674,12 +669,7 @@ pub trait ConstantTimeGreater { /// The bitwise-NOT of the return value of this function should be usable to /// determine if `self <= other`. /// - /// This function should execute in constant time. When collections of elements which may have - /// different lengths must be compared, a strategy such as [shortlex] may be used to avoid - /// leaking timing info based on the contents of the collection. This is how - /// `ConstantTimeGreater` is implemented for slices. - /// - /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order + /// This function should execute in constant time. /// /// # Returns /// @@ -815,21 +805,13 @@ impl ConstantTimeGreater for [T] { /// A type which can be compared in some manner and be determined to be less /// than another of the same type. +/// +/// This trait is automatically implemented for implementors of +/// `ConstantTimeGreater+ConstantTimeEq`. pub trait ConstantTimeLess { /// Determine whether `self < other`. /// - /// The bitwise-NOT of the return value of this function should be usable to - /// determine if `self >= other`. - /// - /// A default implementation is provided and implemented for the unsigned - /// integer types. - /// - /// This function should execute in constant time. When collections of elements which may have - /// different lengths must be compared, a strategy such as [shortlex] may be used to avoid - /// leaking timing info based on the contents of the collection. This is how `ConstantTimeLess` - /// is implemented for slices. - /// - /// [shortlex]: https://en.wikipedia.org/wiki/Shortlex_order + /// This function should execute in constant time. /// /// # Returns /// From bbdf887a5d0c15a63bceef78898d3e7fda0c56e9 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 1 Jul 2022 03:34:55 -0400 Subject: [PATCH 08/10] iterated operations --- src/lib.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 163 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e41ee8a..8ae2be6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -170,6 +170,39 @@ impl From for Choice { } } +/// A method to extend constant-time comparisons to abstract data types with multiple parts to +/// iterate over. +pub trait IteratedOperation { + /// Initialize any state retained across iterations. + fn initiate() -> Self; + /// Parse the state to determine whether the operation succeeded after iteration. + fn extract_result(self) -> Choice; +} + +/// Implementing this trait automatically implements [`ConstantTimeEq`] and/or +/// [`ConstantTimeGreater`], depending on [`Self::To`]. +/// +///``` +/// use subtle_ng::{ConstantTimeEq, ConstantTimeGreater, Convertible}; +/// +/// struct S(pub u8); +/// impl Convertible for S { +/// type To = u8; +/// fn for_constant_operation(&self) -> u8 { self.0 } +/// } +/// +/// assert_eq!(0, S(0).ct_eq(&S(1)).unwrap_u8()); +/// assert_eq!(1, S(1).ct_eq(&S(1)).unwrap_u8()); +/// assert_eq!(1, S(1).ct_gt(&S(0)).unwrap_u8()); +/// assert_eq!(0, S(1).ct_gt(&S(1)).unwrap_u8()); +///``` +pub trait Convertible { + /// The type to convert to. + type To; + /// Convert to a constant-time comparable object. + fn for_constant_operation(&self) -> Self::To; +} + /// An `Eq`-like trait that produces a `Choice` instead of a `bool`. /// /// # Example @@ -194,6 +227,56 @@ pub trait ConstantTimeEq { fn ct_eq(&self, other: &Self) -> Choice; } +/// Get the conjunction of [`ConstantTimeEq::ct_eq`] over multiple possibly-heterogenous pairs +/// of elements. +/// +///``` +/// use subtle_ng::{Choice, ConstantTimeEq, IteratedEq, IteratedOperation}; +/// +/// struct S { pub len: usize, pub live: bool }; +/// impl ConstantTimeEq for S { +/// fn ct_eq(&self, other: &Self) -> Choice { +/// let mut x = IteratedEq::initiate(); +/// x.apply_eq(&self.len, &other.len); +/// x.apply_eq(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// +/// let s1 = S { len: 2, live: true }; +/// let s2 = S { len: 3, live: true }; +/// assert_eq!(0, s1.ct_eq(&s2).unwrap_u8()); +/// assert_eq!(1, s1.ct_eq(&s1).unwrap_u8()); +/// assert_eq!(1, s2.ct_eq(&s2).unwrap_u8()); +///``` +pub struct IteratedEq { + still_equal: u8, +} + +impl IteratedOperation for IteratedEq { + fn initiate() -> Self { + Self { still_equal: 1u8 } + } + fn extract_result(self) -> Choice { + self.still_equal.into() + } +} + +impl IteratedEq { + /// Unconditionally AND internal state with the result of an "equals" comparison. + pub fn apply_eq(&mut self, a: &T, b: &T) { + self.still_equal &= a.ct_eq(b).unwrap_u8(); + } +} + +impl> ConstantTimeEq for C { + fn ct_eq(&self, other: &Self) -> Choice { + let a: T = self.for_constant_operation(); + let b: T = other.for_constant_operation(); + a.ct_eq(&b) + } +} + impl ConstantTimeEq for [T] { /// Check whether two slices of `ConstantTimeEq` types are equal. /// @@ -233,12 +316,12 @@ impl ConstantTimeEq for [T] { // This loop shouldn't be shortcircuitable, since the compiler // shouldn't be able to reason about the value of the `u8` // unwrapped from the `ct_eq` result. - let mut x = 1u8; + let mut x = IteratedEq::initiate(); for (ai, bi) in self.iter().zip(_rhs.iter()) { - x &= ai.ct_eq(bi).unwrap_u8(); + x.apply_eq(ai, bi); } - x.into() + x.extract_result() } } @@ -699,6 +782,78 @@ pub trait ConstantTimeGreater { fn ct_gt(&self, other: &Self) -> Choice; } +/// Get the result of applying [`ConstantTimeGreater::ct_gt`] over multiple possibly-heterogenous +/// pairs of elements. The "greater than" comparison assumes that the order of these pairs +/// is lexicographic. +/// +///``` +/// use subtle_ng::{ +/// Choice, IteratedOperation, ConstantTimeEq, IteratedEq, ConstantTimeGreater, IteratedGreater, +/// }; +/// +/// struct S { pub len: usize, pub live: bool }; +/// impl ConstantTimeEq for S { +/// fn ct_eq(&self, other: &Self) -> Choice { +/// let mut x = IteratedEq::initiate(); +/// x.apply_eq(&self.len, &other.len); +/// x.apply_eq(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// impl ConstantTimeGreater for S { +/// fn ct_gt(&self, other: &Self) -> Choice { +/// let mut x = IteratedGreater::initiate(); +/// x.apply_gt(&(self.len as u64), &(other.len as u64)); +/// x.apply_gt(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// +/// let s1 = S { len: 2, live: true }; +/// let s2 = S { len: 3, live: false }; +/// let s3 = S { len: 3, live: true }; +/// assert_eq!(0, s1.ct_eq(&s2).unwrap_u8()); +/// assert_eq!(1, s1.ct_eq(&s1).unwrap_u8()); +/// assert_eq!(1, s2.ct_gt(&s1).unwrap_u8()); +/// assert_eq!(1, s3.ct_gt(&s2).unwrap_u8()); +///``` +pub struct IteratedGreater { + was_gt: u8, + still_equal: u8, +} + +impl IteratedOperation for IteratedGreater { + fn initiate() -> Self { + Self { + was_gt: 0u8, + still_equal: 1u8, + } + } + fn extract_result(self) -> Choice { + self.was_gt.into() + } +} + +impl IteratedGreater { + /// Unconditionally modify internal state with result of "equals" and "greater" comparisons. + pub fn apply_gt(&mut self, a: &T, b: &T) { + let Self { + was_gt, + still_equal, + } = self; + *was_gt |= *still_equal & a.ct_gt(&b).unwrap_u8(); + *still_equal &= a.ct_eq(&b).unwrap_u8(); + } +} + +impl> ConstantTimeGreater for C { + fn ct_gt(&self, other: &Self) -> Choice { + let a: T = self.for_constant_operation(); + let b: T = other.for_constant_operation(); + a.ct_gt(&b) + } +} + macro_rules! generate_unsigned_integer_greater { ($t_u: ty, $bit_width: expr) => { impl ConstantTimeGreater for $t_u { @@ -785,21 +940,18 @@ impl ConstantTimeGreater for [T] { // Whether all the pairs of elements from both slices have been equal for all // previous iterations. - let mut still_at_least_eq: u8 = 1u8; + // let mut still_at_least_eq: u8 = 1u8; // Whether `self` is indeed greater than `other`. - let mut was_gt: u8 = 0u8; + // let mut was_gt: u8 = 0u8; // This loop shouldn't be shortcircuitable, since the compiler // shouldn't be able to reason about the value of the `u8` // unwrapped from the `ct_eq` and `ct_gt` results. + let mut x = IteratedGreater::initiate(); for (ai, bi) in self.iter().zip(_rhs.iter()) { - // If all the elements have been the same so far, check whether the next one is - // greater. If so, then `self` is indeed greater, and using |= will ensure if `was_gt` - // was ever set to 1, that it remains 1 after iterating through the rest. - was_gt |= still_at_least_eq & ai.ct_gt(bi).unwrap_u8(); - still_at_least_eq &= ai.ct_eq(bi).unwrap_u8(); + x.apply_gt(ai, bi); } - was_gt.into() + x.extract_result() } } From e135b4c20966bb2a8634dcbece8f555f59f33874 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 1 Jul 2022 06:15:22 -0400 Subject: [PATCH 09/10] revert ConstantTimeLess back to using the default trait method --- src/lib.rs | 107 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8ae2be6..e739955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -819,14 +819,14 @@ pub trait ConstantTimeGreater { ///``` pub struct IteratedGreater { was_gt: u8, - still_equal: u8, + was_lt: u8, } impl IteratedOperation for IteratedGreater { fn initiate() -> Self { Self { was_gt: 0u8, - still_equal: 1u8, + was_lt: 0u8, } } fn extract_result(self) -> Choice { @@ -835,18 +835,15 @@ impl IteratedOperation for IteratedGreater { } impl IteratedGreater { - /// Unconditionally modify internal state with result of "equals" and "greater" comparisons. - pub fn apply_gt(&mut self, a: &T, b: &T) { - let Self { - was_gt, - still_equal, - } = self; - *was_gt |= *still_equal & a.ct_gt(&b).unwrap_u8(); - *still_equal &= a.ct_eq(&b).unwrap_u8(); + /// Unconditionally modify internal state with result of two directed "greater" comparisons. + pub fn apply_gt(&mut self, a: &T, b: &T) { + let Self { was_gt, was_lt } = self; + *was_gt |= (!*was_lt) & a.ct_gt(&b).unwrap_u8(); + *was_lt |= b.ct_gt(&a).unwrap_u8(); } } -impl> ConstantTimeGreater for C { +impl> ConstantTimeGreater for C { fn ct_gt(&self, other: &Self) -> Choice { let a: T = self.for_constant_operation(); let b: T = other.for_constant_operation(); @@ -886,6 +883,8 @@ macro_rules! generate_unsigned_integer_greater { Choice::from((bit & 1) as u8) } } + + impl ConstantTimeLess for $t_u {} }; } @@ -960,7 +959,7 @@ impl ConstantTimeGreater for [T] { /// /// This trait is automatically implemented for implementors of /// `ConstantTimeGreater+ConstantTimeEq`. -pub trait ConstantTimeLess { +pub trait ConstantTimeLess: ConstantTimeGreater { /// Determine whether `self < other`. /// /// This function should execute in constant time. @@ -990,15 +989,85 @@ pub trait ConstantTimeLess { /// /// assert_eq!(x_lt_x.unwrap_u8(), 0); /// ``` - fn ct_lt(&self, other: &Self) -> Choice; + fn ct_lt(&self, other: &Self) -> Choice { + other.ct_gt(self) + } } -impl ConstantTimeLess for T { - /// Default implementation for whether `self < other`. - /// - /// This function should execute in constant time. - #[inline] +/// Get the result of applying [`ConstantTimeGreater::ct_gt`] over multiple possibly-heterogenous +/// pairs of elements. The "greater than" comparison assumes that the order of these pairs +/// is lexicographic. +/// +///``` +/// use subtle_ng::{ +/// Choice, IteratedOperation, ConstantTimeEq, IteratedEq, ConstantTimeGreater, IteratedGreater, +/// ConstantTimeLess, IteratedLess, +/// }; +/// +/// struct S { pub len: usize, pub live: bool }; +/// impl ConstantTimeEq for S { +/// fn ct_eq(&self, other: &Self) -> Choice { +/// let mut x = IteratedEq::initiate(); +/// x.apply_eq(&self.len, &other.len); +/// x.apply_eq(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// impl ConstantTimeGreater for S { +/// fn ct_gt(&self, other: &Self) -> Choice { +/// let mut x = IteratedGreater::initiate(); +/// x.apply_gt(&(self.len as u64), &(other.len as u64)); +/// x.apply_gt(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// impl ConstantTimeLess for S { +/// fn ct_lt(&self, other: &Self) -> Choice { +/// let mut x = IteratedLess::initiate(); +/// x.apply_lt(&(self.len as u64), &(other.len as u64)); +/// x.apply_lt(&(self.live as u8), &(other.live as u8)); +/// x.extract_result() +/// } +/// } +/// +/// let s1 = S { len: 2, live: true }; +/// let s2 = S { len: 3, live: false }; +/// let s3 = S { len: 3, live: true }; +/// assert_eq!(0, s1.ct_eq(&s2).unwrap_u8()); +/// assert_eq!(1, s1.ct_eq(&s1).unwrap_u8()); +/// assert_eq!(1, s2.ct_gt(&s1).unwrap_u8()); +/// assert_eq!(1, s2.ct_lt(&s3).unwrap_u8()); +///``` +pub struct IteratedLess { + was_lt: u8, + was_gt: u8, +} + +impl IteratedOperation for IteratedLess { + fn initiate() -> Self { + Self { + was_lt: 0u8, + was_gt: 0u8, + } + } + fn extract_result(self) -> Choice { + self.was_lt.into() + } +} + +impl IteratedLess { + /// Unconditionally modify internal state with result of two directed "less" comparisons. + pub fn apply_lt(&mut self, a: &T, b: &T) { + let Self { was_lt, was_gt } = self; + *was_lt |= (!*was_gt) & a.ct_lt(&b).unwrap_u8(); + *was_gt |= b.ct_lt(&a).unwrap_u8(); + } +} + +impl> ConstantTimeLess for C { fn ct_lt(&self, other: &Self) -> Choice { - !self.ct_gt(other) & !self.ct_eq(other) + let a: T = self.for_constant_operation(); + let b: T = other.for_constant_operation(); + a.ct_lt(&b) } } From 8533dcec59fe2d89d485e7f707c5a4499d53efc4 Mon Sep 17 00:00:00 2001 From: Danny McClanahan <1305167+cosmicexplorer@users.noreply.github.com> Date: Fri, 1 Jul 2022 06:41:58 -0400 Subject: [PATCH 10/10] remove `.unwrap_u8()`: does this break `ConstantTimeEq` slice impl? --- src/lib.rs | 56 +++++++++++++++++++++++++----------------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e739955..863db0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -250,12 +250,14 @@ pub trait ConstantTimeEq { /// assert_eq!(1, s2.ct_eq(&s2).unwrap_u8()); ///``` pub struct IteratedEq { - still_equal: u8, + still_equal: Choice, } impl IteratedOperation for IteratedEq { fn initiate() -> Self { - Self { still_equal: 1u8 } + Self { + still_equal: Choice::from(1), + } } fn extract_result(self) -> Choice { self.still_equal.into() @@ -264,12 +266,14 @@ impl IteratedOperation for IteratedEq { impl IteratedEq { /// Unconditionally AND internal state with the result of an "equals" comparison. + #[inline] pub fn apply_eq(&mut self, a: &T, b: &T) { - self.still_equal &= a.ct_eq(b).unwrap_u8(); + self.still_equal &= a.ct_eq(b); } } impl> ConstantTimeEq for C { + #[inline] fn ct_eq(&self, other: &Self) -> Choice { let a: T = self.for_constant_operation(); let b: T = other.for_constant_operation(); @@ -313,9 +317,6 @@ impl ConstantTimeEq for [T] { return Choice::from(0); } - // This loop shouldn't be shortcircuitable, since the compiler - // shouldn't be able to reason about the value of the `u8` - // unwrapped from the `ct_eq` result. let mut x = IteratedEq::initiate(); for (ai, bi) in self.iter().zip(_rhs.iter()) { x.apply_eq(ai, bi); @@ -818,15 +819,15 @@ pub trait ConstantTimeGreater { /// assert_eq!(1, s3.ct_gt(&s2).unwrap_u8()); ///``` pub struct IteratedGreater { - was_gt: u8, - was_lt: u8, + was_gt: Choice, + was_lt: Choice, } impl IteratedOperation for IteratedGreater { fn initiate() -> Self { Self { - was_gt: 0u8, - was_lt: 0u8, + was_gt: Choice::from(0), + was_lt: Choice::from(0), } } fn extract_result(self) -> Choice { @@ -836,14 +837,16 @@ impl IteratedOperation for IteratedGreater { impl IteratedGreater { /// Unconditionally modify internal state with result of two directed "greater" comparisons. + #[inline] pub fn apply_gt(&mut self, a: &T, b: &T) { let Self { was_gt, was_lt } = self; - *was_gt |= (!*was_lt) & a.ct_gt(&b).unwrap_u8(); - *was_lt |= b.ct_gt(&a).unwrap_u8(); + *was_gt |= (!*was_lt) & a.ct_gt(&b); + *was_lt |= b.ct_gt(&a); } } impl> ConstantTimeGreater for C { + #[inline] fn ct_gt(&self, other: &Self) -> Choice { let a: T = self.for_constant_operation(); let b: T = other.for_constant_operation(); @@ -937,14 +940,6 @@ impl ConstantTimeGreater for [T] { } } - // Whether all the pairs of elements from both slices have been equal for all - // previous iterations. - // let mut still_at_least_eq: u8 = 1u8; - // Whether `self` is indeed greater than `other`. - // let mut was_gt: u8 = 0u8; - // This loop shouldn't be shortcircuitable, since the compiler - // shouldn't be able to reason about the value of the `u8` - // unwrapped from the `ct_eq` and `ct_gt` results. let mut x = IteratedGreater::initiate(); for (ai, bi) in self.iter().zip(_rhs.iter()) { x.apply_gt(ai, bi); @@ -956,13 +951,11 @@ impl ConstantTimeGreater for [T] { /// A type which can be compared in some manner and be determined to be less /// than another of the same type. -/// -/// This trait is automatically implemented for implementors of -/// `ConstantTimeGreater+ConstantTimeEq`. pub trait ConstantTimeLess: ConstantTimeGreater { /// Determine whether `self < other`. /// - /// This function should execute in constant time. + /// This function should execute in constant time. The default implementation simply calls + /// [`ConstantTimeGreater::ct_gt`] with the arguments switched. /// /// # Returns /// @@ -989,6 +982,7 @@ pub trait ConstantTimeLess: ConstantTimeGreater { /// /// assert_eq!(x_lt_x.unwrap_u8(), 0); /// ``` + #[inline] fn ct_lt(&self, other: &Self) -> Choice { other.ct_gt(self) } @@ -1039,15 +1033,15 @@ pub trait ConstantTimeLess: ConstantTimeGreater { /// assert_eq!(1, s2.ct_lt(&s3).unwrap_u8()); ///``` pub struct IteratedLess { - was_lt: u8, - was_gt: u8, + was_lt: Choice, + was_gt: Choice, } impl IteratedOperation for IteratedLess { fn initiate() -> Self { Self { - was_lt: 0u8, - was_gt: 0u8, + was_lt: Choice::from(0), + was_gt: Choice::from(0), } } fn extract_result(self) -> Choice { @@ -1057,14 +1051,16 @@ impl IteratedOperation for IteratedLess { impl IteratedLess { /// Unconditionally modify internal state with result of two directed "less" comparisons. + #[inline] pub fn apply_lt(&mut self, a: &T, b: &T) { let Self { was_lt, was_gt } = self; - *was_lt |= (!*was_gt) & a.ct_lt(&b).unwrap_u8(); - *was_gt |= b.ct_lt(&a).unwrap_u8(); + *was_lt |= (!*was_gt) & a.ct_lt(&b); + *was_gt |= b.ct_lt(&a); } } impl> ConstantTimeLess for C { + #[inline] fn ct_lt(&self, other: &Self) -> Choice { let a: T = self.for_constant_operation(); let b: T = other.for_constant_operation();