1use core::fmt;
7
8use crate::zeroing::{Zeroize, ZeroizeOnDrop};
9
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
12pub struct Error;
13
14impl fmt::Display for Error {
15 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16 formatter.write_str("caller-provided randomness source failed")
17 }
18}
19
20#[cfg(feature = "std")]
21impl std::error::Error for Error {}
22
23pub trait RngCore {
25 fn next_u32(&mut self) -> u32 {
26 let mut bytes = crate::zeroing::Zeroizing::new([0u8; 4]);
27 self.fill_bytes(&mut bytes[..]);
28 u32::from(bytes[0])
29 | (u32::from(bytes[1]) << 8)
30 | (u32::from(bytes[2]) << 16)
31 | (u32::from(bytes[3]) << 24)
32 }
33
34 fn next_u64(&mut self) -> u64 {
35 let mut bytes = crate::zeroing::Zeroizing::new([0u8; 8]);
36 self.fill_bytes(&mut bytes[..]);
37 let mut value = crate::zeroing::Zeroizing::new(0u64);
38 for (index, byte) in bytes.iter().enumerate() {
39 *value |= u64::from(*byte) << (index * 8);
40 }
41 *value
42 }
43
44 fn fill_bytes(&mut self, destination: &mut [u8]) {
45 try_fill_bytes_zeroing_on_error(self, destination)
46 .expect("caller-provided randomness source failed")
47 }
48
49 fn try_fill_bytes(&mut self, destination: &mut [u8]) -> Result<(), Error>;
52}
53
54impl<R: RngCore + ?Sized> RngCore for &mut R {
55 fn next_u32(&mut self) -> u32 {
56 (**self).next_u32()
57 }
58
59 fn next_u64(&mut self) -> u64 {
60 (**self).next_u64()
61 }
62
63 fn fill_bytes(&mut self, destination: &mut [u8]) {
64 try_fill_bytes_zeroing_on_error(&mut **self, destination)
65 .expect("caller-provided randomness source failed")
66 }
67
68 fn try_fill_bytes(&mut self, destination: &mut [u8]) -> Result<(), Error> {
69 (**self).try_fill_bytes(destination)
70 }
71}
72
73pub fn try_fill_bytes_zeroing_on_error<R: RngCore + ?Sized>(
79 rng: &mut R,
80 destination: &mut [u8],
81) -> Result<(), Error> {
82 let result = rng.try_fill_bytes(destination);
83 if result.is_err() {
84 destination.zeroize();
85 }
86 result
87}
88
89pub trait CryptoRng: RngCore {}
91
92impl<R: CryptoRng + ?Sized> CryptoRng for &mut R {}
93
94pub struct ChaCha20Rng {
109 key: [u32; 8],
110 counter: u32,
111 buffer: [u8; 64],
112 offset: usize,
113 exhausted: bool,
114}
115
116impl ChaCha20Rng {
117 pub fn from_seed(seed: [u8; 32]) -> Self {
118 let seed = crate::zeroing::Zeroizing::new(seed);
119 let mut generator = Self {
120 key: [0u32; 8],
121 counter: 0,
122 buffer: [0u8; 64],
123 offset: 64,
124 exhausted: false,
125 };
126 for index in 0..8 {
127 let offset = index * 4;
128 generator.key[index] = u32::from(seed[offset])
129 | (u32::from(seed[offset + 1]) << 8)
130 | (u32::from(seed[offset + 2]) << 16)
131 | (u32::from(seed[offset + 3]) << 24);
132 }
133 generator
134 }
135
136 fn refill(&mut self) -> Result<(), Error> {
137 if self.exhausted {
138 return Err(Error);
139 }
140 chacha20_block(&self.key, self.counter, &mut self.buffer);
141 self.offset = 0;
142 if self.counter == u32::MAX {
143 self.exhausted = true;
144 } else {
145 self.counter += 1;
146 }
147 Ok(())
148 }
149}
150
151impl RngCore for ChaCha20Rng {
152 fn try_fill_bytes(&mut self, destination: &mut [u8]) -> Result<(), Error> {
153 let mut written = 0;
154 while written < destination.len() {
155 if self.offset == self.buffer.len() && self.refill().is_err() {
156 destination.zeroize();
157 return Err(Error);
158 }
159 let available = self.buffer.len() - self.offset;
160 let take = core::cmp::min(available, destination.len() - written);
161 destination[written..written + take]
162 .copy_from_slice(&self.buffer[self.offset..self.offset + take]);
163 self.offset += take;
164 written += take;
165 }
166 Ok(())
167 }
168}
169
170impl CryptoRng for ChaCha20Rng {}
171
172impl Zeroize for ChaCha20Rng {
173 fn zeroize(&mut self) {
174 self.key.zeroize();
175 self.counter.zeroize();
176 self.buffer.zeroize();
177 self.offset.zeroize();
178 self.exhausted.zeroize();
179 }
180}
181
182impl Drop for ChaCha20Rng {
183 fn drop(&mut self) {
184 self.zeroize();
185 }
186}
187
188impl ZeroizeOnDrop for ChaCha20Rng {}
189
190#[inline(always)]
191fn quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
192 state[a] = state[a].wrapping_add(state[b]);
193 state[d] ^= state[a];
194 state[d] = state[d].rotate_left(16);
195 state[c] = state[c].wrapping_add(state[d]);
196 state[b] ^= state[c];
197 state[b] = state[b].rotate_left(12);
198 state[a] = state[a].wrapping_add(state[b]);
199 state[d] ^= state[a];
200 state[d] = state[d].rotate_left(8);
201 state[c] = state[c].wrapping_add(state[d]);
202 state[b] ^= state[c];
203 state[b] = state[b].rotate_left(7);
204}
205
206fn chacha20_block(key: &[u32; 8], counter: u32, output: &mut [u8; 64]) {
207 let initial = crate::zeroing::Zeroizing::new([
208 0x6170_7865,
209 0x3320_646e,
210 0x7962_2d32,
211 0x6b20_6574,
212 key[0],
213 key[1],
214 key[2],
215 key[3],
216 key[4],
217 key[5],
218 key[6],
219 key[7],
220 counter,
221 0,
222 0,
223 0,
224 ]);
225 let mut state = crate::zeroing::Zeroizing::new(*initial);
226 for _ in 0..10 {
227 quarter_round(&mut state, 0, 4, 8, 12);
228 quarter_round(&mut state, 1, 5, 9, 13);
229 quarter_round(&mut state, 2, 6, 10, 14);
230 quarter_round(&mut state, 3, 7, 11, 15);
231 quarter_round(&mut state, 0, 5, 10, 15);
232 quarter_round(&mut state, 1, 6, 11, 12);
233 quarter_round(&mut state, 2, 7, 8, 13);
234 quarter_round(&mut state, 3, 4, 9, 14);
235 }
236 for index in 0..16 {
237 let word = crate::zeroing::Zeroizing::new(state[index].wrapping_add(initial[index]));
238 for byte in 0..4 {
239 output[index * 4 + byte] = (*word >> (byte * 8)) as u8;
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::{chacha20_block, try_fill_bytes_zeroing_on_error, ChaCha20Rng, Error, RngCore};
247 use crate::zeroing::Zeroize;
248
249 struct PartiallyFailingRng;
250
251 impl RngCore for PartiallyFailingRng {
252 fn try_fill_bytes(&mut self, destination: &mut [u8]) -> Result<(), Error> {
253 let written = core::cmp::min(3, destination.len());
254 destination[..written].fill(0xA5);
255 Err(Error)
256 }
257 }
258
259 #[test]
260 fn defensive_fill_erases_partial_rng_output_on_error() {
261 let mut rng = PartiallyFailingRng;
262 let mut destination = crate::zeroing::Zeroizing::new([0xCC; 8]);
263 assert!(try_fill_bytes_zeroing_on_error(&mut rng, &mut destination[..]).is_err());
264 assert_eq!(*destination, [0; 8]);
265 }
266
267 #[test]
268 fn chacha_rng_erases_partial_output_when_counter_exhausts() {
269 let mut rng = ChaCha20Rng::from_seed([9; 32]);
270 rng.counter = u32::MAX;
271 rng.offset = rng.buffer.len();
272
273 let mut destination = crate::zeroing::Zeroizing::new([0xCC; 65]);
274 assert!(rng.try_fill_bytes(&mut destination[..]).is_err());
275 assert_eq!(*destination, [0; 65]);
276 }
277
278 #[test]
279 fn zero_key_zero_nonce_block_matches_rfc_8439_primitive() {
280 let expected = [
281 0x76, 0xb8, 0xe0, 0xad, 0xa0, 0xf1, 0x3d, 0x90, 0x40, 0x5d, 0x6a, 0xe5, 0x53, 0x86,
282 0xbd, 0x28, 0xbd, 0xd2, 0x19, 0xb8, 0xa0, 0x8d, 0xed, 0x1a, 0xa8, 0x36, 0xef, 0xcc,
283 0x8b, 0x77, 0x0d, 0xc7, 0xda, 0x41, 0x59, 0x7c, 0x51, 0x57, 0x48, 0x8d, 0x77, 0x24,
284 0xe0, 0x3f, 0xb8, 0xd8, 0x4a, 0x37, 0x6a, 0x43, 0xb8, 0xf4, 0x15, 0x18, 0xa1, 0x1c,
285 0xc3, 0x87, 0xb6, 0x69, 0xb2, 0xee, 0x65, 0x86,
286 ];
287 let mut actual = crate::zeroing::Zeroizing::new([0u8; 64]);
288 chacha20_block(&[0u32; 8], 0, &mut actual);
289 assert_eq!(*actual, expected);
290 }
291
292 #[test]
293 fn chunking_does_not_change_the_stream() {
294 let seed = crate::zeroing::Zeroizing::new([0x42; 32]);
295 let mut whole = ChaCha20Rng::from_seed(*seed);
296 let mut chunked = ChaCha20Rng::from_seed(*seed);
297 let mut left = crate::zeroing::Zeroizing::new([0u8; 137]);
298 let mut right = crate::zeroing::Zeroizing::new([0u8; 137]);
299 whole.fill_bytes(&mut left[..]);
300 chunked.fill_bytes(&mut right[..3]);
301 chunked.fill_bytes(&mut right[3..91]);
302 chunked.fill_bytes(&mut right[91..]);
303 assert_eq!(*left, *right);
304 }
305
306 #[test]
307 fn explicit_zeroize_clears_generator_state() {
308 let mut generator = ChaCha20Rng::from_seed([0x42; 32]);
309 let mut output = crate::zeroing::Zeroizing::new([0u8; 8]);
310 generator.fill_bytes(&mut output[..]);
311 generator.zeroize();
312 assert_eq!(generator.key, [0; 8]);
313 assert_eq!(generator.counter, 0);
314 assert_eq!(generator.buffer, [0; 64]);
315 assert_eq!(generator.offset, 0);
316 assert!(!generator.exhausted);
317 }
318}