Skip to main content

binius_field/
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//! [`BinaryField128bGhash`]. The extension is defined by the irreducible polynomial
7//! `Y² + Y + X⁻¹` over GHASH, so that `Y² = 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//! [`PackedBinaryGhash2x128b`] (two GHASH lanes in an `M256`) and matches the `{1, Y}` basis used
12//! by the `ExtensionField<BinaryField128bGhash>` implementation.
13//!
14//! Multiplication uses the `mul_m256i_hybrid` algorithm from the `binius_arith_bench::ghash_sq`
15//! module: the two GHASH products that share the AVX2 256-bit CLMUL are batched into a single
16//! [`PackedBinaryGhash2x128b`] multiply.
17
18use std::{
19	fmt::{Debug, Display, Formatter},
20	iter::{Product, Sum},
21	ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
22};
23
24use binius_utils::{
25	DeserializeBytes, FixedSizeSerializeBytes, SerializationError, SerializeBytes,
26	bytes::{Buf, BufMut},
27};
28use bytemuck::{Pod, Zeroable};
29
30use super::{
31	binary_field::{BinaryField, BinaryField1b, TowerField, binary_field, impl_field_extension},
32	extension::ExtensionField,
33};
34use crate::{
35	BinaryField128bGhash, Divisible, Field, PackedBinaryGhash2x128b, PackedField,
36	arch::{M128, M256, packed_256::m256_from_u128s},
37	arithmetic_traits::{InvertOrZero, Square, impl_trivial_wide_mul},
38	binary_field_arithmetic::TowerFieldArithmetic,
39	mul_by_binary_field_1b,
40	underlier::U1,
41};
42
43// The multiplicative generator is `Y` (low 128 bits = 0, high 128 bits = 1).
44// `tests::test_multiplicative_generator` verifies it generates GF(2^256)* against the known
45// factorization of 2^256 - 1.
46binary_field!(pub GhashSq256b(M256), m256_from_u128s(0, 1));
47
48unsafe impl Pod for GhashSq256b {}
49
50impl GhashSq256b {
51	/// Splits the element into its `(a, b)` coefficients over GHASH, where `self = a + b·Y`.
52	#[inline]
53	fn to_coeffs(self) -> [BinaryField128bGhash; 2] {
54		// `GhashSq256b` and `PackedBinaryGhash2x128b` share the `M256` underlier and lane layout
55		// (low lane = coefficient of `1`, high lane = coefficient of `Y`), so this reinterprets.
56		let packed = PackedBinaryGhash2x128b::from_underlier(self.0);
57		[packed.get(0), packed.get(1)]
58	}
59
60	/// Builds an element from its `(a, b)` coefficients over GHASH, so that `self = a + b·Y`.
61	#[inline]
62	fn from_coeffs(coeffs: [BinaryField128bGhash; 2]) -> Self {
63		Self(PackedBinaryGhash2x128b::from_scalars(coeffs).to_underlier())
64	}
65}
66
67/// Multiplies two GHASH² elements via Karatsuba over GHASH.
68///
69/// For `x = x₀ + x₁·Y` and `y = y₀ + y₁·Y`, with `Y² = Y + X⁻¹`:
70/// `z₀ = x₀·y₀ + (x₁·y₁)·X⁻¹`, `z₁ = (x₀+x₁)·(y₀+y₁) + x₀·y₀`.
71///
72/// The products `t₀ = x₀·y₀` and `t₂ = x₁·y₁` are computed together as a single packed
73/// [`PackedBinaryGhash2x128b`] multiply. On AVX2 this lowers to one `vpclmulqdq` over both
74/// 128-bit lanes — the `mul_m256i_hybrid` algorithm — while remaining portable on other targets.
75/// The third product `t₁` is a scalar GHASH multiply.
76#[inline]
77fn mul(x: [BinaryField128bGhash; 2], y: [BinaryField128bGhash; 2]) -> [BinaryField128bGhash; 2] {
78	let [x0, x1] = x;
79	let [y0, y1] = y;
80
81	let t0_t2 = PackedBinaryGhash2x128b::from_scalars([x0, x1])
82		* PackedBinaryGhash2x128b::from_scalars([y0, y1]);
83	let t0 = t0_t2.get(0);
84	let t2 = t0_t2.get(1);
85	let t1 = (x0 + x1) * (y0 + y1);
86
87	[t0 + t2.mul_inv_x(), t1 + t0]
88}
89
90/// Squares a GHASH² element.
91///
92/// For `x = x₀ + x₁·Y`: `(x₀ + x₁·Y)² = (x₀² + x₁²·X⁻¹) + x₁²·Y` (the cross term vanishes in
93/// characteristic two). The squares `x₀²` and `x₁²` are computed with a single packed square.
94#[inline]
95fn square(x: [BinaryField128bGhash; 2]) -> [BinaryField128bGhash; 2] {
96	let t0_t2 = Square::square(PackedBinaryGhash2x128b::from_scalars(x));
97	let t0 = t0_t2.get(0);
98	let t2 = t0_t2.get(1);
99
100	[t0 + t2.mul_inv_x(), t2]
101}
102
103impl TowerField for GhashSq256b {
104	fn min_tower_level(self) -> usize {
105		let [a, b] = self.to_coeffs();
106		if self == Self::ZERO || self == Self::ONE {
107			0
108		} else if b == BinaryField128bGhash::ZERO {
109			// Elements with no `Y` component lie in the GHASH subfield (tower level 7).
110			a.min_tower_level()
111		} else {
112			8
113		}
114	}
115}
116
117impl TowerFieldArithmetic for GhashSq256b {
118	fn multiply(self, rhs: Self) -> Self {
119		Self::from_coeffs(mul(self.to_coeffs(), rhs.to_coeffs()))
120	}
121
122	fn square(self) -> Self {
123		Self::from_coeffs(square(self.to_coeffs()))
124	}
125}
126
127impl InvertOrZero for GhashSq256b {
128	#[inline]
129	fn invert_or_zero(self) -> Self {
130		// For `u = a + b·Y`, the nontrivial automorphism sends `Y -> Y + 1` (the other root of
131		// `Y² + Y + X⁻¹`), so the conjugate is `ū = (a + b) + b·Y`. The norm
132		// `N = u·ū = a² + a·b + b²·X⁻¹` lies in GHASH, and `u⁻¹ = ū·N⁻¹`.
133		let [a, b] = self.to_coeffs();
134
135		let norm = Square::square(a) + a * b + Square::square(b).mul_inv_x();
136		let norm_inv = norm.invert_or_zero();
137
138		let inv_a = (a + b) * norm_inv;
139		let inv_b = b * norm_inv;
140		Self::from_coeffs([inv_a, inv_b])
141	}
142}
143
144impl_trivial_wide_mul!(GhashSq256b);
145
146// Degree-two extension over GHASH: the low 128 bits are the coefficient of `1`, the high 128 bits
147// the coefficient of `Y`. `square_transpose` uses the packed fast path via
148// `PackedBinaryGhash2x128b`.
149impl_field_extension!(BinaryField128bGhash(M128) < @1 => GhashSq256b(M256));
150
151// Extension over GF(2): the 256 underlier bits are the coordinates in the `BinaryField1b` basis.
152impl_field_extension!(BinaryField1b(U1) < @8 => GhashSq256b(M256));
153
154mul_by_binary_field_1b!(GhashSq256b);
155
156// Scalar multiplication by a GHASH subfield element scales both extension coordinates.
157impl Mul<BinaryField128bGhash> for GhashSq256b {
158	type Output = Self;
159
160	#[inline]
161	fn mul(self, rhs: BinaryField128bGhash) -> Self::Output {
162		let [a, b] = self.to_coeffs();
163		Self::from_coeffs([a * rhs, b * rhs])
164	}
165}
166
167impl SerializeBytes for GhashSq256b {
168	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
169		self.0.serialize(write_buf)
170	}
171}
172
173impl DeserializeBytes for GhashSq256b {
174	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
175	where
176		Self: Sized,
177	{
178		Ok(Self(DeserializeBytes::deserialize(read_buf)?))
179	}
180}
181
182impl FixedSizeSerializeBytes for GhashSq256b {
183	const BYTE_SIZE: usize = 32;
184}
185
186#[cfg(test)]
187mod tests {
188	use proptest::prelude::*;
189
190	use super::*;
191
192	/// `X⁻¹` in the GHASH field, i.e. `0x43 + x^127`. Equals `Y² + Y`.
193	const GHASH_INV_X: u128 = 0x80000000000000000000000000000043;
194
195	/// Prime factorization of `2²⁵⁶ - 1` (the multiplicative group order). These are the
196	/// Fermat-number factors `F₀..F₇`: `2²⁵⁶ - 1 = ∏_{k=0}^{7} (2^{2^k} + 1)`.
197	const ORDER_PRIME_FACTORS: [u128; 11] = [
198		3,
199		5,
200		17,
201		257,
202		65537,
203		641,
204		6700417,
205		274177,
206		67280421310721,
207		59649589127497217,
208		5704689200685129054721,
209	];
210
211	fn ghash_sq(a: u128, b: u128) -> GhashSq256b {
212		GhashSq256b::from_coeffs([BinaryField128bGhash::new(a), BinaryField128bGhash::new(b)])
213	}
214
215	fn arb_elem() -> impl Strategy<Value = GhashSq256b> {
216		any::<[u128; 2]>().prop_map(|[a, b]| ghash_sq(a, b))
217	}
218
219	/// Independent reference for [`ExtensionField::square_transpose`]: transposes the
220	/// `DEGREE × DEGREE` matrix whose row `i` is the `F`-basis expansion of `values[i]`. Built only
221	/// from the (separately tested) `iter_bases`/`from_bases` accessors, to check the packed
222	/// `square_transpose` fast path.
223	fn naive_square_transpose<F: BinaryField>(values: &[GhashSq256b]) -> Vec<GhashSq256b>
224	where
225		GhashSq256b: ExtensionField<F>,
226	{
227		let degree = <GhashSq256b as ExtensionField<F>>::DEGREE;
228		assert_eq!(values.len(), degree);
229		let coords: Vec<F> = values
230			.iter()
231			.flat_map(|v| ExtensionField::<F>::iter_bases(v))
232			.collect();
233		(0..degree)
234			.map(|i| {
235				<GhashSq256b as ExtensionField<F>>::from_bases(
236					(0..degree).map(|j| coords[j * degree + i]),
237				)
238			})
239			.collect()
240	}
241
242	/// Computes `(2²⁵⁶ - 1) / p` as little-endian `u64` limbs via bit-by-bit long division. The
243	/// remainder stays below `p` (≤ 73 bits), so it fits in a `u128`.
244	fn order_cofactor(p: u128) -> [u64; 4] {
245		let mut quotient = [0u64; 4];
246		let mut rem: u128 = 0;
247		// The dividend `2²⁵⁶ - 1` is 256 set bits, processed most-significant first.
248		for bit in (0..256).rev() {
249			rem = (rem << 1) | 1;
250			let q_bit = if rem >= p {
251				rem -= p;
252				1u64
253			} else {
254				0
255			};
256			quotient[bit / 64] |= q_bit << (bit % 64);
257		}
258		quotient
259	}
260
261	/// A nonzero element generates the full multiplicative group iff `g^((2²⁵⁶-1)/p) ≠ 1` for
262	/// every prime `p` dividing the group order.
263	fn is_generator(g: GhashSq256b) -> bool {
264		ORDER_PRIME_FACTORS
265			.iter()
266			.all(|&p| Field::pow(&g, order_cofactor(p)) != GhashSq256b::ONE)
267	}
268
269	#[test]
270	fn test_quadratic_relation() {
271		// The extension is defined by `Y² + Y + X⁻¹ = 0`, i.e. `Y² = Y + X⁻¹`.
272		let y = ghash_sq(0, 1);
273		assert_eq!(y * y, y + ghash_sq(GHASH_INV_X, 0));
274	}
275
276	#[test]
277	fn test_subfield_embedding() {
278		// Products of GHASH-subfield elements agree with GHASH multiplication.
279		let a = BinaryField128bGhash::new(0x0123456789abcdef0123456789abcdef);
280		let b = BinaryField128bGhash::new(0xfedcba9876543210fedcba9876543210);
281		assert_eq!(GhashSq256b::from(a) * GhashSq256b::from(b), GhashSq256b::from(a * b),);
282	}
283
284	#[test]
285	fn test_multiplicative_generator() {
286		assert!(
287			is_generator(GhashSq256b::MULTIPLICATIVE_GENERATOR),
288			"baked MULTIPLICATIVE_GENERATOR is not a generator of GF(2^256)*",
289		);
290	}
291
292	#[test]
293	#[ignore = "search utility: prints a valid generator literal to bake into the field"]
294	fn find_generator() {
295		for b in 1u128..256 {
296			for a in 0u128..256 {
297				let candidate = ghash_sq(a, b);
298				if is_generator(candidate) {
299					let [low, high] = candidate.to_coeffs();
300					panic!(
301						"found generator: a={a:#x}, b={b:#x} -> m256_from_u128s({:#034x}, {:#034x})",
302						u128::from(low.val()),
303						u128::from(high.val()),
304					);
305				}
306			}
307		}
308		panic!("no generator found in search range");
309	}
310
311	proptest! {
312		#[test]
313		fn test_mul_commutative(a in arb_elem(), b in arb_elem()) {
314			prop_assert_eq!(a * b, b * a);
315		}
316
317		#[test]
318		fn test_mul_associative(a in arb_elem(), b in arb_elem(), c in arb_elem()) {
319			prop_assert_eq!((a * b) * c, a * (b * c));
320		}
321
322		#[test]
323		fn test_mul_distributive(a in arb_elem(), b in arb_elem(), c in arb_elem()) {
324			prop_assert_eq!(a * (b + c), a * b + a * c);
325		}
326
327		#[test]
328		fn test_mul_identity(a in arb_elem()) {
329			prop_assert_eq!(a * GhashSq256b::ONE, a);
330		}
331
332		#[test]
333		fn test_square_equals_mul(a in arb_elem()) {
334			prop_assert_eq!(Square::square(a), a * a);
335		}
336
337		#[test]
338		fn test_invert(a in arb_elem()) {
339			match a.invert() {
340				Some(inv) => prop_assert_eq!(a * inv, GhashSq256b::ONE),
341				None => prop_assert_eq!(a, GhashSq256b::ZERO),
342			}
343		}
344
345		#[test]
346		fn test_serialization_roundtrip(a in arb_elem()) {
347			let mut buf = Vec::new();
348			a.serialize(&mut buf).unwrap();
349			prop_assert_eq!(buf.len(), GhashSq256b::BYTE_SIZE);
350			let b = GhashSq256b::deserialize(buf.as_slice()).unwrap();
351			prop_assert_eq!(a, b);
352		}
353
354		#[test]
355		fn test_ghash_extension_bases_roundtrip(a in arb_elem()) {
356			let bases: Vec<BinaryField128bGhash> =
357				ExtensionField::<BinaryField128bGhash>::iter_bases(&a).collect();
358			prop_assert_eq!(bases.len(), 2);
359			prop_assert_eq!(
360				<GhashSq256b as ExtensionField<BinaryField128bGhash>>::from_bases(bases),
361				a,
362			);
363		}
364
365		#[test]
366		fn test_b1b_extension_bases_roundtrip(a in arb_elem()) {
367			let bases: Vec<BinaryField1b> =
368				ExtensionField::<BinaryField1b>::iter_bases(&a).collect();
369			prop_assert_eq!(bases.len(), 256);
370			prop_assert_eq!(
371				<GhashSq256b as ExtensionField<BinaryField1b>>::from_bases(bases),
372				a,
373			);
374		}
375
376		#[test]
377		fn test_square_transpose_ghash(a in arb_elem(), b in arb_elem()) {
378			let mut values = [a, b];
379			let expected = naive_square_transpose::<BinaryField128bGhash>(&values);
380			<GhashSq256b as ExtensionField<BinaryField128bGhash>>::square_transpose(&mut values);
381			prop_assert_eq!(values.as_slice(), expected.as_slice());
382		}
383
384		#[test]
385		fn test_square_transpose_b1b(values in prop::collection::vec(arb_elem(), 256)) {
386			let mut values = values;
387			let expected = naive_square_transpose::<BinaryField1b>(&values);
388			<GhashSq256b as ExtensionField<BinaryField1b>>::square_transpose(&mut values);
389			prop_assert_eq!(values, expected);
390		}
391	}
392}