use crate::lanes::{ByteLanes, butterfly_run, group_mask};
pub(crate) type Blocks<const D: usize> = [[u64; D]; D];
const fn mul_alpha(a: u64, bits: usize) -> u64 {
if bits == 1 {
return a;
}
let half = bits / 2;
let mask = u64::MAX >> (64 - half);
let lo = a & mask;
let hi = a >> half;
hi | ((lo ^ mul_alpha(hi, half)) << half)
}
const fn tower_mul(a: u64, b: u64, bits: usize) -> u64 {
if bits == 1 {
return a & b;
}
let half = bits / 2;
let mask = u64::MAX >> (64 - half);
let (a0, a1) = (a & mask, a >> half);
let (b0, b1) = (b & mask, b >> half);
let low = tower_mul(a0, b0, half);
let high = tower_mul(a1, b1, half);
let middle = tower_mul(a0 ^ a1, b0 ^ b1, half) ^ low ^ high;
(low ^ high) | ((middle ^ mul_alpha(high, half)) << half)
}
const fn map_blocks<const D: usize>(scalar: u64) -> Blocks<D> {
let bits = 8 * D;
let mut blocks = [[0u64; D]; D];
let mut j = 0;
while j < D {
let mut b = 0;
while b < 8 {
let column = tower_mul(scalar, 1 << (8 * j + b), bits);
let mut k = 0;
while k < D {
let mut i = 0;
while i < 8 {
if (column >> (8 * k + i)) & 1 == 1 {
blocks[k][j] |= 1 << (8 * (7 - i) + b);
}
i += 1;
}
k += 1;
}
b += 1;
}
j += 1;
}
blocks
}
static BYTE_BLOCKS: [u64; 256] = {
let mut table = [0u64; 256];
let mut c = 0;
while c < 256 {
table[c] = map_blocks::<1>(c as u64)[0][0];
c += 1;
}
table
};
static WORD_BLOCKS: [[Blocks<2>; 256]; 2] = {
let mut table = [[[[0u64; 2]; 2]; 256]; 2];
let mut byte = 0;
while byte < 2 {
let mut c = 0;
while c < 256 {
table[byte][c] = map_blocks::<2>((c as u64) << (8 * byte));
c += 1;
}
byte += 1;
}
table
};
#[inline]
pub(crate) fn byte_blocks(scalar: u8) -> Blocks<1> {
[[BYTE_BLOCKS[scalar as usize]]]
}
#[inline]
pub(crate) fn word_blocks(scalar: u16) -> Blocks<2> {
let low = &WORD_BLOCKS[0][scalar as u8 as usize];
let high = &WORD_BLOCKS[1][(scalar >> 8) as usize];
core::array::from_fn(|k| core::array::from_fn(|j| low[k][j] ^ high[k][j]))
}
#[inline]
pub(crate) fn dword_blocks(scalar: u32) -> Blocks<4> {
let (t0, t1) = (scalar as u16, (scalar >> 16) as u16);
let folded = t0 ^ mul_alpha(t1 as u64, 16) as u16;
let matrix = [[t0, t1], [t1, folded]];
let expanded: [[Blocks<2>; 2]; 2] =
core::array::from_fn(|k| core::array::from_fn(|j| word_blocks(matrix[k][j])));
core::array::from_fn(|row| {
core::array::from_fn(|col| expanded[row / 2][col / 2][row % 2][col % 2])
})
}
#[inline(always)]
fn map_groups<L: ByteLanes, const D: usize>(x: L, blocks: &Blocks<D>) -> L {
let fold = |rotated: L, r: usize| {
let mut acc = rotated.affine(blocks[0][r]);
for k in 1..D {
acc = acc.affine_merge(rotated, blocks[k][(k + r) % D], group_mask(D, k));
}
acc
};
let mut out = fold(x, 0);
for r in 1..D {
out = out.xor(fold(x.rotate_group::<D>(r), r));
}
out
}
#[inline]
pub(crate) fn subfield_butterfly<L: ByteLanes, const D: usize, const INVERSE: bool>(
lo: &mut [u8],
hi: &mut [u8],
blocks: &Blocks<D>,
) -> usize {
butterfly_run::<L, INVERSE>(lo, hi, |x| map_groups::<L, D>(x, blocks))
}
#[cfg(test)]
mod tests {
use alloc::vec;
use alloc::vec::Vec;
use p3_binary_field::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, TowerLevel};
use proptest::prelude::*;
use super::{byte_blocks, dword_blocks, map_blocks, tower_mul, word_blocks};
use crate::test_util::{LANE_BYTES, Model};
const CORNERS: [u32; 8] = [0, 1, 0xff, 0x80, 0x8000, 0xffff, 0x8000_0000, u32::MAX];
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn the_const_tower_product_matches_the_field(a in any::<u32>(), b in any::<u32>()) {
let want = BinaryField8::from_repr(a as u8) * BinaryField8::from_repr(b as u8);
prop_assert_eq!(tower_mul(a as u8 as u64, b as u8 as u64, 8), want.to_repr() as u64);
let want = BinaryField16::from_repr(a as u16) * BinaryField16::from_repr(b as u16);
prop_assert_eq!(
tower_mul(a as u16 as u64, b as u16 as u64, 16),
want.to_repr() as u64
);
let want = BinaryField32::from_repr(a) * BinaryField32::from_repr(b);
prop_assert_eq!(tower_mul(a as u64, b as u64, 32), want.to_repr() as u64);
}
}
fn apply_block(matrix: u64, input: u8) -> u8 {
let rows = matrix.to_le_bytes();
let mut out = 0u8;
for i in 0..8 {
if (rows[7 - i] & input).count_ones() % 2 == 1 {
out |= 1 << i;
}
}
out
}
fn through_blocks<const D: usize>(blocks: &super::Blocks<D>, input: [u8; D]) -> [u8; D] {
core::array::from_fn(|k| {
(0..D).fold(0u8, |acc, j| acc ^ apply_block(blocks[k][j], input[j]))
})
}
#[test]
fn a_one_byte_map_scales_a_byte_of_the_tower() {
for c in 0..=u8::MAX {
let blocks = byte_blocks(c);
for v in 0..=u8::MAX {
let want = BinaryField8::from_repr(c) * BinaryField8::from_repr(v);
assert_eq!(through_blocks(&blocks, [v]), [want.to_repr()], "c {c:#x}");
}
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn a_two_byte_map_scales_a_word_of_the_tower(c in any::<u16>(), v in any::<u16>()) {
let want = BinaryField16::from_repr(c) * BinaryField16::from_repr(v);
let got = through_blocks(&word_blocks(c), v.to_le_bytes());
prop_assert_eq!(u16::from_le_bytes(got), want.to_repr());
}
#[test]
fn a_four_byte_map_scales_a_doubleword_of_the_tower(c in any::<u32>(), v in any::<u32>()) {
let want = BinaryField32::from_repr(c) * BinaryField32::from_repr(v);
let got = through_blocks(&dword_blocks(c), v.to_le_bytes());
prop_assert_eq!(u32::from_le_bytes(got), want.to_repr());
}
}
#[test]
fn the_assembled_tables_agree_with_the_direct_construction() {
for &c in &CORNERS {
assert_eq!(
word_blocks(c as u16),
map_blocks::<2>(c as u16 as u64),
"word {c:#x}"
);
assert_eq!(dword_blocks(c), map_blocks::<4>(c as u64), "dword {c:#x}");
}
}
#[test]
fn the_maps_scale_at_the_corners() {
for &c in &CORNERS {
for &v in &CORNERS {
let want = BinaryField32::from_repr(c) * BinaryField32::from_repr(v);
let got = through_blocks(&dword_blocks(c), v.to_le_bytes());
assert_eq!(
u32::from_le_bytes(got),
want.to_repr(),
"c {c:#x}, v {v:#x}"
);
}
}
}
fn sample(seed: usize) -> Vec<u8> {
(0..LANE_BYTES * 3)
.map(|i| ((i * 31 + seed * 7 + 1) % 251) as u8)
.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;
}
}
}
#[allow(clippy::chunks_exact_to_as_chunks)]
fn as_level<F: TowerLevel>(bytes: &[u8]) -> Vec<F> {
bytes
.chunks_exact(size_of::<F>())
.map(|c| F::from_le_byte_iter(c.iter().copied()))
.collect()
}
fn kernel_agrees<F: TowerLevel, const D: usize, const INVERSE: bool>(
blocks: &super::Blocks<D>,
twiddle: F,
) -> Result<(), TestCaseError> {
let (mut lo, mut hi) = (sample(1), sample(2));
let (mut want_lo, mut want_hi) = (as_level::<F>(&lo), as_level::<F>(&hi));
reference(&mut want_lo, &mut want_hi, twiddle, INVERSE);
let covered = super::subfield_butterfly::<Model, D, INVERSE>(&mut lo, &mut hi, blocks);
prop_assert_eq!(covered, lo.len());
prop_assert_eq!(as_level::<F>(&lo), want_lo);
prop_assert_eq!(as_level::<F>(&hi), want_hi);
Ok(())
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn the_one_byte_kernel_matches_the_field(t in any::<u8>()) {
let blocks = byte_blocks(t);
let twiddle = BinaryField64::from_repr(t as u64);
kernel_agrees::<BinaryField64, 1, false>(&blocks, twiddle)?;
kernel_agrees::<BinaryField64, 1, true>(&blocks, twiddle)?;
}
#[test]
fn the_two_byte_kernel_matches_the_field(t in any::<u16>()) {
let blocks = word_blocks(t);
let twiddle = BinaryField64::from_repr(t as u64);
kernel_agrees::<BinaryField64, 2, false>(&blocks, twiddle)?;
kernel_agrees::<BinaryField64, 2, true>(&blocks, twiddle)?;
}
#[test]
fn the_four_byte_kernel_matches_the_field(t in any::<u32>()) {
let blocks = dword_blocks(t);
let twiddle = BinaryField64::from_repr(t as u64);
kernel_agrees::<BinaryField64, 4, false>(&blocks, twiddle)?;
kernel_agrees::<BinaryField64, 4, true>(&blocks, twiddle)?;
}
}
#[test]
fn the_kernels_match_the_field_at_the_corners() {
for &t in &CORNERS {
kernel_agrees::<BinaryField8, 1, false>(
&byte_blocks(t as u8),
BinaryField8::from_repr(t as u8),
)
.unwrap_or_else(|e| panic!("one-byte twiddle {t:#x}: {e}"));
kernel_agrees::<BinaryField16, 2, true>(
&word_blocks(t as u16),
BinaryField16::from_repr(t as u16),
)
.unwrap_or_else(|e| panic!("two-byte twiddle {t:#x}: {e}"));
kernel_agrees::<BinaryField32, 4, false>(&dword_blocks(t), BinaryField32::from_repr(t))
.unwrap_or_else(|e| panic!("four-byte twiddle {t:#x}: {e}"));
}
}
#[test]
fn a_run_shorter_than_a_register_is_left_to_the_caller() {
let mut lo = vec![1u8; LANE_BYTES - 1];
let mut hi = vec![2u8; LANE_BYTES - 1];
let blocks = byte_blocks(3);
let covered = super::subfield_butterfly::<Model, 1, false>(&mut lo, &mut hi, &blocks);
assert_eq!(covered, 0);
assert!(lo.iter().all(|&b| b == 1) && hi.iter().all(|&b| b == 2));
assert_eq!(
super::subfield_butterfly::<Model, 1, false>(&mut [], &mut [], &blocks),
0
);
}
}