diff --git a/ctutils/src/choice.rs b/ctutils/src/choice.rs index 98dfac91..bb9587d1 100644 --- a/ctutils/src/choice.rs +++ b/ctutils/src/choice.rs @@ -9,7 +9,8 @@ use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, /// This is used as a "belt-and-suspenders" defense in addition to mechanisms like /// constant-time predication intrinsics provided by the `cmov` crate, and is never expected to be /// the only line of defense. -#[derive(Copy, Clone, Debug)] +// TODO(tarcieri): remove `Eq`/`PartialEq` when `crypto-bigint` is updated +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Choice(u8); impl Choice { @@ -59,6 +60,26 @@ impl Choice { core::hint::black_box(self.0) } + /// HACK: workaround to allow `const fn` boolean support on Rust 1.85. + /// + /// This does not apply `black_box` to the output. + // TODO(tarcieri): deprecate/remove this in favor of `to_bool` when MSRV is Rust 1.86 + pub const fn to_bool_vartime(self) -> bool { + self.0 != 0 + } + + /// HACK: workaround to allow `const fn` boolean support on Rust 1.85. + /// + /// This does not apply `black_box` to the output. + // TODO(tarcieri): deprecate/remove this in favor of `to_u8` when MSRV is Rust 1.86 + pub const fn to_u8_vartime(self) -> u8 { + self.0 + } + + // + // Bitwise ops + // + /// Apply an `and` conditional to the given [`Choice`]s. #[inline] pub const fn and(self, rhs: Choice) -> Choice { @@ -83,6 +104,131 @@ impl Choice { // NOTE: assumes self.0 is `0` or `1` as checked in constructor Self(self.0 ^ 1) } + + // + // Comparison ops + // + + /// `const fn` equality operation. + #[inline] + pub const fn eq(self, other: Self) -> Self { + Self::ne(self, other).not() + } + + /// `const fn` not equal operation. + #[inline] + pub const fn ne(self, other: Self) -> Self { + Self::xor(self, other) + } + + // + // `const fn` constructor methods + // + + /// Returns the truthy value if `x == y`, and the falsy value otherwise. + #[inline] + pub const fn from_i64_eq(x: i64, y: i64) -> Self { + Self::from_u64_nonzero(x as u64 ^ y as u64).not() + } + + /// Returns the truthy value if `x == y`, and the falsy value otherwise. + #[inline] + pub const fn from_u32_eq(x: u32, y: u32) -> Self { + Self::from_u32_nonzero(x ^ y).not() + } + + /// Returns the truthy value if `x <= y` and the falsy value otherwise. + #[inline] + pub const fn from_u32_le(x: u32, y: u32) -> Self { + // See "Hacker's Delight" 2nd ed, section 2-12 (Comparison predicates) + let bit = (((!x) | y) & ((x ^ y) | !y.wrapping_sub(x))) >> (u32::BITS - 1); + Self::from_u32_lsb(bit) + } + + /// Initialize from the least significant bit of a `u32`. + #[inline] + pub const fn from_u32_lsb(value: u32) -> Self { + Self::new((value & 0x1) as u8) + } + + /// Returns the truthy value if `x < y`, and the falsy value otherwise. + #[inline] + pub const fn from_u32_lt(x: u32, y: u32) -> Self { + // See "Hacker's Delight" 2nd ed, section 2-12 (Comparison predicates) + let bit = (((!x) & y) | (((!x) | y) & x.wrapping_sub(y))) >> (u32::BITS - 1); + Self::from_u32_lsb(bit) + } + + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. + #[inline] + pub const fn from_u32_nonzero(value: u32) -> Self { + Self::from_u32_lsb((value | value.wrapping_neg()) >> (u32::BITS - 1)) + } + + /// Initialize from the least significant bit of a `u64`. + #[inline] + pub const fn from_u64_lsb(value: u64) -> Self { + Self::new((value & 0x1) as u8) + } + + /// Returns the truthy value if `value != 0`, and the falsy value otherwise. + #[inline] + pub const fn from_u64_nonzero(value: u64) -> Self { + Self::from_u64_lsb((value | value.wrapping_neg()) >> (u64::BITS - 1)) + } + + // + // `const fn` predication methods + // + + /// `const fn` helper: return `b` if `self` is truthy, otherwise return `a`. + /// + /// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context + /// and can't use the trait. The former will provide better constant-time assurances. + #[inline] + pub const fn select_i64(self, a: i64, b: i64) -> i64 { + self.select_u64(a as u64, b as u64) as i64 + } + + /// `const fn` helper: return `b` if `self` is truthy, otherwise return `a`. + /// + /// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context + /// and can't use the trait. The former will provide better constant-time assurances. + #[inline] + pub const fn select_u32(self, a: u32, b: u32) -> u32 { + a ^ (self.to_u32_mask() & (a ^ b)) + } + + /// `const fn` helper: return `b` if `self` is truthy, otherwise return `a`. + /// + /// Only use this instead of the [`CtSelect`] trait in the event you're in a `const fn` context + /// and can't use the trait. The former will provide better constant-time assurances. + #[inline] + pub const fn select_u64(self, a: u64, b: u64) -> u64 { + a ^ (self.to_u64_mask() & (a ^ b)) + } + + /// Create a `u32` bitmask. + /// + /// # Returns + /// - `0` for `Choice::FALSE` + /// - `u32::MAX` for `Choice::TRUE` + #[inline] + #[allow(trivial_numeric_casts)] + pub(crate) const fn to_u32_mask(self) -> u32 { + (self.0 as u32 & 1).wrapping_neg() + } + + /// Create a `u64` bitmask. + /// + /// # Returns + /// - `0` for `Choice::FALSE` + /// - `u64::MAX` for `Choice::TRUE` + #[inline] + #[allow(trivial_numeric_casts)] + pub(crate) const fn to_u64_mask(self) -> u64 { + (self.0 as u64 & 1).wrapping_neg() + } } impl BitAnd for Choice { @@ -253,4 +399,84 @@ mod tests { assert_eq!(Choice::new(0).not().to_u8(), 1); assert_eq!(Choice::new(1).not().to_u8(), 0); } + + #[test] + fn from_i64_eq() { + assert_eq!(Choice::from_i64_eq(0, 1), Choice::FALSE); + assert_eq!(Choice::from_i64_eq(1, 1), Choice::TRUE); + } + + #[test] + fn from_u32_eq() { + assert_eq!(Choice::from_u32_eq(0, 1), Choice::FALSE); + assert_eq!(Choice::from_u32_eq(1, 1), Choice::TRUE); + } + + #[test] + fn from_u32_le() { + assert_eq!(Choice::from_u32_le(0, 0), Choice::TRUE); + assert_eq!(Choice::from_u32_le(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u32_le(1, 1), Choice::TRUE); + assert_eq!(Choice::from_u32_le(1, 2), Choice::TRUE); + } + + #[test] + fn from_u32_lsb() { + assert_eq!(Choice::from_u32_lsb(0), Choice::FALSE); + assert_eq!(Choice::from_u32_lsb(1), Choice::TRUE); + assert_eq!(Choice::from_u32_lsb(2), Choice::FALSE); + assert_eq!(Choice::from_u32_lsb(3), Choice::TRUE); + } + + #[test] + fn from_u32_lt() { + assert_eq!(Choice::from_u32_lt(0, 0), Choice::FALSE); + assert_eq!(Choice::from_u32_lt(1, 0), Choice::FALSE); + assert_eq!(Choice::from_u32_lt(1, 1), Choice::FALSE); + assert_eq!(Choice::from_u32_lt(1, 2), Choice::TRUE); + } + + #[test] + fn from_u32_nonzero() { + assert_eq!(Choice::from_u32_nonzero(0), Choice::FALSE); + assert_eq!(Choice::from_u32_nonzero(1), Choice::TRUE); + assert_eq!(Choice::from_u32_nonzero(2), Choice::TRUE); + } + + #[test] + fn from_u64_lsb() { + assert_eq!(Choice::from_u64_lsb(0), Choice::FALSE); + assert_eq!(Choice::from_u64_lsb(1), Choice::TRUE); + } + + #[test] + fn from_u64_nonzero() { + assert_eq!(Choice::from_u64_nonzero(0), Choice::FALSE); + assert_eq!(Choice::from_u64_nonzero(1), Choice::TRUE); + assert_eq!(Choice::from_u64_nonzero(2), Choice::TRUE); + } + + #[test] + fn select_i64() { + let a: i64 = 1; + let b: i64 = 2; + assert_eq!(Choice::TRUE.select_i64(a, b), b); + assert_eq!(Choice::FALSE.select_i64(a, b), a); + } + + #[test] + fn select_u32() { + let a: u32 = 1; + let b: u32 = 2; + assert_eq!(Choice::TRUE.select_u32(a, b), b); + assert_eq!(Choice::FALSE.select_u32(a, b), a); + } + + #[test] + fn select_u64() { + let a: u64 = 1; + let b: u64 = 2; + assert_eq!(Choice::TRUE.select_u64(a, b), b); + assert_eq!(Choice::FALSE.select_u64(a, b), a); + } } diff --git a/ctutils/src/ct_option.rs b/ctutils/src/ct_option.rs index 44bcf1ac..08b383cb 100644 --- a/ctutils/src/ct_option.rs +++ b/ctutils/src/ct_option.rs @@ -62,15 +62,46 @@ impl CtOption { /// Return the contained value, consuming the `self` value. /// /// # Panics - /// /// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message /// provided as the `msg` argument. #[inline] + #[track_caller] pub fn expect(self, msg: &str) -> T { assert!(self.is_some().to_bool(), "{}", msg); self.value } + /// Return a copy of the contained value without consuming `self`. + /// + /// # Panics + /// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message + /// provided as the `msg` argument. + // TODO(tarcieri): get rid of this when we can make `expect` a `const fn` + // (needs `const_precise_live_drops`) + #[inline] + #[track_caller] + pub const fn expect_copied(&self, msg: &str) -> T + where + T: Copy, + { + *self.expect_ref(msg) + } + + /// Borrow the contained value. + /// + /// # Panics + /// In the event `self.is_some()` is [`Choice::FALSE`], panics with a custom panic message + /// provided as the `msg` argument. + // TODO(tarcieri): get rid of this when we can make `expect` a `const fn` + // (needs `const_precise_live_drops`) + #[inline] + #[track_caller] + pub const fn expect_ref(&self, msg: &str) -> &T { + // TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86 + assert!(self.is_some.to_bool_vartime(), "{}", msg); + &self.value + } + /// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether /// [`CtOption::is_some`] is a truthy or falsy [`Choice`]. /// @@ -229,7 +260,6 @@ impl CtOption { /// of `Option`/`Result` to handle errors) /// /// # Panics - /// /// In the event `self.is_some()` is [`Choice::FALSE`]. #[inline] pub fn unwrap(self) -> T {