Skip to main content

const_num_traits/ops/
clmul.rs

1//! Carry-less ("polynomial" / GF(2)) multiplications — the clmul /
2//! PCLMULQDQ family used by GHASH, CRC, and similar bit-polynomial codes.
3//! Each partial product is XORed (rather than added) into the accumulator,
4//! so there is no carry to propagate: a distinct domain from the integer
5//! arithmetic in [`crate::ops::carrying`].
6//!
7//! All bodies are hand-rolled (ported from core's portable intrinsic
8//! fallbacks) so they work on the crate's MSRV and inside `const` on nightly.
9//!
10//! **CT tier A (CT-implementable)**: all bodies are branchless on the data.
11
12#[cfg(target_pointer_width = "16")]
13type UDoubleSize = u32;
14#[cfg(target_pointer_width = "32")]
15type UDoubleSize = u64;
16#[cfg(target_pointer_width = "64")]
17type UDoubleSize = u128;
18
19c0nst::c0nst! {
20/// Performs carry-less ("polynomial" / XOR) multiplication.
21pub c0nst trait CarrylessMul: Sized {
22    /// Carry-less multiplication of `self` and `rhs`: a multiplication where
23    /// the partial products are XORed instead of added, i.e. multiplication
24    /// of polynomials over GF(2). Returns the low `BITS` bits of the result.
25    ///
26    /// ```
27    /// use const_num_traits::CarrylessMul;
28    ///
29    /// // (x + 1)² = x² + 1 over GF(2)
30    /// assert_eq!(CarrylessMul::carryless_mul(0b11u8, 0b11), 0b101);
31    /// ```
32    type Output;
33    fn carryless_mul(self, rhs: Self) -> Self::Output;
34}
35}
36
37macro_rules! carryless_mul_impl {
38    ($($t:ty)*) => {$(
39        c0nst::c0nst! {
40        c0nst impl CarrylessMul for $t {
41            type Output = $t;
42            #[inline]
43            fn carryless_mul(self, rhs: Self) -> $t {
44                let mut result: $t = 0;
45                let mut i = 0u32;
46                while i < <$t>::BITS {
47                    // branchless: mask is all-ones iff bit i of rhs is set
48                    let mask = ((rhs >> i) & 1).wrapping_neg();
49                    result ^= (self << i) & mask;
50                    i += 1;
51                }
52                result
53            }
54        }
55        }
56    )*};
57}
58
59carryless_mul_impl!(usize u8 u16 u32 u64 u128);
60
61c0nst::c0nst! {
62/// Performs carry-less multiplication that widens into the next-larger
63/// integer type, keeping all result bits.
64pub c0nst trait WideningCarrylessMul: Sized {
65    /// The double-width result type.
66    type Wide;
67
68    /// Widening carry-less multiplication: like
69    /// [`CarrylessMul::carryless_mul`] but returning the full product in the
70    /// double-width type. Like std, only provided for `u8`–`u64`.
71    ///
72    /// ```
73    /// use const_num_traits::WideningCarrylessMul;
74    ///
75    /// assert_eq!(WideningCarrylessMul::widening_carryless_mul(0x80u8, 0x80), 0x4000u16);
76    /// ```
77    fn widening_carryless_mul(self, rhs: Self) -> Self::Wide;
78}
79}
80
81macro_rules! widening_carryless_mul_impl {
82    ($($t:ty => $w:ty;)*) => {$(
83        c0nst::c0nst! {
84        c0nst impl WideningCarrylessMul for $t {
85            type Wide = $w;
86
87            #[inline]
88            fn widening_carryless_mul(self, rhs: Self) -> $w {
89                let wide = self as $w;
90                let mut result: $w = 0;
91                let mut i = 0u32;
92                while i < <$t>::BITS {
93                    // branchless: mask is all-ones iff bit i of rhs is set
94                    let mask = (((rhs >> i) & 1) as $w).wrapping_neg();
95                    result ^= (wide << i) & mask;
96                    i += 1;
97                }
98                result
99            }
100        }
101        }
102    )*};
103}
104
105widening_carryless_mul_impl! {
106    u8 => u16;
107    u16 => u32;
108    u32 => u64;
109    u64 => u128;
110}
111
112c0nst::c0nst! {
113/// Performs full double-width carry-less multiplication with a carry-in,
114/// for chaining multi-word carry-less ("polynomial") multiplications.
115pub c0nst trait CarryingCarrylessMul: Sized {
116    /// Calculates the full carry-less product `self ⊗ rhs`, XORs `carry`
117    /// into the low word (in GF(2), "adding" the carry is XOR and can never
118    /// propagate), and returns `(low, high)`.
119    ///
120    /// Unlike [`WideningCarrylessMul`] this is available for all unsigned
121    /// types including `u128` and `usize`, mirroring std.
122    ///
123    /// ```
124    /// use const_num_traits::CarryingCarrylessMul;
125    ///
126    /// assert_eq!(
127    ///     CarryingCarrylessMul::carrying_carryless_mul(0x80u8, 0x80, 0b11),
128    ///     (0b11, 0x40),
129    /// );
130    /// ```
131    type Output;
132    fn carrying_carryless_mul(self, rhs: Self, carry: Self) -> (Self::Output, Self::Output);
133}
134}
135
136macro_rules! carrying_carryless_mul_impl {
137    // clmul in the double-width type, then split
138    ($($t:ty => $w:ty;)*) => {$(
139        c0nst::c0nst! {
140        c0nst impl CarryingCarrylessMul for $t {
141            type Output = $t;
142            #[inline]
143            fn carrying_carryless_mul(self, rhs: Self, carry: Self) -> ($t, $t) {
144                let a = self as $w;
145                let mut p: $w = 0;
146                let mut i = 0u32;
147                while i < <$t>::BITS {
148                    // branchless: mask is all-ones iff bit i of rhs is set
149                    let mask = (((rhs >> i) & 1) as $w).wrapping_neg();
150                    p ^= (a << i) & mask;
151                    i += 1;
152                }
153                ((p as $t) ^ carry, (p >> <$t>::BITS) as $t)
154            }
155        }
156        }
157    )*};
158}
159
160carrying_carryless_mul_impl! {
161    u8 => u16;
162    u16 => u32;
163    u32 => u64;
164    u64 => u128;
165    usize => UDoubleSize;
166}
167
168// u128 has no wider type; Karatsuba over three 64x64 carry-less products,
169// same as core (carry-less multiplication distributes over XOR).
170const fn wide_clmul_u64(a: u64, b: u64) -> u128 {
171    let a = a as u128;
172    let mut p: u128 = 0;
173    let mut i = 0u32;
174    while i < 64 {
175        // branchless: mask is all-ones iff bit i of b is set
176        let mask = (((b >> i) & 1) as u128).wrapping_neg();
177        p ^= (a << i) & mask;
178        i += 1;
179    }
180    p
181}
182
183c0nst::c0nst! {
184c0nst impl CarryingCarrylessMul for u128 {
185    type Output = u128;
186    #[inline]
187    fn carrying_carryless_mul(self, rhs: Self, carry: Self) -> (u128, u128) {
188        let x0 = self as u64;
189        let x1 = (self >> 64) as u64;
190        let y0 = rhs as u64;
191        let y1 = (rhs >> 64) as u64;
192
193        let z0 = wide_clmul_u64(x0, y0);
194        let z2 = wide_clmul_u64(x1, y1);
195        // Karatsuba: z3 = (x0^x1)(y0^y1) = z0 ^ z1 ^ z2, so
196        let z3 = wide_clmul_u64(x0 ^ x1, y0 ^ y1);
197        let z1 = z3 ^ z0 ^ z2;
198
199        let lo = z0 ^ (z1 << 64);
200        let hi = z2 ^ (z1 >> 64);
201        (lo ^ carry, hi)
202    }
203}
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn carrying_carryless() {
212        // small types: agree with widening clmul split + XOR'd carry
213        for a in [0u8, 1, 3, 0x80, 0xAB, 0xFF] {
214            for b in [0u8, 1, 3, 0x80, 0xCD, 0xFF] {
215                let wide = WideningCarrylessMul::widening_carryless_mul(a, b);
216                let (lo, hi) = CarryingCarrylessMul::carrying_carryless_mul(a, b, 0x5A);
217                assert_eq!(lo, (wide as u8) ^ 0x5A);
218                assert_eq!(hi, (wide >> 8) as u8);
219            }
220        }
221        // u128 Karatsuba: agree with a direct 256-bit shift-xor reference
222        fn reference(a: u128, b: u128) -> (u128, u128) {
223            let (mut lo, mut hi) = (0u128, 0u128);
224            for i in 0..128u32 {
225                if (b >> i) & 1 == 1 {
226                    lo ^= a.wrapping_shl(i);
227                    if i > 0 {
228                        hi ^= a >> (128 - i);
229                    }
230                }
231            }
232            (lo, hi)
233        }
234        let vals = [
235            0u128,
236            1,
237            u128::MAX,
238            0x0123_4567_89ab_cdef_fedc_ba98_7654_3210,
239            1 << 127,
240            0xdead_beef,
241        ];
242        for &a in &vals {
243            for &b in &vals {
244                let (lo, hi) = CarryingCarrylessMul::carrying_carryless_mul(a, b, 0);
245                assert_eq!((lo, hi), reference(a, b), "{a:#x} {b:#x}");
246            }
247        }
248        // carry is XOR'd into the low word only
249        let (lo0, hi0) = CarryingCarrylessMul::carrying_carryless_mul(u128::MAX, u128::MAX, 0);
250        let (lo1, hi1) = CarryingCarrylessMul::carrying_carryless_mul(u128::MAX, u128::MAX, 0xFF);
251        assert_eq!((lo0 ^ 0xFF, hi0), (lo1, hi1));
252    }
253
254    #[test]
255    fn carryless() {
256        assert_eq!(CarrylessMul::carryless_mul(0b11u8, 0b11), 0b101);
257        assert_eq!(CarrylessMul::carryless_mul(0x80u8, 0x80), 0); // truncated
258        assert_eq!(
259            WideningCarrylessMul::widening_carryless_mul(0x80u8, 0x80),
260            0x4000u16
261        );
262        // clmul by a power of two is a plain shift
263        assert_eq!(CarrylessMul::carryless_mul(0b1011u32, 0b100), 0b101100);
264    }
265}