diff --git a/ctutils/src/array.rs b/ctutils/src/array.rs deleted file mode 100644 index 47c52369..00000000 --- a/ctutils/src/array.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Array helpers: generic selection/equality operations for arrays. -//! -//! Use these in the event there isn't an impl of a given trait for `[T; N]`. If there is, however, -//! you should prefer that for performance reasons. - -use crate::{Choice, CtAssign, CtEq, CtSelect, slice}; - -/// Generic implementation of conditional assignment for arrays which works with any type which -/// impls `CtSelect`. Useful in the event there isn't a `CtAssign` impl for `[T; N]`. -/// -/// Assigns `src` to `dst` in constant-time when `choice` is [`Choice::TRUE`]. -/// -/// Unfortunately we can't provide this as a trait impl without specialization, since it would -/// overlap with the optimized type-specific impls we provide. -#[inline] -pub fn ct_assign(dst: &mut [T; N], src: &[T; N], choice: Choice) -where - T: CtAssign, -{ - slice::ct_assign(dst, src, choice); -} - -/// Generic implementation of constant-time equality testing for arrays which works with any type -/// which impls `CtEq`. Useful in the event there isn't a `CtEq` impl for `[T; N]`. -#[inline] -#[must_use] -pub fn ct_eq(a: &[T; N], b: &[T; N]) -> Choice -where - T: CtEq, -{ - let mut ret = Choice::TRUE; - for (a, b) in a.iter().zip(b.iter()) { - ret &= a.ct_eq(b); - } - ret -} - -/// Generic implementation of conditional selection for arrays which works with any type which -/// impls `CtSelect`. Useful in the event there isn't a `CtSelect` impl for `[T; N]`. -/// -/// Selects `a` if `choice` is `Choice::FALSE`, and `b` if `choice` is `Choice::TRUE`. -/// -/// Unfortunately we can't provide this as a trait impl without specialization, since it would -/// overlap with the optimized type-specific impls we provide. -#[inline] -#[must_use] -pub fn ct_select(a: &[T; N], b: &[T; N], choice: Choice) -> [T; N] -where - T: CtSelect, -{ - core::array::from_fn(|i| T::ct_select(&a[i], &b[i], choice)) -} - -#[cfg(test)] -mod tests { - use crate::Choice; - - // Note: this violates our own advice not to use these functions with e.g. `[u8; N]` but this - // is just for testing purposes - const EXAMPLE_A: [u8; 3] = [1, 2, 3]; - const EXAMPLE_B: [u8; 3] = [4, 5, 6]; - - #[test] - fn ct_assign() { - let mut x = EXAMPLE_A; - - super::ct_assign(&mut x, &EXAMPLE_B, Choice::FALSE); - assert_eq!(EXAMPLE_A, x); - - super::ct_assign(&mut x, &EXAMPLE_B, Choice::TRUE); - assert_eq!(EXAMPLE_B, x); - } - - #[test] - fn ct_eq() { - assert!(super::ct_eq(&EXAMPLE_A, &EXAMPLE_A).to_bool()); - assert!(!super::ct_eq(&EXAMPLE_A, &EXAMPLE_B).to_bool()); - } - - #[test] - fn ct_select() { - assert_eq!( - EXAMPLE_A, - super::ct_select(&EXAMPLE_A, &EXAMPLE_B, Choice::FALSE) - ); - assert_eq!( - EXAMPLE_B, - super::ct_select(&EXAMPLE_A, &EXAMPLE_B, Choice::TRUE) - ); - } -} diff --git a/ctutils/src/lib.rs b/ctutils/src/lib.rs index e9bd90e0..8170e40c 100644 --- a/ctutils/src/lib.rs +++ b/ctutils/src/lib.rs @@ -96,9 +96,6 @@ #[cfg(feature = "alloc")] extern crate alloc; -pub mod array; -pub mod slice; - mod choice; mod ct_option; mod traits; @@ -106,6 +103,12 @@ mod traits; pub use choice::Choice; pub use ct_option::CtOption; pub use traits::{ - ct_assign::CtAssign, ct_eq::CtEq, ct_find::CtFind, ct_gt::CtGt, ct_lookup::CtLookup, - ct_lt::CtLt, ct_neg::CtNeg, ct_select::CtSelect, + ct_assign::{CtAssign, CtAssignSlice}, + ct_eq::{CtEq, CtEqSlice}, + ct_find::CtFind, + ct_gt::CtGt, + ct_lookup::CtLookup, + ct_lt::CtLt, + ct_neg::CtNeg, + ct_select::{CtSelect, CtSelectArray}, }; diff --git a/ctutils/src/slice.rs b/ctutils/src/slice.rs deleted file mode 100644 index 469c1f9a..00000000 --- a/ctutils/src/slice.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Slice helpers: generic selection/equality operations for slices. -//! -//! Use these in the event there isn't an impl of a given trait for `[T]`. If there is, however, -//! you should prefer that for performance reasons. - -use crate::{Choice, CtAssign, CtEq}; - -/// Generic implementation of conditional assignment for slices which works with any type which -/// impls `CtSelect`. Useful in the event there isn't a `CtAssign` impl for `[T]`. -/// -/// Assigns `src` to `dst` in constant-time when `choice` is [`Choice::TRUE`], like a conditional -/// version of `[T]::copy_from_slice`. -/// -/// Unfortunately we can't provide this as a trait impl without specialization, since it would -/// overlap with the optimized type-specific impls we provide. -/// -/// # Panics -/// - If the two slices have unequal lengths -#[inline] -pub fn ct_assign(dst: &mut [T], src: &[T], choice: Choice) -where - T: CtAssign, -{ - assert_eq!( - dst.len(), - src.len(), - "source slice length ({}) does not match destination slice length ({})", - src.len(), - dst.len() - ); - - for (a, b) in dst.iter_mut().zip(src) { - a.ct_assign(b, choice); - } -} - -/// Generic implementation of constant-time equality testing for slices which works with any type -/// which impls `CtEq`. Useful in the event there isn't a `CtEq` impl for `[T]`. -// NOTE: `cfg` gated because it uses the `CtEq` impl on `usize` -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -))] -#[inline] -#[must_use] -pub fn ct_eq(a: &[T], b: &[T]) -> Choice -where - T: CtEq, -{ - let mut ret = a.len().ct_eq(&b.len()); - for (a, b) in a.iter().zip(b.iter()) { - ret &= a.ct_eq(b); - } - ret -} - -#[cfg(test)] -mod tests { - use crate::Choice; - - // Note: this violates our own advice not to use these functions with e.g. `[u8]` but this - // is just for testing purposes - const EXAMPLE_A: &[u8] = &[1, 2, 3]; - const EXAMPLE_B: &[u8] = &[4, 5, 6]; - - #[test] - fn ct_assign() { - let mut x = [0u8; 3]; - - super::ct_assign(&mut x, EXAMPLE_A, Choice::FALSE); - assert_eq!([0u8; 3], x); - - super::ct_assign(&mut x, EXAMPLE_A, Choice::TRUE); - assert_eq!(EXAMPLE_A, x); - } - - #[test] - fn ct_eq() { - assert!(super::ct_eq(EXAMPLE_A, EXAMPLE_A).to_bool()); - assert!(!super::ct_eq(EXAMPLE_A, EXAMPLE_B).to_bool()); - } -} diff --git a/ctutils/src/traits/ct_assign.rs b/ctutils/src/traits/ct_assign.rs index 3908bb19..0919f685 100644 --- a/ctutils/src/traits/ct_assign.rs +++ b/ctutils/src/traits/ct_assign.rs @@ -11,12 +11,89 @@ use core::{ #[cfg(feature = "alloc")] use alloc::{boxed::Box, vec::Vec}; +#[cfg(doc)] +use core::num::NonZero; + /// Constant-time conditional assignment: assign a given value to another based on a [`Choice`]. +/// +/// This crate provides built-in implementations for the following types: +/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] +/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] +/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`] +/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`] +/// - [`cmp::Ordering`] +/// - [`Choice`] +/// - `[T]` and `[T; N]` where `T` impls [`CtAssignSlice`], which the previously mentioned +/// types all do. pub trait CtAssign { - /// Conditionally assign `rhs` to `self` if `choice` is [`Choice::TRUE`]. - fn ct_assign(&mut self, rhs: &Rhs, choice: Choice); + /// Conditionally assign `src` to `self` if `choice` is [`Choice::TRUE`]. + fn ct_assign(&mut self, src: &Rhs, choice: Choice); +} + +/// Implementing this trait enables use of the [`CtAssign`] trait for `[T]` where `T` is the +/// `Self` type implementing the trait, via a blanket impl. +/// +/// It needs to be a separate trait from [`CtAssign`] because we need to be able to impl +/// [`CtAssign`] for `[T]`. +pub trait CtAssignSlice: CtAssign + Sized { + /// Conditionally assign `src` to `dst` if `choice` is [`Choice::TRUE`], or leave it unchanged + /// for [`Choice::FALSE`]. + fn ct_assign_slice(dst: &mut [Self], src: &[Self], choice: Choice) { + assert_eq!( + dst.len(), + src.len(), + "source slice length ({}) does not match destination slice length ({})", + src.len(), + dst.len() + ); + + for (a, b) in dst.iter_mut().zip(src) { + a.ct_assign(b, choice); + } + } +} + +impl CtAssign for [T] { + fn ct_assign(&mut self, src: &[T], choice: Choice) { + T::ct_assign_slice(self, src, choice); + } +} + +/// Impl `CtAssign` using the `cmov::Cmov` trait +macro_rules! impl_ct_assign_with_cmov { + ( $($ty:ty),+ ) => { + $( + impl CtAssign for $ty { + #[inline] + fn ct_assign(&mut self, rhs: &Self, choice: Choice) { + self.cmovnz(rhs, choice.into()); + } + } + )+ + }; +} + +/// Impl `CtAssign` and `CtAssignSlice` using the `cmov::Cmov` trait +macro_rules! impl_ct_assign_slice_with_cmov { + ( $($ty:ty),+ ) => { + $( + impl_ct_assign_with_cmov!($ty); + + impl CtAssignSlice for $ty { + #[inline] + fn ct_assign_slice(dst: &mut [Self], src: &[Self], choice: Choice) { + dst.cmovnz(src, choice.into()); + } + } + )+ + }; } +impl_ct_assign_slice_with_cmov!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128); +impl_ct_assign_with_cmov!(isize, usize); +impl CtAssignSlice for isize {} +impl CtAssignSlice for usize {} + /// Impl `CtAssign` using the `CtSelect` trait. /// /// In cases where `CtSelect` is more straightforward to implement, but you want to use a provided @@ -31,6 +108,8 @@ macro_rules! impl_ct_assign_with_ct_select { *self = Self::ct_select(self, rhs, choice); } } + + impl CtAssignSlice for $ty {} )+ }; } @@ -49,50 +128,6 @@ impl_ct_assign_with_ct_select!( NonZeroU128 ); -/// Impl `CtAssign` using the `cmov::Cmov` trait -macro_rules! impl_ct_assign_with_cmov { - ( $($ty:ty),+ ) => { - $( - impl CtAssign for $ty { - #[inline] - fn ct_assign(&mut self, rhs: &Self, choice: Choice) { - self.cmovnz(rhs, choice.into()); - } - } - )+ - }; -} - -impl_ct_assign_with_cmov!( - i8, - i16, - i32, - i64, - i128, - u8, - u16, - u32, - u64, - u128, - [i8], - [i16], - [i32], - [i64], - [i128], - [u8], - [u16], - [u32], - [u64], - [u128] -); - -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -))] -impl_ct_assign_with_cmov!(isize, usize); - impl CtAssign for [T; N] where [T]: CtAssign, @@ -103,6 +138,8 @@ where } } +impl CtAssignSlice for [T; N] where [T]: CtAssign {} + #[cfg(feature = "alloc")] impl CtAssign for Box where diff --git a/ctutils/src/traits/ct_eq.rs b/ctutils/src/traits/ct_eq.rs index bcc30416..588b41fb 100644 --- a/ctutils/src/traits/ct_eq.rs +++ b/ctutils/src/traits/ct_eq.rs @@ -17,6 +17,15 @@ use alloc::{boxed::Box, vec::Vec}; /// /// Impl'd for: [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`], [`cmp::Ordering`], /// [`Choice`], and arrays/slices of any type which also impls [`CtEq`]. +/// +/// This crate provides built-in implementations for the following types: +/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] +/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] +/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`] +/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`] +/// - [`cmp::Ordering`] +/// - [`Choice`] +/// - `[T]` and `[T; N]` where `T` impls [`CtEqSlice`], which the previously mentioned types all do. pub trait CtEq where Rhs: ?Sized, @@ -32,6 +41,39 @@ where } } +/// Implementing this trait enables use of the [`CtEq`] trait for `[T]` where `T` is the +/// `Self` type implementing the trait, via a blanket impl. +/// +/// It needs to be a separate trait from [`CtEq`] because we need to be able to impl +/// [`CtEq`] for `[T]`. +pub trait CtEqSlice: CtEq + Sized { + /// Determine if `a` is equal to `b` in constant-time. + #[must_use] + fn ct_eq_slice(a: &[Self], b: &[Self]) -> Choice { + let mut ret = a.len().ct_eq(&b.len()); + for (a, b) in a.iter().zip(b.iter()) { + ret &= a.ct_eq(b); + } + ret + } + + /// Determine if `a` is NOT equal to `b` in constant-time. + #[must_use] + fn ct_ne_slice(a: &[Self], b: &[Self]) -> Choice { + !Self::ct_eq_slice(a, b) + } +} + +impl CtEq for [T] { + fn ct_eq(&self, other: &Self) -> Choice { + T::ct_eq_slice(self, other) + } + + fn ct_ne(&self, other: &Self) -> Choice { + T::ct_ne_slice(self, other) + } +} + /// Impl `CtEq` using the `cmov::CmovEq` trait macro_rules! impl_ct_eq_with_cmov_eq { ( $($ty:ty),+ ) => { @@ -48,35 +90,28 @@ macro_rules! impl_ct_eq_with_cmov_eq { }; } -impl_ct_eq_with_cmov_eq!( - i8, - i16, - i32, - i64, - i128, - u8, - u16, - u32, - u64, - u128, - [i8], - [i16], - [i32], - [i64], - [i128], - [u8], - [u16], - [u32], - [u64], - [u128] -); +/// Impl `CtEq` and `CtEqSlice` using the `cmov::CmovEq` trait +macro_rules! impl_ct_eq_slice_with_cmov_eq { + ( $($ty:ty),+ ) => { + $( + impl_ct_eq_with_cmov_eq!($ty); -#[cfg(any( - target_pointer_width = "16", - target_pointer_width = "32", - target_pointer_width = "64" -))] + impl CtEqSlice for $ty { + #[inline] + fn ct_eq_slice(a: &[Self], b: &[Self]) -> Choice { + let mut ret = Choice::FALSE; + a.cmoveq(b, 1, &mut ret.0); + ret + } + } + )+ + }; +} + +impl_ct_eq_slice_with_cmov_eq!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128); impl_ct_eq_with_cmov_eq!(isize, usize); +impl CtEqSlice for isize {} +impl CtEqSlice for usize {} /// Impl `CtEq` for `NonZero` by calling `NonZero::get`. macro_rules! impl_ct_eq_for_nonzero_integer { @@ -88,6 +123,8 @@ macro_rules! impl_ct_eq_for_nonzero_integer { self.get().ct_eq(&other.get()) } } + + impl CtEqSlice for $ty {} )+ }; } @@ -113,6 +150,8 @@ impl CtEq for cmp::Ordering { } } +impl CtEqSlice for cmp::Ordering {} + impl CtEq for [T; N] where [T]: CtEq, @@ -123,6 +162,8 @@ where } } +impl CtEqSlice for [T; N] where [T]: CtEq {} + #[cfg(feature = "alloc")] impl CtEq for Box where diff --git a/ctutils/src/traits/ct_select.rs b/ctutils/src/traits/ct_select.rs index b52e2756..e38cdf05 100644 --- a/ctutils/src/traits/ct_select.rs +++ b/ctutils/src/traits/ct_select.rs @@ -12,7 +12,20 @@ use crate::CtOption; #[cfg(feature = "alloc")] use alloc::{boxed::Box, vec::Vec}; -/// Constant-time selection: pick between two values based on a given [`Choice`]. +#[cfg(doc)] +use crate::CtAssignSlice; + +/// Constant-time selection: choose between two values based on a given [`Choice`]. +/// +/// This crate provides built-in implementations for the following types: +/// - [`i8`], [`i16`], [`i32`], [`i64`], [`i128`], [`isize`] +/// - [`u8`], [`u16`], [`u32`], [`u64`], [`u128`], [`usize`] +/// - [`NonZeroI8`], [`NonZeroI16`], [`NonZeroI32`], [`NonZeroI64`], [`NonZeroI128`] +/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`] +/// - [`cmp::Ordering`] +/// - [`Choice`] +/// - `[T; N]` where `T` impls [`CtSelectArray`], which the previously mentioned types all do, +/// as well as any type which impls [`Clone`] + [`CtAssignSlice`] + [`CtSelect`]. pub trait CtSelect: Sized { /// Select between `self` and `other` based on `choice`, returning a copy of the value. /// @@ -30,6 +43,42 @@ pub trait CtSelect: Sized { } } +/// Implementing this trait enables use of the [`CtSelect`] trait to construct `[T; N]` where `T` +/// is the `Self` type implementing the trait, via a blanket impl. +/// +/// All types which impl [`Clone`] + [`CtAssignSlice`] + [`CtSelect`] will receive a blanket impl +/// of this trait and thus also be usable with the [`CtSelect`] impl for `[T; N]`. +pub trait CtSelectArray: CtSelect + Sized { + /// Select between `a` and `b` in constant-time based on `choice`. + #[must_use] + fn ct_select_array(a: &[Self; N], b: &[Self; N], choice: Choice) -> [Self; N] { + core::array::from_fn(|i| Self::ct_select(&a[i], &b[i], choice)) + } +} + +impl CtSelect for [T; N] +where + T: CtSelectArray, +{ + #[inline] + fn ct_select(&self, other: &Self, choice: Choice) -> Self { + T::ct_select_array(self, other, choice) + } +} + +impl CtSelectArray for T +where + T: Clone + CtSelect, + [T]: CtAssign, +{ + #[inline] + fn ct_select_array(a: &[Self; N], b: &[Self; N], choice: Choice) -> [Self; N] { + let mut ret = a.clone(); + ret.ct_assign(b, choice); + ret + } +} + /// Impl `CtSelect` using the `CtAssign` trait. /// /// In cases where `CtAssign` is more straightforward to implement, but you want to use a provided @@ -139,19 +188,6 @@ impl CtSelect for cmp::Ordering { } } -impl CtSelect for [T; N] -where - T: Clone, - [T]: CtAssign, -{ - #[inline] - fn ct_select(&self, other: &Self, choice: Choice) -> Self { - let mut ret = self.clone(); - ret.ct_assign(other, choice); - ret - } -} - #[cfg(feature = "alloc")] impl CtSelect for Box where