Skip to main content

binius_field/
packed_aes.rs

1// Copyright 2024-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use crate::{
5	aes_field::AESTowerField8b,
6	arch::{
7		AesInvert1x, AesInvert16x, AesInvert32x, AesInvert64x, AesSquare1x, AesSquare16x,
8		AesSquare32x, AesSquare64x, AesWideMul1x, AesWideMul16x, AesWideMul32x, AesWideMul64x,
9		M128, M256, M512, MulFromWideMul,
10		portable::packed_macros::{portable_macros::*, *},
11	},
12};
13
14define_packed_binary_field!(
15	PackedAESBinaryField1x8b,
16	AESTowerField8b,
17	u8,
18	(MulFromWideMul),
19	(AesSquare1x),
20	(AesInvert1x),
21	(AesWideMul1x)
22);
23define_packed_binary_field!(
24	PackedAESBinaryField16x8b,
25	AESTowerField8b,
26	M128,
27	(MulFromWideMul),
28	(AesSquare16x),
29	(AesInvert16x),
30	(AesWideMul16x)
31);
32define_packed_binary_field!(
33	PackedAESBinaryField32x8b,
34	AESTowerField8b,
35	M256,
36	(MulFromWideMul),
37	(AesSquare32x),
38	(AesInvert32x),
39	(AesWideMul32x)
40);
41define_packed_binary_field!(
42	PackedAESBinaryField64x8b,
43	AESTowerField8b,
44	M512,
45	(MulFromWideMul),
46	(AesSquare64x),
47	(AesInvert64x),
48	(AesWideMul64x)
49);
50
51#[cfg(test)]
52mod test_utils {
53	/// Test if `mult_func` operation is a valid multiply operation on the given values for
54	/// all possible packed fields defined on 8-512 bits.
55	macro_rules! define_multiply_tests {
56		($mult_func:path, $constraint:ty) => {
57			$crate::packed_binary_field::test_utils::define_check_packed_mul!(
58				$mult_func,
59				$constraint
60			);
61
62			proptest! {
63				#[test]
64				fn test_mul_packed_8(a_val in any::<u8>(), b_val in any::<u8>()) {
65					TestMult::<$crate::PackedAESBinaryField1x8b>::test_mul(
66						a_val.into(),
67						b_val.into(),
68					);
69				}
70
71				#[test]
72				fn test_mul_packed_128(a_val in any::<u128>(), b_val in any::<u128>()) {
73					TestMult::<$crate::PackedAESBinaryField16x8b>::test_mul(
74						a_val.into(),
75						b_val.into(),
76					);
77				}
78
79				#[test]
80				fn test_mul_packed_256(a_val in any::<[u128; 2]>(), b_val in any::<[u128; 2]>()) {
81					TestMult::<$crate::PackedAESBinaryField32x8b>::test_mul(
82						a_val.into(),
83						b_val.into(),
84					);
85				}
86
87				#[test]
88				fn test_mul_packed_512(a_val in any::<[u128; 4]>(), b_val in any::<[u128; 4]>()) {
89					TestMult::<$crate::PackedAESBinaryField64x8b>::test_mul(
90						a_val.into(),
91						b_val.into(),
92					);
93				}
94			}
95		};
96	}
97
98	/// Test if `square_func` operation is a valid square operation on the given value for
99	/// all possible packed fields.
100	macro_rules! define_square_tests {
101		($square_func:path, $constraint:ident) => {
102			$crate::packed_binary_field::test_utils::define_check_packed_square!(
103				$square_func,
104				$constraint
105			);
106
107			proptest! {
108				#[test]
109				fn test_square_packed_8(a_val in any::<u8>()) {
110					TestSquare::<$crate::PackedAESBinaryField1x8b>::test_square(a_val.into());
111				}
112
113				#[test]
114				fn test_square_packed_128(a_val in any::<u128>()) {
115					TestSquare::<$crate::PackedAESBinaryField16x8b>::test_square(a_val.into());
116				}
117
118				#[test]
119				fn test_square_packed_256(a_val in any::<[u128; 2]>()) {
120					TestSquare::<$crate::PackedAESBinaryField32x8b>::test_square(a_val.into());
121				}
122
123				#[test]
124				fn test_square_packed_512(a_val in any::<[u128; 4]>()) {
125					TestSquare::<$crate::PackedAESBinaryField64x8b>::test_square(a_val.into());
126				}
127			}
128		};
129	}
130
131	/// Test if `invert_func` operation is a valid invert operation on the given value for
132	/// all possible packed fields.
133	macro_rules! define_invert_tests {
134		($invert_func:path, $constraint:ident) => {
135			$crate::packed_binary_field::test_utils::define_check_packed_inverse!(
136				$invert_func,
137				$constraint
138			);
139
140			proptest! {
141				#[test]
142				fn test_invert_packed_8(a_val in any::<u8>()) {
143					TestSquare::<$crate::PackedAESBinaryField1x8b>::test_invert(a_val.into());
144				}
145
146				#[test]
147				fn test_invert_packed_128(a_val in any::<u128>()) {
148					TestInvert::<$crate::PackedAESBinaryField16x8b>::test_invert(a_val.into());
149				}
150
151				#[test]
152				fn test_invert_packed_256(a_val in any::<[u128; 2]>()) {
153					TestInvert::<$crate::PackedAESBinaryField32x8b>::test_invert(a_val.into());
154				}
155
156				#[test]
157				fn test_invert_packed_512(a_val in any::<[u128; 4]>()) {
158					TestInvert::<$crate::PackedAESBinaryField64x8b>::test_invert(a_val.into());
159				}
160			}
161		};
162	}
163
164	/// Test the widening multiply against the plain multiply for all AES packings.
165	macro_rules! define_wide_mul_tests {
166		() => {
167			fn check_widening_correctness<P>(a: P::Underlier, b: P::Underlier)
168			where
169				P: $crate::PackedField<Scalar = $crate::AESTowerField8b>
170					+ $crate::WideMul
171					+ $crate::underlier::WithUnderlier,
172			{
173				let a = P::from_underlier(a);
174				let b = P::from_underlier(b);
175				// One deferred product, reduced immediately, must equal the plain multiply.
176				let wide = P::wide_mul(a, b);
177				let reduced = P::reduce(wide);
178				assert_eq!(reduced, a * b);
179			}
180
181			fn check_widening_linearity<P>(
182				a1: P::Underlier,
183				b1: P::Underlier,
184				a2: P::Underlier,
185				b2: P::Underlier,
186			) where
187				P: $crate::PackedField<Scalar = $crate::AESTowerField8b>
188					+ $crate::WideMul
189					+ $crate::underlier::WithUnderlier,
190			{
191				let (a1, b1) = (P::from_underlier(a1), P::from_underlier(b1));
192				let (a2, b2) = (P::from_underlier(a2), P::from_underlier(b2));
193				// Accumulated products reduce once at the end.
194				// The sum reaches wide values no single product produces, so this covers the
195				// reduction's full accumulated domain, not just fresh products.
196				let sum_reduced = P::reduce(P::wide_mul(a1, b1) + P::wide_mul(a2, b2));
197				assert_eq!(sum_reduced, a1 * b1 + a2 * b2);
198			}
199
200			proptest! {
201				#[test]
202				fn test_wide_mul_correctness_8(a in any::<u8>(), b in any::<u8>()) {
203					check_widening_correctness::<$crate::PackedAESBinaryField1x8b>(a, b);
204				}
205
206				#[test]
207				fn test_wide_mul_correctness_128(a in any::<u128>(), b in any::<u128>()) {
208					check_widening_correctness::<$crate::PackedAESBinaryField16x8b>(
209						a.into(),
210						b.into(),
211					);
212				}
213
214				#[test]
215				fn test_wide_mul_correctness_256(a in any::<[u128; 2]>(), b in any::<[u128; 2]>()) {
216					check_widening_correctness::<$crate::PackedAESBinaryField32x8b>(
217						a.into(),
218						b.into(),
219					);
220				}
221
222				#[test]
223				fn test_wide_mul_correctness_512(a in any::<[u128; 4]>(), b in any::<[u128; 4]>()) {
224					check_widening_correctness::<$crate::PackedAESBinaryField64x8b>(
225						a.into(),
226						b.into(),
227					);
228				}
229
230				#[test]
231				fn test_wide_mul_linearity_8(
232					a1 in any::<u8>(), b1 in any::<u8>(),
233					a2 in any::<u8>(), b2 in any::<u8>(),
234				) {
235					check_widening_linearity::<$crate::PackedAESBinaryField1x8b>(a1, b1, a2, b2);
236				}
237
238				#[test]
239				fn test_wide_mul_linearity_128(
240					a1 in any::<u128>(), b1 in any::<u128>(),
241					a2 in any::<u128>(), b2 in any::<u128>(),
242				) {
243					check_widening_linearity::<$crate::PackedAESBinaryField16x8b>(
244						a1.into(), b1.into(), a2.into(), b2.into(),
245					);
246				}
247
248				#[test]
249				fn test_wide_mul_linearity_256(
250					a1 in any::<[u128; 2]>(), b1 in any::<[u128; 2]>(),
251					a2 in any::<[u128; 2]>(), b2 in any::<[u128; 2]>(),
252				) {
253					check_widening_linearity::<$crate::PackedAESBinaryField32x8b>(
254						a1.into(), b1.into(), a2.into(), b2.into(),
255					);
256				}
257
258				#[test]
259				fn test_wide_mul_linearity_512(
260					a1 in any::<[u128; 4]>(), b1 in any::<[u128; 4]>(),
261					a2 in any::<[u128; 4]>(), b2 in any::<[u128; 4]>(),
262				) {
263					check_widening_linearity::<$crate::PackedAESBinaryField64x8b>(
264						a1.into(), b1.into(), a2.into(), b2.into(),
265					);
266				}
267			}
268		};
269	}
270
271	pub(crate) use define_invert_tests;
272	pub(crate) use define_multiply_tests;
273	pub(crate) use define_square_tests;
274	pub(crate) use define_wide_mul_tests;
275}
276
277#[cfg(test)]
278mod tests {
279	use std::ops::Mul;
280
281	use proptest::prelude::*;
282
283	use super::test_utils::{
284		define_invert_tests, define_multiply_tests, define_square_tests, define_wide_mul_tests,
285	};
286	use crate::{
287		PackedField, WideMul,
288		arithmetic_traits::{InvertOrZero, Square},
289	};
290
291	define_multiply_tests!(Mul::mul, PackedField);
292
293	define_square_tests!(Square::square, PackedField);
294
295	define_invert_tests!(InvertOrZero::invert_or_zero, PackedField);
296
297	define_wide_mul_tests!();
298
299	#[test]
300	fn test_wide_mul_exhaustive_scalar_pairs() {
301		// The scalar field has only 2^8 elements, so every product admits an exhaustive check.
302		// Each byte pair is broadcast across the 128-bit packing and multiplied deferred.
303		//
304		//     reduce(wide_mul(a, b)) must equal the scalar product in every lane.
305		//
306		// The scalar multiply is an independent oracle: it runs the tower-field log/exp tables,
307		// not the packed widening path under test.
308		for a in 0..=u8::MAX {
309			for b in 0..=u8::MAX {
310				let expected = crate::AESTowerField8b::new(a) * crate::AESTowerField8b::new(b);
311
312				let a_packed =
313					crate::PackedAESBinaryField16x8b::broadcast(crate::AESTowerField8b::new(a));
314				let b_packed =
315					crate::PackedAESBinaryField16x8b::broadcast(crate::AESTowerField8b::new(b));
316				let reduced = crate::PackedAESBinaryField16x8b::reduce(
317					crate::PackedAESBinaryField16x8b::wide_mul(a_packed, b_packed),
318				);
319
320				assert_eq!(
321					reduced,
322					crate::PackedAESBinaryField16x8b::broadcast(expected),
323					"a={a:#04x} b={b:#04x}"
324				);
325			}
326		}
327	}
328}