clock_bigint/
limbs.rs

1//! Limb manipulation utilities for BigInt operations.
2//!
3//! A limb is a 64-bit unsigned integer. All multi-limb integers use
4//! little-endian limb ordering.
5
6use crate::ct;
7
8/// Limb type (64-bit unsigned integer).
9pub type Limb = u64;
10
11/// Wide limb type (128-bit for multiplication results).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub struct WideLimb {
14    /// Low 64 bits.
15    pub lo: Limb,
16    /// High 64 bits.
17    pub hi: Limb,
18}
19
20impl WideLimb {
21    /// Create a new WideLimb from low and high parts.
22    #[inline(always)]
23    pub const fn new(lo: Limb, hi: Limb) -> Self {
24        Self { lo, hi }
25    }
26}
27
28/// Add two limbs with carry, returning (sum, carry_out).
29///
30/// This is the constant-time `adc` (add with carry) operation.
31/// `carry_in` should be 0 or 1.
32#[inline(always)]
33pub fn limb_add_carry(a: Limb, b: Limb, carry_in: Limb) -> (Limb, Limb) {
34    let carry_in = carry_in & 1; // Ensure it's 0 or 1
35    let sum = a.wrapping_add(b).wrapping_add(carry_in);
36
37    // Constant-time carry calculation:
38    // carry_out = (sum < a) OR ((carry_in == 1) AND (sum == a))
39    let carry1 = ct::ct_lt(sum, a);
40    let carry2 = (carry_in & ct::ct_eq(sum, a)) & 1;
41    let carry_out = carry1 | carry2;
42
43    (sum, carry_out)
44}
45
46/// Subtract two limbs with borrow, returning (difference, borrow_out).
47///
48/// This is the constant-time `sbb` (subtract with borrow) operation.
49/// `borrow_in` should be 0 or 1.
50#[inline(always)]
51pub fn limb_sub_borrow(a: Limb, b: Limb, borrow_in: Limb) -> (Limb, Limb) {
52    let borrow_in = borrow_in & 1; // Ensure it's 0 or 1
53    let diff = a.wrapping_sub(b).wrapping_sub(borrow_in);
54
55    // Constant-time borrow calculation:
56    // borrow_out = (a < b) OR ((borrow_in == 1) AND (a == b))
57    let borrow1 = ct::ct_lt(a, b);
58    let borrow2 = (borrow_in & ct::ct_eq(a, b)) & 1;
59    let borrow_out = borrow1 | borrow2;
60
61    (diff, borrow_out)
62}
63
64/// Multiply two limbs, returning a 128-bit wide result.
65///
66/// This performs 64×64→128 bit multiplication.
67#[inline(always)]
68pub fn limb_mul_wide(a: Limb, b: Limb) -> WideLimb {
69    // Use the standard Rust 128-bit multiplication
70    let result = (a as u128) * (b as u128);
71    WideLimb {
72        lo: result as u64,
73        hi: (result >> 64) as u64,
74    }
75}
76
77/// Multiply-accumulate: compute (x + y + carry) returning (sum, carry_out).
78///
79/// This is used in multiplication algorithms.
80#[inline(always)]
81pub fn limb_mac(x: Limb, y: Limb, carry: Limb) -> (Limb, Limb) {
82    limb_add_carry(x, y, carry)
83}
84
85/// Normalize a limb slice by removing leading zeros.
86///
87/// Returns the number of non-zero limbs (at least 1, even if all are zero).
88/// This operation is constant-time aware but may not be fully constant-time
89/// due to the need to find the actual length.
90pub fn normalize(limbs: &mut [Limb]) -> usize {
91    let mut len = limbs.len();
92
93    // Find the first non-zero limb from the end (most significant)
94    // We iterate in constant-time fashion but still need to return the actual length
95    while len > 1 && limbs[len - 1] == 0 {
96        len -= 1;
97    }
98
99    len
100}
101
102/// Canonicalize a limb slice according to the specification.
103///
104/// Enforces:
105/// 1. Zero: exactly one limb with value 0
106/// 2. No leading zeros for nonzero values
107/// 3. Minimal length
108///
109/// Returns the canonical length.
110pub fn canonicalize(limbs: &mut [Limb]) -> usize {
111    let normalized_len = normalize(limbs);
112
113    // If all limbs are zero, ensure exactly one zero limb
114    if normalized_len == 1 && limbs[0] == 0 {
115        return 1;
116    }
117
118    normalized_len
119}
120
121/// Check if a limb slice represents zero.
122#[inline(always)]
123pub fn is_zero(limbs: &[Limb]) -> bool {
124    limbs.iter().all(|&x| x == 0)
125}
126
127/// Compare two limb slices in constant time.
128///
129/// Returns:
130/// - 0 if a == b
131/// - 1 if a > b
132/// - 2 if a < b (encoded as u64 for constant-time)
133///
134/// Both slices must have the same length.
135pub fn ct_compare(a: &[Limb], b: &[Limb]) -> u64 {
136    debug_assert_eq!(a.len(), b.len());
137
138    let mut gt = 0u64;
139    let mut eq = 1u64;
140
141    // Compare from most significant to least significant
142    for i in (0..a.len()).rev() {
143        let a_gt_b = ct::ct_gt(a[i], b[i]);
144        let a_eq_b = ct::ct_eq(a[i], b[i]);
145
146        // Update gt: if we haven't found a difference yet (eq == 1),
147        // and current limb makes a > b, then set gt
148        gt = ct::ct_select(eq, a_gt_b, gt);
149        eq &= a_eq_b;
150    }
151
152    // Return: 0 if equal, 1 if a > b, 2 if a < b
153    let lt = eq ^ 1; // 0 if equal, 1 if not equal
154    let lt = lt & !gt; // 1 only if a < b
155
156    (lt << 1) | gt
157}
158
159#[cfg(all(test, feature = "alloc"))]
160mod tests {
161    #[cfg(feature = "alloc")]
162    use alloc::vec;
163    use super::*;
164
165    #[test]
166    fn test_limb_add_carry() {
167        let (sum, carry) = limb_add_carry(10, 20, 0);
168        assert_eq!(sum, 30);
169        assert_eq!(carry, 0);
170
171        let (sum, carry) = limb_add_carry(u64::MAX, 1, 0);
172        assert_eq!(sum, 0);
173        assert_eq!(carry, 1);
174
175        let (sum, carry) = limb_add_carry(u64::MAX, 0, 1);
176        assert_eq!(sum, 0);
177        assert_eq!(carry, 1);
178    }
179
180    #[test]
181    fn test_limb_sub_borrow() {
182        let (diff, borrow) = limb_sub_borrow(20, 10, 0);
183        assert_eq!(diff, 10);
184        assert_eq!(borrow, 0);
185
186        let (_diff, borrow) = limb_sub_borrow(10, 20, 0);
187        assert_eq!(borrow, 1);
188
189        let (_diff, borrow) = limb_sub_borrow(0, 0, 1);
190        assert_eq!(borrow, 1);
191    }
192
193    #[test]
194    fn test_limb_mul_wide() {
195        let result = limb_mul_wide(0xFFFFFFFFFFFFFFFF, 2);
196        assert_eq!(result.lo, 0xFFFFFFFFFFFFFFFE);
197        assert_eq!(result.hi, 1);
198
199        let result = limb_mul_wide(1 << 32, 1 << 32);
200        assert_eq!(result.lo, 0);
201        assert_eq!(result.hi, 1);
202    }
203
204    #[test]
205    fn test_normalize() {
206        let mut limbs = vec![1, 0, 0, 0];
207        assert_eq!(normalize(&mut limbs), 1);
208
209        let mut limbs = vec![1, 2, 0, 0];
210        assert_eq!(normalize(&mut limbs), 2);
211
212        let mut limbs = vec![0, 0, 0, 0];
213        assert_eq!(normalize(&mut limbs), 1);
214    }
215
216    #[test]
217    fn test_is_zero() {
218        assert!(is_zero(&[0, 0, 0]));
219        assert!(!is_zero(&[1, 0, 0]));
220        assert!(!is_zero(&[0, 1, 0]));
221    }
222}