use core::fmt;
use tc_block_cipher::{BlockCipher, BlockCipherInit, BlockError, CipherDirection, InitError};
use tc_zeroize::Zeroize;
use threefish::{Threefish256, Threefish512, Threefish1024};
use crate::cipher;
use crate::{
THREEFISH_256_ALGO_NAME, THREEFISH_512_ALGO_NAME, THREEFISH_1024_ALGO_NAME, TWEAK_BYTES,
ThreefishParams,
};
#[expect(
clippy::large_enum_variant,
reason = "a const generic cannot name the backend type; the engine docs disclose the size"
)]
enum Cipher {
Threefish256(Threefish256),
Threefish512(Threefish512),
Threefish1024(Threefish1024),
}
pub struct ThreefishRustCryptoEngine<const WORDS: usize> {
cipher: Option<Cipher>,
direction: CipherDirection,
}
impl<const WORDS: usize> ThreefishRustCryptoEngine<WORDS> {
const VALID_WORD_COUNT: () = assert!(
cipher::valid_word_count(WORDS),
"Threefish WORDS must be 4, 8, or 16"
);
pub const fn new() -> Self {
let () = Self::VALID_WORD_COUNT;
Self {
cipher: None,
direction: CipherDirection::Encrypt,
}
}
}
impl<const WORDS: usize> Default for ThreefishRustCryptoEngine<WORDS> {
fn default() -> Self {
Self::new()
}
}
impl<const WORDS: usize> fmt::Display for ThreefishRustCryptoEngine<WORDS> {
fn fmt(&self, output: &mut fmt::Formatter<'_>) -> fmt::Result {
match WORDS {
4 => output.write_str(THREEFISH_256_ALGO_NAME),
8 => output.write_str(THREEFISH_512_ALGO_NAME),
16 => output.write_str(THREEFISH_1024_ALGO_NAME),
_ => unreachable!("ThreefishRustCryptoEngine validates WORDS"),
}
}
}
impl<const WORDS: usize> BlockCipher for ThreefishRustCryptoEngine<WORDS> {
type Error = BlockError;
fn block_size(&self) -> usize {
WORDS * 8
}
fn process_block(&mut self, input: &[u8], output: &mut [u8]) -> Result<usize, BlockError> {
let cipher = self.cipher.as_ref().ok_or(BlockError::NotInitialised)?;
let block_bytes = self.block_size();
if input.len() < block_bytes || output.len() < block_bytes {
return Err(BlockError::BufferTooShort);
}
let mut words = [0u64; WORDS];
for (word, bytes) in words.iter_mut().zip(input[..block_bytes].chunks_exact(8)) {
*word = u64::from_le_bytes(bytes.try_into().unwrap());
}
let block = words.as_mut_slice();
match (cipher, self.direction) {
(Cipher::Threefish256(c), CipherDirection::Encrypt) => {
c.encrypt_block_u64(block.try_into().unwrap())
}
(Cipher::Threefish256(c), CipherDirection::Decrypt) => {
c.decrypt_block_u64(block.try_into().unwrap())
}
(Cipher::Threefish512(c), CipherDirection::Encrypt) => {
c.encrypt_block_u64(block.try_into().unwrap())
}
(Cipher::Threefish512(c), CipherDirection::Decrypt) => {
c.decrypt_block_u64(block.try_into().unwrap())
}
(Cipher::Threefish1024(c), CipherDirection::Encrypt) => {
c.encrypt_block_u64(block.try_into().unwrap())
}
(Cipher::Threefish1024(c), CipherDirection::Decrypt) => {
c.decrypt_block_u64(block.try_into().unwrap())
}
}
for (word, bytes) in words.iter().zip(output[..block_bytes].chunks_exact_mut(8)) {
bytes.copy_from_slice(&word.to_le_bytes());
}
Ok(block_bytes)
}
}
impl<P: ThreefishParams + ?Sized, const WORDS: usize> BlockCipherInit<P>
for ThreefishRustCryptoEngine<WORDS>
{
type Error = InitError;
fn init(&mut self, direction: CipherDirection, params: &P) -> Result<(), InitError> {
let key = params.key();
if key.len() != WORDS * 8 {
return Err(InitError::InvalidKeyLength(key.len()));
}
let mut tweak = [0u8; TWEAK_BYTES];
if let Some(bytes) = params.tweak() {
if bytes.len() != TWEAK_BYTES {
return Err(InitError::InvalidTweakLength(bytes.len()));
}
tweak.copy_from_slice(bytes);
}
self.cipher = Some(match WORDS {
4 => Cipher::Threefish256(Threefish256::new_with_tweak(
key.try_into().unwrap(),
&tweak,
)),
8 => Cipher::Threefish512(Threefish512::new_with_tweak(
key.try_into().unwrap(),
&tweak,
)),
16 => Cipher::Threefish1024(Threefish1024::new_with_tweak(
key.try_into().unwrap(),
&tweak,
)),
_ => unreachable!("ThreefishRustCryptoEngine validates WORDS"),
});
tweak.zeroize();
self.direction = direction;
Ok(())
}
}