Skip to main content

const_num_traits/ops/
ct.rs

1//! Constant-time (masked-return) counterparts of the Tier-B atoms, built on
2//! [`subtle`]'s [`Choice`] and [`CtOption`]. Only available with the `ct`
3//! cargo feature.
4//!
5//! The plain Tier-B traits (`CheckedAdd`, `Parity`, `IsPowerOfTwo`, …)
6//! return `bool`/`Option`, which invite branching on secret-derived data at
7//! the call site. The traits here return `Choice`/`CtOption` instead, so
8//! the secret-derived condition stays masked until the caller explicitly
9//! unwraps it.
10//!
11//! Selection and equality are deliberately **not** duplicated here — use
12//! `subtle::ConditionallySelectable` and `subtle::ConstantTimeEq` directly.
13//!
14//! These are plain (never-`const`) traits: `subtle`'s constructors are not
15//! `const fn`, so a const port has to wait for subtle. This does not affect
16//! the `c0nst` story of the rest of the crate.
17//!
18//! Note on the primitive impls: "constant time" here means the usual
19//! best-effort guarantee for Rust code — branchless source operating on
20//! values the compiler has no reason to branch on (the same contract subtle
21//! itself provides). Types with stronger needs (e.g. a `Ct`-personality
22//! bigint) implement these traits with their own hardened bodies.
23
24use subtle::{Choice, ConstantTimeEq, CtOption};
25
26/// Constant-time zero check.
27pub trait CtIsZero {
28    /// Returns a masked `true` if `self` is zero.
29    ///
30    /// ```
31    /// use const_num_traits::ops::ct::CtIsZero;
32    ///
33    /// assert_eq!(0u32.ct_is_zero().unwrap_u8(), 1);
34    /// assert_eq!(5u32.ct_is_zero().unwrap_u8(), 0);
35    /// ```
36    fn ct_is_zero(&self) -> Choice;
37}
38
39/// Constant-time parity check (the masked counterpart of [`Parity`]).
40///
41/// [`Parity`]: crate::Parity
42pub trait CtParity {
43    /// Returns a masked `true` if `self` is odd.
44    fn ct_is_odd(&self) -> Choice;
45
46    /// Returns a masked `true` if `self` is even.
47    fn ct_is_even(&self) -> Choice;
48}
49
50/// Constant-time power-of-two check (the masked counterpart of
51/// [`IsPowerOfTwo`]).
52///
53/// [`IsPowerOfTwo`]: crate::IsPowerOfTwo
54pub trait CtIsPowerOfTwo {
55    /// Returns a masked `true` if `self` is a power of two.
56    fn ct_is_power_of_two(&self) -> Choice;
57}
58
59/// Constant-time checked addition (the masked counterpart of
60/// [`CheckedAdd`]).
61///
62/// [`CheckedAdd`]: crate::CheckedAdd
63pub trait CtCheckedAdd: Sized {
64    /// Computes `self + v`, returning a [`CtOption`] that is `None`-masked
65    /// on overflow.
66    ///
67    /// ```
68    /// use const_num_traits::ops::ct::CtCheckedAdd;
69    ///
70    /// assert_eq!(250u8.ct_checked_add(&5).unwrap(), 255);
71    /// assert!(bool::from(255u8.ct_checked_add(&1).is_none()));
72    /// ```
73    fn ct_checked_add(&self, v: &Self) -> CtOption<Self>;
74}
75
76/// Constant-time checked subtraction (the masked counterpart of
77/// [`CheckedSub`]).
78///
79/// [`CheckedSub`]: crate::CheckedSub
80pub trait CtCheckedSub: Sized {
81    /// Computes `self - v`, returning a [`CtOption`] that is `None`-masked
82    /// on overflow.
83    fn ct_checked_sub(&self, v: &Self) -> CtOption<Self>;
84}
85
86/// Constant-time checked multiplication (the masked counterpart of
87/// [`CheckedMul`]).
88///
89/// [`CheckedMul`]: crate::CheckedMul
90pub trait CtCheckedMul: Sized {
91    /// Computes `self * v`, returning a [`CtOption`] that is `None`-masked
92    /// on overflow.
93    fn ct_checked_mul(&self, v: &Self) -> CtOption<Self>;
94}
95
96/// Constant-time checked negation (the masked counterpart of
97/// [`CheckedNeg`]).
98///
99/// [`CheckedNeg`]: crate::CheckedNeg
100pub trait CtCheckedNeg: Sized {
101    /// Computes `-self`, returning a [`CtOption`] that is `None`-masked when
102    /// the result is unrepresentable.
103    fn ct_checked_neg(&self) -> CtOption<Self>;
104}
105
106/// Constant-time signed difference of unsigned values (the masked
107/// counterpart of [`CheckedSignedDiff`]).
108///
109/// [`CheckedSignedDiff`]: crate::CheckedSignedDiff
110pub trait CtCheckedSignedDiff: Sized {
111    /// The signed counterpart type.
112    type Signed;
113
114    /// Computes `self - rhs` as a signed value, `None`-masked if it doesn't
115    /// fit.
116    fn ct_checked_signed_diff(&self, rhs: &Self) -> CtOption<Self::Signed>;
117}
118
119macro_rules! ct_common_impl {
120    ($($t:ty)*) => {$(
121        impl CtIsZero for $t {
122            #[inline]
123            fn ct_is_zero(&self) -> Choice {
124                self.ct_eq(&0)
125            }
126        }
127
128        impl CtParity for $t {
129            #[inline]
130            fn ct_is_odd(&self) -> Choice {
131                Choice::from((*self & 1) as u8)
132            }
133
134            #[inline]
135            fn ct_is_even(&self) -> Choice {
136                Choice::from(((*self & 1) ^ 1) as u8)
137            }
138        }
139
140        impl CtCheckedAdd for $t {
141            #[inline]
142            fn ct_checked_add(&self, v: &Self) -> CtOption<Self> {
143                let (val, overflow) = <$t>::overflowing_add(*self, *v);
144                CtOption::new(val, Choice::from(!overflow as u8))
145            }
146        }
147
148        impl CtCheckedSub for $t {
149            #[inline]
150            fn ct_checked_sub(&self, v: &Self) -> CtOption<Self> {
151                let (val, overflow) = <$t>::overflowing_sub(*self, *v);
152                CtOption::new(val, Choice::from(!overflow as u8))
153            }
154        }
155
156        impl CtCheckedMul for $t {
157            #[inline]
158            fn ct_checked_mul(&self, v: &Self) -> CtOption<Self> {
159                let (val, overflow) = <$t>::overflowing_mul(*self, *v);
160                CtOption::new(val, Choice::from(!overflow as u8))
161            }
162        }
163
164        impl CtCheckedNeg for $t {
165            #[inline]
166            fn ct_checked_neg(&self) -> CtOption<Self> {
167                let (val, overflow) = <$t>::overflowing_neg(*self);
168                CtOption::new(val, Choice::from(!overflow as u8))
169            }
170        }
171    )*};
172}
173
174ct_common_impl!(usize u8 u16 u32 u64 u128);
175ct_common_impl!(isize i8 i16 i32 i64 i128);
176
177macro_rules! ct_unsigned_impl {
178    ($($t:ty => $s:ty;)*) => {$(
179        impl CtIsPowerOfTwo for $t {
180            // popcount is data-independent; compare the count in CT
181            #[inline]
182            fn ct_is_power_of_two(&self) -> Choice {
183                <$t>::count_ones(*self).ct_eq(&1)
184            }
185        }
186
187        impl CtCheckedSignedDiff for $t {
188            type Signed = $s;
189
190            // same algorithm as `checked_signed_diff`, with the overflow
191            // flag masked instead of branched on
192            #[inline]
193            fn ct_checked_signed_diff(&self, rhs: &Self) -> CtOption<$s> {
194                let res = <$t>::wrapping_sub(*self, *rhs) as $s;
195                let overflow = (*self >= *rhs) == (res < 0);
196                CtOption::new(res, Choice::from(!overflow as u8))
197            }
198        }
199    )*};
200}
201
202ct_unsigned_impl! {
203    u8 => i8;
204    u16 => i16;
205    u32 => i32;
206    u64 => i64;
207    usize => isize;
208    u128 => i128;
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn ct_predicates() {
217        assert_eq!(0u64.ct_is_zero().unwrap_u8(), 1);
218        assert_eq!(1u64.ct_is_zero().unwrap_u8(), 0);
219        assert_eq!(0i32.ct_is_zero().unwrap_u8(), 1);
220        assert_eq!((-1i32).ct_is_zero().unwrap_u8(), 0);
221        assert_eq!(3u8.ct_is_odd().unwrap_u8(), 1);
222        assert_eq!(3u8.ct_is_even().unwrap_u8(), 0);
223        assert_eq!((-3i8).ct_is_odd().unwrap_u8(), 1);
224        assert_eq!(16u32.ct_is_power_of_two().unwrap_u8(), 1);
225        assert_eq!(0u32.ct_is_power_of_two().unwrap_u8(), 0);
226        assert_eq!(10u32.ct_is_power_of_two().unwrap_u8(), 0);
227    }
228
229    #[test]
230    fn ct_checked_ops() {
231        assert_eq!(250u8.ct_checked_add(&5).unwrap(), 255);
232        assert!(bool::from(255u8.ct_checked_add(&1).is_none()));
233        assert_eq!(5u8.ct_checked_sub(&5).unwrap(), 0);
234        assert!(bool::from(0u8.ct_checked_sub(&1).is_none()));
235        assert_eq!(16u8.ct_checked_mul(&15).unwrap(), 240);
236        assert!(bool::from(16u8.ct_checked_mul(&16).is_none()));
237        assert_eq!((-5i8).ct_checked_neg().unwrap(), 5);
238        assert!(bool::from(i8::MIN.ct_checked_neg().is_none()));
239        assert!(bool::from(1u8.ct_checked_neg().is_none()));
240        assert_eq!(0u8.ct_checked_neg().unwrap(), 0);
241        // agree with the plain checked ops across a sweep
242        for a in 0u8..=255 {
243            for b in [0u8, 1, 7, 127, 128, 255] {
244                assert_eq!(a.checked_add(b), a.ct_checked_add(&b).into());
245                assert_eq!(a.checked_sub(b), a.ct_checked_sub(&b).into());
246                assert_eq!(a.checked_mul(b), a.ct_checked_mul(&b).into());
247            }
248        }
249    }
250
251    #[test]
252    fn ct_signed_diff() {
253        assert_eq!(10u8.ct_checked_signed_diff(&14).unwrap(), -4);
254        assert!(bool::from(u8::MAX.ct_checked_signed_diff(&0).is_none()));
255        // agree with the plain trait
256        use crate::ops::mixed::CheckedSignedDiff;
257        for a in 0u8..=255 {
258            for b in [0u8, 1, 100, 127, 128, 255] {
259                let plain = CheckedSignedDiff::checked_signed_diff(a, b);
260                let ct: Option<i8> = a.ct_checked_signed_diff(&b).into();
261                assert_eq!(plain, ct, "{a} {b}");
262            }
263        }
264    }
265}