use subtle::{Choice, ConstantTimeEq, CtOption};
pub trait CtIsZero {
fn ct_is_zero(&self) -> Choice;
}
pub trait CtParity {
fn ct_is_odd(&self) -> Choice;
fn ct_is_even(&self) -> Choice;
}
pub trait CtIsPowerOfTwo {
fn ct_is_power_of_two(&self) -> Choice;
}
pub trait CtCheckedAdd: Sized {
fn ct_checked_add(&self, v: &Self) -> CtOption<Self>;
}
pub trait CtCheckedSub: Sized {
fn ct_checked_sub(&self, v: &Self) -> CtOption<Self>;
}
pub trait CtCheckedMul: Sized {
fn ct_checked_mul(&self, v: &Self) -> CtOption<Self>;
}
pub trait CtCheckedNeg: Sized {
fn ct_checked_neg(&self) -> CtOption<Self>;
}
pub trait CtCheckedSignedDiff: Sized {
type Signed;
fn ct_checked_signed_diff(&self, rhs: &Self) -> CtOption<Self::Signed>;
}
macro_rules! ct_common_impl {
($($t:ty)*) => {$(
impl CtIsZero for $t {
#[inline]
fn ct_is_zero(&self) -> Choice {
self.ct_eq(&0)
}
}
impl CtParity for $t {
#[inline]
fn ct_is_odd(&self) -> Choice {
Choice::from((*self & 1) as u8)
}
#[inline]
fn ct_is_even(&self) -> Choice {
Choice::from(((*self & 1) ^ 1) as u8)
}
}
impl CtCheckedAdd for $t {
#[inline]
fn ct_checked_add(&self, v: &Self) -> CtOption<Self> {
let (val, overflow) = <$t>::overflowing_add(*self, *v);
CtOption::new(val, Choice::from(!overflow as u8))
}
}
impl CtCheckedSub for $t {
#[inline]
fn ct_checked_sub(&self, v: &Self) -> CtOption<Self> {
let (val, overflow) = <$t>::overflowing_sub(*self, *v);
CtOption::new(val, Choice::from(!overflow as u8))
}
}
impl CtCheckedMul for $t {
#[inline]
fn ct_checked_mul(&self, v: &Self) -> CtOption<Self> {
let (val, overflow) = <$t>::overflowing_mul(*self, *v);
CtOption::new(val, Choice::from(!overflow as u8))
}
}
impl CtCheckedNeg for $t {
#[inline]
fn ct_checked_neg(&self) -> CtOption<Self> {
let (val, overflow) = <$t>::overflowing_neg(*self);
CtOption::new(val, Choice::from(!overflow as u8))
}
}
)*};
}
ct_common_impl!(usize u8 u16 u32 u64 u128);
ct_common_impl!(isize i8 i16 i32 i64 i128);
macro_rules! ct_unsigned_impl {
($($t:ty => $s:ty;)*) => {$(
impl CtIsPowerOfTwo for $t {
#[inline]
fn ct_is_power_of_two(&self) -> Choice {
<$t>::count_ones(*self).ct_eq(&1)
}
}
impl CtCheckedSignedDiff for $t {
type Signed = $s;
#[inline]
fn ct_checked_signed_diff(&self, rhs: &Self) -> CtOption<$s> {
let res = <$t>::wrapping_sub(*self, *rhs) as $s;
let overflow = (*self >= *rhs) == (res < 0);
CtOption::new(res, Choice::from(!overflow as u8))
}
}
)*};
}
ct_unsigned_impl! {
u8 => i8;
u16 => i16;
u32 => i32;
u64 => i64;
usize => isize;
u128 => i128;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ct_predicates() {
assert_eq!(0u64.ct_is_zero().unwrap_u8(), 1);
assert_eq!(1u64.ct_is_zero().unwrap_u8(), 0);
assert_eq!(0i32.ct_is_zero().unwrap_u8(), 1);
assert_eq!((-1i32).ct_is_zero().unwrap_u8(), 0);
assert_eq!(3u8.ct_is_odd().unwrap_u8(), 1);
assert_eq!(3u8.ct_is_even().unwrap_u8(), 0);
assert_eq!((-3i8).ct_is_odd().unwrap_u8(), 1);
assert_eq!(16u32.ct_is_power_of_two().unwrap_u8(), 1);
assert_eq!(0u32.ct_is_power_of_two().unwrap_u8(), 0);
assert_eq!(10u32.ct_is_power_of_two().unwrap_u8(), 0);
}
#[test]
fn ct_checked_ops() {
assert_eq!(250u8.ct_checked_add(&5).unwrap(), 255);
assert!(bool::from(255u8.ct_checked_add(&1).is_none()));
assert_eq!(5u8.ct_checked_sub(&5).unwrap(), 0);
assert!(bool::from(0u8.ct_checked_sub(&1).is_none()));
assert_eq!(16u8.ct_checked_mul(&15).unwrap(), 240);
assert!(bool::from(16u8.ct_checked_mul(&16).is_none()));
assert_eq!((-5i8).ct_checked_neg().unwrap(), 5);
assert!(bool::from(i8::MIN.ct_checked_neg().is_none()));
assert!(bool::from(1u8.ct_checked_neg().is_none()));
assert_eq!(0u8.ct_checked_neg().unwrap(), 0);
for a in 0u8..=255 {
for b in [0u8, 1, 7, 127, 128, 255] {
assert_eq!(a.checked_add(b), a.ct_checked_add(&b).into());
assert_eq!(a.checked_sub(b), a.ct_checked_sub(&b).into());
assert_eq!(a.checked_mul(b), a.ct_checked_mul(&b).into());
}
}
}
#[test]
fn ct_signed_diff() {
assert_eq!(10u8.ct_checked_signed_diff(&14).unwrap(), -4);
assert!(bool::from(u8::MAX.ct_checked_signed_diff(&0).is_none()));
use crate::ops::mixed::CheckedSignedDiff;
for a in 0u8..=255 {
for b in [0u8, 1, 100, 127, 128, 255] {
let plain = CheckedSignedDiff::checked_signed_diff(a, b);
let ct: Option<i8> = a.ct_checked_signed_diff(&b).into();
assert_eq!(plain, ct, "{a} {b}");
}
}
}
}