Skip to main content

p3_binary_field/
ghash.rs

1//! `GF(2^128)` in the polynomial basis of `x^128 + x^7 + x^2 + x + 1`.
2
3use alloc::vec::Vec;
4use core::fmt::{self, Debug, Display, Formatter};
5use core::iter::{Product, Sum};
6use core::mem::ManuallyDrop;
7use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
8use core::slice;
9
10use num_bigint::BigUint;
11use p3_field::op_assign_macros::{
12    impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
13    impl_sub_assign, impl_sub_base_field, ring_sum,
14};
15use p3_field::{Algebra, Field, Packable, PrimeCharacteristicRing, RawDataSerializable};
16use p3_maybe_rayon::prelude::*;
17use rand::Rng;
18use rand::distr::{Distribution, StandardUniform};
19use serde::{Deserialize, Serialize};
20
21use crate::cantor::CANTOR_BASIS_128;
22use crate::gf2::characteristic_two_methods;
23use crate::tower::TowerLevel;
24use crate::{
25    BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2, clmul,
26};
27
28/// The bit pattern of the multiplicative generator of the tower representation.
29///
30/// Its image is the generator here, so the conversion carries one onto the other.
31const TOWER_GENERATOR: u128 = 0x1_0000_0000_0000_0005;
32
33/// The tower's generator of `GF(2^128)` over `GF(2^64)`, in this representation.
34///
35/// The tower carries that element as a single basis vector, at bit 64.
36const ALPHA: u128 = clmul::tower_image_128(1 << 64);
37
38/// The inverse of an element, with zero sent to zero, by the addition chain over Frobenius maps.
39///
40/// The chain runs whatever the operand is, so its cost says nothing about the value.
41#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
42#[inline]
43fn invert_or_zero(x: Ghash128) -> Ghash128 {
44    Ghash128(clmul::poly_inverse_128(x.0))
45}
46
47/// The inverse of an element, with zero sent to zero, through the tower norm.
48///
49/// The tower recurses through the norm down to a `GF(2^8)` lookup table.
50/// Everywhere the addition chain is not faster, that beats it for no table at all.
51/// That recursion returns early on a zero operand at every level, so this route is not
52/// branch-free.
53#[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
54#[inline]
55fn invert_or_zero(x: Ghash128) -> Ghash128 {
56    BinaryField128::from(x)
57        .try_inverse()
58        .map_or(Ghash128::ZERO, Ghash128::from)
59}
60
61/// The Cantor basis in this representation.
62///
63/// These are the images of the tower's own basis vectors.
64/// Both representations therefore span the same additive NTT domain.
65const CANTOR_BASIS: [u128; 128] = {
66    let mut basis = [0u128; 128];
67    let mut i = 0;
68    while i < 128 {
69        basis[i] = clmul::tower_image_128(CANTOR_BASIS_128[i]);
70        i += 1;
71    }
72    basis
73};
74
75/// The binary field GF(2^128) modulo x^128 + x^7 + x^2 + x + 1.
76///
77/// Bit i stores the coefficient of x^i.
78/// Every 128-bit pattern is a distinct field element.
79///
80/// Hardware carryless multiplication operates directly on this representation.
81/// The tower representation instead provides byte-aligned subfields.
82///
83/// Inversion and conversions to or from the tower use operand-indexed tables.
84/// They are not constant-time for secret inputs.
85///
86/// NIST GCM blocks use the opposite coefficient order.
87/// Reverse all 128 bits of a big-endian block integer before interpreting it here.
88#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
89#[serde(transparent)]
90#[repr(transparent)]
91#[must_use]
92pub struct Ghash128(u128);
93
94/// Elements one parallel task converts in [`Ghash128::from_tower_vec`].
95///
96/// A power of two, so a whole number of blocks of the blocked basis-change kernel.
97const TABLE_CHUNK: usize = 1 << 16;
98
99impl Ghash128 {
100    /// The number of bits of an element.
101    pub(crate) const BITS: usize = 128;
102
103    /// A whole table of tower elements, seen in the polynomial basis, in the table's own buffer.
104    ///
105    /// Each entry becomes what [`From`] makes of it.
106    ///
107    /// Chunks of the table convert in parallel, a block at a time where the build has the
108    /// blocked kernel [`crate::poly_basis::from_tower_slice`] describes.
109    pub fn from_tower_vec(values: Vec<BinaryField128>) -> Vec<Self> {
110        let mut values = ManuallyDrop::new(values);
111        let (ptr, len, capacity) = (values.as_mut_ptr(), values.len(), values.capacity());
112
113        // SAFETY: `BinaryField128` is transparent over `u128`, so the initialized entries are
114        // `len` valid `u128`s. The vector is never used again, so this is the only reference.
115        let words = unsafe { slice::from_raw_parts_mut(ptr.cast::<u128>(), len) };
116        words.par_chunks_mut(TABLE_CHUNK).for_each(|chunk| {
117            clmul::tower_to_poly_128_slice(chunk);
118        });
119
120        // SAFETY: `Ghash128` is transparent over `u128` as well, so the allocation has its size
121        // and alignment, and every `u128` is a valid element. This also relies on taking
122        // `BinaryField128` specifically: its `MASK` is `u128::MAX`, so every bit pattern left in
123        // `words` is already canonical. A smaller tower field's mask clears high bits, so its
124        // buffer could hold non-canonical entries and this reinterpretation would not be sound.
125        unsafe { Vec::from_raw_parts(ptr.cast::<Self>(), len, capacity) }
126    }
127
128    /// Tries to apply an arbitrary `F_2`-linear map, given by its column images, to a batch.
129    ///
130    /// `images[j]` is the output image of input bit `j`, in the little-endian coordinate order.
131    /// The map need not be a change of basis: any linear map, invertible or not, is applied to the
132    /// raw bits of each input.
133    /// A short or unsupported target returns `false` without touching `output`; equal lengths are
134    /// required before that dispatch so an invalid call cannot be mistaken for a refusal.
135    pub fn try_apply_linear_map_into(
136        images: &[Self; 128],
137        input: &[BinaryField128],
138        output: &mut [Self],
139    ) -> bool {
140        assert_eq!(
141            input.len(),
142            output.len(),
143            "input and output must have equal lengths"
144        );
145
146        let columns = images.map(Self::to_repr);
147        // SAFETY: both field types are repr(transparent) over u128; all bit patterns are valid
148        // for BinaryField128 and Ghash128. The slices have already been checked to have equal
149        // lengths, and the map kernel only writes output after a successful target dispatch.
150        let input_words =
151            unsafe { slice::from_raw_parts(input.as_ptr().cast::<u128>(), input.len()) };
152        let output_words =
153            unsafe { slice::from_raw_parts_mut(output.as_mut_ptr().cast::<u128>(), output.len()) };
154        clmul::try_map_tower_coordinates_into(&columns, input_words, output_words)
155    }
156
157    /// Construct a field element from its little-endian byte representation.
158    ///
159    /// Every byte string of this length is a valid element.
160    #[inline]
161    pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
162        Self(u128::from_le_bytes(bytes))
163    }
164
165    /// Combine values against the successive powers of the indeterminate.
166    ///
167    /// ```text
168    ///     sum_k values_k * x^k
169    /// ```
170    ///
171    /// In this representation a power of the indeterminate shifts the coefficients up.
172    ///
173    /// So the combination can be shifts and exclusive ors, with no multiplication at all.
174    ///
175    /// Either way the modulus is folded in once at the end, not once per term.
176    ///
177    /// Which route is faster depends on the target.
178    ///
179    /// A hardware carryless multiply turns each term into one cheap product, and beats the
180    /// shifts.
181    ///
182    /// Without one a product costs sixteen integer multiplies, and the shifts win by an
183    /// order of magnitude.
184    ///
185    /// The choice is made at compile time from the same flag the rest of the crate reads.
186    ///
187    /// # Panics
188    ///
189    /// Panics on more values than the field has bits.
190    ///
191    /// That ceiling is where the last shift would leave the word.
192    #[inline]
193    pub fn dot_powers_of_x(values: &[Self]) -> Self {
194        if clmul::HAS_HARDWARE_CLMUL {
195            // The powers are bare bit patterns, so the deferred dot product needs no table.
196            //
197            // Each one is the previous shifted up, which is cheaper than shifting by the index.
198            let mut power = 1u128;
199            let terms = values.iter().map(|v| {
200                let term = (v.0, power);
201                power <<= 1;
202                term
203            });
204            Self(clmul::poly_dot_128(terms))
205        } else {
206            Self(clmul::poly_dot_powers_128(values.iter().map(|v| v.0)))
207        }
208    }
209
210    /// The inverse of this element, with zero sent to zero.
211    ///
212    /// On a carryless-multiply target the addition chain runs whatever the operand is, so its
213    /// cost says nothing about the value. The operand-indexed tables it uses still make it
214    /// variable-time.
215    #[inline]
216    pub fn invert_or_zero(self) -> Self {
217        invert_or_zero(self)
218    }
219}
220
221impl Packable for Ghash128 {}
222
223impl Display for Ghash128 {
224    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
225        Display::fmt(&self.0, f)
226    }
227}
228
229impl Debug for Ghash128 {
230    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
231        Debug::fmt(&self.0, f)
232    }
233}
234
235impl Distribution<Ghash128> for StandardUniform {
236    #[inline]
237    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Ghash128 {
238        let mut bytes = [0u8; 16];
239        rng.fill_bytes(&mut bytes);
240        Ghash128::from_le_bytes(bytes)
241    }
242}
243
244impl PrimeCharacteristicRing for Ghash128 {
245    #[inline]
246    fn zero_vec(len: usize) -> Vec<Self> {
247        let mut values = ManuallyDrop::new(alloc::vec![0u128; len]);
248        // SAFETY: the transparent wrapper has exactly the integer's layout, and zero is
249        // canonical. The allocation retains its original size and alignment.
250        unsafe { Vec::from_raw_parts(values.as_mut_ptr().cast(), values.len(), values.capacity()) }
251    }
252
253    type PrimeSubfield = Gf2;
254
255    const ZERO: Self = Self(0);
256    const ONE: Self = Self(1);
257    // The characteristic is 2, so `TWO = ONE + ONE = ZERO`.
258    const TWO: Self = Self(0);
259    // The characteristic is 2, so `NEG_ONE = ONE`.
260    const NEG_ONE: Self = Self(1);
261
262    #[inline]
263    fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
264        Self::from_bool(f.is_one())
265    }
266
267    #[inline]
268    fn from_bool(b: bool) -> Self {
269        Self(u128::from(b))
270    }
271
272    characteristic_two_methods!();
273
274    #[inline]
275    fn square(&self) -> Self {
276        Self(clmul::poly_square_128(self.0))
277    }
278
279    #[inline]
280    fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
281        // Reduction is linear, so an entire sum pays for it only once.
282        Self(clmul::poly_dot_128(
283            u.iter().zip(v).map(|(a, b)| (a.0, b.0)),
284        ))
285    }
286
287    /// `x·(x - 1) = x² - x = x² + x` in characteristic 2, and `poly_square_128` skips the
288    /// cross-term carryless multiplies a general product pays for.
289    #[inline]
290    fn bool_check(&self) -> Self {
291        self.square() + *self
292    }
293}
294
295impl Field for Ghash128 {
296    // One element is one 128-bit lane, so a wide carryless multiply packs several of them.
297    // Which register that is, and whether there is one at all, is settled in `packed`.
298    //
299    // Without a packing the alias resolves to this type itself, which is why the lint is off.
300    #[allow(clippy::use_self)]
301    type Packing = crate::packed::Packing;
302
303    const GENERATOR: Self = Self(clmul::tower_image_128(TOWER_GENERATOR));
304
305    /// Invert through precomputed squaring maps on carryless-multiply targets.
306    /// The software backend instead uses the recursive tower norm.
307    ///
308    /// The operand-indexed tables make this operation variable-time.
309    #[inline]
310    fn try_inverse(&self) -> Option<Self> {
311        // The chain runs first; only the answer depends on whether the operand was zero.
312        let inverse = self.invert_or_zero();
313        (self.0 != 0).then_some(inverse)
314    }
315
316    #[inline]
317    fn try_sqrt(&self) -> Option<Self> {
318        // Separate even and odd coefficients to invert the squaring map directly.
319        Some(Self(clmul::poly_sqrt_128(self.0)))
320    }
321
322    #[inline]
323    fn order() -> BigUint {
324        BigUint::from(1u8) << Self::BITS
325    }
326
327    /// An element of `GF(2^n)` is exactly `n` bits wide.
328    #[inline]
329    fn bits() -> usize {
330        Self::BITS
331    }
332
333    /// The enumeration by bit pattern.
334    ///
335    /// The coordinates of the returned element over `GF(2)` are the bits of the index.
336    /// A pointer is never 128 bits wide, so every index is in range.
337    #[inline]
338    fn interpolation_node(i: usize) -> Self {
339        Self(i as u128)
340    }
341}
342
343impl RawDataSerializable for Ghash128 {
344    const NUM_BYTES: usize = 16;
345
346    #[inline]
347    fn into_bytes(self) -> impl IntoIterator<Item = u8> {
348        self.0.to_le_bytes()
349    }
350}
351
352impl crate::tower::private::Sealed for Ghash128 {}
353
354impl TowerLevel for Ghash128 {
355    type Repr = u128;
356
357    const LOG_BITS: usize = 7;
358
359    #[inline]
360    fn from_repr(r: Self::Repr) -> Self {
361        Self(r)
362    }
363
364    #[inline]
365    fn to_repr(self) -> Self::Repr {
366        self.0
367    }
368
369    /// That generator is a basis element in the tower, which gets this for a shift.
370    /// Here it is an arbitrary element, so it costs a full product.
371    #[inline]
372    fn mul_alpha(self) -> Self {
373        self * Self(ALPHA)
374    }
375
376    /// # Panics
377    /// Panics if the stream ends before a whole element has been read.
378    #[inline]
379    fn from_le_byte_iter(mut bytes: impl Iterator<Item = u8>) -> Self {
380        let mut buffer = [0u8; 16];
381        for byte in &mut buffer {
382            *byte = bytes
383                .next()
384                .expect("byte stream ended before a whole element was read");
385        }
386        Self::from_le_bytes(buffer)
387    }
388
389    /// # Panics
390    /// Panics if the index is at least the bit width of the field.
391    #[inline]
392    fn cantor_basis(i: usize) -> Self {
393        assert!(i < Self::BITS, "Cantor basis index out of range");
394        Self(CANTOR_BASIS[i])
395    }
396}
397
398impl From<BinaryField128> for Ghash128 {
399    /// The same field element, seen in the polynomial basis.
400    #[inline]
401    fn from(x: BinaryField128) -> Self {
402        Self(clmul::tower_to_poly_128(x.to_repr()))
403    }
404}
405
406impl From<Ghash128> for BinaryField128 {
407    /// The same field element, seen in the tower basis.
408    #[inline]
409    fn from(x: Ghash128) -> Self {
410        Self::from_repr(clmul::poly_to_tower_128(x.0))
411    }
412}
413
414// The change of basis is a field isomorphism, so it makes this field an algebra over the tower.
415// A mixed operation takes the polynomial-basis operand on the left and returns this basis.
416
417impl Add<BinaryField128> for Ghash128 {
418    type Output = Self;
419
420    /// The sum, in the polynomial basis.
421    ///
422    /// The tower operand is converted first, which costs sixteen table lookups.
423    #[inline]
424    fn add(self, rhs: BinaryField128) -> Self {
425        self + Self::from(rhs)
426    }
427}
428
429impl Sub<BinaryField128> for Ghash128 {
430    type Output = Self;
431
432    /// The difference, in the polynomial basis.
433    ///
434    /// The tower operand is converted first, which costs sixteen table lookups.
435    #[inline]
436    fn sub(self, rhs: BinaryField128) -> Self {
437        self - Self::from(rhs)
438    }
439}
440
441impl Mul<BinaryField128> for Ghash128 {
442    type Output = Self;
443
444    /// The product, in the polynomial basis.
445    ///
446    /// The tower operand is converted first, which costs sixteen table lookups.
447    #[inline]
448    fn mul(self, rhs: BinaryField128) -> Self {
449        self * Self::from(rhs)
450    }
451}
452
453impl Algebra<BinaryField128> for Ghash128 {}
454
455macro_rules! impl_narrow_algebra {
456    ($($field:ty),* $(,)?) => {$(
457        impl From<$field> for Ghash128 {
458            #[inline]
459            fn from(x: $field) -> Self {
460                Self::from(BinaryField128::from(x))
461            }
462        }
463
464        impl_add_base_field!(Ghash128, $field);
465        impl_sub_base_field!(Ghash128, $field);
466        impl_mul_base_field!(Ghash128, $field);
467
468        impl Algebra<$field> for Ghash128 {}
469    )*};
470}
471
472impl_narrow_algebra!(BinaryField8, BinaryField16, BinaryField32, BinaryField64);
473
474impl Add for Ghash128 {
475    type Output = Self;
476
477    #[inline]
478    #[allow(clippy::suspicious_arithmetic_impl)]
479    fn add(self, rhs: Self) -> Self {
480        // Addition in characteristic 2 is `XOR`.
481        Self(self.0 ^ rhs.0)
482    }
483}
484
485impl Sub for Ghash128 {
486    type Output = Self;
487
488    #[inline]
489    #[allow(clippy::suspicious_arithmetic_impl)]
490    fn sub(self, rhs: Self) -> Self {
491        // Subtraction coincides with addition in characteristic 2.
492        self + rhs
493    }
494}
495
496impl Neg for Ghash128 {
497    type Output = Self;
498
499    #[inline]
500    fn neg(self) -> Self::Output {
501        // `-x = x` in characteristic 2.
502        self
503    }
504}
505
506impl Mul for Ghash128 {
507    type Output = Self;
508
509    #[inline]
510    fn mul(self, rhs: Self) -> Self {
511        Self(clmul::poly_mul_128(self.0, rhs.0))
512    }
513}
514
515impl_add_assign!(Ghash128);
516impl_sub_assign!(Ghash128);
517impl_mul_methods!(Ghash128);
518impl_div_methods!(Ghash128, Ghash128);
519ring_sum!(Ghash128);
520
521impl From<Gf2> for Ghash128 {
522    /// `GF(2)` is the prime subfield, embedded as `{ZERO, ONE}`.
523    #[inline]
524    fn from(x: Gf2) -> Self {
525        Self::from_prime_subfield(x)
526    }
527}
528
529impl_add_base_field!(Ghash128, Gf2);
530impl_sub_base_field!(Ghash128, Gf2);
531impl_mul_base_field!(Ghash128, Gf2);
532
533impl Algebra<Gf2> for Ghash128 {}
534
535#[cfg(test)]
536mod tests {
537    extern crate std;
538
539    use std::vec;
540    use std::vec::Vec;
541
542    use p3_field::{Algebra, Field, PrimeCharacteristicRing, RawDataSerializable};
543    use proptest::prelude::*;
544
545    use super::{CANTOR_BASIS, Ghash128};
546    use crate::tower::TowerLevel;
547    use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2};
548
549    #[test]
550    fn narrow_algebras_match_multiplication_in_the_tower() {
551        // The static checks cover every committed alphabet admitted under 128-bit challenges.
552        const fn assert_algebra<F: PrimeCharacteristicRing, A: Algebra<F>>() {}
553        assert_algebra::<BinaryField8, Ghash128>();
554        assert_algebra::<BinaryField16, Ghash128>();
555        assert_algebra::<BinaryField32, Ghash128>();
556        assert_algebra::<BinaryField64, Ghash128>();
557        assert_algebra::<BinaryField128, Ghash128>();
558
559        // A nontrivial 32-bit scalar pins the embedding against the tower-field reference.
560        let x = BinaryField128::from_repr(0x3141_5926_5358_9793_2384_6264_3383_2795);
561        let x_poly = Ghash128::from(x);
562        let scalar = BinaryField32::from_repr(0xa5c3_19e7);
563        assert_eq!(
564            BinaryField128::from(x_poly * scalar),
565            x * BinaryField128::from(scalar)
566        );
567    }
568
569    /// The tower element with the given bit pattern.
570    fn tower(bits: u128) -> BinaryField128 {
571        BinaryField128::from_repr(bits)
572    }
573
574    #[test]
575    fn a_tower_table_converts_entry_by_entry_in_its_own_buffer() {
576        // Invariant: the table map is the element map, and the buffer is reused.
577        //
578        // Fixture state: lengths around one block of the blocked kernel and across several
579        // parallel chunks, with a partial last chunk and a partial last block.
580        for len in [0, 1, 63, 64, 65, 1000, 2 * super::TABLE_CHUNK + 67] {
581            let table: Vec<BinaryField128> = (0..len as u128)
582                .map(|i| tower(i.wrapping_mul(0x9e37_79b9_7f4a_7c15_f39c_c060_5ced_c835) ^ i))
583                .collect();
584            let want: Vec<Ghash128> = table.iter().map(|&x| Ghash128::from(x)).collect();
585
586            let at = table.as_ptr() as usize;
587            let got = Ghash128::from_tower_vec(table);
588
589            assert_eq!(got, want, "length {len}");
590            if len > 0 {
591                assert_eq!(got.as_ptr() as usize, at, "length {len}");
592            }
593        }
594    }
595
596    #[test]
597    fn the_change_of_basis_fixes_the_constants() {
598        // Any field isomorphism fixes zero and one.
599        assert_eq!(Ghash128::from(BinaryField128::ZERO), Ghash128::ZERO);
600        assert_eq!(Ghash128::from(BinaryField128::ONE), Ghash128::ONE);
601    }
602
603    #[test]
604    fn the_algebra_over_the_tower_fixes_the_prime_subfield() {
605        /// Statically require the full `Algebra<BinaryField128>` bound, not merely the operators.
606        const fn assert_algebra_over_the_tower<T: Algebra<BinaryField128>>() {}
607        assert_algebra_over_the_tower::<Ghash128>();
608
609        // Both representations embed `GF(2)` as zero and one, and the isomorphism agrees.
610        for bit in [Gf2::ZERO, Gf2::ONE] {
611            assert_eq!(
612                Ghash128::from(BinaryField128::from(bit)),
613                Ghash128::from(bit)
614            );
615        }
616    }
617
618    #[test]
619    fn the_generator_is_the_image_of_the_tower_generator() {
620        // Both representations are the same field, so one generator maps onto the other.
621        // This is what pins the transcribed bit pattern the constant is built from.
622        assert_eq!(
623            Ghash128::GENERATOR,
624            Ghash128::from(BinaryField128::GENERATOR)
625        );
626    }
627
628    #[test]
629    fn the_modulus_reduces_the_way_the_polynomial_says() {
630        // x^127 * x = x^128 = x^7 + x^2 + x + 1, the tail spelled 0x87.
631        let x = Ghash128::from_repr(2);
632        let top = Ghash128::from_repr(1 << 127);
633        assert_eq!(top * x, Ghash128::from_repr(0x87));
634
635        // x * x = x^2, nowhere near the modulus.
636        assert_eq!(x * x, Ghash128::from_repr(4));
637
638        // (x + 1)^2 = x^2 + 1, since the cross term doubles to zero.
639        assert_eq!(Ghash128::from_repr(3).square(), Ghash128::from_repr(5));
640    }
641
642    #[test]
643    fn the_cantor_basis_satisfies_its_recurrence() {
644        // Invariant: v_0 = 1 and v_i^2 + v_i = v_{i-1}.
645        // This is what the additive NTT domain is built on.
646        assert_eq!(Ghash128::cantor_basis(0), Ghash128::ONE);
647
648        for i in 1..Ghash128::BITS {
649            let v = Ghash128::cantor_basis(i);
650            assert_eq!(v.square() + v, Ghash128::cantor_basis(i - 1), "vector {i}");
651        }
652    }
653
654    #[test]
655    fn the_cantor_basis_is_the_image_of_the_tower_one() {
656        // The two representations must span the same additive NTT domain, vector by vector.
657        for i in 0..Ghash128::BITS {
658            assert_eq!(
659                Ghash128::cantor_basis(i),
660                Ghash128::from(BinaryField128::cantor_basis(i)),
661                "vector {i}"
662            );
663        }
664    }
665
666    #[test]
667    fn the_cantor_basis_is_linearly_independent() {
668        // Row-reduce the vectors over GF(2): 128 independent vectors leave 128 pivots.
669        let mut rows: Vec<u128> = CANTOR_BASIS.to_vec();
670        let mut pivots = 0;
671
672        for bit in 0..128 {
673            // Find a remaining row with this bit set and move it into pivot position.
674            if let Some(k) = (pivots..rows.len()).find(|&k| (rows[k] >> bit) & 1 == 1) {
675                rows.swap(pivots, k);
676
677                // Clear the bit from every other row.
678                for k in 0..rows.len() {
679                    if k != pivots && (rows[k] >> bit) & 1 == 1 {
680                        rows[k] ^= rows[pivots];
681                    }
682                }
683                pivots += 1;
684            }
685        }
686
687        assert_eq!(pivots, 128, "the Cantor basis is not a basis");
688    }
689
690    #[test]
691    #[should_panic = "Cantor basis index out of range"]
692    fn the_cantor_basis_rejects_an_index_beyond_the_field() {
693        let _vector = Ghash128::cantor_basis(128);
694    }
695
696    #[test]
697    fn bool_check_matches_the_vanishing_polynomial_at_zero_and_one() {
698        for x in [Ghash128::ZERO, Ghash128::ONE] {
699            assert_eq!(x.bool_check(), x * (x - Ghash128::ONE));
700        }
701    }
702
703    #[test]
704    fn scaling_by_alpha_agrees_with_the_tower() {
705        // The tower scales by a basis element; here the same element is an arbitrary one.
706        for bits in [0, 1, 2, 0x87, 1 << 127, u128::MAX] {
707            let x = tower(bits);
708            assert_eq!(
709                Ghash128::from(x).mul_alpha(),
710                Ghash128::from(x.mul_alpha()),
711                "{bits:#x}"
712            );
713        }
714    }
715
716    #[test]
717    fn zero_vectors_preserve_layout_and_support_growth() {
718        for len in [0, 1, 33, 1024] {
719            let mut values = Ghash128::zero_vec(len);
720            assert_eq!(values.len(), len);
721            assert!(values.iter().all(|x| *x == Ghash128::ZERO));
722            values.push(Ghash128::from_repr(7));
723            values.reserve(100);
724            assert_eq!(values.pop(), Some(Ghash128::from_repr(7)));
725        }
726    }
727
728    /// An independent definition of an arbitrary binary linear map: XOR the columns selected
729    /// by the input's little-endian bits. This deliberately does not use a field conversion.
730    fn column_walk(columns: &[Ghash128; 128], input: BinaryField128) -> Ghash128 {
731        let mut output = Ghash128::ZERO;
732        let bits = input.to_repr();
733        for (index, &column) in columns.iter().enumerate() {
734            if (bits >> index) & 1 == 1 {
735                output += column;
736            }
737        }
738        output
739    }
740
741    #[test]
742    fn dynamic_coordinate_map_matches_an_independent_bit_column_oracle() {
743        // Reversed singleton columns make every input/output bit order observable. The length
744        // crosses the production threshold so native GFNI builds take the prepared path.
745        let columns = core::array::from_fn(|index| Ghash128::from_repr(1u128 << (127 - index)));
746        let len = 4096 + 63;
747        let input: Vec<BinaryField128> = (0..len)
748            .map(|index| BinaryField128::from_repr(1u128 << (index % 128)))
749            .collect();
750        let original = input.clone();
751        let poison = Ghash128::from_repr(u128::MAX);
752        let mut output = vec![poison; len];
753
754        let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
755        assert_eq!(
756            input, original,
757            "the out-of-place map must not modify its source"
758        );
759        #[cfg(all(
760            target_arch = "x86_64",
761            target_feature = "gfni",
762            target_feature = "avx512f",
763            target_feature = "avx512bw"
764        ))]
765        assert!(accepted, "native GFNI must accept a batch at the threshold");
766        if accepted {
767            for (index, &value) in output.iter().enumerate() {
768                assert_eq!(
769                    value,
770                    column_walk(&columns, input[index]),
771                    "input bit {index}"
772                );
773            }
774        } else {
775            assert!(output.iter().all(|&value| value == poison));
776        }
777    }
778
779    #[test]
780    fn dynamic_coordinate_map_declines_short_inputs_without_touching_output() {
781        let columns = core::array::from_fn(|index| Ghash128::from_repr(index as u128));
782        let input = vec![BinaryField128::from_repr(7); 4095];
783        let poison = Ghash128::from_repr(u128::MAX);
784        let mut output = vec![poison; input.len()];
785
786        assert!(!Ghash128::try_apply_linear_map_into(
787            &columns,
788            &input,
789            &mut output
790        ));
791        assert!(output.iter().all(|&value| value == poison));
792    }
793
794    #[test]
795    fn dynamic_coordinate_map_handles_empty_and_dispatch_boundaries() {
796        let columns = core::array::from_fn(|index| Ghash128::from_repr(1u128 << (127 - index)));
797
798        for len in [0, 4095, 4096, 4097] {
799            let input = (0..len)
800                .map(|index| BinaryField128::from_repr(1u128 << (index % 128)))
801                .collect::<Vec<_>>();
802            let poison = Ghash128::from_repr(u128::MAX);
803            let mut output = vec![poison; len];
804            let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
805
806            #[cfg(all(
807                target_arch = "x86_64",
808                target_feature = "gfni",
809                target_feature = "avx512f",
810                target_feature = "avx512bw"
811            ))]
812            assert_eq!(accepted, len >= 4096, "length {len}");
813            #[cfg(not(all(
814                target_arch = "x86_64",
815                target_feature = "gfni",
816                target_feature = "avx512f",
817                target_feature = "avx512bw"
818            )))]
819            assert!(!accepted, "portable length {len}");
820
821            if accepted {
822                let expected = input
823                    .iter()
824                    .copied()
825                    .map(|value| column_walk(&columns, value))
826                    .collect::<Vec<_>>();
827                assert_eq!(output, expected, "length {len}");
828            } else {
829                assert!(output.iter().all(|&value| value == poison), "length {len}");
830            }
831        }
832    }
833
834    #[test]
835    fn dynamic_coordinate_map_handles_singular_and_mixed_columns() {
836        let maps = [
837            [Ghash128::ZERO; 128],
838            core::array::from_fn(|index| Ghash128::from_repr(1u128 << index)),
839            core::array::from_fn(|index| Ghash128::from_repr(1u128 << (index % 7))),
840            core::array::from_fn(|index| {
841                Ghash128::from_repr(
842                    (index as u128).wrapping_mul(0x9e37_79b9_7f4a_7c15)
843                        ^ (0xD1B5_4A32_1C6E_8F09u128.rotate_left(index as u32)),
844                )
845            }),
846        ];
847        let len = 4096 + 17;
848        let input = (0..len)
849            .map(|index| {
850                BinaryField128::from_repr(
851                    (index as u128).wrapping_mul(0xA5A5_5A5A_3141_5926) ^ (1u128 << (index % 128)),
852                )
853            })
854            .collect::<Vec<_>>();
855
856        for columns in maps {
857            let poison = Ghash128::from_repr(u128::MAX);
858            let mut output = vec![poison; input.len()];
859            let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
860            #[cfg(all(
861                target_arch = "x86_64",
862                target_feature = "gfni",
863                target_feature = "avx512f",
864                target_feature = "avx512bw"
865            ))]
866            assert!(accepted);
867            if accepted {
868                let expected = input
869                    .iter()
870                    .copied()
871                    .map(|value| column_walk(&columns, value))
872                    .collect::<Vec<_>>();
873                assert_eq!(output, expected);
874            } else {
875                assert!(output.iter().all(|&value| value == poison));
876            }
877        }
878    }
879
880    #[test]
881    fn dynamic_coordinate_map_preserves_offsets_and_destination_guards() {
882        let columns = core::array::from_fn(|index| {
883            Ghash128::from_repr((1u128 << (127 - index)) ^ (index as u128))
884        });
885        let input_values = (0..4096 + 63)
886            .map(|index| BinaryField128::from_repr(index as u128 * 17 + 3))
887            .collect::<Vec<_>>();
888        let input_offset = 3;
889        let output_offset = 5;
890        let guard = Ghash128::from_repr(0x5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A);
891        let mut input = vec![BinaryField128::ZERO; input_offset];
892        input.extend_from_slice(&input_values);
893        input.extend(core::iter::repeat_n(BinaryField128::ZERO, 7));
894        let mut output = vec![guard; output_offset];
895        output.extend(core::iter::repeat_n(guard, input_values.len()));
896        output.extend(core::iter::repeat_n(guard, 7));
897
898        let input_slice = &input[input_offset..input_offset + input_values.len()];
899        let output_slice = &mut output[output_offset..output_offset + input_values.len()];
900        let accepted = Ghash128::try_apply_linear_map_into(&columns, input_slice, output_slice);
901        #[cfg(all(
902            target_arch = "x86_64",
903            target_feature = "gfni",
904            target_feature = "avx512f",
905            target_feature = "avx512bw"
906        ))]
907        assert!(accepted);
908        assert_eq!(
909            &input[input_offset..input_offset + input_values.len()],
910            &input_values
911        );
912        assert!(output[..output_offset].iter().all(|&value| value == guard));
913        assert!(
914            output[output_offset + input_values.len()..]
915                .iter()
916                .all(|&value| value == guard)
917        );
918        if accepted {
919            assert!(
920                output[output_offset..]
921                    .iter()
922                    .take(input_values.len())
923                    .enumerate()
924                    .all(|(index, &value)| value == column_walk(&columns, input_values[index]))
925            );
926        } else {
927            assert!(
928                output[output_offset..]
929                    .iter()
930                    .take(input_values.len())
931                    .all(|&value| value == guard)
932            );
933        }
934    }
935
936    #[test]
937    #[should_panic(expected = "equal lengths")]
938    fn dynamic_coordinate_map_rejects_mismatched_shapes() {
939        let columns = [Ghash128::ZERO; 128];
940        let input = [BinaryField128::ZERO; 1];
941        let mut output = [Ghash128::ZERO; 0];
942        let _ = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
943    }
944
945    #[test]
946    fn zero_inverts_to_zero() {
947        assert_eq!(Ghash128::ZERO.invert_or_zero(), Ghash128::ZERO);
948        assert_eq!(Ghash128::ZERO.try_inverse(), None);
949        assert_eq!(Ghash128::ONE.invert_or_zero(), Ghash128::ONE);
950    }
951
952    proptest! {
953        #![proptest_config(ProptestConfig::with_cases(2000))]
954
955        #[test]
956        fn the_change_of_basis_round_trips(bits: u128) {
957            let x = Ghash128::from_repr(bits);
958            prop_assert_eq!(Ghash128::from(BinaryField128::from(x)), x);
959        }
960
961        #[test]
962        fn the_change_of_basis_is_a_ring_isomorphism(a: u128, b: u128) {
963            let (x, y) = (tower(a), tower(b));
964
965            // Additive: both representations add by exclusive or, over the same coordinates.
966            prop_assert_eq!(Ghash128::from(x + y), Ghash128::from(x) + Ghash128::from(y));
967
968            // Multiplicative: this is the part a mere change of coordinates would not give.
969            prop_assert_eq!(Ghash128::from(x * y), Ghash128::from(x) * Ghash128::from(y));
970        }
971
972        #[test]
973        fn the_tower_acts_through_the_change_of_basis(a: u128, b: u128) {
974            let (g, t) = (Ghash128::from_repr(a), tower(b));
975            let converted = Ghash128::from(t);
976
977            // Each mixed operation agrees with converting first.
978            prop_assert_eq!(g + t, g + converted);
979            prop_assert_eq!(g - t, g - converted);
980            prop_assert_eq!(g * t, g * converted);
981
982            let mut acc = g;
983            acc += t;
984            prop_assert_eq!(acc, g + converted);
985            let mut acc = g;
986            acc -= t;
987            prop_assert_eq!(acc, g - converted);
988            let mut acc = g;
989            acc *= t;
990            prop_assert_eq!(acc, g * converted);
991
992            // Carried back, each result is the tower's own arithmetic on the same elements.
993            let tower_g = BinaryField128::from(g);
994            prop_assert_eq!(BinaryField128::from(g + t), tower_g + t);
995            prop_assert_eq!(BinaryField128::from(g - t), tower_g - t);
996            prop_assert_eq!(BinaryField128::from(g * t), tower_g * t);
997        }
998
999        #[test]
1000        fn squaring_agrees_with_multiplying_by_self(bits: u128) {
1001            let x = Ghash128::from_repr(bits);
1002            prop_assert_eq!(x.square(), x * x);
1003        }
1004
1005        #[test]
1006        fn bool_check_agrees_with_the_vanishing_polynomial(bits: u128) {
1007            let x = Ghash128::from_repr(bits);
1008            prop_assert_eq!(x.bool_check(), x * (x - Ghash128::ONE));
1009        }
1010
1011        #[test]
1012        fn a_nonzero_element_times_its_inverse_is_one(bits: u128) {
1013            let x = Ghash128::from_repr(bits);
1014            match x.try_inverse() {
1015                Some(inverse) => prop_assert_eq!(x * inverse, Ghash128::ONE),
1016                None => prop_assert_eq!(x, Ghash128::ZERO),
1017            }
1018        }
1019
1020        #[test]
1021        fn invert_or_zero_agrees_with_try_inverse(bits: u128) {
1022            let x = Ghash128::from_repr(bits);
1023            prop_assert_eq!(
1024                x.invert_or_zero(),
1025                x.try_inverse().unwrap_or(Ghash128::ZERO)
1026            );
1027        }
1028
1029        #[test]
1030        fn the_square_root_squares_back(bits: u128) {
1031            let x = Ghash128::from_repr(bits);
1032            let root = x.try_sqrt().expect("every element of a binary field is a square");
1033            prop_assert_eq!(root.square(), x);
1034        }
1035
1036        #[test]
1037        fn the_prime_subfield_embeds_as_zero_and_one(bit: bool) {
1038            let embedded = Ghash128::from(Gf2::from_bool(bit));
1039            prop_assert_eq!(embedded, Ghash128::from_bool(bit));
1040        }
1041
1042        #[test]
1043        fn the_byte_stream_reads_back_what_the_element_wrote(bits: u128) {
1044            let x = Ghash128::from_repr(bits);
1045            prop_assert_eq!(Ghash128::from_le_byte_iter(x.into_bytes().into_iter()), x);
1046        }
1047    }
1048
1049    #[test]
1050    #[should_panic = "byte stream ended before a whole element was read"]
1051    fn a_truncated_byte_stream_is_rejected() {
1052        // Fifteen bytes is one short of an element.
1053        let _element = Ghash128::from_le_byte_iter([0u8; 15].into_iter());
1054    }
1055
1056    #[test]
1057    fn serde_accepts_every_bit_pattern() {
1058        // Unlike the narrower tower levels, no bit is masked off, so nothing is out of range.
1059        for bits in [0u128, 1, u128::MAX, 1 << 127] {
1060            let x = Ghash128::from_repr(bits);
1061            let encoded = serde_json::to_string(&x).unwrap();
1062            assert_eq!(serde_json::from_str::<Ghash128>(&encoded).unwrap(), x);
1063        }
1064    }
1065}