use std::convert::TryInto;
const SIGMA: [u32; 4] = [
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, ];
#[inline(always)]
fn rotl(a: u32, b: u32) -> u32 {
a.rotate_left(b)
}
#[inline(always)]
fn quarter_round(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
state[a] = state[a].wrapping_add(state[b]); state[d] ^= state[a]; state[d] = rotl(state[d], 16);
state[c] = state[c].wrapping_add(state[d]); state[b] ^= state[c]; state[b] = rotl(state[b], 12);
state[a] = state[a].wrapping_add(state[b]); state[d] ^= state[a]; state[d] = rotl(state[d], 8);
state[c] = state[c].wrapping_add(state[d]); state[b] ^= state[c]; state[b] = rotl(state[b], 7);
}
pub fn chacha8_monero(key: &[u8; 32], nonce: &[u8; 8], input: &[u8]) -> Vec<u8> {
let mut output = vec![0u8; input.len()];
let mut block = [0u8; 64];
let mut counter: u64 = 0;
for (chunk_idx, chunk) in input.chunks(64).enumerate() {
let mut state = [0u32; 16];
state[0..4].copy_from_slice(&SIGMA);
for i in 0..8 {
state[4 + i] = u32::from_le_bytes(key[i * 4..(i + 1) * 4].try_into().unwrap());
}
state[12] = (counter & 0xffffffff) as u32;
state[13] = (counter >> 32) as u32;
state[14] = u32::from_le_bytes(nonce[0..4].try_into().unwrap());
state[15] = u32::from_le_bytes(nonce[4..8].try_into().unwrap());
let initial = state;
for _ in 0..4 {
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 {
let word = state[i].wrapping_add(initial[i]).to_le_bytes();
block[i * 4..(i + 1) * 4].copy_from_slice(&word);
}
let block_len = chunk.len();
for i in 0..block_len {
output[chunk_idx * 64 + i] = chunk[i] ^ block[i];
}
counter += 1;
}
output
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_chacha8_monero_basic() {
let key = [0u8; 32];
let nonce = [0u8; 8];
let plaintext = [0u8; 64];
let ciphertext = chacha8_monero(&key, &nonce, &plaintext);
assert_eq!(ciphertext.len(), 64);
}
}