Skip to content

Commit 5615ace

Browse files
committed
Include ChaCha pseudorandom generator
1 parent 482e778 commit 5615ace

File tree

3 files changed

+288
-1
lines changed

3 files changed

+288
-1
lines changed

src/librand/chacha.rs

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! The ChaCha random number generator.
12+
13+
use core::prelude::*;
14+
15+
use {Rng, SeedableRng, Rand};
16+
17+
static KEY_WORDS : uint = 8; // 8 words for the 256-bit key
18+
static STATE_WORDS : uint = 16;
19+
static CHACHA_ROUNDS: uint = 20; // Cryptographically secure from 8 upwards as of this writing
20+
21+
/// A random number generator that uses the ChaCha20 algorithm [1].
22+
///
23+
/// The ChaCha algorithm is widely accepted as suitable for
24+
/// cryptographic purposes, but this implementation has not been
25+
/// verified as such. Prefer a generator like `OsRng` that defers to
26+
/// the operating system for cases that need high security.
27+
///
28+
/// [1]: D. J. Bernstein, [*ChaCha, a variant of
29+
/// Salsa20*](http://cr.yp.to/chacha.html)
30+
31+
pub struct ChaChaRng {
32+
buffer: [u32, ..STATE_WORDS], // Internal buffer of output
33+
state: [u32, ..STATE_WORDS], // Initial state
34+
index: uint, // Index into state
35+
}
36+
37+
static EMPTY: ChaChaRng = ChaChaRng {
38+
buffer: [0, ..STATE_WORDS],
39+
state: [0, ..STATE_WORDS],
40+
index: STATE_WORDS
41+
};
42+
43+
44+
macro_rules! quarter_round{
45+
($a: expr, $b: expr, $c: expr, $d: expr) => {{
46+
$a += $b; $d ^= $a; $d = $d.rotate_left(16);
47+
$c += $d; $b ^= $c; $b = $b.rotate_left(12);
48+
$a += $b; $d ^= $a; $d = $d.rotate_left( 8);
49+
$c += $d; $b ^= $c; $b = $b.rotate_left( 7);
50+
}}
51+
}
52+
53+
macro_rules! double_round{
54+
($x: expr) => {{
55+
// Column round
56+
quarter_round!($x[ 0], $x[ 4], $x[ 8], $x[12]);
57+
quarter_round!($x[ 1], $x[ 5], $x[ 9], $x[13]);
58+
quarter_round!($x[ 2], $x[ 6], $x[10], $x[14]);
59+
quarter_round!($x[ 3], $x[ 7], $x[11], $x[15]);
60+
// Diagonal round
61+
quarter_round!($x[ 0], $x[ 5], $x[10], $x[15]);
62+
quarter_round!($x[ 1], $x[ 6], $x[11], $x[12]);
63+
quarter_round!($x[ 2], $x[ 7], $x[ 8], $x[13]);
64+
quarter_round!($x[ 3], $x[ 4], $x[ 9], $x[14]);
65+
}}
66+
}
67+
68+
#[inline]
69+
fn core(output: &mut [u32, ..STATE_WORDS], input: &[u32, ..STATE_WORDS]) {
70+
*output = *input;
71+
72+
for _ in range(0, CHACHA_ROUNDS / 2) {
73+
double_round!(output);
74+
}
75+
76+
for i in range(0, STATE_WORDS) {
77+
output[i] += input[i];
78+
}
79+
}
80+
81+
impl ChaChaRng {
82+
83+
/// Create an ChaCha random number generator using the default
84+
/// fixed key of 8 zero words.
85+
pub fn new_unseeded() -> ChaChaRng {
86+
let mut rng = EMPTY;
87+
rng.init(&[0, ..KEY_WORDS]);
88+
rng
89+
}
90+
91+
/// Sets the internal 128-bit ChaCha counter to
92+
/// a user-provided value. This permits jumping
93+
/// arbitrarily ahead (or backwards) in the pseudorandom stream.
94+
///
95+
/// Since the nonce words are used to extend the counter to 128 bits,
96+
/// users wishing to obtain the conventional ChaCha pseudorandom stream
97+
/// associated with a particular nonce can call this function with
98+
/// arguments `0, desired_nonce`.
99+
pub fn set_counter(&mut self, counter_low: u64, counter_high: u64) {
100+
self.state[12] = (counter_low >> 0) as u32;
101+
self.state[13] = (counter_low >> 32) as u32;
102+
self.state[14] = (counter_high >> 0) as u32;
103+
self.state[15] = (counter_high >> 32) as u32;
104+
self.index = STATE_WORDS; // force recomputation
105+
}
106+
107+
/// Initializes `self.state` with the appropriate key and constants
108+
///
109+
/// We deviate slightly from the ChaCha specification regarding
110+
/// the nonce, which is used to extend the counter to 128 bits.
111+
/// This is provably as strong as the original cipher, though,
112+
/// since any distinguishing attack on our variant also works
113+
/// against ChaCha with a chosen-nonce. See the XSalsa20 [1]
114+
/// security proof for a more involved example of this.
115+
///
116+
/// The modified word layout is:
117+
/// ```notrust
118+
/// constant constant constant constant
119+
/// key key key key
120+
/// key key key key
121+
/// counter counter counter counter
122+
/// ```
123+
/// [1]: Daniel J. Bernstein. [*Extending the Salsa20
124+
/// nonce.*](http://cr.yp.to/papers.html#xsalsa)
125+
fn init(&mut self, key: &[u32, ..KEY_WORDS]) {
126+
self.state[0] = 0x61707865;
127+
self.state[1] = 0x3320646E;
128+
self.state[2] = 0x79622D32;
129+
self.state[3] = 0x6B206574;
130+
131+
for i in range(0, KEY_WORDS) {
132+
self.state[4+i] = key[i];
133+
}
134+
135+
self.state[12] = 0;
136+
self.state[13] = 0;
137+
self.state[14] = 0;
138+
self.state[15] = 0;
139+
140+
self.index = STATE_WORDS;
141+
}
142+
143+
/// Refill the internal output buffer (`self.buffer`)
144+
fn update(&mut self) {
145+
core(&mut self.buffer, &self.state);
146+
self.index = 0;
147+
// update 128-bit counter
148+
self.state[12] += 1;
149+
if self.state[12] != 0 { return };
150+
self.state[13] += 1;
151+
if self.state[13] != 0 { return };
152+
self.state[14] += 1;
153+
if self.state[14] != 0 { return };
154+
self.state[15] += 1;
155+
}
156+
}
157+
158+
impl Rng for ChaChaRng {
159+
#[inline]
160+
fn next_u32(&mut self) -> u32 {
161+
if self.index == STATE_WORDS {
162+
self.update();
163+
}
164+
165+
let value = self.buffer[self.index % STATE_WORDS];
166+
self.index += 1;
167+
value
168+
}
169+
}
170+
171+
impl<'a> SeedableRng<&'a [u32]> for ChaChaRng {
172+
173+
fn reseed(&mut self, seed: &'a [u32]) {
174+
// reset state
175+
self.init(&[0u32, ..KEY_WORDS]);
176+
// set key inplace
177+
let key = self.state.slice_mut(4, 4+KEY_WORDS);
178+
for (k, s) in key.iter_mut().zip(seed.iter()) {
179+
*k = *s;
180+
}
181+
}
182+
183+
/// Create a ChaCha generator from a seed,
184+
/// obtained from a variable-length u32 array.
185+
/// Only up to 8 words are used; if less than 8
186+
/// words are used, the remaining are set to zero.
187+
fn from_seed(seed: &'a [u32]) -> ChaChaRng {
188+
let mut rng = EMPTY;
189+
rng.reseed(seed);
190+
rng
191+
}
192+
}
193+
194+
impl Rand for ChaChaRng {
195+
fn rand<R: Rng>(other: &mut R) -> ChaChaRng {
196+
let mut key : [u32, ..KEY_WORDS] = [0, ..KEY_WORDS];
197+
for word in key.iter_mut() {
198+
*word = other.gen();
199+
}
200+
SeedableRng::from_seed(key.as_slice())
201+
}
202+
}
203+
204+
205+
#[cfg(test)]
206+
mod test {
207+
use std::prelude::*;
208+
209+
use core::iter::order;
210+
use {Rng, SeedableRng};
211+
use super::ChaChaRng;
212+
213+
#[test]
214+
fn test_rng_rand_seeded() {
215+
let s = ::test::rng().gen_iter::<u32>().take(8).collect::<Vec<u32>>();
216+
let mut ra: ChaChaRng = SeedableRng::from_seed(s.as_slice());
217+
let mut rb: ChaChaRng = SeedableRng::from_seed(s.as_slice());
218+
assert!(order::equals(ra.gen_ascii_chars().take(100),
219+
rb.gen_ascii_chars().take(100)));
220+
}
221+
222+
#[test]
223+
fn test_rng_seeded() {
224+
let seed : &[_] = &[0,1,2,3,4,5,6,7];
225+
let mut ra: ChaChaRng = SeedableRng::from_seed(seed);
226+
let mut rb: ChaChaRng = SeedableRng::from_seed(seed);
227+
assert!(order::equals(ra.gen_ascii_chars().take(100),
228+
rb.gen_ascii_chars().take(100)));
229+
}
230+
231+
#[test]
232+
fn test_rng_reseed() {
233+
let s = ::test::rng().gen_iter::<u32>().take(8).collect::<Vec<u32>>();
234+
let mut r: ChaChaRng = SeedableRng::from_seed(s.as_slice());
235+
let string1: String = r.gen_ascii_chars().take(100).collect();
236+
237+
r.reseed(s.as_slice());
238+
239+
let string2: String = r.gen_ascii_chars().take(100).collect();
240+
assert_eq!(string1, string2);
241+
}
242+
243+
#[test]
244+
fn test_rng_true_values() {
245+
// Test vectors 1 and 2 from
246+
// http://tools.ietf.org/html/draft-nir-cfrg-chacha20-poly1305-04
247+
let seed : &[_] = &[0u32, ..8];
248+
let mut ra: ChaChaRng = SeedableRng::from_seed(seed);
249+
250+
let v = Vec::from_fn(16, |_| ra.next_u32());
251+
assert_eq!(v,
252+
vec!(0xade0b876, 0x903df1a0, 0xe56a5d40, 0x28bd8653,
253+
0xb819d2bd, 0x1aed8da0, 0xccef36a8, 0xc70d778b,
254+
0x7c5941da, 0x8d485751, 0x3fe02477, 0x374ad8b8,
255+
0xf4b8436a, 0x1ca11815, 0x69b687c3, 0x8665eeb2));
256+
257+
let v = Vec::from_fn(16, |_| ra.next_u32());
258+
assert_eq!(v,
259+
vec!(0xbee7079f, 0x7a385155, 0x7c97ba98, 0x0d082d73,
260+
0xa0290fcb, 0x6965e348, 0x3e53c612, 0xed7aee32,
261+
0x7621b729, 0x434ee69c, 0xb03371d5, 0xd539d874,
262+
0x281fed31, 0x45fb0a51, 0x1f0ae1ac, 0x6f4d794b));
263+
264+
265+
let seed : &[_] = &[0,1,2,3,4,5,6,7];
266+
let mut ra: ChaChaRng = SeedableRng::from_seed(seed);
267+
268+
// Store the 17*i-th 32-bit word,
269+
// i.e., the i-th word of the i-th 16-word block
270+
let mut v : Vec<u32> = Vec::new();
271+
for _ in range(0u, 16) {
272+
v.push(ra.next_u32());
273+
for _ in range(0u, 16) {
274+
ra.next_u32();
275+
}
276+
}
277+
278+
assert_eq!(v,
279+
vec!(0xf225c81a, 0x6ab1be57, 0x04d42951, 0x70858036,
280+
0x49884684, 0x64efec72, 0x4be2d186, 0x3615b384,
281+
0x11cfa18e, 0xd3c50049, 0x75c775f6, 0x434c6530,
282+
0x2c5bad8f, 0x898881dc, 0x5f1c86d9, 0xc1f8e7f4));
283+
}
284+
}
285+

src/librand/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ extern crate core;
3939
use core::prelude::*;
4040

4141
pub use isaac::{IsaacRng, Isaac64Rng};
42+
pub use chacha::ChaChaRng;
4243

4344
use distributions::{Range, IndependentSample};
4445
use distributions::range::SampleRange;
@@ -48,6 +49,7 @@ static RAND_BENCH_N: u64 = 100;
4849

4950
pub mod distributions;
5051
pub mod isaac;
52+
pub mod chacha;
5153
pub mod reseeding;
5254
mod rand_impls;
5355

src/libstd/rand/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ use core_rand::IsaacRng as IsaacWordRng;
233233
use core_rand::Isaac64Rng as IsaacWordRng;
234234

235235
pub use core_rand::{Rand, Rng, SeedableRng, Open01, Closed01};
236-
pub use core_rand::{XorShiftRng, IsaacRng, Isaac64Rng};
236+
pub use core_rand::{XorShiftRng, IsaacRng, Isaac64Rng, ChaChaRng};
237237
pub use core_rand::{distributions, reseeding};
238238
pub use rand::os::OsRng;
239239

0 commit comments

Comments
 (0)