Skip to main content

binius_field/
aes_field.rs

1// Copyright 2024-2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3
4use std::{
5	fmt::{Debug, Display, Formatter},
6	iter::{Product, Sum},
7	marker::PhantomData,
8	ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
9};
10
11use binius_utils::{
12	DeserializeBytes, FixedSizeSerializeBytes, SerializationError, SerializeBytes,
13	bytes::{Buf, BufMut},
14};
15use bytemuck::{Pod, Zeroable};
16
17use super::{
18	PackedExtension, PackedSubfield,
19	arithmetic_traits::InvertOrZero,
20	binary_field::{BinaryField, BinaryField1b, binary_field, impl_field_extension},
21	mul_by_binary_field_1b,
22};
23use crate::{
24	ExtensionField, Field, binary_field_arithmetic::impl_arithmetic_using_packed,
25	linear_transformation::Transformation, underlier::U1,
26};
27
28// These fields represent a tower based on AES GF(2^8) field (GF(256)/x^8+x^4+x^3+x+1)
29// that is isomorphically included into binary tower, i.e.:
30//  - AESTowerField16b is GF(2^16) / (x^2 + x * x_2 + 1) where `x_2` is 0x10 from
31// BinaryField8b isomorphically projected to AESTowerField8b.
32//  - AESTowerField32b is GF(2^32) / (x^2 + x * x_3 + 1), where `x_3` is 0x1000 from
33//    AESTowerField16b.
34//  ...
35binary_field!(pub AESTowerField8b(u8), 0xD0);
36
37impl AESTowerField8b {
38	pub const fn new(value: u8) -> Self {
39		Self(value)
40	}
41}
42
43// `WideMul` for the AES tower field is a direct log/exp-table multiply that already produces the
44// reduced 8-bit element, so the wide product is `Self` and `reduce` is the identity. This is also
45// the single source of truth for the portable packed AES multiply (see `AesLookupWideMul` and the
46// elementwise strategy in the packed AES modules).
47impl crate::arithmetic_traits::WideMul for AESTowerField8b {
48	type Output = Self;
49
50	#[inline]
51	fn wide_mul(a: Self, b: Self) -> Self {
52		Self::new(aes_mul_8b(a.val(), b.val()))
53	}
54
55	#[inline]
56	fn reduce(wide: Self) -> Self {
57		wide
58	}
59}
60
61/// Multiplies two `AESTowerField8b` elements (as raw bytes) via the tower-field log/exp tables,
62/// returning the reduced product. Shared by the scalar `WideMul` and the portable packed AES
63/// widening multiply.
64#[inline]
65pub(crate) fn aes_mul_8b(lhs: u8, rhs: u8) -> u8 {
66	if lhs != 0 && rhs != 0 {
67		let log_index = AES_LOG_TABLE[lhs as usize] as usize + AES_LOG_TABLE[rhs as usize] as usize;
68		// Each log table value is at most 254, so the sum is at most 508; subtracting 255 when it
69		// exceeds 254 keeps the index in `0..=253`.
70		let log_index = if log_index > 254 {
71			log_index - 255
72		} else {
73			log_index
74		};
75		// Safety: `log_index <= 253 < 256` by the bound above.
76		unsafe { *AES_EXP_TABLE.get_unchecked(log_index) }
77	} else {
78		0
79	}
80}
81
82#[rustfmt::skip]
83const AES_EXP_TABLE: [u8; 256] = [
84	0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, 0x1a, 0x2e, 0x72, 0x96, 0xa1, 0xf8, 0x13, 0x35,
85	0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa,
86	0xe5, 0x34, 0x5c, 0xe4, 0x37, 0x59, 0xeb, 0x26, 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31,
87	0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, 0x4f, 0xd1, 0x68, 0xb8, 0xd3, 0x6e, 0xb2, 0xcd,
88	0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88,
89	0x83, 0x9e, 0xb9, 0xd0, 0x6b, 0xbd, 0xdc, 0x7f, 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a,
90	0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, 0x0b, 0x1d, 0x27, 0x69, 0xbb, 0xd6, 0x61, 0xa3,
91	0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0,
92	0xfb, 0x16, 0x3a, 0x4e, 0xd2, 0x6d, 0xb7, 0xc2, 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41,
93	0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, 0x5b, 0xed, 0x2c, 0x74, 0x9c, 0xbf, 0xda, 0x75,
94	0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80,
95	0x9b, 0xb6, 0xc1, 0x58, 0xe8, 0x23, 0x65, 0xaf, 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54,
96	0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, 0x1b, 0x2d, 0x77, 0x99, 0xb0, 0xcb, 0x46, 0xca,
97	0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e,
98	0x12, 0x36, 0x5a, 0xee, 0x29, 0x7b, 0x8d, 0x8c, 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17,
99	0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, 0x1c, 0x24, 0x6c, 0xb4, 0xc7, 0x52, 0xf6, 0x01,
100];
101
102#[rustfmt::skip]
103const AES_LOG_TABLE: [u8; 256] = [
104	0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1a, 0xc6, 0x4b, 0xc7, 0x1b, 0x68, 0x33, 0xee, 0xdf, 0x03,
105	0x64, 0x04, 0xe0, 0x0e, 0x34, 0x8d, 0x81, 0xef, 0x4c, 0x71, 0x08, 0xc8, 0xf8, 0x69, 0x1c, 0xc1,
106	0x7d, 0xc2, 0x1d, 0xb5, 0xf9, 0xb9, 0x27, 0x6a, 0x4d, 0xe4, 0xa6, 0x72, 0x9a, 0xc9, 0x09, 0x78,
107	0x65, 0x2f, 0x8a, 0x05, 0x21, 0x0f, 0xe1, 0x24, 0x12, 0xf0, 0x82, 0x45, 0x35, 0x93, 0xda, 0x8e,
108	0x96, 0x8f, 0xdb, 0xbd, 0x36, 0xd0, 0xce, 0x94, 0x13, 0x5c, 0xd2, 0xf1, 0x40, 0x46, 0x83, 0x38,
109	0x66, 0xdd, 0xfd, 0x30, 0xbf, 0x06, 0x8b, 0x62, 0xb3, 0x25, 0xe2, 0x98, 0x22, 0x88, 0x91, 0x10,
110	0x7e, 0x6e, 0x48, 0xc3, 0xa3, 0xb6, 0x1e, 0x42, 0x3a, 0x6b, 0x28, 0x54, 0xfa, 0x85, 0x3d, 0xba,
111	0x2b, 0x79, 0x0a, 0x15, 0x9b, 0x9f, 0x5e, 0xca, 0x4e, 0xd4, 0xac, 0xe5, 0xf3, 0x73, 0xa7, 0x57,
112	0xaf, 0x58, 0xa8, 0x50, 0xf4, 0xea, 0xd6, 0x74, 0x4f, 0xae, 0xe9, 0xd5, 0xe7, 0xe6, 0xad, 0xe8,
113	0x2c, 0xd7, 0x75, 0x7a, 0xeb, 0x16, 0x0b, 0xf5, 0x59, 0xcb, 0x5f, 0xb0, 0x9c, 0xa9, 0x51, 0xa0,
114	0x7f, 0x0c, 0xf6, 0x6f, 0x17, 0xc4, 0x49, 0xec, 0xd8, 0x43, 0x1f, 0x2d, 0xa4, 0x76, 0x7b, 0xb7,
115	0xcc, 0xbb, 0x3e, 0x5a, 0xfb, 0x60, 0xb1, 0x86, 0x3b, 0x52, 0xa1, 0x6c, 0xaa, 0x55, 0x29, 0x9d,
116	0x97, 0xb2, 0x87, 0x90, 0x61, 0xbe, 0xdc, 0xfc, 0xbc, 0x95, 0xcf, 0xcd, 0x37, 0x3f, 0x5b, 0xd1,
117	0x53, 0x39, 0x84, 0x3c, 0x41, 0xa2, 0x6d, 0x47, 0x14, 0x2a, 0x9e, 0x5d, 0x56, 0xf2, 0xd3, 0xab,
118	0x44, 0x11, 0x92, 0xd9, 0x23, 0x20, 0x2e, 0x89, 0xb4, 0x7c, 0xb8, 0x26, 0x77, 0x99, 0xe3, 0xa5,
119	0x67, 0x4a, 0xed, 0xde, 0xc5, 0x31, 0xfe, 0x18, 0x0d, 0x63, 0x8c, 0x80, 0xc0, 0xf7, 0x70, 0x07,
120];
121
122unsafe impl Pod for AESTowerField8b {}
123
124impl_field_extension!(BinaryField1b(U1) < @3 => AESTowerField8b(u8));
125
126mul_by_binary_field_1b!(AESTowerField8b);
127
128impl_arithmetic_using_packed!(AESTowerField8b);
129
130/// A 3- step transformation :
131/// 1. Cast to base b-bit packed field
132/// 2. Apply linear transformation between aes and binary b8 tower fields
133/// 3. Cast back to the target field
134pub struct SubfieldTransformer<IF, OF, T> {
135	inner_transform: T,
136	_ip_pd: PhantomData<IF>,
137	_op_pd: PhantomData<OF>,
138}
139
140impl<IF, OF, IEP, OEP, T> Transformation<IEP, OEP> for SubfieldTransformer<IF, OF, T>
141where
142	IF: Field,
143	OF: Field,
144	IEP: PackedExtension<IF>,
145	OEP: PackedExtension<OF>,
146	T: Transformation<PackedSubfield<IEP, IF>, PackedSubfield<OEP, OF>>,
147{
148	fn transform(&self, input: &IEP) -> OEP {
149		OEP::cast_ext(self.inner_transform.transform(IEP::cast_base_ref(input)))
150	}
151}
152
153impl SerializeBytes for AESTowerField8b {
154	fn serialize(&self, write_buf: impl BufMut) -> Result<(), SerializationError> {
155		self.0.serialize(write_buf)
156	}
157}
158
159impl DeserializeBytes for AESTowerField8b {
160	fn deserialize(read_buf: impl Buf) -> Result<Self, SerializationError>
161	where
162		Self: Sized,
163	{
164		Ok(Self(DeserializeBytes::deserialize(read_buf)?))
165	}
166}
167
168impl FixedSizeSerializeBytes for AESTowerField8b {
169	const BYTE_SIZE: usize = 1;
170}
171
172#[cfg(test)]
173mod tests {
174	use binius_utils::{SerializeBytes, bytes::BytesMut};
175	use proptest::{arbitrary::any, proptest};
176	use rand::prelude::*;
177
178	use super::*;
179	use crate::{Random, binary_field::tests::is_binary_field_valid_generator};
180
181	fn check_square(f: impl Field) {
182		assert_eq!(f.square(), f * f);
183	}
184
185	proptest! {
186		#[test]
187		fn test_square_8(a in any::<u8>()) {
188			check_square(AESTowerField8b::from(a))
189		}
190	}
191
192	fn check_invert(f: impl Field) {
193		let inversed = f.invert_or_zero();
194		if f.is_zero() {
195			assert!(inversed.is_zero());
196		} else {
197			assert_eq!(inversed * f, Field::ONE);
198		}
199	}
200
201	proptest! {
202		#[test]
203		fn test_invert_8(a in any::<u8>()) {
204			check_invert(AESTowerField8b::from(a))
205		}
206	}
207
208	fn check_mul_by_one<F: Field>(f: F) {
209		assert_eq!(F::ONE * f, f);
210		assert_eq!(f * F::ONE, f);
211	}
212
213	fn check_commutative<F: Field>(f_1: F, f_2: F) {
214		assert_eq!(f_1 * f_2, f_2 * f_1);
215	}
216
217	fn check_associativity_and_lineraity<F: Field>(f_1: F, f_2: F, f_3: F) {
218		assert_eq!(f_1 * (f_2 * f_3), (f_1 * f_2) * f_3);
219		assert_eq!(f_1 * (f_2 + f_3), f_1 * f_2 + f_1 * f_3);
220	}
221
222	fn check_mul<F: Field>(f_1: F, f_2: F, f_3: F) {
223		check_mul_by_one(f_1);
224		check_mul_by_one(f_2);
225		check_mul_by_one(f_3);
226
227		check_commutative(f_1, f_2);
228		check_commutative(f_1, f_3);
229		check_commutative(f_2, f_3);
230
231		check_associativity_and_lineraity(f_1, f_2, f_3);
232		check_associativity_and_lineraity(f_1, f_3, f_2);
233		check_associativity_and_lineraity(f_2, f_1, f_3);
234		check_associativity_and_lineraity(f_2, f_3, f_1);
235		check_associativity_and_lineraity(f_3, f_1, f_2);
236		check_associativity_and_lineraity(f_3, f_2, f_1);
237	}
238
239	proptest! {
240		#[test]
241		fn test_mul_8(a in any::<u8>(), b in any::<u8>(), c in any::<u8>()) {
242			check_mul(AESTowerField8b::from(a), AESTowerField8b::from(b), AESTowerField8b::from(c))
243		}
244	}
245
246	#[test]
247	fn test_multiplicative_generators() {
248		assert!(is_binary_field_valid_generator::<AESTowerField8b>());
249	}
250
251	#[test]
252	fn test_serialization() {
253		let mut buffer = BytesMut::new();
254		let mut rng = StdRng::seed_from_u64(0);
255		let aes8 = AESTowerField8b::random(&mut rng);
256
257		SerializeBytes::serialize(&aes8, &mut buffer).unwrap();
258
259		let mut read_buffer = buffer.freeze();
260
261		assert_eq!(AESTowerField8b::deserialize(&mut read_buffer).unwrap(), aes8);
262	}
263}