macro_rules! gf2m_field {
($elem:ident, $limbs:literal, $limbs2:literal, $m:literal, $f1:literal, $f2:literal, $f3:literal) => {
#[doc = concat!("An element of GF(2^", stringify!($m), ") - see the module doc comment ",
"for the reduction polynomial citation and the little-endian byte-order derivation.")]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct $elem(pub [u64; $limbs]);
const _: () = assert!($f1 > 0 && $f1 < 64 && $f2 > 0 && $f2 < 64 && $f3 > 0 && $f3 < 64);
impl $elem {
pub const ZERO: Self = Self([0u64; $limbs]);
#[must_use]
pub fn from_le_bytes(bytes: &[u8; $limbs * 8]) -> Self {
let mut limbs = [0u64; $limbs];
for (i, limb) in limbs.iter_mut().enumerate() {
let mut word = [0u8; 8];
word.copy_from_slice(&bytes[i * 8..i * 8 + 8]);
*limb = u64::from_le_bytes(word);
}
Self(limbs)
}
#[must_use]
pub fn to_le_bytes(self) -> [u8; $limbs * 8] {
let mut out = [0u8; $limbs * 8];
for (i, limb) in self.0.iter().enumerate() {
out[i * 8..i * 8 + 8].copy_from_slice(&limb.to_le_bytes());
}
out
}
#[must_use]
pub fn add(self, other: Self) -> Self {
let mut out = [0u64; $limbs];
for i in 0..$limbs {
out[i] = self.0[i] ^ other.0[i];
}
Self(out)
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
#[target_feature(enable = "pclmulqdq")]
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation, clippy::cast_sign_loss)]
unsafe fn poly_mul_wide_hw(a: &[u64; $limbs], b: &[u64; $limbs]) -> [u64; $limbs2] {
use std::arch::x86_64::{
_mm_clmulepi64_si128, _mm_cvtsi128_si64, _mm_set_epi64x, _mm_srli_si128,
};
let mut out = [0u64; $limbs2];
for i in 0..$limbs {
for j in 0..$limbs {
let ma = _mm_set_epi64x(0, a[i] as i64);
let mb = _mm_set_epi64x(0, b[j] as i64);
let prod = _mm_clmulepi64_si128(ma, mb, 0x00);
let lo = _mm_cvtsi128_si64(prod) as u64;
let hi = _mm_cvtsi128_si64(_mm_srli_si128::<8>(prod)) as u64;
out[i + j] ^= lo;
out[i + j + 1] ^= hi;
}
}
out
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
#[target_feature(enable = "aes")]
unsafe fn poly_mul_wide_hw(a: &[u64; $limbs], b: &[u64; $limbs]) -> [u64; $limbs2] {
use std::arch::aarch64::vmull_p64;
let mut out = [0u64; $limbs2];
for i in 0..$limbs {
for j in 0..$limbs {
let prod: u128 = vmull_p64(a[i], b[j]);
out[i + j] ^= prod as u64;
out[i + j + 1] ^= (prod >> 64) as u64;
}
}
out
}
#[must_use]
pub fn multiply(self, other: Self) -> Self {
#[cfg(all(
feature = "std",
not(kani),
any(target_arch = "x86_64", target_arch = "aarch64")
))]
if clmul_native::feature_available() {
let wide = unsafe { Self::poly_mul_wide_hw(&self.0, &other.0) };
return Self::reduce(wide);
}
Self::reduce(Self::poly_mul_wide(&self.0, &other.0))
}
#[must_use]
pub fn double(self) -> Self {
let top_bit = (self.0[$limbs - 1] >> 63) & 1;
let mut out = [0u64; $limbs];
let mut carry = 0u64;
for i in 0..$limbs {
let next_carry = self.0[i] >> 63;
out[i] = (self.0[i] << 1) | carry;
carry = next_carry;
}
if top_bit == 1 {
out[0] ^= 1;
let terms: [u32; 3] = [$f1, $f2, $f3];
for term in terms {
let l = (term / 64) as usize;
let b = term % 64;
out[l] ^= 1u64 << b;
}
}
Self(out)
}
fn poly_mul_wide(a: &[u64; $limbs], b: &[u64; $limbs]) -> [u64; $limbs2] {
let mut a_wide = [0u64; $limbs2];
a_wide[..$limbs].copy_from_slice(a);
let mut t: [[u64; $limbs2]; 16] = [[0u64; $limbs2]; 16];
t[1] = a_wide;
for i in 1..8usize {
let mut doubled = t[i];
Self::shl1(&mut doubled);
t[2 * i] = doubled;
let mut with_a = doubled;
for w in 0..$limbs2 {
with_a[w] ^= t[1][w];
}
t[2 * i + 1] = with_a;
}
let mut acc = [0u64; $limbs2];
let nibbles = $m / 4;
for k in (0..nibbles).rev() {
Self::shl4(&mut acc);
let word = k / 16;
let shift = (k % 16) * 4;
let nibble = ((b[word] >> shift) & 0xF) as usize;
for w in 0..$limbs2 {
acc[w] ^= t[nibble][w];
}
}
acc
}
fn shl1(x: &mut [u64; $limbs2]) {
let mut carry = 0u64;
for limb in x.iter_mut() {
let next_carry = *limb >> 63;
*limb = (*limb << 1) | carry;
carry = next_carry;
}
}
fn shl4(x: &mut [u64; $limbs2]) {
let mut carry = 0u64;
for limb in x.iter_mut() {
let next_carry = *limb >> 60;
*limb = (*limb << 4) | carry;
carry = next_carry;
}
}
#[doc = concat!("`x^", stringify!($m), " + x^", stringify!($f1), " + x^",
stringify!($f2), " + x^", stringify!($f3), " + 1`,")]
#[doc = concat!("`x^(64*base+b+m) = x^(64*base+b) * x^", stringify!($m),
" = x^(64*base+b) * (x^", stringify!($f1), " + x^", stringify!($f2), " + x^",
stringify!($f3), " + 1)`,")]
fn reduce(mut c: [u64; $limbs2]) -> Self {
let terms: [u32; 3] = [$f1, $f2, $f3];
for i in ($limbs..$limbs2).rev() {
let t = c[i];
let base = i - $limbs;
c[base] ^= t;
for shift in terms {
c[base] ^= t << shift;
c[base + 1] ^= t >> (64 - shift);
}
}
let mut out = [0u64; $limbs];
out.copy_from_slice(&c[..$limbs]);
Self(out)
}
#[cfg(any(test, kani))]
fn reduce_bit_serial_reference(mut c: [u64; $limbs2]) -> Self {
let top_degree: u32 = ($limbs2 * 64) - 1;
let mut degree = top_degree;
while degree >= $m {
let limb = (degree / 64) as usize;
let bit = degree % 64;
if (c[limb] >> bit) & 1 == 1 {
c[limb] ^= 1u64 << bit;
let shift = degree - $m;
for term in [$f1, $f2, $f3, 0u32] {
let d = shift + term;
let l = (d / 64) as usize;
let b = d % 64;
c[l] ^= 1u64 << b;
}
}
degree -= 1;
}
let mut out = [0u64; $limbs];
out.copy_from_slice(&c[..$limbs]);
Self(out)
}
}
};
}
gf2m_field!(Gf2m128, 2, 4, 128, 7, 2, 1);
gf2m_field!(Gf2m256, 4, 8, 256, 10, 5, 2);
gf2m_field!(Gf2m512, 8, 16, 512, 8, 5, 2);
#[cfg(test)]
mod field_axiom_tests {
use super::{Gf2m128, Gf2m256, Gf2m512};
use proptest::prelude::*;
macro_rules! field_axioms {
($mod_name:ident, $elem:ident, $limbs:literal, $limbs2:literal) => {
mod $mod_name {
use super::*;
const ONE: $elem = {
let mut limbs = [0u64; $limbs];
limbs[0] = 1;
$elem(limbs)
};
const TWO: $elem = {
let mut limbs = [0u64; $limbs];
limbs[0] = 2;
$elem(limbs)
};
const ALL_ONES: $elem = $elem([u64::MAX; $limbs]);
fn arb_element() -> impl Strategy<Value = $elem> {
proptest::collection::vec(any::<u64>(), $limbs).prop_map(|v| {
let mut limbs = [0u64; $limbs];
limbs.copy_from_slice(&v);
$elem(limbs)
})
}
fn arb_wide() -> impl Strategy<Value = [u64; $limbs2]> {
proptest::collection::vec(any::<u64>(), $limbs2).prop_map(|v| {
let mut wide = [0u64; $limbs2];
wide.copy_from_slice(&v);
wide
})
}
fn multiply_sw(a: $elem, b: $elem) -> $elem {
$elem::reduce($elem::poly_mul_wide(&a.0, &b.0))
}
#[test]
fn adding_an_element_to_itself_is_zero() {
assert_eq!(ALL_ONES.add(ALL_ONES), $elem::ZERO);
}
#[test]
fn all_ones_times_one_is_all_ones() {
assert_eq!(ALL_ONES.multiply(ONE), ALL_ONES);
}
#[test]
fn all_ones_squared_does_not_panic() {
let _ = ALL_ONES.multiply(ALL_ONES);
}
#[test]
fn double_of_all_ones_matches_general_multiply_by_two() {
assert_eq!(ALL_ONES.double(), ALL_ONES.multiply(TWO));
}
#[test]
fn reduce_of_all_zero_wide_matches_bit_serial_reference() {
assert_eq!(
$elem::reduce([0u64; $limbs2]),
$elem::reduce_bit_serial_reference([0u64; $limbs2])
);
}
#[test]
fn reduce_of_all_ones_wide_matches_bit_serial_reference() {
assert_eq!(
$elem::reduce([u64::MAX; $limbs2]),
$elem::reduce_bit_serial_reference([u64::MAX; $limbs2])
);
}
proptest! {
#[test]
fn double_matches_general_multiply_by_two(a in arb_element()) {
prop_assert_eq!(a.double(), a.multiply(TWO));
}
#[test]
fn multiply_by_one_is_identity(a in arb_element()) {
prop_assert_eq!(a.multiply(ONE), a);
}
#[test]
fn multiply_is_commutative(a in arb_element(), b in arb_element()) {
prop_assert_eq!(a.multiply(b), b.multiply(a));
}
#[test]
fn multiply_is_associative(a in arb_element(), b in arb_element(), c in arb_element()) {
prop_assert_eq!(a.multiply(b).multiply(c), a.multiply(b.multiply(c)));
}
#[test]
fn multiply_distributes_over_add(a in arb_element(), b in arb_element(), c in arb_element()) {
prop_assert_eq!(a.multiply(b.add(c)), a.multiply(b).add(a.multiply(c)));
}
#[test]
fn reduce_matches_bit_serial_reference(wide in arb_wide()) {
prop_assert_eq!(
$elem::reduce(wide),
$elem::reduce_bit_serial_reference(wide)
);
}
#[test]
fn multiply_matches_explicit_software_path(a in arb_element(), b in arb_element()) {
prop_assert_eq!(a.multiply(b), multiply_sw(a, b));
}
#[test]
fn multiply_sw_by_one_is_identity(a in arb_element()) {
prop_assert_eq!(multiply_sw(a, ONE), a);
}
#[test]
fn multiply_sw_is_commutative(a in arb_element(), b in arb_element()) {
prop_assert_eq!(multiply_sw(a, b), multiply_sw(b, a));
}
#[test]
fn multiply_sw_is_associative(a in arb_element(), b in arb_element(), c in arb_element()) {
prop_assert_eq!(multiply_sw(multiply_sw(a, b), c), multiply_sw(a, multiply_sw(b, c)));
}
#[test]
fn multiply_sw_distributes_over_add(a in arb_element(), b in arb_element(), c in arb_element()) {
prop_assert_eq!(multiply_sw(a, b.add(c)), multiply_sw(a, b).add(multiply_sw(a, c)));
}
}
}
};
}
field_axioms!(gf2m128, Gf2m128, 2, 4);
field_axioms!(gf2m256, Gf2m256, 4, 8);
field_axioms!(gf2m512, Gf2m512, 8, 16);
#[test]
#[ignore]
fn isolated_timing_gf2m256_multiply_vs_kalyna256_256_encrypt_block() {
use std::hint::black_box;
use std::time::Instant;
let key = [0x11u8; 32];
let cipher = super::super::kalyna::Kalyna256_256ExpandedKey::new(&key);
let block = [0x22u8; 32];
let a = Gf2m256([
0x1111_1111_1111_1111u64,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
0x4444_4444_4444_4444,
]);
let b = Gf2m256([
0x5555_5555_5555_5555u64,
0x6666_6666_6666_6666,
0x7777_7777_7777_7777,
0x8888_8888_8888_8888,
]);
const N: u32 = 2_000_000;
let start = Instant::now();
let mut acc_block = block;
for _ in 0..N {
acc_block = black_box(cipher.encrypt_block(black_box(&acc_block)));
}
let block_elapsed = start.elapsed();
black_box(acc_block);
let start = Instant::now();
let mut acc = a;
for _ in 0..N {
acc = black_box(acc.multiply(black_box(b)));
}
let mult_elapsed = start.elapsed();
black_box(acc);
let block_ns = block_elapsed.as_nanos() as f64 / f64::from(N);
let mult_ns = mult_elapsed.as_nanos() as f64 / f64::from(N);
eprintln!(
"encrypt_block: {block_ns:.1} ns/op | Gf2m256::multiply: {mult_ns:.1} ns/op | ratio (multiply/block) = {:.2}x",
mult_ns / block_ns
);
}
#[test]
#[ignore]
fn isolated_timing_gf2m256_poly_mul_wide_vs_reduce_split() {
use std::hint::black_box;
use std::time::Instant;
let b: [u64; 4] = [
0x5555_5555_5555_5555u64,
0x6666_6666_6666_6666,
0x7777_7777_7777_7777,
0x8888_8888_8888_8888,
];
const N: u32 = 2_000_000;
let start = Instant::now();
let mut a: [u64; 4] = [
0x1111_1111_1111_1111u64,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
0x4444_4444_4444_4444,
];
for _ in 0..N {
let wide = black_box(Gf2m256::poly_mul_wide(black_box(&a), black_box(&b)));
a.copy_from_slice(&wide[..4]);
}
let mul_elapsed = start.elapsed();
black_box(a);
let start = Instant::now();
let mut wide: [u64; 8] = [
0x1111_1111_1111_1111u64,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
0x4444_4444_4444_4444,
0x5555_5555_5555_5555,
0x6666_6666_6666_6666,
0x7777_7777_7777_7777,
0x8888_8888_8888_8888,
];
for _ in 0..N {
let reduced = black_box(Gf2m256::reduce(black_box(wide)));
wide[..4].copy_from_slice(&reduced.0);
}
let reduce_elapsed = start.elapsed();
black_box(wide);
let mul_ns = mul_elapsed.as_nanos() as f64 / f64::from(N);
let reduce_ns = reduce_elapsed.as_nanos() as f64 / f64::from(N);
let total_ns = mul_ns + reduce_ns;
eprintln!(
"poly_mul_wide (chained): {mul_ns:.1} ns/op | reduce (chained): {reduce_ns:.1} ns/op | reduce share = {:.1}% | sum = {total_ns:.1} ns/op",
100.0 * reduce_ns / total_ns
);
}
#[test]
#[ignore]
fn isolated_timing_gf2m128_multiply_vs_kalyna128_128_encrypt_block() {
use std::hint::black_box;
use std::time::Instant;
let key = [0x11u8; 16];
let cipher = super::super::kalyna::Kalyna128_128ExpandedKey::new(&key);
let block = [0x22u8; 16];
let a = Gf2m128([0x1111_1111_1111_1111u64, 0x2222_2222_2222_2222]);
let b = Gf2m128([0x5555_5555_5555_5555u64, 0x6666_6666_6666_6666]);
const N: u32 = 2_000_000;
let start = Instant::now();
let mut acc_block = block;
for _ in 0..N {
acc_block = black_box(cipher.encrypt_block(black_box(&acc_block)));
}
let block_elapsed = start.elapsed();
black_box(acc_block);
let start = Instant::now();
let mut acc = a;
for _ in 0..N {
acc = black_box(acc.multiply(black_box(b)));
}
let mult_elapsed = start.elapsed();
black_box(acc);
let block_ns = block_elapsed.as_nanos() as f64 / f64::from(N);
let mult_ns = mult_elapsed.as_nanos() as f64 / f64::from(N);
eprintln!(
"encrypt_block: {block_ns:.1} ns/op | Gf2m128::multiply: {mult_ns:.1} ns/op | ratio (multiply/block) = {:.2}x",
mult_ns / block_ns
);
}
#[test]
#[ignore]
fn isolated_timing_gf2m512_multiply_vs_kalyna512_512_encrypt_block() {
use std::hint::black_box;
use std::time::Instant;
let key = [0x11u8; 64];
let cipher = super::super::kalyna::Kalyna512_512ExpandedKey::new(&key);
let block = [0x22u8; 64];
let a = Gf2m512([
0x1111_1111_1111_1111u64,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
0x4444_4444_4444_4444,
0x1111_1111_1111_1111u64,
0x2222_2222_2222_2222,
0x3333_3333_3333_3333,
0x4444_4444_4444_4444,
]);
let b = Gf2m512([
0x5555_5555_5555_5555u64,
0x6666_6666_6666_6666,
0x7777_7777_7777_7777,
0x8888_8888_8888_8888,
0x5555_5555_5555_5555u64,
0x6666_6666_6666_6666,
0x7777_7777_7777_7777,
0x8888_8888_8888_8888,
]);
const N: u32 = 2_000_000;
let start = Instant::now();
let mut acc_block = block;
for _ in 0..N {
acc_block = black_box(cipher.encrypt_block(black_box(&acc_block)));
}
let block_elapsed = start.elapsed();
black_box(acc_block);
let start = Instant::now();
let mut acc = a;
for _ in 0..N {
acc = black_box(acc.multiply(black_box(b)));
}
let mult_elapsed = start.elapsed();
black_box(acc);
let block_ns = block_elapsed.as_nanos() as f64 / f64::from(N);
let mult_ns = mult_elapsed.as_nanos() as f64 / f64::from(N);
eprintln!(
"encrypt_block: {block_ns:.1} ns/op | Gf2m512::multiply: {mult_ns:.1} ns/op | ratio (multiply/block) = {:.2}x",
mult_ns / block_ns
);
}
}
#[cfg(kani)]
mod kani_proofs {
use super::{Gf2m128, Gf2m256, Gf2m512};
macro_rules! reduce_kani_proof {
($mod_name:ident, $elem:ident, $limbs2:literal) => {
mod $mod_name {
use super::*;
#[kani::proof]
fn reduce_matches_bit_serial_reference() {
let c: [u64; $limbs2] = kani::any();
assert_eq!($elem::reduce(c), $elem::reduce_bit_serial_reference(c));
}
}
};
}
reduce_kani_proof!(gf2m128, Gf2m128, 4);
reduce_kani_proof!(gf2m256, Gf2m256, 8);
reduce_kani_proof!(gf2m512, Gf2m512, 16);
}
#[cfg(all(feature = "std", target_arch = "x86_64"))]
pub(crate) mod clmul_native {
#[cfg(any(test, kani))]
use std::arch::x86_64::{__m128i, _mm_clmulepi64_si128, _mm_set_epi64x, _mm_storeu_si128};
#[cfg(any(test, kani))]
#[target_feature(enable = "pclmulqdq")]
unsafe fn clmul64_impl(a: u64, b: u64) -> (u64, u64) {
let ma = _mm_set_epi64x(0, a as i64);
let mb = _mm_set_epi64x(0, b as i64);
let prod = _mm_clmulepi64_si128(ma, mb, 0x00);
let mut bytes = [0u8; 16];
_mm_storeu_si128(bytes.as_mut_ptr().cast::<__m128i>(), prod);
let lo = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let hi = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
(lo, hi)
}
pub(crate) fn feature_available() -> bool {
is_x86_feature_detected!("pclmulqdq")
}
#[cfg(any(test, kani))]
pub(crate) unsafe fn clmul64(a: u64, b: u64) -> (u64, u64) {
clmul64_impl(a, b)
}
}
#[cfg(all(feature = "std", target_arch = "aarch64"))]
pub(crate) mod clmul_native {
#[cfg(any(test, kani))]
use std::arch::aarch64::vmull_p64;
#[cfg(any(test, kani))]
#[target_feature(enable = "aes")]
unsafe fn clmul64_impl(a: u64, b: u64) -> (u64, u64) {
let prod: u128 = vmull_p64(a, b);
(prod as u64, (prod >> 64) as u64)
}
pub(crate) fn feature_available() -> bool {
std::arch::is_aarch64_feature_detected!("aes")
}
#[cfg(any(test, kani))]
pub(crate) unsafe fn clmul64(a: u64, b: u64) -> (u64, u64) {
clmul64_impl(a, b)
}
}
#[cfg(all(test, any(target_arch = "x86_64", target_arch = "aarch64")))]
mod clmul_spike {
use super::clmul_native::{clmul64, feature_available};
use super::{Gf2m128, Gf2m256, Gf2m512};
use proptest::prelude::*;
const KALYNA_XTS_256_256_CEILING_MB_S: f64 = 163.82;
fn schoolbook_clmul_poly_mul_wide(a: &[u64], b: &[u64]) -> Vec<u64> {
let limbs = a.len();
let mut out = vec![0u64; limbs * 2];
for i in 0..limbs {
for j in 0..limbs {
let (lo, hi) = unsafe { clmul64(a[i], b[j]) };
out[i + j] ^= lo;
out[i + j + 1] ^= hi;
}
}
out
}
macro_rules! clmul_spike_for {
($mod_name:ident, $elem:ident, $limbs:literal, $limbs2:literal) => {
mod $mod_name {
use super::*;
fn arb_narrow() -> impl Strategy<Value = [u64; $limbs]> {
proptest::collection::vec(any::<u64>(), $limbs).prop_map(|v| {
let mut out = [0u64; $limbs];
out.copy_from_slice(&v);
out
})
}
proptest! {
#[test]
fn clmul_poly_mul_wide_matches_software_reference(a in arb_narrow(), b in arb_narrow()) {
if !feature_available() {
return Ok(());
}
let hw = schoolbook_clmul_poly_mul_wide(&a, &b);
let sw = $elem::poly_mul_wide(&a, &b);
prop_assert_eq!(hw.as_slice(), sw.as_slice());
}
}
#[test]
#[ignore]
fn isolated_timing_clmul_vs_software_multiply() {
if !feature_available() {
eprintln!(
"{}: hardware clmul feature not available on this CPU, skipping",
stringify!($elem)
);
return;
}
use std::hint::black_box;
use std::time::Instant;
const N: u32 = 2_000_000;
let a = [0x1111_1111_1111_1111u64; $limbs];
let b = [0x5555_5555_5555_5555u64; $limbs];
let start = Instant::now();
let mut acc = a;
for _ in 0..N {
let wide = $elem::poly_mul_wide(black_box(&acc), black_box(&b));
acc.copy_from_slice(&$elem::reduce(wide).0);
}
let sw_elapsed = start.elapsed();
black_box(acc);
let start = Instant::now();
let mut chained = a;
for _ in 0..N {
let wide = schoolbook_clmul_poly_mul_wide(black_box(&chained), black_box(&b));
let mut wide_arr = [0u64; $limbs2];
wide_arr.copy_from_slice(&wide);
let reduced = $elem::reduce(wide_arr);
chained.copy_from_slice(&reduced.0);
}
let hw_elapsed = start.elapsed();
black_box(chained);
let sw_ns = sw_elapsed.as_nanos() as f64 / f64::from(N);
let hw_ns = hw_elapsed.as_nanos() as f64 / f64::from(N);
eprintln!(
"{}: explicit software multiply = {sw_ns:.1} ns/op | hardware-clmul multiply = {hw_ns:.1} ns/op | speedup = {:.2}x | (Kalyna-XTS 256-256 ceiling = {} MB/s - no GCM projection may exceed this)",
stringify!($elem),
sw_ns / hw_ns,
KALYNA_XTS_256_256_CEILING_MB_S
);
}
}
};
}
clmul_spike_for!(gf2m128_spike, Gf2m128, 2, 4);
clmul_spike_for!(gf2m256_spike, Gf2m256, 4, 8);
clmul_spike_for!(gf2m512_spike, Gf2m512, 8, 16);
}