Skip to main content

ic_cipher/
chacha.rs

1//! RFC 8439 ChaCha20, Poly1305, and the ChaCha20-Poly1305 AEAD.
2//!
3//! **Not FIPS-approved.** These are included because they are the right answer
4//! on hardware without AES acceleration, and because interoperating with TLS
5//! 1.3, WireGuard, and age requires them. The ontology marks the whole family
6//! `fips_status: NotApproved`, and `ic-fips` refuses to construct them while
7//! the module is in approved mode.
8
9use ic_core::traits::{Aead, Algorithm, Mac, SelfTest};
10use ic_core::{ensure, Result, Zeroize};
11
12const SIGMA: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
13
14#[inline(always)]
15fn quarter_round(s: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
16    s[a] = s[a].wrapping_add(s[b]);
17    s[d] = (s[d] ^ s[a]).rotate_left(16);
18    s[c] = s[c].wrapping_add(s[d]);
19    s[b] = (s[b] ^ s[c]).rotate_left(12);
20    s[a] = s[a].wrapping_add(s[b]);
21    s[d] = (s[d] ^ s[a]).rotate_left(8);
22    s[c] = s[c].wrapping_add(s[d]);
23    s[b] = (s[b] ^ s[c]).rotate_left(7);
24}
25
26/// Produce one 64-byte ChaCha20 keystream block.
27fn chacha20_block(key: &[u8; 32], counter: u32, nonce: &[u8; 12], out: &mut [u8; 64]) {
28    let mut state = [0u32; 16];
29    state[..4].copy_from_slice(&SIGMA);
30    for i in 0..8 {
31        state[4 + i] =
32            u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
33    }
34    state[12] = counter;
35    for i in 0..3 {
36        state[13 + i] = u32::from_le_bytes([
37            nonce[i * 4],
38            nonce[i * 4 + 1],
39            nonce[i * 4 + 2],
40            nonce[i * 4 + 3],
41        ]);
42    }
43
44    let mut working = state;
45    for _ in 0..10 {
46        // Column rounds.
47        quarter_round(&mut working, 0, 4, 8, 12);
48        quarter_round(&mut working, 1, 5, 9, 13);
49        quarter_round(&mut working, 2, 6, 10, 14);
50        quarter_round(&mut working, 3, 7, 11, 15);
51        // Diagonal rounds.
52        quarter_round(&mut working, 0, 5, 10, 15);
53        quarter_round(&mut working, 1, 6, 11, 12);
54        quarter_round(&mut working, 2, 7, 8, 13);
55        quarter_round(&mut working, 3, 4, 9, 14);
56    }
57    for i in 0..16 {
58        let v = working[i].wrapping_add(state[i]);
59        out[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
60    }
61    working.zeroize();
62    state.zeroize();
63}
64
65// Eight-way ChaCha20. `std` only, because the detection needs it.
66#[cfg(all(target_arch = "x86_64", feature = "std"))]
67mod avx2;
68
69/// Whether this CPU has AVX2. Asked once; see the SHA-256 dispatch for why.
70#[cfg(all(target_arch = "x86_64", feature = "std"))]
71fn avx2() -> bool {
72    use core::sync::atomic::{AtomicU8, Ordering};
73    static CACHED: AtomicU8 = AtomicU8::new(0);
74    match CACHED.load(Ordering::Relaxed) {
75        1 => true,
76        2 => false,
77        _ => {
78            let have = std::is_x86_feature_detected!("avx2");
79            CACHED.store(u8::from(!have) + 1, Ordering::Relaxed);
80            have
81        }
82    }
83}
84
85/// XOR `data` with the ChaCha20 keystream, starting at `counter`.
86pub fn chacha20_xor(key: &[u8], nonce: &[u8], counter: u32, data: &mut [u8]) -> Result<()> {
87    ic_core::module::operational()?;
88    ensure!(
89        key.len() == 32,
90        InvalidLength,
91        "chacha20 key must be 32 bytes"
92    );
93    ensure!(
94        nonce.len() == 12,
95        InvalidLength,
96        "chacha20 nonce must be 12 bytes"
97    );
98    let mut k = [0u8; 32];
99    k.copy_from_slice(key);
100    let mut n = [0u8; 12];
101    n.copy_from_slice(nonce);
102
103    // Eight blocks at a time where the CPU can. The tail, and every target
104    // without AVX2, falls through to the block-at-a-time path below.
105    #[cfg(all(target_arch = "x86_64", feature = "std"))]
106    let mut done = 0usize;
107    #[cfg(all(target_arch = "x86_64", feature = "std"))]
108    if avx2() {
109        let groups = data.len() / avx2::STRIDE;
110        // The counter must not wrap silently. The scalar path below reports
111        // exhaustion; this has to reach the same conclusion before it starts,
112        // because it advances eight at a time and would otherwise step over the
113        // boundary rather than land on it.
114        let needed = (groups as u64) * (avx2::LANES as u64);
115        if (counter as u64).checked_add(needed).is_some() {
116            for g in 0..groups {
117                let ctr = counter.wrapping_add((g * avx2::LANES) as u32);
118                let at = g * avx2::STRIDE;
119                // SAFETY: `avx2()` confirmed the feature, and the slice is
120                // exactly STRIDE bytes by construction.
121                unsafe { avx2::eight_blocks(&k, &n, ctr, &mut data[at..at + avx2::STRIDE]) };
122            }
123            done = groups * avx2::STRIDE;
124        }
125    }
126    #[cfg(all(target_arch = "x86_64", feature = "std"))]
127    let (data, counter) = {
128        let advanced = counter.wrapping_add((done / 64) as u32);
129        (&mut data[done..], advanced)
130    };
131
132    let mut block = [0u8; 64];
133    for (i, chunk) in data.chunks_mut(64).enumerate() {
134        let ctr = counter
135            .checked_add(i as u32)
136            .ok_or(ic_core::err!(CounterExhausted, "chacha20 block counter"))?;
137        chacha20_block(&k, ctr, &n, &mut block);
138        for (d, b) in chunk.iter_mut().zip(block.iter()) {
139            *d ^= b;
140        }
141    }
142    block.zeroize();
143    k.zeroize();
144    Ok(())
145}
146
147/// RFC 8439 Poly1305 one-time authenticator.
148///
149/// Every key must be used for exactly one message. Reuse reveals the key.
150#[derive(Clone)]
151pub struct Poly1305 {
152    r: [u32; 5],
153    s: [u32; 4],
154    /// `r^2`, `r^3`, `r^4`, for absorbing four blocks at a time.
155    ///
156    /// Poly1305 is a Horner evaluation -- `h = (h + m) * r` -- so each block
157    /// waits on the one before it, exactly as GHASH does. The same identity
158    /// unrolls it:
159    ///
160    /// ```text
161    /// h' = (h + m0)*r^4  +  m1*r^3  +  m2*r^2  +  m3*r
162    /// ```
163    ///
164    /// Four independent products rather than four dependent ones. It also
165    /// reduces once instead of four times: the products are summed while still
166    /// unreduced, which the limb bounds allow, so the carry chain runs once per
167    /// group rather than once per block.
168    powers: [[u32; 5]; 3],
169    acc: [u32; 5],
170    buf: [u8; 16],
171    buffered: usize,
172}
173
174impl Drop for Poly1305 {
175    fn drop(&mut self) {
176        self.r.zeroize();
177        self.s.zeroize();
178        for p in &mut self.powers {
179            p.zeroize();
180        }
181        self.acc.zeroize();
182        self.buf.zeroize();
183    }
184}
185
186impl Algorithm for Poly1305 {
187    const ID: &'static str = "poly1305";
188    const NAME: &'static str = "Poly1305";
189}
190
191impl Poly1305 {
192    /// Absorb one block. Blocks shorter than 16 bytes are the final block and
193    /// get an explicit 0x01 terminator instead of the implicit 2^128 bit.
194    fn absorb_block(&mut self, block: &[u8]) {
195        let mut b = [0u8; 16];
196        b[..block.len()].copy_from_slice(block);
197        let pad = if block.len() < 16 {
198            b[block.len()] = 1;
199            0
200        } else {
201            1 << 24
202        };
203
204        let t0 = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
205        let t1 = u32::from_le_bytes([b[4], b[5], b[6], b[7]]);
206        let t2 = u32::from_le_bytes([b[8], b[9], b[10], b[11]]);
207        let t3 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
208
209        self.acc[0] += t0 & 0x3ff_ffff;
210        self.acc[1] += ((t0 >> 26) | (t1 << 6)) & 0x3ff_ffff;
211        self.acc[2] += ((t1 >> 20) | (t2 << 12)) & 0x3ff_ffff;
212        self.acc[3] += ((t2 >> 14) | (t3 << 18)) & 0x3ff_ffff;
213        self.acc[4] += (t3 >> 8) | pad;
214
215        self.multiply_by_r();
216    }
217
218    fn multiply_by_r(&mut self) {
219        self.acc = reduce(mul_unreduced(self.acc, self.r, five_times(self.r)));
220    }
221
222    /// Absorb four whole blocks with four independent products.
223    ///
224    /// Correct for the same reason the one-at-a-time path is; see `powers`.
225    fn absorb4(&mut self, blocks: &[u8]) {
226        debug_assert_eq!(blocks.len(), 64);
227        let mut m = [[0u32; 5]; 4];
228        for (i, slot) in m.iter_mut().enumerate() {
229            let b = &blocks[i * 16..(i + 1) * 16];
230            let t0 = u32::from_le_bytes([b[0], b[1], b[2], b[3]]);
231            let t1 = u32::from_le_bytes([b[4], b[5], b[6], b[7]]);
232            let t2 = u32::from_le_bytes([b[8], b[9], b[10], b[11]]);
233            let t3 = u32::from_le_bytes([b[12], b[13], b[14], b[15]]);
234            // Whole blocks only, so the 2^128 bit is always set.
235            *slot = [
236                t0 & 0x3ff_ffff,
237                ((t0 >> 26) | (t1 << 6)) & 0x3ff_ffff,
238                ((t1 >> 20) | (t2 << 12)) & 0x3ff_ffff,
239                ((t2 >> 14) | (t3 << 18)) & 0x3ff_ffff,
240                (t3 >> 8) | (1 << 24),
241            ];
242        }
243
244        // The oldest block carries the accumulator and takes the highest power.
245        for (limb, a) in m[0].iter_mut().zip(self.acc) {
246            *limb += a;
247        }
248
249        let multipliers = [self.powers[2], self.powers[1], self.powers[0], self.r];
250        let mut d = [0u64; 5];
251        for (block, r) in m.iter().zip(multipliers) {
252            let part = mul_unreduced(*block, r, five_times(r));
253            for (acc, term) in d.iter_mut().zip(part) {
254                *acc += term;
255            }
256        }
257        self.acc = reduce(d);
258    }
259}
260
261/// `[5*r1, 5*r2, 5*r3, 5*r4]`, the companion the reduction folds in.
262fn five_times(r: [u32; 5]) -> [u32; 4] {
263    [r[1] * 5, r[2] * 5, r[3] * 5, r[4] * 5]
264}
265
266/// The five limb products, summed but not carried.
267///
268/// Each term is at most `2^27 * 5*2^26`, and five of them stay under `2^57`,
269/// so four of these vectors can be added before carrying without leaving
270/// `u64`. That headroom is what lets `absorb4` reduce once instead of四 times.
271fn mul_unreduced(h: [u32; 5], r: [u32; 5], s: [u32; 4]) -> [u64; 5] {
272    let m = |a: u32, b: u32| a as u64 * b as u64;
273    [
274        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]),
275        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]),
276        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]),
277        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]),
278        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]),
279    ]
280}
281
282/// Carry-propagate back into 26-bit limbs, folding `2^130` into `5`.
283///
284/// The carries are 64-bit, and have to be. One product's limbs are under
285/// `2^58` and its carries under `2^32`, which is what this function was
286/// first written for. `absorb4` hands it four products summed: limbs up to
287/// about `2^60`, a first carry up to about `2^34`, and a last carry whose
288/// five-fold is about `2^34` too. Held in a `u32`, the first could be cut
289/// short by its cast and the last could overflow its multiplication. That
290/// needs the message limbs to be large: for a kilobyte of `0xff` bytes it
291/// happened under about one key in twenty, and for random bytes never.
292///
293/// With 64-bit carries nothing here can wrap: a limb plus a carry is under
294/// `2^61`, and `c * 5` under `2^41`.
295fn reduce(d: [u64; 5]) -> [u32; 5] {
296    const MASK: u64 = 0x3ff_ffff;
297    let mut c = d[0] >> 26;
298    let a0 = d[0] & MASK;
299    let d1 = d[1] + c;
300    c = d1 >> 26;
301    let a1 = d1 & MASK;
302    let d2 = d[2] + c;
303    c = d2 >> 26;
304    let a2 = d2 & MASK;
305    let d3 = d[3] + c;
306    c = d3 >> 26;
307    let a3 = d3 & MASK;
308    let d4 = d[4] + c;
309    c = d4 >> 26;
310    let a4 = d4 & MASK;
311    // 2^130 = 5 mod p, so what carried out of the top comes back in at the
312    // bottom five times over.
313    let folded = a0 + c * 5;
314    let a0 = folded & MASK;
315    // Under 2^26 + 2^15: the one limb left a little over its width, as the
316    // multiplication expects.
317    let a1 = a1 + (folded >> 26);
318    [a0 as u32, a1 as u32, a2 as u32, a3 as u32, a4 as u32]
319}
320
321impl Mac for Poly1305 {
322    type Tag = [u8; 16];
323    const TAG_LEN: usize = 16;
324
325    fn new(key: &[u8]) -> Result<Self> {
326        ic_core::module::operational()?;
327        ensure!(
328            key.len() == 32,
329            InvalidLength,
330            "poly1305 key must be 32 bytes"
331        );
332        let t0 = u32::from_le_bytes([key[0], key[1], key[2], key[3]]);
333        let t1 = u32::from_le_bytes([key[4], key[5], key[6], key[7]]);
334        let t2 = u32::from_le_bytes([key[8], key[9], key[10], key[11]]);
335        let t3 = u32::from_le_bytes([key[12], key[13], key[14], key[15]]);
336        // `r` is clamped per RFC 8439 §2.5.
337        let r = [
338            t0 & 0x3ff_ffff,
339            ((t0 >> 26) | (t1 << 6)) & 0x3ff_ff03,
340            ((t1 >> 20) | (t2 << 12)) & 0x3ff_c0ff,
341            ((t2 >> 14) | (t3 << 18)) & 0x3f0_3fff,
342            (t3 >> 8) & 0x000_fffff,
343        ];
344        let s = [
345            u32::from_le_bytes([key[16], key[17], key[18], key[19]]),
346            u32::from_le_bytes([key[20], key[21], key[22], key[23]]),
347            u32::from_le_bytes([key[24], key[25], key[26], key[27]]),
348            u32::from_le_bytes([key[28], key[29], key[30], key[31]]),
349        ];
350        // r^2, r^3 and r^4, each from the previous by the same multiply the
351        // hot path uses. Once per key, off the hot path.
352        let rr = five_times(r);
353        let mut powers = [[0u32; 5]; 3];
354        let mut p = r;
355        for slot in powers.iter_mut() {
356            p = reduce(mul_unreduced(p, r, rr));
357            *slot = p;
358        }
359
360        Ok(Self {
361            r,
362            s,
363            powers,
364            acc: [0u32; 5],
365            buf: [0u8; 16],
366            buffered: 0,
367        })
368    }
369
370    fn update(&mut self, mut data: &[u8]) {
371        if self.buffered > 0 {
372            let take = core::cmp::min(16 - self.buffered, data.len());
373            self.buf[self.buffered..self.buffered + take].copy_from_slice(&data[..take]);
374            self.buffered += take;
375            data = &data[take..];
376            if self.buffered < 16 {
377                return;
378            }
379            let block = self.buf;
380            self.absorb_block(&block);
381            self.buffered = 0;
382        }
383        while data.len() >= 64 {
384            self.absorb4(&data[..64]);
385            data = &data[64..];
386        }
387        let mut chunks = data.chunks_exact(16);
388        for block in &mut chunks {
389            self.absorb_block(block);
390        }
391        let rest = chunks.remainder();
392        self.buf[..rest.len()].copy_from_slice(rest);
393        self.buffered = rest.len();
394    }
395
396    fn finalize(mut self) -> [u8; 16] {
397        if self.buffered > 0 {
398            let n = self.buffered;
399            let block = self.buf;
400            self.absorb_block(&block[..n]);
401            self.buffered = 0;
402        }
403
404        // Final reduction modulo 2^130 - 5.
405        let mut h = self.acc;
406        let mut c = h[1] >> 26;
407        h[1] &= 0x3ff_ffff;
408        h[2] += c;
409        c = h[2] >> 26;
410        h[2] &= 0x3ff_ffff;
411        h[3] += c;
412        c = h[3] >> 26;
413        h[3] &= 0x3ff_ffff;
414        h[4] += c;
415        c = h[4] >> 26;
416        h[4] &= 0x3ff_ffff;
417        h[0] += c * 5;
418        c = h[0] >> 26;
419        h[0] &= 0x3ff_ffff;
420        h[1] += c;
421
422        // g = h + 5, then select g if it did not overflow past 2^130.
423        let mut g = [0u32; 5];
424        let mut carry = 5u32;
425        for i in 0..4 {
426            let v = h[i] + carry;
427            g[i] = v & 0x3ff_ffff;
428            carry = v >> 26;
429        }
430        // The top limb is left unmasked so the borrow out of 2^130 is visible
431        // in its sign bit.
432        g[4] = h[4].wrapping_add(carry).wrapping_sub(1 << 26);
433        let mask = ((g[4] >> 31) ^ 1).wrapping_neg();
434        for i in 0..5 {
435            h[i] = (h[i] & !mask) | (g[i] & mask);
436        }
437
438        // Serialize as a 128-bit little-endian value, then add `s`.
439        let h0 = h[0] | (h[1] << 26);
440        let h1 = (h[1] >> 6) | (h[2] << 20);
441        let h2 = (h[2] >> 12) | (h[3] << 14);
442        let h3 = (h[3] >> 18) | (h[4] << 8);
443
444        let mut f = h0 as u64 + self.s[0] as u64;
445        let r0 = f as u32;
446        f = h1 as u64 + self.s[1] as u64 + (f >> 32);
447        let r1 = f as u32;
448        f = h2 as u64 + self.s[2] as u64 + (f >> 32);
449        let r2 = f as u32;
450        f = h3 as u64 + self.s[3] as u64 + (f >> 32);
451        let r3 = f as u32;
452
453        let mut tag = [0u8; 16];
454        tag[0..4].copy_from_slice(&r0.to_le_bytes());
455        tag[4..8].copy_from_slice(&r1.to_le_bytes());
456        tag[8..12].copy_from_slice(&r2.to_le_bytes());
457        tag[12..16].copy_from_slice(&r3.to_le_bytes());
458        tag
459    }
460}
461
462impl SelfTest for Poly1305 {
463    fn self_test() -> Result<()> {
464        // RFC 8439 §2.5.2.
465        let mut key = [0u8; 32];
466        ic_core::codec::hex_decode(
467            b"85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b",
468            &mut key,
469        )?;
470        let tag = <Self as Mac>::mac(&key, b"Cryptographic Forum Research Group")?;
471        let mut want = [0u8; 16];
472        ic_core::codec::hex_decode(b"a8061dc1305136c6c22b8baf0c0127a9", &mut want)?;
473        ensure!(ic_core::ct::verify(&want, &tag), SelfTestFailed, "poly1305");
474        Ok(())
475    }
476}
477
478/// RFC 8439 ChaCha20-Poly1305 AEAD.
479pub struct ChaCha20Poly1305 {
480    key: [u8; 32],
481}
482
483impl Drop for ChaCha20Poly1305 {
484    fn drop(&mut self) {
485        self.key.zeroize();
486    }
487}
488
489impl Algorithm for ChaCha20Poly1305 {
490    const ID: &'static str = "chacha20-poly1305";
491    const NAME: &'static str = "ChaCha20-Poly1305";
492}
493
494impl ChaCha20Poly1305 {
495    /// Derive the one-time Poly1305 key from block 0 of the ChaCha20 keystream.
496    fn poly_key(&self, nonce: &[u8]) -> Result<[u8; 32]> {
497        let mut block = [0u8; 64];
498        let mut n = [0u8; 12];
499        ensure!(nonce.len() == 12, InvalidLength, "chacha20-poly1305 nonce");
500        n.copy_from_slice(nonce);
501        chacha20_block(&self.key, 0, &n, &mut block);
502        let mut k = [0u8; 32];
503        k.copy_from_slice(&block[..32]);
504        block.zeroize();
505        Ok(k)
506    }
507
508    /// Compute the AEAD tag over `aad || pad || ciphertext || pad || lengths`.
509    fn tag(&self, nonce: &[u8], aad: &[u8], ciphertext: &[u8]) -> Result<[u8; 16]> {
510        let mut poly_key = self.poly_key(nonce)?;
511        let mut m = Poly1305::new(&poly_key)?;
512        poly_key.zeroize();
513
514        m.update(aad);
515        m.update(&[0u8; 16][..(16 - aad.len() % 16) % 16]);
516        m.update(ciphertext);
517        m.update(&[0u8; 16][..(16 - ciphertext.len() % 16) % 16]);
518
519        let mut lens = [0u8; 16];
520        lens[..8].copy_from_slice(&(aad.len() as u64).to_le_bytes());
521        lens[8..].copy_from_slice(&(ciphertext.len() as u64).to_le_bytes());
522        m.update(&lens);
523        Ok(m.finalize())
524    }
525}
526
527impl Aead for ChaCha20Poly1305 {
528    const KEY_LEN: usize = 32;
529    const NONCE_LEN: usize = 12;
530    const TAG_LEN: usize = 16;
531
532    fn new(key: &[u8]) -> Result<Self> {
533        ic_core::module::operational()?;
534        ensure!(key.len() == 32, InvalidLength, "chacha20-poly1305 key");
535        let mut k = [0u8; 32];
536        k.copy_from_slice(key);
537        Ok(Self { key: k })
538    }
539
540    fn seal_detached(
541        &self,
542        nonce: &[u8],
543        aad: &[u8],
544        in_out: &mut [u8],
545        tag: &mut [u8],
546    ) -> Result<()> {
547        ic_core::module::operational()?;
548        ensure!(
549            tag.len() == 16,
550            InvalidLength,
551            "chacha20-poly1305 tag buffer"
552        );
553        // Block 0 is reserved for the Poly1305 key, so data starts at block 1.
554        chacha20_xor(&self.key, nonce, 1, in_out)?;
555        let t = self.tag(nonce, aad, in_out)?;
556        tag.copy_from_slice(&t);
557        Ok(())
558    }
559
560    fn open_detached(&self, nonce: &[u8], aad: &[u8], in_out: &mut [u8], tag: &[u8]) -> Result<()> {
561        ic_core::module::operational()?;
562        ensure!(tag.len() == 16, InvalidLength, "chacha20-poly1305 tag");
563        let expected = self.tag(nonce, aad, in_out)?;
564        if !ic_core::ct::verify(&expected, tag) {
565            in_out.zeroize();
566            return Err(ic_core::err!(AuthenticationFailed, "chacha20-poly1305"));
567        }
568        chacha20_xor(&self.key, nonce, 1, in_out)
569    }
570}
571
572impl SelfTest for ChaCha20Poly1305 {
573    fn self_test() -> Result<()> {
574        let key = [0x42u8; 32];
575        let nonce = [0x24u8; 12];
576        let c = <Self as Aead>::new(&key)?;
577        let mut buf = *b"self-test payload";
578        let original = buf;
579        let mut tag = [0u8; 16];
580        c.seal_detached(&nonce, b"aad", &mut buf, &mut tag)?;
581        ensure!(buf != original, SelfTestFailed, "chacha20-poly1305");
582        c.open_detached(&nonce, b"aad", &mut buf, &tag)?;
583        ensure!(buf == original, SelfTestFailed, "chacha20-poly1305");
584        Ok(())
585    }
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591
592    /// The eight-way path must produce the same keystream as the block path.
593    ///
594    /// RFC 8439's vectors do not establish this. The longest of them is 114
595    /// bytes and the AVX2 stride is 512, so on this machine they exercise the
596    /// scalar path exclusively and would pass with the vector code producing
597    /// anything at all. This builds the expected keystream one block at a time
598    /// -- the function the RFC vectors do validate -- and compares.
599    ///
600    /// Lengths straddle the stride in both directions, including several whole
601    /// groups plus a tail, because the dispatch splits there and an off-by-one
602    /// in the split is the likely error rather than a wrong round function.
603    #[cfg(all(target_arch = "x86_64", feature = "std"))]
604    #[test]
605    fn the_avx2_keystream_matches_the_block_function() {
606        if !avx2() {
607            println!("no AVX2 on this CPU; the backend was not exercised");
608            return;
609        }
610
611        let key = [0x5au8; 32];
612        let nonce = [0x21u8; 12];
613
614        let mut checked = 0;
615        for len in [
616            0usize, 1, 63, 64, 65, 127, 511, 512, 513, 575, 576, 1023, 1024, 1025, 4096, 4097,
617        ] {
618            for counter in [0u32, 1, 7, 8, 9, 1000] {
619                let mut actual = std::vec![0u8; len];
620                chacha20_xor(&key, &nonce, counter, &mut actual).unwrap();
621
622                // The reference: one block at a time, no grouping.
623                let mut expect = std::vec![0u8; len];
624                let mut block = [0u8; 64];
625                for (i, chunk) in expect.chunks_mut(64).enumerate() {
626                    chacha20_block(&key, counter + i as u32, &nonce, &mut block);
627                    for (d, b) in chunk.iter_mut().zip(block.iter()) {
628                        *d ^= b;
629                    }
630                }
631
632                assert_eq!(
633                    actual, expect,
634                    "AVX2 and scalar keystreams differ at {len} bytes, counter {counter}"
635                );
636                checked += 1;
637            }
638        }
639        assert_eq!(checked, 16 * 6, "the comparison did not run");
640    }
641
642    /// Say which path this build will take.
643    #[cfg(all(target_arch = "x86_64", feature = "std"))]
644    #[test]
645    fn the_active_chacha_path_is_reported() {
646        println!(
647            "chacha20 backend: {}",
648            if avx2() { "AVX2 (8 blocks)" } else { "scalar" }
649        );
650    }
651    use ic_core::codec::{hex, unhex};
652
653    #[test]
654    fn rfc8439_chacha20_keystream_vector() {
655        let key: Vec<u8> = (0..32u8).collect();
656        let mut nonce = [0u8; 12];
657        nonce[3] = 0x09;
658        nonce[7] = 0x4a;
659        let mut data = [0u8; 64];
660        chacha20_xor(&key, &nonce, 1, &mut data).unwrap();
661        assert_eq!(
662            hex(&data),
663            "10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4ed2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e"
664        );
665    }
666
667    #[test]
668    fn rfc8439_chacha20_encryption_vector() {
669        let key: Vec<u8> = (0..32u8).collect();
670        let mut nonce = [0u8; 12];
671        nonce[7] = 0x4a;
672        let mut data =
673            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();
674        chacha20_xor(&key, &nonce, 1, &mut data).unwrap();
675        assert_eq!(
676            hex(&data),
677            "6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0bf91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d807ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab77937365af90bbf74a35be6b40b8eedf2785e42874d"
678        );
679    }
680
681    #[test]
682    fn rfc8439_poly1305_vector() {
683        let key =
684            unhex("85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b").unwrap();
685        let tag = Poly1305::mac(&key, b"Cryptographic Forum Research Group").unwrap();
686        assert_eq!(hex(&tag), "a8061dc1305136c6c22b8baf0c0127a9");
687    }
688
689    #[test]
690    fn poly1305_streaming_matches_one_shot() {
691        let key = [0x11u8; 32];
692        let data: Vec<u8> = (0..100u8).collect();
693        for split in [0usize, 1, 15, 16, 17, 50, 100] {
694            let mut m = Poly1305::new(&key).unwrap();
695            m.update(&data[..split]);
696            m.update(&data[split..]);
697            assert_eq!(
698                m.finalize(),
699                Poly1305::mac(&key, &data).unwrap(),
700                "split at {split}"
701            );
702        }
703    }
704
705    #[test]
706    fn rfc8439_aead_vector() {
707        let key =
708            unhex("808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f").unwrap();
709        let nonce = unhex("070000004041424344454647").unwrap();
710        let aad = unhex("50515253c0c1c2c3c4c5c6c7").unwrap();
711        let mut buf =
712            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();
713
714        let c = ChaCha20Poly1305::new(&key).unwrap();
715        let mut tag = [0u8; 16];
716        c.seal_detached(&nonce, &aad, &mut buf, &mut tag).unwrap();
717        assert_eq!(
718            hex(&buf),
719            "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"
720        );
721        assert_eq!(hex(&tag), "1ae10b594f09e26a7e902ecbd0600691");
722
723        c.open_detached(&nonce, &aad, &mut buf, &tag).unwrap();
724        assert_eq!(
725            &buf[..],
726            &b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it."[..]
727        );
728    }
729
730    #[test]
731    fn aead_rejects_tampering_and_wipes() {
732        let c = ChaCha20Poly1305::new(&[1u8; 32]).unwrap();
733        let mut buf = b"secret".to_vec();
734        let mut tag = [0u8; 16];
735        c.seal_detached(&[2u8; 12], b"", &mut buf, &mut tag)
736            .unwrap();
737        tag[15] ^= 0x80;
738        assert!(c.open_detached(&[2u8; 12], b"", &mut buf, &tag).is_err());
739        assert_eq!(buf, vec![0u8; 6]);
740    }
741
742    /// The four-at-a-time Poly1305 must agree with the one-at-a-time path.
743    ///
744    /// RFC 8439's Poly1305 vector is 34 bytes and the group is 64, so it never
745    /// reaches `absorb4`: it would pass with the grouped arithmetic returning
746    /// anything. This drives both over lengths either side of the boundary and
747    /// compares the tags.
748    ///
749    /// The powers are the likely error. `r^2`, `r^3` and `r^4` are built from
750    /// `r` at construction, and a multiply that reduces wrongly, or passes the
751    /// key addend where it wants five times `r`, produces powers that are
752    /// self-consistent and wrong -- which every short vector still accepts.
753    /// The grouped path at the top of its range.
754    ///
755    /// Four products are summed before one reduction, so the reduction's
756    /// carries are four times what the one-block path gives it. With every
757    /// message limb at its largest -- a message of `0xff` bytes, which a
758    /// peer can simply send -- the last carry times five can exceed 32 bits,
759    /// and so can the first carry itself. For fifteen releases the reduction
760    /// held both in a `u32`: where they did not fit, a build with overflow
761    /// checks stopped, and one without them computed a wrong tag. The test
762    /// beside this one could not see it, because its message bytes are
763    /// spread evenly and such sums never come near the top.
764    ///
765    /// So this is the same comparison on the inputs that reach it: the
766    /// largest message limbs, under keys whose `r` is as large as clamping
767    /// allows and under ordinary ones.
768    #[test]
769    fn the_grouped_poly1305_agrees_with_the_serial_one_at_its_largest_carries() {
770        let mut keys = Vec::new();
771        // r with every bit that clamping leaves: 0x0ffffffc0ffffffc0ffffffc0fffffff.
772        let mut largest = [0xffu8; 32];
773        largest[..16].copy_from_slice(&[
774            0xff, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff, 0xff, 0x0f, 0xfc, 0xff,
775            0xff, 0x0f,
776        ]);
777        keys.push(largest);
778        keys.push([0xffu8; 32]);
779        for seed in 0u8..62 {
780            let mut key = [0u8; 32];
781            for (i, b) in key.iter_mut().enumerate() {
782                *b = (i as u8)
783                    .wrapping_mul(0x9d)
784                    .wrapping_add(seed.wrapping_mul(0x3b))
785                    ^ seed;
786            }
787            keys.push(key);
788        }
789
790        let mut checked = 0;
791        for key in &keys {
792            for fill in [0xffu8, 0xfe, 0x80] {
793                for len in [64usize, 128, 129, 192, 256, 1024] {
794                    let data = vec![fill; len];
795                    let grouped = Poly1305::mac(key, &data).unwrap();
796
797                    let mut serial = Poly1305::new(key).unwrap();
798                    for chunk in data.chunks(16) {
799                        serial.absorb_block(chunk);
800                    }
801                    assert_eq!(
802                        grouped,
803                        serial.finalize(),
804                        "grouped and serial Poly1305 differ: {len} bytes of {fill:#04x}"
805                    );
806                    checked += 1;
807                }
808            }
809        }
810        assert_eq!(checked, 64 * 3 * 6, "the comparison did not run");
811    }
812
813    #[test]
814    fn the_grouped_poly1305_agrees_with_the_serial_one() {
815        let key = [0x8eu8; 32];
816
817        let mut checked = 0;
818        for len in [
819            0usize, 1, 15, 16, 17, 31, 32, 63, 64, 65, 79, 80, 127, 128, 129, 255, 256, 1023, 1024,
820            1025,
821        ] {
822            let data: Vec<u8> = (0..len)
823                .map(|i| ((i as u64).wrapping_mul(0x9e37_79b9) >> 5) as u8)
824                .collect();
825
826            let grouped = Poly1305::mac(&key, &data).unwrap();
827
828            // The reference: absorb_block only, never absorb4.
829            let mut serial = Poly1305::new(&key).unwrap();
830            for chunk in data.chunks(16) {
831                serial.absorb_block(chunk);
832            }
833            let serial = serial.finalize();
834
835            assert_eq!(
836                grouped, serial,
837                "grouped and serial Poly1305 differ at {len} bytes"
838            );
839            checked += 1;
840        }
841        assert_eq!(checked, 20, "the comparison did not run");
842    }
843
844    /// `r^2`, `r^3` and `r^4` must be powers of `r`.
845    ///
846    /// Checked against repeated single multiplication, which the RFC vector
847    /// validates, so a wrong multiplier in the precomputation fails here rather
848    /// than showing up as a wrong tag on long inputs only.
849    #[test]
850    fn the_poly1305_powers_are_powers_of_r() {
851        let m = Poly1305::new(&[0x3bu8; 32]).unwrap();
852        let mut expect = m.r;
853        for (i, stored) in m.powers.iter().enumerate() {
854            expect = reduce(mul_unreduced(expect, m.r, five_times(m.r)));
855            assert_eq!(*stored, expect, "power {} is not r^{}", i, i + 2);
856        }
857        assert_ne!(m.powers[0], m.powers[1]);
858        assert_ne!(m.powers[0], m.r);
859    }
860
861    #[test]
862    fn self_tests_pass() {
863        Poly1305::self_test().unwrap();
864        ChaCha20Poly1305::self_test().unwrap();
865    }
866}