use alloc::vec::Vec;
use core::fmt::{self, Debug, Display, Formatter};
use core::iter::{Product, Sum};
use core::mem::ManuallyDrop;
use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
use core::slice;
use num_bigint::BigUint;
use p3_field::op_assign_macros::{
impl_add_assign, impl_add_base_field, impl_div_methods, impl_mul_base_field, impl_mul_methods,
impl_sub_assign, impl_sub_base_field, ring_sum,
};
use p3_field::{Algebra, Field, Packable, PrimeCharacteristicRing, RawDataSerializable};
use p3_maybe_rayon::prelude::*;
use rand::Rng;
use rand::distr::{Distribution, StandardUniform};
use serde::{Deserialize, Serialize};
use crate::cantor::CANTOR_BASIS_128;
use crate::gf2::characteristic_two_methods;
use crate::tower::TowerLevel;
use crate::{
BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2, clmul,
};
const TOWER_GENERATOR: u128 = 0x1_0000_0000_0000_0005;
const ALPHA: u128 = clmul::tower_image_128(1 << 64);
#[cfg(all(target_arch = "x86_64", target_feature = "pclmulqdq"))]
#[inline]
fn invert_or_zero(x: Ghash128) -> Ghash128 {
Ghash128(clmul::poly_inverse_128(x.0))
}
#[cfg(not(all(target_arch = "x86_64", target_feature = "pclmulqdq")))]
#[inline]
fn invert_or_zero(x: Ghash128) -> Ghash128 {
BinaryField128::from(x)
.try_inverse()
.map_or(Ghash128::ZERO, Ghash128::from)
}
const CANTOR_BASIS: [u128; 128] = {
let mut basis = [0u128; 128];
let mut i = 0;
while i < 128 {
basis[i] = clmul::tower_image_128(CANTOR_BASIS_128[i]);
i += 1;
}
basis
};
#[derive(Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(transparent)]
#[repr(transparent)]
#[must_use]
pub struct Ghash128(u128);
const TABLE_CHUNK: usize = 1 << 16;
impl Ghash128 {
pub(crate) const BITS: usize = 128;
pub fn from_tower_vec(values: Vec<BinaryField128>) -> Vec<Self> {
let mut values = ManuallyDrop::new(values);
let (ptr, len, capacity) = (values.as_mut_ptr(), values.len(), values.capacity());
let words = unsafe { slice::from_raw_parts_mut(ptr.cast::<u128>(), len) };
words.par_chunks_mut(TABLE_CHUNK).for_each(|chunk| {
clmul::tower_to_poly_128_slice(chunk);
});
unsafe { Vec::from_raw_parts(ptr.cast::<Self>(), len, capacity) }
}
pub fn try_apply_linear_map_into(
images: &[Self; 128],
input: &[BinaryField128],
output: &mut [Self],
) -> bool {
assert_eq!(
input.len(),
output.len(),
"input and output must have equal lengths"
);
let columns = images.map(Self::to_repr);
let input_words =
unsafe { slice::from_raw_parts(input.as_ptr().cast::<u128>(), input.len()) };
let output_words =
unsafe { slice::from_raw_parts_mut(output.as_mut_ptr().cast::<u128>(), output.len()) };
clmul::try_map_tower_coordinates_into(&columns, input_words, output_words)
}
#[inline]
pub const fn from_le_bytes(bytes: [u8; 16]) -> Self {
Self(u128::from_le_bytes(bytes))
}
#[inline]
pub fn dot_powers_of_x(values: &[Self]) -> Self {
if clmul::HAS_HARDWARE_CLMUL {
let mut power = 1u128;
let terms = values.iter().map(|v| {
let term = (v.0, power);
power <<= 1;
term
});
Self(clmul::poly_dot_128(terms))
} else {
Self(clmul::poly_dot_powers_128(values.iter().map(|v| v.0)))
}
}
#[inline]
pub fn invert_or_zero(self) -> Self {
invert_or_zero(self)
}
}
impl Packable for Ghash128 {}
impl Display for Ghash128 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl Debug for Ghash128 {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
Debug::fmt(&self.0, f)
}
}
impl Distribution<Ghash128> for StandardUniform {
#[inline]
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Ghash128 {
let mut bytes = [0u8; 16];
rng.fill_bytes(&mut bytes);
Ghash128::from_le_bytes(bytes)
}
}
impl PrimeCharacteristicRing for Ghash128 {
#[inline]
fn zero_vec(len: usize) -> Vec<Self> {
let mut values = ManuallyDrop::new(alloc::vec![0u128; len]);
unsafe { Vec::from_raw_parts(values.as_mut_ptr().cast(), values.len(), values.capacity()) }
}
type PrimeSubfield = Gf2;
const ZERO: Self = Self(0);
const ONE: Self = Self(1);
const TWO: Self = Self(0);
const NEG_ONE: Self = Self(1);
#[inline]
fn from_prime_subfield(f: Self::PrimeSubfield) -> Self {
Self::from_bool(f.is_one())
}
#[inline]
fn from_bool(b: bool) -> Self {
Self(u128::from(b))
}
characteristic_two_methods!();
#[inline]
fn square(&self) -> Self {
Self(clmul::poly_square_128(self.0))
}
#[inline]
fn dot_product<const N: usize>(u: &[Self; N], v: &[Self; N]) -> Self {
Self(clmul::poly_dot_128(
u.iter().zip(v).map(|(a, b)| (a.0, b.0)),
))
}
#[inline]
fn bool_check(&self) -> Self {
self.square() + *self
}
}
impl Field for Ghash128 {
#[allow(clippy::use_self)]
type Packing = crate::packed::Packing;
const GENERATOR: Self = Self(clmul::tower_image_128(TOWER_GENERATOR));
#[inline]
fn try_inverse(&self) -> Option<Self> {
let inverse = self.invert_or_zero();
(self.0 != 0).then_some(inverse)
}
#[inline]
fn try_sqrt(&self) -> Option<Self> {
Some(Self(clmul::poly_sqrt_128(self.0)))
}
#[inline]
fn order() -> BigUint {
BigUint::from(1u8) << Self::BITS
}
#[inline]
fn bits() -> usize {
Self::BITS
}
#[inline]
fn interpolation_node(i: usize) -> Self {
Self(i as u128)
}
}
impl RawDataSerializable for Ghash128 {
const NUM_BYTES: usize = 16;
#[inline]
fn into_bytes(self) -> impl IntoIterator<Item = u8> {
self.0.to_le_bytes()
}
}
impl crate::tower::private::Sealed for Ghash128 {}
impl TowerLevel for Ghash128 {
type Repr = u128;
const LOG_BITS: usize = 7;
#[inline]
fn from_repr(r: Self::Repr) -> Self {
Self(r)
}
#[inline]
fn to_repr(self) -> Self::Repr {
self.0
}
#[inline]
fn mul_alpha(self) -> Self {
self * Self(ALPHA)
}
#[inline]
fn from_le_byte_iter(mut bytes: impl Iterator<Item = u8>) -> Self {
let mut buffer = [0u8; 16];
for byte in &mut buffer {
*byte = bytes
.next()
.expect("byte stream ended before a whole element was read");
}
Self::from_le_bytes(buffer)
}
#[inline]
fn cantor_basis(i: usize) -> Self {
assert!(i < Self::BITS, "Cantor basis index out of range");
Self(CANTOR_BASIS[i])
}
}
impl From<BinaryField128> for Ghash128 {
#[inline]
fn from(x: BinaryField128) -> Self {
Self(clmul::tower_to_poly_128(x.to_repr()))
}
}
impl From<Ghash128> for BinaryField128 {
#[inline]
fn from(x: Ghash128) -> Self {
Self::from_repr(clmul::poly_to_tower_128(x.0))
}
}
impl Add<BinaryField128> for Ghash128 {
type Output = Self;
#[inline]
fn add(self, rhs: BinaryField128) -> Self {
self + Self::from(rhs)
}
}
impl Sub<BinaryField128> for Ghash128 {
type Output = Self;
#[inline]
fn sub(self, rhs: BinaryField128) -> Self {
self - Self::from(rhs)
}
}
impl Mul<BinaryField128> for Ghash128 {
type Output = Self;
#[inline]
fn mul(self, rhs: BinaryField128) -> Self {
self * Self::from(rhs)
}
}
impl Algebra<BinaryField128> for Ghash128 {}
macro_rules! impl_narrow_algebra {
($($field:ty),* $(,)?) => {$(
impl From<$field> for Ghash128 {
#[inline]
fn from(x: $field) -> Self {
Self::from(BinaryField128::from(x))
}
}
impl_add_base_field!(Ghash128, $field);
impl_sub_base_field!(Ghash128, $field);
impl_mul_base_field!(Ghash128, $field);
impl Algebra<$field> for Ghash128 {}
)*};
}
impl_narrow_algebra!(BinaryField8, BinaryField16, BinaryField32, BinaryField64);
impl Add for Ghash128 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn add(self, rhs: Self) -> Self {
Self(self.0 ^ rhs.0)
}
}
impl Sub for Ghash128 {
type Output = Self;
#[inline]
#[allow(clippy::suspicious_arithmetic_impl)]
fn sub(self, rhs: Self) -> Self {
self + rhs
}
}
impl Neg for Ghash128 {
type Output = Self;
#[inline]
fn neg(self) -> Self::Output {
self
}
}
impl Mul for Ghash128 {
type Output = Self;
#[inline]
fn mul(self, rhs: Self) -> Self {
Self(clmul::poly_mul_128(self.0, rhs.0))
}
}
impl_add_assign!(Ghash128);
impl_sub_assign!(Ghash128);
impl_mul_methods!(Ghash128);
impl_div_methods!(Ghash128, Ghash128);
ring_sum!(Ghash128);
impl From<Gf2> for Ghash128 {
#[inline]
fn from(x: Gf2) -> Self {
Self::from_prime_subfield(x)
}
}
impl_add_base_field!(Ghash128, Gf2);
impl_sub_base_field!(Ghash128, Gf2);
impl_mul_base_field!(Ghash128, Gf2);
impl Algebra<Gf2> for Ghash128 {}
#[cfg(test)]
mod tests {
extern crate std;
use std::vec;
use std::vec::Vec;
use p3_field::{Algebra, Field, PrimeCharacteristicRing, RawDataSerializable};
use proptest::prelude::*;
use super::{CANTOR_BASIS, Ghash128};
use crate::tower::TowerLevel;
use crate::{BinaryField8, BinaryField16, BinaryField32, BinaryField64, BinaryField128, Gf2};
#[test]
fn narrow_algebras_match_multiplication_in_the_tower() {
const fn assert_algebra<F: PrimeCharacteristicRing, A: Algebra<F>>() {}
assert_algebra::<BinaryField8, Ghash128>();
assert_algebra::<BinaryField16, Ghash128>();
assert_algebra::<BinaryField32, Ghash128>();
assert_algebra::<BinaryField64, Ghash128>();
assert_algebra::<BinaryField128, Ghash128>();
let x = BinaryField128::from_repr(0x3141_5926_5358_9793_2384_6264_3383_2795);
let x_poly = Ghash128::from(x);
let scalar = BinaryField32::from_repr(0xa5c3_19e7);
assert_eq!(
BinaryField128::from(x_poly * scalar),
x * BinaryField128::from(scalar)
);
}
fn tower(bits: u128) -> BinaryField128 {
BinaryField128::from_repr(bits)
}
#[test]
fn a_tower_table_converts_entry_by_entry_in_its_own_buffer() {
for len in [0, 1, 63, 64, 65, 1000, 2 * super::TABLE_CHUNK + 67] {
let table: Vec<BinaryField128> = (0..len as u128)
.map(|i| tower(i.wrapping_mul(0x9e37_79b9_7f4a_7c15_f39c_c060_5ced_c835) ^ i))
.collect();
let want: Vec<Ghash128> = table.iter().map(|&x| Ghash128::from(x)).collect();
let at = table.as_ptr() as usize;
let got = Ghash128::from_tower_vec(table);
assert_eq!(got, want, "length {len}");
if len > 0 {
assert_eq!(got.as_ptr() as usize, at, "length {len}");
}
}
}
#[test]
fn the_change_of_basis_fixes_the_constants() {
assert_eq!(Ghash128::from(BinaryField128::ZERO), Ghash128::ZERO);
assert_eq!(Ghash128::from(BinaryField128::ONE), Ghash128::ONE);
}
#[test]
fn the_algebra_over_the_tower_fixes_the_prime_subfield() {
const fn assert_algebra_over_the_tower<T: Algebra<BinaryField128>>() {}
assert_algebra_over_the_tower::<Ghash128>();
for bit in [Gf2::ZERO, Gf2::ONE] {
assert_eq!(
Ghash128::from(BinaryField128::from(bit)),
Ghash128::from(bit)
);
}
}
#[test]
fn the_generator_is_the_image_of_the_tower_generator() {
assert_eq!(
Ghash128::GENERATOR,
Ghash128::from(BinaryField128::GENERATOR)
);
}
#[test]
fn the_modulus_reduces_the_way_the_polynomial_says() {
let x = Ghash128::from_repr(2);
let top = Ghash128::from_repr(1 << 127);
assert_eq!(top * x, Ghash128::from_repr(0x87));
assert_eq!(x * x, Ghash128::from_repr(4));
assert_eq!(Ghash128::from_repr(3).square(), Ghash128::from_repr(5));
}
#[test]
fn the_cantor_basis_satisfies_its_recurrence() {
assert_eq!(Ghash128::cantor_basis(0), Ghash128::ONE);
for i in 1..Ghash128::BITS {
let v = Ghash128::cantor_basis(i);
assert_eq!(v.square() + v, Ghash128::cantor_basis(i - 1), "vector {i}");
}
}
#[test]
fn the_cantor_basis_is_the_image_of_the_tower_one() {
for i in 0..Ghash128::BITS {
assert_eq!(
Ghash128::cantor_basis(i),
Ghash128::from(BinaryField128::cantor_basis(i)),
"vector {i}"
);
}
}
#[test]
fn the_cantor_basis_is_linearly_independent() {
let mut rows: Vec<u128> = CANTOR_BASIS.to_vec();
let mut pivots = 0;
for bit in 0..128 {
if let Some(k) = (pivots..rows.len()).find(|&k| (rows[k] >> bit) & 1 == 1) {
rows.swap(pivots, k);
for k in 0..rows.len() {
if k != pivots && (rows[k] >> bit) & 1 == 1 {
rows[k] ^= rows[pivots];
}
}
pivots += 1;
}
}
assert_eq!(pivots, 128, "the Cantor basis is not a basis");
}
#[test]
#[should_panic = "Cantor basis index out of range"]
fn the_cantor_basis_rejects_an_index_beyond_the_field() {
let _vector = Ghash128::cantor_basis(128);
}
#[test]
fn bool_check_matches_the_vanishing_polynomial_at_zero_and_one() {
for x in [Ghash128::ZERO, Ghash128::ONE] {
assert_eq!(x.bool_check(), x * (x - Ghash128::ONE));
}
}
#[test]
fn scaling_by_alpha_agrees_with_the_tower() {
for bits in [0, 1, 2, 0x87, 1 << 127, u128::MAX] {
let x = tower(bits);
assert_eq!(
Ghash128::from(x).mul_alpha(),
Ghash128::from(x.mul_alpha()),
"{bits:#x}"
);
}
}
#[test]
fn zero_vectors_preserve_layout_and_support_growth() {
for len in [0, 1, 33, 1024] {
let mut values = Ghash128::zero_vec(len);
assert_eq!(values.len(), len);
assert!(values.iter().all(|x| *x == Ghash128::ZERO));
values.push(Ghash128::from_repr(7));
values.reserve(100);
assert_eq!(values.pop(), Some(Ghash128::from_repr(7)));
}
}
fn column_walk(columns: &[Ghash128; 128], input: BinaryField128) -> Ghash128 {
let mut output = Ghash128::ZERO;
let bits = input.to_repr();
for (index, &column) in columns.iter().enumerate() {
if (bits >> index) & 1 == 1 {
output += column;
}
}
output
}
#[test]
fn dynamic_coordinate_map_matches_an_independent_bit_column_oracle() {
let columns = core::array::from_fn(|index| Ghash128::from_repr(1u128 << (127 - index)));
let len = 4096 + 63;
let input: Vec<BinaryField128> = (0..len)
.map(|index| BinaryField128::from_repr(1u128 << (index % 128)))
.collect();
let original = input.clone();
let poison = Ghash128::from_repr(u128::MAX);
let mut output = vec![poison; len];
let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
assert_eq!(
input, original,
"the out-of-place map must not modify its source"
);
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
assert!(accepted, "native GFNI must accept a batch at the threshold");
if accepted {
for (index, &value) in output.iter().enumerate() {
assert_eq!(
value,
column_walk(&columns, input[index]),
"input bit {index}"
);
}
} else {
assert!(output.iter().all(|&value| value == poison));
}
}
#[test]
fn dynamic_coordinate_map_declines_short_inputs_without_touching_output() {
let columns = core::array::from_fn(|index| Ghash128::from_repr(index as u128));
let input = vec![BinaryField128::from_repr(7); 4095];
let poison = Ghash128::from_repr(u128::MAX);
let mut output = vec![poison; input.len()];
assert!(!Ghash128::try_apply_linear_map_into(
&columns,
&input,
&mut output
));
assert!(output.iter().all(|&value| value == poison));
}
#[test]
fn dynamic_coordinate_map_handles_empty_and_dispatch_boundaries() {
let columns = core::array::from_fn(|index| Ghash128::from_repr(1u128 << (127 - index)));
for len in [0, 4095, 4096, 4097] {
let input = (0..len)
.map(|index| BinaryField128::from_repr(1u128 << (index % 128)))
.collect::<Vec<_>>();
let poison = Ghash128::from_repr(u128::MAX);
let mut output = vec![poison; len];
let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
assert_eq!(accepted, len >= 4096, "length {len}");
#[cfg(not(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
)))]
assert!(!accepted, "portable length {len}");
if accepted {
let expected = input
.iter()
.copied()
.map(|value| column_walk(&columns, value))
.collect::<Vec<_>>();
assert_eq!(output, expected, "length {len}");
} else {
assert!(output.iter().all(|&value| value == poison), "length {len}");
}
}
}
#[test]
fn dynamic_coordinate_map_handles_singular_and_mixed_columns() {
let maps = [
[Ghash128::ZERO; 128],
core::array::from_fn(|index| Ghash128::from_repr(1u128 << index)),
core::array::from_fn(|index| Ghash128::from_repr(1u128 << (index % 7))),
core::array::from_fn(|index| {
Ghash128::from_repr(
(index as u128).wrapping_mul(0x9e37_79b9_7f4a_7c15)
^ (0xD1B5_4A32_1C6E_8F09u128.rotate_left(index as u32)),
)
}),
];
let len = 4096 + 17;
let input = (0..len)
.map(|index| {
BinaryField128::from_repr(
(index as u128).wrapping_mul(0xA5A5_5A5A_3141_5926) ^ (1u128 << (index % 128)),
)
})
.collect::<Vec<_>>();
for columns in maps {
let poison = Ghash128::from_repr(u128::MAX);
let mut output = vec![poison; input.len()];
let accepted = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
assert!(accepted);
if accepted {
let expected = input
.iter()
.copied()
.map(|value| column_walk(&columns, value))
.collect::<Vec<_>>();
assert_eq!(output, expected);
} else {
assert!(output.iter().all(|&value| value == poison));
}
}
}
#[test]
fn dynamic_coordinate_map_preserves_offsets_and_destination_guards() {
let columns = core::array::from_fn(|index| {
Ghash128::from_repr((1u128 << (127 - index)) ^ (index as u128))
});
let input_values = (0..4096 + 63)
.map(|index| BinaryField128::from_repr(index as u128 * 17 + 3))
.collect::<Vec<_>>();
let input_offset = 3;
let output_offset = 5;
let guard = Ghash128::from_repr(0x5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A_5A5A);
let mut input = vec![BinaryField128::ZERO; input_offset];
input.extend_from_slice(&input_values);
input.extend(core::iter::repeat_n(BinaryField128::ZERO, 7));
let mut output = vec![guard; output_offset];
output.extend(core::iter::repeat_n(guard, input_values.len()));
output.extend(core::iter::repeat_n(guard, 7));
let input_slice = &input[input_offset..input_offset + input_values.len()];
let output_slice = &mut output[output_offset..output_offset + input_values.len()];
let accepted = Ghash128::try_apply_linear_map_into(&columns, input_slice, output_slice);
#[cfg(all(
target_arch = "x86_64",
target_feature = "gfni",
target_feature = "avx512f",
target_feature = "avx512bw"
))]
assert!(accepted);
assert_eq!(
&input[input_offset..input_offset + input_values.len()],
&input_values
);
assert!(output[..output_offset].iter().all(|&value| value == guard));
assert!(
output[output_offset + input_values.len()..]
.iter()
.all(|&value| value == guard)
);
if accepted {
assert!(
output[output_offset..]
.iter()
.take(input_values.len())
.enumerate()
.all(|(index, &value)| value == column_walk(&columns, input_values[index]))
);
} else {
assert!(
output[output_offset..]
.iter()
.take(input_values.len())
.all(|&value| value == guard)
);
}
}
#[test]
#[should_panic(expected = "equal lengths")]
fn dynamic_coordinate_map_rejects_mismatched_shapes() {
let columns = [Ghash128::ZERO; 128];
let input = [BinaryField128::ZERO; 1];
let mut output = [Ghash128::ZERO; 0];
let _ = Ghash128::try_apply_linear_map_into(&columns, &input, &mut output);
}
#[test]
fn zero_inverts_to_zero() {
assert_eq!(Ghash128::ZERO.invert_or_zero(), Ghash128::ZERO);
assert_eq!(Ghash128::ZERO.try_inverse(), None);
assert_eq!(Ghash128::ONE.invert_or_zero(), Ghash128::ONE);
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(2000))]
#[test]
fn the_change_of_basis_round_trips(bits: u128) {
let x = Ghash128::from_repr(bits);
prop_assert_eq!(Ghash128::from(BinaryField128::from(x)), x);
}
#[test]
fn the_change_of_basis_is_a_ring_isomorphism(a: u128, b: u128) {
let (x, y) = (tower(a), tower(b));
prop_assert_eq!(Ghash128::from(x + y), Ghash128::from(x) + Ghash128::from(y));
prop_assert_eq!(Ghash128::from(x * y), Ghash128::from(x) * Ghash128::from(y));
}
#[test]
fn the_tower_acts_through_the_change_of_basis(a: u128, b: u128) {
let (g, t) = (Ghash128::from_repr(a), tower(b));
let converted = Ghash128::from(t);
prop_assert_eq!(g + t, g + converted);
prop_assert_eq!(g - t, g - converted);
prop_assert_eq!(g * t, g * converted);
let mut acc = g;
acc += t;
prop_assert_eq!(acc, g + converted);
let mut acc = g;
acc -= t;
prop_assert_eq!(acc, g - converted);
let mut acc = g;
acc *= t;
prop_assert_eq!(acc, g * converted);
let tower_g = BinaryField128::from(g);
prop_assert_eq!(BinaryField128::from(g + t), tower_g + t);
prop_assert_eq!(BinaryField128::from(g - t), tower_g - t);
prop_assert_eq!(BinaryField128::from(g * t), tower_g * t);
}
#[test]
fn squaring_agrees_with_multiplying_by_self(bits: u128) {
let x = Ghash128::from_repr(bits);
prop_assert_eq!(x.square(), x * x);
}
#[test]
fn bool_check_agrees_with_the_vanishing_polynomial(bits: u128) {
let x = Ghash128::from_repr(bits);
prop_assert_eq!(x.bool_check(), x * (x - Ghash128::ONE));
}
#[test]
fn a_nonzero_element_times_its_inverse_is_one(bits: u128) {
let x = Ghash128::from_repr(bits);
match x.try_inverse() {
Some(inverse) => prop_assert_eq!(x * inverse, Ghash128::ONE),
None => prop_assert_eq!(x, Ghash128::ZERO),
}
}
#[test]
fn invert_or_zero_agrees_with_try_inverse(bits: u128) {
let x = Ghash128::from_repr(bits);
prop_assert_eq!(
x.invert_or_zero(),
x.try_inverse().unwrap_or(Ghash128::ZERO)
);
}
#[test]
fn the_square_root_squares_back(bits: u128) {
let x = Ghash128::from_repr(bits);
let root = x.try_sqrt().expect("every element of a binary field is a square");
prop_assert_eq!(root.square(), x);
}
#[test]
fn the_prime_subfield_embeds_as_zero_and_one(bit: bool) {
let embedded = Ghash128::from(Gf2::from_bool(bit));
prop_assert_eq!(embedded, Ghash128::from_bool(bit));
}
#[test]
fn the_byte_stream_reads_back_what_the_element_wrote(bits: u128) {
let x = Ghash128::from_repr(bits);
prop_assert_eq!(Ghash128::from_le_byte_iter(x.into_bytes().into_iter()), x);
}
}
#[test]
#[should_panic = "byte stream ended before a whole element was read"]
fn a_truncated_byte_stream_is_rejected() {
let _element = Ghash128::from_le_byte_iter([0u8; 15].into_iter());
}
#[test]
fn serde_accepts_every_bit_pattern() {
for bits in [0u128, 1, u128::MAX, 1 << 127] {
let x = Ghash128::from_repr(bits);
let encoded = serde_json::to_string(&x).unwrap();
assert_eq!(serde_json::from_str::<Ghash128>(&encoded).unwrap(), x);
}
}
}