core refactoring (#69)

This commit is contained in:
Jean-Philippe Bossuat
2025-08-14 17:20:28 +02:00
committed by GitHub
parent 6303346eef
commit 8d9897b88b
167 changed files with 7972 additions and 6821 deletions

View File

@@ -0,0 +1,39 @@
use backend::{
hal::{api::ModuleNew, layouts::Module},
implementation::cpu_spqlios::FFT64,
};
use crate::blind_rotation::tests::{
generic_cggi::blind_rotatio_test,
generic_lut::{test_lut_extended, test_lut_standard},
};
#[test]
fn lut_standard() {
let module: Module<FFT64> = Module::<FFT64>::new(32);
test_lut_standard(&module);
}
#[test]
fn lut_extended() {
let module: Module<FFT64> = Module::<FFT64>::new(32);
test_lut_extended(&module);
}
#[test]
fn standard() {
let module: Module<FFT64> = Module::<FFT64>::new(512);
blind_rotatio_test(&module, 224, 1, 1);
}
#[test]
fn block_binary() {
let module: Module<FFT64> = Module::<FFT64>::new(512);
blind_rotatio_test(&module, 224, 7, 1);
}
#[test]
fn block_binary_extended() {
let module: Module<FFT64> = Module::<FFT64>::new(512);
blind_rotatio_test(&module, 224, 7, 2);
}

View File

@@ -0,0 +1 @@
mod fft64;

View File

@@ -0,0 +1,163 @@
use backend::hal::{
api::{
ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxAddNormal, VecZnxAddScalarInplace, VecZnxEncodeCoeffsi64, VecZnxFillUniform,
VecZnxRotateInplace, VecZnxSub, VecZnxSwithcDegree, VmpPMatAlloc, VmpPMatPrepare, ZnxView,
},
layouts::{Backend, Module, ScratchOwned},
oep::{
ScratchAvailableImpl, ScratchOwnedAllocImpl, ScratchOwnedBorrowImpl, TakeVecZnxBigImpl, TakeVecZnxDftImpl,
TakeVecZnxDftSliceImpl, TakeVecZnxImpl, TakeVecZnxSliceImpl, VecZnxBigAllocBytesImpl, VecZnxDftAllocBytesImpl,
},
};
use sampling::source::Source;
use crate::{
BlindRotationKeyCGGI, BlindRotationKeyCGGIExec, BlindRotationKeyCGGIExecLayoutFamily, CCGIBlindRotationFamily, Infos,
LookUpTable, cggi_blind_rotate, cggi_blind_rotate_scratch_space,
layouts::{
GLWECiphertext, GLWEPlaintext, GLWESecret, LWECiphertext, LWECiphertextToRef, LWEPlaintext, LWESecret,
prepared::GLWESecretExec,
},
mod_switch_2n,
};
use crate::trait_families::{GLWEDecryptFamily, GLWESecretExecModuleFamily};
pub(crate) trait CGGITestModuleFamily<B: Backend> = CCGIBlindRotationFamily<B>
+ GLWESecretExecModuleFamily<B>
+ GLWEDecryptFamily<B>
+ BlindRotationKeyCGGIExecLayoutFamily<B>
+ VecZnxFillUniform
+ VecZnxAddNormal
+ VecZnxAddScalarInplace
+ VecZnxEncodeCoeffsi64
+ VecZnxRotateInplace
+ VecZnxSwithcDegree
+ VecZnxSub
+ VmpPMatAlloc<B>
+ VmpPMatPrepare<B>;
pub(crate) trait CGGITestScratchFamily<B: Backend> = VecZnxDftAllocBytesImpl<B>
+ VecZnxBigAllocBytesImpl<B>
+ ScratchOwnedAllocImpl<B>
+ ScratchOwnedBorrowImpl<B>
+ TakeVecZnxDftImpl<B>
+ TakeVecZnxBigImpl<B>
+ TakeVecZnxDftSliceImpl<B>
+ ScratchAvailableImpl<B>
+ TakeVecZnxImpl<B>
+ TakeVecZnxSliceImpl<B>;
pub(crate) fn blind_rotatio_test<B: Backend>(module: &Module<B>, n_lwe: usize, block_size: usize, extension_factor: usize)
where
Module<B>: CGGITestModuleFamily<B>,
B: CGGITestScratchFamily<B>,
{
let n: usize = module.n();
let basek: usize = 19;
let k_lwe: usize = 24;
let k_brk: usize = 3 * basek;
let rows_brk: usize = 2; // Ensures first limb is noise-free.
let k_lut: usize = 1 * basek;
let k_res: usize = 2 * basek;
let rank: usize = 1;
let message_modulus: usize = 1 << 4;
let mut source_xs: Source = Source::new([2u8; 32]);
let mut source_xe: Source = Source::new([2u8; 32]);
let mut source_xa: Source = Source::new([1u8; 32]);
let mut sk_glwe: GLWESecret<Vec<u8>> = GLWESecret::alloc(n, rank);
sk_glwe.fill_ternary_prob(0.5, &mut source_xs);
let sk_glwe_dft: GLWESecretExec<Vec<u8>, B> = GLWESecretExec::from(module, &sk_glwe);
let mut sk_lwe: LWESecret<Vec<u8>> = LWESecret::alloc(n_lwe);
sk_lwe.fill_binary_block(block_size, &mut source_xs);
let mut scratch: ScratchOwned<B> = ScratchOwned::<B>::alloc(BlindRotationKeyCGGI::generate_from_sk_scratch_space(
module, n, basek, k_brk, rank,
));
let mut scratch_br: ScratchOwned<B> = ScratchOwned::<B>::alloc(cggi_blind_rotate_scratch_space(
module,
n,
block_size,
extension_factor,
basek,
k_res,
k_brk,
rows_brk,
rank,
));
let mut brk: BlindRotationKeyCGGI<Vec<u8>> = BlindRotationKeyCGGI::alloc(n, n_lwe, basek, k_brk, rows_brk, rank);
brk.generate_from_sk(
module,
&sk_glwe_dft,
&sk_lwe,
&mut source_xa,
&mut source_xe,
3.2,
scratch.borrow(),
);
let mut lwe: LWECiphertext<Vec<u8>> = LWECiphertext::alloc(n_lwe, basek, k_lwe);
let mut pt_lwe: LWEPlaintext<Vec<u8>> = LWEPlaintext::alloc(basek, k_lwe);
let x: i64 = 2;
let bits: usize = 8;
module.encode_coeff_i64(basek, &mut pt_lwe.data, 0, bits, 0, x, bits);
lwe.encrypt_sk(
module,
&pt_lwe,
&sk_lwe,
&mut source_xa,
&mut source_xe,
3.2,
);
let mut f: Vec<i64> = vec![0i64; message_modulus];
f.iter_mut()
.enumerate()
.for_each(|(i, x)| *x = 2 * (i as i64) + 1);
let mut lut: LookUpTable = LookUpTable::alloc(n, basek, k_lut, extension_factor);
lut.set(module, &f, message_modulus);
let mut res: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(n, basek, k_res, rank);
let brk_exec: BlindRotationKeyCGGIExec<Vec<u8>, B> = BlindRotationKeyCGGIExec::from(module, &brk, scratch_br.borrow());
cggi_blind_rotate(module, &mut res, &lwe, &lut, &brk_exec, scratch_br.borrow());
let mut pt_have: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(n, basek, k_res);
res.decrypt(module, &mut pt_have, &sk_glwe_dft, scratch.borrow());
let mut lwe_2n: Vec<i64> = vec![0i64; lwe.n() + 1]; // TODO: from scratch space
mod_switch_2n(
2 * lut.domain_size(),
&mut lwe_2n,
&lwe.to_ref(),
lut.rotation_direction(),
);
let pt_want: i64 = (lwe_2n[0]
+ lwe_2n[1..]
.iter()
.zip(sk_lwe.data.at(0, 0))
.map(|(x, y)| x * y)
.sum::<i64>())
& (2 * lut.domain_size() - 1) as i64;
lut.rotate(module, pt_want);
// First limb should be exactly equal (test are parameterized such that the noise does not reach
// the first limb)
assert_eq!(pt_have.data.at(0, 0), lut.data[0].at(0, 0));
}

View File

@@ -0,0 +1,95 @@
use std::vec;
use backend::hal::{
api::{
VecZnxCopy, VecZnxDecodeVeci64, VecZnxNormalizeInplace, VecZnxNormalizeTmpBytes, VecZnxRotateInplace, VecZnxSwithcDegree,
},
layouts::{Backend, Module},
oep::{ScratchOwnedAllocImpl, ScratchOwnedBorrowImpl},
};
use crate::{DivRound, LookUpTable};
pub(crate) fn test_lut_standard<B: Backend>(module: &Module<B>)
where
Module<B>: VecZnxRotateInplace
+ VecZnxNormalizeInplace<B>
+ VecZnxNormalizeTmpBytes
+ VecZnxSwithcDegree
+ VecZnxCopy
+ VecZnxDecodeVeci64,
B: ScratchOwnedAllocImpl<B> + ScratchOwnedBorrowImpl<B>,
{
let n: usize = module.n();
let basek: usize = 20;
let k_lut: usize = 40;
let message_modulus: usize = 16;
let extension_factor: usize = 1;
let log_scale: usize = basek + 1;
let mut f: Vec<i64> = vec![0i64; message_modulus];
f.iter_mut()
.enumerate()
.for_each(|(i, x)| *x = (i as i64) - 8);
let mut lut: LookUpTable = LookUpTable::alloc(n, basek, k_lut, extension_factor);
lut.set(module, &f, log_scale);
let half_step: i64 = lut.domain_size().div_round(message_modulus << 1) as i64;
lut.rotate(module, half_step);
let step: usize = lut.domain_size().div_round(message_modulus);
let mut lut_dec: Vec<i64> = vec![0i64; module.n()];
module.decode_vec_i64(basek, &lut.data[0], 0, log_scale, &mut lut_dec);
(0..lut.domain_size()).step_by(step).for_each(|i| {
(0..step).for_each(|_| {
assert_eq!(f[i / step] % message_modulus as i64, lut_dec[i]);
});
});
}
pub(crate) fn test_lut_extended<B: Backend>(module: &Module<B>)
where
Module<B>: VecZnxRotateInplace
+ VecZnxNormalizeInplace<B>
+ VecZnxNormalizeTmpBytes
+ VecZnxSwithcDegree
+ VecZnxCopy
+ VecZnxDecodeVeci64,
B: ScratchOwnedAllocImpl<B> + ScratchOwnedBorrowImpl<B>,
{
let n: usize = module.n();
let basek: usize = 20;
let k_lut: usize = 40;
let message_modulus: usize = 16;
let extension_factor: usize = 4;
let log_scale: usize = basek + 1;
let mut f: Vec<i64> = vec![0i64; message_modulus];
f.iter_mut()
.enumerate()
.for_each(|(i, x)| *x = (i as i64) - 8);
let mut lut: LookUpTable = LookUpTable::alloc(n, basek, k_lut, extension_factor);
lut.set(&module, &f, log_scale);
let half_step: i64 = lut.domain_size().div_round(message_modulus << 1) as i64;
lut.rotate(&module, half_step);
let step: usize = module.n().div_round(message_modulus);
let mut lut_dec: Vec<i64> = vec![0i64; module.n()];
(0..extension_factor).for_each(|ext| {
module.decode_vec_i64(basek, &lut.data[ext], 0, log_scale, &mut lut_dec);
(0..module.n()).step_by(step).for_each(|i| {
(0..step).for_each(|_| {
assert_eq!(f[i / step] % message_modulus as i64, lut_dec[i]);
});
});
});
}

View File

@@ -0,0 +1,15 @@
use backend::hal::tests::serialization::test_reader_writer_interface;
use crate::{BlindRotationKeyCGGI, BlindRotationKeyCGGICompressed};
#[test]
fn test_cggi_blind_rotation_key_serialization() {
let original: BlindRotationKeyCGGI<Vec<u8>> = BlindRotationKeyCGGI::alloc(256, 64, 12, 54, 2, 2);
test_reader_writer_interface(original);
}
#[test]
fn test_cggi_blind_rotation_key_compressed_serialization() {
let original: BlindRotationKeyCGGICompressed<Vec<u8>> = BlindRotationKeyCGGICompressed::alloc(256, 64, 12, 54, 2, 2);
test_reader_writer_interface(original);
}

View File

@@ -0,0 +1,396 @@
use backend::hal::{
api::{
ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxAddScalarInplace, VecZnxAutomorphism, VecZnxAutomorphismInplace, VecZnxCopy,
VecZnxStd, VecZnxSubScalarInplace, VecZnxSwithcDegree,
},
layouts::{Backend, Module, ScratchOwned},
oep::{
ScratchAvailableImpl, ScratchOwnedAllocImpl, ScratchOwnedBorrowImpl, TakeScalarZnxImpl, TakeSvpPPolImpl,
TakeVecZnxBigImpl, TakeVecZnxDftImpl, TakeVecZnxImpl,
},
};
use sampling::source::Source;
use crate::{
AutomorphismKey, AutomorphismKeyCompressed, AutomorphismKeyEncryptSkFamily, AutomorphismKeyExec, GGLWEExecLayoutFamily,
GLWEDecryptFamily, GLWEKeyswitchFamily, GLWEPlaintext, GLWESecret, GLWESecretExec, Infos,
noise::log2_std_noise_gglwe_product,
};
pub(crate) trait AutomorphismTestModuleFamily<B: Backend> = AutomorphismKeyEncryptSkFamily<B>
+ GLWEKeyswitchFamily<B>
+ VecZnxAutomorphism
+ GGLWEExecLayoutFamily<B>
+ VecZnxSwithcDegree
+ VecZnxAddScalarInplace
+ VecZnxAutomorphism
+ VecZnxAutomorphismInplace
+ GLWEDecryptFamily<B>
+ VecZnxSubScalarInplace
+ VecZnxStd
+ VecZnxCopy;
pub(crate) trait AutomorphismTestScratchFamily<B: Backend> = ScratchOwnedAllocImpl<B>
+ ScratchOwnedBorrowImpl<B>
+ ScratchAvailableImpl<B>
+ TakeScalarZnxImpl<B>
+ TakeVecZnxDftImpl<B>
+ TakeVecZnxImpl<B>
+ TakeSvpPPolImpl<B>
+ TakeVecZnxBigImpl<B>;
pub(crate) fn test_automorphisk_key_encrypt_sk<B: Backend>(
module: &Module<B>,
basek: usize,
k_ksk: usize,
digits: usize,
rank: usize,
sigma: f64,
) where
Module<B>: AutomorphismTestModuleFamily<B>,
B: AutomorphismTestScratchFamily<B>,
{
let n: usize = TakeScalarZnx;
let rows: usize = (k_ksk - digits * basek) / (digits * basek);
let mut atk: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_ksk, rows, digits, rank);
let mut source_xs: Source = Source::new([0u8; 32]);
let mut source_xe: Source = Source::new([0u8; 32]);
let mut source_xa: Source = Source::new([0u8; 32]);
let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(AutomorphismKey::encrypt_sk_scratch_space(
module, n, basek, k_ksk, rank,
));
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(n, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
let p = -5;
atk.encrypt_sk(
module,
p,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut sk_out: GLWESecret<Vec<u8>> = sk.clone();
(0..atk.rank()).for_each(|i| {
module.vec_znx_automorphism(
module.galois_element_inv(p),
&mut sk_out.data.as_vec_znx_mut(),
i,
&sk.data.as_vec_znx(),
i,
);
});
let sk_out_exec = GLWESecretExec::from(module, &sk_out);
atk.key
.key
.assert_noise(module, &sk_out_exec, &sk.data, sigma);
}
pub(crate) fn test_automorphisk_key_encrypt_sk_compressed<B: Backend>(
module: &Module<B>,
basek: usize,
k_ksk: usize,
digits: usize,
rank: usize,
sigma: f64,
) where
Module<B>: AutomorphismTestModuleFamily<B>,
B: AutomorphismTestScratchFamily<B>,
{
let n: usize = TakeScalarZnx;
let rows: usize = (k_ksk - digits * basek) / (digits * basek);
let mut atk_compressed: AutomorphismKeyCompressed<Vec<u8>> =
AutomorphismKeyCompressed::alloc(n, basek, k_ksk, rows, digits, rank);
let mut source_xs: Source = Source::new([0u8; 32]);
let mut source_xe: Source = Source::new([0u8; 32]);
let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(AutomorphismKey::encrypt_sk_scratch_space(
module, n, basek, k_ksk, rank,
));
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(n, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
let p = -5;
let seed_xa: [u8; 32] = [1u8; 32];
atk_compressed.encrypt_sk(
module,
p,
&sk,
seed_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut sk_out: GLWESecret<Vec<u8>> = sk.clone();
(0..atk_compressed.rank()).for_each(|i| {
module.vec_znx_automorphism(
module.galois_element_inv(p),
&mut sk_out.data.as_vec_znx_mut(),
i,
&sk.data.as_vec_znx(),
i,
);
});
let sk_out_exec = GLWESecretExec::from(module, &sk_out);
let mut atk: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_ksk, rows, digits, rank);
atk.decompress(module, &atk_compressed);
atk.key
.key
.assert_noise(module, &sk_out_exec, &sk.data, sigma);
}
pub(crate) fn test_gglwe_automorphism<B: Backend>(
module: &Module<B>,
p0: i64,
p1: i64,
basek: usize,
digits: usize,
k_in: usize,
k_out: usize,
k_apply: usize,
sigma: f64,
rank: usize,
) where
Module<B>: AutomorphismTestModuleFamily<B>,
B: AutomorphismTestScratchFamily<B>,
{
let n: usize = TakeScalarZnx;
let digits_in: usize = 1;
let rows_in: usize = k_in / (basek * digits);
let rows_apply: usize = k_in.div_ceil(basek * digits);
let mut auto_key_in: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_in, rows_in, digits_in, rank);
let mut auto_key_out: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_out, rows_in, digits_in, rank);
let mut auto_key_apply: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_apply, rows_apply, digits, rank);
let mut source_xs: Source = Source::new([0u8; 32]);
let mut source_xe: Source = Source::new([0u8; 32]);
let mut source_xa: Source = Source::new([0u8; 32]);
let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(
AutomorphismKey::encrypt_sk_scratch_space(module, n, basek, k_apply, rank)
| AutomorphismKey::automorphism_scratch_space(module, n, basek, k_out, k_in, k_apply, digits, rank),
);
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(TakeScalarZnx, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
// gglwe_{s1}(s0) = s0 -> s1
auto_key_in.encrypt_sk(
module,
p0,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
auto_key_apply.encrypt_sk(
module,
p1,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut auto_key_apply_exec: AutomorphismKeyExec<Vec<u8>, B> =
AutomorphismKeyExec::alloc(module, n, basek, k_apply, rows_apply, digits, rank);
auto_key_apply_exec.prepare(module, &auto_key_apply, scratch.borrow());
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
auto_key_out.automorphism(module, &auto_key_in, &auto_key_apply_exec, scratch.borrow());
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(TakeScalarZnx, basek, k_out);
let mut sk_auto: GLWESecret<Vec<u8>> = GLWESecret::alloc(TakeScalarZnx, rank);
sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
(0..rank).for_each(|i| {
module.vec_znx_automorphism(
module.galois_element_inv(p0 * p1),
&mut sk_auto.data.as_vec_znx_mut(),
i,
&sk.data.as_vec_znx(),
i,
);
});
let sk_auto_dft: GLWESecretExec<Vec<u8>, B> = GLWESecretExec::from(module, &sk_auto);
(0..auto_key_out.rank_in()).for_each(|col_i| {
(0..auto_key_out.rows()).for_each(|row_i| {
auto_key_out
.at(row_i, col_i)
.decrypt(module, &mut pt, &sk_auto_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk.data,
col_i,
);
let noise_have: f64 = module.vec_znx_std(basek, &pt.data, 0).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
TakeScalarZnx as f64,
basek * digits,
0.5,
0.5,
0f64,
sigma * sigma,
0f64,
rank as f64,
k_out,
k_apply,
);
assert!(
noise_have < noise_want + 0.5,
"{} {}",
noise_have,
noise_want
);
});
});
}
pub(crate) fn test_gglwe_automorphism_inplace<B: Backend>(
module: &Module<B>,
p0: i64,
p1: i64,
basek: usize,
digits: usize,
k_in: usize,
k_apply: usize,
sigma: f64,
rank: usize,
) where
Module<B>: AutomorphismTestModuleFamily<B>,
B: AutomorphismTestScratchFamily<B>,
{
let n: usize = TakeScalarZnx;
let digits_in: usize = 1;
let rows_in: usize = k_in / (basek * digits);
let rows_apply: usize = k_in.div_ceil(basek * digits);
let mut auto_key: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_in, rows_in, digits_in, rank);
let mut auto_key_apply: AutomorphismKey<Vec<u8>> = AutomorphismKey::alloc(n, basek, k_apply, rows_apply, digits, rank);
let mut source_xs: Source = Source::new([0u8; 32]);
let mut source_xe: Source = Source::new([0u8; 32]);
let mut source_xa: Source = Source::new([0u8; 32]);
let mut scratch: ScratchOwned<B> = ScratchOwned::alloc(
AutomorphismKey::encrypt_sk_scratch_space(module, n, basek, k_apply, rank)
| AutomorphismKey::automorphism_inplace_scratch_space(module, n, basek, k_in, k_apply, digits, rank),
);
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(TakeScalarZnx, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
// gglwe_{s1}(s0) = s0 -> s1
auto_key.encrypt_sk(
module,
p0,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
auto_key_apply.encrypt_sk(
module,
p1,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut auto_key_apply_exec: AutomorphismKeyExec<Vec<u8>, B> =
AutomorphismKeyExec::alloc(module, n, basek, k_apply, rows_apply, digits, rank);
auto_key_apply_exec.prepare(module, &auto_key_apply, scratch.borrow());
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
auto_key.automorphism_inplace(module, &auto_key_apply_exec, scratch.borrow());
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(TakeScalarZnx, basek, k_in);
let mut sk_auto: GLWESecret<Vec<u8>> = GLWESecret::alloc(TakeScalarZnx, rank);
sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
(0..rank).for_each(|i| {
module.vec_znx_automorphism(
module.galois_element_inv(p0 * p1),
&mut sk_auto.data.as_vec_znx_mut(),
i,
&sk.data.as_vec_znx(),
i,
);
});
let sk_auto_dft: GLWESecretExec<Vec<u8>, B> = GLWESecretExec::from(module, &sk_auto);
(0..auto_key.rank_in()).for_each(|col_i| {
(0..auto_key.rows()).for_each(|row_i| {
auto_key
.at(row_i, col_i)
.decrypt(module, &mut pt, &sk_auto_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk.data,
col_i,
);
let noise_have: f64 = module.vec_znx_std(basek, &pt.data, 0).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
TakeScalarZnx as f64,
basek * digits,
0.5,
0.5,
0f64,
sigma * sigma,
0f64,
rank as f64,
k_in,
k_apply,
);
assert!(
noise_have < noise_want + 0.5,
"{} {}",
noise_have,
noise_want
);
});
});
}

View File

@@ -0,0 +1,321 @@
use backend::hal::{
api::{ScratchAvailable, SvpPPolAlloc, SvpPrepare, TakeVecZnx, TakeVecZnxDft, VecZnxAddScalarInplace, ZnxView, ZnxViewMut},
layouts::{Backend, Data, DataMut, DataRef, Module, ReaderFrom, ScalarZnx, ScalarZnxToRef, Scratch, SvpPPol, WriterTo},
};
use sampling::source::Source;
use crate::{
Distribution, GGSWCiphertext, GGSWCiphertextExec, GGSWEncryptSkFamily, GGSWLayoutFamily, GLWESecretExec, Infos, LWESecret,
};
pub struct BlindRotationKeyCGGI<D: Data> {
pub(crate) keys: Vec<GGSWCiphertext<D>>,
pub(crate) dist: Distribution,
}
impl<D: Data> PartialEq for BlindRotationKeyCGGI<D> {
fn eq(&self, other: &Self) -> bool {
if self.keys.len() != other.keys.len() {
return false;
}
for (a, b) in self.keys.iter().zip(other.keys.iter()) {
if a != b {
return false;
}
}
self.dist == other.dist
}
}
impl<D: Data> Eq for BlindRotationKeyCGGI<D> {}
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
impl<D: DataMut> ReaderFrom for BlindRotationKeyCGGI<D> {
fn read_from<R: std::io::Read>(&mut self, reader: &mut R) -> std::io::Result<()> {
match Distribution::read_from(reader) {
Ok(dist) => self.dist = dist,
Err(e) => return Err(e),
}
let len: usize = reader.read_u64::<LittleEndian>()? as usize;
if self.keys.len() != len {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("self.keys.len()={} != read len={}", self.keys.len(), len),
));
}
for key in &mut self.keys {
key.read_from(reader)?;
}
Ok(())
}
}
impl<D: DataRef> WriterTo for BlindRotationKeyCGGI<D> {
fn write_to<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
match self.dist.write_to(writer) {
Ok(()) => {}
Err(e) => return Err(e),
}
writer.write_u64::<LittleEndian>(self.keys.len() as u64)?;
for key in &self.keys {
key.write_to(writer)?;
}
Ok(())
}
}
impl BlindRotationKeyCGGI<Vec<u8>> {
pub fn alloc(n_gglwe: usize, n_lwe: usize, basek: usize, k: usize, rows: usize, rank: usize) -> Self {
let mut data: Vec<GGSWCiphertext<Vec<u8>>> = Vec::with_capacity(n_lwe);
(0..n_lwe).for_each(|_| data.push(GGSWCiphertext::alloc(n_gglwe, basek, k, rows, 1, rank)));
Self {
keys: data,
dist: Distribution::NONE,
}
}
pub fn generate_from_sk_scratch_space<B: Backend>(module: &Module<B>, n: usize, basek: usize, k: usize, rank: usize) -> usize
where
Module<B>: GGSWEncryptSkFamily<B>,
{
GGSWCiphertext::encrypt_sk_scratch_space(module, n, basek, k, rank)
}
}
impl<D: DataRef> BlindRotationKeyCGGI<D> {
#[allow(dead_code)]
pub(crate) fn n(&self) -> usize {
self.keys[0].n()
}
#[allow(dead_code)]
pub(crate) fn rows(&self) -> usize {
self.keys[0].rows()
}
#[allow(dead_code)]
pub(crate) fn k(&self) -> usize {
self.keys[0].k()
}
#[allow(dead_code)]
pub(crate) fn size(&self) -> usize {
self.keys[0].size()
}
#[allow(dead_code)]
pub(crate) fn rank(&self) -> usize {
self.keys[0].rank()
}
pub(crate) fn basek(&self) -> usize {
self.keys[0].basek()
}
#[allow(dead_code)]
pub(crate) fn block_size(&self) -> usize {
match self.dist {
Distribution::BinaryBlock(value) => value,
_ => 1,
}
}
}
impl<D: DataMut> BlindRotationKeyCGGI<D> {
pub fn generate_from_sk<DataSkGLWE, DataSkLWE, B: Backend>(
&mut self,
module: &Module<B>,
sk_glwe: &GLWESecretExec<DataSkGLWE, B>,
sk_lwe: &LWESecret<DataSkLWE>,
source_xa: &mut Source,
source_xe: &mut Source,
sigma: f64,
scratch: &mut Scratch<B>,
) where
DataSkGLWE: DataRef,
DataSkLWE: DataRef,
Module<B>: GGSWEncryptSkFamily<B> + VecZnxAddScalarInplace,
Scratch<B>: TakeVecZnxDft<B> + ScratchAvailable + TakeVecZnx,
{
#[cfg(debug_assertions)]
{
assert_eq!(self.keys.len(), sk_lwe.n());
assert!(sk_glwe.n() <= TakeScalarZnx);
assert_eq!(sk_glwe.rank(), self.keys[0].rank());
match sk_lwe.dist {
Distribution::BinaryBlock(_)
| Distribution::BinaryFixed(_)
| Distribution::BinaryProb(_)
| Distribution::ZERO => {}
_ => panic!(
"invalid GLWESecret distribution: must be BinaryBlock, BinaryFixed or BinaryProb (or ZERO for debugging)"
),
}
}
self.dist = sk_lwe.dist;
let mut pt: ScalarZnx<Vec<u8>> = ScalarZnx::alloc(sk_glwe.n(), 1);
let sk_ref: ScalarZnx<&[u8]> = sk_lwe.data.to_ref();
self.keys.iter_mut().enumerate().for_each(|(i, ggsw)| {
pt.at_mut(0, 0)[0] = sk_ref.at(0, 0)[i];
ggsw.encrypt_sk(module, &pt, sk_glwe, source_xa, source_xe, sigma, scratch);
});
}
}
#[derive(PartialEq, Eq)]
pub struct BlindRotationKeyCGGIExec<D: Data, B: Backend> {
pub(crate) data: Vec<GGSWCiphertextExec<D, B>>,
pub(crate) dist: Distribution,
pub(crate) x_pow_a: Option<Vec<SvpPPol<Vec<u8>, B>>>,
}
impl<D: Data, B: Backend> BlindRotationKeyCGGIExec<D, B> {
#[allow(dead_code)]
pub(crate) fn n(&self) -> usize {
self.data[0].n()
}
#[allow(dead_code)]
pub(crate) fn rows(&self) -> usize {
self.data[0].rows()
}
#[allow(dead_code)]
pub(crate) fn k(&self) -> usize {
self.data[0].k()
}
#[allow(dead_code)]
pub(crate) fn size(&self) -> usize {
self.data[0].size()
}
#[allow(dead_code)]
pub(crate) fn rank(&self) -> usize {
self.data[0].rank()
}
pub(crate) fn basek(&self) -> usize {
self.data[0].basek()
}
pub(crate) fn block_size(&self) -> usize {
match self.dist {
Distribution::BinaryBlock(value) => value,
_ => 1,
}
}
}
pub trait BlindRotationKeyCGGIExecLayoutFamily<B: Backend> = GGSWLayoutFamily<B> + SvpPPolAlloc<B> + SvpPrepare<B>;
impl<B: Backend> BlindRotationKeyCGGIExec<Vec<u8>, B> {
pub fn alloc(module: &Module<B>, n_glwe: usize, n_lwe: usize, basek: usize, k: usize, rows: usize, rank: usize) -> Self
where
Module<B>: BlindRotationKeyCGGIExecLayoutFamily<B>,
{
let mut data: Vec<GGSWCiphertextExec<Vec<u8>, B>> = Vec::with_capacity(n_lwe);
(0..n_lwe).for_each(|_| {
data.push(GGSWCiphertextExec::alloc(
module, n_glwe, basek, k, rows, 1, rank,
))
});
Self {
data,
dist: Distribution::NONE,
x_pow_a: None,
}
}
pub fn from<DataOther>(module: &Module<B>, other: &BlindRotationKeyCGGI<DataOther>, scratch: &mut Scratch<B>) -> Self
where
DataOther: DataRef,
Module<B>: BlindRotationKeyCGGIExecLayoutFamily<B>,
{
let mut brk: BlindRotationKeyCGGIExec<Vec<u8>, B> = Self::alloc(
module,
other.n(),
other.keys.len(),
other.basek(),
other.k(),
other.rows(),
other.rank(),
);
brk.prepare(module, other, scratch);
brk
}
}
impl<D: DataMut, B: Backend> BlindRotationKeyCGGIExec<D, B> {
pub fn prepare<DataOther>(&mut self, module: &Module<B>, other: &BlindRotationKeyCGGI<DataOther>, scratch: &mut Scratch<B>)
where
DataOther: DataRef,
Module<B>: BlindRotationKeyCGGIExecLayoutFamily<B>,
{
#[cfg(debug_assertions)]
{
assert_eq!(self.data.len(), other.keys.len());
}
let n: usize = other.n();
self.data
.iter_mut()
.zip(other.keys.iter())
.for_each(|(ggsw_exec, other)| {
ggsw_exec.prepare(module, other, scratch);
});
self.dist = other.dist;
match other.dist {
Distribution::BinaryBlock(_) => {
let mut x_pow_a: Vec<SvpPPol<Vec<u8>, B>> = Vec::with_capacity(n << 1);
let mut buf: ScalarZnx<Vec<u8>> = ScalarZnx::alloc(n, 1);
(0..n << 1).for_each(|i| {
let mut res: SvpPPol<Vec<u8>, B> = module.svp_ppol_alloc(n, 1);
set_xai_plus_y(module, i, 0, &mut res, &mut buf);
x_pow_a.push(res);
});
self.x_pow_a = Some(x_pow_a);
}
_ => {}
}
}
}
pub fn set_xai_plus_y<A, C, B: Backend>(module: &Module<B>, ai: usize, y: i64, res: &mut SvpPPol<A, B>, buf: &mut ScalarZnx<C>)
where
A: DataMut,
C: DataMut,
Module<B>: SvpPrepare<B>,
{
let n: usize = res.n();
{
let raw: &mut [i64] = buf.at_mut(0, 0);
if ai < n {
raw[ai] = 1;
} else {
raw[(ai - n) & (n - 1)] = -1;
}
raw[0] += y;
}
module.svp_prepare(res, 0, buf, 0);
{
let raw: &mut [i64] = buf.at_mut(0, 0);
if ai < n {
raw[ai] = 0;
} else {
raw[(ai - n) & (n - 1)] = 0;
}
raw[0] = 0;
}
}

View File

@@ -0,0 +1,4 @@
mod cpu_spqlios;
mod generic_cggi;
mod generic_lut;
mod generic_serialization;