Coverage Report

Created: 2026-03-31 07:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/chacha20-0.10.0/src/lib.rs
Line
Count
Source
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
//! # Usage
10
//!
11
//! Cipher functionality is accessed using traits from re-exported [`cipher`] crate, or as a set
12
//! of random number generator types ending in `*Rng` which implement traits from the [`rand_core`]
13
//! crate.
14
//!
15
//! This crate contains the following variants of the ChaCha20 core algorithm:
16
//!
17
//! - [`ChaCha20`]: standard IETF variant with 96-bit nonce
18
//! - [`ChaCha8`] / [`ChaCha12`]: reduced round variants of ChaCha20
19
//! - [`XChaCha20`]: 192-bit extended nonce variant
20
//! - [`XChaCha8`] / [`XChaCha12`]: reduced round variants of XChaCha20
21
//! - [`ChaCha20Legacy`]: "djb" variant with 64-bit nonce.
22
//! **WARNING:** This implementation internally uses 32-bit counter,
23
//! while the original implementation uses 64-bit counter. In other words,
24
//! it does not allow encryption of more than 256 GiB of data.
25
//!
26
//! ## Example
27
#![cfg_attr(feature = "cipher", doc = " ```")]
28
#![cfg_attr(not(feature = "cipher"), doc = " ```ignore")]
29
//! use chacha20::ChaCha20;
30
//! // Import relevant traits
31
//! use chacha20::cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
32
//! use hex_literal::hex;
33
//!
34
//! let key = [0x42; 32];
35
//! let nonce = [0x24; 12];
36
//! let plaintext = hex!("00010203 04050607 08090A0B 0C0D0E0F");
37
//! let ciphertext = hex!("e405626e 4f1236b3 670ee428 332ea20e");
38
//!
39
//! // Key and IV must be references to the `Array` type.
40
//! // Here we use the `Into` trait to convert arrays into it.
41
//! let mut cipher = ChaCha20::new(&key.into(), &nonce.into());
42
//!
43
//! let mut buffer = plaintext.clone();
44
//!
45
//! // apply keystream (encrypt)
46
//! cipher.apply_keystream(&mut buffer);
47
//! assert_eq!(buffer, ciphertext);
48
//!
49
//! let ciphertext = buffer.clone();
50
//!
51
//! // ChaCha ciphers support seeking
52
//! cipher.seek(0u32);
53
//!
54
//! // decrypt ciphertext by applying keystream again
55
//! cipher.apply_keystream(&mut buffer);
56
//! assert_eq!(buffer, plaintext);
57
//!
58
//! // stream ciphers can be used with streaming messages
59
//! cipher.seek(0u32);
60
//! for chunk in buffer.chunks_mut(3) {
61
//!     cipher.apply_keystream(chunk);
62
//! }
63
//! assert_eq!(buffer, ciphertext);
64
//! ```
65
//!
66
//! # Configuration Flags
67
//!
68
//! You can modify crate using the following configuration flags:
69
//!
70
//! - `chacha20_backend="avx2"`: force AVX2 backend on x86/x86_64 targets.
71
//!   Requires enabled AVX2 target feature. Ignored on non-x86(_64) targets.
72
//! - `chacha20_backend="avx512": force AVX-512 backend on x86/x86_64 targets.
73
//!   Requires enabled AVX-512 target feature (MSRV 1.89). Ignored on non-x86(_64) targets.
74
//! - `chacha20_backend="soft"`: force software backend.
75
//! - `chacha20_backend="sse2"`: force SSE2 backend on x86/x86_64 targets.
76
//!   Requires enabled SSE2 target feature. Ignored on non-x86(-64) targets.
77
//!
78
//! The flags can be enabled using `RUSTFLAGS` environmental variable
79
//! (e.g. `RUSTFLAGS='--cfg chacha20_backend="avx2"'`) or by modifying `.cargo/config.toml`:
80
//!
81
//! ```toml
82
//! # In .cargo/config.toml
83
//! [build]
84
//! rustflags = ['--cfg', 'chacha20_backend="avx2"']
85
//! ```
86
//!
87
//! ## AVX-512 support
88
//!
89
//! To use the MSRV 1.89 AVX-512 support, you must enable it using: `--cfg chacha20_avx512`.
90
//!
91
//! [ChaCha]: https://tools.ietf.org/html/rfc8439
92
//! [Salsa]: https://en.wikipedia.org/wiki/Salsa20
93
//! [`chacha20poly1305`]: https://docs.rs/chacha20poly1305
94
95
pub mod variants;
96
97
mod backends;
98
#[cfg(feature = "cipher")]
99
mod chacha;
100
#[cfg(feature = "legacy")]
101
mod legacy;
102
#[cfg(feature = "rng")]
103
mod rng;
104
#[cfg(feature = "xchacha")]
105
mod xchacha;
106
107
#[cfg(feature = "cipher")]
108
pub use chacha::{ChaCha8, ChaCha12, ChaCha20, Key, KeyIvInit};
109
#[cfg(feature = "cipher")]
110
pub use cipher;
111
#[cfg(feature = "legacy")]
112
pub use legacy::{ChaCha20Legacy, LegacyNonce};
113
#[cfg(feature = "rng")]
114
pub use rand_core;
115
#[cfg(feature = "rng")]
116
pub use rng::{ChaCha8Rng, ChaCha12Rng, ChaCha20Rng, Seed, SerializedRngState};
117
#[cfg(feature = "xchacha")]
118
pub use xchacha::{XChaCha8, XChaCha12, XChaCha20, XNonce, hchacha};
119
120
use cfg_if::cfg_if;
121
use core::{fmt, marker::PhantomData};
122
use variants::Variant;
123
124
#[cfg(feature = "cipher")]
125
use cipher::{BlockSizeUser, StreamCipherCore, StreamCipherSeekCore, consts::U64};
126
#[cfg(feature = "zeroize")]
127
use zeroize::{Zeroize, ZeroizeOnDrop};
128
129
/// State initialization constant ("expand 32-byte k")
130
#[cfg(any(feature = "cipher", feature = "rng"))]
131
const CONSTANTS: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
132
133
/// Number of 32-bit words in the ChaCha state
134
const STATE_WORDS: usize = 16;
135
136
/// Marker type for a number of ChaCha rounds to perform.
137
pub trait Rounds: Copy {
138
    /// The amount of rounds to perform
139
    const COUNT: usize;
140
}
141
142
/// 8-rounds
143
#[derive(Copy, Clone, Debug)]
144
pub struct R8;
145
146
impl Rounds for R8 {
147
    const COUNT: usize = 4;
148
}
149
150
/// 12-rounds
151
#[derive(Copy, Clone, Debug)]
152
pub struct R12;
153
154
impl Rounds for R12 {
155
    const COUNT: usize = 6;
156
}
157
158
/// 20-rounds
159
#[derive(Copy, Clone, Debug)]
160
pub struct R20;
161
162
impl Rounds for R20 {
163
    const COUNT: usize = 10;
164
}
165
166
cfg_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
202
/// The ChaCha core function.
203
pub struct ChaChaCore<R: Rounds, V: Variant> {
204
    /// Internal state of the core function
205
    state: [u32; STATE_WORDS],
206
    /// CPU target feature tokens
207
    #[allow(dead_code)]
208
    tokens: Tokens,
209
    /// Number of rounds to perform and the cipher variant
210
    _pd: PhantomData<(R, V)>,
211
}
212
213
impl<R: Rounds, V: Variant> ChaChaCore<R, V> {
214
    /// Constructs a ChaChaCore with the specified `key` and `iv`.
215
    ///
216
    /// You must ensure that the iv is of the correct size when using this method
217
    /// directly.
218
    ///
219
    /// # Panics
220
    /// If `iv.len()` is not equal to 4, 8, or 12.
221
    #[must_use]
222
    #[cfg(any(feature = "cipher", feature = "rng"))]
223
0
    fn new_internal(key: &[u8; 32], iv: &[u8]) -> Self {
224
0
        assert!(matches!(iv.len(), 4 | 8 | 12));
225
226
0
        let mut state = [0u32; STATE_WORDS];
227
228
0
        let ctr_size = size_of::<V::Counter>() / size_of::<u32>();
229
0
        let (const_dst, state_rem) = state.split_at_mut(4);
230
0
        let (key_dst, state_rem) = state_rem.split_at_mut(8);
231
0
        let (_ctr_dst, iv_dst) = state_rem.split_at_mut(ctr_size);
232
233
0
        const_dst.copy_from_slice(&CONSTANTS);
234
235
        // TODO(tarcieri): when MSRV 1.88, use `[T]::as_chunks` to avoid panic
236
        #[allow(clippy::unwrap_used, reason = "MSRV TODO")]
237
        {
238
0
            for (src, dst) in key.chunks_exact(4).zip(key_dst) {
239
0
                *dst = u32::from_le_bytes(src.try_into().unwrap());
240
0
            }
241
242
0
            assert_eq!(size_of_val(iv_dst), size_of_val(iv));
243
0
            for (src, dst) in iv.chunks_exact(4).zip(iv_dst) {
244
0
                *dst = u32::from_le_bytes(src.try_into().unwrap());
245
0
            }
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
0
                        let tokens = (avx2_cpuid::init(), sse2_cpuid::init());
263
                    }
264
                }
265
            } else {
266
                let tokens = ();
267
            }
268
        }
269
0
        Self {
270
0
            state,
271
0
            tokens,
272
0
            _pd: PhantomData,
273
0
        }
274
0
    }
Unexecuted instantiation: <chacha20::ChaChaCore<chacha20::R12, chacha20::variants::Legacy>>::new_internal
Unexecuted instantiation: <chacha20::ChaChaCore<_, _>>::new_internal
275
276
    /// Get the current block position.
277
    #[inline(always)]
278
    #[must_use]
279
0
    pub fn get_block_pos(&self) -> V::Counter {
280
0
        V::get_block_pos(&self.state[12..])
281
0
    }
Unexecuted instantiation: <chacha20::ChaChaCore<chacha20::R12, chacha20::variants::Legacy>>::get_block_pos
Unexecuted instantiation: <chacha20::ChaChaCore<chacha20::R12, chacha20::variants::Legacy>>::get_block_pos
Unexecuted instantiation: <chacha20::ChaChaCore<_, _>>::get_block_pos
282
283
    /// Set the block position.
284
    #[inline(always)]
285
0
    pub fn set_block_pos(&mut self, pos: V::Counter) {
286
0
        V::set_block_pos(&mut self.state[12..], pos);
287
0
    }
288
}
289
290
impl<R: Rounds, V: Variant> fmt::Debug for ChaChaCore<R, V> {
291
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292
0
        write!(
293
0
            f,
294
            "ChaChaCore<R: {}, V: {}-bit)> {{ ... }}",
295
            R::COUNT,
296
            size_of::<V::Counter>() * 8
297
        )
298
0
    }
299
}
300
301
#[cfg(feature = "cipher")]
302
impl<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")]
317
impl<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
                            // SAFETY: runtime CPU feature detection above ensures this is valid
353
                            unsafe {
354
                                backends::avx512::inner::<R, _, V>(&mut self.state, f);
355
                            }
356
                            return;
357
                        }
358
                        if avx2_token.get() {
359
                            // SAFETY: runtime CPU feature detection above ensures this is valid
360
                            unsafe {
361
                                backends::avx2::inner::<R, _, V>(&mut self.state, f);
362
                            }
363
                        } else if sse2_token.get() {
364
                            // SAFETY: runtime CPU feature detection above ensures this is valid
365
                            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
                // SAFETY: we have used conditional compilation to ensure NEON is available
375
                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")]
386
impl<R: Rounds, V: Variant> BlockSizeUser for ChaChaCore<R, V> {
387
    type BlockSize = U64;
388
}
389
390
#[cfg(feature = "zeroize")]
391
impl<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")]
398
impl<R: Rounds, V: Variant> ZeroizeOnDrop for ChaChaCore<R, V> {}
399
400
/// The ChaCha20 quarter round function
401
///
402
/// We located this function in the root of the crate as we want it to be available
403
/// for the soft backend and for xchacha.
404
#[allow(dead_code)]
405
0
pub(crate) fn quarter_round(
406
0
    a: usize,
407
0
    b: usize,
408
0
    c: usize,
409
0
    d: usize,
410
0
    state: &mut [u32; STATE_WORDS],
411
0
) {
412
0
    state[a] = state[a].wrapping_add(state[b]);
413
0
    state[d] ^= state[a];
414
0
    state[d] = state[d].rotate_left(16);
415
416
0
    state[c] = state[c].wrapping_add(state[d]);
417
0
    state[b] ^= state[c];
418
0
    state[b] = state[b].rotate_left(12);
419
420
0
    state[a] = state[a].wrapping_add(state[b]);
421
0
    state[d] ^= state[a];
422
0
    state[d] = state[d].rotate_left(8);
423
424
0
    state[c] = state[c].wrapping_add(state[d]);
425
0
    state[b] ^= state[c];
426
0
    state[b] = state[b].rotate_left(7);
427
0
}