const SIGMA: [u32; 4] = [0x6170_7865, 0x3320_646e, 0x7962_2d32, 0x6b20_6574];
macro_rules! quarter {
($s:ident, $a:literal, $b:literal, $c:literal, $d:literal) => {
$s[$a] = $s[$a].wrapping_add($s[$b]);
$s[$d] = ($s[$d] ^ $s[$a]).rotate_left(16);
$s[$c] = $s[$c].wrapping_add($s[$d]);
$s[$b] = ($s[$b] ^ $s[$c]).rotate_left(12);
$s[$a] = $s[$a].wrapping_add($s[$b]);
$s[$d] = ($s[$d] ^ $s[$a]).rotate_left(8);
$s[$c] = $s[$c].wrapping_add($s[$d]);
$s[$b] = ($s[$b] ^ $s[$c]).rotate_left(7);
};
}
fn rounds(s: &mut [u32; 16]) {
for _ in 0..10 {
quarter!(s, 0, 4, 8, 12);
quarter!(s, 1, 5, 9, 13);
quarter!(s, 2, 6, 10, 14);
quarter!(s, 3, 7, 11, 15);
quarter!(s, 0, 5, 10, 15);
quarter!(s, 1, 6, 11, 12);
quarter!(s, 2, 7, 8, 13);
quarter!(s, 3, 4, 9, 14);
}
}
fn words<const B: usize, const N: usize>(bytes: &[u8; B]) -> [u32; N] {
let mut out = [0u32; N];
for (word, chunk) in out.iter_mut().zip(bytes.chunks_exact(4)) {
let mut four = [0u8; 4];
four.copy_from_slice(chunk);
*word = u32::from_le_bytes(four);
}
out
}
fn initial(key: &[u8; 32], counter: u32, nonce: &[u8; 12]) -> [u32; 16] {
let k: [u32; 8] = words(key);
let n: [u32; 3] = words(nonce);
[
SIGMA[0], SIGMA[1], SIGMA[2], SIGMA[3], k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7],
counter, n[0], n[1], n[2],
]
}
pub fn block(key: &[u8; 32], counter: u32, nonce: &[u8; 12]) -> [u8; 64] {
let start = initial(key, counter, nonce);
let mut state = start;
rounds(&mut state);
let mut out = [0u8; 64];
for ((chunk, word), first) in out.chunks_exact_mut(4).zip(state.iter()).zip(start.iter()) {
chunk.copy_from_slice(&word.wrapping_add(*first).to_le_bytes());
}
out
}
pub fn apply(key: &[u8; 32], counter: u32, nonce: &[u8; 12], data: &mut [u8]) {
let mut start = initial(key, counter, nonce);
let mut blocks = data.chunks_exact_mut(64);
for chunk in &mut blocks {
let mut state = start;
rounds(&mut state);
for ((four, word), first) in chunk
.chunks_exact_mut(4)
.zip(state.iter())
.zip(start.iter())
{
let mut bytes = [0u8; 4];
bytes.copy_from_slice(four);
let mixed = u32::from_le_bytes(bytes) ^ word.wrapping_add(*first);
four.copy_from_slice(&mixed.to_le_bytes());
}
start[12] = start[12].wrapping_add(1);
}
let rest = blocks.into_remainder();
if !rest.is_empty() {
let mut state = start;
rounds(&mut state);
let mut stream = [0u8; 64];
for ((four, word), first) in stream
.chunks_exact_mut(4)
.zip(state.iter())
.zip(start.iter())
{
four.copy_from_slice(&word.wrapping_add(*first).to_le_bytes());
}
for (byte, mask) in rest.iter_mut().zip(stream.iter()) {
*byte ^= mask;
}
}
}
pub fn hchacha20(key: &[u8; 32], nonce: &[u8; 16]) -> [u8; 32] {
let k: [u32; 8] = words(key);
let n: [u32; 4] = words(nonce);
let mut state = [
SIGMA[0], SIGMA[1], SIGMA[2], SIGMA[3], k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7],
n[0], n[1], n[2], n[3],
];
rounds(&mut state);
let picked = [
state[0], state[1], state[2], state[3], state[12], state[13], state[14], state[15],
];
let mut out = [0u8; 32];
for (chunk, word) in out.chunks_exact_mut(4).zip(picked.iter()) {
chunk.copy_from_slice(&word.to_le_bytes());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypt::from_hex;
#[test]
fn the_block_function_matches_rfc_8439_2_3_2() {
let key: [u8; 32] =
from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
let nonce: [u8; 12] = from_hex("000000090000004a00000000");
let expected: [u8; 64] = from_hex(
"10f1e7e4d13b5915500fdd1fa32071c4c7d1f4c733c068030422aa9ac3d46c4e\
d2826446079faa0914c2d705d98b02a2b5129cd1de164eb9cbd083e8a2503c4e",
);
assert_eq!(block(&key, 1, &nonce), expected);
}
#[test]
fn encryption_matches_rfc_8439_2_4_2() {
let key: [u8; 32] =
from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
let nonce: [u8; 12] = from_hex("000000000000004a00000000");
let mut text = b"Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.".to_vec();
apply(&key, 1, &nonce, &mut text);
let expected: [u8; 114] = from_hex(
"6e2e359a2568f98041ba0728dd0d6981e97e7aec1d4360c20a27afccfd9fae0b\
f91b65c5524733ab8f593dabcd62b3571639d624e65152ab8f530c359f0861d8\
07ca0dbf500d6a6156a38e088a22b65e52bc514d16ccf806818ce91ab7793736\
5af90bbf74a35be6b40b8eedf2785e42874d",
);
assert_eq!(text, expected.to_vec());
}
#[test]
fn the_all_zero_block_matches_rfc_8439_a_1() {
let expected: [u8; 64] = from_hex(
"76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7\
da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586",
);
assert_eq!(block(&[0u8; 32], 0, &[0u8; 12]), expected);
}
#[test]
fn hchacha20_matches_the_xchacha_draft_2_2_1() {
let key: [u8; 32] =
from_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
let nonce: [u8; 16] = from_hex("000000090000004a0000000031415927");
let expected: [u8; 32] =
from_hex("82413b4227b27bfed30e42508a877d73a0f9e4d58a74a853c12ec41326d3ecdc");
assert_eq!(hchacha20(&key, &nonce), expected);
}
#[test]
fn the_stream_is_its_own_inverse() {
let key = [7u8; 32];
let nonce = [9u8; 12];
let original: Vec<u8> = (0..200u32).map(|value| value as u8).collect();
let mut data = original.clone();
apply(&key, 5, &nonce, &mut data);
assert_ne!(data, original);
apply(&key, 5, &nonce, &mut data);
assert_eq!(data, original);
}
}