1use crate::{
4 CONSTANTS, ChaChaCore, Key, 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 XNonce = Array<u8, U24>;
18
19pub type XChaCha20 = StreamCipherCoreWrapper<XChaChaCore<R20>>;
35pub type XChaCha12 = StreamCipherCoreWrapper<XChaChaCore<R12>>;
37pub type XChaCha8 = StreamCipherCoreWrapper<XChaChaCore<R8>>;
39
40#[derive(Debug)]
42pub struct XChaChaCore<R: Rounds>(ChaChaCore<R, Ietf>);
43
44impl<R: Rounds> KeySizeUser for XChaChaCore<R> {
45 type KeySize = U32;
46}
47
48impl<R: Rounds> IvSizeUser for XChaChaCore<R> {
49 type IvSize = U24;
50}
51
52impl<R: Rounds> BlockSizeUser for XChaChaCore<R> {
53 type BlockSize = U64;
54}
55
56impl<R: Rounds> KeyIvInit for XChaChaCore<R> {
57 fn new(key: &Key, iv: &XNonce) -> Self {
58 #[allow(clippy::unwrap_used)]
59 let subkey = hchacha::<R>(key, iv[..16].as_ref().try_into().unwrap());
60
61 let mut nonce = [0u8; 12];
62 nonce[4..].copy_from_slice(&iv[16..]);
65 Self(ChaChaCore::<R, Ietf>::new_internal(subkey.as_ref(), &nonce))
66 }
67}
68
69impl<R: Rounds> StreamCipherCore for XChaChaCore<R> {
70 #[inline(always)]
71 fn remaining_blocks(&self) -> Option<usize> {
72 self.0.remaining_blocks()
73 }
74
75 #[inline(always)]
76 fn process_with_backend(&mut self, f: impl StreamCipherClosure<BlockSize = Self::BlockSize>) {
77 self.0.process_with_backend(f);
78 }
79}
80
81impl<R: Rounds> StreamCipherSeekCore for XChaChaCore<R> {
82 type Counter = u32;
83
84 #[inline(always)]
85 fn get_block_pos(&self) -> u32 {
86 self.0.get_block_pos()
87 }
88
89 #[inline(always)]
90 fn set_block_pos(&mut self, pos: u32) {
91 self.0.set_block_pos(pos);
92 }
93}
94
95#[cfg(feature = "zeroize")]
96impl<R: Rounds> ZeroizeOnDrop for XChaChaCore<R> {}
97
98#[must_use]
113pub fn hchacha<R: Rounds>(key: &Key, input: &Array<u8, U16>) -> Array<u8, U32> {
114 let mut state = [0u32; STATE_WORDS];
115 state[..4].copy_from_slice(&CONSTANTS);
116
117 let key_chunks = Array::<u8, U4>::slice_as_chunks(key).0;
119 for (v, chunk) in state[4..12].iter_mut().zip(key_chunks) {
120 *v = u32::from_le_bytes(chunk.0);
121 }
122 let input_chunks = Array::<u8, U4>::slice_as_chunks(input).0;
123 for (v, chunk) in state[12..16].iter_mut().zip(input_chunks) {
124 *v = u32::from_le_bytes(chunk.0);
125 }
126
127 for _ in 0..R::COUNT {
129 quarter_round(0, 4, 8, 12, &mut state);
131 quarter_round(1, 5, 9, 13, &mut state);
132 quarter_round(2, 6, 10, 14, &mut state);
133 quarter_round(3, 7, 11, 15, &mut state);
134
135 quarter_round(0, 5, 10, 15, &mut state);
137 quarter_round(1, 6, 11, 12, &mut state);
138 quarter_round(2, 7, 8, 13, &mut state);
139 quarter_round(3, 4, 9, 14, &mut state);
140 }
141
142 let mut output = Array::default();
143
144 for (chunk, val) in output[..16].chunks_exact_mut(4).zip(&state[..4]) {
145 chunk.copy_from_slice(&val.to_le_bytes());
146 }
147
148 for (chunk, val) in output[16..].chunks_exact_mut(4).zip(&state[12..]) {
149 chunk.copy_from_slice(&val.to_le_bytes());
150 }
151
152 output
153}
154
155#[cfg(test)]
156mod hchacha20_tests {
157 use super::*;
158 use hex_literal::hex;
159
160 #[test]
163 fn test_vector() {
164 const KEY: [u8; 32] = hex!(
165 "000102030405060708090a0b0c0d0e0f"
166 "101112131415161718191a1b1c1d1e1f"
167 );
168
169 const INPUT: [u8; 16] = hex!("000000090000004a0000000031415927");
170
171 const OUTPUT: [u8; 32] = hex!(
172 "82413b4227b27bfed30e42508a877d73"
173 "a0f9e4d58a74a853c12ec41326d3ecdc"
174 );
175
176 let actual = hchacha::<R20>(&KEY.into(), &INPUT.into());
177 assert_eq!(actual.as_slice(), &OUTPUT);
178 }
179}