nanorand/crypto/
chacha.rs

1const CHACHA_TAU: &[u8] = b"expand 32-byte k";
2
3fn chacha_quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
4	state[a] = state[a].wrapping_add(state[b]);
5	state[d] ^= state[a];
6	state[d] = state[d].rotate_left(16);
7
8	state[c] = state[c].wrapping_add(state[d]);
9	state[b] ^= state[c];
10	state[b] = state[b].rotate_left(12);
11
12	state[a] = state[a].wrapping_add(state[b]);
13	state[d] ^= state[a];
14	state[d] = state[d].rotate_left(8);
15
16	state[c] = state[c].wrapping_add(state[d]);
17	state[b] ^= state[c];
18	state[b] = state[b].rotate_left(7);
19}
20
21const fn chacha_pack(unpacked: &[u8], idx: usize) -> u32 {
22	(unpacked[idx] as u32)
23		| ((unpacked[idx + 1] as u32) << 8)
24		| ((unpacked[idx + 2] as u32) << 16)
25		| ((unpacked[idx + 3] as u32) << 24)
26}
27
28/// Do one ChaCha round on the input data.
29pub fn chacha_block<const ROUNDS: u8>(input: [u32; 16]) -> [u32; 16] {
30	let mut x = input;
31	assert_eq!(ROUNDS % 2, 0, "ChaCha rounds must be divisble by 2!");
32	for _ in (0..ROUNDS).step_by(2) {
33		// Odd rounds
34		chacha_quarter_round(&mut x, 0, 4, 8, 12);
35		chacha_quarter_round(&mut x, 1, 5, 9, 13);
36		chacha_quarter_round(&mut x, 2, 6, 10, 14);
37		chacha_quarter_round(&mut x, 3, 7, 11, 15);
38		// Even rounds
39		chacha_quarter_round(&mut x, 0, 5, 10, 15);
40		chacha_quarter_round(&mut x, 1, 6, 11, 12);
41		chacha_quarter_round(&mut x, 2, 7, 8, 13);
42		chacha_quarter_round(&mut x, 3, 4, 9, 14);
43	}
44	x.iter_mut()
45		.zip(input.iter())
46		.for_each(|(l, r)| *l = l.wrapping_add(*r));
47	x
48}
49
50/// Initialize the ChaCha internal state, with a 256-bit key and 64-bit nonce.
51pub const fn chacha_init(key: [u8; 32], nonce: [u8; 8]) -> [u32; 16] {
52	let mut state = [0u32; 16];
53	state[0] = chacha_pack(CHACHA_TAU, 0);
54	state[1] = chacha_pack(CHACHA_TAU, 4);
55	state[2] = chacha_pack(CHACHA_TAU, 8);
56	state[3] = chacha_pack(CHACHA_TAU, 12);
57
58	state[4] = chacha_pack(&key, 0);
59	state[5] = chacha_pack(&key, 4);
60	state[6] = chacha_pack(&key, 8);
61	state[7] = chacha_pack(&key, 12);
62	state[8] = chacha_pack(&key, 16);
63	state[9] = chacha_pack(&key, 20);
64	state[10] = chacha_pack(&key, 24);
65	state[11] = chacha_pack(&key, 28);
66
67	// 64-bit counter
68	state[12] = 0;
69	state[13] = 0;
70	// Nonce
71	state[14] = chacha_pack(&nonce, 0);
72	state[15] = chacha_pack(&nonce, 4);
73	state
74}
75
76/// Increment the 64-bit counter of the internal ChaCha20 state by 1.
77/// Returns `false` if it overflows, `true` otherwise.
78pub fn chacha_increment_counter(state: &mut [u32; 16]) -> bool {
79	let counter = ((state[13] as u64) << 32) | (state[12] as u64);
80	match counter.checked_add(1) {
81		Some(new_counter) => {
82			state[12] = (new_counter & 0xFFFFFFFF) as u32;
83			state[13] = ((counter >> 32) & 0xFFFFFFFF) as u32;
84			true
85		}
86		None => false,
87	}
88}
89
90#[cfg(test)]
91mod tests {
92	use super::*;
93	use std::convert::TryInto;
94
95	macro_rules! ietf_test_vector {
96		($key_hex: tt, $nonce_hex: tt, $keystream_hex: tt) => {
97			let key: [u8; 32] = hex::decode($key_hex).unwrap().try_into().unwrap();
98			let nonce: [u8; 8] = hex::decode($nonce_hex).unwrap().try_into().unwrap();
99			let expected_keystream: Vec<u8> = hex::decode($keystream_hex).unwrap();
100
101			let mut state = chacha_init(key, nonce);
102			let mut keystream: Vec<u8> = Vec::with_capacity(expected_keystream.len());
103
104			while expected_keystream.len() > keystream.len() {
105				chacha_block::<20>(state)
106					.iter()
107					.for_each(|packed| keystream.extend_from_slice(&packed.to_le_bytes()));
108				chacha_increment_counter(&mut state);
109			}
110			keystream.resize(expected_keystream.len(), 0);
111
112			assert_eq!(keystream, expected_keystream);
113		};
114	}
115
116	#[test]
117	fn test_ietf_chacha20_test_vectors() {
118		ietf_test_vector!(
119			"0000000000000000000000000000000000000000000000000000000000000000",
120			"0000000000000000",
121			"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"
122		);
123
124		ietf_test_vector!(
125			"0000000000000000000000000000000000000000000000000000000000000001",
126			"0000000000000000",
127			"4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963"
128		);
129
130		ietf_test_vector!(
131			"0000000000000000000000000000000000000000000000000000000000000000",
132			"0000000000000001",
133			"de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3"
134		);
135
136		ietf_test_vector!(
137			"0000000000000000000000000000000000000000000000000000000000000000",
138			"0100000000000000",
139			"ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b"
140		);
141
142		ietf_test_vector!(
143			"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
144			"0001020304050607",
145			"f798a189f195e66982105ffb640bb7757f579da31602fc93ec01ac56f85ac3c134a4547b733b46413042c9440049176905d3be59ea1c53f15916155c2be8241a38008b9a26bc35941e2444177c8ade6689de95264986d95889fb60e84629c9bd9a5acb1cc118be563eb9b3a4a472f82e09a7e778492b562ef7130e88dfe031c79db9d4f7c7a899151b9a475032b63fc385245fe054e3dd5a97a5f576fe064025d3ce042c566ab2c507b138db853e3d6959660996546cc9c4a6eafdc777c040d70eaf46f76dad3979e5c5360c3317166a1c894c94a371876a94df7628fe4eaaf2ccb27d5aaae0ad7ad0f9d4b6ad3b54098746d4524d38407a6deb3ab78fab78c9"
146		);
147	}
148}