Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 227 additions & 1 deletion ctutils/src/choice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
}
34 changes: 32 additions & 2 deletions ctutils/src/ct_option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,46 @@ impl<T> CtOption<T> {
/// 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`].
///
Expand Down Expand Up @@ -229,7 +260,6 @@ impl<T> CtOption<T> {
/// of `Option`/`Result` to handle errors)
///
/// # Panics
///
/// In the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
pub fn unwrap(self) -> T {
Expand Down