Skip to main content

ic_cipher/
chacha.rs

1//! RFC 8439 ChaCha20, Poly1305, and the ChaCha20-Poly1305 AEAD; and
2//! XChaCha20-Poly1305, the same AEAD with a 192-bit nonce.
3//!
4//! **Not FIPS-approved.** These are included because they are the right answer
5//! on hardware without AES acceleration, and because interoperating with TLS
6//! 1.3, WireGuard, and age requires them. The ontology marks the whole family
7//! `fips_status: NotApproved`, and `ic-fips` refuses to construct them while
8//! the module is in approved mode.
9
10use 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/// The twenty ChaCha rounds, in place, without adding the input back.
30#[inline(always)]
31fn rounds(working: &mut [u32; 16]) {
32    for _ in 0..10 {
33        // Column rounds.
34        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        // Diagonal rounds.
39        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
46/// HChaCha20 (draft-irtf-cfrg-xchacha-03 section 2.2): a subkey from a key
47/// and a 128-bit nonce.
48///
49/// The state is ChaCha20's with the counter and nonce words replaced by the
50/// four words of `nonce`. After the rounds, and without adding the input
51/// back, the first row and the last are the subkey.
52fn 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
77/// Produce one 64-byte ChaCha20 keystream block.
78fn 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// The x86-64 backends, selected by `std`'s run-time detection. The AVX-512
106// keystream and the AVX2 Poly1305 are also compiled for `ct-probes`, whose
107// harness reads them.
108#[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/// Ask once per process whether every feature named is present, and cache
118/// the answer; see the SHA-256 dispatch for why.
119#[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/// Whether this CPU has AVX2.
137#[cfg(all(target_arch = "x86_64", feature = "std"))]
138fn avx2() -> bool {
139    cached_detect!("avx2")
140}
141
142/// Whether this CPU has AVX-512F, which the sixteen-block kernel needs. `std`'s
143/// detection also confirms that the OS saves the `zmm` state.
144#[cfg(all(target_arch = "x86_64", feature = "std"))]
145fn zmm_available() -> bool {
146    cached_detect!("avx512f")
147}
148
149/// Which keystream kernel runs the bulk of a message.
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151enum Backend {
152    /// One block at a time, everywhere.
153    Portable,
154    /// Eight blocks at a time on AVX2, then the portable path.
155    #[cfg(all(target_arch = "x86_64", feature = "std"))]
156    Avx2,
157    /// Sixteen blocks at a time on AVX-512F, then AVX2, then portable.
158    #[cfg(all(target_arch = "x86_64", feature = "std"))]
159    Zmm,
160}
161
162/// The fastest kernel this CPU has.
163fn 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
176/// XOR `data` with the ChaCha20 keystream, starting at `counter`.
177pub 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
198/// The keystream XOR behind [`chacha20_xor`] and the AEAD, on `backend`.
199///
200/// Refuses, before touching `data`, a message whose blocks would run the
201/// 32-bit counter past its last value. The wide kernels rely on that: they
202/// step the counter sixteen or eight blocks at a time and must not step over
203/// the boundary. (They used to be guarded by a check made in 64 bits that
204/// could never fail, so a message crossing the boundary wrapped the counter
205/// in the AVX2 path and reused keystream, where the portable path stopped
206/// with an error halfway through. Now every path refuses the whole message.)
207fn 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    // Sixteen blocks at a time where the CPU has AVX-512F.
225    #[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                // SAFETY: `Backend::Zmm` is chosen only where
232                // `zmm_available()` confirmed AVX-512F.
233                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    // Eight at a time on AVX2: the whole message there, the tail here.
242    #[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            // SAFETY: both wide backends are chosen only where `avx2()`
247            // confirmed AVX2, and the slice is exactly STRIDE bytes.
248            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    // What is left one block at a time; on the portable backend, all of it.
255    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/// RFC 8439 Poly1305 one-time authenticator.
267///
268/// Every key must be used for exactly one message. Reuse reveals the key.
269#[derive(Clone)]
270pub struct Poly1305 {
271    r: [u32; 5],
272    s: [u32; 4],
273    /// `r^2`, `r^3`, `r^4`, for absorbing four blocks at a time.
274    ///
275    /// Poly1305 is a Horner evaluation -- `h = (h + m) * r` -- so each block
276    /// waits on the one before it, exactly as GHASH does. The same identity
277    /// unrolls it:
278    ///
279    /// ```text
280    /// h' = (h + m0)*r^4  +  m1*r^3  +  m2*r^2  +  m3*r
281    /// ```
282    ///
283    /// Four independent products rather than four dependent ones. It also
284    /// reduces once instead of four times: the products are summed while still
285    /// unreduced, which the limb bounds allow, so the carry chain runs once per
286    /// group rather than once per block.
287    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    /// Absorb one block. Blocks shorter than 16 bytes are the final block and
312    /// get an explicit 0x01 terminator instead of the implicit 2^128 bit.
313    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    /// Absorb four whole blocks with four independent products.
342    ///
343    /// Correct for the same reason the one-at-a-time path is; see `powers`.
344    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            // Whole blocks only, so the 2^128 bit is always set.
354            *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        // The oldest block carries the accumulator and takes the highest power.
364        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/// The shortest input `update` hands to the AVX2 Poly1305: below two groups
381/// the lanes' final multiplication by distinct powers costs as much as it
382/// saves.
383#[cfg(all(target_arch = "x86_64", feature = "std"))]
384const POLY_AVX2_MIN: usize = 128;
385
386/// `[5*r1, 5*r2, 5*r3, 5*r4]`, the companion the reduction folds in.
387fn five_times(r: [u32; 5]) -> [u32; 4] {
388    [r[1] * 5, r[2] * 5, r[3] * 5, r[4] * 5]
389}
390
391/// The five limb products, summed but not carried.
392///
393/// Each term is at most `2^27 * 5*2^26`, and five of them stay under `2^57`,
394/// so four of these vectors can be added before carrying without leaving
395/// `u64`. That headroom is what lets `absorb4` reduce once instead ofå›› times.
396fn 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
407/// Carry-propagate back into 26-bit limbs, folding `2^130` into `5`.
408///
409/// The carries are 64-bit, and have to be. One product's limbs are under
410/// `2^58` and its carries under `2^32`, which is what this function was
411/// first written for. `absorb4` hands it four products summed: limbs up to
412/// about `2^60`, a first carry up to about `2^34`, and a last carry whose
413/// five-fold is about `2^34` too. Held in a `u32`, the first could be cut
414/// short by its cast and the last could overflow its multiplication. That
415/// needs the message limbs to be large: for a kilobyte of `0xff` bytes it
416/// happened under about one key in twenty, and for random bytes never.
417///
418/// With 64-bit carries nothing here can wrap: a limb plus a carry is under
419/// `2^61`, and `c * 5` under `2^41`.
420fn 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    // 2^130 = 5 mod p, so what carried out of the top comes back in at the
437    // bottom five times over.
438    let folded = a0 + c * 5;
439    let a0 = folded & MASK;
440    // Under 2^26 + 2^15: the one limb left a little over its width, as the
441    // multiplication expects.
442    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        // `r` is clamped per RFC 8439 §2.5.
462        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        // r^2, r^3 and r^4, each from the previous by the same multiply the
476        // hot path uses. Once per key, off the hot path.
477        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            // SAFETY: `avx2()` confirmed AVX2; `whole` is a nonzero multiple
512            // of 64.
513            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        // Final reduction modulo 2^130 - 5.
539        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        // g = h + 5, then select g if it did not overflow past 2^130.
557        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        // The top limb is left unmasked so the borrow out of 2^130 is visible
565        // in its sign bit.
566        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        // Serialize as a 128-bit little-endian value, then add `s`.
573        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        // RFC 8439 §2.5.2.
599        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
612/// RFC 8439 ChaCha20-Poly1305 AEAD.
613pub 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    /// Derive the one-time Poly1305 key from block 0 of the ChaCha20 keystream.
630    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    /// Compute the AEAD tag over `aad || pad || ciphertext || pad || lengths`.
643    ///
644    /// Two passes over the message, the keystream and then this, rather than
645    /// one pass of cache-sized pieces. Both were measured, at 4 MiB and at
646    /// 256 MiB, and the single pass was not distinguishably faster on Zen 5;
647    /// and opening in one pass would have to decrypt before the tag is
648    /// checked.
649    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        // Block 0 is reserved for the Poly1305 key, so data starts at block 1.
694        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
728/// XChaCha20-Poly1305: ChaCha20-Poly1305 with a 192-bit nonce
729/// (draft-irtf-cfrg-xchacha-03).
730///
731/// The first 16 bytes of the nonce and the key give a subkey through
732/// HChaCha20; the last 8, after four zero bytes, are the nonce
733/// ChaCha20-Poly1305 runs with under that subkey.
734///
735/// The point is the nonce's length. A 96-bit nonce drawn at random collides
736/// often enough that a long-lived key cannot use one; a 192-bit one does not,
737/// so [`seal_with_random_nonce`](Self::seal_with_random_nonce) draws each
738/// nonce and needs no counter kept anywhere. That is what to use. Passing a
739/// nonce by hand through [`Aead`] is for a protocol that dictates one, and
740/// carries the same rule as every AEAD: never twice under one key.
741///
742/// The specification is an Internet-Draft that expired in 2020 without
743/// becoming an RFC. The construction is what libsodium, Go and Tink implement,
744/// and it is checked here against the draft's vectors and Project
745/// Wycheproof's.
746pub 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    /// The ChaCha20-Poly1305 instance and 96-bit nonce a 192-bit nonce names.
763    ///
764    /// The subkey lives only inside the returned cipher, which wipes it when
765    /// dropped.
766    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    /// Seal under a nonce drawn from `rng`, and return the nonce.
783    ///
784    /// The nonce is not secret; send it with the ciphertext. Pass
785    /// `ic_drbg::Rng`, not raw OS bytes.
786    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    /// The AEAD vector of draft-irtf-cfrg-xchacha-03 appendix A.3.1, sealed
840    /// and opened.
841    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    /// The eight-way path must produce the same keystream as the block path.
887    ///
888    /// RFC 8439's vectors do not establish this. The longest of them is 114
889    /// bytes and the AVX2 stride is 512, so on this machine they exercise the
890    /// scalar path exclusively and would pass with the vector code producing
891    /// anything at all. This builds the expected keystream one block at a time
892    /// -- the function the RFC vectors do validate -- and compares.
893    ///
894    /// Lengths straddle the stride in both directions, including several whole
895    /// groups plus a tail, because the dispatch splits there and an off-by-one
896    /// in the split is the likely error rather than a wrong round function.
897    ///
898    /// The same holds for the sixteen-block AVX-512 kernel, whose stride is
899    /// 1024, and for both together: on that backend a message is cut into
900    /// sixteen-block groups, then eight-block ones, then single blocks. So
901    /// every length from 0 to 2100 is run -- each split, each tail -- on
902    /// every backend this CPU has, at several counters, from an unaligned
903    /// start, with data that is not all zeros.
904    #[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                // The reference: one block at a time, no grouping, once for
928                // the longest length; every shorter one is a prefix of it.
929                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                    // Three bytes in, so no kernel sees an aligned buffer.
939                    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        // And the public entry point, which picks the best backend itself,
964        // at lengths well past one group.
965        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    /// The counter is 32 bits and must not wrap: a message whose blocks would
975    /// run past the last counter is refused whole, on every backend, before
976    /// anything is written; one that ends on the last counter is accepted.
977    #[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            // Ending exactly on the last counter is allowed, and the last
1002            // block is the block function's at u32::MAX.
1003            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    /// Say which path this build will take.
1012    #[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    /// The four-at-a-time Poly1305 must agree with the one-at-a-time path.
1113    ///
1114    /// RFC 8439's Poly1305 vector is 34 bytes and the group is 64, so it never
1115    /// reaches `absorb4`: it would pass with the grouped arithmetic returning
1116    /// anything. This drives both over lengths either side of the boundary and
1117    /// compares the tags.
1118    ///
1119    /// The powers are the likely error. `r^2`, `r^3` and `r^4` are built from
1120    /// `r` at construction, and a multiply that reduces wrongly, or passes the
1121    /// key addend where it wants five times `r`, produces powers that are
1122    /// self-consistent and wrong -- which every short vector still accepts.
1123    /// The grouped path at the top of its range.
1124    ///
1125    /// Four products are summed before one reduction, so the reduction's
1126    /// carries are four times what the one-block path gives it. With every
1127    /// message limb at its largest -- a message of `0xff` bytes, which a
1128    /// peer can simply send -- the last carry times five can exceed 32 bits,
1129    /// and so can the first carry itself. For fifteen releases the reduction
1130    /// held both in a `u32`: where they did not fit, a build with overflow
1131    /// checks stopped, and one without them computed a wrong tag. The test
1132    /// beside this one could not see it, because its message bytes are
1133    /// spread evenly and such sums never come near the top.
1134    ///
1135    /// So this is the same comparison on the inputs that reach it: the
1136    /// largest message limbs, under keys whose `r` is as large as clamping
1137    /// allows and under ordinary ones.
1138    #[test]
1139    fn the_grouped_poly1305_agrees_with_the_serial_one_at_its_largest_carries() {
1140        let mut keys = Vec::new();
1141        // r with every bit that clamping leaves: 0x0ffffffc0ffffffc0ffffffc0fffffff.
1142        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            // The reference: absorb_block only, never absorb4.
1199            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    /// The AVX2 Poly1305 must agree with the one-block-at-a-time path.
1215    ///
1216    /// `update` hands it every whole 64-byte group of an input of 128 bytes
1217    /// or more, so this runs every length from 0 to 600 and some far longer,
1218    /// in one call and split where the buffering, the vector path and the
1219    /// scalar tail meet, under the largest clamped `r` and ordinary keys, on
1220    /// message bytes that are all `0xff` -- the largest limbs, where a carry
1221    /// dropped or held too narrow shows -- and on mixed ones.
1222    #[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                    // Split so that a buffered partial block, a vector run
1257                    // and a scalar tail all occur in one tag.
1258                    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    /// The AEAD, on whichever kernels this CPU selects, must give what the
1275    /// portable forms give: the one-block keystream over the message and then
1276    /// the one-block Poly1305 over the ciphertext. Every length from 0 to 600
1277    /// and some far longer, with and without associated data, sealed and
1278    /// opened, from unaligned buffers.
1279    #[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    /// `r^2`, `r^3` and `r^4` must be powers of `r`.
1323    ///
1324    /// Checked against repeated single multiplication, which the RFC vector
1325    /// validates, so a wrong multiplier in the precomputation fails here rather
1326    /// than showing up as a wrong tag on long inputs only.
1327    #[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    /// Hex with whitespace, as the draft prints it.
1340    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    /// The key and nonce of draft-irtf-cfrg-xchacha-03's appendix A.3: the
1348    /// bytes 0x80..0x9f and 0x40..0x57, with the keystream vectors' nonce
1349    /// ending 0x58 instead.
1350    fn draft_key() -> [u8; 32] {
1351        core::array::from_fn(|i| 0x80 + i as u8)
1352    }
1353
1354    /// draft-irtf-cfrg-xchacha-03 section 2.2.1.
1355    #[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    /// XChaCha20 as the draft's pseudocode in section 2.3.1 has it, built
1369    /// from the same subkey and nonce the AEAD uses.
1370    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    /// draft-irtf-cfrg-xchacha-03 appendix A.3.2, at both block counters.
1376    #[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    /// draft-irtf-cfrg-xchacha-03 appendix A.3.1 gives the one-time Poly1305
1423    /// key as well as the ciphertext, which separates a wrong subkey or nonce
1424    /// from a wrong tag.
1425    #[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    /// Each half of the nonce matters, and only a 24-byte one is taken.
1439    ///
1440    /// The draft's vectors vary every nonce byte at once, so they would also
1441    /// pass against a construction that ignored some of them only if those
1442    /// bytes happened not to matter; this changes one byte of each half and
1443    /// requires the ciphertext to change.
1444    #[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    /// A random nonce is drawn once, used, and returned; and a forged tag
1471    /// wipes the buffer as the 96-bit AEAD's does.
1472    #[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}