pub mod backward_compatibility;
pub mod ciphers;
use rayon::prelude::*;
use tfhe_versionable::Versionize;
use crate::conformance::ParameterSetConformant;
use crate::core_crypto::commons::utils::ZipChecked;
use crate::shortint::{Ciphertext, ServerKey};
use crate::transciphering::backward_compatibility::{
StreamCipherKindVersions, StreamCiphertextVersions,
};
use crate::transciphering::ciphers::aes::AesFheState;
use crate::transciphering::ciphers::kreyvium::KreyviumFheState;
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Versionize,
)]
#[versionize(StreamCipherKindVersions)]
pub enum StreamCipherKind {
Dynamic,
Kreyvium,
Aes,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Versionize)]
#[versionize(StreamCiphertextVersions)]
pub struct StreamCiphertext {
kind: StreamCipherKind,
encryption_counter: u64,
n_bits: usize,
bytes: Vec<u8>,
}
impl StreamCiphertext {
pub fn kind(&self) -> StreamCipherKind {
self.kind
}
pub fn encryption_counter(&self) -> u64 {
self.encryption_counter
}
pub fn n_bits(&self) -> usize {
self.n_bits
}
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct StreamCiphertextConformanceParams {
pub kind: StreamCipherKind,
pub n_bits: usize,
}
impl ParameterSetConformant for StreamCiphertext {
type ParameterSet = StreamCiphertextConformanceParams;
fn is_conformant(&self, params: &Self::ParameterSet) -> bool {
self.kind == params.kind
&& self.n_bits == params.n_bits
&& self.bytes.len() == self.n_bits.div_ceil(8)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TranscipherError {
KindMismatch {
expected: StreamCipherKind,
got: StreamCipherKind,
},
CounterMismatch { expected: u64, got: u64 },
}
impl std::fmt::Display for TranscipherError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::KindMismatch { expected, got } => write!(
f,
"stream ciphertext cipher kind mismatch: expected {expected:?}, got {got:?}"
),
Self::CounterMismatch { expected, got } => {
write!(
f,
"stream ciphertext counter mismatch: session at {expected}, \
ciphertext at {got}. Call `seek({})` to align",
got - expected,
)
}
}
}
}
impl std::error::Error for TranscipherError {}
pub trait StreamCipher {
fn kind(&self) -> StreamCipherKind;
fn next_keystream_bits(&mut self, n_bits: usize) -> Vec<u8>;
fn encrypt_bits(&mut self, input: &[u8], n_bits: usize) -> StreamCiphertext {
assert_eq!(
input.len(),
n_bits.div_ceil(8),
"input must have exactly ceil(n_bits / 8) = {} bytes (got {})",
n_bits.div_ceil(8),
input.len()
);
let encryption_counter = self.current_counter();
let mask = self.next_keystream_bits(n_bits);
let bytes: Vec<u8> = input.iter().zip_checked(mask).map(|(i, m)| i ^ m).collect();
StreamCiphertext {
kind: self.kind(),
encryption_counter,
n_bits,
bytes,
}
}
fn encrypt(&mut self, input: &[u8]) -> StreamCiphertext {
self.encrypt_bits(input, 8 * input.len())
}
fn decrypt(&mut self, encrypted: &StreamCiphertext) -> Result<Vec<u8>, TranscipherError> {
if encrypted.kind != self.kind() {
return Err(TranscipherError::KindMismatch {
expected: self.kind(),
got: encrypted.kind,
});
}
if encrypted.encryption_counter != self.current_counter() {
return Err(TranscipherError::CounterMismatch {
expected: self.current_counter(),
got: encrypted.encryption_counter,
});
}
let mask = self.next_keystream_bits(encrypted.n_bits);
Ok(encrypted
.bytes
.iter()
.zip_checked(mask)
.map(|(i, m)| i ^ m)
.collect())
}
fn seek(&mut self, target_counter: u64);
fn current_counter(&self) -> u64;
}
pub trait Transcipherer {
fn kind(&self) -> StreamCipherKind;
fn next_keystream_bits(&mut self, sks: &ServerKey, n_bits: usize) -> FheKeyStream;
fn transcipher(
&mut self,
sks: &ServerKey,
input: &StreamCiphertext,
) -> Result<Vec<Ciphertext>, TranscipherError> {
if input.kind != self.kind() {
return Err(TranscipherError::KindMismatch {
expected: self.kind(),
got: input.kind,
});
}
if input.encryption_counter != self.current_counter() {
return Err(TranscipherError::CounterMismatch {
expected: self.current_counter(),
got: input.encryption_counter,
});
}
let keystream = self.next_keystream_bits(sks, input.n_bits);
Ok(apply_keystream(sks, &keystream, input))
}
fn seek(&mut self, sks: &ServerKey, target_counter: u64);
fn current_counter(&self) -> u64;
}
pub enum TranscipherSession {
Dynamic(Box<dyn Transcipherer + Send + Sync>),
Kreyvium(KreyviumFheState),
Aes(Box<AesFheState>),
}
impl Transcipherer for TranscipherSession {
fn kind(&self) -> StreamCipherKind {
match self {
Self::Dynamic(t) => t.kind(),
Self::Kreyvium(t) => t.kind(),
Self::Aes(t) => t.kind(),
}
}
fn next_keystream_bits(&mut self, sks: &ServerKey, n_bits: usize) -> FheKeyStream {
match self {
Self::Dynamic(t) => t.next_keystream_bits(sks, n_bits),
Self::Kreyvium(t) => t.next_keystream_bits(sks, n_bits),
Self::Aes(t) => t.next_keystream_bits(sks, n_bits),
}
}
fn transcipher(
&mut self,
sks: &ServerKey,
input: &StreamCiphertext,
) -> Result<Vec<Ciphertext>, TranscipherError> {
match self {
Self::Dynamic(t) => t.transcipher(sks, input),
Self::Kreyvium(t) => t.transcipher(sks, input),
Self::Aes(t) => t.transcipher(sks, input),
}
}
fn seek(&mut self, sks: &ServerKey, target_counter: u64) {
match self {
Self::Dynamic(t) => t.seek(sks, target_counter),
Self::Kreyvium(t) => t.seek(sks, target_counter),
Self::Aes(t) => t.seek(sks, target_counter),
}
}
fn current_counter(&self) -> u64 {
match self {
Self::Dynamic(t) => t.current_counter(),
Self::Kreyvium(t) => t.current_counter(),
Self::Aes(t) => t.current_counter(),
}
}
}
pub struct FheKeyStream(Vec<Ciphertext>);
impl FheKeyStream {
pub fn from_raw_parts(bits: Vec<Ciphertext>) -> Self {
Self(bits)
}
pub fn into_raw_parts(self) -> Vec<Ciphertext> {
self.0
}
pub fn iter(&self) -> std::slice::Iter<'_, Ciphertext> {
self.0.iter()
}
}
impl<'a> IntoIterator for &'a FheKeyStream {
type Item = &'a Ciphertext;
type IntoIter = std::slice::Iter<'a, Ciphertext>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
fn bit_at(bytes: &[u8], i: usize) -> u8 {
(bytes[i / 8] >> (i % 8)) & 1
}
pub fn apply_keystream(
sks: &ServerKey,
keystream: &FheKeyStream,
input: &StreamCiphertext,
) -> Vec<Ciphertext> {
assert_eq!(
keystream.0.len(),
input.n_bits,
"keystream length ({}) must equal input.n_bits ({})",
keystream.0.len(),
input.n_bits,
);
if sks.message_modulus.0 == 4 && sks.carry_modulus.0 == 4 {
apply_keystream_2_2(sks, &keystream.0, &input.bytes)
} else {
apply_keystream_naive(sks, &keystream.0, &input.bytes)
}
}
fn apply_keystream_2_2(
sks: &ServerKey,
keystream: &[Ciphertext],
input_stream: &[u8],
) -> Vec<Ciphertext> {
assert_eq!(
input_stream.len(),
keystream.len().div_ceil(8),
"input must have exactly ceil(keystream_len / 8) = {} bytes (got {})",
keystream.len().div_ceil(8),
input_stream.len()
);
let luts: [_; 4] = std::array::from_fn(|i| {
let i_lo = (i & 1) as u64;
let i_hi = ((i >> 1) & 1) as u64;
sks.generate_lookup_table_bivariate(move |k0, k1| {
((k0 & 1) ^ i_lo) | (((k1 & 1) ^ i_hi) << 1)
})
});
let pairs = keystream.par_chunks_exact(2);
let trailing = pairs.remainder();
let pairs_iter = pairs.enumerate().map(|(i, keystream)| {
let lo_idx = 2 * i;
let hi_idx = 2 * i + 1;
let i_lo = bit_at(input_stream, lo_idx);
let i_hi = bit_at(input_stream, hi_idx);
let s = (i_lo | (i_hi << 1)) as usize;
sks.unchecked_apply_lookup_table_bivariate(&keystream[0], &keystream[1], &luts[s])
});
let trailing_iter = trailing.par_iter().map(|last_keystream| {
let last_idx = keystream.len() - 1;
let s = bit_at(input_stream, last_idx) as u64;
let trailing_lut = sks.generate_lookup_table(move |t| (t & 1) ^ s);
let mut last = last_keystream.clone();
sks.apply_lookup_table_assign(&mut last, &trailing_lut);
last
});
pairs_iter.chain(trailing_iter).collect()
}
fn apply_keystream_naive(
sks: &ServerKey,
keystream: &[Ciphertext],
input_stream: &[u8],
) -> Vec<Ciphertext> {
assert_eq!(
input_stream.len(),
keystream.len().div_ceil(8),
"input must have exactly ceil(keystream_len / 8) = {} bytes (got {})",
keystream.len().div_ceil(8),
input_stream.len()
);
let m = sks.message_modulus.0.ilog2() as usize;
let luts: [_; 2] =
std::array::from_fn(|i| sks.generate_lookup_table(move |t| (t & 1) ^ (i as u64)));
let cleaned: Vec<Ciphertext> = keystream
.par_iter()
.enumerate()
.map(|(i, k)| {
let s = bit_at(input_stream, i) as usize;
let mut c = k.clone();
sks.apply_lookup_table_assign(&mut c, &luts[s]);
c
})
.collect();
cleaned
.par_chunks(m)
.map(|chunk| {
let mut out = chunk[0].clone();
for (j, c) in chunk.iter().enumerate().skip(1) {
let shifted = sks.unchecked_scalar_mul(c, 1u8 << j);
sks.unchecked_add_assign(&mut out, &shifted);
}
if chunk.len() >= 2 {
sks.message_extract_assign(&mut out);
}
out
})
.collect()
}