use super::{clmul_64x64, reduce_64};
#[allow(clippy::missing_const_for_fn)]
#[inline]
pub(crate) fn poly_mul_192(a: &[u64; 3], b: &[u64; 3]) -> [u64; 3] {
#[cfg(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
))]
{
super::register::poly_mul_192::<super::Register>(a, b)
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
)))]
{
composed_mul_192(a, b)
}
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
pub(crate) fn poly_square_192(a: &[u64; 3]) -> [u64; 3] {
#[cfg(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
))]
{
super::register::poly_square_192::<super::Register>(a)
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
)))]
{
composed_square_192(a)
}
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
pub(crate) fn poly_mul_192_by_64(a: &[u64; 3], k: &u64) -> [u64; 3] {
#[cfg(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
))]
{
super::register::poly_mul_192_by_64::<super::Register>(a, k)
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
)))]
{
composed_mul_192_by_64(a, k)
}
}
#[inline]
pub(crate) fn poly_dot_192<'a>(
pairs: impl Iterator<Item = (&'a [u64; 3], &'a [u64; 3])>,
) -> [u64; 3] {
#[cfg(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
))]
{
super::register::poly_dot_192::<super::Register>(pairs)
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
)))]
{
composed_dot_192(pairs)
}
}
#[inline]
pub(crate) fn poly_dot_192_by_64<'a>(
pairs: impl Iterator<Item = (&'a [u64; 3], &'a u64)>,
) -> [u64; 3] {
#[cfg(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
))]
{
super::register::poly_dot_192_by_64::<super::Register>(pairs)
}
#[cfg(not(any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
)))]
{
composed_dot_192_by_64(pairs)
}
}
#[allow(clippy::missing_const_for_fn)]
#[inline]
fn mul_unreduced(&[a0, a1, a2]: &[u64; 3], &[b0, b1, b2]: &[u64; 3]) -> [u128; 3] {
let c0 = clmul_64x64(a0, b0);
let c1 = clmul_64x64(a1, b1);
let c2 = clmul_64x64(a2, b2);
let d01 = clmul_64x64(a0 ^ a1, b0 ^ b1);
let d02 = clmul_64x64(a0 ^ a2, b0 ^ b2);
let d12 = clmul_64x64(a1 ^ a2, b1 ^ b2);
[c0 ^ c1 ^ c2 ^ d12, c0 ^ d01 ^ d12, c0 ^ c1 ^ d02]
}
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
),
allow(dead_code)
)]
#[inline]
fn composed_mul_192(a: &[u64; 3], b: &[u64; 3]) -> [u64; 3] {
mul_unreduced(a, b).map(reduce_64)
}
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
),
allow(dead_code)
)]
#[inline]
fn composed_square_192(&[a0, a1, a2]: &[u64; 3]) -> [u64; 3] {
let (s0, s1, s2) = (
clmul_64x64(a0, a0),
clmul_64x64(a1, a1),
clmul_64x64(a2, a2),
);
[s0, s2, s1 ^ s2].map(reduce_64)
}
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
),
allow(dead_code)
)]
#[inline]
fn composed_mul_192_by_64(a: &[u64; 3], &k: &u64) -> [u64; 3] {
a.map(|x| reduce_64(clmul_64x64(x, k)))
}
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
),
allow(dead_code)
)]
#[inline]
fn composed_dot_192<'a>(pairs: impl Iterator<Item = (&'a [u64; 3], &'a [u64; 3])>) -> [u64; 3] {
let sum = pairs.fold([0u128; 3], |sum, (a, b)| {
let product = mul_unreduced(a, b);
core::array::from_fn(|i| sum[i] ^ product[i])
});
sum.map(reduce_64)
}
#[cfg_attr(
any(
all(target_arch = "x86_64", target_feature = "pclmulqdq"),
all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
),
),
allow(dead_code)
)]
#[inline]
fn composed_dot_192_by_64<'a>(pairs: impl Iterator<Item = (&'a [u64; 3], &'a u64)>) -> [u64; 3] {
let sum = pairs.fold([0u128; 3], |sum, (a, &k)| {
core::array::from_fn(|i| sum[i] ^ clmul_64x64(a[i], k))
});
sum.map(reduce_64)
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use proptest::prelude::*;
use super::{
composed_dot_192, composed_dot_192_by_64, composed_mul_192, composed_mul_192_by_64,
composed_square_192, poly_dot_192, poly_dot_192_by_64, poly_mul_192, poly_mul_192_by_64,
poly_square_192,
};
use crate::clmul::poly_mul_64;
fn schoolbook(a: [u64; 3], b: [u64; 3]) -> [u64; 3] {
let mut raw = [0u64; 5];
for i in 0..3 {
for j in 0..3 {
raw[i + j] ^= poly_mul_64(a[i], b[j]);
}
}
[raw[0] ^ raw[3], raw[1] ^ raw[3] ^ raw[4], raw[2] ^ raw[4]]
}
const CORNERS: [u64; 5] = [0, 1, u64::MAX, 1 << 63, 0xf << 60];
#[test]
fn both_routes_are_exact_on_the_extremes() {
let elements: Vec<[u64; 3]> = CORNERS
.iter()
.flat_map(|&x| CORNERS.iter().map(move |&y| [x, y, x ^ y]))
.collect();
for &a in &elements {
assert_eq!(poly_square_192(&a), schoolbook(a, a), "{a:x?} squared");
assert_eq!(composed_square_192(&a), schoolbook(a, a), "{a:x?} squared");
for &b in &elements {
assert_eq!(poly_mul_192(&a, &b), schoolbook(a, b), "{a:x?} * {b:x?}");
assert_eq!(
composed_mul_192(&a, &b),
schoolbook(a, b),
"{a:x?} * {b:x?}"
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(4000))]
#[test]
fn both_routes_match_the_schoolbook_product(a: [u64; 3], b: [u64; 3], k: u64) {
let expected = schoolbook(a, b);
prop_assert_eq!(poly_mul_192(&a, &b), expected);
prop_assert_eq!(composed_mul_192(&a, &b), expected);
prop_assert_eq!(poly_square_192(&a), schoolbook(a, a));
prop_assert_eq!(composed_square_192(&a), schoolbook(a, a));
let scaled = schoolbook(a, [k, 0, 0]);
prop_assert_eq!(poly_mul_192_by_64(&a, &k), scaled);
prop_assert_eq!(composed_mul_192_by_64(&a, &k), scaled);
}
#[test]
fn dot_products_sum_their_terms(
a: [u64; 3], b: [u64; 3], c: [u64; 3], d: [u64; 3], k: u64, l: u64,
) {
let expected: [u64; 3] = {
let (x, y) = (schoolbook(a, b), schoolbook(c, d));
core::array::from_fn(|i| x[i] ^ y[i])
};
let pairs = [(&a, &b), (&c, &d)];
prop_assert_eq!(poly_dot_192(pairs.into_iter()), expected);
prop_assert_eq!(composed_dot_192(pairs.into_iter()), expected);
let mixed: [u64; 3] = {
let (x, y) = (schoolbook(a, [k, 0, 0]), schoolbook(c, [l, 0, 0]));
core::array::from_fn(|i| x[i] ^ y[i])
};
let terms = [(&a, &k), (&c, &l)];
prop_assert_eq!(poly_dot_192_by_64(terms.into_iter()), mixed);
prop_assert_eq!(composed_dot_192_by_64(terms.into_iter()), mixed);
}
}
#[test]
fn an_empty_dot_product_is_zero() {
assert_eq!(poly_dot_192(core::iter::empty()), [0; 3]);
assert_eq!(poly_dot_192_by_64(core::iter::empty()), [0; 3]);
}
}