use std::array::from_fn;
use crate::shortint::server_key::LookupTable;
use crate::shortint::{Ciphertext, ServerKey};
use crate::transciphering::ciphers::aes::AesFheKey;
use rayon::prelude::*;
use super::sbox::sbox;
pub struct AesFheRoundKeys {
round_keys: [AesFheKey; 11],
flush_lut: LookupTable<Vec<u64>>,
}
impl AesFheRoundKeys {
pub fn new(sks: &ServerKey, key_bits: &AesFheKey) -> Self {
assert_eq!(
(sks.message_modulus.0, sks.carry_modulus.0),
(4, 4),
"AES transciphering is implemented for MESSAGE_2_CARRY_2 only \
(got message_modulus={}, carry_modulus={})",
sks.message_modulus.0,
sks.carry_modulus.0,
);
assert!(
sks.max_noise_level.get() >= 5,
"AES transciphering needs max_noise_level >= 5, got {}",
sks.max_noise_level.get(),
);
let flush_lut = sks.generate_lookup_table(|x: u64| x & 1);
let round_keys = key_expansion(sks, key_bits, &flush_lut);
Self {
round_keys,
flush_lut,
}
}
pub(super) fn round_keys(&self) -> &[AesFheKey; 11] {
&self.round_keys
}
pub(super) fn flush_lut(&self) -> &LookupTable<Vec<u64>> {
&self.flush_lut
}
}
const RCON: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36];
fn key_expansion(
sks: &ServerKey,
encrypted_key: &AesFheKey,
flush_lut: &LookupTable<Vec<u64>>,
) -> [AesFheKey; 11] {
const NK: usize = 4;
const NR: usize = 10;
const NB: usize = 4;
const TOTAL_WORDS: usize = NB * (NR + 1);
const WORD_BITS: usize = 32;
const BYTE_BITS: usize = 8;
let mut w = Vec::with_capacity(TOTAL_WORDS * WORD_BITS);
w.extend(encrypted_key.key.iter().cloned());
for i in NK..TOTAL_WORDS {
let mut temp: Vec<Ciphertext> = w[(i - 1) * WORD_BITS..i * WORD_BITS].to_vec();
let rot_shift = if i % NK == 0 {
temp.par_chunks_mut(BYTE_BITS).for_each(|chunk| {
sbox(sks, flush_lut, chunk);
});
for bit in 0..BYTE_BITS {
let rcon_bit = (RCON[i / NK - 1] >> bit) & 1;
if rcon_bit == 1 {
sks.unchecked_scalar_add_assign(&mut temp[bit + BYTE_BITS], 1);
}
}
BYTE_BITS
} else {
0
};
let w_minus_nk = &w[(i - NK) * WORD_BITS..(i - NK + 1) * WORD_BITS];
let mut new_word: Vec<Ciphertext> = (0..WORD_BITS)
.map(|bit| sks.unchecked_add(&w_minus_nk[bit], &temp[(bit + rot_shift) % WORD_BITS]))
.collect();
new_word.par_iter_mut().for_each(|b| {
sks.apply_lookup_table_assign(b, flush_lut);
});
w.extend(new_word);
}
debug_assert_eq!(w.len(), (NR + 1) * (NB * WORD_BITS));
let mut w_iter = w.into_iter();
from_fn(|_| AesFheKey {
key: from_fn(|_| w_iter.next().unwrap()),
})
}