use super::sqrt::compact_even;
use super::{clmul_64x64, reduce_64};
const ROOT_X: u64 = 0xffff_ffff_0000_000a;
#[allow(clippy::missing_const_for_fn)]
#[inline]
pub(crate) fn poly_mul_64(a: u64, b: u64) -> u64 {
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
{
super::x86_64::poly_mul_64(a, b)
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
{
composed_mul_64(a, b)
}
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
pub(crate) fn poly_square_64(a: u64) -> u64 {
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
{
super::x86_64::poly_square_64(a)
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
{
composed_square_64(a)
}
}
#[inline]
pub(crate) fn poly_dot_64(pairs: impl Iterator<Item = (u64, u64)>) -> u64 {
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
{
super::x86_64::poly_dot_64(pairs)
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
{
composed_dot_64(pairs)
}
}
#[allow(clippy::missing_const_for_fn)]
#[cfg_attr(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
allow(dead_code)
)]
#[inline]
fn composed_mul_64(a: u64, b: u64) -> u64 {
reduce_64(clmul_64x64(a, b))
}
#[allow(clippy::missing_const_for_fn)]
#[cfg_attr(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
allow(dead_code)
)]
#[inline]
fn composed_square_64(a: u64) -> u64 {
reduce_64(clmul_64x64(a, a))
}
#[cfg_attr(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
allow(dead_code)
)]
#[inline]
fn composed_dot_64(pairs: impl Iterator<Item = (u64, u64)>) -> u64 {
reduce_64(pairs.fold(0, |sum, (a, b)| sum ^ clmul_64x64(a, b)))
}
#[inline]
fn square_times(mut x: u64, count: usize) -> u64 {
for _ in 0..count {
x = poly_square_64(x);
}
x
}
#[inline]
pub(crate) fn poly_sqrt_64(a: u64) -> u64 {
let even = compact_even(a);
let odd = compact_even(a >> 1);
even ^ poly_mul_64(ROOT_X, odd)
}
#[inline]
pub(crate) fn poly_inverse_64(x: u64) -> u64 {
let b2 = poly_mul_64(poly_square_64(x), x);
let b3 = poly_mul_64(poly_square_64(b2), x);
let b6 = poly_mul_64(square_times(b3, 3), b3);
let b12 = poly_mul_64(square_times(b6, 6), b6);
let b24 = poly_mul_64(square_times(b12, 12), b12);
let b48 = poly_mul_64(square_times(b24, 24), b24);
let b60 = poly_mul_64(square_times(b48, 12), b12);
let b63 = poly_mul_64(square_times(b60, 3), b3);
poly_square_64(b63)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{
ROOT_X, composed_dot_64, composed_mul_64, composed_square_64, poly_dot_64, poly_inverse_64,
poly_mul_64, poly_sqrt_64, poly_square_64,
};
fn schoolbook(a: u64, b: u64) -> u64 {
let mut acc = 0u64;
for i in (0..64).rev() {
let overflowed = acc & (1 << 63) != 0;
acc <<= 1;
if overflowed {
acc ^= 0b1_1011;
}
if (b >> i) & 1 == 1 {
acc ^= a;
}
}
acc
}
#[test]
fn the_modulus_reduces_the_way_the_polynomial_says() {
assert_eq!(poly_mul_64(1 << 63, 2), 0b1_1011);
assert_eq!(poly_mul_64(2, 2), 4);
assert_eq!(poly_square_64(3), 5);
}
#[test]
fn the_square_is_linear_on_every_basis_vector() {
for i in 0..64 {
let a = 1u64 << i;
assert_eq!(poly_square_64(a), schoolbook(a, a), "vector {i}");
}
}
#[test]
fn the_square_root_of_the_variable_squares_back() {
assert_eq!(poly_mul_64(ROOT_X, ROOT_X), 2);
}
#[test]
fn the_product_is_exact_on_the_extremes() {
const CORNERS: [u64; 6] = [0, 1, u64::MAX, 1 << 63, 0b1_1011, (1 << 32) - 1];
for a in CORNERS {
assert_eq!(poly_square_64(a), schoolbook(a, a), "{a:#x} squared");
for b in CORNERS {
assert_eq!(poly_mul_64(a, b), schoolbook(a, b), "{a:#x} * {b:#x}");
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(4000))]
#[test]
fn the_product_matches_the_schoolbook_reduction(a: u64, b: u64) {
let expected = schoolbook(a, b);
prop_assert_eq!(poly_mul_64(a, b), expected);
prop_assert_eq!(composed_mul_64(a, b), expected);
prop_assert_eq!(poly_square_64(a), schoolbook(a, a));
prop_assert_eq!(composed_square_64(a), schoolbook(a, a));
}
#[test]
fn the_dot_product_sums_the_individual_products(a: u64, b: u64, c: u64, d: u64) {
let expected = schoolbook(a, b) ^ schoolbook(c, d);
let pairs = [(a, b), (c, d)];
prop_assert_eq!(poly_dot_64(pairs.into_iter()), expected);
prop_assert_eq!(composed_dot_64(pairs.into_iter()), expected);
}
#[test]
fn the_square_root_squares_back(a: u64) {
let root = poly_sqrt_64(a);
prop_assert_eq!(schoolbook(root, root), a);
}
#[test]
fn the_inverse_multiplies_back_to_one(a: u64) {
prop_assert_eq!(schoolbook(a, poly_inverse_64(a)), u64::from(a != 0));
}
}
}