Skip to main content

ctutils/
primitives.rs

1//! Constant-time integer primitives for cryptographic arithmetic.
2//!
3//! Core integer methods can lower to branches or table lookups on targets without the corresponding
4//! instructions. These helpers provide alternatives if `value` is zero-bit counting  and carry propagation.
5//!
6//! Like [`Choice`], they use optimization barriers where needed. Their generated code should still
7//! be checked for the target and compiler in use.
8//!
9//! Native zero counts are used when supported and advertised by the target configuration.
10
11use crate::Choice;
12use core::hint::black_box;
13
14/// Bitwise less-than-or equal: returns `1` if `x <= y`, and otherwise returns `0`.
15///
16/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
17macro_rules! bitle {
18    ($x:expr, $y:expr, $bits:expr) => {
19        (((!$x) | $y) & (($x ^ $y) | !($y.wrapping_sub($x)))) >> ($bits - 1)
20    };
21}
22
23/// Outgoing borrow bit for a wrapping `x - y - borrow_in` result.
24///
25/// See "Hacker's Delight", 2nd edition, section 2-13 (Overflow detection)
26macro_rules! bitborrow {
27    ($x:expr, $y:expr, $result:expr, $bits:expr) => {
28        (((!$x) & $y) | (((!$x) | $y) & $result)) >> ($bits - 1)
29    };
30}
31
32/// Bitwise less-than: returns `1` if `x < y`, and otherwise returns `0`.
33///
34/// See "Hacker's Delight" 2nd edition, section 2-12 (Comparison predicates)
35macro_rules! bitlt {
36    ($x:expr, $y:expr, $bits:expr) => {
37        $crate::primitives::bitborrow!($x, $y, ($x).wrapping_sub($y), $bits)
38    };
39}
40
41/// Bitwise non-zero: returns `1` if `x != 0`, and otherwise returns `0`.
42macro_rules! bitnz {
43    ($value:expr, $bits:expr) => {
44        ($value | $value.wrapping_neg()) >> ($bits - 1)
45    };
46}
47
48pub(crate) use {bitborrow, bitle, bitlt, bitnz};
49
50// Keep the comparison bit opaque before narrowing it to Choice's byte representation.
51// ARM targets without pointer-width atomics cover Thumb-1 on Rust 1.87, which does not
52// advertise `thumb2` as a target feature. The other targets retain the original lowering.
53macro_rules! comparison_primitives {
54    ($ty:ty, $le:ident, $lt:ident, $nz:ident, $from_lsb:ident) => {
55        #[inline(always)]
56        pub(crate) const fn $le(lhs: $ty, rhs: $ty) -> Choice {
57            let bit = bitle!(lhs, rhs, <$ty>::BITS);
58            #[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
59            let bit = black_box(bit);
60            Choice::$from_lsb(bit)
61        }
62
63        #[cfg_attr(
64            all(target_arch = "arm", not(target_has_atomic = "ptr")),
65            inline(never)
66        )]
67        #[cfg_attr(
68            not(all(target_arch = "arm", not(target_has_atomic = "ptr"))),
69            inline(always)
70        )]
71        pub(crate) const fn $lt(lhs: $ty, rhs: $ty) -> Choice {
72            let bit = bitlt!(lhs, rhs, <$ty>::BITS);
73            #[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
74            let bit = black_box(bit);
75            Choice::$from_lsb(bit)
76        }
77
78        #[inline(always)]
79        pub(crate) const fn $nz(value: $ty) -> Choice {
80            let bit = bitnz!(value, <$ty>::BITS);
81            #[cfg(all(target_arch = "arm", not(target_has_atomic = "ptr")))]
82            let bit = black_box(bit);
83            Choice::$from_lsb(bit)
84        }
85    };
86}
87
88comparison_primitives!(u32, le_u32, lt_u32, nz_u32, from_u32_lsb);
89comparison_primitives!(u64, le_u64, lt_u64, nz_u64, from_u64_lsb);
90
91macro_rules! borrowing_sub_primitives {
92    ($ty:ty, $sub:ident, $from_lsb:ident) => {
93        /// Subtract `rhs` and an incoming borrow from `lhs`.
94        ///
95        /// Returns the wrapping difference and [`Choice::TRUE`] if the subtraction underflows.
96        /// The incoming `borrow` subtracts one when true and zero when false.
97        #[inline]
98        #[must_use]
99        pub const fn $sub(lhs: $ty, rhs: $ty, borrow: Choice) -> ($ty, Choice) {
100            let difference = lhs.wrapping_sub(rhs).wrapping_sub(borrow.to_u8() as $ty);
101            // Keep all arithmetic in the operand type: widening can trigger incorrect
102            // optimization on older compilers: https://github.com/rust-lang/rust/issues/149522
103            // Keep the full-width bit opaque before narrowing it to Choice, so a caller's
104            // masked add-back cannot be folded into a secret-dependent branch.
105            let borrow = black_box(bitborrow!(lhs, rhs, difference, <$ty>::BITS));
106            (difference, Choice::$from_lsb(borrow))
107        }
108    };
109}
110
111borrowing_sub_primitives!(u32, borrowing_sub_u32, from_u32_lsb);
112borrowing_sub_primitives!(u64, borrowing_sub_u64, from_u64_lsb);
113
114/// Count leading zero bits, returning 32 if `value` is zero.
115#[inline]
116#[must_use]
117pub const fn leading_zeros_u32(value: u32) -> u32 {
118    #[cfg(any(
119        target_arch = "aarch64",
120        target_arch = "x86",
121        target_arch = "x86_64",
122        target_feature = "thumb2",
123        target_feature = "zbb"
124    ))]
125    {
126        value.leading_zeros()
127    }
128    #[cfg(not(any(
129        target_arch = "aarch64",
130        target_arch = "x86",
131        target_arch = "x86_64",
132        target_feature = "thumb2",
133        target_feature = "zbb"
134    )))]
135    {
136        leading_zeros_u32_soft(value)
137    }
138}
139
140/// Count trailing zero bits, returning 32 if `value` is zero.
141#[inline]
142#[must_use]
143pub const fn trailing_zeros_u32(value: u32) -> u32 {
144    #[cfg(any(
145        target_arch = "aarch64",
146        target_arch = "x86",
147        target_arch = "x86_64",
148        target_feature = "thumb2",
149        target_feature = "zbb"
150    ))]
151    {
152        value.trailing_zeros()
153    }
154    #[cfg(not(any(
155        target_arch = "aarch64",
156        target_arch = "x86",
157        target_arch = "x86_64",
158        target_feature = "thumb2",
159        target_feature = "zbb"
160    )))]
161    {
162        // Reversing bits uses fixed shifts and masks when RBIT is unavailable.
163        leading_zeros_u32_soft(value.reverse_bits())
164    }
165}
166
167/// Count leading zero bits, returning 64 if `value` is zero.
168#[inline]
169#[must_use]
170#[allow(clippy::cast_possible_truncation)]
171pub const fn leading_zeros_u64(value: u64) -> u32 {
172    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
173    {
174        value.leading_zeros()
175    }
176    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
177    {
178        let hi = (value >> 32) as u32;
179        let lo = value as u32;
180        let mask = Choice::from_u32_nz(hi).not().to_u32_mask();
181        leading_zeros_u32(hi).wrapping_add(mask & leading_zeros_u32(lo))
182    }
183}
184
185/// Count trailing zero bits, returning 64 if `value` is zero.
186#[inline]
187#[must_use]
188#[allow(clippy::cast_possible_truncation)]
189pub const fn trailing_zeros_u64(value: u64) -> u32 {
190    #[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
191    {
192        value.trailing_zeros()
193    }
194    #[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
195    {
196        let hi = (value >> 32) as u32;
197        let lo = value as u32;
198        let mask = Choice::from_u32_nz(lo).not().to_u32_mask();
199        trailing_zeros_u32(lo).wrapping_add(mask & trailing_zeros_u32(hi))
200    }
201}
202
203/// Fixed-stage binary search for the highest set bit, including the zero case.
204#[cfg(any(
205    test,
206    not(any(
207        target_arch = "aarch64",
208        target_arch = "x86",
209        target_arch = "x86_64",
210        target_feature = "thumb2",
211        target_feature = "zbb"
212    ))
213))]
214#[inline]
215#[allow(clippy::arithmetic_side_effects, reason = "shift is between 1 and 16")]
216const fn leading_zeros_u32_soft(mut value: u32) -> u32 {
217    let mut count = 0u32;
218    let mut shift = 16;
219    while shift != 0 {
220        let mask = Choice::from_u32_nz(value >> (u32::BITS - shift))
221            .not()
222            .to_u32_mask();
223        value ^= mask & (value ^ (value << shift));
224        count = count.wrapping_add(mask & shift);
225        shift >>= 1;
226    }
227    count.wrapping_add(Choice::from_u32_nz(value).not().to_u32_mask() & 1)
228}
229
230/// Add two integers, returning the wrapping sum and [`Choice::TRUE`] on overflow.
231#[inline]
232#[must_use]
233pub const fn overflowing_add_u64(lhs: u64, rhs: u64) -> (u64, Choice) {
234    let sum = lhs.wrapping_add(rhs);
235    let carry = ((lhs & rhs) | ((lhs | rhs) & !sum)) >> (u64::BITS - 1);
236    (sum, Choice::from_u64_lsb(carry))
237}
238
239/// Add two integers, returning the wrapping sum and [`Choice::TRUE`] on overflow.
240#[inline]
241#[must_use]
242pub const fn overflowing_add_u128(lhs: u128, rhs: u128) -> (u128, Choice) {
243    #[cfg(target_arch = "riscv32")]
244    {
245        overflowing_add_u128_halves(lhs, rhs)
246    }
247    #[cfg(not(target_arch = "riscv32"))]
248    {
249        let sum = lhs.wrapping_add(rhs);
250        let carry = ((lhs & rhs) | ((lhs | rhs) & !sum)) >> (u128::BITS - 1);
251        (sum, Choice::from_u128_lsb(carry))
252    }
253}
254
255// RV32 can branch even in `u128::wrapping_add`, before computing the final carry.
256// Keep this workaround local to that target, preserving native addition elsewhere.
257#[cfg(any(target_arch = "riscv32", test))]
258#[inline]
259#[allow(clippy::cast_possible_truncation)]
260const fn overflowing_add_u128_halves(lhs: u128, rhs: u128) -> (u128, Choice) {
261    let (lo, lo_carry) = overflowing_add_u64(lhs as u64, rhs as u64);
262    let (hi, hi_carry) = overflowing_add_u64((lhs >> 64) as u64, (rhs >> 64) as u64);
263    let (hi, carry) = overflowing_add_u64(hi, lo_carry.to_u8() as u64);
264    (((hi as u128) << 64) | lo as u128, hi_carry.or(carry))
265}
266
267#[cfg(test)]
268mod tests {
269    use super::*;
270
271    // Keep all public entrypoints usable by const callers such as crypto-bigint.
272    const _: () = {
273        assert!(borrowing_sub_u32(0, 0, Choice::TRUE).0 == u32::MAX);
274        assert!(borrowing_sub_u32(0, 0, Choice::TRUE).1.to_bool());
275        assert!(borrowing_sub_u64(0, 0, Choice::TRUE).0 == u64::MAX);
276        assert!(borrowing_sub_u64(0, 0, Choice::TRUE).1.to_bool());
277        assert!(!borrowing_sub_u32(1, 0, Choice::TRUE).1.to_bool());
278        assert!(!borrowing_sub_u64(1, 0, Choice::TRUE).1.to_bool());
279        assert!(leading_zeros_u32(0) == 32);
280        assert!(trailing_zeros_u32(0) == 32);
281        assert!(leading_zeros_u64(0) == 64);
282        assert!(trailing_zeros_u64(0) == 64);
283        assert!(overflowing_add_u64(u64::MAX, 1).1.to_bool());
284        assert!(overflowing_add_u128(u128::MAX, 1).1.to_bool());
285    };
286
287    #[test]
288    fn zero_counts_u32() {
289        for bit in 0..u32::BITS {
290            for value in [0, 1 << bit, !(1 << bit), u32::MAX >> bit] {
291                assert_eq!(leading_zeros_u32(value), value.leading_zeros());
292                assert_eq!(trailing_zeros_u32(value), value.trailing_zeros());
293                // Exercise the software fallback even on hosts with native instructions.
294                assert_eq!(leading_zeros_u32_soft(value), value.leading_zeros());
295                assert_eq!(
296                    leading_zeros_u32_soft(value.reverse_bits()),
297                    value.trailing_zeros()
298                );
299            }
300        }
301    }
302
303    #[test]
304    fn zero_counts_u64() {
305        for bit in 0..u64::BITS {
306            for value in [0, 1 << bit, !(1 << bit), u64::MAX >> bit] {
307                assert_eq!(leading_zeros_u64(value), value.leading_zeros());
308                assert_eq!(trailing_zeros_u64(value), value.trailing_zeros());
309            }
310        }
311    }
312
313    macro_rules! subtraction_tests {
314        ($test:ident, $ty:ty, $sub:ident) => {
315            #[test]
316            fn $test() {
317                let half = 1 << (<$ty>::BITS >> 1);
318                let high = 1 << (<$ty>::BITS - 1);
319                let values = [0, 1, half - 1, half, half + 1, high - 1, high, <$ty>::MAX];
320                for lhs in values {
321                    for rhs in values {
322                        for incoming in [false, true] {
323                            let (expected, first_borrow) = lhs.overflowing_sub(rhs);
324                            let (expected, second_borrow) =
325                                expected.overflowing_sub(<$ty>::from(incoming));
326                            let (difference, borrow) =
327                                $sub(lhs, rhs, Choice::from_u8_lsb(u8::from(incoming)));
328                            assert_eq!(
329                                (difference, borrow.to_bool()),
330                                (expected, first_borrow | second_borrow)
331                            );
332                        }
333                    }
334                }
335            }
336        };
337    }
338
339    subtraction_tests!(sub_u32, u32, borrowing_sub_u32);
340    subtraction_tests!(sub_u64, u64, borrowing_sub_u64);
341
342    macro_rules! addition_tests {
343        ($test:ident, $ty:ty, $add:ident) => {
344            #[test]
345            fn $test() {
346                // Include both full-width and half-width carry boundaries.
347                let half = 1 << (<$ty>::BITS >> 1);
348                let high = 1 << (<$ty>::BITS - 1);
349                let values = [0, 1, half - 1, half, half + 1, high - 1, high, <$ty>::MAX];
350                for lhs in values {
351                    for rhs in values {
352                        let (sum, carry) = $add(lhs, rhs);
353                        assert_eq!((sum, carry.to_bool()), lhs.overflowing_add(rhs));
354                    }
355                }
356            }
357        };
358    }
359
360    addition_tests!(add_u64, u64, overflowing_add_u64);
361    addition_tests!(add_u128, u128, overflowing_add_u128);
362    addition_tests!(add_u128_halves, u128, overflowing_add_u128_halves);
363}