Skip to main content

crypto/chacha/
hchacha20.rs

1/// HChaCha20 is a hash function derived from ChaCha20.
2///
3/// It takes a 256-bit key and a 128-bit input (nonce) and produces a 256-bit
4/// hash. It is used internally by XChaCha20 to derive a subkey.
5///
6/// # Algorithm
7///
8/// 1. Initialize a ChaCha20 state with the constant, key, and 128-bit nonce
9///    (placed in the counter and nonce positions).
10/// 2. Perform 20 rounds (10 double rounds) of ChaCha quarter rounds.
11/// 3. Output words 0-3 and 12-15 of the final state, serialized as
12///    little-endian bytes (without adding the initial state back).
13pub fn hchacha20(key: &[u8; 32], input: &[u8; 16]) -> [u8; 32] {
14    use super::{CONSTANT, STATE_WORDS, quarter_round};
15
16    let mut state = [0u32; STATE_WORDS];
17
18    state[..4].copy_from_slice(&CONSTANT);
19
20    for (state_word, key_chunk) in state[4..12].iter_mut().zip(key.chunks_exact(4)) {
21        *state_word = u32::from_le_bytes(key_chunk.try_into().unwrap());
22    }
23
24    state[12] = u32::from_le_bytes(input[0..4].try_into().unwrap());
25    state[13] = u32::from_le_bytes(input[4..8].try_into().unwrap());
26    state[14] = u32::from_le_bytes(input[8..12].try_into().unwrap());
27    state[15] = u32::from_le_bytes(input[12..16].try_into().unwrap());
28
29    for _ in 0..10 {
30        quarter_round(&mut state, 0, 4, 8, 12);
31        quarter_round(&mut state, 1, 5, 9, 13);
32        quarter_round(&mut state, 2, 6, 10, 14);
33        quarter_round(&mut state, 3, 7, 11, 15);
34
35        quarter_round(&mut state, 0, 5, 10, 15);
36        quarter_round(&mut state, 1, 6, 11, 12);
37        quarter_round(&mut state, 2, 7, 8, 13);
38        quarter_round(&mut state, 3, 4, 9, 14);
39    }
40
41    let mut output = [0u8; 32];
42    for i in 0..4 {
43        output[i * 4..(i + 1) * 4].copy_from_slice(&state[i].to_le_bytes());
44    }
45    for i in 0..4 {
46        output[16 + i * 4..16 + (i + 1) * 4].copy_from_slice(&state[12 + i].to_le_bytes());
47    }
48
49    return output;
50}
51
52#[cfg(test)]
53mod test {
54    use super::hchacha20;
55
56    /// Test vector from draft-irtf-cfrg-xchacha-03, Section 2.2.1.
57    #[test]
58    fn hchacha20_test_vector_1() {
59        let key: [u8; 32] =
60            hex::decode_array(b"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f").unwrap();
61        let input: [u8; 16] = hex::decode_array(b"000000090000004a0000000031415927").unwrap();
62        let expected: [u8; 32] =
63            hex::decode_array(b"82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc").unwrap();
64
65        assert_eq!(hchacha20(&key, &input), expected);
66    }
67
68    /// Test with all-zero key and input.
69    #[test]
70    fn hchacha20_all_zeros() {
71        let key = [0u8; 32];
72        let input = [0u8; 16];
73        let expected: [u8; 32] =
74            hex::decode_array(b"1140704c328d1d5d0e30086cdf209dbd6a43b8f41518a11cc387b669b2ee6586").unwrap();
75
76        assert_eq!(hchacha20(&key, &input), expected);
77    }
78}