Skip to main content

binius_field/arch/portable/arithmetic/
ghash_sq.rs

1// Copyright 2026 The Binius Developers
2
3//! Portable GHASH² widening multiply: three independent GHASH multiplies over the coordinates.
4//!
5//! This is the strategy for targets whose widest carry-less multiply is 128 bits, where batching
6//! the Karatsuba diagonal into a two-lane packed GHASH multiply buys nothing — that packed multiply
7//! decomposes back into two independent 128-bit multiplies, each with its own reduction. Keeping
8//! the three Karatsuba products separate instead lets the multiply-by-`X` of the irreducible
9//! polynomial be applied to an *unreduced* product ([`MulXWide`]), which folds it into a reduction
10//! that has to happen anyway: two GHASH reductions per GHASH² product rather than three.
11
12use bytemuck::TransparentWrapper;
13
14use crate::{
15	BinaryField128bGhash, PackedGhashSq1x256b, SlicedGhashSqWide, WideMul,
16	arithmetic_traits::MulXWide,
17	packed_ghash_sq::{ghash_sq_coords, ghash_sq_from_coords},
18};
19
20/// The unreduced product of a single GHASH coordinate multiply.
21type GhashWide = <BinaryField128bGhash as WideMul>::Output;
22
23/// [`WideMul`] strategy for [`PackedGhashSq1x256b`] keeping the three Karatsuba products of the
24/// coordinate multiply separate, so that the multiply-by-`X` can be deferred into a reduction.
25#[repr(transparent)]
26#[derive(TransparentWrapper)]
27pub struct GhashSqSlicedWideMul<T>(T);
28
29impl WideMul for GhashSqSlicedWideMul<PackedGhashSq1x256b> {
30	type Output = SlicedGhashSqWide<GhashWide>;
31
32	/// Karatsuba over `Y`: defers the three GHASH products `a·e`, `b·f`, `(a+b)·(e+f)`.
33	#[inline]
34	fn wide_mul(a: Self, b: Self) -> Self::Output {
35		let [a0, a1] = ghash_sq_coords(Self::peel(a));
36		let [b0, b1] = ghash_sq_coords(Self::peel(b));
37
38		SlicedGhashSqWide {
39			t0: BinaryField128bGhash::wide_mul(a0, b0),
40			t2: BinaryField128bGhash::wide_mul(a1, b1),
41			t1: BinaryField128bGhash::wide_mul(a0 + a1, b0 + b1),
42		}
43	}
44
45	/// Folds `Y² = X·Y + X`, recovering the Karatsuba cross term as `t₁ + t₀ + t₂`:
46	/// `z₀ = t₀ + X·t₂` and `z₁ = (t₁ + t₀ + t₂) + X·t₂ = z₀ + t₁ + t₂`.
47	///
48	/// Scaling `t₂` by `X` while it is still unreduced turns `z₀` into a single reduction of an
49	/// accumulated wide product, so the two coordinates cost two reductions in total.
50	#[inline]
51	fn reduce(wide: Self::Output) -> Self {
52		let z0 = BinaryField128bGhash::reduce(wide.t0 + wide.t2.mul_x_wide());
53		let z1 = z0 + BinaryField128bGhash::reduce(wide.t1 + wide.t2);
54
55		Self::wrap(ghash_sq_from_coords([z0, z1]))
56	}
57}