diff --git a/src/lib.rs b/src/lib.rs index 2e94a06..e9116cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,7 @@ #[macro_use] extern crate std; +use core::cmp; use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Neg, Not}; use core::option::Option; @@ -371,6 +372,14 @@ generate_integer_equal!(u64, i64, 64); generate_integer_equal!(u128, i128, 128); generate_integer_equal!(usize, isize, ::core::mem::size_of::() * 8); +/// `Ordering` is `#[repr(i8)]` making it possible to leverage `i8::ct_eq`. +impl ConstantTimeEq for cmp::Ordering { + #[inline] + fn ct_eq(&self, other: &Self) -> Choice { + (*self as i8).ct_eq(&(*other as i8)) + } +} + /// A type which can be conditionally selected in constant time. /// /// This trait also provides generic implementations of conditional @@ -532,6 +541,26 @@ generate_integer_conditional_select!( u64 i64); #[cfg(feature = "i128")] generate_integer_conditional_select!(u128 i128); +/// `Ordering` is `#[repr(i8)]` where: +/// +/// - `Less` => -1 +/// - `Equal` => 0 +/// - `Greater` => 1 +/// +/// Given this, it's possible to operate on orderings as if they're integers, +/// which allows leveraging conditional masking for predication. +impl ConditionallySelectable for cmp::Ordering { + fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { + let a = *a as i8; + let b = *b as i8; + let ret = i8::conditional_select(&a, &b, choice); + + // SAFETY: `Ordering` is `#[repr(i8)]` and `ret` has been assigned to + // a value which was originally a valid `Ordering` then cast to `i8` + unsafe { *((&ret as *const _) as *const cmp::Ordering) } + } +} + impl ConditionallySelectable for Choice { #[inline] fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { @@ -847,6 +876,16 @@ generate_unsigned_integer_greater!(u64, 64); #[cfg(feature = "i128")] generate_unsigned_integer_greater!(u128, 128); +impl ConstantTimeGreater for cmp::Ordering { + #[inline] + fn ct_gt(&self, other: &Self) -> Choice { + // No impl of `ConstantTimeGreater` for `i8`, so use `u8` + let a = (*self as i8) + 1; + let b = (*other as i8) + 1; + (a as u8).ct_gt(&(b as u8)) + } +} + /// 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 { @@ -898,3 +937,13 @@ impl ConstantTimeLess for u32 {} impl ConstantTimeLess for u64 {} #[cfg(feature = "i128")] impl ConstantTimeLess for u128 {} + +impl ConstantTimeLess for cmp::Ordering { + #[inline] + fn ct_lt(&self, other: &Self) -> Choice { + // No impl of `ConstantTimeLess` for `i8`, so use `u8` + let a = (*self as i8) + 1; + let b = (*other as i8) + 1; + (a as u8).ct_lt(&(b as u8)) + } +} diff --git a/tests/mod.rs b/tests/mod.rs index 8cb10b6..8975147 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,6 +1,8 @@ extern crate rand; extern crate subtle; +use std::cmp; + use rand::rngs::OsRng; use rand::RngCore; @@ -96,6 +98,19 @@ fn custom_conditional_select_i16() { assert_eq!(i16::conditional_select(&x, &y, 1.into()), 514); } +#[test] +fn ordering_conditional_select() { + assert_eq!( + cmp::Ordering::conditional_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::Greater + ); +} + macro_rules! generate_integer_equal_tests { ($($t:ty),*) => ($( let y: $t = 0; // all 0 bits @@ -149,6 +164,16 @@ fn choice_equal() { assert!(Choice::from(1).ct_eq(&Choice::from(1)).unwrap_u8() == 1); } +#[test] +fn ordering_equal() { + let a = cmp::Ordering::Equal; + let b = cmp::Ordering::Greater; + let c = a; + + assert_eq!(a.ct_eq(&b).unwrap_u8(), 0); + assert_eq!(a.ct_eq(&c).unwrap_u8(), 1); +} + #[test] fn test_ctoption() { let a = CtOption::new(10, Choice::from(1)); @@ -334,6 +359,12 @@ fn greater_than_u128() { generate_greater_than_test!(u128); } +#[test] +fn greater_than_ordering() { + assert_eq!(cmp::Ordering::Less.ct_gt(&cmp::Ordering::Greater).unwrap_u8(), 0); + assert_eq!(cmp::Ordering::Greater.ct_gt(&cmp::Ordering::Less).unwrap_u8(), 1); +} + #[test] /// Test that the two's compliment min and max, i.e. 0000...0001 < 1111...1110, /// gives the correct result. (This fails using the bit-twiddling algorithm that @@ -389,3 +420,9 @@ fn less_than_u64() { fn less_than_u128() { generate_less_than_test!(u128); } + +#[test] +fn less_than_ordering() { + assert_eq!(cmp::Ordering::Greater.ct_lt(&cmp::Ordering::Less).unwrap_u8(), 0); + assert_eq!(cmp::Ordering::Less.ct_lt(&cmp::Ordering::Greater).unwrap_u8(), 1); +}