use tc_zeroize::Zeroize;
use crate::noekeon::{BLOCK_BYTES, KEY_BYTES};
const ROUNDS: usize = 16;
const ROUND_CONSTANTS: [u8; ROUNDS + 1] = [
0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a,
0xd4,
];
pub(crate) fn prepare_key(for_encryption: bool, key: &[u8; KEY_BYTES], working_key: &mut [u32; 4]) {
read_words(key, working_key);
if !for_encryption {
theta(working_key, &[0; 4]);
}
}
pub(crate) fn encrypt_block(
working_key: &[u32; 4],
input: &[u8; BLOCK_BYTES],
output: &mut [u8; BLOCK_BYTES],
) {
let mut state = [0; 4];
read_words(input, &mut state);
for &constant in &ROUND_CONSTANTS[..ROUNDS] {
state[0] ^= u32::from(constant);
theta(&mut state, working_key);
pi1(&mut state);
gamma(&mut state);
pi2(&mut state);
}
state[0] ^= u32::from(ROUND_CONSTANTS[ROUNDS]);
theta(&mut state, working_key);
write_words(&state, output);
state.zeroize();
}
pub(crate) fn decrypt_block(
working_key: &[u32; 4],
input: &[u8; BLOCK_BYTES],
output: &mut [u8; BLOCK_BYTES],
) {
let mut state = [0; 4];
read_words(input, &mut state);
for &constant in ROUND_CONSTANTS[1..].iter().rev() {
theta(&mut state, working_key);
state[0] ^= u32::from(constant);
pi1(&mut state);
gamma(&mut state);
pi2(&mut state);
}
theta(&mut state, working_key);
state[0] ^= u32::from(ROUND_CONSTANTS[0]);
write_words(&state, output);
state.zeroize();
}
fn read_words(input: &[u8; BLOCK_BYTES], words: &mut [u32; 4]) {
for (word, chunk) in words.iter_mut().zip(input.chunks_exact(4)) {
*word = u32::from_be_bytes(chunk.try_into().unwrap());
}
}
fn write_words(words: &[u32; 4], output: &mut [u8; BLOCK_BYTES]) {
for (word, chunk) in words.iter().zip(output.chunks_exact_mut(4)) {
chunk.copy_from_slice(&word.to_be_bytes());
}
}
fn theta(state: &mut [u32; 4], working_key: &[u32; 4]) {
let mut t02 = state[0] ^ state[2];
t02 ^= t02.rotate_left(8) ^ t02.rotate_left(24);
for (word, key_word) in state.iter_mut().zip(working_key) {
*word ^= key_word;
}
let mut t13 = state[1] ^ state[3];
t13 ^= t13.rotate_left(8) ^ t13.rotate_left(24);
state[0] ^= t13;
state[1] ^= t02;
state[2] ^= t13;
state[3] ^= t02;
t02.zeroize();
t13.zeroize();
}
fn pi1(state: &mut [u32; 4]) {
state[1] = state[1].rotate_left(1);
state[2] = state[2].rotate_left(5);
state[3] = state[3].rotate_left(2);
}
fn pi2(state: &mut [u32; 4]) {
state[1] = state[1].rotate_right(1);
state[2] = state[2].rotate_right(5);
state[3] = state[3].rotate_right(2);
}
fn gamma(state: &mut [u32; 4]) {
let mut original3 = state[3];
state[1] ^= state[3] | state[2];
state[3] = state[0] ^ (state[2] & !state[1]);
state[2] = original3 ^ !state[1] ^ state[2] ^ state[3];
state[1] ^= state[3] | state[2];
state[0] = original3 ^ (state[2] & state[1]);
original3.zeroize();
}
#[cfg(test)]
mod tests {
use super::*;
const STATE: [u32; 4] = [0x0123_4567, 0x89ab_cdef, 0xfedc_ba98, 0x7654_3210];
#[test]
fn gamma_is_an_involution() {
let mut state = STATE;
gamma(&mut state);
assert_ne!(state, STATE);
gamma(&mut state);
assert_eq!(state, STATE);
}
#[test]
fn the_second_bit_shuffle_undoes_the_first() {
let mut state = STATE;
pi1(&mut state);
assert_ne!(state, STATE);
pi2(&mut state);
assert_eq!(state, STATE);
}
#[test]
fn theta_with_a_zero_key_is_an_involution() {
let mut state = STATE;
theta(&mut state, &[0; 4]);
assert_ne!(state, STATE);
theta(&mut state, &[0; 4]);
assert_eq!(state, STATE);
}
}