Vendor dependencies

This commit is contained in:
2026-08-01 16:11:49 +03:00
parent 7f139a0241
commit 6b5e7f0f8b
29706 changed files with 9575646 additions and 0 deletions
+961
View File
@@ -0,0 +1,961 @@
use crate::{CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelectUsingCtAssign};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
#[cfg(feature = "subtle")]
use crate::CtSelect;
/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitle {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
};
}
/// Bitwise less-than: returns `1` if `x < y`, and otherwise returns `0`.
///
/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
macro_rules! bitlt {
($x:expr, $y:expr, $bits:expr) => {
(((!$x) & $y) | (((!$x) | $y) & $x.wrapping_sub($y))) >> ($bits - 1)
};
}
/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
macro_rules! bitnz {
($value:expr, $bits:expr) => {
($value | $value.wrapping_neg()) >> ($bits - 1)
};
}
/// Constant-time analogue of `bool` providing a "best effort" optimization barrier.
///
/// This type attempts to hint to the compiler and its codegen backends that optimizations should
/// not be applied which depend on specific values of this type.
///
/// 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.
// NOTE: we deliberately do NOT impl `Eq`, `Hash`, `PartialEq`, etc. See #1315
#[derive(Copy, Clone, Debug)]
pub struct Choice(pub(crate) u8);
impl Choice {
/// Equivalent of [`false`].
pub const FALSE: Self = Self(0);
/// Equivalent of [`true`].
pub const TRUE: Self = Self(1);
//
// `const fn` bitwise ops
//
/// Apply an `and` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn and(self, rhs: Choice) -> Choice {
Self(self.0 & rhs.0)
}
/// Apply an `or` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn or(self, rhs: Choice) -> Choice {
Self(self.0 | rhs.0)
}
/// Apply an `xor` conditional to the given [`Choice`]s.
#[inline]
#[must_use]
pub const fn xor(self, rhs: Choice) -> Choice {
Self(self.0 ^ rhs.0)
}
/// Compute the boolean inverse of `self`.
#[inline]
#[must_use]
pub const fn not(self) -> Choice {
// NOTE: assumes self.0 is `0` or `1` as checked in constructor
Self(self.0 ^ 1)
}
//
// `const fn` comparison ops
//
/// `const fn` equality operation.
#[inline]
#[must_use]
pub const fn eq(self, other: Self) -> Self {
Self::ne(self, other).not()
}
/// `const fn` not equal operation.
#[inline]
#[must_use]
pub const fn ne(self, other: Self) -> Self {
Self::xor(self, other)
}
//
// `const fn` constructor methods
//
// i64
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
#[allow(clippy::cast_sign_loss)]
pub const fn from_i64_eq(x: i64, y: i64) -> Self {
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
Self::from_u64_nz(x as u64 ^ y as u64).not()
}
// u8
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_eq(x: u8, y: u8) -> Self {
Self::from_u8_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_le(x: u8, y: u8) -> Self {
Self::from_u8_lsb(bitle!(x, y, u8::BITS))
}
/// Initialize from the least significant bit of a `u8`.
#[inline]
#[must_use]
pub const fn from_u8_lsb(value: u8) -> Self {
Self(value & 0x1)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_lt(x: u8, y: u8) -> Self {
Self::from_u8_lsb(bitlt!(x, y, u8::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u8_nz(value: u8) -> Self {
Self::from_u8_lsb(bitnz!(value, u8::BITS))
}
// u16
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_eq(x: u16, y: u16) -> Self {
Self::from_u16_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_le(x: u16, y: u16) -> Self {
Self::from_u16_lsb(bitle!(x, y, u16::BITS))
}
/// Initialize from the least significant bit of a `u16`.
#[inline]
#[must_use]
pub const fn from_u16_lsb(value: u16) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_lt(x: u16, y: u16) -> Self {
Self::from_u16_lsb(bitlt!(x, y, u16::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u16_nz(value: u16) -> Self {
Self::from_u16_lsb(bitnz!(value, u16::BITS))
}
// u32
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_eq(x: u32, y: u32) -> Self {
Self::from_u32_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_le(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitle!(x, y, u32::BITS))
}
/// Initialize from the least significant bit of a `u32`.
#[inline]
#[must_use]
pub const fn from_u32_lsb(value: u32) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_lt(x: u32, y: u32) -> Self {
Self::from_u32_lsb(bitlt!(x, y, u32::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u32_nz(value: u32) -> Self {
Self::from_u32_lsb(bitnz!(value, u32::BITS))
}
// u64
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_eq(x: u64, y: u64) -> Self {
Self::from_u64_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_le(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitle!(x, y, u64::BITS))
}
/// Initialize from the least significant bit of a `u64`.
#[inline]
#[must_use]
pub const fn from_u64_lsb(value: u64) -> Self {
Self((value & 0x1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_lt(x: u64, y: u64) -> Self {
Self::from_u64_lsb(bitlt!(x, y, u64::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u64_nz(value: u64) -> Self {
Self::from_u64_lsb(bitnz!(value, u64::BITS))
}
// u128
/// Returns [`Choice::TRUE`] if `x == y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_eq(x: u128, y: u128) -> Self {
Self::from_u128_nz(x ^ y).not()
}
/// Returns [`Choice::TRUE`] if `x <= y` and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_le(x: u128, y: u128) -> Self {
Self::from_u128_lsb(bitle!(x, y, u128::BITS))
}
/// Initialize from the least significant bit of a `u128`.
#[inline]
#[must_use]
pub const fn from_u128_lsb(value: u128) -> Self {
Self((value & 1) as u8)
}
/// Returns [`Choice::TRUE`] if `x < y`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_lt(x: u128, y: u128) -> Self {
Self::from_u128_lsb(bitlt!(x, y, u128::BITS))
}
/// Returns [`Choice::TRUE`] if `value != 0`, and [`Choice::FALSE`] otherwise.
#[inline]
#[must_use]
pub const fn from_u128_nz(value: u128) -> Self {
Self::from_u128_lsb(bitnz!(value, u128::BITS))
}
//
// `const fn` predication methods
//
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], 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]
#[must_use]
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
pub const fn select_i64(self, a: i64, b: i64) -> i64 {
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
self.select_u64(a as u64, b as u64) as i64
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], 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]
#[must_use]
pub const fn select_u8(self, a: u8, b: u8) -> u8 {
a ^ (self.to_u8_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], 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]
#[must_use]
pub const fn select_u16(self, a: u16, b: u16) -> u16 {
a ^ (self.to_u16_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], 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]
#[must_use]
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 [`Choice::TRUE`], 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]
#[must_use]
pub const fn select_u64(self, a: u64, b: u64) -> u64 {
a ^ (self.to_u64_mask() & (a ^ b))
}
/// `const fn` helper: return `b` if `self` is [`Choice::TRUE`], 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]
#[must_use]
pub const fn select_u128(self, a: u128, b: u128) -> u128 {
a ^ (self.to_u128_mask() & (a ^ b))
}
//
// Output conversion methods
//
/// Convert `Choice` into a `bool`.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// Using this function will introduce timing variability, since computing this at all currently
/// requires a branch.
///
/// This is intended to be used as either the one and only branch at the end of a constant-time
/// operation to e.g. differentiate between success and failure, or in contexts where
/// constant-time doesn't matter, e.g. variable-time code that operates on "maybe secret" types
/// which aren't secrets in a particular context.
///
/// If you are trying to use this in the context of a constant-time operation, be warned that
/// the small amount of timing variability it introduces can potentially be exploited. Whenever
/// possible, prefer fully constant-time approaches instead.
/// </div>
// TODO(tarcieri): `const fn` when MSRV 1.86
#[must_use]
pub fn to_bool(self) -> bool {
self.to_u8() != 0
}
/// Convert [`Choice`] to a `u8`, attempting to apply a "best effort" optimization barrier.
// TODO(tarcieri): `const fn` when MSRV 1.86
#[must_use]
pub fn to_u8(self) -> u8 {
// `black_box` is documented as working on a "best effort" basis. That's fine, this type is
// likewise documented as only working on a "best effort" basis itself. The only way we
// rely on `black_box` for correctness is it behaving as the identity function.
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.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// See the security warnings for [`Choice::to_bool`].
/// </div>
// TODO(tarcieri): deprecate/remove this in favor of `to_bool` when MSRV is Rust 1.86
#[must_use]
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
#[must_use]
pub const fn to_u8_vartime(self) -> u8 {
self.0
}
/// Create a `u8` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u8::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u8_mask(self) -> u8 {
self.0.wrapping_neg()
}
/// Create a `u16` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u16::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u16_mask(self) -> u16 {
(self.0 as u16).wrapping_neg()
}
/// Create a `u32` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u32::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u32_mask(self) -> u32 {
(self.0 as u32).wrapping_neg()
}
/// Create a `u64` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u64::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u64_mask(self) -> u64 {
(self.0 as u64).wrapping_neg()
}
/// Create a `u128` bitmask.
///
/// # Returns
/// - `0` for `Choice::FALSE`
/// - `u128::MAX` for `Choice::TRUE`
#[inline]
#[must_use]
pub const fn to_u128_mask(self) -> u128 {
(self.0 as u128).wrapping_neg()
}
}
impl BitAnd for Choice {
type Output = Choice;
#[inline]
fn bitand(self, rhs: Choice) -> Choice {
self.and(rhs)
}
}
impl BitAndAssign for Choice {
#[inline]
fn bitand_assign(&mut self, rhs: Choice) {
*self = *self & rhs;
}
}
impl BitOr for Choice {
type Output = Choice;
#[inline]
fn bitor(self, rhs: Choice) -> Choice {
self.or(rhs)
}
}
impl BitOrAssign for Choice {
#[inline]
fn bitor_assign(&mut self, rhs: Choice) {
*self = *self | rhs;
}
}
impl BitXor for Choice {
type Output = Choice;
#[inline]
fn bitxor(self, rhs: Choice) -> Choice {
Choice(self.0 ^ rhs.0)
}
}
impl BitXorAssign for Choice {
#[inline]
fn bitxor_assign(&mut self, rhs: Choice) {
*self = *self ^ rhs;
}
}
impl CtAssign for Choice {
#[inline]
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.0.ct_assign(&other.0, choice);
}
}
impl CtAssignSlice for Choice {}
impl CtSelectUsingCtAssign for Choice {}
impl CtEq for Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> Self {
self.0.ct_eq(&other.0)
}
}
impl CtEqSlice for Choice {}
/// DEPRECATED: this exists to aid migrating code from `subtle`. Use `Choice::from_u8_lsb` instead.
///
/// <div class="warning">
/// <b>Note</b>
///
/// Rust doesn't actually let us deprecate an impl block, however this comment is here to
/// discourage future use and warn that this will be removed in a future release.
/// </div>
impl From<u8> for Choice {
fn from(value: u8) -> Self {
Choice::from_u8_lsb(value)
}
}
impl From<Choice> for u8 {
fn from(choice: Choice) -> u8 {
choice.to_u8()
}
}
/// Convert `Choice` into a `bool`.
///
/// <div class = "warning">
/// <b>Security Warning</b>
///
/// Using this function will introduce timing variability, since computing this at all currently
/// requires a branch.
///
/// See the security warnings for [`Choice::to_bool`].
/// </div>
impl From<Choice> for bool {
fn from(choice: Choice) -> bool {
choice.to_bool()
}
}
impl Not for Choice {
type Output = Choice;
#[inline]
fn not(self) -> Choice {
self.not()
}
}
#[cfg(feature = "subtle")]
impl From<subtle::Choice> for Choice {
#[inline]
fn from(choice: subtle::Choice) -> Choice {
Choice(choice.unwrap_u8())
}
}
#[cfg(feature = "subtle")]
impl From<Choice> for subtle::Choice {
#[inline]
fn from(choice: Choice) -> subtle::Choice {
subtle::Choice::from(choice.0)
}
}
#[cfg(feature = "subtle")]
impl subtle::ConditionallySelectable for Choice {
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
CtSelect::ct_select(a, b, choice.into())
}
}
#[cfg(feature = "subtle")]
impl subtle::ConstantTimeEq for Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> subtle::Choice {
CtEq::ct_eq(self, other).into()
}
}
#[cfg(test)]
mod tests {
use super::Choice;
use crate::{CtEq, CtSelect};
#[test]
fn ct_eq() {
let a = Choice::TRUE;
let b = Choice::TRUE;
let c = Choice::FALSE;
assert!(a.ct_eq(&b).to_bool());
assert!(!a.ct_eq(&c).to_bool());
assert!(!b.ct_eq(&c).to_bool());
assert!(!a.ct_ne(&b).to_bool());
assert!(a.ct_ne(&c).to_bool());
assert!(b.ct_ne(&c).to_bool());
}
#[test]
fn ct_select() {
let a = Choice::FALSE;
let b = Choice::TRUE;
assert_eq!(a.ct_select(&b, Choice::FALSE).to_bool(), a.to_bool());
assert_eq!(a.ct_select(&b, Choice::TRUE).to_bool(), b.to_bool());
}
#[test]
fn and() {
assert_eq!((Choice::FALSE & Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE & Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::FALSE & Choice::TRUE).to_u8(), 0);
assert_eq!((Choice::TRUE & Choice::TRUE).to_u8(), 1);
}
#[test]
fn or() {
assert_eq!((Choice::FALSE | Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE | Choice::FALSE).to_u8(), 1);
assert_eq!((Choice::FALSE | Choice::TRUE).to_u8(), 1);
assert_eq!((Choice::TRUE | Choice::TRUE).to_u8(), 1);
}
#[test]
fn xor() {
assert_eq!((Choice::FALSE ^ Choice::FALSE).to_u8(), 0);
assert_eq!((Choice::TRUE ^ Choice::FALSE).to_u8(), 1);
assert_eq!((Choice::FALSE ^ Choice::TRUE).to_u8(), 1);
assert_eq!((Choice::TRUE ^ Choice::TRUE).to_u8(), 0);
}
#[test]
fn not() {
assert_eq!(Choice::FALSE.not().to_u8(), 1);
assert_eq!(Choice::TRUE.not().to_u8(), 0);
}
#[test]
fn from_i64_eq() {
assert!(Choice::from_i64_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_i64_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_eq() {
assert!(Choice::from_u8_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_le() {
assert!(Choice::from_u8_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_lsb() {
assert!(Choice::from_u8_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_lt() {
assert!(Choice::from_u8_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u8_nz() {
assert!(Choice::from_u8_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u8_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u8_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_eq() {
assert!(Choice::from_u16_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_le() {
assert!(Choice::from_u16_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_lsb() {
assert!(Choice::from_u16_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_lt() {
assert!(Choice::from_u16_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u16_nz() {
assert!(Choice::from_u16_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u16_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u16_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_eq() {
assert!(Choice::from_u32_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_le() {
assert!(Choice::from_u32_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_lsb() {
assert!(Choice::from_u32_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lsb(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_lsb(2).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lsb(3).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_lt() {
assert!(Choice::from_u32_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u32_nz() {
assert!(Choice::from_u32_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u32_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u32_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_eq() {
assert!(Choice::from_u64_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_le() {
assert!(Choice::from_u64_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_lsb() {
assert!(Choice::from_u64_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lsb(1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_lt() {
assert!(Choice::from_u64_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u64_nz() {
assert!(Choice::from_u64_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u64_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u64_nz(2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_eq() {
assert!(Choice::from_u128_eq(0, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_eq(1, 1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_le() {
assert!(Choice::from_u128_le(0, 0).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_le(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_le(1, 1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_le(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_lsb() {
assert!(Choice::from_u128_lsb(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lsb(1).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_lt() {
assert!(Choice::from_u128_lt(0, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 1).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_lt(1, 2).eq(Choice::TRUE).to_bool());
}
#[test]
fn from_u128_nz() {
assert!(Choice::from_u128_nz(0).eq(Choice::FALSE).to_bool());
assert!(Choice::from_u128_nz(1).eq(Choice::TRUE).to_bool());
assert!(Choice::from_u128_nz(2).eq(Choice::TRUE).to_bool());
}
#[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_u8() {
let a: u8 = 1;
let b: u8 = 2;
assert_eq!(Choice::TRUE.select_u8(a, b), b);
assert_eq!(Choice::FALSE.select_u8(a, b), a);
}
#[test]
fn select_u16() {
let a: u16 = 1;
let b: u16 = 2;
assert_eq!(Choice::TRUE.select_u16(a, b), b);
assert_eq!(Choice::FALSE.select_u16(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);
}
#[test]
fn select_u128() {
let a: u128 = 1;
let b: u128 = 2;
assert_eq!(Choice::TRUE.select_u128(a, b), b);
assert_eq!(Choice::FALSE.select_u128(a, b), a);
}
#[test]
fn to_bool() {
assert!(!Choice::FALSE.to_bool());
assert!(Choice::TRUE.to_bool());
}
#[test]
fn to_u8() {
assert_eq!(Choice::FALSE.to_u8(), 0);
assert_eq!(Choice::TRUE.to_u8(), 1);
}
#[test]
fn to_u8_mask() {
assert_eq!(Choice::FALSE.to_u8_mask(), 0);
assert_eq!(Choice::TRUE.to_u8_mask(), u8::MAX);
}
#[test]
fn to_u16_mask() {
assert_eq!(Choice::FALSE.to_u16_mask(), 0);
assert_eq!(Choice::TRUE.to_u16_mask(), u16::MAX);
}
#[test]
fn to_u32_mask() {
assert_eq!(Choice::FALSE.to_u32_mask(), 0);
assert_eq!(Choice::TRUE.to_u32_mask(), u32::MAX);
}
#[test]
fn to_u64_mask() {
assert_eq!(Choice::FALSE.to_u64_mask(), 0);
assert_eq!(Choice::TRUE.to_u64_mask(), u64::MAX);
}
#[test]
fn to_u128_mask() {
assert_eq!(Choice::FALSE.to_u128_mask(), 0);
assert_eq!(Choice::TRUE.to_u128_mask(), u128::MAX);
}
}
+918
View File
@@ -0,0 +1,918 @@
use crate::{Choice, CtAssign, CtAssignSlice, CtEq, CtEqSlice, CtSelect};
use core::ops::{Deref, DerefMut};
/// Helper macro for providing behavior like the [`CtOption::map`] combinator that works in
/// `const fn` contexts.
///
/// Requires a provided `$mapper` function to convert from one type to another, e.g.
///
/// ```ignore
/// const fn mapper(value: T) -> U
/// ```
#[macro_export]
macro_rules! map {
($opt:expr, $mapper:path) => {{ $crate::CtOption::new($mapper($opt.to_inner_unchecked()), $opt.is_some()) }};
}
/// Helper macro for providing behavior like the [`CtOption::unwrap_or`] combinator that works in
/// `const fn` contexts.
///
/// Requires a provided selector function `$select` to perform constant-time selection which takes
/// two `T` values by reference along with a [`Choice`], returning the first `T` for
/// [`Choice::FALSE`], and the second for [`Choice::TRUE`], e.g.:
///
/// ```ignore
/// const fn ct_select(a: &T, b: &T, condition: Choice) -> T
/// ```
#[macro_export]
macro_rules! unwrap_or {
($opt:expr, $default:expr, $select:path) => {
$select(&$default, $opt.as_inner_unchecked(), $opt.is_some())
};
}
/// Equivalent of [`Option`] but predicated on a [`Choice`] with combinators that allow for
/// constant-time operations which always perform the same sequence of instructions regardless of
/// the value of `is_some`.
///
/// Unlike [`Option`], [`CtOption`] always contains a value, and will use the contained value when
/// e.g. evaluating the callbacks of combinator methods, which unlike `core` it does unconditionally
/// in order to ensure constant-time operation. This approach stands in contrast to the lazy
/// evaluation similar methods on [`Option`] provide.
#[derive(Clone, Copy, Debug)]
pub struct CtOption<T> {
value: T,
is_some: Choice,
}
impl<T> CtOption<T> {
/// Construct a new [`CtOption`], with a [`Choice`] parameter `is_some` as a stand-in for
/// `Some` or `None` enum variants of a typical [`Option`] type.
#[inline]
#[must_use]
pub const fn new(value: T, is_some: Choice) -> CtOption<T> {
Self { value, is_some }
}
/// Construct a new [`CtOption`] where `self.is_some()` is [`Choice::TRUE`].
#[inline]
#[must_use]
pub const fn some(value: T) -> CtOption<T> {
Self::new(value, Choice::TRUE)
}
/// Construct a new [`CtOption`] with the [`Default`] value, and where `self.is_some()` is
/// [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn none() -> CtOption<T>
where
T: Default,
{
Self::new(Default::default(), Choice::FALSE)
}
/// Convert from a `&mut CtOption<T>` to `CtOption<&mut T>`.
#[inline]
#[must_use]
pub const fn as_mut(&mut self) -> CtOption<&mut T> {
CtOption {
value: &mut self.value,
is_some: self.is_some,
}
}
/// Convert from a `&CtOption<T>` to `CtOption<&T>`.
#[inline]
#[must_use]
pub const fn as_ref(&self) -> CtOption<&T> {
CtOption {
value: &self.value,
is_some: self.is_some,
}
}
/// Convert from `CtOption<T>` (or `&CtOption<T>`) to `CtOption<&T::Target>`, for types which
/// impl the [`Deref`] trait.
#[inline]
#[must_use]
pub fn as_deref(&self) -> CtOption<&T::Target>
where
T: Deref,
{
self.as_ref().map(Deref::deref)
}
/// Convert from `CtOption<T>` (or `&mut CtOption<T>`) to `CtOption<&mut T::Target>`, for types
/// which impl the [`DerefMut`] trait.
#[inline]
#[must_use]
pub fn as_deref_mut(&mut self) -> CtOption<&mut T::Target>
where
T: DerefMut,
{
self.as_mut().map(DerefMut::deref_mut)
}
/// 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]
#[must_use]
#[track_caller]
pub fn expect(self, msg: &str) -> T {
assert!(self.is_some().to_bool(), "{}", msg);
self.value
}
/// Return the contained value, consuming the `self` value, with `const fn` support.
///
/// Relies on a `Copy` bound which implies `!Drop` which is needed to be able to move out of
/// `self` in a `const fn` without `feature(const_precise_live_drops)`.
///
/// # 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]
#[must_use]
#[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]
#[must_use]
#[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.as_inner_unchecked()
}
/// Inserts `value` into the [`CtOption`], then returns a mutable reference to it.
///
/// If the option already contains a value, the old value is dropped.
pub fn insert(&mut self, value: T) -> &mut T {
self.value = value;
self.is_some = Choice::TRUE;
&mut self.value
}
/// Conditionally inserts `value` into the [`CtOption`] if the given condition holds.
pub fn insert_if(&mut self, value: &T, condition: Choice)
where
T: CtAssign,
{
self.value.ct_assign(value, condition);
self.is_some.ct_assign(&Choice::TRUE, condition);
}
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
///
/// This function exists to avoid ending up with ugly, verbose and/or bad handled conversions
/// from the [`CtOption`] wraps to an [`Option`] or [`Result`].
///
/// It's equivalent to the corresponding [`From`] impl, however this version is friendlier for
/// type inference.
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Option`] will do it anyway.
/// </div>
#[inline]
pub fn into_option(self) -> Option<T> {
if self.is_some.to_bool() {
Some(self.value)
} else {
None
}
}
/// Convert the [`CtOption`] wrapper into an [`Option`] in a `const fn`-friendly manner.
///
/// This is the equivalent of [`CtOption::into_option`] but is `const fn`-friendly by only
/// allowing `Copy` types which are implicitly `!Drop` and don't run into problems with
/// `const fn` and destructors.
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Option`] will do it anyway.
/// </div>
#[inline]
pub const fn into_option_copied(self) -> Option<T>
where
T: Copy,
{
// TODO(tarcieri): use `self.is_some().to_bool()` when MSRV is 1.86
if self.is_some.to_bool_vartime() {
Some(self.value)
} else {
None
}
}
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `Some`.
#[inline]
#[must_use]
pub const fn is_some(&self) -> Choice {
self.is_some
}
/// Returns [`Choice::TRUE`] if the option is the equivalent of a `None`.
#[inline]
#[must_use]
pub const fn is_none(&self) -> Choice {
self.is_some.not()
}
/// Returns `optb` if `self.is_some()` is [`Choice::TRUE`], otherwise returns a [`CtOption`]
/// where `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn and<U>(self, mut optb: CtOption<U>) -> CtOption<U> {
optb.is_some &= self.is_some;
optb
}
/// Calls the provided callback with the wrapped inner value, returning the resulting
/// [`CtOption`] value in the event that `self.is_some()` is [`Choice::TRUE`], or if not
/// returns a [`CtOption`] with `self.is_none()`.
///
/// Unlike [`Option`], the provided callback `f` is unconditionally evaluated to ensure
/// constant-time operation. This requires evaluating the function with "dummy" value of `T`
/// (e.g. if the [`CtOption`] was constructed with a supplied placeholder value and
/// [`Choice::FALSE`], the placeholder value will be provided).
#[inline]
#[must_use]
pub fn and_then<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> CtOption<U>,
{
let mut ret = f(self.value);
ret.is_some &= self.is_some;
ret
}
/// Obtain a reference to the inner value without first checking that `self.is_some()` is
/// [`Choice::TRUE`].
///
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
/// possible to use the safe combinator methods, and returns a reference to avoid issues with
/// `const fn` destructors.
///
/// <div class="warning">
/// <b>Use with care!</b>
///
/// This method does not ensure the `value` is actually valid. Callers of this method should
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn as_inner_unchecked(&self) -> &T {
&self.value
}
/// Calls the provided callback with the wrapped inner value, which computes a [`Choice`],
/// and updates `self.is_some()`.
///
/// It updates it to be [`Choice::FALSE`] in the event the returned choice is also false.
/// If it was [`Choice::FALSE`] to begin with, it will unconditionally remain that way.
#[inline]
#[must_use]
pub fn filter<P>(mut self, predicate: P) -> Self
where
P: FnOnce(&T) -> Choice,
{
self.is_some &= predicate(&self.value);
self
}
/// Apply an additional [`Choice`] requirement to `is_some`.
#[inline]
#[must_use]
pub const fn filter_by(mut self, is_some: Choice) -> Self {
self.is_some = self.is_some.and(is_some);
self
}
/// Maps a `CtOption<T>` to a `CtOption<U>` by unconditionally applying a function to the
/// contained `value`, but returning a new option value which inherits `self.is_some()`.
#[inline]
#[must_use]
pub fn map<U, F>(self, f: F) -> CtOption<U>
where
F: FnOnce(T) -> U,
{
CtOption::new(f(self.value), self.is_some)
}
/// Maps a `CtOption<T>` to a `U` value, eagerly evaluating the provided function, and returning
/// the supplied `default` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use = "if you don't need the returned value, use `if let` instead"]
pub fn map_or<U, F>(self, default: U, f: F) -> U
where
U: CtSelect,
F: FnOnce(T) -> U,
{
self.map(f).unwrap_or(default)
}
/// Maps a `CtOption<T>` to a `U` value, eagerly evaluating the provided function, precomputing
/// `U::default()` using the [`Default`] trait, and returning it in the event `self.is_some()`
/// is [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn map_or_default<U, F>(self, f: F) -> U
where
U: CtSelect + Default,
F: FnOnce(T) -> U,
{
self.map_or(U::default(), f)
}
/// Transforms a `CtOption<T>` into a `Result<T, E>`, mapping to `Ok(T)` if `self.is_some()` is
/// [`Choice::TRUE`], or mapping to the provided `err` in the event `self.is_some()` is
/// [`Choice::FALSE`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Result`] will do it anyway.
/// </div>
///
/// # Errors
/// - Returns `err` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
self.into_option().ok_or(err)
}
/// Transforms a `CtOption<T>` into a `Result<T, E>` by unconditionally calling the provided
/// callback value and using its result in the event `self.is_some()` is [`Choice::FALSE`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the [`Result`] will do it anyway.
/// </div>
///
/// # Errors
/// - Returns `err` in the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[allow(clippy::missing_errors_doc)]
pub fn ok_or_else<E, F>(self, err: F) -> Result<T, E>
where
F: FnOnce() -> E,
{
self.ok_or(err())
}
/// Returns `self` if `self.is_some()` is [`Choice::TRUE`], otherwise returns `optb`.
#[inline]
#[must_use]
pub fn or(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some | optb.is_some,
}
}
/// Obtain a copy of the inner value without first checking that `self.is_some()` is
/// [`Choice::TRUE`].
///
/// This method is primarily intended for use in `const fn` scenarios where it's not yet
/// possible to use the safe combinator methods, and uses a `Copy` bound to avoid issues with
/// `const fn` destructors.
///
/// <div class="warning">
/// <b>Use with care!</b>
///
/// This method does not ensure the `value` is actually valid. Callers of this method should
/// take great care to ensure that `self.is_some()` is checked elsewhere.
/// </div>
#[inline]
#[must_use]
pub const fn to_inner_unchecked(self) -> T
where
T: Copy,
{
self.value
}
/// Return the contained value, consuming the `self` value.
///
/// Use of this function is discouraged due to panic potential. Instead, prefer non-panicking
/// alternatives such as `unwrap_or` or `unwrap_or_default` which operate in constant-time.
///
/// As the final step of a sequence of constant-time operations, or in the event you are dealing
/// with a [`CtOption`] in a non-secret context where constant-time does not matter, you can
/// also convert to [`Option`] using `into_option` or the [`From`] impl on [`Option`]. Note
/// this introduces a branch and with it a small amount of timing variability. If possible try
/// to avoid this branch when writing constant-time code (e.g. use implicit rejection instead
/// of `Option`/`Result` to handle errors)
///
/// # Panics
/// In the event `self.is_some()` is [`Choice::FALSE`].
#[inline]
#[must_use]
#[track_caller]
pub fn unwrap(self) -> T {
assert!(
self.is_some.to_bool(),
"called `CtOption::unwrap()` on a value with `is_some` set to `Choice::FALSE`"
);
self.value
}
/// Return the contained value in the event `self.is_some()` is [`Choice::TRUE`], or if not,
/// uses a provided default.
#[inline]
#[must_use]
pub fn unwrap_or(self, default: T) -> T
where
T: CtSelect,
{
default.ct_select(&self.value, self.is_some)
}
/// Unconditionally computes `T::default()` using the [`Default`] trait, then returns either
/// the contained value if `self.is_some()` is [`Choice::TRUE`], or if it's [`Choice::FALSE`]
/// returns the previously computed default.
#[inline]
#[must_use]
pub fn unwrap_or_default(self) -> T
where
T: CtSelect + Default,
{
self.unwrap_or(T::default())
}
/// Returns an "is some" [`CtOption`] with the contained value from either `self` or `optb` in
/// the event exactly one of them has `self.is_some()` set to [`Choice::TRUE`], or else returns
/// a [`CtOption`] with `self.is_some()` set to [`Choice::FALSE`].
#[inline]
#[must_use]
pub fn xor(self, optb: CtOption<T>) -> CtOption<T>
where
T: CtSelect,
{
CtOption {
value: self.value.ct_select(&optb.value, self.is_none()),
is_some: self.is_some ^ optb.is_some,
}
}
/// Zips `self` with another [`CtOption`].
///
/// If `self.is_some() && other.is_some()`, this method returns a new [`CtOption`] for a 2-tuple
/// of their contents where `is_some()` is [`Choice::TRUE`].
///
/// Otherwise, a [`CtOption`] where `is_some()` is [`Choice::FALSE`] is returned.
pub fn zip<U>(self, other: CtOption<U>) -> CtOption<(T, U)> {
CtOption {
value: (self.value, other.value),
is_some: self.is_some & other.is_some,
}
}
/// Zips `self` and another `CtOption` with function `f`.
///
/// If `self.is_some() && other.is_some()`, this method returns a new [`CtOption`] for
/// the result of `f` applied to their inner values where `is_some()` is [`Choice::TRUE`].
///
/// Otherwise, a [`CtOption`] where `is_some()` is [`Choice::FALSE`] is returned.
pub fn zip_with<U, F, R>(self, other: CtOption<U>, f: F) -> CtOption<R>
where
F: FnOnce(T, U) -> R,
{
self.zip(other).map(|(a, b)| f(a, b))
}
}
impl<T> CtOption<&T> {
/// Maps a `CtOption<&T>` to `CtOption<T>` by copying the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
/// Maps a `CtOption<&T>` to `CtOption<T>` by cloning the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T> CtOption<&mut T> {
/// Maps a `CtOption<&mut T>` to `CtOption<T>` by copying the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub const fn copied(self) -> CtOption<T>
where
T: Copy,
{
CtOption {
value: *self.value,
is_some: self.is_some,
}
}
/// Maps a `CtOption<&mut T>` to `CtOption<T>` by cloning the contents of the option.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> CtOption<T>
where
T: Clone,
{
CtOption {
value: self.value.clone(),
is_some: self.is_some,
}
}
}
impl<T: CtAssign> CtAssign for CtOption<T> {
fn ct_assign(&mut self, other: &Self, choice: Choice) {
self.value.ct_assign(&other.value, choice);
self.is_some.ct_assign(&other.is_some, choice);
}
}
impl<T: CtAssign> CtAssignSlice for CtOption<T> {}
impl<T: CtEq> CtEq for CtOption<T> {
#[inline]
fn ct_eq(&self, other: &CtOption<T>) -> Choice {
(self.is_some & other.is_some & self.value.ct_eq(&other.value))
| (self.is_none() & other.is_none())
}
}
impl<T: CtEq> CtEqSlice for CtOption<T> {}
impl<T: CtSelect> CtSelect for CtOption<T> {
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
Self {
value: self.value.ct_select(&other.value, choice),
is_some: self.is_some.ct_select(&other.is_some, choice),
}
}
}
impl<T: Default> Default for CtOption<T> {
fn default() -> Self {
Self::none()
}
}
/// Convert the [`CtOption`] wrapper into an [`Option`], depending on whether
/// [`CtOption::is_some`] is a truthy or falsy [`Choice`].
///
/// <div class="warning">
/// <b>Warning: variable-time!</b>
///
/// This implementation doesn't intend to be constant-time nor try to protect the leakage of the
/// `T` value since the `Option` will do it anyway.
/// </div>
impl<T> From<CtOption<T>> for Option<T> {
fn from(src: CtOption<T>) -> Option<T> {
src.into_option()
}
}
/// NOTE: in order to be able to unwrap the `subtle::CtOption` we rely on a `Default` bound in
/// order to have a placeholder value, and `ConditionallySelectable` to be able to use `unwrap_or`.
#[cfg(feature = "subtle")]
impl<T> From<subtle::CtOption<T>> for CtOption<T>
where
T: subtle::ConditionallySelectable + Default,
{
#[inline]
fn from(src: subtle::CtOption<T>) -> CtOption<T> {
let is_some = src.is_some();
CtOption {
value: src.unwrap_or(Default::default()),
is_some: is_some.into(),
}
}
}
#[cfg(feature = "subtle")]
impl<T> From<CtOption<T>> for subtle::CtOption<T> {
#[inline]
fn from(src: CtOption<T>) -> subtle::CtOption<T> {
subtle::CtOption::new(src.value, src.is_some.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConditionallySelectable for CtOption<T>
where
T: Copy, // `ConditionallySelectable` supertrait bound
Self: CtSelect,
{
#[inline]
fn conditional_select(a: &Self, b: &Self, choice: subtle::Choice) -> Self {
CtSelect::ct_select(a, b, choice.into())
}
}
#[cfg(feature = "subtle")]
impl<T> subtle::ConstantTimeEq for CtOption<T>
where
Self: CtEq,
{
#[inline]
fn ct_eq(&self, other: &Self) -> subtle::Choice {
CtEq::ct_eq(self, other).into()
}
}
#[cfg(test)]
mod tests {
use crate::{Choice, CtEq, CtOption, CtSelect};
/// Example wrapped value for testing
const VALUE: u8 = 42;
/// Example option which is like `Option::Some`
const SOME: CtOption<u8> = CtOption::new(VALUE, Choice::TRUE);
/// Example option which is like `Option::None`
const NONE: CtOption<u8> = CtOption::new(VALUE, Choice::FALSE);
/// Another option containing a different value
const OTHER: CtOption<u8> = CtOption::new(VALUE + 1, Choice::TRUE);
/// Dummy error type
#[derive(Debug, Eq, PartialEq)]
struct Error;
#[test]
fn map_macro() {
assert!(map!(NONE, u16::from).is_none().to_bool());
assert_eq!(map!(SOME, u16::from).unwrap(), u16::from(VALUE));
}
#[test]
fn unwrap_or_macro() {
// Don't actually use this! It's just a test function implemented in variable-time
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn select_vartime(a: &u8, b: &u8, choice: Choice) -> u8 {
if choice.to_bool_vartime() { *b } else { *a }
}
assert_eq!(
unwrap_or!(NONE, OTHER.unwrap(), select_vartime),
OTHER.unwrap()
);
assert_eq!(unwrap_or!(SOME, OTHER.unwrap(), select_vartime), VALUE);
}
#[test]
fn ct_eq() {
assert!(NONE.ct_eq(&NONE).to_bool());
assert!(NONE.ct_ne(&SOME).to_bool());
assert!(SOME.ct_ne(&NONE).to_bool());
assert!(SOME.ct_eq(&SOME).to_bool());
assert!(SOME.ct_ne(&OTHER).to_bool());
}
#[test]
fn ct_select() {
assert!(NONE.ct_select(&SOME, Choice::FALSE).is_none().to_bool());
assert!(NONE.ct_select(&SOME, Choice::TRUE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::FALSE).ct_eq(&SOME).to_bool());
assert!(SOME.ct_select(&NONE, Choice::TRUE).is_none().to_bool());
}
#[test]
fn default() {
assert!(NONE.ct_eq(&CtOption::default()).to_bool());
}
#[test]
fn expect_some() {
assert_eq!(SOME.expect("should succeed"), VALUE);
}
#[test]
#[should_panic]
fn expect_none() {
let _ = NONE.expect("should panic");
}
#[test]
fn into_option() {
assert_eq!(SOME.into_option(), Some(VALUE));
assert_eq!(NONE.into_option(), None);
}
#[test]
fn into_option_copied() {
assert_eq!(SOME.into_option_copied(), Some(VALUE));
assert_eq!(NONE.into_option_copied(), None);
}
#[test]
fn is_some() {
assert!(SOME.is_some().to_bool());
assert!(!NONE.is_some().to_bool());
}
#[test]
fn is_none() {
assert!(!SOME.is_none().to_bool());
assert!(NONE.is_none().to_bool());
}
#[test]
fn and() {
assert!(SOME.and(NONE).is_none().to_bool());
assert_eq!(SOME.and(OTHER).unwrap(), OTHER.unwrap());
}
#[test]
fn and_then() {
assert!(NONE.and_then(|_| NONE).is_none().to_bool());
assert!(NONE.and_then(|_| SOME).is_none().to_bool());
let ret = SOME.and_then(|value| {
assert_eq!(VALUE, value);
OTHER
});
assert!(ret.ct_eq(&OTHER).to_bool());
}
#[test]
fn filter() {
assert!(NONE.filter(|_| Choice::TRUE).ct_eq(&NONE).to_bool());
assert!(NONE.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
assert!(SOME.filter(|_| Choice::FALSE).ct_eq(&NONE).to_bool());
let ret = SOME.filter(|&value| {
assert_eq!(VALUE, value);
Choice::TRUE
});
assert_eq!(ret.unwrap(), VALUE);
}
#[test]
fn filter_by() {
assert!(NONE.filter_by(Choice::FALSE).is_none().to_bool());
assert!(NONE.filter_by(Choice::TRUE).is_none().to_bool());
assert!(SOME.filter_by(Choice::FALSE).ct_eq(&NONE).to_bool());
assert_eq!(SOME.filter_by(Choice::TRUE).unwrap(), VALUE);
}
#[test]
fn insert() {
let mut example = NONE;
assert!(example.is_none().to_bool());
let ret = example.insert(42);
assert_eq!(ret, &42);
assert!(example.is_some().to_bool());
}
#[test]
fn insert_if() {
let mut example = NONE;
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::FALSE);
assert!(example.is_none().to_bool());
example.insert_if(&42, Choice::TRUE);
assert_eq!(example.unwrap(), 42);
}
#[test]
fn map() {
assert!(NONE.map(|value| value + 1).ct_eq(&NONE).to_bool());
assert!(SOME.map(|value| value + 1).ct_eq(&OTHER).to_bool());
}
#[test]
fn map_or() {
let example = 52;
assert_eq!(NONE.map_or(example, |value| value + 1), example);
assert_eq!(SOME.map_or(example, |value| value + 1), VALUE + 1);
}
#[test]
fn map_or_default() {
assert_eq!(NONE.map_or_default(|value| value + 1), Default::default());
assert_eq!(SOME.map_or_default(|value| value + 1), VALUE + 1);
}
#[test]
fn ok_or() {
assert_eq!(NONE.ok_or(Error), Err(Error));
assert_eq!(SOME.ok_or(Error), Ok(VALUE));
}
#[test]
fn ok_or_else() {
assert_eq!(NONE.ok_or_else(|| Error), Err(Error));
assert_eq!(SOME.ok_or_else(|| Error), Ok(VALUE));
}
#[test]
fn or() {
assert!(NONE.or(NONE).is_none().to_bool());
assert!(SOME.or(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.or(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.or(OTHER).ct_eq(&SOME).to_bool());
}
#[test]
fn some() {
assert!(CtOption::some(VALUE).ct_eq(&SOME).to_bool());
}
#[test]
fn unwrap_some() {
assert_eq!(SOME.unwrap(), VALUE);
}
#[test]
#[should_panic]
fn unwrap_none() {
let _ = NONE.unwrap();
}
#[test]
fn unwrap_or() {
let example = 52;
assert_eq!(NONE.unwrap_or(example), example);
assert_eq!(SOME.unwrap_or(example), VALUE);
}
#[test]
fn unwrap_or_default() {
assert_eq!(NONE.unwrap_or_default(), Default::default());
assert_eq!(SOME.unwrap_or_default(), VALUE);
}
#[test]
fn xor() {
assert!(NONE.xor(NONE).is_none().to_bool());
assert!(SOME.xor(NONE).ct_eq(&SOME).to_bool());
assert!(NONE.xor(SOME).ct_eq(&SOME).to_bool());
assert!(SOME.xor(OTHER).is_none().to_bool());
}
#[test]
fn zip() {
assert!(NONE.zip(NONE).is_none().to_bool());
assert!(NONE.zip(SOME).is_none().to_bool());
assert!(SOME.zip(NONE).is_none().to_bool());
assert_eq!(SOME.zip(OTHER).unwrap(), (SOME.unwrap(), OTHER.unwrap()));
}
#[test]
fn zip_with() {
assert!(NONE.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert!(NONE.zip_with(SOME, |a, b| a + b).is_none().to_bool());
assert!(SOME.zip_with(NONE, |a, b| a + b).is_none().to_bool());
assert_eq!(
SOME.zip_with(OTHER, |a, b| a + b).unwrap(),
SOME.unwrap() + OTHER.unwrap()
);
}
}
+140
View File
@@ -0,0 +1,140 @@
#![no_std]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
)]
#![forbid(unsafe_code)] // `unsafe` should go in `cmov`
#![warn(
clippy::arithmetic_side_effects,
clippy::integer_division_remainder_used,
clippy::panic
)]
//! # API Design
//!
//! ## [`Choice`]: constant-time analogue for [`bool`]
//! Values of this type are one of either [`Choice::FALSE`] or [`Choice::TRUE`].
//!
//! To achieve constant-time operation, `Choice` is ultimately used in combination with special
//! CPU-specific constant-time predication instructions implemented by the [`cmov`] crate
//! (with a portable "best effort" fallback that cannot provide guarantees).
//!
//! It additionally uses various methods to hint to the compiler that it should avoid inserting
//! branches based on its value where it otherwise would if `bool` were used instead, but cannot
//! provide guarantees in this regard.
//!
//! ## [`CtOption`]: constant-time analogue for [`Option`]
//! The core `Option` type is typically great for representing the conditional absence or presence
//! of a value, and provides a number of handy combinators for operating on them.
//!
//! However, it has a rather fundamental flaw when constant-time is desirable: its combinators are
//! lazily evaluated. To ensure constant-time operation, all combinators must be eagerly evaluated
//! so they aren't conditionally executed based on the value's presence.
//!
//! `CtOption` instead carries a `Choice` along with a value, which makes it possible to do
//! something it isn't with `Option`: evaluate combinators eagerly instead of lazily, running the
//! same functions regardless of the value's effective presence or absence.
//!
//! ## [`CtAssign`]: constant-time additional assignment using [predication]
//! Support for conditionally assigning to a type or slices thereof (for types which impl the
//! [`CtAssignSlice`] trait) based on a provided condition value.
//!
//! Uses predication instructions or a portable simulation thereof to perform constant-time
//! conditional assignment based ona [`Choice`].
//!
//! *NOTE: for `subtle` users, this trait provides the equivalent of the
//! `ConditionallySelectable::conditional_assign` method, but as its own trait without a `Sized`
//! bound so it can also be impl'd for slices*
//!
//! ## [`CtEq`]: constant-time analogue for [`PartialEq`]/[`Eq`]
//! Equality testing often short circuits for performance reasons, but when comparing values in
//! constant-time such short-circuiting is forbidden.
//!
//! The `CtEq` trait is a replacement for these scenarios. It's impl'd for several core types
//! including unsigned and signed integers as well as slices and arrays. It returns a `Choice`
//! as opposed to a `bool`], following the standard practice in this crate.
//!
//! *NOTE: for `subtle` users, this is the equivalent of the `ConstantTimeEq` trait*
//!
//! ## [`CtSelect`]: constant-time [predication]
//! Predication in computer architecture describes methods for conditionally modifying state
//! using non-branch instructions which perform conditional modifications based on a *predicate*
//! or boolean value, in the design of this library a `Choice`.
//!
//! The `CtSelect` trait provides methods for performing conditional selection between two
//! different inputs and returning a new one.
//!
//! *NOTE: for `subtle` users, this is the equivalent of the `ConditionallySelectable` trait*
//!
//! [predication]: https://en.wikipedia.org/wiki/Predication_(computer_architecture)
//!
//! # [`subtle`] interop
//!
//! When the `subtle` feature of this crate is enabled, bidirectional [`From`] impls are available
//! for the following types:
//!
//! - [`Choice`] <=> [`subtle::Choice`]
//! - [`CtOption`] <=> [`subtle::CtOption`]
//!
//! This makes it possible to use `ctutils` in a codebase where other dependencies are using
//! `subtle`.
//!
//! # [`subtle`] migration guide
//!
//! This library presents an API which is largely the same shape as `subtle` and amenable to mostly
//! mechanical find-and-replace updates. Using the above `subtle` interop, you can also migrate
//! incrementally by converting `ctutils::Choice` <=> `subtle::Choice` and `ctutils::CtOption`
//! <=> `subtle::CtOption`.
//!
//! The following substitutions can be used to perform the migration:
//!
//! 1. `subtle` => `ctutils`
//! 2. `ConstantTimeEq` => `CtEq`, `ConstantTimeGreater` => `CtGt`, `ConstantTimeLess` => `CtLt`.
//! - These all use the same `ct_eq`/`ct_gt`/`ct_lt` method names as `subtle` with the same type
//! signatures, so only the trait names need to be changed.
//! 3. `ConditionallySelectable` => `CtSelect`, `conditional_select` => `ct_select`.
//! - Note that `ct_select` has a slightly different type signature in that it accepts `&self`
//! as the LHS argument. This needs to be changed in the `impl` blocks, but call sites are
//! compatible if you update the method name alone because it's valid "fully qualified syntax".
//! Changing them from `T::conditional_select(&a, &b, choice)` => `a.ct_select(&b, choice)`
//! may still be nice for brevity.
//! - `conditional_assign` => `CtAssign::ct_assign`: this one will require some manual work as
//! this method has been split out of `ConditionallySelectable` into its own `CtAssign` trait,
//! which makes it possible to impl on DSTs like slices which can't be returned from a select
//! operation because they're `!Sized`.
//! 4. `ConditionallyNegatable` => `CtNeg`, `conditional_negate` => `ct_neg`
//!
//! ## `CtOption` notes
//!
//! A notable semantic change from `subtle` is combinators like `CtOption::map` no longer have a
//! `Default` bound and will call the provided function with the contained value unconditionally.
//!
//! This means whatever value was provided at the time the `CtOption` was constructed now needs to
//! uphold whatever invariants the provided function is expecting.
//!
//! Code which previously constructed a `CtOption` with an invalid inner value that worked with
//! `subtle` because the `Default` value upheld these invariants might break when the provided
//! function is now called with the invalid inner value.
//!
//! See also: [dalek-cryptography/subtle#63](https://github.com/dalek-cryptography/subtle/issues/63)
#[cfg(feature = "alloc")]
extern crate alloc;
mod choice;
mod ct_option;
mod traits;
pub use choice::Choice;
pub use ct_option::CtOption;
pub use traits::{
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, CtSelectUsingCtAssign},
};
+13
View File
@@ -0,0 +1,13 @@
//! Trait definitions.
//!
//! These are each in their own module so we can also define tests for the core types they're impl'd
//! on in the same module.
pub(crate) mod ct_assign;
pub(crate) mod ct_eq;
pub(crate) mod ct_find;
pub(crate) mod ct_gt;
pub(crate) mod ct_lookup;
pub(crate) mod ct_lt;
pub(crate) mod ct_neg;
pub(crate) mod ct_select;
+213
View File
@@ -0,0 +1,213 @@
use crate::Choice;
use cmov::Cmov;
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
},
};
#[cfg(feature = "subtle")]
use crate::CtSelect;
#[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`], [`NonZeroI128`]
/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`],, [`NonZeroUsize`]
/// - [`cmp::Ordering`]
/// - [`Choice`]
/// - `[T]` and `[T; N]` where `T` impls [`CtAssignSlice`], which the previously mentioned
/// types all do.
pub trait CtAssign<Rhs: ?Sized = Self> {
/// 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]` which is `?Sized`.
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<T: CtAssignSlice> 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());
}
}
)+
};
}
// NOTE: impls `CtAssign` and `CtAssignSlice`
impl_ct_assign_slice_with_cmov!(
i8,
i16,
i32,
i64,
i128,
u8,
u16,
u32,
u64,
u128,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize,
cmp::Ordering
);
impl_ct_assign_with_cmov!(isize, usize);
impl CtAssignSlice for isize {}
impl CtAssignSlice for usize {}
impl<T, const N: usize> CtAssign for [T; N]
where
T: CtAssignSlice,
{
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.as_mut_slice().ct_assign(rhs, choice);
}
}
impl<T, const N: usize> CtAssignSlice for [T; N] where T: CtAssignSlice {}
#[cfg(feature = "subtle")]
impl CtAssign for subtle::Choice {
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
*self = Self::ct_select(self, rhs, choice);
}
}
#[cfg(feature = "subtle")]
impl<T> CtAssign for subtle::CtOption<T>
where
T: Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
use subtle::ConditionallySelectable as _;
self.conditional_assign(rhs, choice.into());
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtAssign, CtAssignSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtAssign for Box<T>
where
T: CtAssign,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
(**self).ct_assign(rhs, choice);
}
}
impl<T> CtAssign for Box<[T]>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.ct_assign(&**rhs, choice);
}
}
impl<T> CtAssign<[T]> for Box<[T]>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &[T], choice: Choice) {
(**self).ct_assign(rhs, choice);
}
}
impl<T> CtAssign for Vec<T>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &Self, choice: Choice) {
self.ct_assign(rhs.as_slice(), choice);
}
}
impl<T> CtAssign<[T]> for Vec<T>
where
T: CtAssignSlice,
{
#[inline]
#[track_caller]
fn ct_assign(&mut self, rhs: &[T], choice: Choice) {
self.as_mut_slice().ct_assign(rhs, choice);
}
}
}
+327
View File
@@ -0,0 +1,327 @@
use crate::Choice;
use cmov::CmovEq;
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroU8, NonZeroU16,
NonZeroU32, NonZeroU64, NonZeroU128,
},
};
#[cfg(feature = "subtle")]
use crate::CtOption;
/// Constant-time equality: like `(Partial)Eq` with [`Choice`] instead of [`bool`].
///
/// 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<Rhs = Self>
where
Rhs: ?Sized,
{
/// Determine if `self` is equal to `other` in constant-time.
#[must_use]
fn ct_eq(&self, other: &Rhs) -> Choice;
/// Determine if `self` is NOT equal to `other` in constant-time.
#[must_use]
fn ct_ne(&self, other: &Rhs) -> Choice {
!self.ct_eq(other)
}
}
/// 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]` which is `?Sized`.
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<T: CtEqSlice> 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),+ ) => {
$(
impl CtEq for $ty {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
let mut ret = Choice::FALSE;
self.cmoveq(other, 1, &mut ret.0);
ret
}
}
)+
};
}
/// 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);
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<T>` by calling `NonZero::get`.
macro_rules! impl_ct_eq_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtEq for $ty {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.get().ct_eq(&other.get())
}
}
impl CtEqSlice for $ty {}
)+
};
}
impl_ct_eq_for_nonzero_integer!(
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128
);
impl CtEq for cmp::Ordering {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
// `Ordering` is `repr(i8)`, which has a `CtEq` impl
(*self as i8).ct_eq(&(*other as i8))
}
}
impl CtEqSlice for cmp::Ordering {}
impl<T, const N: usize> CtEq for [T; N]
where
T: CtEqSlice,
{
#[inline]
fn ct_eq(&self, other: &[T; N]) -> Choice {
self.as_slice().ct_eq(other.as_slice())
}
}
impl<T, const N: usize> CtEqSlice for [T; N] where T: CtEqSlice {}
#[cfg(feature = "subtle")]
impl CtEq for subtle::Choice {
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
self.unwrap_u8().ct_eq(&other.unwrap_u8())
}
}
#[cfg(feature = "subtle")]
impl<T> CtEq for subtle::CtOption<T>
where
T: CtEq + Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_eq(&self, other: &Self) -> Choice {
CtOption::from(*self).ct_eq(&CtOption::from(*other))
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtEq, CtEqSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtEq for Box<T>
where
T: CtEq,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
(**self).ct_eq(rhs)
}
}
impl<T> CtEq for Box<[T]>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
self.ct_eq(&**rhs)
}
}
impl<T> CtEq<[T]> for Box<[T]>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &[T]) -> Choice {
(**self).ct_eq(rhs)
}
}
impl<T> CtEq for Vec<T>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &Self) -> Choice {
self.ct_eq(rhs.as_slice())
}
}
impl<T> CtEq<[T]> for Vec<T>
where
T: CtEqSlice,
{
#[inline]
#[track_caller]
fn ct_eq(&self, rhs: &[T]) -> Choice {
self.as_slice().ct_eq(rhs)
}
}
}
#[cfg(test)]
mod tests {
use super::CtEq;
use core::cmp::Ordering;
macro_rules! truth_table {
($a:expr, $b:expr, $c:expr) => {
assert!($a.ct_eq(&$b).to_bool());
assert!(!$a.ct_eq(&$c).to_bool());
assert!(!$b.ct_eq(&$c).to_bool());
assert!(!$a.ct_ne(&$b).to_bool());
assert!($a.ct_ne(&$c).to_bool());
assert!($b.ct_ne(&$c).to_bool());
};
}
macro_rules! ct_eq_test_unsigned {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a = <$ty>::MAX;
let b = <$ty>::MAX;
let c = <$ty>::MIN;
truth_table!(a, b, c);
}
};
}
macro_rules! ct_eq_test_signed {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a = <$ty>::MAX;
let b = <$ty>::MAX;
let c = <$ty>::MIN;
truth_table!(a, b, c);
}
};
}
ct_eq_test_unsigned!(u8, u8_ct_eq);
ct_eq_test_unsigned!(u16, u16_ct_eq);
ct_eq_test_unsigned!(u32, u32_ct_eq);
ct_eq_test_unsigned!(u64, u64_ct_eq);
ct_eq_test_unsigned!(u128, u128_ct_eq);
ct_eq_test_unsigned!(usize, usize_ct_eq);
ct_eq_test_signed!(i8, i8_ct_eq);
ct_eq_test_signed!(i16, i16_ct_eq);
ct_eq_test_signed!(i32, i32_ct_eq);
ct_eq_test_signed!(i64, i64_ct_eq);
ct_eq_test_signed!(i128, i128_ct_eq);
ct_eq_test_signed!(isize, isize_ct_eq);
#[test]
fn array_ct_eq() {
let a = [1u64, 2, 3];
let b = [1u64, 2, 3];
let c = [1u64, 2, 4];
truth_table!(a, b, c);
}
#[test]
fn ordering_ct_eq() {
let a = Ordering::Greater;
let b = Ordering::Greater;
let c = Ordering::Less;
truth_table!(a, b, c);
}
#[test]
fn slice_ct_eq() {
let a: &[u64] = &[1, 2, 3];
let b: &[u64] = &[1, 2, 3];
let c: &[u64] = &[1, 2, 4];
truth_table!(a, b, c);
// Length mismatches
assert!(a.ct_ne(&[]).to_bool());
assert!(a.ct_ne(&[1, 2]).to_bool());
}
}
+127
View File
@@ -0,0 +1,127 @@
use crate::{Choice, CtAssign, CtOption};
#[cfg(doc)]
use core::iter::Iterator;
/// Constant-time equivalent of [`Iterator::find`], which can search a collection by iterating over
/// every element and applying the given predicate to each item, then selecting the first matching
/// entry.
pub trait CtFind<T: CtAssign> {
/// Iterate through every `T` item in `&self`, applying the given `predicate` which can select
/// a specific item by returning [`Choice::TRUE`].
///
/// The first item where `predicate` returns [`Choice::TRUE`] is selected, or the [`CtOption`]
/// equivalent of `None` is returned if the `predicate` returns [`Choice::FALSE`] for all items.
#[must_use]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice;
}
impl<T> CtFind<T> for [T]
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
let mut ret = CtOption::none();
for item in self {
ret.insert_if(item, predicate(item) & ret.is_none());
}
ret
}
}
impl<T, const N: usize> CtFind<T> for [T; N]
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
self.as_slice().ct_find(predicate)
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{Choice, CtAssign, CtFind, CtOption};
use ::alloc::{boxed::Box, vec::Vec};
impl<T> CtFind<T> for Box<[T]>
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
(**self).ct_find(predicate)
}
}
#[cfg(feature = "alloc")]
impl<T> CtFind<T> for Vec<T>
where
T: CtAssign + Default,
{
#[inline]
fn ct_find<P>(&self, predicate: P) -> CtOption<T>
where
P: Fn(&T) -> Choice,
{
self.as_slice().ct_find(predicate)
}
}
}
#[cfg(test)]
mod tests {
use super::CtFind;
mod array {
use super::*;
use crate::{CtEq, CtGt};
const ARRAY: [u8; 6] = [0, 0, 0, 1, 2, 3];
#[test]
fn ct_find() {
// Find the first nonzero even number
assert_eq!(
ARRAY.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
2
);
// Predicate where nothing matches
assert!(ARRAY.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
}
}
mod slice {
use super::*;
use crate::{CtEq, CtGt};
const SLICE: &[u8] = &[0, 0, 0, 1, 2, 3];
#[test]
fn ct_find() {
// Find the first nonzero even number
assert_eq!(
SLICE.ct_find(|n| n.ct_ne(&0) & (n & 1).ct_eq(&0)).unwrap(),
2
);
// Predicate where nothing matches
assert!(SLICE.ct_find(|n| n.ct_gt(&3)).is_none().to_bool());
}
}
}
+102
View File
@@ -0,0 +1,102 @@
use crate::Choice;
use core::{
cmp,
num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize},
};
/// Constant time greater than.
pub trait CtGt {
/// Compute whether `self > other` in constant time.
#[must_use]
fn ct_gt(&self, other: &Self) -> Choice;
}
// Impl `CtGt` using overflowing subtraction
macro_rules! impl_unsigned_ct_gt {
( $($uint:ty),+ ) => {
$(
impl CtGt for $uint {
#[inline]
fn ct_gt(&self, other: &Self) -> Choice {
let (_, overflow) = other.overflowing_sub(*self);
Choice(overflow.into())
}
}
)+
};
}
impl_unsigned_ct_gt!(u8, u16, u32, u64, u128, usize);
/// Impl `CtGt` for `NonZero<T>` by calling `NonZero::get`.
macro_rules! impl_ct_gt_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtGt for $ty {
#[inline]
fn ct_gt(&self, other: &Self) -> Choice {
self.get().ct_gt(&other.get())
}
}
)+
};
}
impl_ct_gt_for_nonzero_integer!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
impl CtGt for cmp::Ordering {
#[inline]
#[allow(clippy::arithmetic_side_effects, clippy::cast_sign_loss)]
fn ct_gt(&self, other: &Self) -> Choice {
// No impl of `CtGt` for `i8`, so use `u8`
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
(a as u8).ct_gt(&(b as u8))
}
}
#[cfg(test)]
mod tests {
use super::CtGt;
use core::cmp::Ordering;
/// Test `CtGt`
macro_rules! ct_gt_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use super::CtGt;
#[test]
fn ct_gt() {
let a = <$int>::MIN;
let b = <$int>::MAX;
assert!(!a.ct_gt(&a).to_bool());
assert!(!a.ct_gt(&b).to_bool());
assert!(b.ct_gt(&a).to_bool());
}
}
)+
};
}
ct_gt_tests!(u8, u16, u32, u64, u128, usize);
#[test]
fn ordering() {
assert!(!Ordering::Equal.ct_gt(&Ordering::Equal).to_bool());
assert!(!Ordering::Less.ct_gt(&Ordering::Greater).to_bool());
assert!(Ordering::Greater.ct_gt(&Ordering::Less).to_bool());
}
}
+137
View File
@@ -0,0 +1,137 @@
use crate::{CtAssign, CtEq, CtOption};
use core::ops::AddAssign;
#[cfg(doc)]
use core::ops::Index;
/// Constant-time lookup by index, similar to the [`Index`] trait, but returning an owned result in
/// constant-time.
pub trait CtLookup<Idx> {
/// Output type returned by the lookup operation.
type Output: CtAssign;
/// Attempt to retrieve the item at the given `index`, either returning it or the [`CtOption`]
/// equivalent of [`None`] if the `index` was out-of-bounds.
#[must_use]
fn ct_lookup(&self, index: Idx) -> CtOption<Self::Output>;
}
impl<T, Idx> CtLookup<Idx> for [T]
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
let mut ret = CtOption::none();
let mut i = Idx::default();
for item in self {
ret.insert_if(item, i.ct_eq(&index));
// TODO(tarcieri): ideally we'd prevent overflow here but there's no core `CheckedAdd`
i += Idx::from(1u8);
}
ret
}
}
impl<T, Idx, const N: usize> CtLookup<Idx> for [T; N]
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
self.as_slice().ct_lookup(index)
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::{AddAssign, CtAssign, CtEq, CtLookup, CtOption};
use ::alloc::{boxed::Box, vec::Vec};
impl<T, Idx> CtLookup<Idx> for Box<[T]>
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
(**self).ct_lookup(index)
}
}
impl<T, Idx> CtLookup<Idx> for Vec<T>
where
T: CtAssign + Default,
Idx: AddAssign + CtEq + Default + From<u8>,
{
type Output = T;
#[inline]
fn ct_lookup(&self, index: Idx) -> CtOption<T> {
self.as_slice().ct_lookup(index)
}
}
}
#[cfg(test)]
mod tests {
mod array {
use crate::CtLookup;
const EXAMPLE: [u8; 3] = [1, 2, 3];
#[test]
fn ct_lookup_u32() {
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
}
#[test]
fn ct_lookup_usize() {
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
}
}
mod slice {
use crate::CtLookup;
const EXAMPLE: &[u8] = &[1, 2, 3];
#[test]
fn ct_lookup_u32() {
assert_eq!(EXAMPLE.ct_lookup(0u32).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1u32).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2u32).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3u32).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4u32).is_none().to_bool());
}
#[test]
fn ct_lookup_usize() {
assert_eq!(EXAMPLE.ct_lookup(0usize).unwrap(), 1);
assert_eq!(EXAMPLE.ct_lookup(1usize).unwrap(), 2);
assert_eq!(EXAMPLE.ct_lookup(2usize).unwrap(), 3);
assert!(EXAMPLE.ct_lookup(3usize).is_none().to_bool());
assert!(EXAMPLE.ct_lookup(4usize).is_none().to_bool());
}
}
}
+111
View File
@@ -0,0 +1,111 @@
use crate::Choice;
use core::{
cmp,
num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize},
};
/// Constant time less than.
pub trait CtLt {
/// Compute whether `self < other` in constant time.
#[must_use]
fn ct_lt(&self, other: &Self) -> Choice;
}
// Impl `CtLt` using overflowing subtraction
macro_rules! impl_unsigned_ct_lt {
( $($uint:ty),+ ) => {
$(
impl CtLt for $uint {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
let (_, overflow) = self.overflowing_sub(*other);
Choice(overflow.into())
}
}
)+
};
}
impl_unsigned_ct_lt!(u8, u16, u32, u64, u128, usize);
/// Impl `CtLt` for `NonZero<T>` by calling `NonZero::get`.
macro_rules! impl_ct_lt_for_nonzero_integer {
( $($ty:ty),+ ) => {
$(
impl CtLt for $ty {
#[inline]
fn ct_lt(&self, other: &Self) -> Choice {
self.get().ct_lt(&other.get())
}
}
)+
};
}
impl_ct_lt_for_nonzero_integer!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
impl CtLt for cmp::Ordering {
#[inline]
#[allow(clippy::arithmetic_side_effects, clippy::cast_sign_loss)]
fn ct_lt(&self, other: &Self) -> Choice {
// No impl of `CtLt` for `i8`, so use `u8`
// TODO(tarcieri): use `cast_signed` when MSRV is 1.87
let a = (*self as i8) + 1;
let b = (*other as i8) + 1;
// TODO(tarcieri): use `cast_unsigned` when MSRV is 1.87
(a as u8).ct_lt(&(b as u8))
}
}
#[cfg(test)]
mod tests {
use super::CtLt;
use core::cmp::Ordering;
#[test]
fn ct_lt() {
let a = 42u64;
let b = 43u64;
assert!(!a.ct_lt(&a).to_bool());
assert!(a.ct_lt(&b).to_bool());
assert!(!b.ct_lt(&a).to_bool());
}
/// Test `CtLt`
macro_rules! ct_lt_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use super::CtLt;
#[test]
fn ct_gt() {
let a = <$int>::MIN;
let b = <$int>::MAX;
assert!(!a.ct_lt(&a).to_bool());
assert!(a.ct_lt(&b).to_bool());
assert!(!b.ct_lt(&a).to_bool());
}
}
)+
};
}
ct_lt_tests!(u8, u16, u32, u64, u128, usize);
#[test]
fn ordering() {
assert!(!Ordering::Equal.ct_lt(&Ordering::Equal).to_bool());
assert!(Ordering::Less.ct_lt(&Ordering::Greater).to_bool());
assert!(!Ordering::Greater.ct_lt(&Ordering::Less).to_bool());
}
}
+167
View File
@@ -0,0 +1,167 @@
use crate::{Choice, CtAssign, CtSelect};
use core::num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
};
/// Constant-time conditional negation: negates a value when `choice` is [`Choice::TRUE`].
pub trait CtNeg: Sized {
/// Conditionally negate `self`, returning `-self` if `choice` is [`Choice::TRUE`], or `self`
/// otherwise.
#[must_use]
fn ct_neg(&self, choice: Choice) -> Self;
/// Conditionally negate `self` in-place, replacing it with `-self` if `choice` is
/// [`Choice::TRUE`].
fn ct_neg_assign(&mut self, choice: Choice) {
*self = self.ct_neg(choice);
}
}
// Impl `CtNeg` for a signed integer (`i*`) type which impls `CtSelect`
macro_rules! impl_signed_ct_neg {
( $($int:ty),+ ) => {
$(
impl CtNeg for $int {
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_neg(&self, choice: Choice) -> Self {
self.ct_select(&-*self, choice)
}
#[inline]
#[allow(clippy::arithmetic_side_effects)]
fn ct_neg_assign(&mut self, choice: Choice) {
self.ct_assign(&-*self, choice)
}
}
)+
};
}
// Impl `CtNeg` for an unsigned integer (`u*`) type which impls `CtSelect`
macro_rules! impl_unsigned_ct_neg {
( $($uint:ty),+ ) => {
$(
impl CtNeg for $uint {
#[inline]
fn ct_neg(&self, choice: Choice) -> Self {
self.ct_select(&self.wrapping_neg(), choice)
}
#[inline]
fn ct_neg_assign(&mut self, choice: Choice) {
self.ct_assign(&self.wrapping_neg(), choice)
}
}
)+
};
}
impl_signed_ct_neg!(
i8,
i16,
i32,
i64,
i128,
isize,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize
);
impl_unsigned_ct_neg!(u8, u16, u32, u64, u128, usize);
/// Unfortunately `NonZeroU*` doesn't support `wrapping_neg` for some reason (but `NonZeroI*` does),
/// even though the wrapping negation of any non-zero integer should also be non-zero.
///
/// So we need a special case just for `NonZeroU*`, at least for now.
macro_rules! impl_ct_neg_for_unsigned_nonzero {
( $($nzuint:ident),+ ) => {
$(
impl CtNeg for $nzuint {
#[inline]
fn ct_neg(&self, choice: Choice) -> Self {
// TODO(tarcieri): use `NonZero::wrapping_neg` if it becomes available
let n = self.get().ct_select(&self.get().wrapping_neg(), choice);
$nzuint::new(n).expect("should be non-zero")
}
}
)+
};
}
impl_ct_neg_for_unsigned_nonzero!(
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize
);
#[cfg(test)]
mod tests {
/// Test `CtNeg` impl on `i*`
macro_rules! signed_ct_neg_tests {
( $($int:ident),+ ) => {
$(
mod $int {
use crate::{Choice, CtNeg};
#[test]
fn ct_neg() {
let n: $int = 42;
assert_eq!(n, n.ct_neg(Choice::FALSE));
assert_eq!(-n, n.ct_neg(Choice::TRUE));
}
#[test]
fn ct_neg_assign() {
let n: $int = 42;
let mut x = n;
x.ct_neg_assign(Choice::FALSE);
assert_eq!(n, x);
x.ct_neg_assign(Choice::TRUE);
assert_eq!(-n, x);
}
}
)+
};
}
/// Test `CtNeg` impl on `u*`
macro_rules! unsigned_ct_neg_tests {
( $($uint:ident),+ ) => {
$(
mod $uint {
use crate::{Choice, CtNeg};
#[test]
fn ct_neg() {
let n: $uint = 42;
assert_eq!(n, n.ct_neg(Choice::FALSE));
assert_eq!(<$uint>::MAX - n + 1, n.ct_neg(Choice::TRUE));
}
#[test]
fn ct_neg_assign() {
let n: $uint = 42;
let mut x = n;
x.ct_neg_assign(Choice::FALSE);
assert_eq!(n, x);
x.ct_neg_assign(Choice::TRUE);
assert_eq!(<$uint>::MAX - n + 1, x);
}
}
)+
};
}
signed_ct_neg_tests!(i8, i16, i32, i64, i128, isize);
unsigned_ct_neg_tests!(u8, u16, u32, u64, u128, usize);
}
+209
View File
@@ -0,0 +1,209 @@
use crate::{Choice, CtAssign, CtAssignSlice};
use core::{
cmp,
num::{
NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
},
};
#[cfg(feature = "subtle")]
use crate::CtOption;
/// 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`], [`NonZeroI128`]
/// - [`NonZeroU8`], [`NonZeroU16`], [`NonZeroU32`], [`NonZeroU64`], [`NonZeroU128`],, [`NonZeroUsize`]
/// - [`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.
///
/// # Returns
/// - `self` if `choice` is [`Choice::FALSE`].
/// - `other` if `choice` is [`Choice::TRUE`].
#[must_use]
fn ct_select(&self, other: &Self, choice: Choice) -> Self;
/// Conditionally swap `self` and `other` if `choice` is [`Choice::TRUE`].
fn ct_swap(&mut self, other: &mut Self, choice: Choice) {
let tmp = self.ct_select(other, choice);
*other = Self::ct_select(other, self, choice);
*self = tmp;
}
}
/// 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<const N: usize>: 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<T, const N: usize> CtSelect for [T; N]
where
T: CtSelectArray<N>,
{
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
T::ct_select_array(self, other, choice)
}
}
impl<T, const N: usize> CtSelectArray<N> for T
where
T: Clone + CtAssignSlice + CtSelect,
{
#[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
}
}
/// Marker trait which enables a blanket impl of [`CtSelect`] for types which also impl
/// [`Clone`] + [`CtAssign`].
pub trait CtSelectUsingCtAssign: Clone + CtAssign {}
impl<T: CtSelectUsingCtAssign> CtSelect for T {
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
let mut ret = self.clone();
ret.ct_assign(other, choice);
ret
}
}
/// Macro to write impls of `CtSelectUsingCtAssign`.
macro_rules! impl_ct_select_with_ct_assign {
( $($ty:ty),+ ) => { $(impl CtSelectUsingCtAssign for $ty {})+ };
}
impl_ct_select_with_ct_assign!(
i8,
i16,
i32,
i64,
i128,
isize,
u8,
u16,
u32,
u64,
u128,
usize,
NonZeroI8,
NonZeroI16,
NonZeroI32,
NonZeroI64,
NonZeroI128,
NonZeroIsize,
NonZeroU8,
NonZeroU16,
NonZeroU32,
NonZeroU64,
NonZeroU128,
NonZeroUsize,
cmp::Ordering
);
#[cfg(feature = "subtle")]
impl CtSelect for subtle::Choice {
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
Choice::from(*self)
.ct_select(&Choice::from(*other), choice)
.into()
}
}
#[cfg(feature = "subtle")]
impl<T> CtSelect for subtle::CtOption<T>
where
T: CtSelect + Default + subtle::ConditionallySelectable,
{
#[inline]
fn ct_select(&self, other: &Self, choice: Choice) -> Self {
CtOption::from(*self)
.ct_select(&CtOption::from(*other), choice)
.into()
}
}
#[cfg(feature = "alloc")]
mod alloc {
use super::CtSelectUsingCtAssign;
use crate::{CtAssign, CtAssignSlice};
use ::alloc::{boxed::Box, vec::Vec};
impl<T: Clone + CtAssign> CtSelectUsingCtAssign for Box<T> {}
#[cfg(feature = "alloc")]
impl<T> CtSelectUsingCtAssign for Box<[T]> where T: Clone + CtAssignSlice {}
#[cfg(feature = "alloc")]
impl<T: Clone + CtAssignSlice> CtSelectUsingCtAssign for Vec<T> {}
}
#[cfg(test)]
mod tests {
use super::{Choice, CtSelect, cmp};
macro_rules! ct_select_test_unsigned {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a: $ty = 1;
let b: $ty = 2;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
};
}
macro_rules! ct_select_test_signed {
($ty:ty, $name:ident) => {
#[test]
fn $name() {
let a: $ty = 1;
let b: $ty = -2;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
};
}
ct_select_test_unsigned!(u8, u8_ct_select);
ct_select_test_unsigned!(u16, u16_ct_select);
ct_select_test_unsigned!(u32, u32_ct_select);
ct_select_test_unsigned!(u64, u64_ct_select);
ct_select_test_unsigned!(u128, u128_ct_select);
ct_select_test_unsigned!(usize, usize_ct_select);
ct_select_test_signed!(i8, i8_ct_select);
ct_select_test_signed!(i16, i16_ct_select);
ct_select_test_signed!(i32, i32_ct_select);
ct_select_test_signed!(i64, i64_ct_select);
ct_select_test_signed!(i128, i128_ct_select);
ct_select_test_signed!(isize, isize_ct_select);
#[test]
fn ordering_ct_select() {
let a = cmp::Ordering::Less;
let b = cmp::Ordering::Greater;
assert_eq!(a.ct_select(&b, Choice::FALSE), a);
assert_eq!(a.ct_select(&b, Choice::TRUE), b);
}
}