1use 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
28const TOWER_GENERATOR: u128 = 0x1_0000_0000_0000_0005;
32
33const ALPHA: u128 = clmul::tower_image_128(1 << 64);
37
38#[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#[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
61const 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#[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
94const TABLE_CHUNK: usize = 1 << 16;
98
99impl Ghash128 {
100 pub(crate) const BITS: usize = 128;
102
103 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 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 unsafe { Vec::from_raw_parts(ptr.cast::<Self>(), len, capacity) }
126 }
127
128 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 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 #[inline]
161 pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
162 Self(u128::from_le_bytes(bytes))
163 }
164
165 #[inline]
193 pub fn dot_powers_of_x(values: &[Self]) -> Self {
194 if clmul::HAS_HARDWARE_CLMUL {
195 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 #[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 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 const TWO: Self = Self(0);
259 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 Self(clmul::poly_dot_128(
283 u.iter().zip(v).map(|(a, b)| (a.0, b.0)),
284 ))
285 }
286
287 #[inline]
290 fn bool_check(&self) -> Self {
291 self.square() + *self
292 }
293}
294
295impl Field for Ghash128 {
296 #[allow(clippy::use_self)]
301 type Packing = crate::packed::Packing;
302
303 const GENERATOR: Self = Self(clmul::tower_image_128(TOWER_GENERATOR));
304
305 #[inline]
310 fn try_inverse(&self) -> Option<Self> {
311 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 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 #[inline]
329 fn bits() -> usize {
330 Self::BITS
331 }
332
333 #[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 #[inline]
372 fn mul_alpha(self) -> Self {
373 self * Self(ALPHA)
374 }
375
376 #[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 #[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 #[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 #[inline]
409 fn from(x: Ghash128) -> Self {
410 Self::from_repr(clmul::poly_to_tower_128(x.0))
411 }
412}
413
414impl Add<BinaryField128> for Ghash128 {
418 type Output = Self;
419
420 #[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 #[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 #[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 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 self + rhs
493 }
494}
495
496impl Neg for Ghash128 {
497 type Output = Self;
498
499 #[inline]
500 fn neg(self) -> Self::Output {
501 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 #[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 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 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 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 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 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 const fn assert_algebra_over_the_tower<T: Algebra<BinaryField128>>() {}
607 assert_algebra_over_the_tower::<Ghash128>();
608
609 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 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 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 assert_eq!(x * x, Ghash128::from_repr(4));
637
638 assert_eq!(Ghash128::from_repr(3).square(), Ghash128::from_repr(5));
640 }
641
642 #[test]
643 fn the_cantor_basis_satisfies_its_recurrence() {
644 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 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 let mut rows: Vec<u128> = CANTOR_BASIS.to_vec();
670 let mut pivots = 0;
671
672 for bit in 0..128 {
673 if let Some(k) = (pivots..rows.len()).find(|&k| (rows[k] >> bit) & 1 == 1) {
675 rows.swap(pivots, k);
676
677 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 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 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 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 prop_assert_eq!(Ghash128::from(x + y), Ghash128::from(x) + Ghash128::from(y));
967
968 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 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 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 let _element = Ghash128::from_le_byte_iter([0u8; 15].into_iter());
1054 }
1055
1056 #[test]
1057 fn serde_accepts_every_bit_pattern() {
1058 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}