Skip to main content

crypto_bigint/
primitives.rs

1//! Integer primitives for cryptographic arithmetic.
2
3use crate::{Choice, WideWord, Word};
4use core::hint::black_box;
5
6/// Computes `lhs + rhs + carry`, returning the result along with the new carry (0, 1, or 2).
7#[inline(always)]
8#[allow(clippy::cast_possible_truncation)]
9pub(crate) const fn carrying_add(lhs: Word, rhs: Word, carry: Word) -> (Word, Word) {
10    // We could use `Word::overflowing_add()` here analogous to `overflowing_add()`,
11    // but this version seems to produce a slightly better assembly.
12    let a = lhs as WideWord;
13    let b = rhs as WideWord;
14    let carry = carry as WideWord;
15    let ret = a + b + carry;
16    (ret as Word, (ret >> Word::BITS) as Word)
17}
18
19/// Computes `lhs + rhs`, returning the result along with the carry (0 or 1).
20#[inline(always)]
21pub(crate) const fn overflowing_add(lhs: Word, rhs: Word) -> (Word, Word) {
22    let (res, carry) = lhs.overflowing_add(rhs);
23    (res, carry as Word)
24}
25
26/// Computes `lhs - rhs - borrow`, using word-sized borrow masks.
27///
28/// The incoming borrow is the most significant bit of `borrow`; all other bits are ignored.
29/// The outgoing borrow is `Word::MAX` if the subtraction underflows, or zero otherwise.
30#[inline]
31pub(crate) const fn borrowing_sub(lhs: Word, rhs: Word, borrow: Word) -> (Word, Word) {
32    // Keep all arithmetic in Word: widening can trigger incorrect optimization on older compilers:
33    // https://github.com/rust-lang/rust/issues/149522
34    let difference = lhs
35        .wrapping_sub(rhs)
36        .wrapping_sub(borrow >> (Word::BITS - 1));
37
38    // See "Hacker's Delight", 2nd edition, section 2-13 (Overflow detection).
39    let borrow = ((!lhs & rhs) | ((!lhs | rhs) & difference)) >> (Word::BITS - 1);
40
41    // Keep the full-width bit opaque before forming a mask, so a caller's masked addend can't be
42    // folded into a secret-dependent branch.
43    (difference, black_box(borrow).wrapping_neg())
44}
45
46/// Computes `lhs * rhs`, returning the low and the high words of the result.
47#[inline(always)]
48#[allow(clippy::cast_possible_truncation)]
49pub(crate) const fn widening_mul(lhs: Word, rhs: Word) -> (Word, Word) {
50    let a = lhs as WideWord;
51    let b = rhs as WideWord;
52    let ret = a * b;
53    (ret as Word, (ret >> Word::BITS) as Word)
54}
55
56/// Computes `(lhs * rhs) + addend + carry`, returning the result along with the new carry.
57#[inline(always)]
58#[allow(clippy::cast_possible_truncation)]
59pub(crate) const fn carrying_mul_add(
60    lhs: Word,
61    rhs: Word,
62    addend: Word,
63    carry: Word,
64) -> (Word, Word) {
65    let lhs = lhs as WideWord;
66    let rhs = rhs as WideWord;
67    let addend = addend as WideWord;
68    let carry = carry as WideWord;
69
70    // Cannot overflow:
71    // lhs      * rhs      + addend   + carry
72    // (2^64-1) * (2^64-1) + (2^64-1) + (2^64-1) =
73    // 2^128 - 2^65 + 1 + 2^64 - 1 + 2^64 - 1 =
74    // 2^128 - 2^65 + 2*2^64 - 1 =
75    // 2^128 - 1 = u128::MAX
76    let ret = ((lhs * rhs) + addend) + carry;
77    (ret as Word, (ret >> Word::BITS) as Word)
78}
79
80/// Count leading zero bits, returning `Word::BITS` if `value` is zero.
81#[inline]
82#[allow(clippy::cast_possible_truncation)]
83pub(crate) const fn leading_zeros(value: Word) -> u32 {
84    if cfg!(any(target_arch = "aarch64", target_arch = "x86_64")) {
85        value.leading_zeros()
86    } else {
87        cpubits::cpubits! {
88            32 => { leading_zeros_u32(value) }
89            64 => {
90                let hi = (value >> 32) as u32;
91                let lo = value as u32;
92                let mask = Choice::from_u32_nz(hi).not().to_u32_mask();
93                leading_zeros_u32(hi).wrapping_add(mask & leading_zeros_u32(lo))
94            }
95        }
96    }
97}
98
99/// Count leading zero bits, using native instructions where supported.
100#[inline]
101const fn leading_zeros_u32(value: u32) -> u32 {
102    if cfg!(any(
103        target_arch = "aarch64",
104        target_arch = "x86",
105        target_arch = "x86_64",
106        target_feature = "thumb2",
107        target_feature = "zbb"
108    )) {
109        value.leading_zeros()
110    } else {
111        leading_zeros_u32_soft(value)
112    }
113}
114
115/// Fixed-stage binary search for the highest set bit, including the zero case.
116#[inline]
117const fn leading_zeros_u32_soft(mut value: u32) -> u32 {
118    let mut count = 0u32;
119    let mut shift = 16;
120
121    while shift != 0 {
122        let mask = Choice::from_u32_nz(value >> (u32::BITS - shift))
123            .not()
124            .to_u32_mask();
125        value ^= mask & (value ^ (value << shift));
126        count = count.wrapping_add(mask & shift);
127        shift >>= 1;
128    }
129
130    count.wrapping_add(Choice::from_u32_nz(value).not().to_u32_mask() & 1)
131}
132
133/// Count trailing zero bits, returning `Word::BITS` if `value` is zero.
134#[inline]
135#[allow(clippy::cast_possible_truncation)]
136pub(crate) const fn trailing_zeros(value: Word) -> u32 {
137    if cfg!(any(target_arch = "aarch64", target_arch = "x86_64")) {
138        value.trailing_zeros()
139    } else {
140        cpubits::cpubits! {
141            32 => { trailing_zeros_u32(value) }
142            64 => {
143                let hi = (value >> 32) as u32;
144                let lo = value as u32;
145                let mask = Choice::from_u32_nz(lo).not().to_u32_mask();
146                trailing_zeros_u32(lo).wrapping_add(mask & trailing_zeros_u32(hi))
147            }
148        }
149    }
150}
151
152/// Count trailing zero bits, using native instructions where supported.
153#[inline]
154const fn trailing_zeros_u32(value: u32) -> u32 {
155    if cfg!(any(
156        target_arch = "aarch64",
157        target_arch = "x86",
158        target_arch = "x86_64",
159        target_feature = "thumb2",
160        target_feature = "zbb"
161    )) {
162        value.trailing_zeros()
163    } else {
164        // Reversing bits uses fixed shifts and masks when RBIT is unavailable.
165        leading_zeros_u32_soft(value.reverse_bits())
166    }
167}
168
169/// `const fn` equivalent of `u32::max(a, b)`.
170#[inline]
171pub(crate) const fn u32_max(a: u32, b: u32) -> u32 {
172    Choice::from_u32_lt(a, b).select_u32(a, b)
173}
174
175/// `const` equivalent of `u32::min(a, b)`.
176#[inline]
177pub(crate) const fn u32_min(a: u32, b: u32) -> u32 {
178    Choice::from_u32_lt(a, b).select_u32(b, a)
179}
180
181/// Remainder calculation, constant time for a given divisor `d`.
182/// Based on "Faster Remainder by Direct Computation: Applications to Compilers and Software Libraries"
183/// by Daniel Lemire, Owen Kaser, and Nathan Kurz., Fig. 1.
184#[inline(never)]
185#[allow(clippy::cast_possible_truncation, reason = "needs triage")]
186#[allow(clippy::integer_division_remainder_used, reason = "needs triage")]
187pub(crate) const fn u32_rem(n: u32, d: u32) -> u32 {
188    assert!(d > 0, "divisor must be nonzero");
189    let c = u64::MAX / (d as u64) + 1;
190    (((c.wrapping_mul(n as u64) as u128) * d as u128) >> 64) as u32
191}
192
193/// Compute the number of bits needed to represent `n`.
194#[inline(always)]
195pub(crate) const fn u32_bits(n: u32) -> u32 {
196    u32::BITS - n.leading_zeros()
197}
198
199/// Return a `Choice` representing whether `a < b`.
200#[allow(clippy::cast_possible_truncation)]
201#[cfg(target_pointer_width = "32")]
202#[inline]
203pub(crate) const fn usize_lt(a: usize, b: usize) -> Choice {
204    Choice::from_u32_lt(a as u32, b as u32)
205}
206
207/// Return a `Choice` representing whether `a < b`.
208#[allow(clippy::cast_possible_truncation)]
209#[cfg(target_pointer_width = "64")]
210#[inline]
211pub(crate) const fn usize_lt(a: usize, b: usize) -> Choice {
212    Choice::from_u64_lt(a as u64, b as u64)
213}
214
215cpubits::cpubits! {
216    32 => {
217        /// Returns the multiplicative inverse of the argument modulo 2^32.
218        ///
219        /// For correct results, the input `value` must be odd.
220        #[must_use]
221        pub(crate) const fn u32_invert_odd(value: u32) -> u32 {
222            debug_assert!(value & 1 == 1, "value must be odd");
223            let x = value.wrapping_mul(3) ^ 2;
224            let y = 1u32.wrapping_sub(x.wrapping_mul(value));
225            let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
226            let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
227            x.wrapping_mul(y.wrapping_add(1))
228        }
229    }
230}
231
232/// Returns the multiplicative inverse of the argument modulo 2^64. The implementation is based
233/// on Hurchalla's method for computing the multiplicative inverse modulo a power of two, and
234/// is essentially an optimized Newton iteration.
235///
236/// For correct results, the input `value` must be odd.
237///
238/// For better understanding the implementation, the following paper is recommended:
239/// J. Hurchalla, "An Improved Integer Multiplicative Inverse (modulo 2^w)",
240/// <https://arxiv.org/abs/2204.04342>
241#[must_use]
242pub(crate) const fn u64_invert_odd(value: u64) -> u64 {
243    debug_assert!(value & 1 == 1, "value must be odd");
244    let x = value.wrapping_mul(3) ^ 2;
245    let y = 1u64.wrapping_sub(x.wrapping_mul(value));
246    let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
247    let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
248    let (x, y) = (x.wrapping_mul(y.wrapping_add(1)), y.wrapping_mul(y));
249    x.wrapping_mul(y.wrapping_add(1))
250}
251
252#[cfg(test)]
253mod tests {
254    use super::{u32_max, u32_min, u32_rem, usize_lt};
255    use crate::Word;
256
257    // These primitives must remain usable by const callers.
258    const _: () = {
259        assert!(super::borrowing_sub(0, 0, Word::MAX).0 == Word::MAX);
260        assert!(super::borrowing_sub(0, 0, Word::MAX).1 == Word::MAX);
261        assert!(super::borrowing_sub(1, 0, Word::MAX).1 == 0);
262        assert!(super::leading_zeros(0) == Word::BITS);
263        assert!(super::trailing_zeros(0) == Word::BITS);
264        assert!(super::leading_zeros_u32_soft(0) == u32::BITS);
265    };
266
267    #[test]
268    fn zero_counts() {
269        for bit in 0..Word::BITS {
270            for value in [0, 1 << bit, !(1 << bit), Word::MAX >> bit] {
271                assert_eq!(super::leading_zeros(value), value.leading_zeros());
272                assert_eq!(super::trailing_zeros(value), value.trailing_zeros());
273            }
274        }
275    }
276
277    #[test]
278    fn zero_counts_u32() {
279        for bit in 0..u32::BITS {
280            for value in [0, 1 << bit, !(1 << bit), u32::MAX >> bit] {
281                assert_eq!(super::leading_zeros_u32(value), value.leading_zeros());
282                assert_eq!(super::trailing_zeros_u32(value), value.trailing_zeros());
283                // Exercise the software fallback even on hosts with native instructions.
284                assert_eq!(super::leading_zeros_u32_soft(value), value.leading_zeros());
285                assert_eq!(
286                    super::leading_zeros_u32_soft(value.reverse_bits()),
287                    value.trailing_zeros()
288                );
289            }
290        }
291    }
292
293    #[test]
294    fn carrying_mul_add_cannot_overflow() {
295        let lhs = Word::MAX;
296        let rhs = Word::MAX;
297        let addend = Word::MAX;
298        let carry_in = Word::MAX;
299        let (result, carry_out) = super::carrying_mul_add(lhs, rhs, addend, carry_in);
300        assert_eq!(result, Word::MAX);
301        assert_eq!(carry_out, Word::MAX);
302    }
303
304    #[test]
305    fn test_u32_const_min() {
306        assert_eq!(u32_min(0, 5), 0);
307        assert_eq!(u32_min(7, 0), 0);
308        assert_eq!(u32_min(7, 5), 5);
309        assert_eq!(u32_min(7, 7), 7);
310    }
311
312    #[test]
313    fn test_u32_const_max() {
314        assert_eq!(u32_max(0, 5), 5);
315        assert_eq!(u32_max(7, 0), 7);
316        assert_eq!(u32_max(7, 5), 7);
317        assert_eq!(u32_max(7, 7), 7);
318    }
319
320    #[test]
321    fn test_u32_const_rem() {
322        assert_eq!(u32_rem(0, 5), 0);
323        assert_eq!(u32_rem(4, 5), 4);
324        assert_eq!(u32_rem(7, 5), 2);
325        assert_eq!(u32_rem(101, 5), 1);
326    }
327
328    #[test]
329    fn test_usize_const_lt() {
330        assert!(usize_lt(0, 5).to_bool());
331        assert!(!usize_lt(7, 0).to_bool());
332        assert!(!usize_lt(7, 5).to_bool());
333        assert!(!usize_lt(7, 7).to_bool());
334    }
335
336    cpubits::cpubits! {
337        32 => {
338            #[test]
339            fn test_u32_invert_odd() {
340                use super::u32_invert_odd;
341
342                assert_eq!(u32_invert_odd(1), 1);
343                assert_eq!(u32_invert_odd(5).wrapping_mul(5), 1);
344                assert_eq!(u32_invert_odd(u32::MAX).wrapping_mul(u32::MAX), 1);
345            }
346        }
347    }
348}