Skip to content

Commit b210c8a

Browse files
nsarlin-zamatmontaiguIceTDrinker
committed
feat(transciphering): add OPRF generation
Co-authored-by: Thomas Montaigu <thomas.montaigu@zama.ai> Co-authored-by: Arthur Meyre <arthur.meyre@zama.ai>
1 parent 6e9ca6e commit b210c8a

15 files changed

Lines changed: 542 additions & 27 deletions

File tree

tfhe/src/shortint/backward_compatibility/parameters/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod list_compression;
44
pub mod modulus_switch_noise_reduction;
55
pub mod noise_squashing;
66
pub mod re_randomization;
7+
pub mod transciphering;
78

89
use crate::core_crypto::commons::parameters::{
910
DecompositionBaseLog, DecompositionLevelCount, DynamicDistribution, GlweDimension,
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
use super::parameters::transciphering::TranscipheringParameters;
2+
use tfhe_versionable::VersionsDispatch;
3+
4+
#[derive(VersionsDispatch)]
5+
pub enum TranscipheringParametersVersions {
6+
V0(TranscipheringParameters),
7+
}

tfhe/src/shortint/oprf.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,32 @@ impl<C: Container<Element = c64> + Sync> GenericOprfServerKey<C> {
678678
prf_re_randomization_context,
679679
)
680680
}
681+
682+
/// Uniformly generates a sequence of encrypted random bits from a single seed in one call.
683+
///
684+
/// Returns `num_blocks` blocks, each ciphertext encrypting a value in `[0, 2[`, i.e. a single
685+
/// random bit whatever the message modulus of `target_sks`.
686+
///
687+
/// # Panics
688+
///
689+
/// * Panics if `self` is not compatible with `target_sks`
690+
pub fn generate_random_boolean_sequence(
691+
&self,
692+
seed: impl OprfSeed,
693+
num_blocks: u64,
694+
target_sks: &ServerKey,
695+
) -> Vec<Ciphertext> {
696+
let max_random_bits_per_block = 1;
697+
self.inner
698+
.generate_pseudo_random_bits_chunks(
699+
seed,
700+
&[num_blocks],
701+
max_random_bits_per_block, // Each ciphertext is a boolean
702+
target_sks,
703+
)
704+
.pop()
705+
.unwrap()
706+
}
681707
}
682708

683709
// Owned-only methods.

tfhe/src/shortint/parameters/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub mod parameters_wopbs_only;
5151
pub mod re_randomization;
5252
#[cfg(test)]
5353
pub mod test_params;
54+
pub mod transciphering;
5455
pub mod v0_10;
5556
pub mod v0_11;
5657
pub mod v1_0;
@@ -73,6 +74,7 @@ use super::server_key::PBSConformanceParams;
7374
pub use super::PBSOrder;
7475
use crate::shortint::ciphertext::MaxDegree;
7576
pub use crate::shortint::parameters::list_compression::CompressionParameters;
77+
pub use crate::shortint::parameters::transciphering::TranscipheringParameters;
7678
pub use classic::ClassicPBSParameters;
7779
pub use compact_public_key_only::{
7880
CastingFunctionsOwned, CastingFunctionsView, CompactCiphertextListExpansionKind,
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use crate::named::Named;
2+
use crate::shortint::backward_compatibility::parameters::transciphering::TranscipheringParametersVersions;
3+
use serde::{Deserialize, Serialize};
4+
use tfhe_versionable::Versionize;
5+
6+
/// Parameters of the key material used by the transciphering subsystem.
7+
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Versionize)]
8+
#[versionize(TranscipheringParametersVersions)]
9+
#[non_exhaustive]
10+
pub enum TranscipheringParameters {
11+
/// Generate the transciphering key with the parameters of the compute key.
12+
SameAsCompute,
13+
}
14+
15+
impl Named for TranscipheringParameters {
16+
const NAME: &'static str = "shortint::TranscipheringParameters";
17+
}

tfhe/src/transciphering/backward_compatibility/mod.rs

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use tfhe_versionable::VersionsDispatch;
22

3-
use crate::transciphering::{StreamCipherKind, StreamCiphertext};
3+
use crate::transciphering::{
4+
AesIv, CompressedTranscipheringServerKey, KreyviumIV, OneTimePadFheSecretMask,
5+
OneTimePadPlainSecretMask, SerializableAesFheKey, SerializableKreyviumFheKey, StreamCipherKind,
6+
StreamCiphertext, TranscipheringPrivateKey, TranscipheringServerKey,
7+
};
48

59
#[derive(VersionsDispatch)]
610
pub enum StreamCipherKindVersions {
@@ -11,3 +15,48 @@ pub enum StreamCipherKindVersions {
1115
pub enum StreamCiphertextVersions {
1216
V0(StreamCiphertext),
1317
}
18+
19+
#[derive(VersionsDispatch)]
20+
pub enum SerializableKreyviumFheKeyVersions {
21+
V0(SerializableKreyviumFheKey),
22+
}
23+
24+
#[derive(VersionsDispatch)]
25+
pub enum KreyviumIVVersions {
26+
V0(KreyviumIV),
27+
}
28+
29+
#[derive(VersionsDispatch)]
30+
pub enum SerializableAesFheKeyVersions {
31+
V0(SerializableAesFheKey),
32+
}
33+
34+
#[derive(VersionsDispatch)]
35+
pub enum AesIvVersions {
36+
V0(AesIv),
37+
}
38+
39+
#[derive(VersionsDispatch)]
40+
pub enum OneTimePadFheSecretMaskVersions {
41+
V0(OneTimePadFheSecretMask),
42+
}
43+
44+
#[derive(VersionsDispatch)]
45+
pub enum OneTimePadPlainSecretMaskVersions {
46+
V0(OneTimePadPlainSecretMask),
47+
}
48+
49+
#[derive(VersionsDispatch)]
50+
pub enum TranscipheringPrivateKeyVersions {
51+
V0(TranscipheringPrivateKey),
52+
}
53+
54+
#[derive(VersionsDispatch)]
55+
pub enum TranscipheringServerKeyVersions {
56+
V0(TranscipheringServerKey),
57+
}
58+
59+
#[derive(VersionsDispatch)]
60+
pub enum CompressedTranscipheringServerKeyVersions {
61+
V0(CompressedTranscipheringServerKey),
62+
}

tfhe/src/transciphering/ciphers/aes/mod.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ pub use fhe::AesFheState;
1212
pub use key::AesFheRoundKeys;
1313
pub use plain::AesPlainState;
1414

15-
use crate::shortint::{Ciphertext, ClientKey};
15+
use crate::named::Named;
16+
use crate::shortint::oprf::OprfSeed;
17+
use crate::shortint::{Ciphertext, ClientKey, ServerKey};
18+
use crate::transciphering::backward_compatibility::{AesIvVersions, SerializableAesFheKeyVersions};
1619
use crate::transciphering::ciphers::*;
20+
use crate::transciphering::TranscipheringServerKey;
21+
use serde::{Deserialize, Serialize};
22+
use tfhe_versionable::Versionize;
1723

1824
/// Big endian byte order.
1925
///
@@ -73,16 +79,78 @@ impl From<[bool; 128]> for AesPlainKey {
7379
}
7480
}
7581

82+
#[derive(Clone, Serialize, Deserialize, Versionize)]
83+
#[serde(into = "SerializableAesFheKey", try_from = "SerializableAesFheKey")]
84+
#[versionize(into = "SerializableAesFheKey", try_from = "SerializableAesFheKey")]
7685
pub struct AesFheKey {
7786
key: [Ciphertext; 128],
7887
}
7988

89+
impl AesFheKey {
90+
pub fn ciphertexts(&self) -> &[Ciphertext; 128] {
91+
&self.key
92+
}
93+
94+
pub fn new_random(
95+
seed: impl OprfSeed,
96+
transciphering_key: &TranscipheringServerKey,
97+
sks: &ServerKey,
98+
) -> Self {
99+
let encrypted_bits = transciphering_key
100+
.oprf_key()
101+
.generate_random_boolean_sequence(seed, 128, sks);
102+
103+
let key: [Ciphertext; 128] = encrypted_bits.try_into().expect("the vec has 128 elements");
104+
105+
Self { key }
106+
}
107+
108+
/// Decrypt the key bits
109+
pub fn decrypt(&self, client_key: &ClientKey) -> AesPlainKey {
110+
let mut decrypted_bits = [false; 128];
111+
for (ct, out) in self.key.iter().zip(decrypted_bits.iter_mut()) {
112+
*out = client_key.decrypt(ct) != 0;
113+
}
114+
AesPlainKey::from(decrypted_bits)
115+
}
116+
}
117+
118+
/// Serialization form of [`AesFheKey`]. The 128 key ciphertexts are stored in a
119+
/// `Vec` because serde/versionize don't support arrays longer than 32; the
120+
/// fixed-size array is restored on deserialization.
121+
#[derive(Clone, Serialize, Deserialize, Versionize)]
122+
#[versionize(SerializableAesFheKeyVersions)]
123+
pub struct SerializableAesFheKey {
124+
key: Vec<Ciphertext>,
125+
}
126+
127+
impl From<AesFheKey> for SerializableAesFheKey {
128+
fn from(value: AesFheKey) -> Self {
129+
Self {
130+
key: value.key.into(),
131+
}
132+
}
133+
}
134+
135+
impl TryFrom<SerializableAesFheKey> for AesFheKey {
136+
type Error = crate::Error;
137+
138+
fn try_from(value: SerializableAesFheKey) -> Result<Self, Self::Error> {
139+
let len = value.key.len();
140+
let key: [Ciphertext; 128] = value.key.try_into().map_err(|_| {
141+
crate::error!("an AES key must hold exactly 128 ciphertexts, got {len}")
142+
})?;
143+
Ok(Self { key })
144+
}
145+
}
146+
80147
/// AES-128 IV / initial CTR counter, as a plain 128-bit integer.
81148
///
82149
/// The counter is incremented directly (`iv + block_index`), the big-endian
83150
/// (NIST) convention only applies when bytes are involved, i.e. at the
84151
/// `[u8; 16]` / `[bool; 128]` construction boundaries.
85-
#[derive(Clone, Copy)]
152+
#[derive(Clone, Copy, Serialize, Deserialize, Versionize)]
153+
#[versionize(AesIvVersions)]
86154
pub struct AesIv(u128);
87155

88156
impl AesIv {
@@ -110,3 +178,7 @@ impl From<[bool; 128]> for AesIv {
110178
bits.into()
111179
}
112180
}
181+
182+
impl Named for AesIv {
183+
const NAME: &'static str = "transciphering::AesIv";
184+
}

tfhe/src/transciphering/ciphers/aes/test.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,26 @@ fn aes_plain_key_conversions_are_homogeneous() {
128128
}
129129
}
130130

131+
/// `AesFheKey::decrypt` must be the exact inverse of `AesPlainKey::encrypt`,
132+
/// including the byte and bit order they agree on
133+
#[test]
134+
fn aes_fhe_key_encrypt_decrypt_round_trip() {
135+
let (cks, _sks) = gen_keys(PARAM);
136+
137+
// An asymmetric value plus both extremes, so a reversed byte or bit order
138+
// does not round-trip by accident.
139+
for v in [KEY, 0u128, u128::MAX, 0x0123456789abcdeffedcba9876543210] {
140+
let plain = AesPlainKey::from(v);
141+
let recovered = plain.encrypt(&cks).decrypt(&cks);
142+
143+
assert_eq!(
144+
recovered.to_csprng_key_u128(),
145+
plain.to_csprng_key_u128(),
146+
"AES key did not survive the encrypt/decrypt round trip for 0x{v:032x}"
147+
);
148+
}
149+
}
150+
131151
/// Anchor the plain side to the NIST SP 800-38A AES-128 vector. The other
132152
/// tests use `AesPlainStream` as oracle.
133153
#[test]

tfhe/src/transciphering/ciphers/kreyvium/fhe.rs

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,31 @@
11
//! TFHE implementation of the Kreyvium Algorithm
22
33
use crate::shortint::ciphertext::NoiseLevel;
4-
use crate::shortint::{Ciphertext, ServerKey};
4+
use crate::shortint::oprf::OprfSeed;
5+
use crate::shortint::{Ciphertext, ClientKey, ServerKey};
6+
use crate::transciphering::backward_compatibility::SerializableKreyviumFheKeyVersions;
57
use crate::transciphering::ciphers::shift_register::ShiftRegister;
6-
use crate::transciphering::{FheKeyStream, InsufficientKeystream, StreamCipherKind, Transcipherer};
8+
use crate::transciphering::{
9+
FheKeyStream, InsufficientKeystream, StreamCipherKind, Transcipherer, TranscipheringServerKey,
10+
};
11+
use serde::{Deserialize, Serialize};
12+
use tfhe_versionable::Versionize;
713

814
use super::{
9-
collect_boxed_array, KreyviumBackwardRoundOutput, KreyviumIV, KreyviumRound,
15+
collect_boxed_array, KreyviumBackwardRoundOutput, KreyviumIV, KreyviumPlainKey, KreyviumRound,
1016
KreyviumRoundInput, KreyviumRoundOutput, KreyviumState,
1117
};
1218

1319
/// A kreyvium key encrypted in LWE, one ciphertext per bit
20+
#[derive(Clone, Serialize, Deserialize, Versionize)]
21+
#[serde(
22+
into = "SerializableKreyviumFheKey",
23+
try_from = "SerializableKreyviumFheKey"
24+
)]
25+
#[versionize(
26+
into = "SerializableKreyviumFheKey",
27+
try_from = "SerializableKreyviumFheKey"
28+
)]
1429
pub struct KreyviumFheKey {
1530
cts: Box<[Ciphertext; 128]>,
1631
}
@@ -32,9 +47,72 @@ impl KreyviumFheKey {
3247
Self { cts }
3348
}
3449

50+
pub fn new_random(
51+
seed: impl OprfSeed,
52+
transciphering_key: &TranscipheringServerKey,
53+
sks: &ServerKey,
54+
) -> Self {
55+
let encrypted_bits = transciphering_key
56+
.oprf_key()
57+
.generate_random_boolean_sequence(seed, 128, sks);
58+
59+
let boxed: Box<[Ciphertext; 128]> = encrypted_bits
60+
.into_boxed_slice()
61+
.try_into()
62+
.expect("the vec has 128 elements");
63+
64+
Self::new(boxed)
65+
}
66+
3567
pub fn init_state(self, iv: KreyviumIV, sk: &ServerKey) -> KreyviumFheState {
3668
KreyviumFheState::new(self, iv, sk)
3769
}
70+
71+
/// Decrypt the key bits
72+
pub fn decrypt(&self, client_key: &ClientKey) -> KreyviumPlainKey {
73+
let mut decrypted_bits = [false; 128];
74+
for (ct, out) in self.cts.iter().zip(decrypted_bits.iter_mut()) {
75+
*out = client_key.decrypt(ct) != 0;
76+
}
77+
KreyviumPlainKey::from(decrypted_bits)
78+
}
79+
80+
/// Borrow the underlying 128 single-bit shortint ciphertexts, MSB-first
81+
/// within each byte of the packed key (see [`super::KreyviumPlainKey`]).
82+
pub fn ciphertexts(&self) -> &[Ciphertext; 128] {
83+
&self.cts
84+
}
85+
}
86+
87+
/// Serialization form of [`KreyviumFheKey`]. The 128 key ciphertexts are stored
88+
/// in a `Vec` because serde/versionize don't support arrays longer than 32; the
89+
/// fixed-size array is restored on deserialization.
90+
#[derive(Clone, Serialize, Deserialize, Versionize)]
91+
#[versionize(SerializableKreyviumFheKeyVersions)]
92+
pub struct SerializableKreyviumFheKey {
93+
cts: Vec<Ciphertext>,
94+
}
95+
96+
impl From<KreyviumFheKey> for SerializableKreyviumFheKey {
97+
fn from(value: KreyviumFheKey) -> Self {
98+
let cts: Box<[Ciphertext]> = value.cts;
99+
Self {
100+
cts: cts.into_vec(),
101+
}
102+
}
103+
}
104+
105+
impl TryFrom<SerializableKreyviumFheKey> for KreyviumFheKey {
106+
type Error = crate::Error;
107+
108+
fn try_from(value: SerializableKreyviumFheKey) -> Result<Self, Self::Error> {
109+
let len = value.cts.len();
110+
let cts: Box<[Ciphertext; 128]> =
111+
value.cts.into_boxed_slice().try_into().map_err(|_| {
112+
crate::error!("a kreyvium key must hold exactly 128 ciphertexts, got {len}")
113+
})?;
114+
Ok(Self { cts })
115+
}
38116
}
39117

40118
pub type KreyviumFheState = KreyviumState<Ciphertext>;

tfhe/src/transciphering/ciphers/kreyvium/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ mod test;
1212

1313
use super::shift_register::ShiftRegister;
1414

15-
pub use fhe::{KreyviumFheKey, KreyviumFheState};
15+
pub use fhe::{KreyviumFheKey, KreyviumFheState, SerializableKreyviumFheKey};
1616
pub use plain::{KreyviumIV, KreyviumPlainKey, KreyviumPlainState};
1717
use rayon::prelude::*;
1818

0 commit comments

Comments
 (0)