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