1use super::{invert_byte, mul_bytes};
12
13const BIT_LADDER: u64 = 0x0102_0408_1020_4080;
19
20const SPREAD: u64 = 0x0101_0101_0101_0101;
22
23#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
35#[repr(transparent)]
36#[must_use]
37pub struct ByteMatrix(u64);
38
39impl ByteMatrix {
40 pub const ZERO: Self = Self(0);
42
43 pub const IDENTITY: Self = Self(BIT_LADDER);
45
46 pub const fn from_images(images: [u8; 8]) -> Self {
50 let mut quadword = 0u64;
51 let mut i = 0;
52 while i < 8 {
53 let mut row = 0u64;
55 let mut b = 0;
56 while b < 8 {
57 if (images[b] >> i) & 1 == 1 {
58 row |= 1 << b;
59 }
60 b += 1;
61 }
62 quadword |= row << (8 * (7 - i));
63 i += 1;
64 }
65 Self(quadword)
66 }
67
68 #[must_use]
70 pub const fn images(self) -> [u8; 8] {
71 let mut images = [0u8; 8];
72 let mut b = 0;
73 while b < 8 {
74 let mut i = 0;
75 while i < 8 {
76 if (self.0 >> (8 * (7 - i) + b)) & 1 == 1 {
77 images[b] |= 1 << i;
78 }
79 i += 1;
80 }
81 b += 1;
82 }
83 images
84 }
85
86 #[must_use]
88 pub const fn apply(self, byte: u8) -> u8 {
89 let hit = self.0 & (byte as u64).wrapping_mul(SPREAD);
91
92 let mut parity = hit;
94 parity ^= parity >> 4;
95 parity ^= parity >> 2;
96 parity ^= parity >> 1;
97 parity &= SPREAD;
98
99 ((parity.wrapping_mul(BIT_LADDER) >> 56) as u8).reverse_bits()
103 }
104
105 pub const fn compose(self, first: Self) -> Self {
107 let images = first.images();
108 let mut composed = [0u8; 8];
109 let mut b = 0;
110 while b < 8 {
111 composed[b] = self.apply(images[b]);
112 b += 1;
113 }
114 Self::from_images(composed)
115 }
116
117 #[must_use]
119 pub const fn to_quadword(self) -> u64 {
120 self.0
121 }
122
123 pub const fn from_quadword(rows: u64) -> Self {
127 Self(rows)
128 }
129
130 #[inline]
134 pub fn apply_slice(self, bytes: &mut [u8]) {
135 map_slice(&Affine(self), bytes);
136 }
137}
138
139trait ByteMap {
143 fn quadword(&self) -> u64;
145
146 fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L;
148
149 fn scalar(&self, x: u8) -> u8;
151}
152
153struct Affine(ByteMatrix);
155
156impl ByteMap for Affine {
157 #[inline(always)]
158 fn quadword(&self) -> u64 {
159 self.0.to_quadword()
160 }
161
162 #[inline(always)]
163 fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L {
164 x.affine(matrix)
165 }
166
167 #[inline(always)]
168 fn scalar(&self, x: u8) -> u8 {
169 self.0.apply(x)
170 }
171}
172
173struct Invert;
175
176impl ByteMap for Invert {
177 #[inline(always)]
179 fn quadword(&self) -> u64 {
180 ByteMatrix::IDENTITY.to_quadword()
181 }
182
183 #[inline(always)]
184 fn wide<L: ByteLanes>(&self, x: L, matrix: L::Matrix) -> L {
185 x.inverse_then_affine(matrix)
186 }
187
188 #[inline(always)]
189 fn scalar(&self, x: u8) -> u8 {
190 invert_byte(x)
191 }
192}
193
194unsafe trait ByteLanes: Copy {
204 const WIDTH: usize;
206
207 type Matrix: Copy;
209
210 fn matrix(quadword: u64) -> Self::Matrix;
212
213 unsafe fn load(from: *const u8) -> Self;
219
220 unsafe fn store(to: *mut u8, value: Self);
226
227 fn mul(self, other: Self) -> Self;
229
230 fn affine(self, matrix: Self::Matrix) -> Self;
232
233 fn inverse_then_affine(self, matrix: Self::Matrix) -> Self;
235}
236
237unsafe impl ByteLanes for u8 {
239 const WIDTH: usize = 1;
240
241 type Matrix = u64;
242
243 #[inline(always)]
244 fn matrix(quadword: u64) -> Self::Matrix {
245 quadword
246 }
247
248 #[inline(always)]
249 unsafe fn load(from: *const u8) -> Self {
250 unsafe { *from }
252 }
253
254 #[inline(always)]
255 unsafe fn store(to: *mut u8, value: Self) {
256 unsafe { *to = value }
258 }
259
260 #[inline(always)]
261 fn mul(self, other: Self) -> Self {
262 mul_bytes(self, other)
263 }
264
265 #[inline(always)]
266 fn affine(self, matrix: Self::Matrix) -> Self {
267 ByteMatrix(matrix).apply(self)
268 }
269
270 #[inline(always)]
271 fn inverse_then_affine(self, matrix: Self::Matrix) -> Self {
272 let inverse = invert_byte(self);
273
274 if matrix == ByteMatrix::IDENTITY.to_quadword() {
278 inverse
279 } else {
280 ByteMatrix(matrix).apply(inverse)
281 }
282 }
283}
284
285#[cfg(all(target_arch = "x86_64", target_feature = "gfni"))]
286mod x86_64;
287
288#[cfg(all(target_arch = "x86_64", target_feature = "gfni"))]
290use x86_64::{Narrow, Wide};
291
292#[cfg(not(all(target_arch = "x86_64", target_feature = "gfni")))]
294type Wide = u8;
295
296#[cfg(not(all(target_arch = "x86_64", target_feature = "gfni")))]
300type Narrow = u8;
301
302#[allow(clippy::chunks_exact_to_as_chunks)]
306#[inline(always)]
307fn map_registers<'a, L: ByteLanes, M: ByteMap>(map: &M, bytes: &'a mut [u8]) -> &'a mut [u8] {
308 const {
309 assert!(
310 size_of::<L>() == L::WIDTH,
311 "a register must be its own width in bytes"
312 );
313 };
314
315 let covered = (bytes.len() / L::WIDTH) * L::WIDTH;
316 let (head, tail) = bytes.split_at_mut(covered);
317
318 let matrix = L::matrix(map.quadword());
320
321 for chunk in head.chunks_exact_mut(L::WIDTH) {
322 unsafe {
325 let mapped = map.wide(L::load(chunk.as_ptr()), matrix);
326 L::store(chunk.as_mut_ptr(), mapped);
327 }
328 }
329 tail
330}
331
332#[inline]
334fn map_slice<M: ByteMap>(map: &M, bytes: &mut [u8]) {
335 let rest = map_registers::<Wide, M>(map, bytes);
336 let rest = map_registers::<Narrow, M>(map, rest);
337 for byte in rest {
338 *byte = map.scalar(*byte);
339 }
340}
341
342#[allow(clippy::chunks_exact_to_as_chunks)]
346#[inline(always)]
347fn mul_registers<'a, 'b, L: ByteLanes>(
348 dst: &'a mut [u8],
349 src: &'b [u8],
350) -> (&'a mut [u8], &'b [u8]) {
351 const {
352 assert!(
353 size_of::<L>() == L::WIDTH,
354 "a register must be its own width in bytes"
355 );
356 };
357
358 let covered = (dst.len() / L::WIDTH) * L::WIDTH;
359 let (head, tail) = dst.split_at_mut(covered);
360 let (factors, rest) = src.split_at(covered);
361 for (chunk, factor) in head
362 .chunks_exact_mut(L::WIDTH)
363 .zip(factors.chunks_exact(L::WIDTH))
364 {
365 unsafe {
368 let product = L::load(chunk.as_ptr()).mul(L::load(factor.as_ptr()));
369 L::store(chunk.as_mut_ptr(), product);
370 }
371 }
372 (tail, rest)
373}
374
375#[inline]
381pub(crate) fn mul_slice(dst: &mut [u8], src: &[u8]) {
382 assert_eq!(dst.len(), src.len(), "elementwise product lengths differ");
383 let (dst, src) = mul_registers::<Wide>(dst, src);
384 let (dst, src) = mul_registers::<Narrow>(dst, src);
385 for (value, factor) in dst.iter_mut().zip(src) {
386 *value = mul_bytes(*value, *factor);
387 }
388}
389
390#[inline]
392pub(crate) fn invert_slice(bytes: &mut [u8]) {
393 map_slice(&Invert, bytes);
394}
395
396#[cfg(test)]
397mod tests {
398 use proptest::prelude::*;
399
400 use super::{ByteMatrix, invert_slice, mul_slice};
401 use crate::aes::{invert_byte, mul_bytes};
402
403 fn apply_by_columns(images: [u8; 8], byte: u8) -> u8 {
407 (0..8).fold(0, |acc, b| {
408 acc ^ if (byte >> b) & 1 == 1 { images[b] } else { 0 }
409 })
410 }
411
412 #[test]
413 fn the_row_layout_is_the_one_the_instruction_documents() {
414 assert_eq!(ByteMatrix::IDENTITY.to_quadword(), 0x0102_0408_1020_4080);
418 assert_eq!(ByteMatrix::IDENTITY.images(), [1, 2, 4, 8, 16, 32, 64, 128]);
419
420 let one_entry = ByteMatrix::from_images([0x80, 0, 0, 0, 0, 0, 0, 0]);
424 assert_eq!(one_entry.to_quadword(), 1);
425 assert_eq!(one_entry.apply(0xff), 0x80);
426 assert_eq!(one_entry.apply(0xfe), 0);
427 }
428
429 #[test]
430 fn the_constant_maps_behave() {
431 for byte in 0..=u8::MAX {
433 assert_eq!(ByteMatrix::ZERO.apply(byte), 0);
434 assert_eq!(ByteMatrix::IDENTITY.apply(byte), byte);
435 }
436 }
437
438 proptest! {
439 #![proptest_config(ProptestConfig::with_cases(512))]
440
441 #[test]
443 fn the_row_packing_agrees_with_the_column_definition(images: [u8; 8], byte: u8) {
444 let matrix = ByteMatrix::from_images(images);
445 prop_assert_eq!(matrix.apply(byte), apply_by_columns(images, byte));
446 }
447
448 #[test]
450 fn the_column_round_trip_is_the_identity(images: [u8; 8]) {
451 prop_assert_eq!(ByteMatrix::from_images(images).images(), images);
452 }
453
454 #[test]
456 fn composition_applies_the_second_map_to_the_first_image(
457 outer: [u8; 8],
458 inner: [u8; 8],
459 byte: u8,
460 ) {
461 let (outer, inner) = (ByteMatrix::from_images(outer), ByteMatrix::from_images(inner));
462 prop_assert_eq!(outer.compose(inner).apply(byte), outer.apply(inner.apply(byte)));
463 }
464
465 #[test]
469 fn the_slice_kernels_agree_with_the_scalar_routines(
470 values in prop::collection::vec(any::<u8>(), 0..200),
471 factors in prop::collection::vec(any::<u8>(), 0..200),
472 images: [u8; 8],
473 ) {
474 let len = values.len().min(factors.len());
475 let (values, factors) = (&values[..len], &factors[..len]);
476
477 let mut products = values.to_vec();
478 mul_slice(&mut products, factors);
479 let expected: alloc::vec::Vec<u8> =
480 values.iter().zip(factors).map(|(&a, &b)| mul_bytes(a, b)).collect();
481 prop_assert_eq!(&products, &expected);
482
483 let mut inverses = values.to_vec();
484 invert_slice(&mut inverses);
485 let expected: alloc::vec::Vec<u8> = values.iter().map(|&a| invert_byte(a)).collect();
486 prop_assert_eq!(&inverses, &expected);
487
488 let matrix = ByteMatrix::from_images(images);
489 let mut mapped = values.to_vec();
490 matrix.apply_slice(&mut mapped);
491 let expected: alloc::vec::Vec<u8> =
492 values.iter().map(|&a| apply_by_columns(images, a)).collect();
493 prop_assert_eq!(&mapped, &expected);
494 }
495 }
496
497 #[test]
498 #[should_panic = "elementwise product lengths differ"]
499 fn the_product_kernel_rejects_mismatched_lengths() {
500 mul_slice(&mut [0, 0], &[0]);
501 }
502}