chacha20/
xchacha.rs

1//! XChaCha is an extended nonce variant of ChaCha
2
3use crate::{
4    CONSTANTS, ChaChaCore, R8, R12, R20, Rounds, STATE_WORDS, quarter_round, variants::Ietf,
5};
6use cipher::{
7    BlockSizeUser, IvSizeUser, KeyIvInit, KeySizeUser, StreamCipherClosure, StreamCipherCore,
8    StreamCipherCoreWrapper, StreamCipherSeekCore,
9    array::Array,
10    consts::{U4, U16, U24, U32, U64},
11};
12
13#[cfg(feature = "zeroize")]
14use zeroize::ZeroizeOnDrop;
15
16/// Key type used by all ChaCha variants.
17pub type Key = Array<u8, U32>;
18
19/// Nonce type used by XChaCha variants.
20pub type XNonce = Array<u8, U24>;
21
22/// XChaCha is a ChaCha20 variant with an extended 192-bit (24-byte) nonce.
23///
24/// The construction is an adaptation of the same techniques used by
25/// XChaCha as described in the paper "Extending the Salsa20 Nonce",
26/// applied to the 96-bit nonce variant of ChaCha20, and derive a
27/// separate subkey/nonce for each extended nonce:
28///
29/// <https://cr.yp.to/snuffle/xsalsa-20081128.pdf>
30///
31/// No authoritative specification exists for XChaCha20, however the
32/// construction has "rough consensus and running code" in the form of
33/// several interoperable libraries and protocols (e.g. libsodium, WireGuard)
34/// and is documented in an (expired) IETF draft:
35///
36/// <https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha>
37pub type XChaCha20 = StreamCipherCoreWrapper<XChaChaCore<R20>>;
38/// XChaCha12 stream cipher (reduced-round variant of [`XChaCha20`] with 12 rounds)
39pub type XChaCha12 = StreamCipherCoreWrapper<XChaChaCore<R12>>;
40/// XChaCha8 stream cipher (reduced-round variant of [`XChaCha20`] with 8 rounds)
41pub type XChaCha8 = StreamCipherCoreWrapper<XChaChaCore<R8>>;
42
43/// The XChaCha core function.
44#[derive(Debug)]
45pub struct XChaChaCore<R: Rounds>(ChaChaCore<R, Ietf>);
46
47impl<R: Rounds> KeySizeUser for XChaChaCore<R> {
48    type KeySize = U32;
49}
50
51impl<R: Rounds> IvSizeUser for XChaChaCore<R> {
52    type IvSize = U24;
53}
54
55impl<R: Rounds> BlockSizeUser for XChaChaCore<R> {
56    type BlockSize = U64;
57}
58
59impl<R: Rounds> KeyIvInit for XChaChaCore<R> {
60    fn new(key: &Key, iv: &XNonce) -> Self {
61        #[allow(clippy::unwrap_used)]
62        let subkey = hchacha::<R>(key, iv[..16].as_ref().try_into().unwrap());
63
64        let mut nonce = [0u8; 12];
65        // first 4 bytes are 0, last 8 bytes are last 8 from the iv
66        // according to draft-arciszewski-xchacha-03
67        nonce[4..].copy_from_slice(&iv[16..]);
68        Self(ChaChaCore::<R, Ietf>::new_internal(subkey.as_ref(), &nonce))
69    }
70}
71
72impl<R: Rounds> StreamCipherCore for XChaChaCore<R> {
73    #[inline(always)]
74    fn remaining_blocks(&self) -> Option<usize> {
75        self.0.remaining_blocks()
76    }
77
78    #[inline(always)]
79    fn process_with_backend(&mut self, f: impl StreamCipherClosure<BlockSize = Self::BlockSize>) {
80        self.0.process_with_backend(f);
81    }
82}
83
84impl<R: Rounds> StreamCipherSeekCore for XChaChaCore<R> {
85    type Counter = u32;
86
87    #[inline(always)]
88    fn get_block_pos(&self) -> u32 {
89        self.0.get_block_pos()
90    }
91
92    #[inline(always)]
93    fn set_block_pos(&mut self, pos: u32) {
94        self.0.set_block_pos(pos);
95    }
96}
97
98#[cfg(feature = "zeroize")]
99impl<R: Rounds> ZeroizeOnDrop for XChaChaCore<R> {}
100
101/// The HChaCha function: adapts the ChaCha core function in the same
102/// manner that HSalsa adapts the Salsa function.
103///
104/// HChaCha takes 512-bits of input:
105///
106/// - Constants: `u32` x 4
107/// - Key: `u32` x 8
108/// - Nonce: `u32` x 4
109///
110/// It produces 256-bits of output suitable for use as a ChaCha key
111///
112/// For more information on HSalsa on which HChaCha is based, see:
113///
114/// <http://cr.yp.to/snuffle/xsalsa-20110204.pdf>
115#[must_use]
116pub fn hchacha<R: Rounds>(key: &Key, input: &Array<u8, U16>) -> Array<u8, U32> {
117    let mut state = [0u32; STATE_WORDS];
118    state[..4].copy_from_slice(&CONSTANTS);
119
120    // TODO(tarcieri): use `[T]::as_chunks` when MSRV 1.88
121    let key_chunks = Array::<u8, U4>::slice_as_chunks(key).0;
122    for (v, chunk) in state[4..12].iter_mut().zip(key_chunks) {
123        *v = u32::from_le_bytes(chunk.0);
124    }
125    let input_chunks = Array::<u8, U4>::slice_as_chunks(input).0;
126    for (v, chunk) in state[12..16].iter_mut().zip(input_chunks) {
127        *v = u32::from_le_bytes(chunk.0);
128    }
129
130    // R rounds consisting of R/2 column rounds and R/2 diagonal rounds
131    for _ in 0..R::COUNT {
132        // column rounds
133        quarter_round(0, 4, 8, 12, &mut state);
134        quarter_round(1, 5, 9, 13, &mut state);
135        quarter_round(2, 6, 10, 14, &mut state);
136        quarter_round(3, 7, 11, 15, &mut state);
137
138        // diagonal rounds
139        quarter_round(0, 5, 10, 15, &mut state);
140        quarter_round(1, 6, 11, 12, &mut state);
141        quarter_round(2, 7, 8, 13, &mut state);
142        quarter_round(3, 4, 9, 14, &mut state);
143    }
144
145    let mut output = Array::default();
146
147    for (chunk, val) in output[..16].chunks_exact_mut(4).zip(&state[..4]) {
148        chunk.copy_from_slice(&val.to_le_bytes());
149    }
150
151    for (chunk, val) in output[16..].chunks_exact_mut(4).zip(&state[12..]) {
152        chunk.copy_from_slice(&val.to_le_bytes());
153    }
154
155    output
156}
157
158#[cfg(test)]
159mod hchacha20_tests {
160    use super::*;
161    use hex_literal::hex;
162
163    /// Test vectors from:
164    /// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha#section-2.2.1
165    #[test]
166    fn test_vector() {
167        const KEY: [u8; 32] = hex!(
168            "000102030405060708090a0b0c0d0e0f"
169            "101112131415161718191a1b1c1d1e1f"
170        );
171
172        const INPUT: [u8; 16] = hex!("000000090000004a0000000031415927");
173
174        const OUTPUT: [u8; 32] = hex!(
175            "82413b4227b27bfed30e42508a877d73"
176            "a0f9e4d58a74a853c12ec41326d3ecdc"
177        );
178
179        let actual = hchacha::<R20>(&KEY.into(), &INPUT.into());
180        assert_eq!(actual.as_slice(), &OUTPUT);
181    }
182}