mirror of
https://github.com/arnaucube/poulpy.git
synced 2026-02-10 05:06:44 +01:00
wip on BR + added enc/dec for LWE
This commit is contained in:
@@ -26,7 +26,7 @@ fn bench_external_product_glwe_fft64(c: &mut Criterion) {
|
||||
let rank: usize = p.rank;
|
||||
let digits: usize = 1;
|
||||
|
||||
let rows: usize = (p.k_ct_in + p.basek - 1) / p.basek;
|
||||
let rows: usize = 1; //(p.k_ct_in + p.basek - 1) / p.basek;
|
||||
let sigma: f64 = 3.2;
|
||||
|
||||
let mut ct_ggsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank);
|
||||
@@ -81,11 +81,11 @@ fn bench_external_product_glwe_fft64(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
let params_set: Vec<Params> = vec![Params {
|
||||
log_n: 10,
|
||||
basek: 7,
|
||||
k_ct_in: 27,
|
||||
k_ct_out: 27,
|
||||
k_ggsw: 27,
|
||||
log_n: 11,
|
||||
basek: 22,
|
||||
k_ct_in: 44,
|
||||
k_ct_out: 44,
|
||||
k_ggsw: 54,
|
||||
rank: 1,
|
||||
}];
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use backend::{MatZnxDftOps, MatZnxDftScratch, Module, ScalarZnxDftOps, Scratch, VecZnxDftOps, VecZnxOps, ZnxView, ZnxViewMut, ZnxZero, FFT64};
|
||||
use itertools::izip;
|
||||
|
||||
use crate::{
|
||||
GGSWCiphertext, GLWECiphertext, GLWECiphertextToMut, GLWECiphertextToRef, GLWEPlaintext, Infos, LWECiphertext,
|
||||
ScratchCore, blind_rotation::key::BlindRotationKeyCGGI, lwe::ciphertext::LWECiphertextToRef,
|
||||
};
|
||||
|
||||
pub fn cggi_blind_rotate_scratch_space(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k_lut: usize,
|
||||
k_brk: usize,
|
||||
rows: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
let size = k_brk.div_ceil(basek);
|
||||
GGSWCiphertext::<Vec<u8>, FFT64>::bytes_of(module, basek, k_brk, rows, 1, rank)
|
||||
+ (module.mat_znx_dft_mul_x_pow_minus_one_scratch_space(size, rank + 1)
|
||||
| GLWECiphertext::external_product_inplace_scratch_space(module, basek, k_lut, k_brk, 1, rank))
|
||||
}
|
||||
|
||||
pub fn cggi_blind_rotate<DataRes, DataIn, DataLUT>(
|
||||
module: &Module<FFT64>,
|
||||
res: &mut GLWECiphertext<DataRes>,
|
||||
lwe: &LWECiphertext<DataIn>,
|
||||
lut: &GLWEPlaintext<DataLUT>,
|
||||
brk: &BlindRotationKeyCGGI<FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) where
|
||||
DataRes: AsRef<[u8]> + AsMut<[u8]>,
|
||||
DataIn: AsRef<[u8]>,
|
||||
DataLUT: AsRef<[u8]>,
|
||||
{
|
||||
|
||||
println!("{}", lwe.n());
|
||||
|
||||
let mut lwe_2n: Vec<i64> = vec![0i64; lwe.n() + 1]; // TODO: from scratch space
|
||||
let mut out_mut: GLWECiphertext<&mut [u8]> = res.to_mut();
|
||||
let lwe_ref: LWECiphertext<&[u8]> = lwe.to_ref();
|
||||
let lut_ref: GLWECiphertext<&[u8]> = lut.to_ref();
|
||||
|
||||
let cols = out_mut.rank()+1;
|
||||
|
||||
mod_switch_2n(module, &mut lwe_2n, &lwe_ref);
|
||||
|
||||
let a: &[i64] = &lwe_2n[1..];
|
||||
let b: i64 = lwe_2n[0];
|
||||
|
||||
out_mut.data.zero();
|
||||
|
||||
// Initialize out to X^{b} * LUT(X)
|
||||
module.vec_znx_rotate(b, &mut out_mut.data, 0, &lut_ref.data, 0);
|
||||
|
||||
let block_size: usize = brk.block_size();
|
||||
|
||||
// ACC + [sum DFT(X^ai -1) * (DFT(ACC) x BRKi)]
|
||||
|
||||
let (mut acc_dft, scratch1) = scratch.tmp_glwe_fourier(module, brk.basek(), out_mut.k(), out_mut.rank());
|
||||
let (mut acc_add_dft, scratch2) = scratch1.tmp_glwe_fourier(module, brk.basek(), out_mut.k(), out_mut.rank());
|
||||
let (mut vmp_res, scratch3) = scratch2.tmp_vec_znx_dft(module, acc_dft.rank()+1, acc_dft.size());
|
||||
let (mut xai_minus_one, scratch4) = scratch3.tmp_scalar_znx(module, 1);
|
||||
let (mut xai_minus_one_dft, scratch5) = scratch4.tmp_scalar_znx_dft(module, 1);
|
||||
|
||||
let start: Instant = Instant::now();
|
||||
izip!(
|
||||
a.chunks_exact(block_size),
|
||||
brk.data.chunks_exact(block_size)
|
||||
)
|
||||
.for_each(|(ai, ski)| {
|
||||
|
||||
out_mut.dft(module, &mut acc_dft);
|
||||
acc_add_dft.data.zero();
|
||||
|
||||
izip!(ai.iter(), ski.iter())
|
||||
.enumerate()
|
||||
.for_each(|(i, (aii, skii))| {
|
||||
|
||||
// vmp_res = DFT(acc) * BRK[i]
|
||||
module.vmp_apply(&mut vmp_res, &acc_dft.data, &skii.data, scratch5);
|
||||
|
||||
// DFT(X^ai -1)
|
||||
xai_minus_one.zero();
|
||||
xai_minus_one.at_mut(0, 0)[0] = 1;
|
||||
module.vec_znx_rotate_inplace(*aii, &mut xai_minus_one, 0);
|
||||
xai_minus_one.at_mut(0, 0)[0] -= 1;
|
||||
module.svp_prepare(&mut xai_minus_one_dft, 0, &xai_minus_one, 0);
|
||||
|
||||
// DFT(X^ai -1) * (DFT(acc) * BRK[i])
|
||||
(0..cols).for_each(|i|{
|
||||
module.svp_apply_inplace(&mut vmp_res, i, &xai_minus_one_dft, 0);
|
||||
module.vec_znx_dft_add_inplace(&mut acc_add_dft.data, i, &vmp_res, i);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
acc_add_dft.idft(module, &mut out_mut, scratch5);
|
||||
});
|
||||
let duration: std::time::Duration = start.elapsed();
|
||||
println!("external products: {} us", duration.as_micros());
|
||||
}
|
||||
|
||||
fn mod_switch_2n(module: &Module<FFT64>, res: &mut [i64], lwe: &LWECiphertext<&[u8]>) {
|
||||
let basek: usize = lwe.basek();
|
||||
|
||||
let log2n: usize = module.log_n() + 1;
|
||||
|
||||
res.copy_from_slice(&lwe.data.at(0, 0));
|
||||
|
||||
if basek > log2n {
|
||||
let diff: usize = basek - log2n;
|
||||
res.iter_mut().for_each(|x| {
|
||||
*x = div_signed_by_pow2(x, diff);
|
||||
})
|
||||
} else {
|
||||
let rem: usize = basek - (log2n % basek);
|
||||
let size: usize = log2n.div_ceil(basek);
|
||||
(1..size).for_each(|i| {
|
||||
if i == size - 1 && rem != basek {
|
||||
let k_rem: usize = basek - rem;
|
||||
izip!(lwe.data.at(0, i).iter(), res.iter_mut()).for_each(|(x, y)| {
|
||||
*y = (*y << k_rem) + (x >> rem);
|
||||
});
|
||||
} else {
|
||||
izip!(lwe.data.at(0, i).iter(), res.iter_mut()).for_each(|(x, y)| {
|
||||
*y = (*y << basek) + x;
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn div_signed_by_pow2(x: &i64, k: usize) -> i64 {
|
||||
let bias: i64 = (1 << k) - 1;
|
||||
(x + ((x >> 63) & bias)) >> k
|
||||
}
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
use backend::{Backend, FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxToRef, Scratch, ZnxView, ZnxViewMut};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{Distribution, FourierGLWESecret, GGSWCiphertext, GLWEAutomorphismKey, LWESecret};
|
||||
use crate::{Distribution, FourierGLWESecret, GGSWCiphertext, Infos, LWESecret};
|
||||
|
||||
pub struct BlindRotationKeyCGGI<B: Backend> {
|
||||
pub(crate) data: Vec<GGSWCiphertext<Vec<u8>, B>>,
|
||||
pub(crate) dist: Distribution,
|
||||
}
|
||||
|
||||
pub struct BlindRotationKeyFHEW<B: Backend> {
|
||||
pub(crate) data: Vec<GGSWCiphertext<Vec<u8>, B>>,
|
||||
pub(crate) auto: Vec<GLWEAutomorphismKey<Vec<u8>, B>>,
|
||||
}
|
||||
// pub struct BlindRotationKeyFHEW<B: Backend> {
|
||||
// pub(crate) data: Vec<GGSWCiphertext<Vec<u8>, B>>,
|
||||
// pub(crate) auto: Vec<GLWEAutomorphismKey<Vec<u8>, B>>,
|
||||
//}
|
||||
|
||||
impl BlindRotationKeyCGGI<FFT64> {
|
||||
pub fn allocate(module: &Module<FFT64>, lwe_degree: usize, basek: usize, k: usize, rows: usize, rank: usize) -> Self {
|
||||
let mut data: Vec<GGSWCiphertext<Vec<u8>, FFT64>> = Vec::with_capacity(lwe_degree);
|
||||
(0..lwe_degree).for_each(|_| data.push(GGSWCiphertext::alloc(module, basek, k, rows, 1, rank)));
|
||||
pub fn allocate(module: &Module<FFT64>, n_lwe: usize, basek: usize, k: usize, rows: usize, rank: usize) -> Self {
|
||||
let mut data: Vec<GGSWCiphertext<Vec<u8>, FFT64>> = Vec::with_capacity(n_lwe);
|
||||
(0..n_lwe).for_each(|_| data.push(GGSWCiphertext::alloc(module, basek, k, rows, 1, rank)));
|
||||
Self {
|
||||
data,
|
||||
dist: Distribution::NONE,
|
||||
@@ -61,4 +61,27 @@ impl BlindRotationKeyCGGI<FFT64> {
|
||||
ggsw.encrypt_sk(module, &pt, sk_glwe, source_xa, source_xe, sigma, scratch);
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn block_size(&self) -> usize {
|
||||
match self.dist {
|
||||
Distribution::BinaryBlock(value) => value,
|
||||
_ => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rows(&self) -> usize {
|
||||
self.data[0].rows()
|
||||
}
|
||||
|
||||
pub(crate) fn k(&self) -> usize {
|
||||
self.data[0].k()
|
||||
}
|
||||
|
||||
pub(crate) fn rank(&self) -> usize {
|
||||
self.data[0].rank()
|
||||
}
|
||||
|
||||
pub(crate) fn basek(&self) -> usize {
|
||||
self.data[0].basek()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,6 @@
|
||||
// pub mod cggi;
|
||||
pub mod ccgi;
|
||||
pub mod key;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_fft64;
|
||||
|
||||
85
core/src/blind_rotation/test_fft64/cggi.rs
Normal file
85
core/src/blind_rotation/test_fft64/cggi.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use core::time;
|
||||
use std::time::Instant;
|
||||
|
||||
use backend::{Encoding, Module, ScratchOwned, FFT64};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
blind_rotation::{ccgi::{cggi_blind_rotate, cggi_blind_rotate_scratch_space}, key::BlindRotationKeyCGGI}, lwe::LWEPlaintext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWESecret, LWECiphertext, LWESecret
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn blind_rotation() {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(2048);
|
||||
let basek: usize = 17;
|
||||
|
||||
let n_lwe: usize = 1071;
|
||||
|
||||
let k_lwe: usize = 22;
|
||||
let k_brk: usize = 54;
|
||||
let rows_brk: usize = 1;
|
||||
let k_lut: usize = 44;
|
||||
let rank: usize = 1;
|
||||
let block_size: usize = 7;
|
||||
|
||||
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 sk_glwe: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
|
||||
sk_glwe.fill_ternary_prob(0.5, &mut source_xs);
|
||||
let sk_glwe_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::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 = ScratchOwned::new(BlindRotationKeyCGGI::generate_from_sk_scratch_space(
|
||||
&module, basek, k_brk, rank,
|
||||
) | cggi_blind_rotate_scratch_space(&module, basek, k_lut, k_brk, rows_brk, rank));
|
||||
|
||||
let start: Instant = Instant::now();
|
||||
let mut brk: BlindRotationKeyCGGI<FFT64> = BlindRotationKeyCGGI::allocate(&module, 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 duration: std::time::Duration = start.elapsed();
|
||||
println!("brk-gen: {} ms", duration.as_millis());
|
||||
|
||||
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);
|
||||
|
||||
pt_lwe.data.encode_coeff_i64(0, basek, 7, 0, 63, 7);
|
||||
|
||||
println!("{}", pt_lwe.data);
|
||||
|
||||
lwe.encrypt_sk(&pt_lwe, &sk_lwe, &mut source_xa, &mut source_xe, 3.2);
|
||||
|
||||
lwe.decrypt(&mut pt_lwe, &sk_lwe);
|
||||
|
||||
println!("{}", pt_lwe.data);
|
||||
|
||||
let lut: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_lut);
|
||||
|
||||
let mut res: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_lut, rank);
|
||||
|
||||
let start: Instant = Instant::now();
|
||||
(0..32).for_each(|i|{
|
||||
cggi_blind_rotate(&module, &mut res, &lwe, &lut, &brk, scratch.borrow());
|
||||
});
|
||||
|
||||
let duration: std::time::Duration = start.elapsed();
|
||||
println!("blind-rotate: {} ms", duration.as_millis());
|
||||
|
||||
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_lut);
|
||||
|
||||
res.decrypt(&module , &mut pt, &sk_glwe_dft, scratch.borrow());
|
||||
|
||||
println!("{}", pt.data);
|
||||
}
|
||||
1
core/src/blind_rotation/test_fft64/mod.rs
Normal file
1
core/src/blind_rotation/test_fft64/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod cggi;
|
||||
@@ -1,6 +1,6 @@
|
||||
use backend::{Backend, Module, ZnxInfos};
|
||||
|
||||
use crate::{FourierGLWECiphertext, div_ceil};
|
||||
use crate::FourierGLWECiphertext;
|
||||
|
||||
pub trait Infos {
|
||||
type Inner: ZnxInfos;
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
use backend::{
|
||||
Backend, FFT64, MatZnxDftOps, MatZnxDftScratch, Module, ScalarZnxDftOps, Scratch, VecZnxAlloc, VecZnxBig, VecZnxBigAlloc,
|
||||
VecZnxBigOps, VecZnxBigScratch, VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, ZnxZero,
|
||||
};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{GGSWCiphertext, GLWECiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, Infos, ScratchCore};
|
||||
|
||||
pub struct FourierGLWECiphertext<C, B: Backend> {
|
||||
pub data: VecZnxDft<C, B>,
|
||||
pub basek: usize,
|
||||
pub k: usize,
|
||||
}
|
||||
|
||||
impl<B: Backend> FourierGLWECiphertext<Vec<u8>, B> {
|
||||
pub fn alloc(module: &Module<B>, basek: usize, k: usize, rank: usize) -> Self {
|
||||
Self {
|
||||
data: module.new_vec_znx_dft(rank + 1, k.div_ceil(basek)),
|
||||
basek: basek,
|
||||
k: k,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_of(module: &Module<B>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
module.bytes_of_vec_znx_dft(rank + 1, k.div_ceil(basek))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B: Backend> Infos for FourierGLWECiphertext<T, B> {
|
||||
type Inner = VecZnxDft<T, B>;
|
||||
|
||||
fn inner(&self) -> &Self::Inner {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn basek(&self) -> usize {
|
||||
self.basek
|
||||
}
|
||||
|
||||
fn k(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B: Backend> FourierGLWECiphertext<T, B> {
|
||||
pub fn rank(&self) -> usize {
|
||||
self.cols() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl FourierGLWECiphertext<Vec<u8>, FFT64> {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn idft_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
module.bytes_of_vec_znx(1, k.div_ceil(basek))
|
||||
+ (module.vec_znx_big_normalize_tmp_bytes() | module.vec_znx_idft_tmp_bytes())
|
||||
}
|
||||
|
||||
pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
module.bytes_of_vec_znx(rank + 1, k.div_ceil(basek)) + GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
|
||||
}
|
||||
|
||||
pub fn decrypt_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = k.div_ceil(basek);
|
||||
(module.vec_znx_big_normalize_tmp_bytes()
|
||||
| module.bytes_of_vec_znx_dft(1, size)
|
||||
| (module.bytes_of_vec_znx_big(1, size) + module.vec_znx_idft_tmp_bytes()))
|
||||
+ module.bytes_of_vec_znx_big(1, size)
|
||||
}
|
||||
|
||||
pub fn keyswitch_scratch_space(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k_out: usize,
|
||||
k_in: usize,
|
||||
k_ksk: usize,
|
||||
digits: usize,
|
||||
rank_in: usize,
|
||||
rank_out: usize,
|
||||
) -> usize {
|
||||
GLWECiphertext::bytes_of(module, basek, k_out, rank_out)
|
||||
+ GLWECiphertext::keyswitch_from_fourier_scratch_space(module, basek, k_out, k_in, k_ksk, digits, rank_in, rank_out)
|
||||
}
|
||||
|
||||
pub fn keyswitch_inplace_scratch_space(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k_out: usize,
|
||||
k_ksk: usize,
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
Self::keyswitch_scratch_space(module, basek, k_out, k_out, k_ksk, digits, rank, rank)
|
||||
}
|
||||
|
||||
// WARNING TODO: UPDATE
|
||||
pub fn external_product_scratch_space(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
_k_out: usize,
|
||||
k_in: usize,
|
||||
k_ggsw: usize,
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
let ggsw_size: usize = k_ggsw.div_ceil(basek);
|
||||
let res_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, ggsw_size);
|
||||
let in_size: usize = k_in.div_ceil(basek).div_ceil(digits);
|
||||
let ggsw_size: usize = k_ggsw.div_ceil(basek);
|
||||
let vmp: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size)
|
||||
+ module.vmp_apply_tmp_bytes(ggsw_size, in_size, in_size, rank + 1, rank + 1, ggsw_size);
|
||||
let res_small: usize = module.bytes_of_vec_znx(rank + 1, ggsw_size);
|
||||
let normalize: usize = module.vec_znx_big_normalize_tmp_bytes();
|
||||
res_dft + (vmp | (res_small + normalize))
|
||||
}
|
||||
|
||||
pub fn external_product_inplace_scratch_space(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k_out: usize,
|
||||
k_ggsw: usize,
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
Self::external_product_scratch_space(module, basek, k_out, k_out, k_ggsw, digits, rank)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> FourierGLWECiphertext<DataSelf, FFT64> {
|
||||
pub fn encrypt_zero_sk<DataSk: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
sk: &GLWESecret<DataSk, FFT64>,
|
||||
source_xa: &mut Source,
|
||||
source_xe: &mut Source,
|
||||
sigma: f64,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
let (mut tmp_ct, scratch1) = scratch.tmp_glwe_ct(module, self.basek(), self.k(), self.rank());
|
||||
tmp_ct.encrypt_zero_sk(module, sk, source_xa, source_xe, sigma, scratch1);
|
||||
tmp_ct.dft(module, self);
|
||||
}
|
||||
|
||||
pub fn keyswitch<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
lhs: &FourierGLWECiphertext<DataLhs, FFT64>,
|
||||
rhs: &GLWESwitchingKey<DataRhs, FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
let (mut tmp_ct, scratch1) = scratch.tmp_glwe_ct(module, self.basek(), self.k(), self.rank());
|
||||
tmp_ct.keyswitch_from_fourier(module, lhs, rhs, scratch1);
|
||||
tmp_ct.dft(module, self);
|
||||
}
|
||||
|
||||
pub fn keyswitch_inplace<DataRhs: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
rhs: &GLWESwitchingKey<DataRhs, FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
unsafe {
|
||||
let self_ptr: *mut FourierGLWECiphertext<DataSelf, FFT64> = self as *mut FourierGLWECiphertext<DataSelf, FFT64>;
|
||||
self.keyswitch(&module, &*self_ptr, rhs, scratch);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn external_product<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
lhs: &FourierGLWECiphertext<DataLhs, FFT64>,
|
||||
rhs: &GGSWCiphertext<DataRhs, FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
let basek: usize = self.basek();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_eq!(rhs.rank(), lhs.rank());
|
||||
assert_eq!(rhs.rank(), self.rank());
|
||||
assert_eq!(self.basek(), basek);
|
||||
assert_eq!(lhs.basek(), basek);
|
||||
assert_eq!(rhs.n(), module.n());
|
||||
assert_eq!(self.n(), module.n());
|
||||
assert_eq!(lhs.n(), module.n());
|
||||
assert!(
|
||||
scratch.available()
|
||||
>= FourierGLWECiphertext::external_product_scratch_space(
|
||||
module,
|
||||
self.basek(),
|
||||
self.k(),
|
||||
lhs.k(),
|
||||
rhs.k(),
|
||||
rhs.digits(),
|
||||
rhs.rank(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let cols: usize = rhs.rank() + 1;
|
||||
let digits = rhs.digits();
|
||||
|
||||
// Space for VMP result in DFT domain and high precision
|
||||
let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols, rhs.size());
|
||||
let (mut a_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols, (lhs.size() + digits - 1) / digits);
|
||||
|
||||
{
|
||||
(0..digits).for_each(|di| {
|
||||
a_dft.set_size((lhs.size() + di) / digits);
|
||||
|
||||
// Small optimization for digits > 2
|
||||
// VMP produce some error e, and since we aggregate vmp * 2^{di * B}, then
|
||||
// we also aggregate ei * 2^{di * B}, with the largest error being ei * 2^{(digits-1) * B}.
|
||||
// As such we can ignore the last digits-2 limbs safely of the sum of vmp products.
|
||||
// It is possible to further ignore the last digits-1 limbs, but this introduce
|
||||
// ~0.5 to 1 bit of additional noise, and thus not chosen here to ensure that the same
|
||||
// noise is kept with respect to the ideal functionality.
|
||||
res_dft.set_size(rhs.size() - ((digits - di) as isize - 2).max(0) as usize);
|
||||
|
||||
(0..cols).for_each(|col_i| {
|
||||
module.vec_znx_dft_copy(digits, digits - 1 - di, &mut a_dft, col_i, &lhs.data, col_i);
|
||||
});
|
||||
|
||||
if di == 0 {
|
||||
module.vmp_apply(&mut res_dft, &a_dft, &rhs.data, scratch2);
|
||||
} else {
|
||||
module.vmp_apply_add(&mut res_dft, &a_dft, &rhs.data, di, scratch2);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// VMP result in high precision
|
||||
let res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume::<&mut [u8]>(res_dft);
|
||||
|
||||
// Space for VMP result normalized
|
||||
let (mut res_small, scratch2) = scratch1.tmp_vec_znx(module, cols, rhs.size());
|
||||
(0..cols).for_each(|i| {
|
||||
module.vec_znx_big_normalize(basek, &mut res_small, i, &res_big, i, scratch2);
|
||||
module.vec_znx_dft(1, 0, &mut self.data, i, &res_small, i);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn external_product_inplace<DataRhs: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
rhs: &GGSWCiphertext<DataRhs, FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
unsafe {
|
||||
let self_ptr: *mut FourierGLWECiphertext<DataSelf, FFT64> = self as *mut FourierGLWECiphertext<DataSelf, FFT64>;
|
||||
self.external_product(&module, &*self_ptr, rhs, scratch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsRef<[u8]>> FourierGLWECiphertext<DataSelf, FFT64> {
|
||||
pub fn decrypt<DataPt: AsRef<[u8]> + AsMut<[u8]>, DataSk: AsRef<[u8]>>(
|
||||
&self,
|
||||
module: &Module<FFT64>,
|
||||
pt: &mut GLWEPlaintext<DataPt>,
|
||||
sk: &GLWESecret<DataSk, FFT64>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_eq!(self.rank(), sk.rank());
|
||||
assert_eq!(self.n(), module.n());
|
||||
assert_eq!(pt.n(), module.n());
|
||||
assert_eq!(sk.n(), module.n());
|
||||
}
|
||||
|
||||
let cols = self.rank() + 1;
|
||||
|
||||
let (mut pt_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, self.size()); // TODO optimize size when pt << ct
|
||||
pt_big.zero();
|
||||
|
||||
{
|
||||
(1..cols).for_each(|i| {
|
||||
let (mut ci_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, self.size()); // TODO optimize size when pt << ct
|
||||
module.svp_apply(&mut ci_dft, 0, &sk.data_fourier, i - 1, &self.data, i);
|
||||
let ci_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(ci_dft);
|
||||
module.vec_znx_big_add_inplace(&mut pt_big, 0, &ci_big, 0);
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let (mut c0_big, scratch_2) = scratch_1.tmp_vec_znx_big(module, 1, self.size());
|
||||
// c0_big = (a * s) + (-a * s + m + e) = BIG(m + e)
|
||||
module.vec_znx_idft(&mut c0_big, 0, &self.data, 0, scratch_2);
|
||||
module.vec_znx_big_add_inplace(&mut pt_big, 0, &c0_big, 0);
|
||||
}
|
||||
|
||||
// pt = norm(BIG(m + e))
|
||||
module.vec_znx_big_normalize(self.basek(), &mut pt.data, 0, &mut pt_big, 0, scratch_1);
|
||||
|
||||
pt.basek = self.basek();
|
||||
pt.k = pt.k().min(self.k());
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn idft<DataRes: AsRef<[u8]> + AsMut<[u8]>>(
|
||||
&self,
|
||||
module: &Module<FFT64>,
|
||||
res: &mut GLWECiphertext<DataRes>,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_eq!(self.rank(), res.rank());
|
||||
assert_eq!(self.basek(), res.basek())
|
||||
}
|
||||
|
||||
let min_size: usize = self.size().min(res.size());
|
||||
|
||||
let (mut res_big, scratch1) = scratch.tmp_vec_znx_big(module, 1, min_size);
|
||||
|
||||
(0..self.rank() + 1).for_each(|i| {
|
||||
module.vec_znx_idft(&mut res_big, 0, &self.data, i, scratch1);
|
||||
module.vec_znx_big_normalize(self.basek(), &mut res.data, i, &res_big, 0, scratch1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use backend::{Backend, Module, VecZnxDft, VecZnxDftAlloc};
|
||||
|
||||
use crate::{Infos, div_ceil};
|
||||
use crate::Infos;
|
||||
|
||||
pub struct FourierGLWECiphertext<C, B: Backend> {
|
||||
pub data: VecZnxDft<C, B>,
|
||||
@@ -11,14 +11,14 @@ pub struct FourierGLWECiphertext<C, B: Backend> {
|
||||
impl<B: Backend> FourierGLWECiphertext<Vec<u8>, B> {
|
||||
pub fn alloc(module: &Module<B>, basek: usize, k: usize, rank: usize) -> Self {
|
||||
Self {
|
||||
data: module.new_vec_znx_dft(rank + 1, div_ceil(k, basek)),
|
||||
data: module.new_vec_znx_dft(rank + 1, k.div_ceil(basek)),
|
||||
basek: basek,
|
||||
k: k,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_of(module: &Module<B>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
module.bytes_of_vec_znx_dft(rank + 1, div_ceil(k, basek))
|
||||
module.bytes_of_vec_znx_dft(rank + 1, k.div_ceil(basek))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ use backend::{
|
||||
VecZnxDftOps, ZnxZero,
|
||||
};
|
||||
|
||||
use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, Infos, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, Infos};
|
||||
|
||||
impl FourierGLWECiphertext<Vec<u8>, FFT64> {
|
||||
pub fn decrypt_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
let size: usize = k.div_ceil(basek);
|
||||
(module.vec_znx_big_normalize_tmp_bytes()
|
||||
| module.bytes_of_vec_znx_dft(1, size)
|
||||
| (module.bytes_of_vec_znx_big(1, size) + module.vec_znx_idft_tmp_bytes()))
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use backend::{FFT64, Module, Scratch, VecZnxAlloc, VecZnxBigScratch, VecZnxDftOps};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, Infos, ScratchCore, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, Infos, ScratchCore};
|
||||
|
||||
impl FourierGLWECiphertext<Vec<u8>, FFT64> {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn idft_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
module.bytes_of_vec_znx(1, div_ceil(k, basek))
|
||||
module.bytes_of_vec_znx(1, k.div_ceil(basek))
|
||||
+ (module.vec_znx_big_normalize_tmp_bytes() | module.vec_znx_idft_tmp_bytes())
|
||||
}
|
||||
|
||||
pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
module.bytes_of_vec_znx(rank + 1, div_ceil(k, basek)) + GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
|
||||
module.bytes_of_vec_znx(rank + 1, k.div_ceil(basek)) + GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use backend::{
|
||||
VecZnxDftAlloc, VecZnxDftOps,
|
||||
};
|
||||
|
||||
use crate::{FourierGLWECiphertext, GGSWCiphertext, Infos, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, GGSWCiphertext, Infos};
|
||||
|
||||
impl FourierGLWECiphertext<Vec<u8>, FFT64> {
|
||||
// WARNING TODO: UPDATE
|
||||
@@ -16,10 +16,10 @@ impl FourierGLWECiphertext<Vec<u8>, FFT64> {
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
let ggsw_size: usize = div_ceil(k_ggsw, basek);
|
||||
let ggsw_size: usize = k_ggsw.div_ceil(basek);
|
||||
let res_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, ggsw_size);
|
||||
let in_size: usize = div_ceil(div_ceil(k_in, basek), digits);
|
||||
let ggsw_size: usize = div_ceil(k_ggsw, basek);
|
||||
let in_size: usize = k_in.div_ceil(basek).div_ceil(digits);
|
||||
let ggsw_size: usize = k_ggsw.div_ceil(basek);
|
||||
let vmp: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size)
|
||||
+ module.vmp_apply_tmp_bytes(ggsw_size, in_size, in_size, rank + 1, rank + 1, ggsw_size);
|
||||
let res_small: usize = module.bytes_of_vec_znx(rank + 1, ggsw_size);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GGSWCiphertext, GLWECiphertext, GLWEOps, GLWEPlaintext, GLWESecret, Infos,
|
||||
div_ceil, noise::noise_ggsw_product,
|
||||
noise::noise_ggsw_product,
|
||||
};
|
||||
use backend::{FFT64, FillUniform, Module, ScalarZnx, ScalarZnxAlloc, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
|
||||
use sampling::source::Source;
|
||||
@@ -10,7 +10,7 @@ fn apply() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_in: usize = 45;
|
||||
let digits: usize = div_ceil(k_in, basek);
|
||||
let digits: usize = k_in.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ggsw: usize = k_in + basek * di;
|
||||
@@ -26,7 +26,7 @@ fn apply_inplace() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_ct: usize = 60;
|
||||
let digits: usize = div_ceil(k_ct, basek);
|
||||
let digits: usize = k_ct.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ggsw: usize = k_ct + basek * di;
|
||||
@@ -39,7 +39,7 @@ fn apply_inplace() {
|
||||
fn test_apply(log_n: usize, basek: usize, k_out: usize, k_in: usize, k_ggsw: usize, digits: usize, rank: usize, sigma: f64) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_in, digits * basek);
|
||||
let rows: usize = k_in.div_ceil(digits * basek);
|
||||
|
||||
let mut ct_ggsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank);
|
||||
let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_in, rank);
|
||||
@@ -147,7 +147,7 @@ fn test_apply(log_n: usize, basek: usize, k_out: usize, k_in: usize, k_ggsw: usi
|
||||
|
||||
fn test_apply_inplace(log_n: usize, basek: usize, k_ct: usize, k_ggsw: usize, digits: usize, rank: usize, sigma: f64) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
let rows: usize = div_ceil(k_ct, digits * basek);
|
||||
let rows: usize = k_ct.div_ceil(digits * basek);
|
||||
|
||||
let mut ct_ggsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank);
|
||||
let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, Infos, div_ceil,
|
||||
FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, Infos,
|
||||
noise::log2_std_noise_gglwe_product,
|
||||
};
|
||||
use backend::{FFT64, FillUniform, Module, ScratchOwned, Stats, VecZnxOps};
|
||||
@@ -10,7 +10,7 @@ fn apply() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_in: usize = 45;
|
||||
let digits: usize = div_ceil(k_in, basek);
|
||||
let digits: usize = k_in.div_ceil(basek);
|
||||
(1..4).for_each(|rank_in| {
|
||||
(1..4).for_each(|rank_out| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
@@ -31,7 +31,7 @@ fn apply_inplace() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_ct: usize = 45;
|
||||
let digits: usize = div_ceil(k_ct, basek);
|
||||
let digits: usize = k_ct.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ksk: usize = k_ct + basek * di;
|
||||
@@ -54,7 +54,7 @@ fn test_apply(
|
||||
) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_in, basek * digits);
|
||||
let rows: usize = k_in.div_ceil(basek * digits);
|
||||
|
||||
let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> =
|
||||
GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_in, rank_out);
|
||||
@@ -152,7 +152,7 @@ fn test_apply(
|
||||
fn test_apply_inplace(log_n: usize, basek: usize, k_ct: usize, k_ksk: usize, digits: usize, rank: usize, sigma: f64) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_ct, basek * digits);
|
||||
let rows: usize = k_ct.div_ceil(basek * digits);
|
||||
|
||||
let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank, rank);
|
||||
let mut ct_glwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
use backend::{
|
||||
Backend, FFT64, MatZnxDft, MatZnxDftAlloc, MatZnxDftOps, Module, ScalarZnx, Scratch, VecZnxAlloc, VecZnxDftAlloc, VecZnxOps,
|
||||
ZnxInfos, ZnxZero,
|
||||
};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{FourierGLWECiphertext, GLWECiphertext, GLWESecret, GetRow, Infos, ScratchCore, SetRow, div_ceil};
|
||||
|
||||
pub struct GGLWECiphertext<C, B: Backend> {
|
||||
pub(crate) data: MatZnxDft<C, B>,
|
||||
pub(crate) basek: usize,
|
||||
pub(crate) k: usize,
|
||||
pub(crate) digits: usize,
|
||||
}
|
||||
|
||||
impl<B: Backend> GGLWECiphertext<Vec<u8>, B> {
|
||||
pub fn alloc(
|
||||
module: &Module<B>,
|
||||
basek: usize,
|
||||
k: usize,
|
||||
rows: usize,
|
||||
digits: usize,
|
||||
rank_in: usize,
|
||||
rank_out: usize,
|
||||
) -> Self {
|
||||
let size: usize = k.div_ceil(basek);
|
||||
debug_assert!(
|
||||
size > digits,
|
||||
"invalid gglwe: ceil(k/basek): {} <= digits: {}",
|
||||
size,
|
||||
digits
|
||||
);
|
||||
|
||||
debug_assert!(
|
||||
rows * digits <= size,
|
||||
"invalid gglwe: rows: {} * digits:{} > ceil(k/basek): {}",
|
||||
rows,
|
||||
digits,
|
||||
size
|
||||
);
|
||||
|
||||
Self {
|
||||
data: module.new_mat_znx_dft(rows, rank_in, rank_out + 1, size),
|
||||
basek: basek,
|
||||
k,
|
||||
digits,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_of(
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k: usize,
|
||||
rows: usize,
|
||||
digits: usize,
|
||||
rank_in: usize,
|
||||
rank_out: usize,
|
||||
) -> usize {
|
||||
let size: usize = k.div_ceil(basek);
|
||||
debug_assert!(
|
||||
size > digits,
|
||||
"invalid gglwe: ceil(k/basek): {} <= digits: {}",
|
||||
size,
|
||||
digits
|
||||
);
|
||||
|
||||
assert!(
|
||||
rows * digits <= size,
|
||||
"invalid gglwe: rows: {} * digits:{} > ceil(k/basek): {}",
|
||||
rows,
|
||||
digits,
|
||||
size
|
||||
);
|
||||
|
||||
module.bytes_of_mat_znx_dft(rows, rank_in, rank_out + 1, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B: Backend> Infos for GGLWECiphertext<T, B> {
|
||||
type Inner = MatZnxDft<T, B>;
|
||||
|
||||
fn inner(&self) -> &Self::Inner {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn basek(&self) -> usize {
|
||||
self.basek
|
||||
}
|
||||
|
||||
fn k(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B: Backend> GGLWECiphertext<T, B> {
|
||||
pub fn rank(&self) -> usize {
|
||||
self.data.cols_out() - 1
|
||||
}
|
||||
|
||||
pub fn digits(&self) -> usize {
|
||||
self.digits
|
||||
}
|
||||
|
||||
pub fn rank_in(&self) -> usize {
|
||||
self.data.cols_in()
|
||||
}
|
||||
|
||||
pub fn rank_out(&self) -> usize {
|
||||
self.data.cols_out() - 1
|
||||
}
|
||||
}
|
||||
|
||||
impl GGLWECiphertext<Vec<u8>, FFT64> {
|
||||
pub fn generate_from_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
let size = k.div_ceil(basek);
|
||||
GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
|
||||
+ module.bytes_of_vec_znx(rank + 1, size)
|
||||
+ module.bytes_of_vec_znx(1, size)
|
||||
+ module.bytes_of_vec_znx_dft(rank + 1, size)
|
||||
}
|
||||
|
||||
pub fn generate_from_pk_scratch_space(_module: &Module<FFT64>, _basek: usize, _k: usize, _rank: usize) -> usize {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GGLWECiphertext<DataSelf, FFT64> {
|
||||
pub fn encrypt_sk<DataPt: AsRef<[u8]>, DataSk: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
pt: &ScalarZnx<DataPt>,
|
||||
sk: &GLWESecret<DataSk, FFT64>,
|
||||
source_xa: &mut Source,
|
||||
source_xe: &mut Source,
|
||||
sigma: f64,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_eq!(self.rank_in(), pt.cols());
|
||||
assert_eq!(self.rank_out(), sk.rank());
|
||||
assert_eq!(self.n(), module.n());
|
||||
assert_eq!(sk.n(), module.n());
|
||||
assert_eq!(pt.n(), module.n());
|
||||
assert!(
|
||||
scratch.available()
|
||||
>= GGLWECiphertext::generate_from_sk_scratch_space(module, self.basek(), self.k(), self.rank()),
|
||||
"scratch.available: {} < GGLWECiphertext::generate_from_sk_scratch_space(module, self.rank()={}, \
|
||||
self.size()={}): {}",
|
||||
scratch.available(),
|
||||
self.rank(),
|
||||
self.size(),
|
||||
GGLWECiphertext::generate_from_sk_scratch_space(module, self.basek(), self.k(), self.rank())
|
||||
);
|
||||
assert!(
|
||||
self.rows() * self.digits() * self.basek() <= self.k(),
|
||||
"self.rows() : {} * self.digits() : {} * self.basek() : {} = {} >= self.k() = {}",
|
||||
self.rows(),
|
||||
self.digits(),
|
||||
self.basek(),
|
||||
self.rows() * self.digits() * self.basek(),
|
||||
self.k()
|
||||
);
|
||||
}
|
||||
|
||||
let rows: usize = self.rows();
|
||||
let digits: usize = self.digits();
|
||||
let basek: usize = self.basek();
|
||||
let k: usize = self.k();
|
||||
let rank_in: usize = self.rank_in();
|
||||
let rank_out: usize = self.rank_out();
|
||||
|
||||
let (mut tmp_pt, scrach_1) = scratch.tmp_glwe_pt(module, basek, k);
|
||||
let (mut tmp_ct, scrach_2) = scrach_1.tmp_glwe_ct(module, basek, k, rank_out);
|
||||
let (mut tmp_ct_dft, scratch_3) = scrach_2.tmp_glwe_fourier(module, basek, k, rank_out);
|
||||
|
||||
// For each input column (i.e. rank) produces a GGLWE ciphertext of rank_out+1 columns
|
||||
//
|
||||
// Example for ksk rank 2 to rank 3:
|
||||
//
|
||||
// (-(a0*s0 + a1*s1 + a2*s2) + s0', a0, a1, a2)
|
||||
// (-(b0*s0 + b1*s1 + b2*s2) + s0', b0, b1, b2)
|
||||
//
|
||||
// Example ksk rank 2 to rank 1
|
||||
//
|
||||
// (-(a*s) + s0, a)
|
||||
// (-(b*s) + s1, b)
|
||||
(0..rank_in).for_each(|col_i| {
|
||||
(0..rows).for_each(|row_i| {
|
||||
// Adds the scalar_znx_pt to the i-th limb of the vec_znx_pt
|
||||
tmp_pt.data.zero(); // zeroes for next iteration
|
||||
module.vec_znx_add_scalar_inplace(
|
||||
&mut tmp_pt.data,
|
||||
0,
|
||||
(digits - 1) + row_i * digits,
|
||||
pt,
|
||||
col_i,
|
||||
);
|
||||
module.vec_znx_normalize_inplace(basek, &mut tmp_pt.data, 0, scratch_3);
|
||||
|
||||
// rlwe encrypt of vec_znx_pt into vec_znx_ct
|
||||
tmp_ct.encrypt_sk(module, &tmp_pt, sk, source_xa, source_xe, sigma, scratch_3);
|
||||
|
||||
// Switch vec_znx_ct into DFT domain
|
||||
tmp_ct.dft(module, &mut tmp_ct_dft);
|
||||
|
||||
// Stores vec_znx_dft_ct into thw i-th row of the MatZnxDft
|
||||
self.set_row(module, row_i, col_i, &tmp_ct_dft);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsRef<[u8]>> GetRow<FFT64> for GGLWECiphertext<C, FFT64> {
|
||||
fn get_row<R: AsMut<[u8]> + AsRef<[u8]>>(
|
||||
&self,
|
||||
module: &Module<FFT64>,
|
||||
row_i: usize,
|
||||
col_j: usize,
|
||||
res: &mut FourierGLWECiphertext<R, FFT64>,
|
||||
) {
|
||||
module.mat_znx_dft_get_row(&mut res.data, &self.data, row_i, col_j);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: AsMut<[u8]> + AsRef<[u8]>> SetRow<FFT64> for GGLWECiphertext<C, FFT64> {
|
||||
fn set_row<R: AsRef<[u8]>>(
|
||||
&mut self,
|
||||
module: &Module<FFT64>,
|
||||
row_i: usize,
|
||||
col_j: usize,
|
||||
a: &FourierGLWECiphertext<R, FFT64>,
|
||||
) {
|
||||
module.mat_znx_dft_set_row(&mut self.data, row_i, col_j, &a.data);
|
||||
}
|
||||
}
|
||||
@@ -62,24 +62,33 @@ impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
|
||||
self.rank_out(),
|
||||
rhs.rank_out()
|
||||
);
|
||||
assert!(
|
||||
self.k() <= lhs.k(),
|
||||
"output k={} cannot be greater than input k={}",
|
||||
self.k(),
|
||||
lhs.k()
|
||||
)
|
||||
}
|
||||
|
||||
let cols_out: usize = rhs.rank_out() + 1;
|
||||
|
||||
let (mut tmp_dft, scratch1) = scratch.tmp_glwe_fourier(module, lhs.basek(), lhs.k(), lhs.rank());
|
||||
|
||||
(0..self.rank_in()).for_each(|col_i| {
|
||||
(0..self.rows()).for_each(|row_j| {
|
||||
// Extracts relevant row
|
||||
lhs.get_row(module, row_j, col_i, &mut tmp_dft);
|
||||
let (mut tmp_idft_data, scratct1) = scratch.tmp_vec_znx_big(module, cols_out, self.size());
|
||||
|
||||
// Get a VecZnxBig from scratch space
|
||||
let (mut tmp_idft_data, scratch2) = scratch1.tmp_vec_znx_big(module, cols_out, self.size());
|
||||
{
|
||||
let (mut tmp_dft, scratch2) = scratct1.tmp_glwe_fourier(module, lhs.basek(), lhs.k(), lhs.rank());
|
||||
|
||||
// Switches input outside of DFT
|
||||
(0..cols_out).for_each(|i| {
|
||||
module.vec_znx_idft(&mut tmp_idft_data, i, &tmp_dft.data, i, scratch2);
|
||||
});
|
||||
// Extracts relevant row
|
||||
lhs.get_row(module, row_j, col_i, &mut tmp_dft);
|
||||
|
||||
// Get a VecZnxBig from scratch space
|
||||
|
||||
// Switches input outside of DFT
|
||||
(0..cols_out).for_each(|i| {
|
||||
module.vec_znx_idft(&mut tmp_idft_data, i, &tmp_dft.data, i, scratch2);
|
||||
});
|
||||
}
|
||||
|
||||
// Consumes to small vec znx
|
||||
let mut tmp_idft_small_data: VecZnx<&mut [u8]> = tmp_idft_data.to_vec_znx_small();
|
||||
@@ -97,20 +106,25 @@ impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
|
||||
};
|
||||
|
||||
// Key-switch (-sa + pi_{k}(s), a) to (-pi^{-1}_{k'}(s)a + pi_{k}(s), a)
|
||||
tmp_idft.keyswitch_inplace(module, &rhs.key, scratch2);
|
||||
tmp_idft.keyswitch_inplace(module, &rhs.key, scratct1);
|
||||
|
||||
// Applies back the automorphism X^{k}: (-pi^{-1}_{k'}(s)a + pi_{k}(s), a) -> (-pi^{-1}_{k'+k}(s)a + s, a)
|
||||
// and switches back to DFT domain
|
||||
(0..self.rank_out() + 1).for_each(|i| {
|
||||
module.vec_znx_automorphism_inplace(lhs.p(), &mut tmp_idft.data, i);
|
||||
module.vec_znx_dft(1, 0, &mut tmp_dft.data, i, &tmp_idft.data, i);
|
||||
});
|
||||
{
|
||||
let (mut tmp_dft, _) = scratct1.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
|
||||
|
||||
// Sets back the relevant row
|
||||
self.set_row(module, row_j, col_i, &tmp_dft);
|
||||
// Applies back the automorphism X^{k}: (-pi^{-1}_{k'}(s)a + pi_{k}(s), a) -> (-pi^{-1}_{k'+k}(s)a + s, a)
|
||||
// and switches back to DFT domain
|
||||
(0..self.rank_out() + 1).for_each(|i| {
|
||||
module.vec_znx_automorphism_inplace(lhs.p(), &mut tmp_idft.data, i);
|
||||
module.vec_znx_dft(1, 0, &mut tmp_dft.data, i, &tmp_idft.data, i);
|
||||
});
|
||||
|
||||
// Sets back the relevant row
|
||||
self.set_row(module, row_j, col_i, &tmp_dft);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let (mut tmp_dft, _) = scratch.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
|
||||
tmp_dft.data.zero();
|
||||
|
||||
(self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use backend::{Backend, FFT64, MatZnxDft, MatZnxDftAlloc, MatZnxDftOps, Module};
|
||||
|
||||
use crate::{FourierGLWECiphertext, GetRow, Infos, SetRow, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, GetRow, Infos, SetRow};
|
||||
|
||||
pub struct GGLWECiphertext<C, B: Backend> {
|
||||
pub(crate) data: MatZnxDft<C, B>,
|
||||
@@ -19,7 +19,7 @@ impl<B: Backend> GGLWECiphertext<Vec<u8>, B> {
|
||||
rank_in: usize,
|
||||
rank_out: usize,
|
||||
) -> Self {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
let size: usize = k.div_ceil(basek);
|
||||
debug_assert!(
|
||||
size > digits,
|
||||
"invalid gglwe: ceil(k/basek): {} <= digits: {}",
|
||||
@@ -52,7 +52,7 @@ impl<B: Backend> GGLWECiphertext<Vec<u8>, B> {
|
||||
rank_in: usize,
|
||||
rank_out: usize,
|
||||
) -> usize {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
let size: usize = k.div_ceil(basek);
|
||||
debug_assert!(
|
||||
size > digits,
|
||||
"invalid gglwe: ceil(k/basek): {} <= digits: {}",
|
||||
|
||||
@@ -5,12 +5,12 @@ use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWESecret, GGLWECiphertext, GLWEAutomorphismKey, GLWECiphertext, GLWESecret, GLWESwitchingKey, GLWETensorKey, Infos,
|
||||
ScratchCore, SetRow, div_ceil,
|
||||
ScratchCore, SetRow,
|
||||
};
|
||||
|
||||
impl GGLWECiphertext<Vec<u8>, FFT64> {
|
||||
pub fn generate_from_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
let size = div_ceil(k, basek);
|
||||
let size = k.div_ceil(basek);
|
||||
GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
|
||||
+ module.bytes_of_vec_znx(rank + 1, size)
|
||||
+ module.bytes_of_vec_znx(1, size)
|
||||
|
||||
@@ -2,7 +2,7 @@ use backend::{FFT64, Module, ScalarZnxOps, ScratchOwned, Stats, VecZnxOps};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GLWEAutomorphismKey, GLWEPlaintext, GLWESecret, GetRow, Infos, div_ceil,
|
||||
FourierGLWECiphertext, FourierGLWESecret, GLWEAutomorphismKey, GLWEPlaintext, GLWESecret, GetRow, Infos,
|
||||
noise::log2_std_noise_gglwe_product,
|
||||
};
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GGSWCiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, GetRow, Infos,
|
||||
div_ceil,
|
||||
noise::{log2_std_noise_gglwe_product, noise_ggsw_product},
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWESwitchingKey, GLWETensorKey, GetRow,
|
||||
Infos, ScratchCore, SetRow, div_ceil,
|
||||
Infos, ScratchCore, SetRow,
|
||||
};
|
||||
|
||||
pub struct GGSWCiphertext<C, B: Backend> {
|
||||
@@ -17,8 +17,8 @@ pub struct GGSWCiphertext<C, B: Backend> {
|
||||
pub(crate) digits: usize,
|
||||
}
|
||||
|
||||
impl<B: Backend> GGSWCiphertext<Vec<u8>, B> {
|
||||
pub fn alloc(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self {
|
||||
impl GGSWCiphertext<Vec<u8>, FFT64> {
|
||||
pub fn alloc(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self {
|
||||
let size: usize = k.div_ceil(basek);
|
||||
debug_assert!(
|
||||
size > digits,
|
||||
|
||||
@@ -6,7 +6,7 @@ use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWECiphertext, FourierGLWESecret, GGSWCiphertext, GLWEAutomorphismKey, GLWEPlaintext, GLWESecret, GLWESwitchingKey,
|
||||
GLWETensorKey, GetRow, Infos, div_ceil,
|
||||
GLWETensorKey, GetRow, Infos,
|
||||
noise::{noise_ggsw_keyswitch, noise_ggsw_product},
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use backend::{
|
||||
Backend, FFT64, Module, VecZnx, VecZnxAlloc, VecZnxBigAlloc, VecZnxBigScratch, VecZnxDftAlloc, VecZnxDftOps, VecZnxToMut,
|
||||
VecZnxToRef,
|
||||
};
|
||||
use backend::{Backend, FFT64, Module, VecZnx, VecZnxAlloc, VecZnxDftOps, VecZnxToMut, VecZnxToRef};
|
||||
|
||||
use crate::{FourierGLWECiphertext, GLWEOps, Infos, SetMetaData, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, GLWEOps, Infos, SetMetaData};
|
||||
|
||||
pub struct GLWECiphertext<C> {
|
||||
pub data: VecZnx<C>,
|
||||
@@ -14,14 +11,14 @@ pub struct GLWECiphertext<C> {
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn alloc<B: Backend>(module: &Module<B>, basek: usize, k: usize, rank: usize) -> Self {
|
||||
Self {
|
||||
data: module.new_vec_znx(rank + 1, div_ceil(k, basek)),
|
||||
data: module.new_vec_znx(rank + 1, k.div_ceil(basek)),
|
||||
basek,
|
||||
k,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bytes_of(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
|
||||
module.bytes_of_vec_znx(rank + 1, div_ceil(k, basek))
|
||||
module.bytes_of_vec_znx(rank + 1, k.div_ceil(basek))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,10 +59,13 @@ impl<C: AsRef<[u8]>> GLWECiphertext<C> {
|
||||
}
|
||||
}
|
||||
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn decrypt_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
(module.vec_znx_big_normalize_tmp_bytes() | module.bytes_of_vec_znx_dft(1, size)) + module.bytes_of_vec_znx_big(1, size)
|
||||
impl<DataSelf: AsRef<[u8]>> GLWECiphertext<DataSelf> {
|
||||
pub fn clone(&self) -> GLWECiphertext<Vec<u8>> {
|
||||
GLWECiphertext {
|
||||
data: self.data.clone(),
|
||||
basek: self.basek(),
|
||||
k: self.k(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
use backend::{FFT64, Module, ScalarZnxDftOps, Scratch, VecZnxBigOps, VecZnxDftOps, ZnxZero};
|
||||
use backend::{
|
||||
FFT64, Module, ScalarZnxDftOps, Scratch, VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch, VecZnxDftAlloc, VecZnxDftOps,
|
||||
ZnxZero,
|
||||
};
|
||||
|
||||
use crate::{FourierGLWESecret, GLWECiphertext, GLWEPlaintext, Infos};
|
||||
|
||||
impl<DataSelf: AsRef<[u8]>> GLWECiphertext<DataSelf> {
|
||||
pub fn clone(&self) -> GLWECiphertext<Vec<u8>> {
|
||||
GLWECiphertext {
|
||||
data: self.data.clone(),
|
||||
basek: self.basek(),
|
||||
k: self.k(),
|
||||
}
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn decrypt_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = k.div_ceil(basek);
|
||||
(module.vec_znx_big_normalize_tmp_bytes() | module.bytes_of_vec_znx_dft(1, size)) + module.bytes_of_vec_znx_big(1, size)
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsRef<[u8]>> GLWECiphertext<DataSelf> {
|
||||
pub fn decrypt<DataPt: AsMut<[u8]> + AsRef<[u8]>, DataSk: AsRef<[u8]>>(
|
||||
&self,
|
||||
module: &Module<FFT64>,
|
||||
|
||||
@@ -4,15 +4,15 @@ use backend::{
|
||||
};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWEPublicKey, Infos, SIX_SIGMA, dist::Distribution, div_ceil};
|
||||
use crate::{FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWEPublicKey, Infos, SIX_SIGMA, dist::Distribution};
|
||||
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
let size: usize = k.div_ceil(basek);
|
||||
module.vec_znx_big_normalize_tmp_bytes() + module.bytes_of_vec_znx_dft(1, size) + module.bytes_of_vec_znx(1, size)
|
||||
}
|
||||
pub fn encrypt_pk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize) -> usize {
|
||||
let size: usize = div_ceil(k, basek);
|
||||
let size: usize = k.div_ceil(basek);
|
||||
((module.bytes_of_vec_znx_dft(1, size) + module.bytes_of_vec_znx_big(1, size)) | module.bytes_of_scalar_znx(1))
|
||||
+ module.bytes_of_scalar_znx_dft(1)
|
||||
+ module.vec_znx_big_normalize_tmp_bytes()
|
||||
@@ -71,7 +71,7 @@ impl<DataSelf: AsRef<[u8]> + AsMut<[u8]>> GLWECiphertext<DataSelf> {
|
||||
sigma: f64,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
self.encrypt_pk_private(
|
||||
self.encrypt_pk_private::<DataPt, DataPk>(
|
||||
module,
|
||||
Some((pt, 0)),
|
||||
pk,
|
||||
@@ -91,7 +91,7 @@ impl<DataSelf: AsRef<[u8]> + AsMut<[u8]>> GLWECiphertext<DataSelf> {
|
||||
sigma: f64,
|
||||
scratch: &mut Scratch,
|
||||
) {
|
||||
self.encrypt_pk_private(
|
||||
self.encrypt_pk_private::<Vec<u8>, DataPk>(
|
||||
module,
|
||||
None::<(&GLWEPlaintext<Vec<u8>>, usize)>,
|
||||
pk,
|
||||
|
||||
@@ -2,7 +2,7 @@ use backend::{
|
||||
FFT64, MatZnxDftOps, MatZnxDftScratch, Module, Scratch, VecZnxBig, VecZnxBigOps, VecZnxDftAlloc, VecZnxDftOps, VecZnxScratch,
|
||||
};
|
||||
|
||||
use crate::{FourierGLWECiphertext, GGSWCiphertext, GLWECiphertext, Infos, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, GGSWCiphertext, GLWECiphertext, Infos};
|
||||
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn external_product_scratch_space(
|
||||
@@ -10,14 +10,14 @@ impl GLWECiphertext<Vec<u8>> {
|
||||
basek: usize,
|
||||
k_out: usize,
|
||||
k_in: usize,
|
||||
ggsw_k: usize,
|
||||
k_ggsw: usize,
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
let res_dft: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank);
|
||||
let in_size: usize = div_ceil(div_ceil(k_in, basek), digits);
|
||||
let out_size: usize = div_ceil(k_out, basek);
|
||||
let ggsw_size: usize = div_ceil(ggsw_k, basek);
|
||||
let in_size: usize = k_in.div_ceil(basek).div_ceil(digits);
|
||||
let out_size: usize = k_out.div_ceil(basek);
|
||||
let ggsw_size: usize = k_ggsw.div_ceil(basek);
|
||||
let vmp: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size)
|
||||
+ module.vmp_apply_tmp_bytes(
|
||||
out_size,
|
||||
@@ -35,11 +35,11 @@ impl GLWECiphertext<Vec<u8>> {
|
||||
module: &Module<FFT64>,
|
||||
basek: usize,
|
||||
k_out: usize,
|
||||
ggsw_k: usize,
|
||||
k_ggsw: usize,
|
||||
digits: usize,
|
||||
rank: usize,
|
||||
) -> usize {
|
||||
Self::external_product_scratch_space(module, basek, k_out, k_out, ggsw_k, digits, rank)
|
||||
Self::external_product_scratch_space(module, basek, k_out, k_out, k_ggsw, digits, rank)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use backend::{
|
||||
VecZnxDftOps, ZnxZero,
|
||||
};
|
||||
|
||||
use crate::{FourierGLWECiphertext, GLWECiphertext, GLWESwitchingKey, Infos, div_ceil};
|
||||
use crate::{FourierGLWECiphertext, GLWECiphertext, GLWESwitchingKey, Infos};
|
||||
|
||||
impl GLWECiphertext<Vec<u8>> {
|
||||
pub fn keyswitch_scratch_space(
|
||||
@@ -17,9 +17,9 @@ impl GLWECiphertext<Vec<u8>> {
|
||||
rank_out: usize,
|
||||
) -> usize {
|
||||
let res_dft: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank_out + 1);
|
||||
let in_size: usize = div_ceil(div_ceil(k_in, basek), digits);
|
||||
let out_size: usize = div_ceil(k_out, basek);
|
||||
let ksk_size: usize = div_ceil(k_ksk, basek);
|
||||
let in_size: usize = k_in.div_ceil(basek).div_ceil(digits);
|
||||
let out_size: usize = k_out.div_ceil(basek);
|
||||
let ksk_size: usize = k_ksk.div_ceil(basek);
|
||||
let ai_dft: usize = module.bytes_of_vec_znx_dft(rank_in, in_size);
|
||||
let vmp: usize = module.vmp_apply_tmp_bytes(out_size, in_size, in_size, rank_in, rank_out + 1, ksk_size)
|
||||
+ module.bytes_of_vec_znx_dft(rank_in, in_size);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use backend::{Backend, FFT64, Module, VecZnx, VecZnxAlloc, VecZnxToMut, VecZnxToRef};
|
||||
|
||||
use crate::{GLWECiphertext, GLWECiphertextToMut, GLWECiphertextToRef, GLWEOps, Infos, SetMetaData, div_ceil};
|
||||
use crate::{GLWECiphertext, GLWECiphertextToMut, GLWECiphertextToRef, GLWEOps, Infos, SetMetaData};
|
||||
|
||||
pub struct GLWEPlaintext<C> {
|
||||
pub data: VecZnx<C>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use backend::{FFT64, FillUniform, Module, ScratchOwned, Stats, VecZnxOps};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos, div_ceil,
|
||||
FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos,
|
||||
noise::log2_std_noise_gglwe_product,
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ fn apply_inplace() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_ct: usize = 60;
|
||||
let digits: usize = div_ceil(k_ct, basek);
|
||||
let digits: usize = k_ct.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ksk: usize = k_ct + basek * di;
|
||||
@@ -27,7 +27,7 @@ fn apply() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_in: usize = 60;
|
||||
let digits: usize = div_ceil(k_in, basek);
|
||||
let digits: usize = k_in.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ksk: usize = k_in + basek * di;
|
||||
@@ -51,7 +51,7 @@ fn test_automorphism(
|
||||
) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_in, basek * digits);
|
||||
let rows: usize = k_in.div_ceil(basek * digits);
|
||||
|
||||
let mut autokey: GLWEAutomorphismKey<Vec<u8>, FFT64> = GLWEAutomorphismKey::alloc(&module, basek, k_ksk, rows, digits, rank);
|
||||
let mut ct_in: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_in, rank);
|
||||
@@ -149,7 +149,7 @@ fn test_automorphism_inplace(
|
||||
) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_ct, basek * digits);
|
||||
let rows: usize = k_ct.div_ceil(basek * digits);
|
||||
|
||||
let mut autokey: GLWEAutomorphismKey<Vec<u8>, FFT64> = GLWEAutomorphismKey::alloc(&module, basek, k_ksk, rows, digits, rank);
|
||||
let mut ct: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
|
||||
|
||||
@@ -2,7 +2,7 @@ use backend::{FFT64, FillUniform, Module, ScalarZnx, ScalarZnxAlloc, ScratchOwne
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWESecret, GGSWCiphertext, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos, div_ceil, noise::noise_ggsw_product,
|
||||
FourierGLWESecret, GGSWCiphertext, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos, noise::noise_ggsw_product,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@@ -10,7 +10,7 @@ fn apply() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_in: usize = 45;
|
||||
let digits: usize = div_ceil(k_in, basek);
|
||||
let digits: usize = k_in.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ggsw: usize = k_in + basek * di;
|
||||
|
||||
@@ -2,7 +2,7 @@ use backend::{FFT64, FillUniform, Module, ScratchOwned, Stats, VecZnxOps};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, Infos, div_ceil,
|
||||
FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWESecret, GLWESwitchingKey, Infos,
|
||||
noise::log2_std_noise_gglwe_product,
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ fn apply() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_in: usize = 45;
|
||||
let digits: usize = div_ceil(k_in, basek);
|
||||
let digits: usize = k_in.div_ceil(basek);
|
||||
(1..4).for_each(|rank_in| {
|
||||
(1..4).for_each(|rank_out| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
@@ -32,7 +32,7 @@ fn apply_inplace() {
|
||||
let log_n: usize = 8;
|
||||
let basek: usize = 12;
|
||||
let k_ct: usize = 45;
|
||||
let digits: usize = div_ceil(k_ct, basek);
|
||||
let digits: usize = k_ct.div_ceil(basek);
|
||||
(1..4).for_each(|rank| {
|
||||
(1..digits + 1).for_each(|di| {
|
||||
let k_ksk: usize = k_ct + basek * di;
|
||||
@@ -55,7 +55,7 @@ fn test_keyswitch(
|
||||
) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_in, basek * digits);
|
||||
let rows: usize = k_in.div_ceil(basek * digits);
|
||||
|
||||
let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> =
|
||||
GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_in, rank_out);
|
||||
@@ -148,7 +148,7 @@ fn test_keyswitch(
|
||||
fn test_keyswitch_inplace(log_n: usize, basek: usize, k_ct: usize, k_ksk: usize, digits: usize, rank: usize, sigma: f64) {
|
||||
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
|
||||
|
||||
let rows: usize = div_ceil(k_ct, basek * digits);
|
||||
let rows: usize = k_ct.div_ceil(basek * digits);
|
||||
|
||||
let mut ct_grlwe: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank, rank);
|
||||
let mut ct_glwe: GLWECiphertext<Vec<u8>> = GLWECiphertext::alloc(&module, basek, k_ct, rank);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEOps, GLWEPacker, GLWEPlaintext, GLWESecret, div_ceil};
|
||||
use crate::{FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEOps, GLWEPacker, GLWEPlaintext, GLWESecret};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use backend::{Encoding, FFT64, Module, ScratchOwned, Stats};
|
||||
|
||||
@@ -4,7 +4,7 @@ use backend::{FFT64, FillUniform, Module, ScratchOwned, Stats, VecZnxOps, ZnxVie
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{
|
||||
FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos, div_ceil,
|
||||
FourierGLWESecret, GLWEAutomorphismKey, GLWECiphertext, GLWEPlaintext, GLWESecret, Infos,
|
||||
noise::var_noise_gglwe_product,
|
||||
};
|
||||
|
||||
|
||||
@@ -7,18 +7,17 @@ pub mod ggsw;
|
||||
pub mod glwe;
|
||||
pub mod lwe;
|
||||
pub mod noise;
|
||||
mod utils;
|
||||
|
||||
use backend::Backend;
|
||||
use backend::FFT64;
|
||||
use backend::Module;
|
||||
pub use elem::*;
|
||||
pub use elem::{GetRow, Infos, SetMetaData, SetRow};
|
||||
pub use fourier_glwe::{FourierGLWECiphertext, FourierGLWESecret};
|
||||
pub use gglwe::{GGLWECiphertext, GLWEAutomorphismKey, GLWESwitchingKey, GLWETensorKey};
|
||||
pub use ggsw::*;
|
||||
pub use ggsw::GGSWCiphertext;
|
||||
pub use glwe::{GLWECiphertext, GLWEOps, GLWEPacker, GLWEPlaintext, GLWEPublicKey, GLWESecret};
|
||||
pub(crate) use glwe::{GLWECiphertextToMut, GLWECiphertextToRef};
|
||||
pub use lwe::*;
|
||||
pub use lwe::{LWECiphertext, LWESecret};
|
||||
|
||||
pub use backend::Scratch;
|
||||
pub use backend::ScratchOwned;
|
||||
@@ -174,7 +173,7 @@ impl ScratchCore<FFT64> for Scratch {
|
||||
k: usize,
|
||||
rank: usize,
|
||||
) -> (FourierGLWECiphertext<&mut [u8], FFT64>, &mut Self) {
|
||||
let (data, scratch) = self.tmp_vec_znx_dft(module, rank + 1, div_ceil(k, basek));
|
||||
let (data, scratch) = self.tmp_vec_znx_dft(module, rank + 1, k.div_ceil(basek));
|
||||
(FourierGLWECiphertext { data, basek, k }, scratch)
|
||||
}
|
||||
|
||||
|
||||
77
core/src/lwe/ciphertext.rs
Normal file
77
core/src/lwe/ciphertext.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use backend::{VecZnx, VecZnxToMut, VecZnxToRef};
|
||||
|
||||
use crate::{Infos, SetMetaData};
|
||||
|
||||
pub struct LWECiphertext<D> {
|
||||
pub(crate) data: VecZnx<D>,
|
||||
pub(crate) k: usize,
|
||||
pub(crate) basek: usize,
|
||||
}
|
||||
|
||||
impl LWECiphertext<Vec<u8>> {
|
||||
pub fn alloc(n: usize, basek: usize, k: usize) -> Self {
|
||||
Self {
|
||||
data: VecZnx::new::<i64>(n, 1, k.div_ceil(basek)),
|
||||
k: k,
|
||||
basek: basek,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Infos for LWECiphertext<T> {
|
||||
type Inner = VecZnx<T>;
|
||||
|
||||
fn n(&self) -> usize{
|
||||
&self.inner().n-1
|
||||
}
|
||||
|
||||
fn inner(&self) -> &Self::Inner {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn basek(&self) -> usize {
|
||||
self.basek
|
||||
}
|
||||
|
||||
fn k(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> SetMetaData for LWECiphertext<DataSelf> {
|
||||
fn set_k(&mut self, k: usize) {
|
||||
self.k = k
|
||||
}
|
||||
|
||||
fn set_basek(&mut self, basek: usize) {
|
||||
self.basek = basek
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LWECiphertextToRef {
|
||||
fn to_ref(&self) -> LWECiphertext<&[u8]>;
|
||||
}
|
||||
|
||||
impl<D: AsRef<[u8]>> LWECiphertextToRef for LWECiphertext<D> {
|
||||
fn to_ref(&self) -> LWECiphertext<&[u8]> {
|
||||
LWECiphertext {
|
||||
data: self.data.to_ref(),
|
||||
basek: self.basek,
|
||||
k: self.k,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LWECiphertextToMut {
|
||||
fn to_mut(&mut self) -> LWECiphertext<&mut [u8]>;
|
||||
}
|
||||
|
||||
impl<D: AsMut<[u8]> + AsRef<[u8]>> LWECiphertextToMut for LWECiphertext<D> {
|
||||
fn to_mut(&mut self) -> LWECiphertext<&mut [u8]> {
|
||||
LWECiphertext {
|
||||
data: self.data.to_mut(),
|
||||
basek: self.basek,
|
||||
k: self.k,
|
||||
}
|
||||
}
|
||||
}
|
||||
21
core/src/lwe/decryption.rs
Normal file
21
core/src/lwe/decryption.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use backend::{alloc_aligned, ZnxView, ZnxViewMut};
|
||||
|
||||
use crate::{lwe::{LWEPlaintext}, Infos, LWECiphertext, LWESecret, SetMetaData};
|
||||
|
||||
impl<DataSelf> LWECiphertext<DataSelf> where DataSelf: AsRef<[u8]>{
|
||||
pub fn decrypt<DataPt, DataSk>(&self, pt: &mut LWEPlaintext<DataPt>, sk: &LWESecret<DataSk>) where DataPt: AsRef<[u8]> + AsMut<[u8]>, DataSk: AsRef<[u8]>{
|
||||
#[cfg(debug_assertions)]{
|
||||
assert_eq!(self.n(), sk.n());
|
||||
}
|
||||
|
||||
(0..pt.size().min(self.size())).for_each(|i|{
|
||||
pt.data.at_mut(0, i)[0] = self.data.at(0, i)[0] + self.data.at(0, i)[1..].iter().zip(sk.data.at(0, 0)).map(|(x, y)| x * y).sum::<i64>();
|
||||
});
|
||||
|
||||
let mut tmp_bytes: Vec<u8> = alloc_aligned(size_of::<i64>());
|
||||
pt.data.normalize(self.basek(), 0, &mut tmp_bytes);
|
||||
|
||||
pt.set_basek(self.basek());
|
||||
pt.set_k(self.k().min(pt.size() * self.basek()));
|
||||
}
|
||||
}
|
||||
35
core/src/lwe/encryption.rs
Normal file
35
core/src/lwe/encryption.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use backend::{alloc_aligned, AddNormal, FillUniform, VecZnx, ZnxView, ZnxViewMut};
|
||||
use sampling::source::Source;
|
||||
|
||||
use crate::{lwe::LWEPlaintext, Infos, LWECiphertext, LWESecret, SIX_SIGMA};
|
||||
|
||||
|
||||
|
||||
impl<DataSelf> LWECiphertext<DataSelf> where DataSelf: AsMut<[u8]> + AsRef<[u8]>{
|
||||
pub fn encrypt_sk<DataPt, DataSk>(&mut self, pt: &LWEPlaintext<DataPt>, sk: &LWESecret<DataSk>, source_xa: &mut Source, source_xe: &mut Source, sigma: f64) where DataPt: AsRef<[u8]>, DataSk: AsRef<[u8]>{
|
||||
|
||||
#[cfg(debug_assertions)]{
|
||||
assert_eq!(self.n(), sk.n())
|
||||
}
|
||||
|
||||
let basek: usize = self.basek();
|
||||
|
||||
self.data.fill_uniform(basek, 0, self.size(), source_xa);
|
||||
let mut tmp_znx: VecZnx<Vec<u8>> = VecZnx::<Vec<u8>>::new::<i64>(1, 1, self.size());
|
||||
|
||||
(0..self.size()).for_each(|i|{
|
||||
tmp_znx.at_mut(0, i)[0] = pt.data.at(0, i)[0] - self.data.at(0, i)[1..].iter().zip(sk.data.at(0, 0)).map(|(x, y)| x * y).sum::<i64>();
|
||||
});
|
||||
|
||||
tmp_znx.add_normal(basek, 0, self.k(), source_xe, sigma, sigma*SIX_SIGMA);
|
||||
|
||||
let mut tmp_bytes: Vec<u8> = alloc_aligned(size_of::<i64>());
|
||||
|
||||
tmp_znx.normalize(basek, 0, &mut tmp_bytes);
|
||||
|
||||
(0..self.size()).for_each(|i|{
|
||||
self.data.at_mut(0, i)[0] = tmp_znx.at(0, i)[0];
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
pub mod ciphertext;
|
||||
pub mod secret;
|
||||
pub mod encryption;
|
||||
pub mod decryption;
|
||||
pub mod plaintext;
|
||||
|
||||
pub use ciphertext::LWECiphertext;
|
||||
pub use secret::LWESecret;
|
||||
pub use plaintext::LWEPlaintext;
|
||||
|
||||
73
core/src/lwe/plaintext.rs
Normal file
73
core/src/lwe/plaintext.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use backend::{VecZnx, VecZnxToMut, VecZnxToRef};
|
||||
|
||||
use crate::{Infos, SetMetaData};
|
||||
|
||||
pub struct LWEPlaintext<D>{
|
||||
pub(crate) data: VecZnx<D>,
|
||||
pub(crate) k: usize,
|
||||
pub(crate) basek: usize,
|
||||
}
|
||||
|
||||
impl LWEPlaintext<Vec<u8>> {
|
||||
pub fn alloc(basek: usize, k: usize) -> Self {
|
||||
Self {
|
||||
data: VecZnx::new::<i64>(1, 1, k.div_ceil(basek)),
|
||||
k: k,
|
||||
basek: basek,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Infos for LWEPlaintext<T> {
|
||||
type Inner = VecZnx<T>;
|
||||
|
||||
fn inner(&self) -> &Self::Inner {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn basek(&self) -> usize {
|
||||
self.basek
|
||||
}
|
||||
|
||||
fn k(&self) -> usize {
|
||||
self.k
|
||||
}
|
||||
}
|
||||
|
||||
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> SetMetaData for LWEPlaintext<DataSelf> {
|
||||
fn set_k(&mut self, k: usize) {
|
||||
self.k = k
|
||||
}
|
||||
|
||||
fn set_basek(&mut self, basek: usize) {
|
||||
self.basek = basek
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LWEPlaintextToRef {
|
||||
fn to_ref(&self) -> LWEPlaintext<&[u8]>;
|
||||
}
|
||||
|
||||
impl<D: AsRef<[u8]>> LWEPlaintextToRef for LWEPlaintext<D> {
|
||||
fn to_ref(&self) -> LWEPlaintext<&[u8]> {
|
||||
LWEPlaintext {
|
||||
data: self.data.to_ref(),
|
||||
basek: self.basek,
|
||||
k: self.k,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait LWEPlaintextToMut {
|
||||
fn to_mut(&mut self) -> LWEPlaintext<&mut [u8]>;
|
||||
}
|
||||
|
||||
impl<D: AsMut<[u8]> + AsRef<[u8]>> LWEPlaintextToMut for LWEPlaintext<D> {
|
||||
fn to_mut(&mut self) -> LWEPlaintext<&mut [u8]> {
|
||||
LWEPlaintext {
|
||||
data: self.data.to_mut(),
|
||||
basek: self.basek,
|
||||
k: self.k,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user