mod eq;
mod ord;
mod select;
pub use eq::ConstantTimeEq;
pub use ord::{ConstantTimeGreater, ConstantTimeLess};
pub use select::ConditionallySelectable;
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Not};
#[derive(Clone, Copy, Debug, Default)]
pub struct Choice(u8);
impl Choice {
#[inline]
pub fn unwrap_u8(self) -> u8 {
self.0
}
}
impl From<u8> for Choice {
#[inline]
fn from(value: u8) -> Self {
debug_assert!(value == 0 || value == 1, "Choice must be 0 or 1");
Choice(((value | value.wrapping_neg()) >> 7) & 1)
}
}
impl From<Choice> for bool {
#[inline]
fn from(choice: Choice) -> Self {
choice.0 != 0
}
}
impl BitAnd for Choice {
type Output = Choice;
#[inline]
fn bitand(self, rhs: Choice) -> Choice {
Choice(self.0 & rhs.0)
}
}
impl BitAndAssign for Choice {
#[inline]
fn bitand_assign(&mut self, rhs: Choice) {
self.0 &= rhs.0;
}
}
impl BitOr for Choice {
type Output = Choice;
#[inline]
fn bitor(self, rhs: Choice) -> Choice {
Choice(self.0 | rhs.0)
}
}
impl BitOrAssign for Choice {
#[inline]
fn bitor_assign(&mut self, rhs: Choice) {
self.0 |= rhs.0;
}
}
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.0 ^= rhs.0;
}
}
impl Not for Choice {
type Output = Choice;
#[inline]
fn not(self) -> Choice {
Choice(self.0 ^ 1)
}
}
impl ConstantTimeEq for Choice {
#[inline]
fn ct_eq(&self, other: &Choice) -> Choice {
Choice((self.0 ^ other.0) ^ 1)
}
}
impl ConditionallySelectable for Choice {
#[inline]
fn conditional_select(a: &Choice, b: &Choice, choice: Choice) -> Choice {
Choice(u8::conditional_select(&a.0, &b.0, choice))
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct CtOption<T> {
value: T,
is_some: Choice,
}
impl<T> CtOption<T> {
#[inline]
pub fn new(value: T, is_some: Choice) -> Self {
CtOption { value, is_some }
}
#[inline]
pub fn is_some(&self) -> Choice {
self.is_some
}
#[inline]
pub fn is_none(&self) -> Choice {
!self.is_some
}
#[inline]
pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> CtOption<U> {
CtOption::new(f(self.value), self.is_some)
}
}
impl<T: ConditionallySelectable> CtOption<T> {
#[inline]
pub fn unwrap_or(self, default: T) -> T {
T::conditional_select(&self.value, &default, self.is_some)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn truthy(c: Choice) -> bool {
c.into()
}
#[test]
fn choice_logic() {
let t = Choice::from(1);
let f = Choice::from(0);
assert!(truthy(t) && !truthy(f));
assert!(truthy(t & t) && !truthy(t & f));
assert!(truthy(t | f) && !truthy(f | f));
assert!(truthy(t ^ f) && !truthy(t ^ t));
assert!(truthy(!f) && !truthy(!t));
}
#[test]
fn choice_from_nonzero_normalization() {
let normalize = |value: u8| ((value | value.wrapping_neg()) >> 7) & 1;
assert_eq!(normalize(0x00), 0);
for v in 1u16..=255 {
let v = v as u8;
assert_eq!(normalize(v), 1, "byte {v:#04x} must normalize to 1");
}
assert!(truthy(Choice::from(1)));
assert!(!truthy(Choice::from(0)));
}
#[test]
fn choice_assign_ops() {
let mut c = Choice::from(1);
c &= Choice::from(0);
assert!(!truthy(c));
c |= Choice::from(1);
assert!(truthy(c));
c ^= Choice::from(1);
assert!(!truthy(c));
}
#[test]
fn choice_eq_and_select() {
assert!(truthy(Choice::from(1).ct_eq(&Choice::from(1))));
assert!(!truthy(Choice::from(1).ct_eq(&Choice::from(0))));
let a = Choice::from(1);
let b = Choice::from(0);
assert!(truthy(Choice::conditional_select(&a, &b, Choice::from(1))));
assert!(!truthy(Choice::conditional_select(&a, &b, Choice::from(0))));
}
#[test]
fn ct_option_basics() {
let some = CtOption::new(7u32, Choice::from(1));
let none = CtOption::new(7u32, Choice::from(0));
assert!(truthy(some.is_some()) && !truthy(some.is_none()));
assert!(!truthy(none.is_some()) && truthy(none.is_none()));
assert_eq!(some.unwrap_or(99), 7);
assert_eq!(none.unwrap_or(99), 99);
assert_eq!(some.map(|v| v + 1).unwrap_or(0), 8);
}
}