mod poly64;
mod subfield;
use p3_binary_field::poly_basis::HAS_HARDWARE_CLMUL;
use p3_binary_field::{
BinaryField2, BinaryField4, BinaryField8, BinaryField16, BinaryField32, BinaryField64,
BinaryField128, Gf2, Ghash128, Poly64, Rijndael8b, TowerLevel,
};
use p3_field::{PackedValue, PrimeCharacteristicRing};
use p3_util::{log2_ceil_usize, log2_strict_usize};
use subfield::{byte_map_twiddle_bits, coordinate_butterfly};
use crate::lch;
use crate::poly::stages::{INTO_POLY, INTO_TOWER, convert};
pub trait ButterflyField: TowerLevel {
const FUSE_RADIX8: bool = false;
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self);
#[inline]
fn butterfly_radix8<const INVERSE: bool>(rows: &mut [&mut [Self]; 8], t: &[Self; 7]) {
radix8::<Self, INVERSE>(rows, t);
}
#[inline]
fn lch_transform<const INVERSE: bool>(values: &mut [Self], width: usize, shift: Self) {
lch::transform::<Self, INVERSE>(values, width, shift);
}
#[inline]
fn lch_transform_cosets(values: &mut [Self], width: usize, log_message: usize) {
lch::transform_cosets::<Self>(values, width, log_message);
}
}
#[inline]
fn radix8<F: ButterflyField, const INVERSE: bool>(rows: &mut [&mut [F]; 8], t: &[F; 7]) {
assert!(
rows.iter().all(|row| row.len() == rows[0].len()),
"radix-8 row lengths differ"
);
let [r0, r1, r2, r3, r4, r5, r6, r7] = rows;
if INVERSE {
F::butterfly::<true>(r0, r1, t[3]);
F::butterfly::<true>(r2, r3, t[4]);
F::butterfly::<true>(r4, r5, t[5]);
F::butterfly::<true>(r6, r7, t[6]);
F::butterfly::<true>(r0, r2, t[1]);
F::butterfly::<true>(r1, r3, t[1]);
F::butterfly::<true>(r4, r6, t[2]);
F::butterfly::<true>(r5, r7, t[2]);
F::butterfly::<true>(r0, r4, t[0]);
F::butterfly::<true>(r1, r5, t[0]);
F::butterfly::<true>(r2, r6, t[0]);
F::butterfly::<true>(r3, r7, t[0]);
} else {
F::butterfly::<false>(r0, r4, t[0]);
F::butterfly::<false>(r1, r5, t[0]);
F::butterfly::<false>(r2, r6, t[0]);
F::butterfly::<false>(r3, r7, t[0]);
F::butterfly::<false>(r0, r2, t[1]);
F::butterfly::<false>(r1, r3, t[1]);
F::butterfly::<false>(r4, r6, t[2]);
F::butterfly::<false>(r5, r7, t[2]);
F::butterfly::<false>(r0, r1, t[3]);
F::butterfly::<false>(r2, r3, t[4]);
F::butterfly::<false>(r4, r5, t[5]);
F::butterfly::<false>(r6, r7, t[6]);
}
}
#[inline]
pub(crate) fn packed_butterfly<F: TowerLevel, const INVERSE: bool>(
lo: &mut [F],
hi: &mut [F],
t: F,
) {
let (lo, lo_tail) = F::Packing::pack_slice_with_suffix_mut(lo);
let (hi, hi_tail) = F::Packing::pack_slice_with_suffix_mut(hi);
let zero = t.is_zero();
butterfly_values::<_, INVERSE>(lo, hi, t.into(), zero);
butterfly_values::<_, INVERSE>(lo_tail, hi_tail, t, zero);
}
#[inline]
fn butterfly_values<R: PrimeCharacteristicRing + Copy, const INVERSE: bool>(
lo: &mut [R],
hi: &mut [R],
t: R,
zero: bool,
) {
if zero {
for (u, v) in lo.iter_mut().zip(hi) {
*v += *u;
}
} else if INVERSE {
for (u, v) in lo.iter_mut().zip(hi) {
*v += *u;
*u += t * *v;
}
} else {
for (u, v) in lo.iter_mut().zip(hi) {
*u += t * *v;
*v += *u;
}
}
}
#[inline]
fn plain_butterfly<F: TowerLevel, const INVERSE: bool>(lo: &mut [F], hi: &mut [F], t: F) {
assert_eq!(lo.len(), hi.len(), "butterfly lengths differ");
packed_butterfly::<F, INVERSE>(lo, hi, t);
}
macro_rules! impl_butterfly_field {
($kernel:ident: $($field:ty),* $(,)?) => {$(
impl ButterflyField for $field {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
$kernel::<Self, INVERSE>(lo, hi, t);
}
}
)*};
}
impl_butterfly_field!(coordinate_butterfly: BinaryField8, BinaryField16, BinaryField32, BinaryField64);
impl_butterfly_field!(plain_butterfly: Gf2, BinaryField2, BinaryField4, Ghash128, Rijndael8b);
impl ButterflyField for Poly64 {
const FUSE_RADIX8: bool = cfg!(all(
target_arch = "aarch64",
target_endian = "little",
target_feature = "aes"
));
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
poly64::butterfly::<INVERSE>(lo, hi, t);
}
#[inline]
fn butterfly_radix8<const INVERSE: bool>(rows: &mut [&mut [Self]; 8], t: &[Self; 7]) {
poly64::radix8::<INVERSE>(rows, t);
}
}
impl ButterflyField for BinaryField128 {
#[inline]
fn butterfly<const INVERSE: bool>(lo: &mut [Self], hi: &mut [Self], t: Self) {
coordinate_butterfly::<Self, INVERSE>(lo, hi, t);
}
fn lch_transform<const INVERSE: bool>(values: &mut [Self], width: usize, shift: Self) {
let log_n = log2_strict_usize(values.len() / width);
if HAS_HARDWARE_CLMUL && wide_stages_repay_conversion(width, log_n, shift) {
ghash_transform::<INVERSE>(values, width, shift);
} else {
lch::transform::<Self, INVERSE>(values, width, shift);
}
}
fn lch_transform_cosets(values: &mut [Self], width: usize, log_message: usize) {
let log_n = log2_ceil_usize(values.len() / width);
if HAS_HARDWARE_CLMUL && wide_stages_repay_conversion(width, log_n, Self::ZERO) {
ghash_transform_cosets(values, width, log_message);
} else {
lch::transform_cosets::<Self>(values, width, log_message);
}
}
}
const SHORT_ROW_BYTES: usize = 64;
#[inline]
fn wide_stages_repay_conversion(width: usize, log_n: usize, shift: BinaryField128) -> bool {
let row_bytes = width * size_of::<BinaryField128>();
let bits = byte_map_twiddle_bits(row_bytes);
let wide_stages = if shift.to_repr() >> bits != 0 {
log_n
} else {
log_n.saturating_sub(bits)
};
let needed = if row_bytes < SHORT_ROW_BYTES { 1 } else { 2 };
wide_stages >= needed
}
pub(crate) fn ghash_transform<const INVERSE: bool>(
values: &mut [BinaryField128],
width: usize,
shift: BinaryField128,
) {
let words = BinaryField128::as_repr_slice_mut(values);
convert(words, INTO_POLY);
lch::transform::<Ghash128, INVERSE>(as_ghash(words), width, Ghash128::from(shift));
convert(words, INTO_TOWER);
}
pub(crate) fn ghash_transform_cosets(
values: &mut [BinaryField128],
width: usize,
log_message: usize,
) {
let words = BinaryField128::as_repr_slice_mut(values);
convert(&mut words[..width << log_message], INTO_POLY);
lch::transform_cosets::<Ghash128>(as_ghash(words), width, log_message);
convert(words, INTO_TOWER);
}
#[inline]
const fn as_ghash(words: &mut [u128]) -> &mut [Ghash128] {
unsafe { core::slice::from_raw_parts_mut(words.as_mut_ptr().cast::<Ghash128>(), words.len()) }
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use p3_binary_field::TowerLevel;
use p3_field::PrimeCharacteristicRing;
use proptest::prelude::*;
use super::{
BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, ButterflyField,
Ghash128, Poly64, byte_map_twiddle_bits, wide_stages_repay_conversion,
};
#[test]
fn radix8_zero_corners_and_tails_match_separate_stages() {
for width in [0, 1, 2, 3, 7, 8, 15, 16, 17] {
for corner in [0, 1, 1 << 63, u64::MAX] {
let t = core::array::from_fn(|i| Poly64::new(if i % 2 == 0 { corner } else { 0 }));
let input: [Vec<Poly64>; 8] = core::array::from_fn(|r| {
(0..width)
.map(|lane| Poly64::new((r * width + lane) as u64 ^ corner))
.collect()
});
for inverse in [false, true] {
let mut got = input.clone();
let mut want = input.clone();
if inverse {
Poly64::butterfly_radix8::<true>(
&mut got.each_mut().map(Vec::as_mut_slice),
&t,
);
super::radix8::<Poly64, true>(
&mut want.each_mut().map(Vec::as_mut_slice),
&t,
);
} else {
Poly64::butterfly_radix8::<false>(
&mut got.each_mut().map(Vec::as_mut_slice),
&t,
);
super::radix8::<Poly64, false>(
&mut want.each_mut().map(Vec::as_mut_slice),
&t,
);
}
assert_eq!(
got, want,
"width={width}, corner={corner}, inverse={inverse}"
);
}
let mut roundtrip = input.clone();
Poly64::butterfly_radix8::<false>(
&mut roundtrip.each_mut().map(Vec::as_mut_slice),
&t,
);
Poly64::butterfly_radix8::<true>(
&mut roundtrip.each_mut().map(Vec::as_mut_slice),
&t,
);
assert_eq!(roundtrip, input);
}
}
}
#[test]
#[should_panic(expected = "radix-8 row lengths differ")]
fn radix8_rejects_mismatched_rows() {
let mut input: [Vec<Poly64>; 8] = core::array::from_fn(|i| alloc::vec![Poly64::ZERO; i]);
Poly64::butterfly_radix8::<false>(
&mut input.each_mut().map(Vec::as_mut_slice),
&[Poly64::ZERO; 7],
);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn radix8_random_lanes_match_separate_stages(
raw in any::<[[u64; 17];8]>(), t in any::<[u64;7]>(), width in 0usize..=17
) {
let t=t.map(Poly64::new);
let input: [Vec<Poly64>;8]=raw.map(|row|row[..width].iter().copied().map(Poly64::new).collect());
let mut got=input.clone();
let mut want=input.clone();
Poly64::butterfly_radix8::<false>(&mut got.each_mut().map(Vec::as_mut_slice), &t);
super::radix8::<Poly64, false>(&mut want.each_mut().map(Vec::as_mut_slice), &t);
prop_assert_eq!(&got,&want);
Poly64::butterfly_radix8::<true>(&mut got.each_mut().map(Vec::as_mut_slice), &t);
prop_assert_eq!(&got,&input);
}
}
const CORNERS: [u128; 15] = [
0,
1,
0x80,
0xff,
0x100,
0x8000,
0xffff,
0x1_0000,
0x8000_0000,
0xffff_ffff,
0x1_0000_0000,
1 << 63,
u64::MAX as u128,
1 << 127,
0x87,
];
const LENGTHS: core::ops::RangeInclusive<usize> = 0..=70;
fn sample<F: TowerLevel>(len: usize, seed: u64) -> Vec<F> {
(0..len)
.map(|i| {
let bits = seed
.wrapping_mul(0x9e37_79b9_7f4a_7c15)
.wrapping_add(i as u64 + 1)
.wrapping_mul(0xbf58_476d_1ce4_e5b9);
F::from_le_byte_iter(bits.to_le_bytes().into_iter().cycle())
})
.collect()
}
fn reference<F: TowerLevel>(lo: &mut [F], hi: &mut [F], t: F, inverse: bool) {
for (u, v) in lo.iter_mut().zip(hi) {
if inverse {
*v += *u;
*u += t * *v;
} else {
*u += t * *v;
*v += *u;
}
}
}
fn agrees<F: ButterflyField>(len: usize, t: F) -> Result<(), TestCaseError> {
let (lo, hi) = (sample::<F>(len, 1), sample::<F>(len, 2));
let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
reference(&mut want_lo, &mut want_hi, t, false);
let (mut got_lo, mut got_hi) = (lo.clone(), hi.clone());
F::butterfly::<false>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &want_lo);
prop_assert_eq!(&got_hi, &want_hi);
F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &lo);
prop_assert_eq!(&got_hi, &hi);
let (mut want_lo, mut want_hi) = (lo.clone(), hi.clone());
reference(&mut want_lo, &mut want_hi, t, true);
let (mut got_lo, mut got_hi) = (lo, hi);
F::butterfly::<true>(&mut got_lo, &mut got_hi, t);
prop_assert_eq!(&got_lo, &want_lo);
prop_assert_eq!(&got_hi, &want_hi);
Ok(())
}
fn sweep<F: ButterflyField>(name: &str) {
for &bits in &CORNERS {
let t =
F::from_le_byte_iter(bits.to_le_bytes().into_iter().chain(core::iter::repeat(0)));
for len in LENGTHS {
agrees::<F>(len, t)
.unwrap_or_else(|e| panic!("{name}, twiddle {bits:#x}, len {len}: {e}"));
}
}
}
#[test]
fn every_tower_level_agrees_with_the_element_loop_at_the_corners() {
sweep::<BinaryField8>("BinaryField8");
sweep::<BinaryField16>("BinaryField16");
sweep::<BinaryField32>("BinaryField32");
sweep::<BinaryField64>("BinaryField64");
sweep::<BinaryField128>("BinaryField128");
sweep::<Ghash128>("Ghash128");
}
#[test]
fn the_polynomial_basis_level_agrees_with_the_element_loop_at_the_corners() {
sweep::<Poly64>("Poly64");
}
#[test]
fn one_byte_twiddles_agree_around_every_register_boundary() {
const LENS: [usize; 14] = [0, 1, 15, 16, 17, 31, 32, 33, 255, 256, 257, 511, 512, 513];
for t in [0x01u8, 0x02, 0x03, 0x80, 0xa5, 0xff] {
for len in LENS {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t)).unwrap();
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16)).unwrap();
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32)).unwrap();
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64)).unwrap();
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128)).unwrap();
}
}
}
#[test]
#[should_panic = "butterfly lengths differ"]
fn a_length_mismatch_is_refused() {
let mut lo = [BinaryField32::ONE; 4];
let mut hi = [BinaryField32::ONE; 3];
BinaryField32::butterfly::<false>(&mut lo, &mut hi, BinaryField32::ONE);
}
#[test]
fn rows_of_64_bytes_and_up_need_two_wide_stages_for_the_ghash_route() {
let full = BinaryField128::from_repr(1 << 127);
for (width, needed) in [(1, 1), (3, 1), (4, 2), (16, 2)] {
let bits = byte_map_twiddle_bits(width * size_of::<BinaryField128>());
for stages in 0..=3 {
let want = stages >= needed;
assert_eq!(
wide_stages_repay_conversion(width, bits + stages, BinaryField128::ZERO),
want,
"width={width}, unshifted, wide stages={stages}"
);
assert_eq!(
wide_stages_repay_conversion(width, stages, full),
want,
"width={width}, shifted, wide stages={stages}"
);
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn random_twiddles_agree_with_the_element_loop(
bits in any::<u128>(),
len in 0usize..70,
) {
let bytes = bits.to_le_bytes();
agrees::<BinaryField32>(len, BinaryField32::from_le_byte_iter(bytes.into_iter()))?;
agrees::<BinaryField64>(len, BinaryField64::from_le_byte_iter(bytes.into_iter()))?;
agrees::<BinaryField128>(len, BinaryField128::from_le_byte_iter(bytes.into_iter()))?;
agrees::<Poly64>(len, Poly64::from_le_byte_iter(bytes.into_iter()))?;
}
#[test]
fn byte_twiddles_agree_with_the_element_loop(t in any::<u8>(), len in 0usize..70) {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn byte_twiddles_agree_on_runs_past_the_register_threshold(
t in any::<u8>(),
len in 0usize..600,
) {
agrees::<BinaryField8>(len, BinaryField8::from_repr(t))?;
agrees::<BinaryField16>(len, BinaryField16::from_repr(t as u16))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn word_twiddles_agree_with_the_element_loop(t in 0x100u16.., len in 0usize..70) {
agrees::<BinaryField16>(len, BinaryField16::from_repr(t))?;
agrees::<BinaryField32>(len, BinaryField32::from_repr(t as u32))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
#[test]
fn double_word_twiddles_agree_with_the_element_loop(
t in 0x1_0000u32..,
len in 0usize..70,
) {
agrees::<BinaryField32>(len, BinaryField32::from_repr(t))?;
agrees::<BinaryField64>(len, BinaryField64::from_repr(t as u64))?;
agrees::<BinaryField128>(len, BinaryField128::from_repr(t as u128))?;
}
}
}