Skip to main content

binius_field/packed_fields/
ghash.rs

1// Copyright 2024-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use crate::{
5	Ghash128b,
6	arch::{
7		GhashInvert1x, GhashInvert2x, GhashInvert4x, GhashMulX1x, GhashMulX2x, GhashMulX4x,
8		GhashSquare1x, GhashSquare2x, GhashSquare4x, GhashWideMul1x, GhashWideMul2x,
9		GhashWideMul4x, M128, M256, M512, portable::packed_macros::*,
10	},
11	arithmetic_traits::impl_mul_x_with,
12};
13
14define_packed_binary_field!(
15	PackedGhash1x128b,
16	Ghash128b,
17	M128,
18	(GhashSquare1x),
19	(GhashInvert1x),
20	(GhashWideMul1x)
21);
22
23define_packed_binary_field!(
24	PackedGhash2x128b,
25	Ghash128b,
26	M256,
27	(GhashSquare2x),
28	(GhashInvert2x),
29	(GhashWideMul2x)
30);
31
32define_packed_binary_field!(
33	PackedGhash4x128b,
34	Ghash128b,
35	M512,
36	(GhashSquare4x),
37	(GhashInvert4x),
38	(GhashWideMul4x)
39);
40
41// Scaling by `X` is not a multiply, so it wires through its own strategy rather than a slot of the
42// packing definition -- only the GHASH packings have one.
43impl_mul_x_with!(PackedGhash1x128b @ GhashMulX1x);
44impl_mul_x_with!(PackedGhash2x128b @ GhashMulX2x);
45impl_mul_x_with!(PackedGhash4x128b @ GhashMulX4x);
46
47#[cfg(test)]
48mod tests {
49	use proptest::{arbitrary::any, proptest};
50
51	use super::*;
52	use crate::{
53		Ghash128b, MulX, PackedField, packed_fields::test_utils::packed_field_tests,
54		underlier::UnderlierView,
55	};
56
57	fn check_get_set<const WIDTH: usize, PT>(a: [u128; WIDTH], b: [u128; WIDTH])
58	where
59		PT: PackedField<Scalar = Ghash128b> + UnderlierView<Underlier: From<[u128; WIDTH]>>,
60	{
61		let mut val = PT::from_underlier(a.into());
62		for i in 0..WIDTH {
63			assert_eq!(val.get(i), Ghash128b::from(a[i]));
64			val.set(i, Ghash128b::from(b[i]));
65			assert_eq!(val.get(i), Ghash128b::from(b[i]));
66		}
67	}
68
69	/// Scaling by `X` must agree with multiplying by the field element `X` in every lane.
70	///
71	/// The multiply is an independent oracle: it runs the product and the modular reduction, none
72	/// of which the scaling touches.
73	fn check_mul_x<P>(underlier: P::Underlier)
74	where
75		P: PackedField<Scalar = Ghash128b> + UnderlierView + MulX,
76	{
77		let packed = P::from_underlier(underlier);
78		let scaled = packed.mul_x();
79		let x = Ghash128b::new(2);
80
81		for i in 0..P::WIDTH {
82			assert_eq!(scaled.get(i), packed.get(i) * x, "lane {i}");
83		}
84	}
85
86	proptest! {
87		#[test]
88		fn test_get_set_256(a in any::<[u128; 2]>(), b in any::<[u128; 2]>()) {
89			check_get_set::<2, PackedGhash2x128b>(a, b);
90		}
91
92		#[test]
93		fn test_get_set_512(a in any::<[u128; 4]>(), b in any::<[u128; 4]>()) {
94			check_get_set::<4, PackedGhash4x128b>(a, b);
95		}
96
97		#[test]
98		#[allow(clippy::useless_conversion)] // the conversion depends on the target platform
99		fn mul_x_is_multiplication_by_x_1x(a in any::<u128>()) {
100			check_mul_x::<PackedGhash1x128b>(a.into());
101		}
102
103		#[test]
104		fn mul_x_is_multiplication_by_x_2x(a in any::<[u128; 2]>()) {
105			check_mul_x::<PackedGhash2x128b>(a.into());
106		}
107
108		#[test]
109		fn mul_x_is_multiplication_by_x_4x(a in any::<[u128; 4]>()) {
110			check_mul_x::<PackedGhash4x128b>(a.into());
111		}
112	}
113
114	packed_field_tests!(ghash_1x128b, PackedGhash1x128b);
115	packed_field_tests!(ghash_2x128b, PackedGhash2x128b);
116	packed_field_tests!(ghash_4x128b, PackedGhash4x128b);
117
118	#[test]
119	fn test_wide_mul_zero_inputs() {
120		use super::PackedGhash1x128b as P;
121		use crate::{WideMul, field::FieldOps};
122
123		let zero = P::default();
124		let one = P::one();
125
126		assert_eq!(P::reduce(P::wide_mul(zero, zero)), zero);
127		assert_eq!(P::reduce(P::wide_mul(zero, one)), zero);
128		assert_eq!(P::reduce(P::wide_mul(one, zero)), zero);
129		assert_eq!(P::reduce(P::wide_mul(one, one)), one);
130
131		let wide_zero = <P as WideMul>::Output::default();
132		assert_eq!(P::reduce(wide_zero), zero);
133	}
134
135	#[test]
136	fn test_wide_mul_single_accumulation() {
137		use rand::{SeedableRng, rngs::StdRng};
138
139		use super::PackedGhash1x128b as P;
140		use crate::{Random, WideMul};
141
142		let mut rng = StdRng::seed_from_u64(77);
143		let a = P::random(&mut rng);
144		let b = P::random(&mut rng);
145
146		let wide = P::wide_mul(a, b);
147		let sum = wide + <P as WideMul>::Output::default();
148		assert_eq!(P::reduce(sum), a * b);
149	}
150}