mod basis;
pub(crate) use basis::{poly_to_tower_128, tower_to_poly_128};
#[cfg(all(target_arch = "aarch64", target_feature = "aes"))]
mod aarch64;
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
mod x86_64;
pub(crate) const HAS_HARDWARE_CLMUL: bool = cfg!(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(target_arch = "aarch64", target_feature = "aes"),
));
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(target_arch = "aarch64", target_feature = "aes"),
),
// Superseded by a backend, but still the reference the tests compare against.
allow(dead_code)
)]
#[inline]
const fn scalar_clmul_64x64(a: u64, b: u64) -> u128 {
let a = a as u128;
let mut acc = 0u128;
let mut i = 0;
while i < 64 {
let selected = 0u128.wrapping_sub(((b >> i) & 1) as u128);
acc ^= (a << i) & selected;
i += 1;
}
acc
}
#[cfg(all(target_arch = "aarch64", target_feature = "aes"))]
use aarch64::clmul_64x64;
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
use x86_64::clmul_64x64;
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(target_arch = "aarch64", target_feature = "aes"),
)))]
#[allow(clippy::missing_const_for_fn)]
#[inline]
fn clmul_64x64(a: u64, b: u64) -> u128 {
scalar_clmul_64x64(a, b)
}
#[inline]
fn clmul_128x128(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 low = clmul_64x64(a0, b0);
let high = clmul_64x64(a1, b1);
let middle = clmul_64x64(a0, b1) ^ clmul_64x64(a1, b0);
(low ^ (middle << 64), high ^ (middle >> 64))
}
#[inline]
const fn reduce_64(product: u128) -> u64 {
let low = product as u64;
let high = (product >> 64) as u64;
let folded = (high << 4) ^ (high << 3) ^ (high << 1) ^ high;
let spill = (high >> 60) ^ (high >> 61) ^ (high >> 63);
low ^ folded ^ ((spill << 4) ^ (spill << 3) ^ (spill << 1) ^ spill)
}
#[inline]
const fn reduce_128(low: u128, high: u128) -> u128 {
let folded = (high << 7) ^ (high << 2) ^ (high << 1) ^ high;
let spill = (high >> 121) ^ (high >> 126) ^ (high >> 127);
low ^ folded ^ ((spill << 7) ^ (spill << 2) ^ (spill << 1) ^ spill)
}
#[inline]
pub(crate) fn mul_64(a: u64, b: u64) -> u64 {
let product = clmul_64x64(basis::tower_to_poly_64(a), basis::tower_to_poly_64(b));
basis::poly_to_tower_64(reduce_64(product))
}
#[inline]
pub(crate) fn poly_mul_128(a: u128, b: u128) -> u128 {
let (low, high) = clmul_128x128(a, b);
reduce_128(low, high)
}
#[inline]
pub(crate) fn mul_128(a: u128, b: u128) -> u128 {
let product = poly_mul_128(basis::tower_to_poly_128(a), basis::tower_to_poly_128(b));
basis::poly_to_tower_128(product)
}
#[inline]
pub(crate) fn square_64(a: u64) -> u64 {
let poly = basis::tower_to_poly_64(a);
basis::poly_to_tower_64(reduce_64(clmul_64x64(poly, poly)))
}
#[inline]
pub(crate) fn poly_square_128(a: u128) -> u128 {
let (p0, p1) = (a as u64, (a >> 64) as u64);
reduce_128(clmul_64x64(p0, p0), clmul_64x64(p1, p1))
}
#[inline]
pub(crate) fn square_128(a: u128) -> u128 {
basis::poly_to_tower_128(poly_square_128(basis::tower_to_poly_128(a)))
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::basis::{TAIL_64, TAIL_128, poly_mul};
use crate::tower::TowerLevel;
use crate::{BinaryField64, BinaryField128};
fn scalar_clmul_128x128(a: u128, b: u128) -> (u128, u128) {
let mut low = 0u128;
let mut high = 0u128;
for i in 0..128 {
if (b >> i) & 1 == 1 {
low ^= a << i;
high ^= if i == 0 { 0 } else { a >> (128 - i) };
}
}
(low, high)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2000))]
#[test]
fn clmul_matches_reference(a: u128, b: u128) {
let x = BinaryField128::from_repr(a);
let y = BinaryField128::from_repr(b);
prop_assert_eq!(super::mul_128(a, b), x.reference_mul(y).to_repr());
}
#[test]
fn clmul_64_matches_reference(a: u64, b: u64) {
let x = BinaryField64::from_repr(a);
let y = BinaryField64::from_repr(b);
prop_assert_eq!(super::mul_64(a, b), x.reference_mul(y).to_repr());
}
#[test]
fn clmul_square_matches_reference(a: u128) {
let x = BinaryField128::from_repr(a);
prop_assert_eq!(super::square_128(a), x.reference_mul(x).to_repr());
}
#[test]
fn clmul_64_square_matches_reference(a: u64) {
let x = BinaryField64::from_repr(a);
prop_assert_eq!(super::square_64(a), x.reference_mul(x).to_repr());
}
#[test]
fn the_dispatched_carryless_product_matches_the_bit_serial_one(a: u64, b: u64) {
prop_assert_eq!(super::clmul_64x64(a, b), super::scalar_clmul_64x64(a, b));
}
#[test]
fn the_wide_product_matches_the_bit_serial_one(a: u128, b: u128) {
prop_assert_eq!(super::clmul_128x128(a, b), scalar_clmul_128x128(a, b));
}
#[test]
fn the_reduction_agrees_with_bit_serial_modular_multiplication(a: u128, b: u128) {
let (low, high) = scalar_clmul_128x128(a, b);
prop_assert_eq!(super::reduce_128(low, high), poly_mul(a, b, 128, TAIL_128));
let product = super::scalar_clmul_64x64(a as u64, b as u64);
prop_assert_eq!(
u128::from(super::reduce_64(product)),
poly_mul(u128::from(a as u64), u128::from(b as u64), 64, TAIL_64),
);
}
}
#[test]
fn hand_checkable_products() {
assert_eq!(super::mul_128(0b10, 0b10), 0b11);
assert_eq!(super::mul_64(0b10, 0b10), 0b11);
assert_eq!(super::mul_128(0, 0xdead_beef), 0);
assert_eq!(super::mul_64(0, 0xdead_beef), 0);
let a = 0x0123_4567_89ab_cdef_fedc_ba98_7654_3210u128;
assert_eq!(super::mul_128(a, 1), a);
assert_eq!(super::mul_128(1, a), a);
assert_eq!(super::mul_64(a as u64, 1), a as u64);
assert_eq!(super::mul_64(1, a as u64), a as u64);
}
}