1mod engine;
14mod frobenius;
15mod packed;
16
17use core::fmt::{self, Debug, Display, Formatter};
18use core::iter::{Product, Sum};
19use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
20
21use num_bigint::BigUint;
22use p3_field::op_assign_macros::{
23 impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
24 impl_sub_assign, impl_sub_base_field, ring_sum,
25};
26use p3_field::{Algebra, Field, Packable, PrimeCharacteristicRing, RawDataSerializable};
27use rand::Rng;
28use rand::distr::{Distribution, StandardUniform};
29use serde::{Deserialize, Serialize};
30
31pub use crate::aes::engine::ByteMatrix;
32pub use crate::aes::frobenius::LinearizedPoly8b;
33pub use crate::aes::packed::PackedRijndael8b;
34use crate::cantor::CANTOR_BASIS_128;
35use crate::gf2::characteristic_two_methods;
36use crate::tower::TowerLevel;
37use crate::{BinaryField8, Gf2};
38
39const fn fold_tail(v: u16) -> u16 {
43 (v << 4) ^ (v << 3) ^ (v << 1) ^ v
44}
45
46const fn carryless(a: u8, b: u8) -> u16 {
48 let a = a as u16;
49 let mut acc = 0u16;
50 let mut i = 0;
51 while i < 8 {
52 let selected = 0u16.wrapping_sub(((b >> i) & 1) as u16);
54 acc ^= (a << i) & selected;
55 i += 1;
56 }
57 acc
58}
59
60#[inline]
68pub(crate) const fn mul_bytes(a: u8, b: u8) -> u8 {
69 let product = carryless(a, b);
70
71 let folded = fold_tail(product >> 8);
73
74 (product ^ folded ^ fold_tail(folded >> 8)) as u8
76}
77
78#[inline]
94pub(crate) const fn invert_byte(a: u8) -> u8 {
95 let x2 = mul_bytes(a, a);
96 let x3 = mul_bytes(x2, a);
97 let x6 = mul_bytes(x3, x3);
98 let x12 = mul_bytes(x6, x6);
99 let x15 = mul_bytes(x12, x3);
100 let x30 = mul_bytes(x15, x15);
101 let x60 = mul_bytes(x30, x30);
102 let x120 = mul_bytes(x60, x60);
103 let x240 = mul_bytes(x120, x120);
104 let x252 = mul_bytes(x240, x12);
105 mul_bytes(x252, x2)
106}
107
108const XI: [u8; 3] = {
116 let mut xi = [0u8; 3];
117 let mut k = 0;
118 while k < 3 {
119 let previous = if k == 0 { 1 } else { xi[k - 1] };
120 let mut candidate = 1u16;
121 while candidate < 256 {
122 let c = candidate as u8;
123 if mul_bytes(c, c) ^ mul_bytes(previous, c) ^ 1 == 0 {
124 xi[k] = c;
125 break;
126 }
127 candidate += 1;
128 }
129 assert!(xi[k] != 0, "the tower relations have no solution here");
130 k += 1;
131 }
132 xi
133};
134
135const TOWER_GENERATOR: u8 = 0x13;
139
140const fn tower_images() -> [u8; 8] {
146 let mut images = [0u8; 8];
147 images[0] = 1;
148 let mut j = 1usize;
149 while j < 8 {
150 let lowest = j.trailing_zeros() as usize;
151 images[j] = mul_bytes(XI[lowest], images[j & (j - 1)]);
152 j += 1;
153 }
154 images
155}
156
157const FROM_TOWER: ByteMatrix = ByteMatrix::from_images(tower_images());
159
160const INTO_TOWER: ByteMatrix = {
166 let mut images = [0u8; 8];
167 let mut b = 0;
168 while b < 8 {
169 let target = 1u8 << b;
170 let mut x = 1u16;
171 while x < 256 {
172 if FROM_TOWER.apply(x as u8) == target {
173 images[b] = x as u8;
174 break;
175 }
176 x += 1;
177 }
178 b += 1;
179 }
180 ByteMatrix::from_images(images)
181};
182
183const _: () = assert!(
187 INTO_TOWER.compose(FROM_TOWER).to_quadword() == ByteMatrix::IDENTITY.to_quadword(),
188 "the change of basis is not invertible"
189);
190
191const CANTOR_BASIS: [u8; 8] = {
195 let mut basis = [0u8; 8];
196 let mut i = 0;
197 while i < 8 {
198 basis[i] = FROM_TOWER.apply(CANTOR_BASIS_128[i] as u8);
199 i += 1;
200 }
201 basis
202};
203
204#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
214#[serde(transparent)]
215#[repr(transparent)]
216#[must_use]
217pub struct Rijndael8b(u8);
218
219impl Rijndael8b {
220 pub(crate) const BITS: usize = 8;
222
223 #[inline]
227 pub const fn from_byte(byte: u8) -> Self {
228 Self(byte)
229 }
230
231 #[must_use]
233 #[inline]
234 pub const fn to_byte(self) -> u8 {
235 self.0
236 }
237
238 #[inline]
242 pub const fn invert_or_zero(self) -> Self {
243 Self(invert_byte(self.0))
244 }
245
246 pub const fn scaling_matrix(self) -> ByteMatrix {
250 let mut images = [0u8; 8];
251 let mut b = 0;
252 while b < 8 {
253 images[b] = mul_bytes(self.0, 1 << b);
254 b += 1;
255 }
256 ByteMatrix::from_images(images)
257 }
258}
259
260impl Packable for Rijndael8b {}
261
262impl Display for Rijndael8b {
263 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
264 Display::fmt(&self.0, f)
265 }
266}
267
268impl Debug for Rijndael8b {
269 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
270 Debug::fmt(&self.0, f)
271 }
272}
273
274impl Distribution<Rijndael8b> for StandardUniform {
275 #[inline]
276 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Rijndael8b {
277 let mut byte = [0u8; 1];
278 rng.fill_bytes(&mut byte);
279 Rijndael8b(byte[0])
280 }
281}
282
283impl PrimeCharacteristicRing for Rijndael8b {
284 type PrimeSubfield = Gf2;
285
286 const ZERO: Self = Self(0);
287 const ONE: Self = Self(1);
288 const TWO: Self = Self(0);
290 const NEG_ONE: Self = Self(1);
292
293 #[inline]
294 fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
295 Self::from_bool(f.is_one())
296 }
297
298 #[inline]
299 fn from_bool(b: bool) -> Self {
300 Self(u8::from(b))
301 }
302
303 characteristic_two_methods!();
304
305 #[inline]
306 fn square(&self) -> Self {
307 Self(Self::frobenius_map(1).apply(self.0))
308 }
309
310 #[inline]
312 fn exp_power_of_2(&self, power_log: usize) -> Self {
313 Self(Self::frobenius_map(power_log).apply(self.0))
314 }
315}
316
317impl Field for Rijndael8b {
318 type Packing = PackedRijndael8b<{ packed::PACKING_WIDTH }>;
319
320 const GENERATOR: Self = Self(FROM_TOWER.apply(TOWER_GENERATOR));
321
322 #[inline]
323 fn try_inverse(&self) -> Option<Self> {
324 let inverse = self.invert_or_zero();
328 (self.0 != 0).then_some(inverse)
329 }
330
331 #[inline]
332 fn try_sqrt(&self) -> Option<Self> {
333 Some(self.exp_power_of_2(Self::BITS - 1))
335 }
336
337 #[inline]
338 fn order() -> BigUint {
339 BigUint::from(1u16 << Self::BITS)
340 }
341
342 #[inline]
344 fn bits() -> usize {
345 Self::BITS
346 }
347
348 #[inline]
355 fn interpolation_node(i: usize) -> Self {
356 assert!(i < 1 << Self::BITS, "interpolation node index out of range");
357 Self(i as u8)
358 }
359}
360
361impl RawDataSerializable for Rijndael8b {
362 const NUM_BYTES: usize = 1;
363
364 #[inline]
365 fn into_bytes(self) -> impl IntoIterator<Item = u8> {
366 [self.0]
367 }
368}
369
370impl crate::tower::private::Sealed for Rijndael8b {}
371
372impl TowerLevel for Rijndael8b {
373 type Repr = u8;
374
375 const LOG_BITS: usize = 3;
376
377 #[inline]
378 fn from_repr(r: Self::Repr) -> Self {
379 Self(r)
380 }
381
382 #[inline]
383 fn to_repr(self) -> Self::Repr {
384 self.0
385 }
386
387 #[inline]
391 fn mul_alpha(self) -> Self {
392 Self(mul_bytes(self.0, XI[2]))
393 }
394
395 #[inline]
398 fn from_le_byte_iter(mut bytes: impl Iterator<Item = u8>) -> Self {
399 Self(
400 bytes
401 .next()
402 .expect("byte stream ended before a whole element was read"),
403 )
404 }
405
406 #[inline]
409 fn cantor_basis(i: usize) -> Self {
410 assert!(i < Self::BITS, "Cantor basis index out of range");
411 Self(CANTOR_BASIS[i])
412 }
413}
414
415impl From<BinaryField8> for Rijndael8b {
416 #[inline]
418 fn from(x: BinaryField8) -> Self {
419 Self(FROM_TOWER.apply(x.to_repr()))
420 }
421}
422
423impl From<Rijndael8b> for BinaryField8 {
424 #[inline]
426 fn from(x: Rijndael8b) -> Self {
427 Self::from_repr(INTO_TOWER.apply(x.0))
428 }
429}
430
431impl Add for Rijndael8b {
432 type Output = Self;
433
434 #[inline]
435 #[allow(clippy::suspicious_arithmetic_impl)]
436 fn add(self, rhs: Self) -> Self {
437 Self(self.0 ^ rhs.0)
439 }
440}
441
442impl Sub for Rijndael8b {
443 type Output = Self;
444
445 #[inline]
446 #[allow(clippy::suspicious_arithmetic_impl)]
447 fn sub(self, rhs: Self) -> Self {
448 self + rhs
450 }
451}
452
453impl Neg for Rijndael8b {
454 type Output = Self;
455
456 #[inline]
457 fn neg(self) -> Self {
458 self
460 }
461}
462
463impl Mul for Rijndael8b {
464 type Output = Self;
465
466 #[inline]
467 fn mul(self, rhs: Self) -> Self {
468 Self(mul_bytes(self.0, rhs.0))
469 }
470}
471
472impl_add_assign!(Rijndael8b);
473impl_sub_assign!(Rijndael8b);
474impl_mul_methods!(Rijndael8b);
475impl_div_methods!(Rijndael8b, Rijndael8b);
476ring_sum!(Rijndael8b);
477
478impl From<Gf2> for Rijndael8b {
479 #[inline]
481 fn from(x: Gf2) -> Self {
482 Self::from_prime_subfield(x)
483 }
484}
485
486impl_add_base_field!(Rijndael8b, Gf2);
487impl_sub_base_field!(Rijndael8b, Gf2);
488impl_mul_base_field!(Rijndael8b, Gf2);
489
490impl Algebra<Gf2> for Rijndael8b {}
491
492#[cfg(test)]
493mod tests {
494 extern crate std;
495
496 use p3_field::{Field, PrimeCharacteristicRing};
497 use proptest::prelude::*;
498
499 use super::{CANTOR_BASIS, Rijndael8b, XI, mul_bytes};
500 use crate::tower::TowerLevel;
501 use crate::{BinaryField8, ByteMatrix};
502
503 fn schoolbook(a: u8, b: u8) -> u8 {
509 let mut acc = 0u8;
510 for i in (0..8).rev() {
511 let overflowed = acc & 0x80 != 0;
513 acc <<= 1;
514 if overflowed {
515 acc ^= 0x1b;
516 }
517 if (b >> i) & 1 == 1 {
518 acc ^= a;
519 }
520 }
521 acc
522 }
523
524 #[test]
525 fn the_modulus_reduces_the_way_the_polynomial_says() {
526 assert_eq!(mul_bytes(0x80, 0x02), 0x1b);
528
529 assert_eq!(mul_bytes(0xd4, 0x02), 0xb3);
531 assert_eq!(mul_bytes(0xbf, 0x03), 0xda);
532
533 assert_eq!(mul_bytes(0x02, 0x04), 0x08);
535 }
536
537 #[test]
538 fn the_published_substitution_box_entries_come_out() {
539 let affine = ByteMatrix::from_images([0x1f, 0x3e, 0x7c, 0xf8, 0xf1, 0xe3, 0xc7, 0x8f]);
545
546 assert_eq!(affine.to_quadword(), 0xf1e3_c78f_1f3e_7cf8);
550
551 let sbox = |x: u8| affine.apply(super::invert_byte(x)) ^ 0x63;
552
553 assert_eq!(sbox(0x00), 0x63);
555 assert_eq!(sbox(0x01), 0x7c);
556 assert_eq!(sbox(0x53), 0xed);
557 assert_eq!(sbox(0xff), 0x16);
558 }
559
560 #[test]
561 fn every_nonzero_byte_inverts() {
562 for byte in 1..=u8::MAX {
564 assert_eq!(mul_bytes(byte, super::invert_byte(byte)), 1, "{byte:#x}");
565 }
566 assert_eq!(super::invert_byte(0), 0);
568 assert_eq!(Rijndael8b::ZERO.try_inverse(), None);
569 }
570
571 #[test]
572 fn the_generator_is_the_image_of_the_tower_generator() {
573 assert_eq!(
577 Rijndael8b::GENERATOR,
578 Rijndael8b::from(BinaryField8::GENERATOR)
579 );
580 }
581
582 #[test]
583 fn the_generator_has_the_full_order() {
584 let g = Rijndael8b::GENERATOR;
588 for divisor in [3u64, 5, 17] {
589 assert_ne!(g.exp_u64(255 / divisor), Rijndael8b::ONE, "{divisor}");
590 }
591 assert_eq!(g.exp_u64(255), Rijndael8b::ONE);
592 }
593
594 #[test]
595 fn the_generator_images_satisfy_the_tower_relations() {
596 let mut previous = 1u8;
600 for (k, &xi) in XI.iter().enumerate() {
601 assert_eq!(
602 mul_bytes(xi, xi) ^ mul_bytes(previous, xi) ^ 1,
603 0,
604 "generator {k}"
605 );
606 previous = xi;
607 }
608 }
609
610 #[test]
611 fn the_change_of_basis_is_a_ring_isomorphism() {
612 for a in 0..=u8::MAX {
614 for b in 0..=u8::MAX {
615 let (x, y) = (BinaryField8::from_repr(a), BinaryField8::from_repr(b));
616
617 assert_eq!(
619 Rijndael8b::from(x + y),
620 Rijndael8b::from(x) + Rijndael8b::from(y)
621 );
622
623 assert_eq!(
625 Rijndael8b::from(x.reference_mul(y)),
626 Rijndael8b::from(x) * Rijndael8b::from(y)
627 );
628 }
629 let x = Rijndael8b::from_byte(a);
631 assert_eq!(Rijndael8b::from(BinaryField8::from(x)), x);
632 }
633 }
634
635 #[test]
636 fn the_cantor_basis_satisfies_its_recurrence() {
637 assert_eq!(Rijndael8b::cantor_basis(0), Rijndael8b::ONE);
639 for i in 1..Rijndael8b::BITS {
640 let v = Rijndael8b::cantor_basis(i);
641 assert_eq!(
642 v.square() + v,
643 Rijndael8b::cantor_basis(i - 1),
644 "vector {i}"
645 );
646 }
647
648 for i in 0..Rijndael8b::BITS {
650 assert_eq!(
651 Rijndael8b::cantor_basis(i),
652 Rijndael8b::from(BinaryField8::cantor_basis(i)),
653 "vector {i}"
654 );
655 }
656 assert_eq!(CANTOR_BASIS[0], 1);
657 }
658
659 #[test]
660 fn scaling_by_the_top_generator_agrees_with_the_tower() {
661 for byte in 0..=u8::MAX {
663 let x = BinaryField8::from_repr(byte);
664 assert_eq!(
665 Rijndael8b::from(x).mul_alpha(),
666 Rijndael8b::from(x.mul_alpha()),
667 "{byte:#x}"
668 );
669 }
670 }
671
672 #[test]
673 #[should_panic = "Cantor basis index out of range"]
674 fn the_cantor_basis_rejects_an_index_beyond_the_field() {
675 let _vector = Rijndael8b::cantor_basis(8);
676 }
677
678 #[test]
679 #[should_panic = "byte stream ended before a whole element was read"]
680 fn a_truncated_byte_stream_is_rejected() {
681 let _element = Rijndael8b::from_le_byte_iter(core::iter::empty());
682 }
683
684 proptest! {
685 #![proptest_config(ProptestConfig::with_cases(2000))]
686
687 #[test]
689 fn the_product_matches_the_schoolbook_reduction(a: u8, b: u8) {
690 prop_assert_eq!(mul_bytes(a, b), schoolbook(a, b));
691 }
692
693 #[test]
695 fn the_scaling_matrix_is_the_product(a: u8, b: u8) {
696 prop_assert_eq!(Rijndael8b::from_byte(a).scaling_matrix().apply(b), mul_bytes(a, b));
697 }
698
699 #[test]
701 fn the_square_root_squares_back(a: u8) {
702 let x = Rijndael8b::from_byte(a);
703 let root = x.try_sqrt().expect("every element of a binary field is a square");
704 prop_assert_eq!(root.square(), x);
705 }
706 }
707
708 #[test]
709 fn serde_accepts_every_byte() {
710 for byte in [0u8, 1, 0x1b, u8::MAX] {
712 let x = Rijndael8b::from_byte(byte);
713 let encoded = serde_json::to_string(&x).unwrap();
714 assert_eq!(serde_json::from_str::<Rijndael8b>(&encoded).unwrap(), x);
715 }
716 }
717}