Skip to main content

binius_field/fields/
ghash_sq.rs

1// Copyright 2026 The Binius Developers
2
3//! Binary field implementation of GF(2^256) as a degree-two extension of the GHASH field.
4//!
5//! Elements are pairs `(a, b)` representing `a + b·Y`, where `a` and `b` are elements of
6//! [`Ghash128b`]. The extension is defined by the irreducible polynomial
7//! `Y² + X·Y + X` over GHASH, so that `Y² = X·Y + X`.
8//!
9//! The field is backed by [`M256`], with the low 128 bits holding the coefficient of `1` (`a`) and
10//! the high 128 bits holding the coefficient of `Y` (`b`). This is the same layout as
11//! [`PackedGhash2x128b`](crate::PackedGhash2x128b) (two GHASH lanes in an `M256`) and
12//! matches the `{1, Y}` basis used by the `ExtensionField<Ghash128b>` implementation.
13//!
14//! Reducing with `Y² = X·Y + X` multiplies by `X` (a left shift) rather than by `X⁻¹`, and the
15//! multiply-by-`X` folds into the reduction. Multiplication batches the two GHASH products that
16//! share the AVX2 256-bit CLMUL into a single
17//! [`PackedGhash2x128b`](crate::PackedGhash2x128b) multiply (the `mul_m256i_hybrid`
18//! algorithm).
19
20use std::{
21	fmt::{Debug, Display, Formatter},
22	iter::{Product, Sum},
23	ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
24};
25
26use bytemuck::{Pod, Zeroable};
27
28use crate::{
29	Field, Ghash128b,
30	arch::{M128, M256, m256_from_u128s},
31	binary_field::{BinaryField, BinaryField1b, binary_field, impl_field_extension},
32	field::ExtensionField,
33	underlier::U1,
34};
35
36// The multiplicative generator is `Y` (low 128 bits = 0, high 128 bits = 1).
37// `tests::test_multiplicative_generator` verifies it generates GF(2^256)* against the known
38// factorization of 2^256 - 1.
39//
40// The field's `Mul`/`Square`/`InvertOrZero`/`WideMul` are the width-one packing's arithmetic,
41// derived by the `binary_field!` macro from [`PackedGhashSq1x256b`] (`PackedPrimitiveType<M256,
42// GhashSq256b>`), whose implementation lives in `packed_fields::ghash_sq`.
43// Bit 248 is the lowest single-bit element of trace 1; bit 254 is the only other one. Bit 248 sits
44// at position 120 of the high word.
45binary_field!(pub GhashSq256b(M256), m256_from_u128s(0, 1), m256_from_u128s(0, 1 << 120));
46
47unsafe impl Pod for GhashSq256b {}
48
49// Degree-two extension over GHASH: the low 128 bits are the coefficient of `1`, the high 128 bits
50// the coefficient of `Y`. `square_transpose` uses the packed fast path via
51// `PackedGhash2x128b`.
52impl_field_extension!(Ghash128b(M128) < @1 => GhashSq256b(M256));
53
54// Extension over GF(2): the 256 underlier bits are the coordinates in the `BinaryField1b` basis.
55impl_field_extension!(BinaryField1b(U1) < @8 => GhashSq256b(M256));
56
57#[cfg(test)]
58mod tests {
59	use binius_utils::{DeserializeBytes, FixedSizeSerializeBytes, SerializeBytes};
60	use proptest::prelude::*;
61
62	use super::*;
63	use crate::{
64		Divisible, PackedField, PackedGhash2x128b,
65		arithmetic_traits::{InvertOrZero, Square, WideMul},
66	};
67
68	impl GhashSq256b {
69		/// Splits the element into its `(a, b)` coefficients over GHASH, where `self = a + b·Y`.
70		#[inline]
71		fn to_coeffs(self) -> [Ghash128b; 2] {
72			// `GhashSq256b` and `PackedGhash2x128b` share the `M256` underlier and lane
73			// layout (low lane = coefficient of `1`, high lane = coefficient of `Y`), so this
74			// reinterprets.
75			let packed = PackedGhash2x128b::from_underlier(self.0);
76			[packed.get(0), packed.get(1)]
77		}
78
79		/// Builds an element from its `(a, b)` coefficients over GHASH, so that `self = a + b·Y`.
80		#[inline]
81		fn from_coeffs(coeffs: [Ghash128b; 2]) -> Self {
82			Self(PackedGhash2x128b::from_scalars(coeffs).to_underlier())
83		}
84	}
85
86	/// `X`, the generator of the GHASH field in the standard polynomial basis.
87	const GHASH_X: u128 = 0x02;
88
89	/// Prime factorization of `2²⁵⁶ - 1` (the multiplicative group order). These are the
90	/// Fermat-number factors `F₀..F₇`: `2²⁵⁶ - 1 = ∏_{k=0}^{7} (2^{2^k} + 1)`.
91	const ORDER_PRIME_FACTORS: [u128; 11] = [
92		3,
93		5,
94		17,
95		257,
96		65537,
97		641,
98		6700417,
99		274177,
100		67280421310721,
101		59649589127497217,
102		5704689200685129054721,
103	];
104
105	fn ghash_sq(a: u128, b: u128) -> GhashSq256b {
106		GhashSq256b::from_coeffs([Ghash128b::new(a), Ghash128b::new(b)])
107	}
108
109	fn arb_elem() -> impl Strategy<Value = GhashSq256b> {
110		any::<[u128; 2]>().prop_map(|[a, b]| ghash_sq(a, b))
111	}
112
113	/// Independent reference for [`ExtensionField::square_transpose`]: transposes the
114	/// `DEGREE × DEGREE` matrix whose row `i` is the `F`-basis expansion of `values[i]`. Built only
115	/// from the (separately tested) `iter_bases`/`from_bases` accessors, to check the packed
116	/// `square_transpose` fast path.
117	fn naive_square_transpose<F: BinaryField>(values: &[GhashSq256b]) -> Vec<GhashSq256b>
118	where
119		GhashSq256b: ExtensionField<F>,
120	{
121		let degree = GhashSq256b::DEGREE;
122		assert_eq!(values.len(), degree);
123		let coords: Vec<F> = values
124			.iter()
125			.flat_map(|v| ExtensionField::<F>::iter_bases(v))
126			.collect();
127		(0..degree)
128			.map(|i| {
129				<GhashSq256b as ExtensionField<F>>::from_bases(
130					(0..degree).map(|j| coords[j * degree + i]),
131				)
132			})
133			.collect()
134	}
135
136	/// Computes `(2²⁵⁶ - 1) / p` as little-endian `u64` limbs via bit-by-bit long division. The
137	/// remainder stays below `p` (≤ 73 bits), so it fits in a `u128`.
138	fn order_cofactor(p: u128) -> [u64; 4] {
139		let mut quotient = [0u64; 4];
140		let mut rem: u128 = 0;
141		// The dividend `2²⁵⁶ - 1` is 256 set bits, processed most-significant first.
142		for bit in (0..256).rev() {
143			rem = (rem << 1) | 1;
144			let q_bit = if rem >= p {
145				rem -= p;
146				1u64
147			} else {
148				0
149			};
150			quotient[bit / 64] |= q_bit << (bit % 64);
151		}
152		quotient
153	}
154
155	/// A nonzero element generates the full multiplicative group iff `g^((2²⁵⁶-1)/p) ≠ 1` for
156	/// every prime `p` dividing the group order.
157	fn is_generator(g: GhashSq256b) -> bool {
158		ORDER_PRIME_FACTORS
159			.iter()
160			.all(|&p| Field::pow(&g, order_cofactor(p)) != GhashSq256b::ONE)
161	}
162
163	#[test]
164	fn test_quadratic_relation() {
165		// The extension is defined by `Y² + X·Y + X = 0`, i.e. `Y² = X·Y + X`, whose coordinates in
166		// the `{1, Y}` basis are `(X, X)`.
167		let y = ghash_sq(0, 1);
168		assert_eq!(y * y, ghash_sq(GHASH_X, GHASH_X));
169	}
170
171	#[test]
172	fn test_subfield_embedding() {
173		// Products of GHASH-subfield elements agree with GHASH multiplication.
174		let a = Ghash128b::new(0x0123456789abcdef0123456789abcdef);
175		let b = Ghash128b::new(0xfedcba9876543210fedcba9876543210);
176		assert_eq!(GhashSq256b::from(a) * GhashSq256b::from(b), GhashSq256b::from(a * b),);
177	}
178
179	#[test]
180	fn test_multiplicative_generator() {
181		assert!(
182			is_generator(GhashSq256b::MULTIPLICATIVE_GENERATOR),
183			"baked MULTIPLICATIVE_GENERATOR is not a generator of GF(2^256)*",
184		);
185	}
186
187	#[test]
188	#[ignore = "search utility: prints a valid generator literal to bake into the field"]
189	fn find_generator() {
190		for b in 1u128..256 {
191			for a in 0u128..256 {
192				let candidate = ghash_sq(a, b);
193				if is_generator(candidate) {
194					let [low, high] = candidate.to_coeffs();
195					panic!(
196						"found generator: a={a:#x}, b={b:#x} -> m256_from_u128s({:#034x}, {:#034x})",
197						u128::from(low.val()),
198						u128::from(high.val()),
199					);
200				}
201			}
202		}
203		panic!("no generator found in search range");
204	}
205
206	proptest! {
207		#[test]
208		fn test_mul_commutative(a in arb_elem(), b in arb_elem()) {
209			prop_assert_eq!(a * b, b * a);
210		}
211
212		#[test]
213		fn test_mul_associative(a in arb_elem(), b in arb_elem(), c in arb_elem()) {
214			prop_assert_eq!((a * b) * c, a * (b * c));
215		}
216
217		#[test]
218		fn test_mul_distributive(a in arb_elem(), b in arb_elem(), c in arb_elem()) {
219			prop_assert_eq!(a * (b + c), a * b + a * c);
220		}
221
222		#[test]
223		fn test_mul_identity(a in arb_elem()) {
224			prop_assert_eq!(a * GhashSq256b::ONE, a);
225		}
226
227		#[test]
228		fn test_subfield_scalar_mul(a in arb_elem(), scalar in any::<u128>()) {
229			// `impl_field_extension!` derives `Mul<Ghash128b> for GhashSq256b` from the
230			// underlier's packing; it must agree with multiplying by the embedded full-field
231			// element, and with scaling each GHASH coordinate independently.
232			let scalar = Ghash128b::new(scalar);
233			prop_assert_eq!(a * scalar, a * GhashSq256b::from(scalar));
234			let [x, y] = a.to_coeffs();
235			prop_assert_eq!(a * scalar, GhashSq256b::from_coeffs([x * scalar, y * scalar]));
236		}
237
238		#[test]
239		fn test_square_equals_mul(a in arb_elem()) {
240			prop_assert_eq!(Square::square(a), a * a);
241		}
242
243		#[test]
244		fn test_wide_mul_deferred_reduction(
245			pairs in prop::collection::vec((arb_elem(), arb_elem()), 1..16),
246		) {
247			// Inner product over GHASH^2: the deferred form must equal the eager form.
248			//
249			//     eager:    sum_i reduce(wide_mul(a_i, b_i))  =  sum_i a_i * b_i
250			//     deferred: reduce( sum_i wide_mul(a_i, b_i) )
251			//
252			// This holds because the GHASH reduction and the multiply-by-`X` are both GF(2)-linear.
253			// So a sum of products costs one reduction, not one per term.
254			let eager: GhashSq256b = pairs.iter().map(|&(a, b)| a * b).sum();
255			let deferred = GhashSq256b::reduce(
256				pairs.iter().map(|&(a, b)| GhashSq256b::wide_mul(a, b)).sum(),
257			);
258			prop_assert_eq!(deferred, eager);
259		}
260
261		#[test]
262		fn test_invert(a in arb_elem()) {
263			let inv = a.invert_or_zero();
264			if a == GhashSq256b::ZERO {
265				prop_assert_eq!(inv, GhashSq256b::ZERO);
266			} else {
267				prop_assert_eq!(a * inv, GhashSq256b::ONE);
268			}
269		}
270
271		#[test]
272		fn test_serialization_roundtrip(a in arb_elem()) {
273			let mut buf = Vec::new();
274			a.serialize(&mut buf).unwrap();
275			prop_assert_eq!(buf.len(), GhashSq256b::BYTE_SIZE);
276			let b = GhashSq256b::deserialize(buf.as_slice()).unwrap();
277			prop_assert_eq!(a, b);
278		}
279
280		#[test]
281		fn test_ghash_extension_bases_roundtrip(a in arb_elem()) {
282			let bases: Vec<Ghash128b> =
283				ExtensionField::<Ghash128b>::iter_bases(&a).collect();
284			prop_assert_eq!(bases.len(), 2);
285			prop_assert_eq!(
286				<GhashSq256b as ExtensionField<Ghash128b>>::from_bases(bases),
287				a,
288			);
289		}
290
291		#[test]
292		fn test_b1b_extension_bases_roundtrip(a in arb_elem()) {
293			let bases: Vec<BinaryField1b> =
294				ExtensionField::<BinaryField1b>::iter_bases(&a).collect();
295			prop_assert_eq!(bases.len(), 256);
296			prop_assert_eq!(
297				<GhashSq256b as ExtensionField<BinaryField1b>>::from_bases(bases),
298				a,
299			);
300		}
301
302		#[test]
303		fn test_square_transpose_ghash(a in arb_elem(), b in arb_elem()) {
304			let mut values = [a, b];
305			let expected = naive_square_transpose::<Ghash128b>(&values);
306			<GhashSq256b as ExtensionField<Ghash128b>>::square_transpose(&mut values);
307			prop_assert_eq!(values.as_slice(), expected.as_slice());
308		}
309
310		#[test]
311		fn test_square_transpose_b1b(values in prop::collection::vec(arb_elem(), 256)) {
312			let mut values = values;
313			let expected = naive_square_transpose::<BinaryField1b>(&values);
314			<GhashSq256b as ExtensionField<BinaryField1b>>::square_transpose(&mut values);
315			prop_assert_eq!(values, expected);
316		}
317	}
318}