chacha20/
xchacha.rs

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