Skip to content

Commit

Permalink
Add deterministic encryption (#303)
Browse files Browse the repository at this point in the history
* Add deterministic encryption

* Add deterministic encryption flag, runtime

* Fix clippy

* Address PR comments
  • Loading branch information
ryanorendorff authored Aug 21, 2023
1 parent 4018eab commit e90c119
Show file tree
Hide file tree
Showing 9 changed files with 253 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,4 @@ sunscreen_runtime = { version = "0.7", path = "./sunscreen_runtime" }
sunscreen_compiler_common = { version = "0.7.0", path = "./sunscreen_compiler_common" }
sunscreen_math = { version = "0.7.0", path = "./sunscreen_math" }
sunscreen_math_macros = { version = "0.7.0", path = "./sunscreen_math_macros" }
seal_fhe = { version = "0.7.0", path = "./seal_fhe" }
seal_fhe = { version = "0.7.0", path = "./seal_fhe" }
1 change: 1 addition & 0 deletions seal_fhe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ serde_json = { workspace = true }
[features]
hexl = []
transparent-ciphertexts = []
deterministic = []
2 changes: 1 addition & 1 deletion seal_fhe/SEAL
158 changes: 157 additions & 1 deletion seal_fhe/src/encryptor_decryptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,6 @@ impl Encryptor {
* pool pointed to by the given MemoryPoolHandle.
*
* * `plainext` - The plaintext to encrypt.
* * `disable_special_modulus` - Whether to disable the special modulus switching
*/
pub fn encrypt_return_components(
&self,
Expand All @@ -159,6 +158,101 @@ impl Encryptor {

Ok((ciphertext, u_destination, e_destination, fix_destination))
}

/**
*
* DO NOT USE THIS FUNCTION IN PRODUCTION: IT PRODUCES DETERMINISTIC
* ENCRYPTIONS. IT IS INHERENTLY INSECURE, AND ONLY MEANT FOR TESTING OR
* DEMONSTRATION PURPOSES.
*
* Encrypts a plaintext with the public key and returns the ciphertext as a
* serializable object.
*
* The encryption parameters for the resulting ciphertext correspond to:
* 1) in BFV, the highest (data) level in the modulus switching chain,
* 2) in CKKS, the encryption parameters of the plaintext.
* Dynamic memory allocations in the process are allocated from the memory
* pool pointed to by the given MemoryPoolHandle.
*
* * `plainext` - The plaintext to encrypt.
* * `seed` - The seed to use for encryption.
*/
#[cfg(feature = "deterministic")]
pub fn encrypt_deterministic(
&self,
plaintext: &Plaintext,
seed: &[u64; 8],
) -> Result<Ciphertext> {
let ciphertext = Ciphertext::new()?;
let u_destination = PolynomialArray::new()?;
let e_destination = PolynomialArray::new()?;
let fix_destination = Plaintext::new()?;

// We do not need the components so we do not export them.
convert_seal_error(unsafe {
bindgen::Encryptor_EncryptReturnComponentsSetSeed(
self.handle,
plaintext.get_handle(),
false,
ciphertext.get_handle(),
u_destination.get_handle(),
e_destination.get_handle(),
fix_destination.get_handle(),
seed.as_ptr() as *mut c_void,
null_mut(),
)
})?;

Ok(ciphertext)
}

/**
*
* DO NOT USE THIS FUNCTION IN PRODUCTION: IT PRODUCES DETERMINISTIC
* ENCRYPTIONS. IT IS INHERENTLY INSECURE, AND ONLY MEANT FOR TESTING OR
* DEMONSTRATION PURPOSES.
*
* Encrypts a plaintext with the public key and returns the ciphertext as a
* serializable object. Also returns the u and e values used in encrypting
* the value.
*
* The encryption parameters for the resulting ciphertext correspond to:
* 1) in BFV, the highest (data) level in the modulus switching chain,
* 2) in CKKS, the encryption parameters of the plaintext.
* Dynamic memory allocations in the process are allocated from the memory
* pool pointed to by the given MemoryPoolHandle.
*
* * `plainext` - The plaintext to encrypt.
* * `seed` - The seed to use for encryption.
*/
#[cfg(feature = "deterministic")]
pub fn encrypt_return_components_deterministic(
&self,
plaintext: &Plaintext,
seed: &[u64; 8],
) -> Result<(Ciphertext, PolynomialArray, PolynomialArray, Plaintext)> {
let ciphertext = Ciphertext::new()?;
let u_destination = PolynomialArray::new()?;
let e_destination = PolynomialArray::new()?;
let fix_destination = Plaintext::new()?;

// We do not need the components so we do not export them.
convert_seal_error(unsafe {
bindgen::Encryptor_EncryptReturnComponentsSetSeed(
self.handle,
plaintext.get_handle(),
true,
ciphertext.get_handle(),
u_destination.get_handle(),
e_destination.get_handle(),
fix_destination.get_handle(),
seed.as_ptr() as *mut c_void,
null_mut(),
)
})?;

Ok((ciphertext, u_destination, e_destination, fix_destination))
}
}

impl Drop for Encryptor {
Expand Down Expand Up @@ -469,4 +563,66 @@ mod tests {

assert_eq!(data, data_2);
}

#[cfg(feature = "deterministic")]
mod deterministic {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

use crate::*;

#[test]
fn encrypt_deterministic() {
let params = BfvEncryptionParametersBuilder::new()
.set_poly_modulus_degree(8192)
.set_coefficient_modulus(
CoefficientModulus::create(8192, &[50, 30, 30, 50, 50]).unwrap(),
)
.set_plain_modulus(PlainModulus::batching(8192, 20).unwrap())
.build()
.unwrap();

let ctx = Context::new(&params, false, SecurityLevel::TC128).unwrap();

let encoder = BFVEncoder::new(&ctx).unwrap();

let mut data = vec![];

for i in 0..encoder.get_slot_count() {
data.push(i as u64);
}

let plaintext = encoder.encode_unsigned(&data).unwrap();

let public_key_bytes = include_bytes!("../tests/data/public_key.bin");
let secret_key_bytes = include_bytes!("../tests/data/secret_key.bin");

let public_key = PublicKey::from_bytes(&ctx, public_key_bytes).unwrap();
let secret_key = SecretKey::from_bytes(&ctx, secret_key_bytes).unwrap();

let encryptor =
Encryptor::with_public_and_secret_key(&ctx, &public_key, &secret_key).unwrap();
let decryptor = Decryptor::new(&ctx, &secret_key).unwrap();

let ciphertext = encryptor
.encrypt_deterministic(&plaintext, &[0, 0, 0, 0, 0, 0, 0, 0])
.unwrap();
let decrypted = decryptor.decrypt(&ciphertext).unwrap();

let data_2 = encoder.decode_unsigned(&decrypted).unwrap();

assert_eq!(data, data_2);

let cipher_bytes = ciphertext.as_bytes().unwrap();

let mut s = DefaultHasher::new();
cipher_bytes.hash(&mut s);
let hash = s.finish();

assert_eq!(hash, 14319785560025809101);
}
}

#[cfg(feature = "deterministic")]
pub use deterministic::*;
}
Binary file added seal_fhe/tests/data/public_key.bin
Binary file not shown.
Binary file added seal_fhe/tests/data/secret_key.bin
Binary file not shown.
1 change: 1 addition & 0 deletions sunscreen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ serde_json = { workspace = true }
bulletproofs = ["sunscreen_zkp_backend/bulletproofs"]
hexl = ["seal_fhe/hexl"]
transparent-ciphertexts = ["seal_fhe/transparent-ciphertexts"]
deterministic = ["seal_fhe/deterministic", "sunscreen_runtime/deterministic"]

[[bench]]
name = "fractional_range_proof"
Expand Down
3 changes: 3 additions & 0 deletions sunscreen_runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ thiserror = { workspace = true }

[dev-dependencies]
serde_json = { workspace = true }

[features]
deterministic = ["seal_fhe/deterministic"]
94 changes: 89 additions & 5 deletions sunscreen_runtime/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,42 @@ use sunscreen_zkp_backend::CompiledZkpProgram;
use sunscreen_zkp_backend::Proof;
use sunscreen_zkp_backend::ZkpBackend;

#[cfg(feature = "deterministic")]
fn encrypt_function(
encryptor: &Encryptor,
val: &seal_fhe::Plaintext,
seed: Option<&[u64; 8]>,
) -> Result<(
seal_fhe::Ciphertext,
PolynomialArray,
PolynomialArray,
seal_fhe::Plaintext,
)> {
let result = if let Some(seed) = seed {
encryptor.encrypt_return_components_deterministic(val, seed)
} else {
encryptor.encrypt_return_components(val)
};

result.map_err(Error::SealError)
}

#[cfg(not(feature = "deterministic"))]
fn encrypt_function(
encryptor: &Encryptor,
val: &seal_fhe::Plaintext,
_seed: Option<&[u64; 8]>,
) -> Result<(
seal_fhe::Ciphertext,
PolynomialArray,
PolynomialArray,
seal_fhe::Plaintext,
)> {
encryptor
.encrypt_return_components(val)
.map_err(Error::SealError)
}

enum Context {
Seal(SealContext),
}
Expand Down Expand Up @@ -431,7 +467,31 @@ where
where
P: TryIntoPlaintext + TypeName,
{
self.encrypt_return_components_switched(val, public_key, false)
self.encrypt_return_components_switched(val, public_key, false, None)
.map(|x| x.ciphertext)
}

/**
* DO NOT USE THIS FUNCTION IN PRODUCTION: IT PRODUCES DETERMINISTIC
* ENCRYPTIONS. IT IS INHERENTLY INSECURE, AND ONLY MEANT FOR TESTING OR
* DEMONSTRATION PURPOSES.
*
* Encrypts the given [`FheType`](crate::FheType) using the given public key.
*
* Returns [`Error::ParameterMismatch`] if the plaintext is incompatible with this runtime's
* scheme.
*/
#[cfg(feature = "deterministic")]
pub fn encrypt_deterministic<P>(
&self,
val: P,
public_key: &PublicKey,
seed: &[u64; 8],
) -> Result<Ciphertext>
where
P: TryIntoPlaintext + TypeName,
{
self.encrypt_return_components_switched(val, public_key, false, Some(seed))
.map(|x| x.ciphertext)
}

Expand All @@ -451,8 +511,33 @@ where
where
P: TryIntoPlaintext + TypeName,
{
self.encrypt_return_components_switched(val, public_key, true)
self.encrypt_return_components_switched(val, public_key, true, None)
}

/**
* DO NOT USE THIS FUNCTION IN PRODUCTION: IT PRODUCES DETERMINISTIC
* ENCRYPTIONS. IT IS INHERENTLY INSECURE, AND ONLY MEANT FOR TESTING OR
* DEMONSTRATION PURPOSES.
*
* Encrypts the given [`FheType`](crate::FheType) using the given public key.
*
* Returns [`Error::ParameterMismatch`] if the plaintext is incompatible with this runtime's
* scheme.
*/
#[cfg(feature = "deterministic")]
#[allow(dead_code)]
fn encrypt_return_components_deterministic<P>(
&self,
val: P,
public_key: &PublicKey,
seed: &[u64; 8],
) -> Result<BFVEncryptionComponents>
where
P: TryIntoPlaintext + TypeName,
{
self.encrypt_return_components_switched(val, public_key, true, Some(seed))
}

/**
* Encrypts the given [`FheType`](crate::FheType) using the given public
* key, returning the encrypted value along with the components added to the
Expand All @@ -468,6 +553,7 @@ where
val: P,
public_key: &PublicKey,
export_components: bool,
seed: Option<&[u64; 8]>,
) -> Result<BFVEncryptionComponents>
where
P: TryIntoPlaintext + TypeName,
Expand Down Expand Up @@ -495,9 +581,7 @@ where
let ciphertext = if export_components {
encryptor.encrypt(p).map_err(Error::SealError)
} else {
let (ciphertext, u, e, r) = encryptor
.encrypt_return_components(p)
.map_err(Error::SealError)?;
let (ciphertext, u, e, r) = encrypt_function(&encryptor, p, seed)?;

let r_context = WithContext {
params: fhe_data.params.clone(),
Expand Down

0 comments on commit e90c119

Please sign in to comment.