use super::gf2m_wide::{Gf2m128, Gf2m256, Gf2m512};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum XtsError {
InvalidLength,
}
fn xor_block(buf: &mut [u8], other: &[u8]) {
for (b, o) in buf.iter_mut().zip(other) {
*b ^= *o;
}
}
macro_rules! kalyna_xts_variant {
($name:ident, $expanded:ident, $key_bytes:literal, $block_bytes:literal, $gf:ty) => {
#[doc = concat!(
"XTS mode over [`super::kalyna::", stringify!($expanded), "`] - see the module doc ",
"comment for the citation, the ciphertext-stealing derivation, and the misuse note."
)]
pub struct $name {
key: super::kalyna::$expanded,
}
impl $name {
#[must_use]
pub fn new(key: &[u8; $key_bytes]) -> Self {
Self {
key: super::kalyna::$expanded::new(key),
}
}
pub fn encrypt_in_place(
&self,
iv: &[u8; $block_bytes],
buffer: &mut [u8],
) -> Result<(), XtsError> {
let n = buffer.len();
if n < $block_bytes {
return Err(XtsError::InvalidLength);
}
let mut gamma = <$gf>::from_le_bytes(&self.key.encrypt_block(iv));
let k = n / $block_bytes;
let r = n % $block_bytes;
if r == 0 {
let mut off = 0usize;
while off < n {
gamma = gamma.double();
let block = &mut buffer[off..off + $block_bytes];
xor_block(block, &gamma.to_le_bytes());
let mut tmp = [0u8; $block_bytes];
tmp.copy_from_slice(block);
let enc = self.key.encrypt_block(&tmp);
block.copy_from_slice(&enc);
xor_block(block, &gamma.to_le_bytes());
off += $block_bytes;
}
} else {
for i in 0..k {
gamma = gamma.double();
let off = i * $block_bytes;
let block = &mut buffer[off..off + $block_bytes];
xor_block(block, &gamma.to_le_bytes());
let mut tmp = [0u8; $block_bytes];
tmp.copy_from_slice(block);
let enc = self.key.encrypt_block(&tmp);
block.copy_from_slice(&enc);
xor_block(block, &gamma.to_le_bytes());
}
let last_off = (k - 1) * $block_bytes;
let tail_off = k * $block_bytes;
let mut scratch = [0u8; $block_bytes];
scratch.copy_from_slice(&buffer[last_off..last_off + $block_bytes]);
let mut combined = [0u8; $block_bytes];
combined[..r].copy_from_slice(&buffer[tail_off..tail_off + r]);
combined[r..].copy_from_slice(&scratch[r..]);
gamma = gamma.double();
xor_block(&mut combined, &gamma.to_le_bytes());
let enc = self.key.encrypt_block(&combined);
combined = enc;
xor_block(&mut combined, &gamma.to_le_bytes());
buffer[last_off..last_off + $block_bytes].copy_from_slice(&combined);
buffer[tail_off..tail_off + r].copy_from_slice(&scratch[..r]);
}
Ok(())
}
pub fn decrypt_in_place(
&self,
iv: &[u8; $block_bytes],
buffer: &mut [u8],
) -> Result<(), XtsError> {
let n = buffer.len();
if n < $block_bytes {
return Err(XtsError::InvalidLength);
}
let mut gamma = <$gf>::from_le_bytes(&self.key.encrypt_block(iv));
let k = n / $block_bytes;
let r = n % $block_bytes;
if r == 0 {
let mut off = 0usize;
while off < n {
gamma = gamma.double();
let block = &mut buffer[off..off + $block_bytes];
xor_block(block, &gamma.to_le_bytes());
let mut tmp = [0u8; $block_bytes];
tmp.copy_from_slice(block);
let dec = self.key.decrypt_block(&tmp);
block.copy_from_slice(&dec);
xor_block(block, &gamma.to_le_bytes());
off += $block_bytes;
}
} else {
for i in 0..(k - 1) {
gamma = gamma.double();
let off = i * $block_bytes;
let block = &mut buffer[off..off + $block_bytes];
xor_block(block, &gamma.to_le_bytes());
let mut tmp = [0u8; $block_bytes];
tmp.copy_from_slice(block);
let dec = self.key.decrypt_block(&tmp);
block.copy_from_slice(&dec);
xor_block(block, &gamma.to_le_bytes());
}
gamma = gamma.double(); let gamma_k = gamma;
let gamma_k_plus_1 = gamma.double();
let last_off = (k - 1) * $block_bytes;
let tail_off = k * $block_bytes;
let mut combined = [0u8; $block_bytes];
combined.copy_from_slice(&buffer[last_off..last_off + $block_bytes]);
xor_block(&mut combined, &gamma_k_plus_1.to_le_bytes());
combined = self.key.decrypt_block(&combined);
xor_block(&mut combined, &gamma_k_plus_1.to_le_bytes());
let mut reconstructed = [0u8; $block_bytes];
reconstructed[..r].copy_from_slice(&buffer[tail_off..tail_off + r]);
reconstructed[r..].copy_from_slice(&combined[r..]);
xor_block(&mut reconstructed, &gamma_k.to_le_bytes());
reconstructed = self.key.decrypt_block(&reconstructed);
xor_block(&mut reconstructed, &gamma_k.to_le_bytes());
buffer[last_off..last_off + $block_bytes].copy_from_slice(&reconstructed);
buffer[tail_off..tail_off + r].copy_from_slice(&combined[..r]);
}
Ok(())
}
}
};
}
kalyna_xts_variant!(Kalyna128_128Xts, Kalyna128_128ExpandedKey, 16, 16, Gf2m128);
kalyna_xts_variant!(Kalyna128_256Xts, Kalyna128_256ExpandedKey, 32, 16, Gf2m128);
kalyna_xts_variant!(Kalyna256_256Xts, Kalyna256_256ExpandedKey, 32, 32, Gf2m256);
kalyna_xts_variant!(Kalyna256_512Xts, Kalyna256_512ExpandedKey, 64, 32, Gf2m256);
kalyna_xts_variant!(Kalyna512_512Xts, Kalyna512_512ExpandedKey, 64, 64, Gf2m512);