use crate::primitives::chacha20::{init_state, quarter_round, CHACHA20_KEY_SIZE};
pub const CHACHA40_KEY_SIZE: usize = 64;
pub const CHACHA40_NONCE_SIZE: usize = 12;
pub const CHACHA40_BLOCK_SIZE: usize = 64;
const CONSTANTS: [u32; 4] = [0x61707865, 0x3320646e, 0x79622d32, 0x6b206574];
fn chacha_block_custom_rounds(initial: &[u32; 16], double_rounds: usize) -> [u8; 64] {
let mut state = *initial;
for _ in 0..double_rounds {
quarter_round(&mut state, 0, 4, 8, 12);
quarter_round(&mut state, 1, 5, 9, 13);
quarter_round(&mut state, 2, 6, 10, 14);
quarter_round(&mut state, 3, 7, 11, 15);
quarter_round(&mut state, 0, 5, 10, 15);
quarter_round(&mut state, 1, 6, 11, 12);
quarter_round(&mut state, 2, 7, 8, 13);
quarter_round(&mut state, 3, 4, 9, 14);
}
for i in 0..16 {
state[i] = state[i].wrapping_add(initial[i]);
}
let mut out = [0u8; 64];
for i in 0..16 {
out[i * 4..(i + 1) * 4].copy_from_slice(&state[i].to_le_bytes());
}
out
}
pub fn chacha40_block(
key: &[u8; CHACHA40_KEY_SIZE],
counter: u32,
nonce: &[u8; CHACHA40_NONCE_SIZE],
) -> [u8; CHACHA40_BLOCK_SIZE] {
let key_a: [u8; CHACHA20_KEY_SIZE] = {
let mut k = [0u8; 32];
k.copy_from_slice(&key[..32]);
k
};
let key_b: [u8; CHACHA20_KEY_SIZE] = {
let mut k = [0u8; 32];
k.copy_from_slice(&key[32..]);
k
};
let state_a = init_state(&key_a, counter, nonce);
let state_b = init_state(&key_b, counter, nonce);
let block_a = chacha_block_custom_rounds(&state_a, 20);
let block_b = chacha_block_custom_rounds(&state_b, 20);
let mut combined = [0u8; CHACHA40_BLOCK_SIZE];
combined[..32].copy_from_slice(&block_a[..32]);
combined[32..].copy_from_slice(&block_b[..32]);
combined
}
pub fn chacha40_encrypt(
key: &[u8; CHACHA40_KEY_SIZE],
counter: u32,
nonce: &[u8; CHACHA40_NONCE_SIZE],
data: &[u8],
) -> Vec<u8> {
let mut output = Vec::with_capacity(data.len());
let mut block_counter = counter;
for chunk in data.chunks(CHACHA40_BLOCK_SIZE) {
let keystream = chacha40_block(key, block_counter, nonce);
for (i, &byte) in chunk.iter().enumerate() {
output.push(byte ^ keystream[i]);
}
block_counter = block_counter.wrapping_add(1);
}
output
}
pub fn hchacha40(
key: &[u8; CHACHA40_KEY_SIZE],
nonce: &[u8; 16],
) -> [u8; CHACHA40_KEY_SIZE] {
let key_a: [u8; CHACHA20_KEY_SIZE] = {
let mut k = [0u8; 32];
k.copy_from_slice(&key[..32]);
k
};
let key_b: [u8; CHACHA20_KEY_SIZE] = {
let mut k = [0u8; 32];
k.copy_from_slice(&key[32..]);
k
};
let subkey_a = hchacha40_internal(&key_a, nonce);
let subkey_b = hchacha40_internal(&key_b, nonce);
let mut full_subkey = [0u8; CHACHA40_KEY_SIZE];
full_subkey[..32].copy_from_slice(&subkey_a);
full_subkey[32..].copy_from_slice(&subkey_b);
full_subkey
}
fn hchacha40_internal(
key: &[u8; CHACHA20_KEY_SIZE],
nonce: &[u8; 16],
) -> [u8; CHACHA20_KEY_SIZE] {
let mut state = [0u32; 16];
state[0] = CONSTANTS[0];
state[1] = CONSTANTS[1];
state[2] = CONSTANTS[2];
state[3] = CONSTANTS[3];
for i in 0..8 {
state[4 + i] =
u32::from_le_bytes([key[i * 4], key[i * 4 + 1], key[i * 4 + 2], key[i * 4 + 3]]);
}
for i in 0..4 {
state[12 + i] = u32::from_le_bytes([
nonce[i * 4],
nonce[i * 4 + 1],
nonce[i * 4 + 2],
nonce[i * 4 + 3],
]);
}
for _ in 0..20 {
quarter_round(&mut state, 0, 4, 8, 12);
quarter_round(&mut state, 1, 5, 9, 13);
quarter_round(&mut state, 2, 6, 10, 14);
quarter_round(&mut state, 3, 7, 11, 15);
quarter_round(&mut state, 0, 5, 10, 15);
quarter_round(&mut state, 1, 6, 11, 12);
quarter_round(&mut state, 2, 7, 8, 13);
quarter_round(&mut state, 3, 4, 9, 14);
}
let mut subkey = [0u8; CHACHA20_KEY_SIZE];
subkey[0..4].copy_from_slice(&state[0].to_le_bytes());
subkey[4..8].copy_from_slice(&state[1].to_le_bytes());
subkey[8..12].copy_from_slice(&state[2].to_le_bytes());
subkey[12..16].copy_from_slice(&state[3].to_le_bytes());
subkey[16..20].copy_from_slice(&state[12].to_le_bytes());
subkey[20..24].copy_from_slice(&state[13].to_le_bytes());
subkey[24..28].copy_from_slice(&state[14].to_le_bytes());
subkey[28..32].copy_from_slice(&state[15].to_le_bytes());
subkey
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chacha40_differs_from_chacha20() {
let key_20 = [42u8; 32];
let nonce = [0u8; 12];
let standard_block = crate::primitives::chacha20::chacha20_block(&key_20, 0, &nonce);
let mut chacha40_key = [0u8; 64];
chacha40_key[..32].copy_from_slice(&key_20);
let chacha40_block_out = chacha40_block(&chacha40_key, 0, &nonce);
assert_ne!(chacha40_block_out[..32], standard_block[..32]);
}
#[test]
fn test_chacha40_roundtrip() {
let key = [42u8; 64];
let nonce = [0u8; 12];
let plaintext = b"ChaCha40 post-quantum hardened encryption roundtrip test.";
let ciphertext = chacha40_encrypt(&key, 0, &nonce, plaintext);
assert_ne!(ciphertext, plaintext);
let decrypted = chacha40_encrypt(&key, 0, &nonce, &ciphertext);
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_chacha40_multi_block() {
let key = [1u8; 64];
let nonce = [2u8; 12];
let plaintext: Vec<u8> = (0..512).map(|i| i as u8).collect();
let ciphertext = chacha40_encrypt(&key, 0, &nonce, &plaintext);
assert_eq!(ciphertext.len(), plaintext.len());
let decrypted = chacha40_encrypt(&key, 0, &nonce, &ciphertext);
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_chacha40_deterministic() {
let key = [99u8; 64];
let nonce = [7u8; 12];
let block1 = chacha40_block(&key, 1, &nonce);
let block2 = chacha40_block(&key, 1, &nonce);
assert_eq!(block1, block2);
}
#[test]
fn test_chacha40_counter_distinction() {
let key = [42u8; 64];
let nonce = [0u8; 12];
let block0 = chacha40_block(&key, 0, &nonce);
let block1 = chacha40_block(&key, 1, &nonce);
assert_ne!(block0, block1);
}
#[test]
fn test_chacha40_nonce_distinction() {
let key = [42u8; 64];
let nonce_a = [0u8; 12];
let mut nonce_b = [0u8; 12];
nonce_b[0] = 1;
let block_a = chacha40_block(&key, 0, &nonce_a);
let block_b = chacha40_block(&key, 0, &nonce_b);
assert_ne!(block_a, block_b);
}
#[test]
fn test_chacha40_key_distinction() {
let mut key_a = [0u8; 64];
let mut key_b = [0u8; 64];
key_b[63] = 1;
let nonce = [0u8; 12];
let block_a = chacha40_block(&key_a, 0, &nonce);
let block_b = chacha40_block(&key_b, 0, &nonce);
assert_ne!(block_a, block_b);
}
#[test]
fn test_chacha40_both_halves_matter() {
let nonce = [0u8; 12];
let mut key_original = [0u8; 64];
key_original[0] = 1;
key_original[32] = 2;
let block_original = chacha40_block(&key_original, 0, &nonce);
let mut key_b_zero = [0u8; 64];
key_b_zero[0] = 1;
let block_b_zero = chacha40_block(&key_b_zero, 0, &nonce);
assert_ne!(block_original, block_b_zero);
}
#[test]
fn test_hchacha40_deterministic() {
let key = [42u8; 64];
let nonce = [7u8; 16];
let subkey1 = hchacha40(&key, &nonce);
let subkey2 = hchacha40(&key, &nonce);
assert_eq!(subkey1, subkey2);
}
#[test]
fn test_hchacha40_key_size() {
let key = [42u8; 64];
let nonce = [7u8; 16];
let subkey = hchacha40(&key, &nonce);
assert_eq!(subkey.len(), 64);
}
#[test]
fn test_hchacha40_key_distinction() {
let mut key_a = [0u8; 64];
let mut key_b = [0u8; 64];
key_b[63] = 1;
let nonce = [0u8; 16];
assert_ne!(hchacha40(&key_a, &nonce), hchacha40(&key_b, &nonce));
}
#[test]
fn test_chacha40_encrypt_matches_block_xor() {
let key = [77u8; 64];
let nonce = [3u8; 12];
let msg: Vec<u8> = (0..64).map(|i| i as u8).collect();
let ct = chacha40_encrypt(&key, 0, &nonce, &msg);
let block = chacha40_block(&key, 0, &nonce);
let expected: Vec<u8> = msg.iter().zip(block.iter()).map(|(m, k)| m ^ k).collect();
assert_eq!(ct, expected);
}
}