use super::{invert_byte, mul_bytes};
const BIT_LADDER: u64 = 0x0102_0408_1020_4080;
const SPREAD: u64 = 0x0101_0101_0101_0101;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
#[repr(transparent)]
#[must_use]
pub struct ByteMatrix(u64);
impl ByteMatrix {
pub const ZERO: Self = Self(0);
pub const IDENTITY: Self = Self(BIT_LADDER);
pub const fn from_images(images: [u8; 8]) -> Self {
let mut quadword = 0u64;
let mut i = 0;
while i < 8 {
let mut row = 0u64;
let mut b = 0;
while b < 8 {
if (images[b] >> i) & 1 == 1 {
row |= 1 << b;
}
b += 1;
}
quadword |= row << (8 * (7 - i));
i += 1;
}
Self(quadword)
}
#[must_use]
pub const fn images(self) -> [u8; 8] {
let mut images = [0u8; 8];
let mut b = 0;
while b < 8 {
let mut i = 0;
while i < 8 {
if (self.0 >> (8 * (7 - i) + b)) & 1 == 1 {
images[b] |= 1 << i;
}
i += 1;
}
b += 1;
}
images
}
#[must_use]
pub const fn apply(self, byte: u8) -> u8 {
let hit = self.0 & (byte as u64).wrapping_mul(SPREAD);
let mut parity = hit;
parity ^= parity >> 4;
parity ^= parity >> 2;
parity ^= parity >> 1;
parity &= SPREAD;
((parity.wrapping_mul(BIT_LADDER) >> 56) as u8).reverse_bits()
}
pub const fn compose(self, first: Self) -> Self {
let images = first.images();
let mut composed = [0u8; 8];
let mut b = 0;
while b < 8 {
composed[b] = self.apply(images[b]);
b += 1;
}
Self::from_images(composed)
}
#[must_use]
pub const fn to_quadword(self) -> u64 {
self.0
}
pub const fn from_quadword(rows: u64) -> Self {
Self(rows)
}
#[inline]
pub fn apply_slice(self, bytes: &mut [u8]) {
map_slice(&Affine(self), bytes);
}
}
trait ByteMap {
fn quadword(&self) -> u64;
fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L;
fn scalar(&self, x: u8) -> u8;
}
struct Affine(ByteMatrix);
impl ByteMap for Affine {
#[inline(always)]
fn quadword(&self) -> u64 {
self.0.to_quadword()
}
#[inline(always)]
fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L {
x.affine(matrix)
}
#[inline(always)]
fn scalar(&self, x: u8) -> u8 {
self.0.apply(x)
}
}
struct Invert;
impl ByteMap for Invert {
#[inline(always)]
fn quadword(&self) -> u64 {
ByteMatrix::IDENTITY.to_quadword()
}
#[inline(always)]
fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L {
x.inverse_then_affine(matrix)
}
#[inline(always)]
fn scalar(&self, x: u8) -> u8 {
invert_byte(x)
}
}
unsafe trait ByteLanes: Copy {
const WIDTH: usize;
type Matrix: Copy;
fn matrix(quadword: u64) -> Self::Matrix;
unsafe fn load(from: *const u8) -> Self;
unsafe fn store(to: *mut u8, value: Self);
fn mul(self, other: Self) -> Self;
fn affine(self, matrix: Self::Matrix) -> Self;
fn inverse_then_affine(self, matrix: Self::Matrix) -> Self;
}
unsafe impl ByteLanes for u8 {
const WIDTH: usize = 1;
type Matrix = u64;
#[inline(always)]
fn matrix(quadword: u64) -> Self::Matrix {
quadword
}
#[inline(always)]
unsafe fn load(from: *const u8) -> Self {
unsafe { *from }
}
#[inline(always)]
unsafe fn store(to: *mut u8, value: Self) {
unsafe { *to = value }
}
#[inline(always)]
fn mul(self, other: Self) -> Self {
mul_bytes(self, other)
}
#[inline(always)]
fn affine(self, matrix: Self::Matrix) -> Self {
ByteMatrix(matrix).apply(self)
}
#[inline(always)]
fn inverse_then_affine(self, matrix: Self::Matrix) -> Self {
let inverse = invert_byte(self);
if matrix == ByteMatrix::IDENTITY.to_quadword() {
inverse
} else {
ByteMatrix(matrix).apply(inverse)
}
}
}
#[cfg(all(target_arch = "x86_64", target_feature = "gfni"))]
mod x86_64;
#[cfg(all(target_arch = "x86_64", target_feature = "gfni"))]
use x86_64::{Narrow, Wide};
#[cfg(not(all(target_arch = "x86_64", target_feature = "gfni")))]
type Wide = u8;
#[cfg(not(all(target_arch = "x86_64", target_feature = "gfni")))]
type Narrow = u8;
#[allow(clippy::chunks_exact_to_as_chunks)]
#[inline(always)]
fn map_registers<'a, L: ByteLanes, M: ByteMap>(map: &M, bytes: &'a mut [u8]) -> &'a mut [u8] {
const {
assert!(
size_of::<L>() == L::WIDTH,
"a register must be its own width in bytes"
);
};
let covered = (bytes.len() / L::WIDTH) * L::WIDTH;
let (head, tail) = bytes.split_at_mut(covered);
let matrix = L::matrix(map.quadword());
for chunk in head.chunks_exact_mut(L::WIDTH) {
unsafe {
let mapped = map.wide(L::load(chunk.as_ptr()), matrix);
L::store(chunk.as_mut_ptr(), mapped);
}
}
tail
}
#[inline]
fn map_slice<M: ByteMap>(map: &M, bytes: &mut [u8]) {
let rest = map_registers::<Wide, M>(map, bytes);
let rest = map_registers::<Narrow, M>(map, rest);
for byte in rest {
*byte = map.scalar(*byte);
}
}
#[allow(clippy::chunks_exact_to_as_chunks)]
#[inline(always)]
fn mul_registers<'a, 'b, L: ByteLanes>(
dst: &'a mut [u8],
src: &'b [u8],
) -> (&'a mut [u8], &'b [u8]) {
const {
assert!(
size_of::<L>() == L::WIDTH,
"a register must be its own width in bytes"
);
};
let covered = (dst.len() / L::WIDTH) * L::WIDTH;
let (head, tail) = dst.split_at_mut(covered);
let (factors, rest) = src.split_at(covered);
for (chunk, factor) in head
.chunks_exact_mut(L::WIDTH)
.zip(factors.chunks_exact(L::WIDTH))
{
unsafe {
let product = L::load(chunk.as_ptr()).mul(L::load(factor.as_ptr()));
L::store(chunk.as_mut_ptr(), product);
}
}
(tail, rest)
}
#[inline]
pub(crate) fn mul_slice(dst: &mut [u8], src: &[u8]) {
assert_eq!(dst.len(), src.len(), "elementwise product lengths differ");
let (dst, src) = mul_registers::<Wide>(dst, src);
let (dst, src) = mul_registers::<Narrow>(dst, src);
for (value, factor) in dst.iter_mut().zip(src) {
*value = mul_bytes(*value, *factor);
}
}
#[inline]
pub(crate) fn invert_slice(bytes: &mut [u8]) {
map_slice(&Invert, bytes);
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::{ByteMatrix, invert_slice, mul_slice};
use crate::aes::{invert_byte, mul_bytes};
fn apply_by_columns(images: [u8; 8], byte: u8) -> u8 {
(0..8).fold(0, |acc, b| {
acc ^ if (byte >> b) & 1 == 1 { images[b] } else { 0 }
})
}
#[test]
fn the_row_layout_is_the_one_the_instruction_documents() {
assert_eq!(ByteMatrix::IDENTITY.to_quadword(), 0x0102_0408_1020_4080);
assert_eq!(ByteMatrix::IDENTITY.images(), [1, 2, 4, 8, 16, 32, 64, 128]);
let one_entry = ByteMatrix::from_images([0x80, 0, 0, 0, 0, 0, 0, 0]);
assert_eq!(one_entry.to_quadword(), 1);
assert_eq!(one_entry.apply(0xff), 0x80);
assert_eq!(one_entry.apply(0xfe), 0);
}
#[test]
fn the_constant_maps_behave() {
for byte in 0..=u8::MAX {
assert_eq!(ByteMatrix::ZERO.apply(byte), 0);
assert_eq!(ByteMatrix::IDENTITY.apply(byte), byte);
}
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(512))]
#[test]
fn the_row_packing_agrees_with_the_column_definition(images: [u8; 8], byte: u8) {
let matrix = ByteMatrix::from_images(images);
prop_assert_eq!(matrix.apply(byte), apply_by_columns(images, byte));
}
#[test]
fn the_column_round_trip_is_the_identity(images: [u8; 8]) {
prop_assert_eq!(ByteMatrix::from_images(images).images(), images);
}
#[test]
fn composition_applies_the_second_map_to_the_first_image(
outer: [u8; 8],
inner: [u8; 8],
byte: u8,
) {
let (outer, inner) = (ByteMatrix::from_images(outer), ByteMatrix::from_images(inner));
prop_assert_eq!(outer.compose(inner).apply(byte), outer.apply(inner.apply(byte)));
}
#[test]
fn the_slice_kernels_agree_with_the_scalar_routines(
values in prop::collection::vec(any::<u8>(), 0..200),
factors in prop::collection::vec(any::<u8>(), 0..200),
images: [u8; 8],
) {
let len = values.len().min(factors.len());
let (values, factors) = (&values[..len], &factors[..len]);
let mut products = values.to_vec();
mul_slice(&mut products, factors);
let expected: alloc::vec::Vec<u8> =
values.iter().zip(factors).map(|(&a, &b)| mul_bytes(a, b)).collect();
prop_assert_eq!(&products, &expected);
let mut inverses = values.to_vec();
invert_slice(&mut inverses);
let expected: alloc::vec::Vec<u8> = values.iter().map(|&a| invert_byte(a)).collect();
prop_assert_eq!(&inverses, &expected);
let matrix = ByteMatrix::from_images(images);
let mut mapped = values.to_vec();
matrix.apply_slice(&mut mapped);
let expected: alloc::vec::Vec<u8> =
values.iter().map(|&a| apply_by_columns(images, a)).collect();
prop_assert_eq!(&mapped, &expected);
}
}
#[test]
#[should_panic = "elementwise product lengths differ"]
fn the_product_kernel_rejects_mismatched_lengths() {
mul_slice(&mut [0, 0], &[0]);
}
}