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