1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5 html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
6 html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
7)]
8
9#![cfg_attr(feature = "cipher", doc = " ```")]
28#![cfg_attr(not(feature = "cipher"), doc = " ```ignore")]
29pub mod variants;
96
97mod backends;
98#[cfg(feature = "cipher")]
99mod chacha;
100#[cfg(feature = "legacy")]
101mod legacy;
102#[cfg(feature = "rng")]
103mod rng;
104#[cfg(feature = "xchacha")]
105mod xchacha;
106
107#[cfg(feature = "cipher")]
108pub use chacha::{ChaCha8, ChaCha12, ChaCha20, Key, KeyIvInit};
109#[cfg(feature = "cipher")]
110pub use cipher;
111#[cfg(feature = "legacy")]
112pub use legacy::{ChaCha20Legacy, LegacyNonce};
113#[cfg(feature = "rng")]
114pub use rand_core;
115#[cfg(feature = "rng")]
116pub use rng::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng, Seed, SerializedRngState};
117#[cfg(feature = "xchacha")]
118pub use xchacha::{XChaCha8, XChaCha12, XChaCha20, XNonce, hchacha};
119
120use cfg_if::cfg_if;
121use core::{fmt, marker::PhantomData};
122use variants::Variant;
123
124#[cfg(feature = "cipher")]
125use cipher::{BlockSizeUser, StreamCipherCore, StreamCipherSeekCore, consts::U64};
126#[cfg(feature = "zeroize")]
127use zeroize::{Zeroize, ZeroizeOnDrop};
128
129#[cfg(any(feature = "cipher", feature = "rng"))]
131const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
132
133const STATE_WORDS: usize = 16;
135
136pub trait Rounds: Copy {
138 const COUNT: usize;
140}
141
142#[derive(Copy, Clone, Debug)]
144pub struct R8;
145
146impl Rounds for R8 {
147 const COUNT: usize = 4;
148}
149
150#[derive(Copy, Clone, Debug)]
152pub struct R12;
153
154impl Rounds for R12 {
155 const COUNT: usize = 6;
156}
157
158#[derive(Copy, Clone, Debug)]
160pub struct R20;
161
162impl Rounds for R20 {
163 const COUNT: usize = 10;
164}
165
166cfg_if! {
167 if #[cfg(chacha20_backend = "soft")] {
168 type Tokens = ();
169 } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
170 cfg_if! {
171 if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
172 #[cfg(not(all(target_feature = "avx512f", target_feature = "avx512vl")))]
173 compile_error!("You must enable `avx512f` and `avx512vl` target features with \
174 `chacha20_backend = "avx512"` configuration option");
175 type Tokens = ();
176 } else if #[cfg(chacha20_backend = "avx2")] {
177 #[cfg(not(target_feature = "avx2"))]
178 compile_error!("You must enable `avx2` target feature with \
179 `chacha20_backend = "avx2"` configuration option");
180 type Tokens = ();
181 } else if #[cfg(chacha20_backend = "sse2")] {
182 #[cfg(not(target_feature = "sse2"))]
183 compile_error!("You must enable `sse2` target feature with \
184 `chacha20_backend = "sse2"` configuration option");
185 type Tokens = ();
186 } else {
187 #[cfg(chacha20_avx512)]
188 cpufeatures::new!(avx512_cpuid, "avx512f", "avx512vl");
189 cpufeatures::new!(avx2_cpuid, "avx2");
190 cpufeatures::new!(sse2_cpuid, "sse2");
191 #[cfg(chacha20_avx512)]
192 type Tokens = (avx512_cpuid::InitToken, avx2_cpuid::InitToken, sse2_cpuid::InitToken);
193 #[cfg(not(chacha20_avx512))]
194 type Tokens = (avx2_cpuid::InitToken, sse2_cpuid::InitToken);
195 }
196 }
197 } else {
198 type Tokens = ();
199 }
200}
201
202pub struct ChaChaCore<R: Rounds, V: Variant> {
204 state: [u32; STATE_WORDS],
206 #[allow(dead_code)]
208 tokens: Tokens,
209 _pd: PhantomData<(R, V)>,
211}
212
213impl<R: Rounds, V: Variant> ChaChaCore<R, V> {
214 #[must_use]
222 #[cfg(any(feature = "cipher", feature = "rng"))]
223 fn new_internal(key: &[u8; 32], iv: &[u8]) -> Self {
224 assert!(matches!(iv.len(), 4 | 8 | 12));
225
226 let mut state = [0u32; STATE_WORDS];
227
228 let ctr_size = size_of::<V::Counter>() / size_of::<u32>();
229 let (const_dst, state_rem) = state.split_at_mut(4);
230 let (key_dst, state_rem) = state_rem.split_at_mut(8);
231 let (_ctr_dst, iv_dst) = state_rem.split_at_mut(ctr_size);
232
233 const_dst.copy_from_slice(&CONSTANTS);
234
235 #[allow(clippy::unwrap_used, reason = "MSRV TODO")]
237 {
238 for (src, dst) in key.chunks_exact(4).zip(key_dst) {
239 *dst = u32::from_le_bytes(src.try_into().unwrap());
240 }
241
242 assert_eq!(size_of_val(iv_dst), size_of_val(iv));
243 for (src, dst) in iv.chunks_exact(4).zip(iv_dst) {
244 *dst = u32::from_le_bytes(src.try_into().unwrap());
245 }
246 }
247
248 cfg_if! {
249 if #[cfg(chacha20_backend = "soft")] {
250 let tokens = ();
251 } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
252 cfg_if! {
253 if #[cfg(chacha20_backend = "avx512")] {
254 let tokens = ();
255 } else if #[cfg(chacha20_backend = "avx2")] {
256 let tokens = ();
257 } else if #[cfg(chacha20_backend = "sse2")] {
258 let tokens = ();
259 } else if #[cfg(chacha20_avx512)] {
260 let tokens = (avx512_cpuid::init(), avx2_cpuid::init(), sse2_cpuid::init());
261 } else {
262 let tokens = (avx2_cpuid::init(), sse2_cpuid::init());
263 }
264 }
265 } else {
266 let tokens = ();
267 }
268 }
269 Self {
270 state,
271 tokens,
272 _pd: PhantomData,
273 }
274 }
275
276 #[inline(always)]
278 #[must_use]
279 pub fn get_block_pos(&self) -> V::Counter {
280 V::get_block_pos(&self.state[12..])
281 }
282
283 #[inline(always)]
285 pub fn set_block_pos(&mut self, pos: V::Counter) {
286 V::set_block_pos(&mut self.state[12..], pos);
287 }
288}
289
290impl<R: Rounds, V: Variant> fmt::Debug for ChaChaCore<R, V> {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 write!(
293 f,
294 "ChaChaCore<R: {}, V: {}-bit)> {{ ... }}",
295 R::COUNT,
296 size_of::<V::Counter>() * 8
297 )
298 }
299}
300
301#[cfg(feature = "cipher")]
302impl<R: Rounds, V: Variant> StreamCipherSeekCore for ChaChaCore<R, V> {
303 type Counter = V::Counter;
304
305 #[inline(always)]
306 fn get_block_pos(&self) -> Self::Counter {
307 self.get_block_pos()
308 }
309
310 #[inline(always)]
311 fn set_block_pos(&mut self, pos: Self::Counter) {
312 self.set_block_pos(pos);
313 }
314}
315
316#[cfg(feature = "cipher")]
317impl<R: Rounds, V: Variant> StreamCipherCore for ChaChaCore<R, V> {
318 #[inline(always)]
319 fn remaining_blocks(&self) -> Option<usize> {
320 V::remaining_blocks(self.get_block_pos())
321 }
322
323 fn process_with_backend(
324 &mut self,
325 f: impl cipher::StreamCipherClosure<BlockSize = Self::BlockSize>,
326 ) {
327 cfg_if! {
328 if #[cfg(chacha20_backend = "soft")] {
329 f.call(&mut backends::soft::Backend(self));
330 } else if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
331 cfg_if! {
332 if #[cfg(all(chacha20_avx512, chacha20_backend = "avx512"))] {
333 unsafe {
334 backends::avx512::inner::<R, _, V>(&mut self.state, f);
335 }
336 } else if #[cfg(chacha20_backend = "avx2")] {
337 unsafe {
338 backends::avx2::inner::<R, _, V>(&mut self.state, f);
339 }
340 } else if #[cfg(chacha20_backend = "sse2")] {
341 unsafe {
342 backends::sse2::inner::<R, _, V>(&mut self.state, f);
343 }
344 } else {
345 #[cfg(chacha20_avx512)]
346 let (avx512_token, avx2_token, sse2_token) = self.tokens;
347 #[cfg(not(chacha20_avx512))]
348 let (avx2_token, sse2_token) = self.tokens;
349
350 #[cfg(chacha20_avx512)]
351 if avx512_token.get() {
352 unsafe {
354 backends::avx512::inner::<R, _, V>(&mut self.state, f);
355 }
356 return;
357 }
358 if avx2_token.get() {
359 unsafe {
361 backends::avx2::inner::<R, _, V>(&mut self.state, f);
362 }
363 } else if sse2_token.get() {
364 unsafe {
366 backends::sse2::inner::<R, _, V>(&mut self.state, f);
367 }
368 } else {
369 f.call(&mut backends::soft::Backend(self));
370 }
371 }
372 }
373 } else if #[cfg(all(target_arch = "aarch64", target_feature = "neon"))] {
374 unsafe {
376 backends::neon::inner::<R, _, V>(&mut self.state, f);
377 }
378 } else {
379 f.call(&mut backends::soft::Backend(self));
380 }
381 }
382 }
383}
384
385#[cfg(feature = "cipher")]
386impl<R: Rounds, V: Variant> BlockSizeUser for ChaChaCore<R, V> {
387 type BlockSize = U64;
388}
389
390#[cfg(feature = "zeroize")]
391impl<R: Rounds, V: Variant> Drop for ChaChaCore<R, V> {
392 fn drop(&mut self) {
393 self.state.zeroize();
394 }
395}
396
397#[cfg(feature = "zeroize")]
398impl<R: Rounds, V: Variant> ZeroizeOnDrop for ChaChaCore<R, V> {}
399
400#[allow(dead_code)]
405pub(crate) fn quarter_round(
406 a: usize,
407 b: usize,
408 c: usize,
409 d: usize,
410 state: &mut [u32; STATE_WORDS],
411) {
412 state[a] = state[a].wrapping_add(state[b]);
413 state[d] ^= state[a];
414 state[d] = state[d].rotate_left(16);
415
416 state[c] = state[c].wrapping_add(state[d]);
417 state[b] ^= state[c];
418 state[b] = state[b].rotate_left(12);
419
420 state[a] = state[a].wrapping_add(state[b]);
421 state[d] ^= state[a];
422 state[d] = state[d].rotate_left(8);
423
424 state[c] = state[c].wrapping_add(state[d]);
425 state[b] ^= state[c];
426 state[b] = state[b].rotate_left(7);
427}