1use 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
16pub type Key = Array<u8, U32>;
18
19pub type XNonce = Array<u8, U24>;
21
22pub type XChaCha20 = StreamCipherCoreWrapper<XChaChaCore<R20>>;
38pub type XChaCha12 = StreamCipherCoreWrapper<XChaChaCore<R12>>;
40pub type XChaCha8 = StreamCipherCoreWrapper<XChaChaCore<R8>>;
42
43#[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 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#[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 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 for _ in 0..R::COUNT {
132 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 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]
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}