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