Skip to main content

fixed_bigint/heapless/
shift.rs

1//! `Shl<usize>` / `Shr<usize>` for `HeaplessBigInt`.
2//!
3//! Bit-count shifts by a public `usize` amount. Output `len` is derived
4//! from operand `len` + shift amount, both public shape parameters. The
5//! shape math:
6//!
7//! - `Shl`: width-preserving — `out_len = self.len`, bits shifted past the
8//!   operand width are discarded (`x << bits mod 2^(len·word_bits)`), so a
9//!   value at `len = k` shifts exactly like `FixedUInt<T, k>`. `CAP` never
10//!   enters; the words beyond `len` do not exist. A caller wanting the
11//!   shifted value to occupy more words constructs it at the wider width
12//!   first (as `div_rem` does).
13//! - `Shr`: `out_len = self.len.saturating_sub(bits / word_bits)` on `Nct`. The
14//!   top limb may become zero — that's fine under the zero-tail invariant
15//!   and downstream can trim explicitly if needed.
16//!
17//! Personality dispatch: the `Nct` arms take the direct word/bit shift above
18//! (branching on the amount, fine for a public amount). The `Ct` arms route
19//! through the branchless barrels [`const_shl_ct`] / [`const_shr_ct`] so a
20//! secret shift amount never drives control flow — and `Ct` `Shr` is
21//! width-preserving, since a data-dependent `len` shrink would itself leak.
22
23use super::{HeaplessBigInt, zero};
24use crate::MachineWord;
25use const_num_traits::{Bounded, Personality, PersonalityTag};
26use core::marker::PhantomData;
27use core::ops::{Shl, ShlAssign, Shr, ShrAssign};
28
29/// Width-preserving left shift by a **public** `bits`: the raw word/bit-shift
30/// body, output `len == value.len`. Branches on `word_shift`/`bit_shift`, so it
31/// is only ever called with a public amount (the `Nct` operator arm, or a
32/// barrel stage `2^k`). The `Ct` operator arm routes through [`const_shl_ct`]
33/// instead so a secret amount never reaches this branchful body.
34fn shl_wp<T: MachineWord, const CAP: usize, P: Personality>(
35    value: HeaplessBigInt<T, CAP, P>,
36    bits: usize,
37) -> HeaplessBigInt<T, CAP, P> {
38    let word_bits = core::mem::size_of::<T>() * 8;
39    let word_shift = bits / word_bits;
40    let bit_shift = bits % word_bits;
41    let out_len = value.len as usize;
42    let mut limbs = [zero::<T>(); CAP];
43    let mut i = 0;
44    while i < out_len {
45        let dst_lo = i + word_shift;
46        if dst_lo < out_len {
47            limbs[dst_lo] |= value.limbs[i] << bit_shift;
48            if bit_shift > 0 {
49                let dst_hi = dst_lo + 1;
50                if dst_hi < out_len {
51                    limbs[dst_hi] |= value.limbs[i] >> (word_bits - bit_shift);
52                }
53            }
54        }
55        i += 1;
56    }
57    HeaplessBigInt {
58        limbs,
59        len: value.len,
60        _p: PhantomData,
61    }
62}
63
64/// Width-preserving right shift by a **public** `bits`, output `len ==
65/// value.len` (the top limbs zero-fill rather than the `len` shrinking as the
66/// `Nct` `Shr` operator does). Public-only, like [`shl_wp`]; a secret amount
67/// goes through [`const_shr_ct`].
68fn shr_wp<T: MachineWord, const CAP: usize, P: Personality>(
69    value: HeaplessBigInt<T, CAP, P>,
70    bits: usize,
71) -> HeaplessBigInt<T, CAP, P> {
72    let word_bits = core::mem::size_of::<T>() * 8;
73    let word_shift = bits / word_bits;
74    let bit_shift = bits % word_bits;
75    let n = value.len as usize;
76    let mut limbs = [zero::<T>(); CAP];
77    let mut i = 0;
78    while i < n {
79        let src_lo = i + word_shift;
80        let lo = if src_lo < n {
81            value.limbs[src_lo] >> bit_shift
82        } else {
83            zero::<T>()
84        };
85        let hi = if bit_shift > 0 && src_lo + 1 < n {
86            value.limbs[src_lo + 1] << (word_bits - bit_shift)
87        } else {
88            zero::<T>()
89        };
90        limbs[i] = lo | hi;
91        i += 1;
92    }
93    HeaplessBigInt {
94        limbs,
95        len: value.len,
96        _p: PhantomData,
97    }
98}
99
100/// Full-`T` mask from bit 0 of a `black_box`'d choice: `0` when clear, `T::MAX`
101/// when set. Mirrors `FixedUInt::const_ct_select` — the `black_box` stops LLVM
102/// recognising the XOR-AND-XOR select and rewriting it into a secret-flag
103/// conditional move (which asm-grep can't see but ctgrind's taint pass can).
104#[inline]
105fn ct_mask<T: MachineWord>(choice_bit: u8) -> T {
106    let bit = core::hint::black_box(choice_bit & 1);
107    let bit_t = <T as core::convert::From<u8>>::from(bit);
108    <T as core::ops::Mul>::mul(bit_t, <T as Bounded>::max_value())
109}
110
111/// Constant-time left shift by a **secret** `bits`: a branchless barrel
112/// shifter, the heapless twin of `FixedUInt::const_shl_ct`. Each public stage
113/// `2^k` is applied-or-not via a per-limb masked XOR select on bit `k` of
114/// `bits`, so the secret never drives control flow or a memory index. Result
115/// width is `value.len` (as `<<`).
116pub(crate) fn const_shl_ct<T: MachineWord, const CAP: usize, P: Personality>(
117    value: HeaplessBigInt<T, CAP, P>,
118    bits: usize,
119) -> HeaplessBigInt<T, CAP, P> {
120    let n = value.len as usize;
121    // `1usize << k` for `k < usize::BITS` stays in range; this covers every
122    // amount, over-width ones included (a stage that shifts past the width
123    // zeroes the operand, so the select folds to zero). Matches
124    // `FixedUInt::const_shl_ct`'s `layers`.
125    let layers = core::mem::size_of::<usize>() * 8;
126    let mut target = value;
127    let mut k = 0;
128    while k < layers {
129        let shifted = shl_wp(target, 1usize << k);
130        let mask = ct_mask::<T>(((bits >> k) & 1) as u8);
131        let mut i = 0;
132        while i < n {
133            let diff = target.limbs[i] ^ shifted.limbs[i];
134            target.limbs[i] ^= mask & diff;
135            i += 1;
136        }
137        k += 1;
138    }
139    target
140}
141
142/// Constant-time right shift by a **secret** `bits`: mirror of
143/// [`const_shl_ct`] via [`shr_wp`]. Width-preserving (`len == value.len`).
144pub(crate) fn const_shr_ct<T: MachineWord, const CAP: usize, P: Personality>(
145    value: HeaplessBigInt<T, CAP, P>,
146    bits: usize,
147) -> HeaplessBigInt<T, CAP, P> {
148    let n = value.len as usize;
149    let layers = core::mem::size_of::<usize>() * 8;
150    let mut target = value;
151    let mut k = 0;
152    while k < layers {
153        let shifted = shr_wp(target, 1usize << k);
154        let mask = ct_mask::<T>(((bits >> k) & 1) as u8);
155        let mut i = 0;
156        while i < n {
157            let diff = target.limbs[i] ^ shifted.limbs[i];
158            target.limbs[i] ^= mask & diff;
159            i += 1;
160        }
161        k += 1;
162    }
163    target
164}
165
166/// Secret-amount left shift used by `ct_next_pow2`. Thin `u32` wrapper over
167/// [`const_shl_ct`].
168pub(crate) fn ct_shl<T: MachineWord, const CAP: usize, P: Personality>(
169    value: HeaplessBigInt<T, CAP, P>,
170    amount: u32,
171) -> HeaplessBigInt<T, CAP, P> {
172    const_shl_ct(value, amount as usize)
173}
174
175// `Shl<u32>` / `Shr<u32>` delegate to the `usize` impls, matching `FixedUInt`.
176// The `num_traits` shift traits (`WrappingShl`, `CheckedShl`, …) require these
177// as supertraits.
178//
179// An over-width amount is guarded *before* the `as usize` cast: on a 16-bit
180// `usize` target a bare cast of a `u32 >= 2^16` would truncate an over-width
181// count into a small in-range one (e.g. `<< 65536` becoming `<< 0`), so we
182// short-circuit to the over-width result the `usize` impls would produce
183// (`Shl` zeroes at `self.len`, `Shr` empties to len 0). `value_bits()` is a
184// `u32`, so the comparison itself never truncates.
185impl<T: MachineWord, const CAP: usize, P: Personality> Shl<u32> for HeaplessBigInt<T, CAP, P> {
186    type Output = Self;
187    fn shl(self, bits: u32) -> Self::Output {
188        match P::TAG {
189            // Ct: the over-width guard would branch on the (secret) amount, so
190            // go straight to the barrel — it collapses over-width shifts to
191            // zero on its own. `bits as usize` never truncates a meaningful
192            // amount (over-width already yields zero).
193            PersonalityTag::Ct => const_shl_ct(self, bits as usize),
194            PersonalityTag::Nct => {
195                let value_bits = self.len as u32 * (core::mem::size_of::<T>() as u32 * 8);
196                if bits >= value_bits {
197                    Self::new_zero_with_len(self.len())
198                } else {
199                    self << (bits as usize)
200                }
201            }
202        }
203    }
204}
205
206impl<T: MachineWord, const CAP: usize, P: Personality> Shr<u32> for HeaplessBigInt<T, CAP, P> {
207    type Output = Self;
208    fn shr(self, bits: u32) -> Self::Output {
209        match P::TAG {
210            PersonalityTag::Ct => const_shr_ct(self, bits as usize),
211            PersonalityTag::Nct => {
212                let value_bits = self.len as u32 * (core::mem::size_of::<T>() as u32 * 8);
213                if bits >= value_bits {
214                    Self::new_zero_with_len(0)
215                } else {
216                    self >> (bits as usize)
217                }
218            }
219        }
220    }
221}
222
223impl<T: MachineWord, const CAP: usize, P: Personality> Shl<usize> for HeaplessBigInt<T, CAP, P> {
224    type Output = Self;
225
226    fn shl(self, bits: usize) -> Self::Output {
227        // Ct routes a (possibly secret) amount through the branchless barrel;
228        // Nct takes the direct word/bit shift. `P::TAG` is a compile-time
229        // constant, so each monomorphisation keeps only its own arm.
230        match P::TAG {
231            PersonalityTag::Nct => shl_wp(self, bits),
232            PersonalityTag::Ct => const_shl_ct(self, bits),
233        }
234    }
235}
236
237impl<T: MachineWord, const CAP: usize, P: Personality> ShlAssign<usize>
238    for HeaplessBigInt<T, CAP, P>
239{
240    fn shl_assign(&mut self, bits: usize) {
241        *self = *self << bits;
242    }
243}
244
245impl<T: MachineWord, const CAP: usize, P: Personality> ShrAssign<usize>
246    for HeaplessBigInt<T, CAP, P>
247{
248    fn shr_assign(&mut self, bits: usize) {
249        *self = *self >> bits;
250    }
251}
252
253impl<T: MachineWord, const CAP: usize, P: Personality> Shr<usize> for HeaplessBigInt<T, CAP, P> {
254    type Output = Self;
255
256    fn shr(self, bits: usize) -> Self::Output {
257        match P::TAG {
258            // Ct: branchless barrel, width-preserving (a data-dependent `len`
259            // shrink would itself leak a secret amount).
260            PersonalityTag::Ct => const_shr_ct(self, bits),
261            // Nct: direct shift, shrinking `out_len = len - word_shift` (the
262            // documented heapless `Shr` width behaviour).
263            PersonalityTag::Nct => {
264                let word_bits = core::mem::size_of::<T>() * 8;
265                let word_shift = bits / word_bits;
266                let bit_shift = bits % word_bits;
267
268                let mut limbs = [zero::<T>(); CAP];
269                if word_shift >= self.len as usize {
270                    return Self {
271                        limbs,
272                        len: 0,
273                        _p: PhantomData,
274                    };
275                }
276
277                let out_len = self.len as usize - word_shift;
278
279                let mut i = 0;
280                while i < out_len {
281                    let src_lo = i + word_shift;
282                    let lo = self.limbs[src_lo] >> bit_shift;
283                    let hi = if bit_shift > 0 && src_lo + 1 < self.len as usize {
284                        self.limbs[src_lo + 1] << (word_bits - bit_shift)
285                    } else {
286                        zero::<T>()
287                    };
288                    limbs[i] = lo | hi;
289                    i += 1;
290                }
291
292                Self {
293                    limbs,
294                    len: out_len as u16,
295                    _p: PhantomData,
296                }
297            }
298        }
299    }
300}
301
302// ── Ref-receiver / ref-RHS operand forms ──
303//
304// The value `Shl<usize>`/`Shr<usize>` (and their `u32` forms above) carry the
305// shift logic; every remaining operand form deref-and-forwards to them.
306// `HeaplessBigInt: Copy`, so each deref is a no-op at runtime. This completes
307// the same operand matrix `FixedUInt` exposes — receiver ∈ {value, &}, RHS ∈
308// {usize, u32, &usize, &u32} — given the value/`usize` and value/`u32` forms.
309macro_rules! shift_operand_forms {
310    ($imp:ident, $method:ident, $op:tt, $scalar:ty) => {
311        impl<T: MachineWord, const CAP: usize, P: Personality> $imp<&$scalar>
312            for HeaplessBigInt<T, CAP, P>
313        {
314            type Output = HeaplessBigInt<T, CAP, P>;
315            fn $method(self, bits: &$scalar) -> Self::Output {
316                self $op *bits
317            }
318        }
319
320        impl<T: MachineWord, const CAP: usize, P: Personality> $imp<$scalar>
321            for &HeaplessBigInt<T, CAP, P>
322        {
323            type Output = HeaplessBigInt<T, CAP, P>;
324            fn $method(self, bits: $scalar) -> Self::Output {
325                *self $op bits
326            }
327        }
328
329        impl<T: MachineWord, const CAP: usize, P: Personality> $imp<&$scalar>
330            for &HeaplessBigInt<T, CAP, P>
331        {
332            type Output = HeaplessBigInt<T, CAP, P>;
333            fn $method(self, bits: &$scalar) -> Self::Output {
334                *self $op *bits
335            }
336        }
337    };
338}
339
340shift_operand_forms!(Shl, shl, <<, usize);
341shift_operand_forms!(Shl, shl, <<, u32);
342shift_operand_forms!(Shr, shr, >>, usize);
343shift_operand_forms!(Shr, shr, >>, u32);
344
345// Assign forms. `ShlAssign<usize>`/`ShrAssign<usize>` are hand-written above;
346// these add the `u32`, `&usize`, and `&u32` RHS variants so `x <<= n` accepts
347// the same amounts as `x << n`.
348impl<T: MachineWord, const CAP: usize, P: Personality> ShlAssign<u32>
349    for HeaplessBigInt<T, CAP, P>
350{
351    fn shl_assign(&mut self, bits: u32) {
352        *self = *self << bits;
353    }
354}
355
356impl<T: MachineWord, const CAP: usize, P: Personality> ShrAssign<u32>
357    for HeaplessBigInt<T, CAP, P>
358{
359    fn shr_assign(&mut self, bits: u32) {
360        *self = *self >> bits;
361    }
362}
363
364impl<T: MachineWord, const CAP: usize, P: Personality> ShlAssign<&usize>
365    for HeaplessBigInt<T, CAP, P>
366{
367    fn shl_assign(&mut self, bits: &usize) {
368        *self = *self << *bits;
369    }
370}
371
372impl<T: MachineWord, const CAP: usize, P: Personality> ShrAssign<&usize>
373    for HeaplessBigInt<T, CAP, P>
374{
375    fn shr_assign(&mut self, bits: &usize) {
376        *self = *self >> *bits;
377    }
378}
379
380impl<T: MachineWord, const CAP: usize, P: Personality> ShlAssign<&u32>
381    for HeaplessBigInt<T, CAP, P>
382{
383    fn shl_assign(&mut self, bits: &u32) {
384        *self = *self << *bits;
385    }
386}
387
388impl<T: MachineWord, const CAP: usize, P: Personality> ShrAssign<&u32>
389    for HeaplessBigInt<T, CAP, P>
390{
391    fn shr_assign(&mut self, bits: &u32) {
392        *self = *self >> *bits;
393    }
394}
395
396#[cfg(test)]
397mod ct_shl_tests {
398    use super::{HeaplessBigInt, ct_shl};
399    use const_num_traits::{Ct, Nct};
400
401    type HC = HeaplessBigInt<u8, 4, Ct>; // 32-bit width
402    type HN = HeaplessBigInt<u8, 4, Nct>;
403
404    #[test]
405    fn ct_shl_matches_plain_shift_all_amounts() {
406        // The barrel shifter must produce the same value as the (leaky) `<<`
407        // for every amount, including over-width (both yield 0 at the width).
408        for &raw in &[1u32, 0x1234_5678, 0xFFFF_FFFF, 0x8000_0000] {
409            let v = HC::from(raw);
410            for amount in 0u32..=40 {
411                assert_eq!(
412                    ct_shl(v, amount),
413                    v << (amount as usize),
414                    "ct_shl({raw:#x}, {amount})"
415                );
416            }
417        }
418    }
419
420    // The Ct barrels (`const_shl_ct` / `const_shr_ct`) must produce the same
421    // VALUE as the Nct reference shift at the full operand width — only timing
422    // differs. The CT fixtures check the barrels are branchless, not that they
423    // compute the right answer, so pin correctness here across both directions
424    // and all amounts (including over-width, which yields 0). `all_limbs`
425    // compares the full array, so the Ct width-preserving `>>` and the Nct
426    // len-shrinking `>>` still match limb-for-limb via the zero tail.
427    #[test]
428    fn ct_shifts_match_nct_reference() {
429        let cases = [
430            [1u8, 0, 0, 0],
431            [0x78, 0x56, 0x34, 0x12],
432            [0xFF, 0xFF, 0xFF, 0xFF],
433            [0, 0, 0, 0x80],
434        ];
435        for a in cases {
436            for amount in 0usize..=40 {
437                assert_eq!(
438                    (HC::from_limbs(a, 4) << amount).all_limbs(),
439                    (HN::from_limbs(a, 4) << amount).all_limbs(),
440                    "shl {a:?} << {amount}"
441                );
442                assert_eq!(
443                    (HC::from_limbs(a, 4) >> amount).all_limbs(),
444                    (HN::from_limbs(a, 4) >> amount).all_limbs(),
445                    "shr {a:?} >> {amount}"
446                );
447            }
448        }
449    }
450}