use std::collections::HashMap;
use aes::cipher::{BlockCipherEncrypt, KeyInit};
use aes::{Aes128, Aes192, Aes256, Block};
use num_bigint::BigUint;
use num_integer::Integer;
use num_traits::{One, Zero};
pub(crate) enum Aes {
Aes128(Aes128),
Aes192(Aes192),
Aes256(Aes256),
}
impl Aes {
fn new(key: &[u8]) -> Result<Aes, String> {
match key.len() {
16 => Ok(Aes::Aes128(
Aes128::new_from_slice(key).expect("len checked"),
)),
24 => Ok(Aes::Aes192(
Aes192::new_from_slice(key).expect("len checked"),
)),
32 => Ok(Aes::Aes256(
Aes256::new_from_slice(key).expect("len checked"),
)),
n => Err(format!("key must be 16, 24, or 32 bytes, got {n}")),
}
}
fn encrypt_block(&self, block: &mut Block) {
match self {
Aes::Aes128(c) => c.encrypt_block(block),
Aes::Aes192(c) => c.encrypt_block(block),
Aes::Aes256(c) => c.encrypt_block(block),
}
}
}
pub(crate) fn prf(cipher: &Aes, data: &[u8]) -> Result<Vec<u8>, String> {
let mut chain = [0u8; 16];
for block in data.as_chunks::<16>().0 {
for (slot, byte) in chain.iter_mut().zip(block) {
*slot ^= byte;
}
let mut b = Block::from(chain);
cipher.encrypt_block(&mut b);
chain.copy_from_slice(&b);
}
Ok(chain.to_vec())
}
pub(crate) fn cipher_block(cipher: &Aes, block: &[u8; 16]) -> Result<[u8; 16], String> {
let mut b = Block::from(*block);
cipher.encrypt_block(&mut b);
let mut out = [0u8; 16];
out.copy_from_slice(&b);
Ok(out)
}
#[cfg(any(test, feature = "internal"))]
pub fn prf_with_key(key: &[u8], data: &[u8]) -> Result<Vec<u8>, String> {
prf(&Aes::new(key)?, data)
}
#[cfg(any(test, feature = "internal"))]
pub fn cipher_block_with_key(key: &[u8], block: &[u8; 16]) -> Result<[u8; 16], String> {
cipher_block(&Aes::new(key)?, block)
}
pub fn encode_len_u32(len: usize) -> Result<[u8; 4], String> {
u32::try_from(len)
.map(u32::to_be_bytes)
.map_err(|_| format!("length {len} does not fit the four-byte field of Algorithm 7 step 5"))
}
pub const D_C_THRESHOLD: usize = 64;
pub fn num_radix_reference(radix: u32, numerals: &[u16]) -> BigUint {
let mut value = BigUint::zero();
let base = BigUint::from(radix);
for &x in numerals {
value = value * &base + BigUint::from(x);
}
value
}
pub fn str_radix_reference(value: &BigUint, radix: u32, length: usize) -> Vec<u16> {
let mut out = vec![0u16; length];
let mut v = value.clone();
let base = BigUint::from(radix);
for slot in out.iter_mut().rev() {
let (q, r) = v.div_rem(&base);
*slot = u16::try_from(r.iter_u32_digits().next().unwrap_or(0))
.expect("remainder fits u16 for radix < 2**16");
v = q;
}
out
}
fn radix_power(radix: u32, exponent: usize, cache: &mut HashMap<usize, BigUint>) -> &BigUint {
cache.entry(exponent).or_insert_with(|| {
BigUint::from(radix).pow(u32::try_from(exponent).expect("exponent < 2**32 (length bound)"))
})
}
fn num_radix_split(radix: u32, numerals: &[u16], cache: &mut HashMap<usize, BigUint>) -> BigUint {
let length = numerals.len();
if length <= D_C_THRESHOLD {
return num_radix_reference(radix, numerals);
}
let high_len = length / 2;
let high = num_radix_split(radix, &numerals[..high_len], cache);
let low = num_radix_split(radix, &numerals[high_len..], cache);
high * radix_power(radix, length - high_len, cache) + low
}
fn pow2_exponent(radix: u32) -> Option<u32> {
if radix & (radix - 1) == 0 {
Some(u32::BITS - (radix - 1).leading_zeros())
} else {
None
}
}
fn pow2_chunk_size(k: u32) -> usize {
for candidate in [1usize, 2, 4] {
if (k as usize * candidate).is_multiple_of(8) {
return candidate;
}
}
8
}
fn num_radix_pow2(k: u32, numerals: &[u16]) -> BigUint {
let chunk_size = pow2_chunk_size(k);
let bytes_per_chunk = k as usize * chunk_size / 8;
let pad = (chunk_size - numerals.len() % chunk_size) % chunk_size;
let padded: Vec<u16> = std::iter::repeat_n(0u16, pad)
.chain(numerals.iter().copied())
.collect();
let mut packed = Vec::with_capacity(padded.len() / chunk_size * bytes_per_chunk);
for group in padded.chunks(chunk_size) {
let mut acc: u128 = 0;
for &numeral in group {
acc = (acc << k) | u128::from(numeral);
}
packed.extend_from_slice(&acc.to_be_bytes()[16 - bytes_per_chunk..]);
}
BigUint::from_bytes_be(&packed)
}
fn str_radix_pow2(value: &BigUint, k: u32, length: usize) -> Vec<u16> {
let chunk_size = pow2_chunk_size(k);
let bytes_per_chunk = k as usize * chunk_size / 8;
let n_chunks = length.div_ceil(chunk_size);
let total_bytes = n_chunks * bytes_per_chunk;
let mask = (BigUint::one() << (k as usize * length)) - BigUint::one();
let masked = (value & mask).to_bytes_be();
let mut data = vec![0u8; total_bytes];
if masked != [0] {
data[total_bytes - masked.len()..].copy_from_slice(&masked);
}
let digit_mask: u128 = (1u128 << k) - 1;
let mut out: Vec<u16> = Vec::with_capacity(n_chunks * chunk_size);
for group in data.chunks(bytes_per_chunk) {
let mut buf = [0u8; 16];
buf[16 - bytes_per_chunk..].copy_from_slice(group);
let chunk_int = u128::from_be_bytes(buf);
for index in (0..chunk_size).rev() {
out.push(((chunk_int >> (index * k as usize)) & digit_mask) as u16);
}
}
out.drain(..n_chunks * chunk_size - length);
out
}
pub fn num_radix(radix: u32, numerals: &[u16]) -> BigUint {
if numerals.len() <= D_C_THRESHOLD {
return num_radix_reference(radix, numerals);
}
if let Some(k) = pow2_exponent(radix) {
return num_radix_pow2(k, numerals);
}
num_radix_split(radix, numerals, &mut HashMap::new())
}
fn str_radix_split(
value: &BigUint,
radix: u32,
length: usize,
cache: &mut HashMap<usize, BigUint>,
) -> Vec<u16> {
if length <= D_C_THRESHOLD {
return str_radix_reference(value, radix, length);
}
let high_len = length / 2;
let (high, low) = value.div_rem(radix_power(radix, length - high_len, cache));
let mut out = str_radix_split(&high, radix, high_len, cache);
out.extend(str_radix_split(&low, radix, length - high_len, cache));
out
}
pub fn str_radix(value: &BigUint, radix: u32, length: usize) -> Vec<u16> {
if length <= D_C_THRESHOLD {
return str_radix_reference(value, radix, length);
}
if let Some(k) = pow2_exponent(radix) {
return str_radix_pow2(value, k, length);
}
str_radix_split(value, radix, length, &mut HashMap::new())
}
#[cfg_attr(not(any(test, feature = "internal")), allow(dead_code))]
pub struct TraceRecord {
pub i: u8,
pub u: usize,
pub v: usize,
pub b: usize,
pub d: usize,
pub p: Vec<u8>,
pub q: Vec<u8>,
pub r: Vec<u8>,
pub s: Vec<u8>,
pub y: Vec<u8>,
pub m: usize,
pub c: Vec<u8>,
pub c_block: Vec<u16>,
}
pub fn ff1(
key: &[u8],
radix: u32,
x: &[u16],
tweak: &[u8],
encrypt: bool,
) -> Result<Vec<u16>, String> {
ff1_impl(key, radix, x, tweak, encrypt, None)
}
#[cfg(any(test, feature = "internal"))]
pub fn ff1_traced(
key: &[u8],
radix: u32,
x: &[u16],
tweak: &[u8],
) -> Result<(Vec<u16>, Vec<TraceRecord>), String> {
let mut trace: Vec<TraceRecord> = Vec::with_capacity(10);
let out = ff1_impl(key, radix, x, tweak, true, Some(&mut trace))?;
Ok((out, trace))
}
fn ff1_impl(
key: &[u8],
radix: u32,
x: &[u16],
tweak: &[u8],
encrypt: bool,
mut trace: Option<&mut Vec<TraceRecord>>,
) -> Result<Vec<u16>, String> {
let n = x.len();
if n == 0 {
return Err("empty input".to_string());
}
let cipher = Aes::new(key)?;
let u = n / 2;
let v = n - u;
let mut a: Vec<u16> = x[..u].to_vec();
let mut b_side: Vec<u16> = x[u..].to_vec();
let radix_big = BigUint::from(radix);
#[allow(clippy::manual_div_ceil)]
let b: usize = (((radix_big.pow(v as u32) - BigUint::one()).bits() + 7) / 8)
.try_into()
.expect("b fits usize");
#[allow(clippy::manual_div_ceil)]
let d: usize = 4 * ((b + 3) / 4) + 4;
let t = tweak.len();
let pad = (-(t as isize) - b as isize - 1).rem_euclid(16) as usize;
let mut p_block = Vec::with_capacity(16);
p_block.extend_from_slice(&[1u8, 2, 1]);
p_block.push((radix >> 16) as u8);
p_block.push((radix >> 8) as u8);
p_block.push(radix as u8);
p_block.push(10);
p_block.push((u % 256) as u8);
p_block.extend_from_slice(&encode_len_u32(n)?);
p_block.extend_from_slice(&encode_len_u32(t)?);
let radix_u = radix_big.pow(u as u32);
let radix_v = radix_big.pow(v as u32);
let rounds: Box<dyn Iterator<Item = u8>> = if encrypt {
Box::new(0..10)
} else {
Box::new((0..10).rev())
};
for i in rounds {
let q_source: &[u16] = if encrypt { &b_side } else { &a };
let q_num = num_radix(radix, q_source);
let q_bytes = q_num.to_bytes_be();
if q_bytes.len() > b {
return Err("NUM_radix(Q source) does not fit in b bytes".to_string());
}
let mut q_block = Vec::with_capacity(t + pad + 1 + b);
q_block.extend_from_slice(tweak);
q_block.extend(std::iter::repeat_n(0u8, pad));
q_block.push(i);
q_block.extend(std::iter::repeat_n(0u8, b - q_bytes.len()));
q_block.extend_from_slice(&q_bytes);
let mut prf_input = p_block.clone();
prf_input.extend_from_slice(&q_block);
let r_block = prf(&cipher, &prf_input)?;
let mut s_block = r_block.clone();
if s_block.len() < d {
let mut j: u128 = 1;
while s_block.len() < d {
let j_be = j.to_be_bytes();
let xored: [u8; 16] = std::array::from_fn(|idx| r_block[idx] ^ j_be[idx]);
s_block.extend_from_slice(&cipher_block(&cipher, &xored)?);
j += 1;
}
}
s_block.truncate(d);
let y = BigUint::from_bytes_be(&s_block);
let m_is_u = i % 2 == 0;
let modulus = if m_is_u { &radix_u } else { &radix_v };
let c = if encrypt {
(num_radix(radix, &a) + &y) % modulus
} else {
(num_radix(radix, &b_side) + modulus - (&y % modulus)) % modulus
};
let m = if m_is_u { u } else { v };
let c_block = str_radix(&c, radix, m);
if let Some(trace) = trace.as_deref_mut() {
trace.push(TraceRecord {
i,
u,
v,
b,
d,
p: p_block.clone(),
q: q_block.clone(),
r: r_block.clone(),
s: s_block.clone(),
y: y.to_bytes_be(),
m,
c: c.to_bytes_be(),
c_block: c_block.clone(),
});
}
if encrypt {
a = b_side;
b_side = c_block;
} else {
b_side = a;
a = c_block;
}
}
let mut out = a;
out.extend_from_slice(&b_side);
Ok(out)
}