1#[inline]
11fn qr(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
12 s[a] = s[a].wrapping_add(s[b]);
13 s[d] = (s[d] ^ s[a]).rotate_left(16);
14 s[c] = s[c].wrapping_add(s[d]);
15 s[b] = (s[b] ^ s[c]).rotate_left(12);
16 s[a] = s[a].wrapping_add(s[b]);
17 s[d] = (s[d] ^ s[a]).rotate_left(8);
18 s[c] = s[c].wrapping_add(s[d]);
19 s[b] = (s[b] ^ s[c]).rotate_left(7);
20}
21
22fn chacha20_block(key: &[u32; 8], counter: u32, nonce: &[u32; 3]) -> [u32; 16] {
23 let mut state = [
24 0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574, key[0], key[1], key[2], key[3], key[4],
25 key[5], key[6], key[7], counter, nonce[0], nonce[1], nonce[2],
26 ];
27 let mut w = state;
28 for _ in 0..10 {
29 qr(&mut w, 0, 4, 8, 12);
30 qr(&mut w, 1, 5, 9, 13);
31 qr(&mut w, 2, 6, 10, 14);
32 qr(&mut w, 3, 7, 11, 15);
33 qr(&mut w, 0, 5, 10, 15);
34 qr(&mut w, 1, 6, 11, 12);
35 qr(&mut w, 2, 7, 8, 13);
36 qr(&mut w, 3, 4, 9, 14);
37 }
38 for i in 0..16 {
39 state[i] = w[i].wrapping_add(state[i]);
40 }
41 state
42}
43
44fn le_u32x8(b: &[u8; 32]) -> [u32; 8] {
45 std::array::from_fn(|i| u32::from_le_bytes(b[i * 4..i * 4 + 4].try_into().unwrap()))
46}
47fn le_u32x3(b: &[u8; 12]) -> [u32; 3] {
48 std::array::from_fn(|i| u32::from_le_bytes(b[i * 4..i * 4 + 4].try_into().unwrap()))
49}
50
51pub fn chacha20_xor(key: &[u8; 32], nonce: &[u8; 12], counter: u32, data: &[u8]) -> Vec<u8> {
54 #[cfg(target_arch = "x86_64")]
55 {
56 if data.len() >= 256 && std::is_x86_feature_detected!("avx2") {
59 return unsafe { chacha20_xor_avx2(&le_u32x8(key), &le_u32x3(nonce), counter, data) };
60 }
61 }
62 chacha20_xor_scalar(key, nonce, counter, data)
63}
64
65fn chacha20_xor_scalar(key: &[u8; 32], nonce: &[u8; 12], counter: u32, data: &[u8]) -> Vec<u8> {
66 let kw = le_u32x8(key);
67 let nw = le_u32x3(nonce);
68 let mut out = Vec::with_capacity(data.len());
69 for (bi, chunk) in data.chunks(64).enumerate() {
70 let block = chacha20_block(&kw, counter.wrapping_add(bi as u32), &nw);
71 let mut ks = [0u8; 64];
72 for i in 0..16 {
73 ks[i * 4..i * 4 + 4].copy_from_slice(&block[i].to_le_bytes());
74 }
75 for (j, &b) in chunk.iter().enumerate() {
76 out.push(b ^ ks[j]);
77 }
78 }
79 out
80}
81
82#[cfg(target_arch = "x86_64")]
85#[target_feature(enable = "avx2")]
86unsafe fn chacha20_xor_avx2(key: &[u32; 8], nonce: &[u32; 3], counter: u32, data: &[u8]) -> Vec<u8> {
87 use std::arch::x86_64::*;
88 let rot16 = _mm256_setr_epi8(
89 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9, 14, 15, 12, 13, 2, 3, 0, 1, 6, 7, 4, 5, 10, 11, 8, 9,
90 14, 15, 12, 13,
91 );
92 let rot8 = _mm256_setr_epi8(
93 3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10, 15, 12, 13, 14, 3, 0, 1, 2, 7, 4, 5, 6, 11, 8, 9, 10,
94 15, 12, 13, 14,
95 );
96 #[inline(always)]
97 unsafe fn rotl12(x: std::arch::x86_64::__m256i) -> std::arch::x86_64::__m256i {
98 use std::arch::x86_64::*;
99 _mm256_or_si256(_mm256_slli_epi32::<12>(x), _mm256_srli_epi32::<20>(x))
100 }
101 #[inline(always)]
102 unsafe fn rotl7(x: std::arch::x86_64::__m256i) -> std::arch::x86_64::__m256i {
103 use std::arch::x86_64::*;
104 _mm256_or_si256(_mm256_slli_epi32::<7>(x), _mm256_srli_epi32::<25>(x))
105 }
106
107 let mut out = Vec::with_capacity(data.len());
108 let lane_ctr = _mm256_setr_epi32(0, 1, 2, 3, 4, 5, 6, 7);
109 let mut off = 0usize;
110 let mut base = counter;
111 while off < data.len() {
112 let init: [__m256i; 16] = [
113 _mm256_set1_epi32(0x6170_7865u32 as i32),
114 _mm256_set1_epi32(0x3320_646eu32 as i32),
115 _mm256_set1_epi32(0x7962_2d32u32 as i32),
116 _mm256_set1_epi32(0x6b20_6574u32 as i32),
117 _mm256_set1_epi32(key[0] as i32),
118 _mm256_set1_epi32(key[1] as i32),
119 _mm256_set1_epi32(key[2] as i32),
120 _mm256_set1_epi32(key[3] as i32),
121 _mm256_set1_epi32(key[4] as i32),
122 _mm256_set1_epi32(key[5] as i32),
123 _mm256_set1_epi32(key[6] as i32),
124 _mm256_set1_epi32(key[7] as i32),
125 _mm256_add_epi32(_mm256_set1_epi32(base as i32), lane_ctr),
126 _mm256_set1_epi32(nonce[0] as i32),
127 _mm256_set1_epi32(nonce[1] as i32),
128 _mm256_set1_epi32(nonce[2] as i32),
129 ];
130 let mut v = init;
131 macro_rules! qr {
132 ($a:tt, $b:tt, $c:tt, $d:tt) => {
133 v[$a] = _mm256_add_epi32(v[$a], v[$b]);
134 v[$d] = _mm256_shuffle_epi8(_mm256_xor_si256(v[$d], v[$a]), rot16);
135 v[$c] = _mm256_add_epi32(v[$c], v[$d]);
136 v[$b] = rotl12(_mm256_xor_si256(v[$b], v[$c]));
137 v[$a] = _mm256_add_epi32(v[$a], v[$b]);
138 v[$d] = _mm256_shuffle_epi8(_mm256_xor_si256(v[$d], v[$a]), rot8);
139 v[$c] = _mm256_add_epi32(v[$c], v[$d]);
140 v[$b] = rotl7(_mm256_xor_si256(v[$b], v[$c]));
141 };
142 }
143 for _ in 0..10 {
144 qr!(0, 4, 8, 12);
145 qr!(1, 5, 9, 13);
146 qr!(2, 6, 10, 14);
147 qr!(3, 7, 11, 15);
148 qr!(0, 5, 10, 15);
149 qr!(1, 6, 11, 12);
150 qr!(2, 7, 8, 13);
151 qr!(3, 4, 9, 14);
152 }
153 let mut words = [[0u32; 8]; 16];
154 for j in 0..16 {
155 let sum = _mm256_add_epi32(v[j], init[j]);
156 _mm256_storeu_si256(words[j].as_mut_ptr() as *mut __m256i, sum);
157 }
158 let mut ks = [0u8; 512];
159 for blk in 0..8 {
160 for j in 0..16 {
161 ks[blk * 64 + j * 4..blk * 64 + j * 4 + 4]
162 .copy_from_slice(&words[j][blk].to_le_bytes());
163 }
164 }
165 let n = (data.len() - off).min(512);
166 let dst_start = out.len();
167 out.resize(dst_start + n, 0);
168 let dst = out.as_mut_ptr().add(dst_start);
169 let src = data.as_ptr().add(off);
170 let mut i = 0;
171 while i + 32 <= n {
172 let a = _mm256_loadu_si256(src.add(i) as *const __m256i);
173 let b = _mm256_loadu_si256(ks.as_ptr().add(i) as *const __m256i);
174 _mm256_storeu_si256(dst.add(i) as *mut __m256i, _mm256_xor_si256(a, b));
175 i += 32;
176 }
177 while i < n {
178 *dst.add(i) = *src.add(i) ^ ks[i];
179 i += 1;
180 }
181 off += 512;
182 base = base.wrapping_add(8);
183 }
184 out
185}
186
187const MASK26: u32 = 0x3ff_ffff;
190
191fn poly1305_clamp(key: &[u8; 32]) -> [u32; 5] {
193 let t0 = u32::from_le_bytes([key[0], key[1], key[2], key[3]]);
194 let t1 = u32::from_le_bytes([key[4], key[5], key[6], key[7]]);
195 let t2 = u32::from_le_bytes([key[8], key[9], key[10], key[11]]);
196 let t3 = u32::from_le_bytes([key[12], key[13], key[14], key[15]]);
197 [
198 t0 & 0x3ff_ffff,
199 ((t0 >> 26) | (t1 << 6)) & 0x3ff_ff03,
200 ((t1 >> 20) | (t2 << 12)) & 0x3ff_c0ff,
201 ((t2 >> 14) | (t3 << 18)) & 0x3f0_3fff,
202 (t3 >> 8) & 0x00f_ffff,
203 ]
204}
205
206fn poly1305_block(b: &[u8; 16], hibit: u32) -> [u32; 5] {
209 let m0 = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
210 let m1 = u32::from_le_bytes([b[4], b[5], b[6], b[7]]);
211 let m2 = u32::from_le_bytes([b[8], b[9], b[10], b[11]]);
212 let m3 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
213 [
214 m0 & MASK26,
215 ((m0 >> 26) | (m1 << 6)) & MASK26,
216 ((m1 >> 20) | (m2 << 12)) & MASK26,
217 ((m2 >> 14) | (m3 << 18)) & MASK26,
218 (m3 >> 8) | (hibit << 24),
219 ]
220}
221
222fn poly1305_reduce(d: [u64; 5]) -> [u32; 5] {
225 let m = MASK26 as u64;
226 let mut c = d[0] >> 26;
227 let h0 = (d[0] & m) as u32;
228 let d1 = d[1] + c;
229 c = d1 >> 26;
230 let h1 = (d1 & m) as u32;
231 let d2 = d[2] + c;
232 c = d2 >> 26;
233 let h2 = (d2 & m) as u32;
234 let d3 = d[3] + c;
235 c = d3 >> 26;
236 let h3 = (d3 & m) as u32;
237 let d4 = d[4] + c;
238 c = d4 >> 26;
239 let h4 = (d4 & m) as u32;
240 let h0w = h0 as u64 + c * 5;
242 let mut h = [(h0w & m) as u32, h1 + (h0w >> 26) as u32, h2, h3, h4];
243 let c1 = h[1] >> 26;
244 h[1] &= MASK26;
245 h[2] += c1;
246 h
247}
248
249fn poly1305_mul(a: &[u32; 5], b: &[u32; 5]) -> [u32; 5] {
251 let (a0, a1, a2, a3, a4) = (a[0] as u64, a[1] as u64, a[2] as u64, a[3] as u64, a[4] as u64);
252 let (b0, b1, b2, b3, b4) = (b[0] as u64, b[1] as u64, b[2] as u64, b[3] as u64, b[4] as u64);
253 let (s1, s2, s3, s4) = (b1 * 5, b2 * 5, b3 * 5, b4 * 5);
254 poly1305_reduce([
255 a0 * b0 + a1 * s4 + a2 * s3 + a3 * s2 + a4 * s1,
256 a0 * b1 + a1 * b0 + a2 * s4 + a3 * s3 + a4 * s2,
257 a0 * b2 + a1 * b1 + a2 * b0 + a3 * s4 + a4 * s3,
258 a0 * b3 + a1 * b2 + a2 * b1 + a3 * b0 + a4 * s4,
259 a0 * b4 + a1 * b3 + a2 * b2 + a3 * b1 + a4 * b0,
260 ])
261}
262
263fn poly1305_finalize(h: [u32; 5], key: &[u8; 32]) -> [u8; 16] {
265 let (mut h0, mut h1, mut h2, mut h3, mut h4) = (h[0], h[1], h[2], h[3], h[4]);
266 let mut c;
267 c = h1 >> 26;
268 h1 &= 0x3ff_ffff;
269 h2 += c;
270 c = h2 >> 26;
271 h2 &= 0x3ff_ffff;
272 h3 += c;
273 c = h3 >> 26;
274 h3 &= 0x3ff_ffff;
275 h4 += c;
276 c = h4 >> 26;
277 h4 &= 0x3ff_ffff;
278 h0 += c * 5;
279 c = h0 >> 26;
280 h0 &= 0x3ff_ffff;
281 h1 += c;
282 let mut g0 = h0.wrapping_add(5);
283 c = g0 >> 26;
284 g0 &= 0x3ff_ffff;
285 let mut g1 = h1.wrapping_add(c);
286 c = g1 >> 26;
287 g1 &= 0x3ff_ffff;
288 let mut g2 = h2.wrapping_add(c);
289 c = g2 >> 26;
290 g2 &= 0x3ff_ffff;
291 let mut g3 = h3.wrapping_add(c);
292 c = g3 >> 26;
293 g3 &= 0x3ff_ffff;
294 let g4 = h4.wrapping_add(c).wrapping_sub(1 << 26);
295 let mask = (g4 >> 31).wrapping_sub(1);
296 let nmask = !mask;
297 h0 = (h0 & nmask) | (g0 & mask);
298 h1 = (h1 & nmask) | (g1 & mask);
299 h2 = (h2 & nmask) | (g2 & mask);
300 h3 = (h3 & nmask) | (g3 & mask);
301 h4 = (h4 & nmask) | (g4 & mask);
302 let f0 = (h0 | (h1 << 26)) as u64;
303 let f1 = ((h1 >> 6) | (h2 << 20)) as u64;
304 let f2 = ((h2 >> 12) | (h3 << 14)) as u64;
305 let f3 = ((h3 >> 18) | (h4 << 8)) as u64;
306 let s0 = u32::from_le_bytes([key[16], key[17], key[18], key[19]]) as u64;
307 let s1 = u32::from_le_bytes([key[20], key[21], key[22], key[23]]) as u64;
308 let s2 = u32::from_le_bytes([key[24], key[25], key[26], key[27]]) as u64;
309 let s3 = u32::from_le_bytes([key[28], key[29], key[30], key[31]]) as u64;
310 let mut tag = [0u8; 16];
311 let mut f = f0 + s0;
312 tag[0..4].copy_from_slice(&(f as u32).to_le_bytes());
313 f = f1 + s1 + (f >> 32);
314 tag[4..8].copy_from_slice(&(f as u32).to_le_bytes());
315 f = f2 + s2 + (f >> 32);
316 tag[8..12].copy_from_slice(&(f as u32).to_le_bytes());
317 f = f3 + s3 + (f >> 32);
318 tag[12..16].copy_from_slice(&(f as u32).to_le_bytes());
319 tag
320}
321
322#[inline]
324fn poly1305_absorb(h: &mut [u32; 5], msg: &[u8], i: usize, r: &[u32; 5]) {
325 let n = (msg.len() - i).min(16);
326 let mut b = [0u8; 16];
327 b[..n].copy_from_slice(&msg[i..i + n]);
328 let hibit = if n == 16 {
329 1
330 } else {
331 b[n] = 1;
332 0
333 };
334 let m = poly1305_block(&b, hibit);
335 let sum = [h[0] + m[0], h[1] + m[1], h[2] + m[2], h[3] + m[3], h[4] + m[4]];
336 *h = poly1305_mul(&sum, r);
337}
338
339pub fn poly1305(key: &[u8; 32], msg: &[u8]) -> [u8; 16] {
342 #[cfg(target_arch = "x86_64")]
343 {
344 if msg.len() >= 256 && std::is_x86_feature_detected!("avx2") {
345 return unsafe { poly1305_avx2(key, msg) };
346 }
347 }
348 poly1305_scalar(key, msg)
349}
350
351fn poly1305_scalar(key: &[u8; 32], msg: &[u8]) -> [u8; 16] {
352 let r = poly1305_clamp(key);
353 let mut h = [0u32; 5];
354 let mut i = 0;
355 while i < msg.len() {
356 poly1305_absorb(&mut h, msg, i, &r);
357 i += 16;
358 }
359 poly1305_finalize(h, key)
360}
361
362#[cfg(target_arch = "x86_64")]
364#[target_feature(enable = "avx2")]
365#[inline]
366unsafe fn poly1305_hsum(v: std::arch::x86_64::__m256i) -> u64 {
367 use std::arch::x86_64::*;
368 let lo = _mm256_castsi256_si128(v);
369 let hi = _mm256_extracti128_si256::<1>(v);
370 let s = _mm_add_epi64(lo, hi);
371 let s2 = _mm_add_epi64(s, _mm_unpackhi_epi64(s, s));
372 _mm_cvtsi128_si64(s2) as u64
373}
374
375#[cfg(target_arch = "x86_64")]
377#[target_feature(enable = "avx2")]
378unsafe fn poly1305_vectors(
379 r: &[u32; 5],
380) -> ([std::arch::x86_64::__m256i; 5], [std::arch::x86_64::__m256i; 5]) {
381 use std::arch::x86_64::*;
382 let r2 = poly1305_mul(r, r);
383 let r3 = poly1305_mul(&r2, r);
384 let r4 = poly1305_mul(&r2, &r2);
385 let mut vb = [_mm256_setzero_si256(); 5];
386 let mut vs = [_mm256_setzero_si256(); 5];
387 for l in 0..5 {
388 vb[l] = _mm256_set_epi32(0, r[l] as i32, 0, r2[l] as i32, 0, r3[l] as i32, 0, r4[l] as i32);
389 vs[l] = _mm256_set_epi32(
390 0, (r[l] * 5) as i32, 0, (r2[l] * 5) as i32, 0, (r3[l] * 5) as i32, 0, (r4[l] * 5) as i32,
391 );
392 }
393 (vb, vs)
394}
395
396#[cfg(target_arch = "x86_64")]
399#[target_feature(enable = "avx2")]
400unsafe fn poly1305_groups(
401 h: &mut [u32; 5],
402 data: &[u8],
403 vb: &[std::arch::x86_64::__m256i; 5],
404 vs: &[std::arch::x86_64::__m256i; 5],
405) -> usize {
406 use std::arch::x86_64::*;
407 let groups = (data.len() / 16) / 4;
408 for g in 0..groups {
409 let base = g * 64;
410 let mut t = [[0u32; 5]; 4];
411 for k in 0..4 {
412 let b: &[u8; 16] = data[base + k * 16..base + k * 16 + 16].try_into().unwrap();
413 t[k] = poly1305_block(b, 1);
414 }
415 for l in 0..5 {
416 t[0][l] += h[l];
417 }
418 let mut va = [_mm256_setzero_si256(); 5];
419 for l in 0..5 {
420 va[l] = _mm256_set_epi32(
421 0, t[3][l] as i32, 0, t[2][l] as i32, 0, t[1][l] as i32, 0, t[0][l] as i32,
422 );
423 }
424 macro_rules! mul {
425 ($x:expr, $y:expr) => {
426 _mm256_mul_epu32($x, $y)
427 };
428 }
429 macro_rules! add5 {
430 ($a:expr, $b:expr, $c:expr, $d:expr, $e:expr) => {
431 _mm256_add_epi64(
432 _mm256_add_epi64(_mm256_add_epi64($a, $b), _mm256_add_epi64($c, $d)),
433 $e,
434 )
435 };
436 }
437 let d = [
438 add5!(mul!(va[0], vb[0]), mul!(va[1], vs[4]), mul!(va[2], vs[3]), mul!(va[3], vs[2]), mul!(va[4], vs[1])),
439 add5!(mul!(va[0], vb[1]), mul!(va[1], vb[0]), mul!(va[2], vs[4]), mul!(va[3], vs[3]), mul!(va[4], vs[2])),
440 add5!(mul!(va[0], vb[2]), mul!(va[1], vb[1]), mul!(va[2], vb[0]), mul!(va[3], vs[4]), mul!(va[4], vs[3])),
441 add5!(mul!(va[0], vb[3]), mul!(va[1], vb[2]), mul!(va[2], vb[1]), mul!(va[3], vb[0]), mul!(va[4], vs[4])),
442 add5!(mul!(va[0], vb[4]), mul!(va[1], vb[3]), mul!(va[2], vb[2]), mul!(va[3], vb[1]), mul!(va[4], vb[0])),
443 ];
444 *h = poly1305_reduce([
445 poly1305_hsum(d[0]),
446 poly1305_hsum(d[1]),
447 poly1305_hsum(d[2]),
448 poly1305_hsum(d[3]),
449 poly1305_hsum(d[4]),
450 ]);
451 }
452 groups * 64
453}
454
455#[cfg(target_arch = "x86_64")]
457#[target_feature(enable = "avx2")]
458unsafe fn poly1305_avx2(key: &[u8; 32], msg: &[u8]) -> [u8; 16] {
459 let r = poly1305_clamp(key);
460 let (vb, vs) = poly1305_vectors(&r);
461 let mut h = [0u32; 5];
462 let mut i = poly1305_groups(&mut h, msg, &vb, &vs);
463 while i < msg.len() {
464 poly1305_absorb(&mut h, msg, i, &r);
465 i += 16;
466 }
467 poly1305_finalize(h, key)
468}
469
470#[inline]
474fn poly1305_absorb_padded(h: &mut [u32; 5], data: &[u8], r: &[u32; 5]) {
475 let n = data.len().min(16);
476 let mut b = [0u8; 16];
477 b[..n].copy_from_slice(&data[..n]);
478 let m = poly1305_block(&b, 1);
479 let sum = [h[0] + m[0], h[1] + m[1], h[2] + m[2], h[3] + m[3], h[4] + m[4]];
480 *h = poly1305_mul(&sum, r);
481}
482
483fn poly1305_aead(otk: &[u8; 32], aad: &[u8], ct: &[u8]) -> [u8; 16] {
487 let r = poly1305_clamp(otk);
488 let mut h = [0u32; 5];
489 let mut a = 0;
490 while a < aad.len() {
491 poly1305_absorb_padded(&mut h, &aad[a..(a + 16).min(aad.len())], &r);
492 a += 16;
493 }
494 let mut i = 0usize;
495 #[cfg(target_arch = "x86_64")]
496 {
497 if ct.len() >= 256 && std::is_x86_feature_detected!("avx2") {
498 unsafe {
499 let (vb, vs) = poly1305_vectors(&r);
500 i = poly1305_groups(&mut h, ct, &vb, &vs);
501 }
502 }
503 }
504 while i < ct.len() {
505 poly1305_absorb_padded(&mut h, &ct[i..(i + 16).min(ct.len())], &r);
506 i += 16;
507 }
508 let mut lb = [0u8; 16];
509 lb[0..8].copy_from_slice(&(aad.len() as u64).to_le_bytes());
510 lb[8..16].copy_from_slice(&(ct.len() as u64).to_le_bytes());
511 poly1305_absorb_padded(&mut h, &lb, &r);
512 poly1305_finalize(h, otk)
513}
514
515fn poly1305_key_gen(key: &[u8; 32], nonce: &[u8; 12]) -> [u8; 32] {
518 let block = chacha20_block(&le_u32x8(key), 0, &le_u32x3(nonce));
519 let mut otk = [0u8; 32];
520 for i in 0..8 {
521 otk[i * 4..i * 4 + 4].copy_from_slice(&block[i].to_le_bytes());
522 }
523 otk
524}
525
526#[cfg(test)]
529fn mac_data(aad: &[u8], ciphertext: &[u8]) -> Vec<u8> {
530 let mut m = Vec::with_capacity(aad.len() + ciphertext.len() + 32);
531 m.extend_from_slice(aad);
532 m.resize(m.len() + ((16 - aad.len() % 16) % 16), 0);
533 m.extend_from_slice(ciphertext);
534 m.resize(m.len() + ((16 - ciphertext.len() % 16) % 16), 0);
535 m.extend_from_slice(&(aad.len() as u64).to_le_bytes());
536 m.extend_from_slice(&(ciphertext.len() as u64).to_le_bytes());
537 m
538}
539
540pub fn chacha20poly1305_seal(key: &[u8; 32], nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
542 let otk = poly1305_key_gen(key, nonce);
543 let mut out = chacha20_xor(key, nonce, 1, plaintext);
544 let tag = poly1305_aead(&otk, aad, &out);
545 out.extend_from_slice(&tag);
546 out
547}
548
549pub fn chacha20poly1305_open(
551 key: &[u8; 32],
552 nonce: &[u8; 12],
553 aad: &[u8],
554 sealed: &[u8],
555) -> Option<Vec<u8>> {
556 if sealed.len() < 16 {
557 return None;
558 }
559 let (ciphertext, tag) = sealed.split_at(sealed.len() - 16);
560 let otk = poly1305_key_gen(key, nonce);
561 let expected = poly1305_aead(&otk, aad, ciphertext);
562 let mut diff = 0u8;
563 for i in 0..16 {
564 diff |= expected[i] ^ tag[i];
565 }
566 if diff != 0 {
567 return None;
568 }
569 Some(chacha20_xor(key, nonce, 1, ciphertext))
570}
571
572fn bytes(s: &[i64]) -> Vec<u8> {
575 s.iter().map(|&x| x.rem_euclid(256) as u8).collect()
576}
577fn seq(v: &[u8]) -> logicaffeine_data::LogosSeq<i64> {
578 logicaffeine_data::LogosSeq::from_vec(v.iter().map(|&b| b as i64).collect())
579}
580
581pub fn chacha20_encrypt_seq(
585 key: &[i64],
586 nonce: &[i64],
587 counter: i64,
588 data: &[i64],
589) -> logicaffeine_data::LogosSeq<i64> {
590 let k: [u8; 32] = bytes(key)[..32].try_into().unwrap();
591 let n: [u8; 12] = bytes(nonce)[..12].try_into().unwrap();
592 seq(&chacha20_xor(&k, &n, counter as u32, &bytes(data)))
593}
594
595pub fn poly1305_seq(key: &[i64], msg: &[i64]) -> logicaffeine_data::LogosSeq<i64> {
597 let k: [u8; 32] = bytes(key)[..32].try_into().unwrap();
598 seq(&poly1305(&k, &bytes(msg)))
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 fn hex(b: &[u8]) -> String {
606 b.iter().map(|x| format!("{x:02x}")).collect()
607 }
608 fn unhex(s: &str) -> Vec<u8> {
609 (0..s.len()).step_by(2).map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()).collect()
610 }
611
612 #[test]
613 fn chacha20_avx2_matches_scalar_over_lengths() {
614 let key: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(31).wrapping_add(7));
617 let nonce: [u8; 12] = std::array::from_fn(|i| (i as u8).wrapping_mul(17));
618 let mut s = 0x1234_5678u64;
619 let mut data = vec![0u8; 2000];
620 for b in data.iter_mut() {
621 s = s.wrapping_mul(6364136223846793005).wrapping_add(1);
622 *b = (s >> 33) as u8;
623 }
624 for &len in &[0usize, 1, 63, 64, 65, 511, 512, 513, 1024, 1100, 2000] {
625 for &ctr in &[0u32, 1, 7, u32::MAX - 3] {
626 let want = chacha20_xor_scalar(&key, &nonce, ctr, &data[..len]);
627 let got = chacha20_xor(&key, &nonce, ctr, &data[..len]);
628 assert_eq!(got, want, "AVX2 == scalar, len {len} ctr {ctr}");
629 }
630 }
631 }
632
633 #[test]
634 fn poly1305_avx2_matches_scalar_over_lengths() {
635 let key: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(29).wrapping_add(3));
638 let mut s = 0xdead_beefu64;
639 let mut msg = vec![0u8; 4096];
640 for b in msg.iter_mut() {
641 s = s.wrapping_mul(6364136223846793005).wrapping_add(1);
642 *b = (s >> 33) as u8;
643 }
644 for &len in &[
645 0usize, 1, 16, 17, 63, 64, 255, 256, 257, 271, 512, 1000, 1024, 1025, 4000, 4096,
646 ] {
647 let want = poly1305_scalar(&key, &msg[..len]);
648 let got = poly1305(&key, &msg[..len]);
649 assert_eq!(got, want, "AVX2 == scalar Poly1305, len {len}");
650 }
651 }
652
653 #[test]
654 fn poly1305_aead_streamed_matches_mac_data() {
655 let mut s = 0x0123_4567_89ab_cdefu64;
658 let mut next = |buf: &mut [u8]| {
659 for b in buf.iter_mut() {
660 s = s.wrapping_mul(6364136223846793005).wrapping_add(1);
661 *b = (s >> 33) as u8;
662 }
663 };
664 let mut otk = [0u8; 32];
665 next(&mut otk);
666 let mut ct = vec![0u8; 4100];
667 next(&mut ct);
668 let mut aad = vec![0u8; 64];
669 next(&mut aad);
670 for &al in &[0usize, 1, 11, 16, 17, 32, 48] {
671 for &cl in &[0usize, 1, 15, 16, 63, 64, 255, 256, 257, 271, 1024, 1025, 4100] {
672 let streamed = poly1305_aead(&otk, &aad[..al], &ct[..cl]);
673 let reference = poly1305(&otk, &mac_data(&aad[..al], &ct[..cl]));
674 assert_eq!(streamed, reference, "streamed AEAD MAC, aad {al} ct {cl}");
675 }
676 }
677 }
678
679 #[test]
680 fn chacha20_xor_matches_rfc8439_2_4_2() {
681 let key: [u8; 32] = std::array::from_fn(|i| i as u8);
683 let nonce: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0x4a, 0, 0, 0, 0];
684 let plaintext = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
685 let ct = chacha20_xor(&key, &nonce, 1, plaintext);
686 assert_eq!(
687 hex(&ct),
688 "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d",
689 "ChaCha20 keystream XOR must match RFC 8439 §2.4.2"
690 );
691 }
692
693 #[test]
694 fn poly1305_matches_rfc8439_2_5_2() {
695 let key: [u8; 32] =
696 unhex("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b")[..32]
697 .try_into()
698 .unwrap();
699 let msg = b"Cryptographic Forum Research Group";
700 let tag = poly1305(&key, msg);
701 assert_eq!(hex(&tag), "a8061dc1305136c6c22b8baf0c0127a9", "Poly1305 must match RFC 8439 §2.5.2");
702 }
703
704 #[test]
705 fn aead_seals_and_opens_round_trip_and_rejects_tamper() {
706 let key: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(3));
707 let nonce: [u8; 12] = std::array::from_fn(|i| i as u8 + 0x40);
708 let aad = b"channel-suite-id-v1";
709 let msg = b"post-quantum sealed wire payload";
710 let sealed = chacha20poly1305_seal(&key, &nonce, aad, msg);
711 assert_eq!(sealed.len(), msg.len() + 16, "ciphertext + 16-byte tag");
712 assert_eq!(chacha20poly1305_open(&key, &nonce, aad, &sealed).as_deref(), Some(&msg[..]));
713 let mut t = sealed.clone();
715 t[0] ^= 1;
716 assert_eq!(chacha20poly1305_open(&key, &nonce, aad, &t), None, "ciphertext tamper rejects");
717 let mut t = sealed.clone();
718 let last = t.len() - 1;
719 t[last] ^= 1;
720 assert_eq!(chacha20poly1305_open(&key, &nonce, aad, &t), None, "tag tamper rejects");
721 assert_eq!(chacha20poly1305_open(&key, &nonce, b"wrong-aad", &sealed), None, "AAD mismatch rejects");
722 }
723
724 #[test]
725 #[ignore = "benchmark — run with --ignored --nocapture"]
726 fn bench_aead_vs_rustcrypto() {
727 use chacha20poly1305::aead::{Aead, KeyInit, Payload};
728 use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce};
729 use std::time::Instant;
730
731 let key: [u8; 32] = std::array::from_fn(|i| (i as u8).wrapping_mul(7).wrapping_add(1));
732 let nonce: [u8; 12] = std::array::from_fn(|i| i as u8 + 0x10);
733 let aad = b"channel-aad";
734 let cipher = ChaCha20Poly1305::new(Key::from_slice(&key));
735 let n = Nonce::from_slice(&nonce);
736
737 for &len in &[64usize, 1024, 16384] {
738 let pt = vec![0x5au8; len];
739 let sealed = chacha20poly1305_seal(&key, &nonce, aad, &pt);
740 let sealed_o = cipher.encrypt(n, Payload { msg: &pt, aad }).unwrap();
741 assert_eq!(sealed, sealed_o, "our AEAD must match RustCrypto for {len}B");
742
743 const ITERS: u32 = 20000;
744 macro_rules! mbps {
747 ($body:expr) => {{
748 for _ in 0..50 { std::hint::black_box($body); }
749 let mut best = f64::INFINITY;
750 for _ in 0..15 {
751 let t = Instant::now();
752 for _ in 0..ITERS { std::hint::black_box($body); }
753 best = best.min(t.elapsed().as_secs_f64());
754 }
755 (len as f64 * ITERS as f64) / best / 1e6
756 }};
757 }
758 let ours = mbps!(chacha20poly1305_seal(&key, &nonce, aad, &pt));
759 let theirs = mbps!(cipher.encrypt(n, Payload { msg: &pt, aad }).unwrap());
760 eprintln!(
761 "seal {:>6}B: ours {:>8.0} MB/s RustCrypto {:>8.0} MB/s ({:.2}x)",
762 len, ours, theirs, theirs / ours
763 );
764 }
765 }
766
767 #[test]
768 fn aead_matches_rfc8439_2_8_2() {
769 let key: [u8; 32] =
770 unhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f")[..32]
771 .try_into()
772 .unwrap();
773 let nonce: [u8; 12] = unhex("070000004041424344454647")[..12].try_into().unwrap();
774 let aad = unhex("50515253c0c1c2c3c4c5c6c7");
775 let plaintext = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.";
776 let sealed = chacha20poly1305_seal(&key, &nonce, &aad, plaintext);
777 assert_eq!(
779 hex(&sealed),
780 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd0600691",
781 "AEAD seal must match RFC 8439 §2.8.2"
782 );
783 assert_eq!(
784 chacha20poly1305_open(&key, &nonce, &aad, &sealed).as_deref(),
785 Some(&plaintext[..]),
786 "AEAD open round-trips the RFC vector"
787 );
788 }
789}