1use super::portable::{Schedule, BLOCK_LEN};
31use ic_core::{ensure, Result, Zeroize};
32
33#[cfg(target_arch = "x86")]
34use core::arch::x86::*;
35#[cfg(target_arch = "x86_64")]
36use core::arch::x86_64::*;
37
38pub const PARALLEL_BLOCKS: usize = 8;
45
46#[derive(Clone, Copy)]
48pub struct Keys {
49 enc: [__m128i; 15],
50 dec: [__m128i; 15],
51 rounds: usize,
52}
53
54impl Keys {
55 #[target_feature(enable = "aes")]
62 pub unsafe fn load(sched: &Schedule) -> Keys {
63 let rounds = sched.rounds;
64 let mut enc = [_mm_setzero_si128(); 15];
65 let mut dec = [_mm_setzero_si128(); 15];
66
67 for (r, slot) in enc.iter_mut().enumerate().take(rounds + 1) {
68 let rk = sched.round_key(r);
69 *slot = unsafe { _mm_loadu_si128(rk.as_ptr() as *const __m128i) };
72 }
73
74 dec[0] = enc[rounds];
77 for i in 1..rounds {
78 dec[i] = _mm_aesimc_si128(enc[rounds - i]);
79 }
80 dec[rounds] = enc[0];
81
82 Keys { enc, dec, rounds }
83 }
84
85 #[cfg(target_arch = "x86_64")]
88 #[inline]
89 pub(crate) fn enc_round_keys(&self) -> (&[__m128i; 15], usize) {
90 (&self.enc, self.rounds)
91 }
92
93 #[target_feature(enable = "aes")]
99 #[inline]
100 unsafe fn encrypt(&self, block: __m128i) -> __m128i {
101 let mut b = _mm_xor_si128(block, self.enc[0]);
102 for r in 1..self.rounds {
103 b = _mm_aesenc_si128(b, self.enc[r]);
104 }
105 _mm_aesenclast_si128(b, self.enc[self.rounds])
106 }
107
108 #[target_feature(enable = "aes")]
114 #[inline]
115 unsafe fn decrypt(&self, block: __m128i) -> __m128i {
116 let mut b = _mm_xor_si128(block, self.dec[0]);
117 for r in 1..self.rounds {
118 b = _mm_aesdec_si128(b, self.dec[r]);
119 }
120 _mm_aesdeclast_si128(b, self.dec[self.rounds])
121 }
122}
123
124#[target_feature(enable = "aes")]
130pub unsafe fn encrypt_block(keys: &Keys, block: &mut [u8]) -> Result<()> {
131 ensure!(block.len() == BLOCK_LEN, InvalidLength, "aes block");
132 unsafe {
135 let b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
136 let out = keys.encrypt(b);
137 _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, out);
138 }
139 Ok(())
140}
141
142#[target_feature(enable = "aes")]
148pub unsafe fn decrypt_block(keys: &Keys, block: &mut [u8]) -> Result<()> {
149 ensure!(block.len() == BLOCK_LEN, InvalidLength, "aes block");
150 unsafe {
152 let b = _mm_loadu_si128(block.as_ptr() as *const __m128i);
153 let out = keys.decrypt(b);
154 _mm_storeu_si128(block.as_mut_ptr() as *mut __m128i, out);
155 }
156 Ok(())
157}
158
159#[target_feature(enable = "aes")]
165pub unsafe fn encrypt_blocks(keys: &Keys, data: &mut [u8]) -> Result<()> {
166 ensure!(
167 data.len().is_multiple_of(BLOCK_LEN),
168 InvalidLength,
169 "aes batch must be block-aligned"
170 );
171
172 let mut chunks = data.chunks_exact_mut(BLOCK_LEN * PARALLEL_BLOCKS);
173 for chunk in &mut chunks {
174 let p = chunk.as_mut_ptr();
175 let mut b = [_mm_setzero_si128(); PARALLEL_BLOCKS];
176
177 unsafe {
180 for (i, slot) in b.iter_mut().enumerate() {
181 *slot = _mm_loadu_si128(p.add(i * BLOCK_LEN) as *const __m128i);
182 }
183 }
184
185 for slot in b.iter_mut() {
188 *slot = _mm_xor_si128(*slot, keys.enc[0]);
189 }
190 for r in 1..keys.rounds {
191 let rk = keys.enc[r];
192 for slot in b.iter_mut() {
193 *slot = _mm_aesenc_si128(*slot, rk);
194 }
195 }
196 let last = keys.enc[keys.rounds];
197 for slot in b.iter_mut() {
198 *slot = _mm_aesenclast_si128(*slot, last);
199 }
200
201 unsafe {
203 for (i, slot) in b.iter().enumerate() {
204 _mm_storeu_si128(p.add(i * BLOCK_LEN) as *mut __m128i, *slot);
205 }
206 }
207 }
208
209 for block in chunks.into_remainder().chunks_exact_mut(BLOCK_LEN) {
211 unsafe { encrypt_block(keys, block)? };
213 }
214 Ok(())
215}
216
217#[target_feature(enable = "aes")]
237pub unsafe fn ctr32_xor(keys: &Keys, counter: &mut [u8; BLOCK_LEN], data: &mut [u8]) {
238 let mut n = u32::from_be_bytes([counter[12], counter[13], counter[14], counter[15]]);
239 let mut blocks = [[0u8; BLOCK_LEN]; PARALLEL_BLOCKS];
240 for block in blocks.iter_mut() {
241 block[..12].copy_from_slice(&counter[..12]);
242 }
243
244 for chunk in data.chunks_mut(BLOCK_LEN * PARALLEL_BLOCKS) {
245 for (i, block) in blocks.iter_mut().enumerate() {
246 block[12..].copy_from_slice(&n.wrapping_add(i as u32).to_be_bytes());
247 }
248 let mut b = [_mm_setzero_si128(); PARALLEL_BLOCKS];
249 for (slot, block) in b.iter_mut().zip(blocks.iter()) {
250 *slot = _mm_xor_si128(
252 unsafe { _mm_loadu_si128(block.as_ptr() as *const __m128i) },
253 keys.enc[0],
254 );
255 }
256 for r in 1..keys.rounds {
257 let rk = keys.enc[r];
258 for slot in b.iter_mut() {
259 *slot = _mm_aesenc_si128(*slot, rk);
260 }
261 }
262 let last = keys.enc[keys.rounds];
263 for slot in b.iter_mut() {
264 *slot = _mm_aesenclast_si128(*slot, last);
265 }
266
267 let p = chunk.as_mut_ptr();
268 if chunk.len() == BLOCK_LEN * PARALLEL_BLOCKS {
269 for (i, ks) in b.iter().enumerate() {
270 unsafe {
273 let d = _mm_loadu_si128(p.add(i * BLOCK_LEN) as *const __m128i);
274 _mm_storeu_si128(p.add(i * BLOCK_LEN) as *mut __m128i, _mm_xor_si128(d, *ks));
275 }
276 }
277 } else {
278 let mut stream = [0u8; BLOCK_LEN * PARALLEL_BLOCKS];
281 for (i, ks) in b.iter().enumerate() {
282 unsafe {
284 _mm_storeu_si128(stream.as_mut_ptr().add(i * BLOCK_LEN) as *mut __m128i, *ks)
285 };
286 }
287 for (d, k) in chunk.iter_mut().zip(stream.iter()) {
288 *d ^= k;
289 }
290 stream.zeroize();
291 }
292 n = n.wrapping_add(chunk.len().div_ceil(BLOCK_LEN) as u32);
293 }
294 counter[12..].copy_from_slice(&n.to_be_bytes());
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use crate::aes::portable;
301
302 fn available() -> bool {
303 std::arch::is_x86_feature_detected!("aes")
304 }
305
306 #[test]
311 fn matches_the_portable_backend_on_every_key_length() {
312 if !available() {
313 return;
314 }
315 for key_len in [16usize, 24, 32] {
316 let key: Vec<u8> = (0..key_len).map(|i| (i * 7 + 1) as u8).collect();
317 let sched = portable::Schedule::expand(&key).unwrap();
318 let keys = unsafe { Keys::load(&sched) };
320
321 for seed in 0..64u8 {
322 let original: [u8; 16] =
323 core::array::from_fn(|i| seed ^ (i as u8).wrapping_mul(31));
324
325 let mut a = original;
326 let mut b = original;
327 portable::encrypt_block(&sched, &mut a).unwrap();
328 unsafe { encrypt_block(&keys, &mut b).unwrap() };
329 assert_eq!(a, b, "encrypt mismatch, key_len {key_len}, seed {seed}");
330
331 let mut a = original;
332 let mut b = original;
333 portable::decrypt_block(&sched, &mut a).unwrap();
334 unsafe { decrypt_block(&keys, &mut b).unwrap() };
335 assert_eq!(a, b, "decrypt mismatch, key_len {key_len}, seed {seed}");
336 }
337 }
338 }
339
340 #[test]
343 fn batch_matches_single_block_at_every_length() {
344 if !available() {
345 return;
346 }
347 let sched = portable::Schedule::expand(&[0x2bu8; 32]).unwrap();
348 let keys = unsafe { Keys::load(&sched) };
350
351 for blocks in 0..=(PARALLEL_BLOCKS * 2 + 3) {
352 let data: Vec<u8> = (0..blocks * BLOCK_LEN).map(|i| (i * 13) as u8).collect();
353
354 let mut batched = data.clone();
355 unsafe { encrypt_blocks(&keys, &mut batched).unwrap() };
356
357 let mut one_at_a_time = data.clone();
358 for block in one_at_a_time.chunks_exact_mut(BLOCK_LEN) {
359 unsafe { encrypt_block(&keys, block).unwrap() };
360 }
361
362 assert_eq!(batched, one_at_a_time, "{blocks} blocks");
363 }
364 }
365
366 #[test]
367 fn batch_rejects_unaligned_input() {
368 if !available() {
369 return;
370 }
371 let sched = portable::Schedule::expand(&[0u8; 16]).unwrap();
372 let keys = unsafe { Keys::load(&sched) };
374 let mut data = [0u8; BLOCK_LEN + 1];
375 assert!(unsafe { encrypt_blocks(&keys, &mut data) }.is_err());
376 }
377
378 #[test]
380 fn decryption_inverts_encryption() {
381 if !available() {
382 return;
383 }
384 let sched = portable::Schedule::expand(&[0x42u8; 24]).unwrap();
385 let keys = unsafe { Keys::load(&sched) };
387 let original: [u8; 16] = core::array::from_fn(|i| i as u8);
388 let mut block = original;
389 unsafe {
390 encrypt_block(&keys, &mut block).unwrap();
391 assert_ne!(block, original);
392 decrypt_block(&keys, &mut block).unwrap();
393 }
394 assert_eq!(block, original);
395 }
396}