Skip to main content

p3_binary_field/aes/
mod.rs

1//! `GF(2^8)` in the polynomial basis of `x^8 + x^4 + x^3 + x + 1`, the block cipher's field.
2//!
3//! The tower already carries a `GF(2^8)`, and the two are the same field up to isomorphism.
4//!
5//! What this representation buys is hardware.
6//!
7//! The byte-wise product, linear map and inverse are all defined for this modulus alone.
8//!
9//! Sixty-four bytes therefore multiply, map or invert in one instruction.
10//!
11//! The tower's byte field takes a table lookup per element instead.
12
13mod 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
39/// The carryless product of a value with the modulus below its leading term.
40///
41/// That tail is `x^4 + x^3 + x + 1`, so its four set bits are the four shifts.
42const fn fold_tail(v: u16) -> u16 {
43    (v << 4) ^ (v << 3) ^ (v << 1) ^ v
44}
45
46/// The carryless product of two bytes, as a polynomial of degree at most fourteen.
47const 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        // All ones when bit `i` of the second operand is set, all zeros otherwise.
53        let selected = 0u16.wrapping_sub(((b >> i) & 1) as u16);
54        acc ^= (a << i) & selected;
55        i += 1;
56    }
57    acc
58}
59
60/// The product of two elements, as bytes.
61///
62/// The modulus rewrites `x^8` as a tail of degree 4, so the high half folds down by that tail.
63///
64/// That fold reaches degree `6 + 4`, spilling three bits back over the top.
65///
66/// A second fold of those lands at degree `2 + 4`, below the top, so two rounds are exact.
67#[inline]
68pub(crate) const fn mul_bytes(a: u8, b: u8) -> u8 {
69    let product = carryless(a, b);
70
71    // The half above the modulus, rewritten by the tail.
72    let folded = fold_tail(product >> 8);
73
74    // What that fold pushed back over the top, rewritten once more.
75    (product ^ folded ^ fold_tail(folded >> 8)) as u8
76}
77
78/// The inverse of an element, with zero left alone.
79///
80/// The multiplicative group has order 255, so inversion is the map raising to the power 254.
81///
82/// Zero is outside that group, and the same power sends it to itself.
83///
84/// That is also the convention the byte-wise hardware inverse follows.
85///
86/// # Algorithm
87///
88/// An addition chain on the exponent, eleven products of which seven are squarings:
89///
90/// ```text
91///     1, 2, 3, 6, 12, 15, 30, 60, 120, 240, 252, 254
92/// ```
93#[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
108/// The images of the tower generators here, as the smallest solution at each level.
109///
110/// The tower is generated by elements obeying `X_k^2 + X_{k-1} X_k + 1 = 0`, with `X_-1 = 1`.
111///
112/// Any images obeying those same relations extend to a homomorphism of `F_2`-algebras.
113///
114/// Both sides are fields of the same size, so that homomorphism is an isomorphism.
115const 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
135/// The bit pattern of the multiplicative generator of the tower representation.
136///
137/// Its image here is the generator, so the change of basis carries one onto the other.
138const TOWER_GENERATOR: u8 = 0x13;
139
140/// The map out of the tower basis, column by column.
141///
142/// The tower basis vector indexed by a bit pattern is the product of the generators it names.
143///
144/// Stripping the lowest set bit reuses the image already computed for the rest.
145const 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
157/// Where each basis vector of the tower representation lands here.
158const FROM_TOWER: ByteMatrix = ByteMatrix::from_images(tower_images());
159
160/// Where each basis vector here lands in the tower representation.
161///
162/// An injective map on a byte is a permutation of the 256 values.
163///
164/// Searching for the preimage of each basis vector therefore recovers the inverse.
165const 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
183// Composing the two directions must leave every byte alone.
184//
185// That rules out both a wrong preimage search and a change of basis that is not invertible.
186const _: () = assert!(
187    INTO_TOWER.compose(FROM_TOWER).to_quadword() == ByteMatrix::IDENTITY.to_quadword(),
188    "the change of basis is not invertible"
189);
190
191/// The Cantor basis in this representation, carried over from the tower's own.
192///
193/// Both representations then span the same additive transform domain.
194const 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/// The binary field `GF(2^8)` modulo `x^8 + x^4 + x^3 + x + 1`.
205///
206/// Bit `i` stores the coefficient of `x^i`, and every byte is a distinct field element.
207///
208/// This is the modulus the byte-wise field instructions are defined for.
209///
210/// That is what makes it worth carrying beside the tower's own byte field.
211///
212/// Arithmetic here is table-free: nothing indexes a table by an operand.
213#[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    /// The number of bits of an element.
221    pub(crate) const BITS: usize = 8;
222
223    /// Construct a field element from its byte representation.
224    ///
225    /// Every byte is a valid element.
226    #[inline]
227    pub const fn from_byte(byte: u8) -> Self {
228        Self(byte)
229    }
230
231    /// The byte representation of this element.
232    #[must_use]
233    #[inline]
234    pub const fn to_byte(self) -> u8 {
235        self.0
236    }
237
238    /// The inverse of this element, with zero sent to zero.
239    ///
240    /// The chain runs whatever the operand is, so its cost says nothing about the value.
241    #[inline]
242    pub const fn invert_or_zero(self) -> Self {
243        Self(invert_byte(self.0))
244    }
245
246    /// Multiplication by this element, as a map ready for the byte-wise engine.
247    ///
248    /// Scaling is `F_2`-linear, so a fixed factor is a matrix like any other.
249    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    // The characteristic is 2, so `TWO = ONE + ONE = ZERO`.
289    const TWO: Self = Self(0);
290    // The characteristic is 2, so `NEG_ONE = ONE`.
291    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    /// Raising to `2^k` is one tabulated map, whatever the exponent.
311    #[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        // The chain runs whatever the operand, so its cost says nothing about the value.
325        //
326        // Zero is outside the multiplicative group and the chain already sends it to itself.
327        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        // Squaring has order 8 as a map, so the seventh power of it squares back.
334        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    /// An element of `GF(2^n)` is exactly `n` bits wide.
343    #[inline]
344    fn bits() -> usize {
345        Self::BITS
346    }
347
348    /// The enumeration by bit pattern.
349    ///
350    /// The coordinates of the returned element over `GF(2)` are the bits of the index.
351    ///
352    /// # Panics
353    /// Panics if the index does not fit in a byte, which would otherwise alias an earlier node.
354    #[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    /// The tower scales by a basis vector, which costs it a shift.
388    ///
389    /// Here the same element is an arbitrary one, so it costs a full product.
390    #[inline]
391    fn mul_alpha(self) -> Self {
392        Self(mul_bytes(self.0, XI[2]))
393    }
394
395    /// # Panics
396    /// Panics if the stream ends before a whole element has been read.
397    #[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    /// # Panics
407    /// Panics if the index is at least the bit width of the field.
408    #[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    /// The same field element, seen in the polynomial basis.
417    #[inline]
418    fn from(x: BinaryField8) -> Self {
419        Self(FROM_TOWER.apply(x.to_repr()))
420    }
421}
422
423impl From<Rijndael8b> for BinaryField8 {
424    /// The same field element, seen in the tower basis.
425    #[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        // Addition in characteristic 2 is `XOR`.
438        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        // Subtraction coincides with addition in characteristic 2.
449        self + rhs
450    }
451}
452
453impl Neg for Rijndael8b {
454    type Output = Self;
455
456    #[inline]
457    fn neg(self) -> Self {
458        // `-x = x` in characteristic 2.
459        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    /// `GF(2)` is the prime subfield, embedded as `{ZERO, ONE}`.
480    #[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    /// Multiplication straight from the modulus, one coefficient of the multiplier at a time.
504    ///
505    /// The routine under test packs both folds into shifts of a whole byte.
506    ///
507    /// This loop shares none of that structure.
508    fn schoolbook(a: u8, b: u8) -> u8 {
509        let mut acc = 0u8;
510        for i in (0..8).rev() {
511            // Double, rewriting the overflow as the modulus tail.
512            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        // x^7 * x = x^8 = x^4 + x^3 + x + 1, the tail spelled 0x1b.
527        assert_eq!(mul_bytes(0x80, 0x02), 0x1b);
528
529        // The worked example from the block cipher's own column mixing.
530        assert_eq!(mul_bytes(0xd4, 0x02), 0xb3);
531        assert_eq!(mul_bytes(0xbf, 0x03), 0xda);
532
533        // Far below the modulus, so nothing folds at all.
534        assert_eq!(mul_bytes(0x02, 0x04), 0x08);
535    }
536
537    #[test]
538    fn the_published_substitution_box_entries_come_out() {
539        // The cipher's substitution box is inversion followed by a fixed affine map.
540        //
541        // Its published entries therefore pin both the inverse and the map engine.
542        //
543        //     s(x) = A(x^254) + 0x63,   A the map with the circulant rows of 0x1f
544        let affine = ByteMatrix::from_images([0x1f, 0x3e, 0x7c, 0xf8, 0xf1, 0xe3, 0xc7, 0x8f]);
545
546        // The quadword published for the inverting affine-byte instruction's matrix operand.
547        //
548        // This pins the row layout to the hardware on every host, not only on those that run it.
549        assert_eq!(affine.to_quadword(), 0xf1e3_c78f_1f3e_7cf8);
550
551        let sbox = |x: u8| affine.apply(super::invert_byte(x)) ^ 0x63;
552
553        // Entries 0x00, 0x01, 0x53 and 0xff of the published table.
554        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        // Fixture state: 255 nonzero bytes, each with exactly one inverse.
563        for byte in 1..=u8::MAX {
564            assert_eq!(mul_bytes(byte, super::invert_byte(byte)), 1, "{byte:#x}");
565        }
566        // Zero is outside the group and the exponentiation leaves it alone.
567        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        // Both representations are the same field, so one generator maps onto the other.
574        //
575        // This is what pins the transcribed bit pattern the constant is built from.
576        assert_eq!(
577            Rijndael8b::GENERATOR,
578            Rijndael8b::from(BinaryField8::GENERATOR)
579        );
580    }
581
582    #[test]
583    fn the_generator_has_the_full_order() {
584        // Invariant: the group has order 255 = 3 * 5 * 17.
585        //
586        // A generator survives all three proper-divisor powers.
587        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        // Invariant: xi_k^2 + xi_{k-1} xi_k + 1 = 0, with xi_-1 = 1.
597        //
598        // This is what makes the change of basis multiplicative rather than merely invertible.
599        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        // Exhaustive over both operands: 65536 pairs settles the whole field.
613        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                // Additive, which any change of coordinates would give.
618                assert_eq!(
619                    Rijndael8b::from(x + y),
620                    Rijndael8b::from(x) + Rijndael8b::from(y)
621                );
622
623                // Multiplicative, which is the part that pins the generator images.
624                assert_eq!(
625                    Rijndael8b::from(x.reference_mul(y)),
626                    Rijndael8b::from(x) * Rijndael8b::from(y)
627                );
628            }
629            // And back again, so neither direction can be a different map.
630            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        // Invariant: v_0 = 1 and v_i^2 + v_i = v_{i-1}.
638        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        // And it is the image of the tower's own, so both span the same transform domain.
649        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        // The tower scales by a basis vector; here the same element is an arbitrary one.
662        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        /// The shift-and-fold product must agree with reduction straight from the modulus.
688        #[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        /// A fixed factor tabulated as a map must be the same product.
694        #[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        /// The square root must square back, on every byte.
700        #[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        // No bit is masked off, so nothing is out of range.
711        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}