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