binius_field/arch/portable/arithmetic/
itoh_tsujii.rs1use std::{array, iter, ops::Mul, sync::LazyLock};
21
22use bytemuck::TransparentWrapper;
23
24use crate::{
25 BinaryField1b, Divisible, ExtensionField,
26 arch::M128,
27 arithmetic_traits::{InvertOrZero, Square},
28 ghash::BinaryField128bGhash as GhashB128,
29 linear_transformation::{
30 BytewiseLookupTransformation, BytewiseLookupTransformationFactory,
31 InputWrappingTransformationFactory, LinearTransformationFactory,
32 OutputWrappingTransformationFactory, Transformation, WrappingTransformation,
33 },
34};
35
36const FIELD_BITS: usize = 128;
38
39type GhashPowerMap =
44 WrappingTransformation<BytewiseLookupTransformation<M128, M128>, GhashB128, GhashB128>;
45
46struct GhashPowerMapTables {
51 pow_2_3: GhashPowerMap,
52 pow_2_7: GhashPowerMap,
53 pow_2_14: GhashPowerMap,
54 pow_2_28: GhashPowerMap,
55 pow_2_63: GhashPowerMap,
56}
57
58impl GhashPowerMapTables {
59 fn new() -> Self {
60 Self {
61 pow_2_3: compute_power_map_transform(3),
62 pow_2_7: compute_power_map_transform(7),
63 pow_2_14: compute_power_map_transform(14),
64 pow_2_28: compute_power_map_transform(28),
65 pow_2_63: compute_power_map_transform(63),
66 }
67 }
68}
69
70static GHASH_POWER_MAP_TABLES: LazyLock<GhashPowerMapTables> =
71 LazyLock::new(GhashPowerMapTables::new);
72
73fn compute_power_map_transform(n: usize) -> GhashPowerMap {
79 let matrix = compute_power_map_matrix(n);
80 OutputWrappingTransformationFactory::<_, GhashB128, GhashB128>::new(
81 InputWrappingTransformationFactory::<_, GhashB128, M128>::new(
82 BytewiseLookupTransformationFactory,
83 ),
84 )
85 .create(&matrix)
86}
87
88fn compute_power_map_matrix(n: usize) -> [GhashB128; FIELD_BITS] {
93 array::from_fn(|i| {
94 let basis = <GhashB128 as ExtensionField<BinaryField1b>>::basis(i);
95 iter::successors(Some(basis), |basis_pow_2_i| Some(basis_pow_2_i.square()))
96 .nth(n)
97 .expect("closure always returns Some")
98 })
99}
100
101pub fn invert_b128<P>(x: P) -> P
113where
114 P: Copy + Square + Mul<Output = P> + Divisible<GhashB128>,
115{
116 let tables = &*GHASH_POWER_MAP_TABLES;
117
118 let beta_1 = x;
120 let beta_2 = beta_1.square() * beta_1;
121 let beta_3 = beta_2.square() * beta_1;
122 let beta_6 = pow_2_n(beta_3, &tables.pow_2_3) * beta_3;
123 let beta_7 = beta_6.square() * beta_1;
124 let beta_14 = pow_2_n(beta_7, &tables.pow_2_7) * beta_7;
125 let beta_28 = pow_2_n(beta_14, &tables.pow_2_14) * beta_14;
126 let beta_56 = pow_2_n(beta_28, &tables.pow_2_28) * beta_28;
127 let beta_63 = pow_2_n(beta_56, &tables.pow_2_7) * beta_7;
128 let beta_126 = pow_2_n(beta_63, &tables.pow_2_63) * beta_63;
129 let beta_127 = beta_126.square() * beta_1;
130 beta_127.square()
132}
133
134fn pow_2_n<P>(x: P, power_map: &GhashPowerMap) -> P
136where
137 P: Divisible<GhashB128>,
138{
139 Divisible::<GhashB128>::from_iter(
140 Divisible::<GhashB128>::value_iter(x).map(|scalar| power_map.transform(&scalar)),
141 )
142}
143
144#[repr(transparent)]
151#[derive(TransparentWrapper)]
152pub struct GhashItohTsujii<T>(T);
153
154impl<P> InvertOrZero for GhashItohTsujii<P>
155where
156 P: Copy + Square + Mul<Output = P> + Divisible<GhashB128>,
157{
158 #[inline]
159 fn invert_or_zero(self) -> Self {
160 Self::wrap(invert_b128(Self::peel(self)))
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use proptest::prelude::*;
167
168 use super::*;
169 use crate::{Field, PackedBinaryGhash1x128b, PackedBinaryGhash2x128b, PackedField};
170
171 #[test]
172 fn test_compute_power_map_matrix_is_squaring() {
173 let matrix = compute_power_map_matrix(1);
175 for i in 0..FIELD_BITS {
176 let basis = <GhashB128 as ExtensionField<BinaryField1b>>::basis(i);
177 assert_eq!(matrix[i], basis.square());
178 }
179 }
180
181 #[test]
182 fn test_power_map_transform_matches_repeated_squaring() {
183 let power_map = compute_power_map_transform(7);
184 for &raw in &[0u128, 1, 2, 0x87, 0x21ac73a21d46a21badd6747bcdfc5d4d] {
185 let x = GhashB128::from(raw);
186 let mut expected = x;
187 for _ in 0..7 {
188 expected = expected.square();
189 }
190 assert_eq!(power_map.transform(&x), expected);
191 }
192 }
193
194 #[test]
195 fn test_invert_b128_known_values() {
196 let one = PackedBinaryGhash1x128b::broadcast(GhashB128::ONE);
197 assert_eq!(invert_b128(one), one);
198
199 let zero = PackedBinaryGhash1x128b::broadcast(GhashB128::ZERO);
200 assert_eq!(invert_b128(zero), zero);
201 }
202
203 proptest! {
207 #[test]
208 fn test_invert_b128_is_multiplicative_inverse_scalar(raw in any::<u128>()) {
209 let x = GhashB128::from(raw);
210 let inv = invert_b128(x);
211 if x == GhashB128::ZERO {
212 prop_assert_eq!(inv, GhashB128::ZERO);
213 } else {
214 prop_assert_eq!(x * inv, GhashB128::ONE);
215 }
216 }
217
218 #[test]
219 fn test_invert_b128_is_multiplicative_inverse_1x(raw in any::<u128>()) {
220 let scalar = GhashB128::from(raw);
221 let x = PackedBinaryGhash1x128b::broadcast(scalar);
222 let inv = invert_b128(x);
223 if scalar == GhashB128::ZERO {
224 prop_assert_eq!(inv, x);
225 } else {
226 prop_assert_eq!(x * inv, PackedBinaryGhash1x128b::broadcast(GhashB128::ONE));
227 }
228 }
229
230 #[test]
231 fn test_invert_b128_is_multiplicative_inverse_2x(a in any::<u128>(), b in any::<u128>()) {
232 let x = PackedBinaryGhash2x128b::from_scalars([a, b].map(GhashB128::from));
233 let inv = invert_b128(x);
234 let ones = PackedBinaryGhash2x128b::from_scalars(
235 [a, b].map(|raw| {
236 if GhashB128::from(raw) == GhashB128::ZERO {
237 GhashB128::ZERO
238 } else {
239 GhashB128::ONE
240 }
241 }),
242 );
243 prop_assert_eq!(x * inv, ones);
244 }
245 }
246}