pub mod backward_compatibility;
pub mod ciphers;
mod keys;
pub use keys::{
CompressedTranscipheringServerKey, ExpandedTranscipheringServerKey, TranscipheringPrivateKey,
TranscipheringServerKey,
};
pub use ciphers::aes::{
AesFheKey, AesFheRoundKeys, AesFheState, AesIv, AesPlainKey, AesPlainState,
SerializableAesFheKey,
};
pub use ciphers::kreyvium::{
KreyviumFheKey, KreyviumFheState, KreyviumIV, KreyviumPlainKey, KreyviumPlainState,
SerializableKreyviumFheKey,
};
pub use ciphers::one_time_pad::{
OneTimePadFheSecretMask, OneTimePadFheState, OneTimePadPlainSecretMask,
OneTimePadPlainSecretMaskConformanceParams, OneTimePadPlainState,
};
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,
};
#[derive(
Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Versionize,
)]
#[versionize(StreamCipherKindVersions)]
pub enum StreamCipherKind {
Dynamic,
Kreyvium,
Aes,
OneTimePad,
}
#[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 {
session_kind: StreamCipherKind,
ciphertext_kind: StreamCipherKind,
},
CounterMismatch {
session_counter: u64,
ciphertext_counter: u64,
},
InsufficientKeystream,
}
impl std::fmt::Display for TranscipherError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::KindMismatch {
session_kind,
ciphertext_kind,
} => write!(
f,
"stream ciphertext cipher kind mismatch: session kind {session_kind:?}, \
ciphertext kind {ciphertext_kind:?}"
),
Self::CounterMismatch {
session_counter,
ciphertext_counter,
} => {
write!(
f,
"stream ciphertext counter mismatch: session at {session_counter}, \
ciphertext at {ciphertext_counter}. \
Call `seek({ciphertext_counter})` to align",
)
}
Self::InsufficientKeystream => InsufficientKeystream.fmt(f),
}
}
}
impl std::error::Error for TranscipherError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InsufficientKeystream;
impl std::fmt::Display for InsufficientKeystream {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Cannot generate enoug keystream from the current cipher state. \
A new fresh state should be generated"
)
}
}
impl std::error::Error for InsufficientKeystream {}
impl From<InsufficientKeystream> for TranscipherError {
fn from(_value: InsufficientKeystream) -> Self {
Self::InsufficientKeystream
}
}
pub trait StreamCipher {
fn kind(&self) -> StreamCipherKind;
fn next_keystream_bits(&mut self, n_bits: usize) -> Result<Vec<u8>, InsufficientKeystream>;
fn encrypt_bits(
&mut self,
input: &[u8],
n_bits: usize,
) -> Result<StreamCiphertext, InsufficientKeystream> {
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();
Ok(StreamCiphertext {
kind: self.kind(),
encryption_counter,
n_bits,
bytes,
})
}
fn encrypt(&mut self, input: &[u8]) -> Result<StreamCiphertext, InsufficientKeystream> {
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 {
session_kind: self.kind(),
ciphertext_kind: encrypted.kind,
});
}
if encrypted.encryption_counter != self.current_counter() {
return Err(TranscipherError::CounterMismatch {
session_counter: self.current_counter(),
ciphertext_counter: 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,
) -> Result<FheKeyStream, InsufficientKeystream>;
fn transcipher(
&mut self,
sks: &ServerKey,
input: &StreamCiphertext,
) -> Result<Vec<Ciphertext>, TranscipherError> {
check_transcipher_input(self, input)?;
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>),
OneTimePad(OneTimePadFheState),
}
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(),
Self::OneTimePad(t) => t.kind(),
}
}
fn next_keystream_bits(
&mut self,
sks: &ServerKey,
n_bits: usize,
) -> Result<FheKeyStream, InsufficientKeystream> {
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),
Self::OneTimePad(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),
Self::OneTimePad(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),
Self::OneTimePad(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(),
Self::OneTimePad(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
}
fn check_transcipher_input<T: Transcipherer + ?Sized>(
transcipherer: &T,
input: &StreamCiphertext,
) -> Result<(), TranscipherError> {
if input.kind != transcipherer.kind() {
return Err(TranscipherError::KindMismatch {
session_kind: transcipherer.kind(),
ciphertext_kind: input.kind,
});
}
if input.encryption_counter != transcipherer.current_counter() {
return Err(TranscipherError::CounterMismatch {
session_counter: transcipherer.current_counter(),
ciphertext_counter: input.encryption_counter,
});
}
Ok(())
}
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()
}