Skip to main content

ctutils/traits/
ct_lt.rs

1use crate::Choice;
2use core::{
3    cmp,
4    num::{NonZeroU8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize},
5};
6
7/// Constant time less than.
8pub trait CtLt {
9    /// Compute whether `self < other` in constant time.
10    #[must_use]
11    fn ct_lt(&self, other: &Self) -> Choice;
12}
13
14macro_rules! impl_unsigned_ct_lt {
15    ( $(($uint:ty, $from_lt:ident)),+ ) => {
16        $(
17            impl CtLt for $uint {
18                #[inline]
19                fn ct_lt(&self, other: &Self) -> Choice {
20                    // Thumb-1 can branch when converting an overflowing subtraction's borrow flag
21                    // into a byte, before `Choice`'s barrier or selection is reached.
22                    // Reuse the hardened constructors there.
23                    #[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
24                    {
25                        Choice::$from_lt(*self, *other)
26                    }
27                    // Retain native subtraction on other targets.
28                    #[cfg(not(all(target_arch = "arm", not(target_has_atomic = "ptr"))))]
29                    {
30                        let (_, overflow) = self.overflowing_sub(*other);
31                        Choice(overflow.into())
32                    }
33                }
34            }
35        )+
36    };
37}
38
39impl_unsigned_ct_lt!((u8, from_u8_lt), (u16, from_u16_lt), (u32, from_u32_lt));
40
41#[cfg(not(target_arch = "riscv32"))]
42impl_unsigned_ct_lt!((u64, from_u64_lt));
43
44#[cfg(target_arch = "riscv32")]
45impl CtLt for u64 {
46    #[inline]
47    fn ct_lt(&self, other: &Self) -> Choice {
48        // Native wide comparison can short-circuit after caller specialization.
49        Choice::from_u64_lt(*self, *other)
50    }
51}
52
53impl CtLt for usize {
54    #[inline]
55    #[allow(clippy::cast_possible_truncation, reason = "matches pointer width")]
56    fn ct_lt(&self, other: &Self) -> Choice {
57        #[cfg(target_pointer_width = "16")]
58        let (a, b) = (*self as u16, *other as u16);
59        #[cfg(target_pointer_width = "32")]
60        let (a, b) = (*self as u32, *other as u32);
61        #[cfg(target_pointer_width = "64")]
62        let (a, b) = (*self as u64, *other as u64);
63        a.ct_lt(&b)
64    }
65}
66
67impl CtLt for u128 {
68    #[inline]
69    fn ct_lt(&self, other: &Self) -> Choice {
70        Choice::from_u128_lt(*self, *other)
71    }
72}
73
74/// Impl `CtLt` for `NonZero<T>` by calling `NonZero::get`.
75macro_rules! impl_ct_lt_for_nonzero_integer {
76    ( $($ty:ty),+ ) => {
77        $(
78            impl CtLt for $ty {
79                #[inline]
80                fn ct_lt(&self, other: &Self) -> Choice {
81                    self.get().ct_lt(&other.get())
82                }
83            }
84        )+
85    };
86}
87
88impl_ct_lt_for_nonzero_integer!(
89    NonZeroU8,
90    NonZeroU16,
91    NonZeroU32,
92    NonZeroU64,
93    NonZeroU128,
94    NonZeroUsize
95);
96
97impl CtLt for cmp::Ordering {
98    #[inline]
99    #[allow(clippy::arithmetic_side_effects, clippy::cast_sign_loss)]
100    fn ct_lt(&self, other: &Self) -> Choice {
101        let a = (*self as i8) + 1;
102        let b = (*other as i8) + 1;
103
104        // No impl of `CtLt` for `i8`, so use `u8`
105        a.cast_unsigned().ct_lt(&b.cast_unsigned())
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::CtLt;
112    use core::cmp::Ordering;
113
114    #[test]
115    fn ct_lt() {
116        let a = 42u64;
117        let b = 43u64;
118        assert!(!a.ct_lt(&a).to_bool());
119        assert!(a.ct_lt(&b).to_bool());
120        assert!(!b.ct_lt(&a).to_bool());
121    }
122
123    /// Test `CtLt`
124    macro_rules! ct_lt_tests {
125         ( $($int:ident),+ ) => {
126             $(
127                mod $int {
128                    use super::CtLt;
129
130                    #[test]
131                    fn ct_gt() {
132                        let a = <$int>::MIN;
133                        let b = <$int>::MAX;
134                        assert!(!a.ct_lt(&a).to_bool());
135                        assert!(a.ct_lt(&b).to_bool());
136                        assert!(!b.ct_lt(&a).to_bool());
137                    }
138
139                }
140             )+
141        };
142    }
143
144    ct_lt_tests!(u8, u16, u32, u64, u128, usize);
145
146    #[test]
147    fn ordering() {
148        assert!(!Ordering::Equal.ct_lt(&Ordering::Equal).to_bool());
149        assert!(Ordering::Less.ct_lt(&Ordering::Greater).to_bool());
150        assert!(!Ordering::Greater.ct_lt(&Ordering::Less).to_bool());
151    }
152}