1use ic_core::traits::internal::Mac;
11
12use ic_core::traits::{Aead, Algorithm, RandomSource, SelfTest};
13use ic_core::{ensure, Result, Zeroize};
14
15const SIGMA: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
16
17#[inline(always)]
18fn quarter_round(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
19 s[a] = s[a].wrapping_add(s[b]);
20 s[d] = (s[d] ^ s[a]).rotate_left(16);
21 s[c] = s[c].wrapping_add(s[d]);
22 s[b] = (s[b] ^ s[c]).rotate_left(12);
23 s[a] = s[a].wrapping_add(s[b]);
24 s[d] = (s[d] ^ s[a]).rotate_left(8);
25 s[c] = s[c].wrapping_add(s[d]);
26 s[b] = (s[b] ^ s[c]).rotate_left(7);
27}
28
29#[inline(always)]
31fn rounds(working: &mut [u32; 16]) {
32 for _ in 0..10 {
33 quarter_round(working, 0, 4, 8, 12);
35 quarter_round(working, 1, 5, 9, 13);
36 quarter_round(working, 2, 6, 10, 14);
37 quarter_round(working, 3, 7, 11, 15);
38 quarter_round(working, 0, 5, 10, 15);
40 quarter_round(working, 1, 6, 11, 12);
41 quarter_round(working, 2, 7, 8, 13);
42 quarter_round(working, 3, 4, 9, 14);
43 }
44}
45
46fn hchacha20(key: &[u8; 32], nonce: &[u8; 16]) -> [u8; 32] {
53 let mut state = [0u32; 16];
54 state[..4].copy_from_slice(&SIGMA);
55 for i in 0..8 {
56 state[4 + i] =
57 u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
58 }
59 for i in 0..4 {
60 state[12 + i] = u32::from_le_bytes([
61 nonce[i * 4],
62 nonce[i * 4 + 1],
63 nonce[i * 4 + 2],
64 nonce[i * 4 + 3],
65 ]);
66 }
67 rounds(&mut state);
68 let mut subkey = [0u8; 32];
69 for i in 0..4 {
70 subkey[i * 4..i * 4 + 4].copy_from_slice(&state[i].to_le_bytes());
71 subkey[16 + i * 4..16 + i * 4 + 4].copy_from_slice(&state[12 + i].to_le_bytes());
72 }
73 state.zeroize();
74 subkey
75}
76
77fn chacha20_block(key: &[u8; 32], counter: u32, nonce: &[u8; 12], out: &mut [u8; 64]) {
79 let mut state = [0u32; 16];
80 state[..4].copy_from_slice(&SIGMA);
81 for i in 0..8 {
82 state[4 + i] =
83 u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
84 }
85 state[12] = counter;
86 for i in 0..3 {
87 state[13 + i] = u32::from_le_bytes([
88 nonce[i * 4],
89 nonce[i * 4 + 1],
90 nonce[i * 4 + 2],
91 nonce[i * 4 + 3],
92 ]);
93 }
94
95 let mut working = state;
96 rounds(&mut working);
97 for i in 0..16 {
98 let v = working[i].wrapping_add(state[i]);
99 out[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
100 }
101 working.zeroize();
102 state.zeroize();
103}
104
105#[cfg(all(target_arch = "x86_64", feature = "std"))]
109mod avx2;
110#[cfg(all(target_arch = "x86_64", any(feature = "std", feature = "ct-probes")))]
111#[cfg_attr(not(feature = "std"), allow(dead_code))]
112pub(crate) mod poly_avx2;
113#[cfg(all(target_arch = "x86_64", any(feature = "std", feature = "ct-probes")))]
114#[cfg_attr(not(feature = "std"), allow(dead_code))]
115pub(crate) mod zmm;
116
117#[cfg(all(target_arch = "x86_64", feature = "std"))]
120macro_rules! cached_detect {
121 ($($f:tt),+) => {{
122 use core::sync::atomic::{AtomicU8, Ordering};
123 static CACHED: AtomicU8 = AtomicU8::new(0);
124 match CACHED.load(Ordering::Relaxed) {
125 1 => true,
126 2 => false,
127 _ => {
128 let have = true $(&& std::is_x86_feature_detected!($f))+;
129 CACHED.store(u8::from(!have) + 1, Ordering::Relaxed);
130 have
131 }
132 }
133 }};
134}
135
136#[cfg(all(target_arch = "x86_64", feature = "std"))]
138fn avx2() -> bool {
139 cached_detect!("avx2")
140}
141
142#[cfg(all(target_arch = "x86_64", feature = "std"))]
145fn zmm_available() -> bool {
146 cached_detect!("avx512f")
147}
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151enum Backend {
152 Portable,
154 #[cfg(all(target_arch = "x86_64", feature = "std"))]
156 Avx2,
157 #[cfg(all(target_arch = "x86_64", feature = "std"))]
159 Zmm,
160}
161
162fn best_backend() -> Backend {
164 #[cfg(all(target_arch = "x86_64", feature = "std"))]
165 {
166 if zmm_available() && avx2() {
167 return Backend::Zmm;
168 }
169 if avx2() {
170 return Backend::Avx2;
171 }
172 }
173 Backend::Portable
174}
175
176pub fn chacha20_xor(key: &[u8], nonce: &[u8], counter: u32, data: &mut [u8]) -> Result<()> {
178 ic_core::module::operational()?;
179 ensure!(
180 key.len() == 32,
181 InvalidLength,
182 "chacha20 key must be 32 bytes"
183 );
184 ensure!(
185 nonce.len() == 12,
186 InvalidLength,
187 "chacha20 nonce must be 12 bytes"
188 );
189 let mut k = [0u8; 32];
190 k.copy_from_slice(key);
191 let mut n = [0u8; 12];
192 n.copy_from_slice(nonce);
193 let result = xor_keystream(best_backend(), &k, &n, counter, data);
194 k.zeroize();
195 result
196}
197
198fn xor_keystream(
208 backend: Backend,
209 k: &[u8; 32],
210 n: &[u8; 12],
211 counter: u32,
212 data: &mut [u8],
213) -> Result<()> {
214 let blocks = (data.len() as u64).div_ceil(64);
215 ensure!(
216 u64::from(counter) + blocks <= 1 << 32,
217 CounterExhausted,
218 "chacha20 block counter"
219 );
220 let _ = backend;
221 #[allow(unused_mut)]
222 let (mut data, mut counter) = (data, counter);
223
224 #[cfg(all(target_arch = "x86_64", feature = "std"))]
226 if backend == Backend::Zmm && data.len() >= zmm::STRIDE {
227 let mut start = zmm::Start::new(k, n, counter);
228 let mut groups = data.chunks_exact_mut(zmm::STRIDE);
229 for group in &mut groups {
230 if let Ok(group) = <&mut [u8; zmm::STRIDE]>::try_from(group) {
231 unsafe { zmm::group(&start, group) };
234 }
235 counter = counter.wrapping_add(zmm::LANES as u32);
236 start.set_counter(counter);
237 }
238 data = groups.into_remainder();
239 }
240
241 #[cfg(all(target_arch = "x86_64", feature = "std"))]
243 if backend != Backend::Portable {
244 let mut groups = data.chunks_exact_mut(avx2::STRIDE);
245 for group in &mut groups {
246 unsafe { avx2::eight_blocks(k, n, counter, group) };
249 counter = counter.wrapping_add(avx2::LANES as u32);
250 }
251 data = groups.into_remainder();
252 }
253
254 let mut block = [0u8; 64];
256 for (i, chunk) in data.chunks_mut(64).enumerate() {
257 chacha20_block(k, counter.wrapping_add(i as u32), n, &mut block);
258 for (d, b) in chunk.iter_mut().zip(block.iter()) {
259 *d ^= b;
260 }
261 }
262 block.zeroize();
263 Ok(())
264}
265
266#[derive(Clone)]
270pub struct Poly1305 {
271 r: [u32; 5],
272 s: [u32; 4],
273 powers: [[u32; 5]; 3],
288 acc: [u32; 5],
289 buf: [u8; 16],
290 buffered: usize,
291}
292
293impl Drop for Poly1305 {
294 fn drop(&mut self) {
295 self.r.zeroize();
296 self.s.zeroize();
297 for p in &mut self.powers {
298 p.zeroize();
299 }
300 self.acc.zeroize();
301 self.buf.zeroize();
302 }
303}
304
305impl Algorithm for Poly1305 {
306 const ID: &'static str = "poly1305";
307 const NAME: &'static str = "Poly1305";
308}
309
310impl Poly1305 {
311 fn absorb_block(&mut self, block: &[u8]) {
314 let mut b = [0u8; 16];
315 b[..block.len()].copy_from_slice(block);
316 let pad = if block.len() < 16 {
317 b[block.len()] = 1;
318 0
319 } else {
320 1 << 24
321 };
322
323 let t0 = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
324 let t1 = u32::from_le_bytes([b[4], b[5], b[6], b[7]]);
325 let t2 = u32::from_le_bytes([b[8], b[9], b[10], b[11]]);
326 let t3 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
327
328 self.acc[0] += t0 & 0x3ff_ffff;
329 self.acc[1] += ((t0 >> 26) | (t1 << 6)) & 0x3ff_ffff;
330 self.acc[2] += ((t1 >> 20) | (t2 << 12)) & 0x3ff_ffff;
331 self.acc[3] += ((t2 >> 14) | (t3 << 18)) & 0x3ff_ffff;
332 self.acc[4] += (t3 >> 8) | pad;
333
334 self.multiply_by_r();
335 }
336
337 fn multiply_by_r(&mut self) {
338 self.acc = reduce(mul_unreduced(self.acc, self.r, five_times(self.r)));
339 }
340
341 fn absorb4(&mut self, blocks: &[u8]) {
345 debug_assert_eq!(blocks.len(), 64);
346 let mut m = [[0u32; 5]; 4];
347 for (i, slot) in m.iter_mut().enumerate() {
348 let b = &blocks[i * 16..(i + 1) * 16];
349 let t0 = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
350 let t1 = u32::from_le_bytes([b[4], b[5], b[6], b[7]]);
351 let t2 = u32::from_le_bytes([b[8], b[9], b[10], b[11]]);
352 let t3 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
353 *slot = [
355 t0 & 0x3ff_ffff,
356 ((t0 >> 26) | (t1 << 6)) & 0x3ff_ffff,
357 ((t1 >> 20) | (t2 << 12)) & 0x3ff_ffff,
358 ((t2 >> 14) | (t3 << 18)) & 0x3ff_ffff,
359 (t3 >> 8) | (1 << 24),
360 ];
361 }
362
363 for (limb, a) in m[0].iter_mut().zip(self.acc) {
365 *limb += a;
366 }
367
368 let multipliers = [self.powers[2], self.powers[1], self.powers[0], self.r];
369 let mut d = [0u64; 5];
370 for (block, r) in m.iter().zip(multipliers) {
371 let part = mul_unreduced(*block, r, five_times(r));
372 for (acc, term) in d.iter_mut().zip(part) {
373 *acc += term;
374 }
375 }
376 self.acc = reduce(d);
377 }
378}
379
380#[cfg(all(target_arch = "x86_64", feature = "std"))]
384const POLY_AVX2_MIN: usize = 128;
385
386fn five_times(r: [u32; 5]) -> [u32; 4] {
388 [r[1] * 5, r[2] * 5, r[3] * 5, r[4] * 5]
389}
390
391fn mul_unreduced(h: [u32; 5], r: [u32; 5], s: [u32; 4]) -> [u64; 5] {
397 let m = |a: u32, b: u32| a as u64 * b as u64;
398 [
399 m(h[0], r[0]) + m(h[1], s[3]) + m(h[2], s[2]) + m(h[3], s[1]) + m(h[4], s[0]),
400 m(h[0], r[1]) + m(h[1], r[0]) + m(h[2], s[3]) + m(h[3], s[2]) + m(h[4], s[1]),
401 m(h[0], r[2]) + m(h[1], r[1]) + m(h[2], r[0]) + m(h[3], s[3]) + m(h[4], s[2]),
402 m(h[0], r[3]) + m(h[1], r[2]) + m(h[2], r[1]) + m(h[3], r[0]) + m(h[4], s[3]),
403 m(h[0], r[4]) + m(h[1], r[3]) + m(h[2], r[2]) + m(h[3], r[1]) + m(h[4], r[0]),
404 ]
405}
406
407fn reduce(d: [u64; 5]) -> [u32; 5] {
421 const MASK: u64 = 0x3ff_ffff;
422 let mut c = d[0] >> 26;
423 let a0 = d[0] & MASK;
424 let d1 = d[1] + c;
425 c = d1 >> 26;
426 let a1 = d1 & MASK;
427 let d2 = d[2] + c;
428 c = d2 >> 26;
429 let a2 = d2 & MASK;
430 let d3 = d[3] + c;
431 c = d3 >> 26;
432 let a3 = d3 & MASK;
433 let d4 = d[4] + c;
434 c = d4 >> 26;
435 let a4 = d4 & MASK;
436 let folded = a0 + c * 5;
439 let a0 = folded & MASK;
440 let a1 = a1 + (folded >> 26);
443 [a0 as u32, a1 as u32, a2 as u32, a3 as u32, a4 as u32]
444}
445
446impl Mac for Poly1305 {
447 type Tag = [u8; 16];
448 const TAG_LEN: usize = 16;
449
450 fn new(key: &[u8]) -> Result<Self> {
451 ic_core::module::operational()?;
452 ensure!(
453 key.len() == 32,
454 InvalidLength,
455 "poly1305 key must be 32 bytes"
456 );
457 let t0 = u32::from_le_bytes([key[0], key[1], key[2], key[3]]);
458 let t1 = u32::from_le_bytes([key[4], key[5], key[6], key[7]]);
459 let t2 = u32::from_le_bytes([key[8], key[9], key[10], key[11]]);
460 let t3 = u32::from_le_bytes([key[12], key[13], key[14], key[15]]);
461 let r = [
463 t0 & 0x3ff_ffff,
464 ((t0 >> 26) | (t1 << 6)) & 0x3ff_ff03,
465 ((t1 >> 20) | (t2 << 12)) & 0x3ff_c0ff,
466 ((t2 >> 14) | (t3 << 18)) & 0x3f0_3fff,
467 (t3 >> 8) & 0x000_fffff,
468 ];
469 let s = [
470 u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
471 u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
472 u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
473 u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
474 ];
475 let rr = five_times(r);
478 let mut powers = [[0u32; 5]; 3];
479 let mut p = r;
480 for slot in powers.iter_mut() {
481 p = reduce(mul_unreduced(p, r, rr));
482 *slot = p;
483 }
484
485 Ok(Self {
486 r,
487 s,
488 powers,
489 acc: [0u32; 5],
490 buf: [0u8; 16],
491 buffered: 0,
492 })
493 }
494
495 fn update(&mut self, mut data: &[u8]) {
496 if self.buffered > 0 {
497 let take = core::cmp::min(16 - self.buffered, data.len());
498 self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
499 self.buffered += take;
500 data = &data[take..];
501 if self.buffered < 16 {
502 return;
503 }
504 let block = self.buf;
505 self.absorb_block(&block);
506 self.buffered = 0;
507 }
508 #[cfg(all(target_arch = "x86_64", feature = "std"))]
509 if data.len() >= POLY_AVX2_MIN && avx2() {
510 let whole = data.len() & !63;
511 let d = unsafe { poly_avx2::absorb(&self.acc, &self.r, &self.powers, &data[..whole]) };
514 self.acc = reduce(d);
515 data = &data[whole..];
516 }
517 while data.len() >= 64 {
518 self.absorb4(&data[..64]);
519 data = &data[64..];
520 }
521 let mut chunks = data.chunks_exact(16);
522 for block in &mut chunks {
523 self.absorb_block(block);
524 }
525 let rest = chunks.remainder();
526 self.buf[..rest.len()].copy_from_slice(rest);
527 self.buffered = rest.len();
528 }
529
530 fn finalize(mut self) -> [u8; 16] {
531 if self.buffered > 0 {
532 let n = self.buffered;
533 let block = self.buf;
534 self.absorb_block(&block[..n]);
535 self.buffered = 0;
536 }
537
538 let mut h = self.acc;
540 let mut c = h[1] >> 26;
541 h[1] &= 0x3ff_ffff;
542 h[2] += c;
543 c = h[2] >> 26;
544 h[2] &= 0x3ff_ffff;
545 h[3] += c;
546 c = h[3] >> 26;
547 h[3] &= 0x3ff_ffff;
548 h[4] += c;
549 c = h[4] >> 26;
550 h[4] &= 0x3ff_ffff;
551 h[0] += c * 5;
552 c = h[0] >> 26;
553 h[0] &= 0x3ff_ffff;
554 h[1] += c;
555
556 let mut g = [0u32; 5];
558 let mut carry = 5u32;
559 for i in 0..4 {
560 let v = h[i] + carry;
561 g[i] = v & 0x3ff_ffff;
562 carry = v >> 26;
563 }
564 g[4] = h[4].wrapping_add(carry).wrapping_sub(1 << 26);
567 let mask = ((g[4] >> 31) ^ 1).wrapping_neg();
568 for i in 0..5 {
569 h[i] = (h[i] & !mask) | (g[i] & mask);
570 }
571
572 let h0 = h[0] | (h[1] << 26);
574 let h1 = (h[1] >> 6) | (h[2] << 20);
575 let h2 = (h[2] >> 12) | (h[3] << 14);
576 let h3 = (h[3] >> 18) | (h[4] << 8);
577
578 let mut f = h0 as u64 + self.s[0] as u64;
579 let r0 = f as u32;
580 f = h1 as u64 + self.s[1] as u64 + (f >> 32);
581 let r1 = f as u32;
582 f = h2 as u64 + self.s[2] as u64 + (f >> 32);
583 let r2 = f as u32;
584 f = h3 as u64 + self.s[3] as u64 + (f >> 32);
585 let r3 = f as u32;
586
587 let mut tag = [0u8; 16];
588 tag[0..4].copy_from_slice(&r0.to_le_bytes());
589 tag[4..8].copy_from_slice(&r1.to_le_bytes());
590 tag[8..12].copy_from_slice(&r2.to_le_bytes());
591 tag[12..16].copy_from_slice(&r3.to_le_bytes());
592 tag
593 }
594}
595
596impl SelfTest for Poly1305 {
597 fn self_test() -> Result<()> {
598 let mut key = [0u8; 32];
600 ic_core::codec::hex_decode(
601 b"85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b",
602 &mut key,
603 )?;
604 let tag = <Self as Mac>::mac(&key, b"Cryptographic Forum Research Group")?;
605 let mut want = [0u8; 16];
606 ic_core::codec::hex_decode(b"a8061dc1305136c6c22b8baf0c0127a9", &mut want)?;
607 ensure!(ic_core::ct::verify(&want, &tag), SelfTestFailed, "poly1305");
608 Ok(())
609 }
610}
611
612pub struct ChaCha20Poly1305 {
614 key: [u8; 32],
615}
616
617impl Drop for ChaCha20Poly1305 {
618 fn drop(&mut self) {
619 self.key.zeroize();
620 }
621}
622
623impl Algorithm for ChaCha20Poly1305 {
624 const ID: &'static str = "chacha20-poly1305";
625 const NAME: &'static str = "ChaCha20-Poly1305";
626}
627
628impl ChaCha20Poly1305 {
629 fn poly_key(&self, nonce: &[u8]) -> Result<[u8; 32]> {
631 let mut block = [0u8; 64];
632 let mut n = [0u8; 12];
633 ensure!(nonce.len() == 12, InvalidLength, "chacha20-poly1305 nonce");
634 n.copy_from_slice(nonce);
635 chacha20_block(&self.key, 0, &n, &mut block);
636 let mut k = [0u8; 32];
637 k.copy_from_slice(&block[..32]);
638 block.zeroize();
639 Ok(k)
640 }
641
642 fn tag(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result<[u8; 16]> {
650 let mut poly_key = self.poly_key(nonce)?;
651 let mut m = Poly1305::new(&poly_key)?;
652 poly_key.zeroize();
653
654 m.update(aad);
655 m.update(&[0u8; 16][..(16 - aad.len() % 16) % 16]);
656 m.update(ciphertext);
657 m.update(&[0u8; 16][..(16 - ciphertext.len() % 16) % 16]);
658
659 let mut lens = [0u8; 16];
660 lens[..8].copy_from_slice(&(aad.len() as u64).to_le_bytes());
661 lens[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes());
662 m.update(&lens);
663 Ok(m.finalize())
664 }
665}
666
667impl Aead for ChaCha20Poly1305 {
668 const KEY_LEN: usize = 32;
669 const NONCE_LEN: usize = 12;
670 const TAG_LEN: usize = 16;
671
672 fn new(key: &[u8]) -> Result<Self> {
673 ic_core::module::operational()?;
674 ensure!(key.len() == 32, InvalidLength, "chacha20-poly1305 key");
675 let mut k = [0u8; 32];
676 k.copy_from_slice(key);
677 Ok(Self { key: k })
678 }
679
680 fn seal_detached(
681 &self,
682 nonce: &[u8],
683 aad: &[u8],
684 in_out: &mut [u8],
685 tag: &mut [u8],
686 ) -> Result<()> {
687 ic_core::module::operational()?;
688 ensure!(
689 tag.len() == 16,
690 InvalidLength,
691 "chacha20-poly1305 tag buffer"
692 );
693 chacha20_xor(&self.key, nonce, 1, in_out)?;
695 let t = self.tag(nonce, aad, in_out)?;
696 tag.copy_from_slice(&t);
697 Ok(())
698 }
699
700 fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()> {
701 ic_core::module::operational()?;
702 ensure!(tag.len() == 16, InvalidLength, "chacha20-poly1305 tag");
703 let expected = self.tag(nonce, aad, in_out)?;
704 if !ic_core::ct::verify(&expected, tag) {
705 in_out.zeroize();
706 return Err(ic_core::err!(AuthenticationFailed, "chacha20-poly1305"));
707 }
708 chacha20_xor(&self.key, nonce, 1, in_out)
709 }
710}
711
712impl SelfTest for ChaCha20Poly1305 {
713 fn self_test() -> Result<()> {
714 let key = [0x42u8; 32];
715 let nonce = [0x24u8; 12];
716 let c = <Self as Aead>::new(&key)?;
717 let mut buf = *b"self-test payload";
718 let original = buf;
719 let mut tag = [0u8; 16];
720 c.seal_detached(&nonce, b"aad", &mut buf, &mut tag)?;
721 ensure!(buf != original, SelfTestFailed, "chacha20-poly1305");
722 c.open_detached(&nonce, b"aad", &mut buf, &tag)?;
723 ensure!(buf == original, SelfTestFailed, "chacha20-poly1305");
724 Ok(())
725 }
726}
727
728pub struct XChaCha20Poly1305 {
747 key: [u8; 32],
748}
749
750impl Drop for XChaCha20Poly1305 {
751 fn drop(&mut self) {
752 self.key.zeroize();
753 }
754}
755
756impl Algorithm for XChaCha20Poly1305 {
757 const ID: &'static str = "xchacha20-poly1305";
758 const NAME: &'static str = "XChaCha20-Poly1305";
759}
760
761impl XChaCha20Poly1305 {
762 fn inner(&self, nonce: &[u8]) -> Result<(ChaCha20Poly1305, [u8; 12])> {
767 ensure!(
768 nonce.len() == 24,
769 InvalidLength,
770 "xchacha20-poly1305 nonce must be 24 bytes"
771 );
772 let mut prefix = [0u8; 16];
773 prefix.copy_from_slice(&nonce[..16]);
774 let inner = ChaCha20Poly1305 {
775 key: hchacha20(&self.key, &prefix),
776 };
777 let mut n = [0u8; 12];
778 n[4..].copy_from_slice(&nonce[16..]);
779 Ok((inner, n))
780 }
781
782 pub fn seal_with_random_nonce<R: RandomSource + ?Sized>(
787 &self,
788 rng: &mut R,
789 aad: &[u8],
790 in_out: &mut [u8],
791 tag: &mut [u8],
792 ) -> Result<[u8; 24]> {
793 ic_core::module::operational()?;
794 ensure!(
795 tag.len() == 16,
796 InvalidLength,
797 "xchacha20-poly1305 tag buffer"
798 );
799 let mut nonce = [0u8; 24];
800 rng.fill(&mut nonce)?;
801 self.seal_detached(&nonce, aad, in_out, tag)?;
802 Ok(nonce)
803 }
804}
805
806impl Aead for XChaCha20Poly1305 {
807 const KEY_LEN: usize = 32;
808 const NONCE_LEN: usize = 24;
809 const TAG_LEN: usize = 16;
810
811 fn new(key: &[u8]) -> Result<Self> {
812 ic_core::module::operational()?;
813 ensure!(key.len() == 32, InvalidLength, "xchacha20-poly1305 key");
814 let mut k = [0u8; 32];
815 k.copy_from_slice(key);
816 Ok(Self { key: k })
817 }
818
819 fn seal_detached(
820 &self,
821 nonce: &[u8],
822 aad: &[u8],
823 in_out: &mut [u8],
824 tag: &mut [u8],
825 ) -> Result<()> {
826 ic_core::module::operational()?;
827 let (inner, n) = self.inner(nonce)?;
828 inner.seal_detached(&n, aad, in_out, tag)
829 }
830
831 fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()> {
832 ic_core::module::operational()?;
833 let (inner, n) = self.inner(nonce)?;
834 inner.open_detached(&n, aad, in_out, tag)
835 }
836}
837
838impl SelfTest for XChaCha20Poly1305 {
839 fn self_test() -> Result<()> {
842 let mut key = [0u8; 32];
843 for (i, k) in key.iter_mut().enumerate() {
844 *k = 0x80 + i as u8;
845 }
846 let mut nonce = [0u8; 24];
847 for (i, n) in nonce.iter_mut().enumerate() {
848 *n = 0x40 + i as u8;
849 }
850 let aad = [
851 0x50, 0x51, 0x52, 0x53, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
852 ];
853 let plaintext: &[u8; 114] = b"Ladies and Gentlemen of the class of '99: If I could offer \
854you only one tip for the future, sunscreen would be it.";
855 let mut want = [0u8; 114];
856 ic_core::codec::hex_decode(
857 b"bd6d179d3e83d43b9576579493c0e939572a1700252bfaccbed2902c21396cbb\
858 731c7f1b0b4aa6440bf3a82f4eda7e39ae64c6708c54c216cb96b72e1213b452\
859 2f8c9ba40db5d945b11b69b982c1bb9e3f3fac2bc369488f76b2383565d3fff9\
860 21f9664c97637da9768812f615c68b13b52e",
861 &mut want,
862 )?;
863 let mut want_tag = [0u8; 16];
864 ic_core::codec::hex_decode(b"c0875924c1c7987947deafd8780acf49", &mut want_tag)?;
865
866 let c = <Self as Aead>::new(&key)?;
867 let mut buf = *plaintext;
868 let mut tag = [0u8; 16];
869 c.seal_detached(&nonce, &aad, &mut buf, &mut tag)?;
870 ensure!(buf == want, SelfTestFailed, "xchacha20-poly1305");
871 ensure!(
872 ic_core::ct::verify(&want_tag, &tag),
873 SelfTestFailed,
874 "xchacha20-poly1305"
875 );
876 c.open_detached(&nonce, &aad, &mut buf, &tag)?;
877 ensure!(&buf == plaintext, SelfTestFailed, "xchacha20-poly1305");
878 Ok(())
879 }
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885
886 #[cfg(all(target_arch = "x86_64", feature = "std"))]
905 #[test]
906 fn the_wide_keystreams_match_the_block_function() {
907 let mut backends = std::vec![Backend::Portable];
908 if avx2() {
909 backends.push(Backend::Avx2);
910 }
911 if zmm_available() && avx2() {
912 backends.push(Backend::Zmm);
913 }
914 if backends.len() < 3 {
915 println!("only {backends:?} on this CPU; the others were not exercised");
916 }
917
918 let key: [u8; 32] = core::array::from_fn(|i| 0x5a ^ (i as u8).wrapping_mul(37));
919 let nonce: [u8; 12] = core::array::from_fn(|i| 0x21 + i as u8);
920 let data: std::vec::Vec<u8> = (0..2101 + 3)
921 .map(|i| ((i as u32).wrapping_mul(0x9e37_79b9) >> 13) as u8)
922 .collect();
923
924 let mut checked = 0;
925 for &backend in &backends {
926 for counter in [0u32, 1, 15, 16, 17, 1000] {
927 let mut expect = data[3..].to_vec();
930 let mut block = [0u8; 64];
931 for (i, chunk) in expect.chunks_mut(64).enumerate() {
932 chacha20_block(&key, counter + i as u32, &nonce, &mut block);
933 for (d, b) in chunk.iter_mut().zip(block.iter()) {
934 *d ^= b;
935 }
936 }
937 for len in 0..=2100usize {
938 let mut buf = data.clone();
940 let region = &mut buf[3..3 + len];
941 xor_keystream(backend, &key, &nonce, counter, region).unwrap();
942 assert_eq!(
943 &buf[3..3 + len],
944 &expect[..len],
945 "{backend:?} and the block function differ at {len} bytes, counter {counter}"
946 );
947 assert_eq!(buf[..3], data[..3], "{backend:?} wrote before the buffer");
948 assert_eq!(
949 buf[3 + len..],
950 data[3 + len..],
951 "{backend:?} wrote past the buffer"
952 );
953 checked += 1;
954 }
955 }
956 }
957 assert_eq!(
958 checked,
959 backends.len() * 6 * 2101,
960 "the comparison did not run"
961 );
962
963 for len in [65536usize, 65536 + 1000, 200_000] {
966 let mut actual = std::vec![0u8; len];
967 chacha20_xor(&key, &nonce, 7, &mut actual).unwrap();
968 let mut expect = std::vec![0u8; len];
969 xor_keystream(Backend::Portable, &key, &nonce, 7, &mut expect).unwrap();
970 assert!(actual == expect, "chacha20_xor differs at {len} bytes");
971 }
972 }
973
974 #[test]
978 fn the_counter_is_refused_before_it_wraps() {
979 let key = [3u8; 32];
980 let nonce = [4u8; 12];
981 let mut backends = std::vec![Backend::Portable];
982 #[cfg(all(target_arch = "x86_64", feature = "std"))]
983 {
984 if avx2() {
985 backends.push(Backend::Avx2);
986 }
987 if zmm_available() && avx2() {
988 backends.push(Backend::Zmm);
989 }
990 }
991 for backend in backends {
992 for (counter, len) in [(u32::MAX, 65), (u32::MAX - 3, 1024), (u32::MAX - 15, 1025)] {
993 let mut buf = std::vec![0u8; len];
994 let err = xor_keystream(backend, &key, &nonce, counter, &mut buf).unwrap_err();
995 assert_eq!(err.kind(), ic_core::ErrorKind::CounterExhausted);
996 assert!(
997 buf.iter().all(|&b| b == 0),
998 "{backend:?} wrote before refusing"
999 );
1000 }
1001 let mut buf = std::vec![0u8; 1024];
1004 xor_keystream(backend, &key, &nonce, u32::MAX - 15, &mut buf).unwrap();
1005 let mut last = [0u8; 64];
1006 chacha20_block(&key, u32::MAX, &nonce, &mut last);
1007 assert_eq!(buf[960..], last[..], "{backend:?}");
1008 }
1009 }
1010
1011 #[test]
1013 fn the_active_chacha_path_is_reported() {
1014 println!("chacha20 backend: {:?}", best_backend());
1015 #[cfg(all(target_arch = "x86_64", feature = "std"))]
1016 println!(
1017 "poly1305: {}",
1018 if avx2() { "AVX2 (4 lanes)" } else { "scalar" }
1019 );
1020 }
1021 use ic_core::codec::{hex, unhex};
1022
1023 #[test]
1024 fn rfc8439_chacha20_keystream_vector() {
1025 let key: Vec<u8> = (0..32u8).collect();
1026 let mut nonce = [0u8; 12];
1027 nonce[3] = 0x09;
1028 nonce[7] = 0x4a;
1029 let mut data = [0u8; 64];
1030 chacha20_xor(&key, &nonce, 1, &mut data).unwrap();
1031 assert_eq!(
1032 hex(&data),
1033 "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"
1034 );
1035 }
1036
1037 #[test]
1038 fn rfc8439_chacha20_encryption_vector() {
1039 let key: Vec<u8> = (0..32u8).collect();
1040 let mut nonce = [0u8; 12];
1041 nonce[7] = 0x4a;
1042 let mut data =
1043 b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.".to_vec();
1044 chacha20_xor(&key, &nonce, 1, &mut data).unwrap();
1045 assert_eq!(
1046 hex(&data),
1047 "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d"
1048 );
1049 }
1050
1051 #[test]
1052 fn rfc8439_poly1305_vector() {
1053 let key =
1054 unhex("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b").unwrap();
1055 let tag = Poly1305::mac(&key, b"Cryptographic Forum Research Group").unwrap();
1056 assert_eq!(hex(&tag), "a8061dc1305136c6c22b8baf0c0127a9");
1057 }
1058
1059 #[test]
1060 fn poly1305_streaming_matches_one_shot() {
1061 let key = [0x11u8; 32];
1062 let data: Vec<u8> = (0..100u8).collect();
1063 for split in [0usize, 1, 15, 16, 17, 50, 100] {
1064 let mut m = Poly1305::new(&key).unwrap();
1065 m.update(&data[..split]);
1066 m.update(&data[split..]);
1067 assert_eq!(
1068 m.finalize(),
1069 Poly1305::mac(&key, &data).unwrap(),
1070 "split at {split}"
1071 );
1072 }
1073 }
1074
1075 #[test]
1076 fn rfc8439_aead_vector() {
1077 let key =
1078 unhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f").unwrap();
1079 let nonce = unhex("070000004041424344454647").unwrap();
1080 let aad = unhex("50515253c0c1c2c3c4c5c6c7").unwrap();
1081 let mut buf =
1082 b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.".to_vec();
1083
1084 let c = ChaCha20Poly1305::new(&key).unwrap();
1085 let mut tag = [0u8; 16];
1086 c.seal_detached(&nonce, &aad, &mut buf, &mut tag).unwrap();
1087 assert_eq!(
1088 hex(&buf),
1089 "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"
1090 );
1091 assert_eq!(hex(&tag), "1ae10b594f09e26a7e902ecbd0600691");
1092
1093 c.open_detached(&nonce, &aad, &mut buf, &tag).unwrap();
1094 assert_eq!(
1095 &buf[..],
1096 &b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."[..]
1097 );
1098 }
1099
1100 #[test]
1101 fn aead_rejects_tampering_and_wipes() {
1102 let c = ChaCha20Poly1305::new(&[1u8; 32]).unwrap();
1103 let mut buf = b"secret".to_vec();
1104 let mut tag = [0u8; 16];
1105 c.seal_detached(&[2u8; 12], b"", &mut buf, &mut tag)
1106 .unwrap();
1107 tag[15] ^= 0x80;
1108 assert!(c.open_detached(&[2u8; 12], b"", &mut buf, &tag).is_err());
1109 assert_eq!(buf, vec![0u8; 6]);
1110 }
1111
1112 #[test]
1139 fn the_grouped_poly1305_agrees_with_the_serial_one_at_its_largest_carries() {
1140 let mut keys = Vec::new();
1141 let mut largest = [0xffu8; 32];
1143 largest[..16].copy_from_slice(&[
1144 0xff, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff,
1145 0xff, 0x0f,
1146 ]);
1147 keys.push(largest);
1148 keys.push([0xffu8; 32]);
1149 for seed in 0u8..62 {
1150 let mut key = [0u8; 32];
1151 for (i, b) in key.iter_mut().enumerate() {
1152 *b = (i as u8)
1153 .wrapping_mul(0x9d)
1154 .wrapping_add(seed.wrapping_mul(0x3b))
1155 ^ seed;
1156 }
1157 keys.push(key);
1158 }
1159
1160 let mut checked = 0;
1161 for key in &keys {
1162 for fill in [0xffu8, 0xfe, 0x80] {
1163 for len in [64usize, 128, 129, 192, 256, 1024] {
1164 let data = vec![fill; len];
1165 let grouped = Poly1305::mac(key, &data).unwrap();
1166
1167 let mut serial = Poly1305::new(key).unwrap();
1168 for chunk in data.chunks(16) {
1169 serial.absorb_block(chunk);
1170 }
1171 assert_eq!(
1172 grouped,
1173 serial.finalize(),
1174 "grouped and serial Poly1305 differ: {len} bytes of {fill:#04x}"
1175 );
1176 checked += 1;
1177 }
1178 }
1179 }
1180 assert_eq!(checked, 64 * 3 * 6, "the comparison did not run");
1181 }
1182
1183 #[test]
1184 fn the_grouped_poly1305_agrees_with_the_serial_one() {
1185 let key = [0x8eu8; 32];
1186
1187 let mut checked = 0;
1188 for len in [
1189 0usize, 1, 15, 16, 17, 31, 32, 63, 64, 65, 79, 80, 127, 128, 129, 255, 256, 1023, 1024,
1190 1025,
1191 ] {
1192 let data: Vec<u8> = (0..len)
1193 .map(|i| ((i as u64).wrapping_mul(0x9e37_79b9) >> 5) as u8)
1194 .collect();
1195
1196 let grouped = Poly1305::mac(&key, &data).unwrap();
1197
1198 let mut serial = Poly1305::new(&key).unwrap();
1200 for chunk in data.chunks(16) {
1201 serial.absorb_block(chunk);
1202 }
1203 let serial = serial.finalize();
1204
1205 assert_eq!(
1206 grouped, serial,
1207 "grouped and serial Poly1305 differ at {len} bytes"
1208 );
1209 checked += 1;
1210 }
1211 assert_eq!(checked, 20, "the comparison did not run");
1212 }
1213
1214 #[test]
1223 fn the_avx2_poly1305_agrees_with_the_serial_one() {
1224 #[cfg(all(target_arch = "x86_64", feature = "std"))]
1225 if !avx2() {
1226 println!("no AVX2 on this CPU; the vector path was not exercised");
1227 }
1228 let mut largest = [0xffu8; 32];
1229 largest[..16].copy_from_slice(&[
1230 0xff, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff,
1231 0xff, 0x0f,
1232 ]);
1233 let ordinary: [u8; 32] = core::array::from_fn(|i| (i as u8).wrapping_mul(0x9d) ^ 0x3b);
1234 let mixed: Vec<u8> = (0..70_000u32)
1235 .map(|i| (i.wrapping_mul(0x9e37_79b9) >> 11) as u8)
1236 .collect();
1237 let ones = vec![0xffu8; 70_000];
1238
1239 let serial = |key: &[u8; 32], data: &[u8]| {
1240 let mut m = Poly1305::new(key).unwrap();
1241 for chunk in data.chunks(16) {
1242 m.absorb_block(chunk);
1243 }
1244 m.finalize()
1245 };
1246
1247 let mut lengths: Vec<usize> = (0..=600).collect();
1248 lengths.extend([1023, 1024, 1025, 4096, 16_383, 16_384, 16_385, 69_999]);
1249 let mut checked = 0;
1250 for key in [&largest, &ordinary] {
1251 for source in [&ones, &mixed] {
1252 for &len in &lengths {
1253 let data = &source[..len];
1254 let want = serial(key, data);
1255 assert_eq!(Poly1305::mac(key, data).unwrap(), want, "{len} bytes");
1256 for split in [1usize, 15, 17, 64, 129] {
1259 if split > len {
1260 continue;
1261 }
1262 let mut m = Poly1305::new(key).unwrap();
1263 m.update(&data[..split]);
1264 m.update(&data[split..]);
1265 assert_eq!(m.finalize(), want, "{len} bytes split at {split}");
1266 }
1267 checked += 1;
1268 }
1269 }
1270 }
1271 assert_eq!(checked, 2 * 2 * lengths.len(), "the comparison did not run");
1272 }
1273
1274 #[test]
1280 fn the_aead_matches_the_portable_forms() {
1281 let key: [u8; 32] = core::array::from_fn(|i| 0x80 + i as u8);
1282 let nonce: [u8; 12] = core::array::from_fn(|i| 0x40 + i as u8);
1283 let c = ChaCha20Poly1305::new(&key).unwrap();
1284 let reference = |aad: &[u8], pt: &[u8]| {
1285 let mut ct = pt.to_vec();
1286 xor_keystream(Backend::Portable, &key, &nonce, 1, &mut ct).unwrap();
1287 let mut block = [0u8; 64];
1288 chacha20_block(&key, 0, &nonce, &mut block);
1289 let mut m = Poly1305::new(&block[..32]).unwrap();
1290 let mut mac_input = aad.to_vec();
1291 mac_input.resize(aad.len().div_ceil(16) * 16, 0);
1292 mac_input.extend_from_slice(&ct);
1293 mac_input.resize(mac_input.len().div_ceil(16) * 16, 0);
1294 mac_input.extend_from_slice(&(aad.len() as u64).to_le_bytes());
1295 mac_input.extend_from_slice(&(ct.len() as u64).to_le_bytes());
1296 for chunk in mac_input.chunks(16) {
1297 m.absorb_block(chunk);
1298 }
1299 (ct, m.finalize())
1300 };
1301 let source: Vec<u8> = (0..70_000u32)
1302 .map(|i| (i.wrapping_mul(0x2545_f491) >> 7) as u8)
1303 .collect();
1304 let mut lengths: Vec<usize> = (0..=600).collect();
1305 lengths.extend([1024, 16_383, 16_384, 16_385, 32_768 + 1000, 69_000]);
1306 for aad in [&b""[..], &b"0123456789abcdefXYZ"[..]] {
1307 for &len in &lengths {
1308 let pt = &source[1..1 + len];
1309 let (want_ct, want_tag) = reference(aad, pt);
1310 let mut buf = source[..1 + len].to_vec();
1311 let mut tag = [0u8; 16];
1312 c.seal_detached(&nonce, aad, &mut buf[1..], &mut tag)
1313 .unwrap();
1314 assert!(buf[1..] == want_ct[..], "ciphertext at {len} bytes");
1315 assert_eq!(tag, want_tag, "tag at {len} bytes");
1316 c.open_detached(&nonce, aad, &mut buf[1..], &tag).unwrap();
1317 assert!(&buf[1..] == pt, "round trip at {len} bytes");
1318 }
1319 }
1320 }
1321
1322 #[test]
1328 fn the_poly1305_powers_are_powers_of_r() {
1329 let m = Poly1305::new(&[0x3bu8; 32]).unwrap();
1330 let mut expect = m.r;
1331 for (i, stored) in m.powers.iter().enumerate() {
1332 expect = reduce(mul_unreduced(expect, m.r, five_times(m.r)));
1333 assert_eq!(*stored, expect, "power {} is not r^{}", i, i + 2);
1334 }
1335 assert_ne!(m.powers[0], m.powers[1]);
1336 assert_ne!(m.powers[0], m.r);
1337 }
1338
1339 fn spaced_hex(s: &str) -> Vec<u8> {
1341 let s: std::string::String = s.split_whitespace().collect();
1342 let mut out = vec![0u8; s.len() / 2];
1343 ic_core::codec::hex_decode(s.as_bytes(), &mut out).unwrap();
1344 out
1345 }
1346
1347 fn draft_key() -> [u8; 32] {
1351 core::array::from_fn(|i| 0x80 + i as u8)
1352 }
1353
1354 #[test]
1356 fn hchacha20_matches_the_draft() {
1357 let key: [u8; 32] = core::array::from_fn(|i| i as u8);
1358 let nonce: [u8; 16] = spaced_hex("000000090000004a0000000031415927")
1359 .try_into()
1360 .unwrap();
1361 let subkey = hchacha20(&key, &nonce);
1362 assert_eq!(
1363 subkey.to_vec(),
1364 spaced_hex("82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc")
1365 );
1366 }
1367
1368 fn xchacha20_xor(key: &[u8; 32], nonce: &[u8; 24], counter: u32, data: &mut [u8]) {
1371 let (inner, n) = XChaCha20Poly1305 { key: *key }.inner(nonce).unwrap();
1372 chacha20_xor(&inner.key, &n, counter, data).unwrap();
1373 }
1374
1375 #[test]
1377 fn xchacha20_keystream_matches_the_draft() {
1378 let nonce: [u8; 24] = core::array::from_fn(|i| 0x40 + i as u8 + u8::from(i == 23));
1379 let plaintext = spaced_hex(
1380 "5468652064686f6c65202870726f6e6f756e6365642022646f6c652229206973
1381 20616c736f206b6e6f776e2061732074686520417369617469632077696c6420
1382 646f672c2072656420646f672c20616e642077686973746c696e6720646f672e
1383 2049742069732061626f7574207468652073697a65206f662061204765726d61
1384 6e20736865706865726420627574206c6f6f6b73206d6f7265206c696b652061
1385 206c6f6e672d6c656767656420666f782e205468697320686967686c7920656c
1386 757369766520616e6420736b696c6c6564206a756d70657220697320636c6173
1387 736966696564207769746820776f6c7665732c20636f796f7465732c206a6163
1388 6b616c732c20616e6420666f78657320696e20746865207461786f6e6f6d6963
1389 2066616d696c792043616e696461652e",
1390 );
1391 let at_zero = spaced_hex(
1392 "4559abba4e48c16102e8bb2c05e6947f50a786de162f9b0b7e592a9b53d0d4e9
1393 8d8d6410d540a1a6375b26d80dace4fab52384c731acbf16a5923c0c48d3575d
1394 4d0d2c673b666faa731061277701093a6bf7a158a8864292a41c48e3a9b4c0da
1395 ece0f8d98d0d7e05b37a307bbb66333164ec9e1b24ea0d6c3ffddcec4f68e744
1396 3056193a03c810e11344ca06d8ed8a2bfb1e8d48cfa6bc0eb4e2464b74814240
1397 7c9f431aee769960e15ba8b96890466ef2457599852385c661f752ce20f9da0c
1398 09ab6b19df74e76a95967446f8d0fd415e7bee2a12a114c20eb5292ae7a349ae
1399 577820d5520a1f3fb62a17ce6a7e68fa7c79111d8860920bc048ef43fe84486c
1400 cb87c25f0ae045f0cce1e7989a9aa220a28bdd4827e751a24a6d5c62d790a663
1401 93b93111c1a55dd7421a10184974c7c5",
1402 );
1403 let at_one = spaced_hex(
1404 "7d0a2e6b7f7c65a236542630294e063b7ab9b555a5d5149aa21e4ae1e4fbce87
1405 ecc8e08a8b5e350abe622b2ffa617b202cfad72032a3037e76ffdcdc4376ee05
1406 3a190d7e46ca1de04144850381b9cb29f051915386b8a710b8ac4d027b8b050f
1407 7cba5854e028d564e453b8a968824173fc16488b8970cac828f11ae53cabd201
1408 12f87107df24ee6183d2274fe4c8b1485534ef2c5fbc1ec24bfc3663efaa08bc
1409 047d29d25043532db8391a8a3d776bf4372a6955827ccb0cdd4af403a7ce4c63
1410 d595c75a43e045f0cce1f29c8b93bd65afc5974922f214a40b7c402cdb91ae73
1411 c0b63615cdad0480680f16515a7ace9d39236464328a37743ffc28f4ddb324f4
1412 d0f5bbdc270c65b1749a6efff1fbaa09536175ccd29fb9e6057b307320d31683
1413 8a9c71f70b5b5907a66f7ea49aadc409",
1414 );
1415 for (counter, want) in [(0, at_zero), (1, at_one)] {
1416 let mut buf = plaintext.clone();
1417 xchacha20_xor(&draft_key(), &nonce, counter, &mut buf);
1418 assert_eq!(buf, want, "block counter {counter}");
1419 }
1420 }
1421
1422 #[test]
1426 fn xchacha20_poly1305_derives_the_drafts_poly1305_key() {
1427 let nonce: [u8; 24] = core::array::from_fn(|i| 0x40 + i as u8);
1428 let (inner, n) = XChaCha20Poly1305 { key: draft_key() }
1429 .inner(&nonce)
1430 .unwrap();
1431 assert_eq!(n[..4], [0u8; 4], "the 32-bit fixed-common part");
1432 assert_eq!(
1433 inner.poly_key(&n).unwrap().to_vec(),
1434 spaced_hex("7b191f80f361f099094f6f4b8fb97df847cc6873a8f2b190dd73807183f907d5")
1435 );
1436 }
1437
1438 #[test]
1445 fn xchacha20_poly1305_uses_the_whole_nonce_and_refuses_other_lengths() {
1446 let c = XChaCha20Poly1305::new(&draft_key()).unwrap();
1447 let seal = |nonce: &[u8]| {
1448 let mut buf = *b"sixteen bytes!!!";
1449 let mut tag = [0u8; 16];
1450 c.seal_detached(nonce, b"", &mut buf, &mut tag)
1451 .map(|()| (buf, tag))
1452 };
1453 let base = [7u8; 24];
1454 let reference = seal(&base).unwrap();
1455 for at in [0, 15, 16, 23] {
1456 let mut other = base;
1457 other[at] ^= 1;
1458 assert_ne!(seal(&other).unwrap().0, reference.0, "nonce byte {at}");
1459 }
1460 for len in [0, 12, 16, 23, 25] {
1461 let err = seal(&vec![7u8; len]).unwrap_err();
1462 assert_eq!(
1463 err.kind(),
1464 ic_core::ErrorKind::InvalidLength,
1465 "{len}-byte nonce"
1466 );
1467 }
1468 }
1469
1470 #[test]
1473 fn xchacha20_poly1305_seals_with_a_drawn_nonce_and_refuses_a_forgery() {
1474 struct Counting(u8, usize);
1475 impl RandomSource for Counting {
1476 fn fill(&mut self, out: &mut [u8]) -> Result<()> {
1477 self.1 += 1;
1478 for b in out.iter_mut() {
1479 self.0 = self.0.wrapping_add(1);
1480 *b = self.0;
1481 }
1482 Ok(())
1483 }
1484 }
1485 let c = XChaCha20Poly1305::new(&draft_key()).unwrap();
1486 let mut rng = Counting(0, 0);
1487 let mut buf = *b"attack at dawn";
1488 let mut tag = [0u8; 16];
1489 let nonce = c
1490 .seal_with_random_nonce(&mut rng, b"hdr", &mut buf, &mut tag)
1491 .unwrap();
1492 assert_eq!(rng.1, 1);
1493 assert_eq!(nonce, core::array::from_fn::<u8, 24, _>(|i| i as u8 + 1));
1494
1495 let mut short = [0u8; 15];
1496 assert!(c
1497 .seal_with_random_nonce(&mut rng, b"hdr", &mut buf, &mut short)
1498 .is_err());
1499 assert_eq!(
1500 rng.1, 1,
1501 "nothing drawn for a buffer that cannot hold the tag"
1502 );
1503
1504 let mut opened = buf;
1505 c.open_detached(&nonce, b"hdr", &mut opened, &tag).unwrap();
1506 assert_eq!(&opened, b"attack at dawn");
1507
1508 let mut forged = tag;
1509 forged[0] ^= 1;
1510 let mut attempt = buf;
1511 let err = c
1512 .open_detached(&nonce, b"hdr", &mut attempt, &forged)
1513 .unwrap_err();
1514 assert_eq!(err.kind(), ic_core::ErrorKind::AuthenticationFailed);
1515 assert_eq!(attempt, [0u8; 14], "wiped");
1516 }
1517
1518 #[test]
1519 fn self_tests_pass() {
1520 XChaCha20Poly1305::self_test().unwrap();
1521 Poly1305::self_test().unwrap();
1522 ChaCha20Poly1305::self_test().unwrap();
1523 }
1524}