const NIBBLE_0: u64 = 0x1111_1111_1111_1111;
const NIBBLE_1: u64 = NIBBLE_0 << 1;
const NIBBLE_2: u64 = NIBBLE_0 << 2;
const NIBBLE_3: u64 = NIBBLE_0 << 3;
#[inline]
pub(super) const fn bmul64(x: u64, y: u64) -> u64 {
let (x0, x1) = (x & NIBBLE_0, x & NIBBLE_1);
let (x2, x3) = (x & NIBBLE_2, x & NIBBLE_3);
let (y0, y1) = (y & NIBBLE_0, y & NIBBLE_1);
let (y2, y3) = (y & NIBBLE_2, y & NIBBLE_3);
let z0 = x0.wrapping_mul(y0) ^ x1.wrapping_mul(y3) ^ x2.wrapping_mul(y2) ^ x3.wrapping_mul(y1);
let z1 = x0.wrapping_mul(y1) ^ x1.wrapping_mul(y0) ^ x2.wrapping_mul(y3) ^ x3.wrapping_mul(y2);
let z2 = x0.wrapping_mul(y2) ^ x1.wrapping_mul(y1) ^ x2.wrapping_mul(y0) ^ x3.wrapping_mul(y3);
let z3 = x0.wrapping_mul(y3) ^ x1.wrapping_mul(y2) ^ x2.wrapping_mul(y1) ^ x3.wrapping_mul(y0);
(z0 & NIBBLE_0) | (z1 & NIBBLE_1) | (z2 & NIBBLE_2) | (z3 & NIBBLE_3)
}
#[inline]
const fn clmul_64x64_halves(a: u64, b: u64) -> (u64, u64) {
let low = bmul64(a, b);
let high = bmul64(a.reverse_bits(), b.reverse_bits()).reverse_bits() >> 1;
(low, high)
}
#[inline]
pub(super) const fn clmul_64x64(a: u64, b: u64) -> u128 {
let (low, high) = clmul_64x64_halves(a, b);
(low as u128) | ((high as u128) << 64)
}
#[inline]
pub(crate) const fn poly_mul_128(a: u128, b: u128) -> u128 {
let (low, high) = wide_mul_128(a, b);
super::reduce_128(low, high)
}
#[inline]
const fn wide_mul_128(a: u128, b: u128) -> (u128, u128) {
let (a0, a1) = (a as u64, (a >> 64) as u64);
let (b0, b1) = (b as u64, (b >> 64) as u64);
let (l0, l1) = clmul_64x64_halves(a0, b0);
let (h0, h1) = clmul_64x64_halves(a1, b1);
let (m0, m1) = clmul_64x64_halves(a0 ^ a1, b0 ^ b1);
let (m0, m1) = (m0 ^ l0 ^ h0, m1 ^ l1 ^ h1);
let low = (l0 as u128) | (((l1 ^ m0) as u128) << 64);
let high = ((h0 ^ m1) as u128) | ((h1 as u128) << 64);
(low, high)
}
#[inline]
pub(crate) fn poly_dot_128(pairs: impl Iterator<Item = (u128, u128)>) -> u128 {
let (low, high) = pairs.fold((0, 0), |(low, high), (a, b)| {
let (l, h) = wide_mul_128(a, b);
(low ^ l, high ^ h)
});
super::reduce_128(low, high)
}
#[inline]
const fn spread_bits_64(v: u64) -> u128 {
let mut x = v as u128;
x = (x | (x << 32)) & 0x0000_0000_ffff_ffff_0000_0000_ffff_ffff;
x = (x | (x << 16)) & 0x0000_ffff_0000_ffff_0000_ffff_0000_ffff;
x = (x | (x << 8)) & 0x00ff_00ff_00ff_00ff_00ff_00ff_00ff_00ff;
x = (x | (x << 4)) & 0x0f0f_0f0f_0f0f_0f0f_0f0f_0f0f_0f0f_0f0f;
x = (x | (x << 2)) & 0x3333_3333_3333_3333_3333_3333_3333_3333;
x = (x | (x << 1)) & 0x5555_5555_5555_5555_5555_5555_5555_5555;
x
}
#[inline]
pub(crate) const fn poly_square_128(a: u128) -> u128 {
super::reduce_128(spread_bits_64(a as u64), spread_bits_64((a >> 64) as u64))
}
#[inline]
pub(crate) const fn poly_mul_128_by_64(a: u128, b: u64) -> u128 {
let low = clmul_64x64(a as u64, b);
let middle = clmul_64x64((a >> 64) as u64, b);
super::reduce_128(low ^ (middle << 64), middle >> 64)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{bmul64, clmul_64x64, spread_bits_64};
use crate::clmul::basis::{TAIL_128, poly_mul};
fn spread_bits_64_reference(v: u64) -> u128 {
(0..64)
.filter(|i| (v >> i) & 1 == 1)
.fold(0u128, |acc, i| acc | (1u128 << (2 * i)))
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(4000))]
#[test]
fn the_masked_multiply_matches_the_bit_serial_product(a: u64, b: u64) {
let expected = super::super::scalar_clmul_64x64(a, b);
prop_assert_eq!(clmul_64x64(a, b), expected);
prop_assert_eq!(bmul64(a, b), expected as u64);
}
#[test]
fn the_bit_spread_matches_a_bit_at_a_time(v: u64) {
prop_assert_eq!(spread_bits_64(v), spread_bits_64_reference(v));
}
#[test]
fn the_software_product_matches_bit_serial_modular_multiplication(a: u128, b: u128) {
prop_assert_eq!(super::poly_mul_128(a, b), poly_mul(a, b, 128, TAIL_128));
}
#[test]
fn the_software_square_matches_the_software_product(a: u128) {
prop_assert_eq!(super::poly_square_128(a), super::poly_mul_128(a, a));
}
}
#[test]
fn the_masked_multiply_is_exact_where_the_carries_are_worst() {
const WORST: [u64; 4] = [
u64::MAX,
0x8888_8888_8888_8888,
0x1111_1111_1111_1111,
1 << 63,
];
for a in WORST {
for b in WORST {
assert_eq!(
clmul_64x64(a, b),
super::super::scalar_clmul_64x64(a, b),
"{a:#x} * {b:#x}"
);
}
}
}
}