use crate::integer::ciphertext::compressed_ciphertext_list::*;
use crate::integer::ciphertext::{ReRandomizationContext, ReRandomizationKey};
use crate::integer::key_switching_key::{KeySwitchingKeyBuildHelper, KeySwitchingKeyMaterial};
use crate::integer::{
gen_keys, BooleanBlock, CompactPrivateKey, CompactPublicKey, IntegerKeyKind, RadixCiphertext,
SignedRadixCiphertext,
};
use crate::shortint::parameters::test_params::{
TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2,
};
use crate::shortint::parameters::{
CompactPublicKeyEncryptionParameters, CompressionParameters, ReRandomizationParameters,
};
use crate::shortint::ShortintParameterSet;
use itertools::Itertools;
use rand::Rng;
const NB_TESTS: usize = 10;
const NUM_BLOCKS: usize = 32;
#[test]
fn test_ciphertext_re_randomization_after_compression_with_dedicated_cpk() {
let params = TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let comp_params = TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let cpk_params = TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2;
let rerand_ksk_params = TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let rerand_params =
ReRandomizationParameters::LegacyDedicatedCPKWithKeySwitch { rerand_ksk_params };
test_ciphertext_re_randomization_after_compression_impl(
params.into(),
comp_params,
Some(cpk_params),
rerand_params,
);
}
#[test]
fn test_ciphertext_re_randomization_after_compression_with_derived_cpk() {
let params = TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let comp_params = TEST_COMP_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let rerand_params = ReRandomizationParameters::DerivedCPKWithoutKeySwitch;
test_ciphertext_re_randomization_after_compression_impl(
params.into(),
comp_params,
None,
rerand_params,
);
}
fn test_ciphertext_re_randomization_after_compression_impl(
params: ShortintParameterSet,
comp_params: CompressionParameters,
cpk_params: Option<CompactPublicKeyEncryptionParameters>,
rerand_params: ReRandomizationParameters,
) {
let (cks, sks) = gen_keys::<ShortintParameterSet>(params, IntegerKeyKind::Radix);
let private_compression_key = cks.new_compression_private_key(comp_params);
let (compression_key, decompression_key) =
cks.new_compression_decompression_keys(&private_compression_key);
let cpk;
let rerand_ksk: KeySwitchingKeyMaterial;
let re_randomization_key = match (cpk_params, rerand_params) {
(
Some(cpk_params),
ReRandomizationParameters::LegacyDedicatedCPKWithKeySwitch { rerand_ksk_params },
) => {
let dedicated_compact_private_key = CompactPrivateKey::new(cpk_params);
cpk = CompactPublicKey::new(&dedicated_compact_private_key);
rerand_ksk = KeySwitchingKeyBuildHelper::new(
(&dedicated_compact_private_key, None),
(&cks, &sks),
rerand_ksk_params,
)
.into();
ReRandomizationKey::LegacyDedicatedCPK {
cpk: &cpk,
ksk: rerand_ksk.as_view(),
}
}
(None, ReRandomizationParameters::DerivedCPKWithoutKeySwitch) => {
let derived_compact_private_key: CompactPrivateKey<&[u64]> = (&cks).try_into().unwrap();
cpk = CompactPublicKey::new(&derived_compact_private_key);
ReRandomizationKey::DerivedCPKWithoutKeySwitch { cpk: &cpk }
}
_ => panic!("Inconsistent rerand test setup"),
};
let rerand_domain_separator = *b"TFHE_Rrd";
let compact_public_encryption_domain_separator = *b"TFHE_Enc";
let metadata = b"lol".as_slice();
let mut rng = rand::thread_rng();
let message_modulus: u128 = cks.parameters().message_modulus().0 as u128;
let modulus = message_modulus.pow(NUM_BLOCKS as u32);
for _ in 0..NB_TESTS {
let message = rng.gen::<u128>() % modulus;
let ct = cks.encrypt_radix(message, NUM_BLOCKS);
let mut builder = CompressedCiphertextListBuilder::new();
builder.push(ct);
let compressed = builder.build(&compression_key);
let decompressed: RadixCiphertext = compressed.get(0, &decompression_key).unwrap().unwrap();
let mut re_randomizer_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata],
compact_public_encryption_domain_separator,
);
re_randomizer_context.add_ciphertext(&decompressed);
let mut seed_gen = re_randomizer_context.finalize();
let mut re_randomized = decompressed.clone();
re_randomized
.re_randomize(re_randomization_key, seed_gen.next_seed().unwrap())
.unwrap();
assert_ne!(decompressed, re_randomized);
let decrypted: u128 = cks.decrypt_radix(&re_randomized);
assert_eq!(decrypted, message);
}
let modulus = message_modulus.pow((NUM_BLOCKS - 1) as u32) as i128;
for _ in 0..NB_TESTS {
let message = rng.gen::<i128>() % modulus;
let ct = cks.encrypt_signed_radix(message, NUM_BLOCKS);
let mut builder = CompressedCiphertextListBuilder::new();
builder.push(ct);
let compressed = builder.build(&compression_key);
let decompressed: SignedRadixCiphertext =
compressed.get(0, &decompression_key).unwrap().unwrap();
let mut re_randomizer_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata],
compact_public_encryption_domain_separator,
);
re_randomizer_context.add_ciphertext(&decompressed);
let mut seed_gen = re_randomizer_context.finalize();
let mut re_randomized = decompressed.clone();
re_randomized
.re_randomize(re_randomization_key, seed_gen.next_seed().unwrap())
.unwrap();
assert_ne!(decompressed, re_randomized);
let decrypted: i128 = cks.decrypt_signed_radix(&re_randomized);
assert_eq!(decrypted, message);
}
for _ in 0..NB_TESTS {
let messages = [false, true];
let cts = messages
.iter()
.map(|message| cks.encrypt_bool(*message))
.collect_vec();
let mut builder = CompressedCiphertextListBuilder::new();
builder.extend(cts.into_iter());
let compressed = builder.build(&compression_key);
for (i, message) in messages.iter().enumerate() {
let decompressed: BooleanBlock =
compressed.get(i, &decompression_key).unwrap().unwrap();
let mut re_randomizer_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata],
compact_public_encryption_domain_separator,
);
re_randomizer_context.add_ciphertext(&decompressed);
let mut seed_gen = re_randomizer_context.finalize();
let mut re_randomized = decompressed.clone();
re_randomized
.re_randomize(re_randomization_key, seed_gen.next_seed().unwrap())
.unwrap();
assert_ne!(decompressed, re_randomized);
let decrypted = cks.decrypt_bool(&re_randomized);
assert_eq!(decrypted, *message);
}
}
}
#[cfg(feature = "zk-pok")]
mod zk {
use crate::core_crypto::prelude::LweCiphertextCount;
use crate::integer::ciphertext::{ProvenCompactCiphertextList, ReRandomizationContext};
use crate::integer::key_switching_key::KeySwitchingKey;
use crate::integer::parameters::IntegerCompactCiphertextListExpansionMode;
use crate::integer::{
BooleanBlock, ClientKey, CompactPrivateKey, CompactPublicKey, RadixCiphertext, ServerKey,
SignedRadixCiphertext,
};
use crate::shortint::parameters::test_params::{
TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128,
TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2,
};
use crate::zk::{CompactPkeCrs, ZkComputeLoad};
use rand::Rng;
#[test]
fn test_proven_compact_ciphertext_list_re_randomization() {
let pke_params = TEST_PARAM_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2;
let ksk_params = TEST_PARAM_KEYSWITCH_PKE_TO_BIG_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let fhe_params = TEST_PARAM_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128;
let num_blocks = 4usize;
let metadata = [b't', b'e', b's', b't'];
let rerand_domain_separator = *b"TFHE_Rrd";
let compact_public_encryption_domain_separator = *b"TFHE_Enc";
let cks = ClientKey::new(fhe_params);
let sks = ServerKey::new_radix_server_key(&cks);
let compact_private_key = CompactPrivateKey::new(pke_params);
let ksk = KeySwitchingKey::new((&compact_private_key, None), (&cks, &sks), ksk_params);
let pk = CompactPublicKey::new(&compact_private_key);
let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(512)).unwrap();
let mut rng = rand::thread_rng();
let message_modulus = pke_params.message_modulus.0 as u128;
{
let modulus = message_modulus.pow(num_blocks as u32);
let message = rng.gen::<u128>() % modulus;
let mut builder = ProvenCompactCiphertextList::builder(&pk);
builder.push_with_num_blocks(message, num_blocks);
let proven_ct = builder
.build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof)
.unwrap();
let mut re_rand_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata.as_slice()],
compact_public_encryption_domain_separator,
);
re_rand_context.add_proven_ciphertext_list(&proven_ct);
let mut seed_gen = re_rand_context.finalize();
let mut re_randomized = proven_ct.clone();
re_randomized
.re_randomize(&pk, seed_gen.next_seed().unwrap())
.unwrap();
assert!(proven_ct != re_randomized);
let expander = re_randomized
.expand_without_verification(
IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(
ksk.as_view(),
),
)
.unwrap();
let expanded: RadixCiphertext = expander.get(0).unwrap().unwrap();
let decrypted: u128 = cks.decrypt_radix(&expanded);
assert_eq!(decrypted, message);
}
{
let modulus = message_modulus.pow((num_blocks - 1) as u32) as i128;
let message = rng.gen::<i128>() % modulus;
let mut builder = ProvenCompactCiphertextList::builder(&pk);
builder.push_with_num_blocks(message, num_blocks);
let proven_ct = builder
.build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof)
.unwrap();
let mut re_rand_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata.as_slice()],
compact_public_encryption_domain_separator,
);
re_rand_context.add_proven_ciphertext_list(&proven_ct);
let mut seed_gen = re_rand_context.finalize();
let mut re_randomized = proven_ct.clone();
re_randomized
.re_randomize(&pk, seed_gen.next_seed().unwrap())
.unwrap();
assert!(proven_ct != re_randomized);
let expander = re_randomized
.expand_without_verification(
IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(
ksk.as_view(),
),
)
.unwrap();
let expanded: SignedRadixCiphertext = expander.get(0).unwrap().unwrap();
let decrypted: i128 = cks.decrypt_signed_radix(&expanded);
assert_eq!(decrypted, message);
}
{
for message in [false, true] {
let mut builder = ProvenCompactCiphertextList::builder(&pk);
builder.push(message);
let proven_ct = builder
.build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof)
.unwrap();
let mut re_rand_context = ReRandomizationContext::new(
rerand_domain_separator,
[metadata.as_slice()],
compact_public_encryption_domain_separator,
);
re_rand_context.add_proven_ciphertext_list(&proven_ct);
let mut seed_gen = re_rand_context.finalize();
let mut re_randomized = proven_ct.clone();
re_randomized
.re_randomize(&pk, seed_gen.next_seed().unwrap())
.unwrap();
assert!(proven_ct != re_randomized);
let expander = re_randomized
.expand_without_verification(
IntegerCompactCiphertextListExpansionMode::CastAndUnpackIfNecessary(
ksk.as_view(),
),
)
.unwrap();
let expanded: BooleanBlock = expander.get(0).unwrap().unwrap();
let decrypted = cks.decrypt_bool(&expanded);
assert_eq!(decrypted, message);
}
}
}
#[cfg(feature = "gpu")]
#[test]
fn test_proven_compact_ciphertext_list_re_rand_cpu_gpu_compatibility() {
use crate::core_crypto::gpu::CudaStreams;
use crate::integer::gpu::zk::CudaProvenCompactCiphertextList;
use crate::shortint::parameters::test_params::TEST_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2;
let pke_params = TEST_PARAM_PKE_TO_SMALL_MESSAGE_2_CARRY_2_KS_PBS_TUNIFORM_2M128_ZKV2;
let compact_private_key = CompactPrivateKey::new(pke_params);
let pk = CompactPublicKey::new(&compact_private_key);
let compact_public_encryption_domain_separator = *b"TFHE_Enc";
let rerand_domain_separator = *b"TFHE_Rrd";
let crs = CompactPkeCrs::from_shortint_params(pke_params, LweCiphertextCount(8)).unwrap();
let metadata = [b'r', b'e', b'r', b'a', b'n', b'd'];
let clear_a = rand::random::<u64>();
let clear_b = rand::random::<i8>();
let proven_ct = ProvenCompactCiphertextList::builder(&pk)
.push(clear_a)
.push(clear_b)
.push(false)
.build_with_proof_packed(&crs, &metadata, ZkComputeLoad::Proof)
.unwrap();
let mut cpu_list = proven_ct.clone();
let nonce: [u8; 256 / 8] = core::array::from_fn(|_| rand::random());
let cpu_seed = {
let mut ctx = ReRandomizationContext::new(
rerand_domain_separator,
[b"expand".as_slice(), nonce.as_slice()],
compact_public_encryption_domain_separator,
);
ctx.add_proven_ciphertext_list(&proven_ct);
ctx.finalize().next_seed().unwrap()
};
let gpu_seed = {
let mut ctx = ReRandomizationContext::new(
rerand_domain_separator,
[b"expand".as_slice(), nonce.as_slice()],
compact_public_encryption_domain_separator,
);
ctx.add_proven_ciphertext_list(&proven_ct);
ctx.finalize().next_seed().unwrap()
};
cpu_list.re_randomize(&pk, cpu_seed).unwrap();
let streams = CudaStreams::new_multi_gpu();
let mut gpu_list = CudaProvenCompactCiphertextList::from_proven_compact_ciphertext_list(
&proven_ct, &streams,
);
gpu_list.re_randomize(&pk, gpu_seed, &streams).unwrap();
let gpu_compact_lists = gpu_list
.d_flattened_compact_lists
.to_vec_shortint_compact_ciphertext_list(&streams)
.unwrap();
let gpu_proved_lists: Vec<_> = gpu_compact_lists
.into_iter()
.zip(gpu_list.h_proved_lists.ct_list.proved_lists.iter())
.map(|(ct, (_, proof))| (ct, proof.clone()))
.collect();
let gpu_on_cpu = ProvenCompactCiphertextList {
ct_list: crate::shortint::ciphertext::ProvenCompactCiphertextList {
proved_lists: gpu_proved_lists,
},
info: gpu_list.h_proved_lists.info.clone(),
};
assert!(cpu_list == gpu_on_cpu);
}
}