Reorganized other crates

This commit is contained in:
Jean-Philippe Bossuat
2025-06-13 18:57:28 +02:00
parent 4d4b43a4e5
commit e8cfb5e2ab
52 changed files with 2787 additions and 1380 deletions

View File

@@ -0,0 +1,136 @@
use backend::{FFT64, Module, Scratch, VecZnx, VecZnxDftOps, VecZnxOps, ZnxZero};
use crate::{FourierGLWECiphertext, GLWEAutomorphismKey, GLWECiphertext, GetRow, Infos, ScratchCore, SetRow};
impl GLWEAutomorphismKey<Vec<u8>, FFT64> {
pub fn automorphism_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_in: usize,
k_ksk: usize,
digits: usize,
rank: usize,
) -> usize {
let tmp_dft: usize = FourierGLWECiphertext::bytes_of(module, basek, k_in, rank);
let tmp_idft: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank);
let idft: usize = module.vec_znx_idft_tmp_bytes();
let keyswitch: usize = GLWECiphertext::keyswitch_inplace_scratch_space(module, basek, k_out, k_ksk, digits, rank);
tmp_dft + tmp_idft + idft + keyswitch
}
pub fn automorphism_inplace_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_ksk: usize,
digits: usize,
rank: usize,
) -> usize {
GLWEAutomorphismKey::automorphism_scratch_space(module, basek, k_out, k_out, k_ksk, digits, rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
pub fn automorphism<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
lhs: &GLWEAutomorphismKey<DataLhs, FFT64>,
rhs: &GLWEAutomorphismKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(
self.rank_in(),
lhs.rank_in(),
"ksk_out input rank: {} != ksk_in input rank: {}",
self.rank_in(),
lhs.rank_in()
);
assert_eq!(
lhs.rank_out(),
rhs.rank_in(),
"ksk_in output rank: {} != ksk_apply input rank: {}",
self.rank_out(),
rhs.rank_in()
);
assert_eq!(
self.rank_out(),
rhs.rank_out(),
"ksk_out output rank: {} != ksk_apply output rank: {}",
self.rank_out(),
rhs.rank_out()
);
}
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);
// Get a VecZnxBig from scratch space
let (mut tmp_idft_data, scratch2) = scratch1.tmp_vec_znx_big(module, cols_out, self.size());
// 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();
// Reverts the automorphis key from (-pi^{-1}_{k}(s)a + s, a) to (-sa + pi_{k}(s), a)
(0..cols_out).for_each(|i| {
module.vec_znx_automorphism_inplace(lhs.p(), &mut tmp_idft_small_data, i);
});
// Wraps into ciphertext
let mut tmp_idft: GLWECiphertext<&mut [u8]> = GLWECiphertext::<&mut [u8]> {
data: tmp_idft_small_data,
basek: self.basek(),
k: self.k(),
};
// 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);
// 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);
});
});
tmp_dft.data.zero();
(self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
(0..self.rank_in()).for_each(|col_j| {
self.set_row(module, row_i, col_j, &tmp_dft);
});
});
self.p = (lhs.p * rhs.p) % (module.cyclotomic_order() as i64);
}
pub fn automorphism_inplace<DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
rhs: &GLWEAutomorphismKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
unsafe {
let self_ptr: *mut GLWEAutomorphismKey<DataSelf, FFT64> = self as *mut GLWEAutomorphismKey<DataSelf, FFT64>;
self.automorphism(&module, &*self_ptr, rhs, scratch);
}
}
}

View File

@@ -0,0 +1,83 @@
use backend::{Backend, FFT64, MatZnxDft, MatZnxDftOps, Module};
use crate::{FourierGLWECiphertext, GLWESwitchingKey, GetRow, Infos, SetRow};
pub struct GLWEAutomorphismKey<Data, B: Backend> {
pub(crate) key: GLWESwitchingKey<Data, B>,
pub(crate) p: i64,
}
impl GLWEAutomorphismKey<Vec<u8>, FFT64> {
pub fn alloc(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self {
GLWEAutomorphismKey {
key: GLWESwitchingKey::alloc(module, basek, k, rows, digits, rank, rank),
p: 0,
}
}
pub fn bytes_of(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize {
GLWESwitchingKey::<Vec<u8>, FFT64>::bytes_of(module, basek, k, rows, digits, rank, rank)
}
}
impl<T, B: Backend> Infos for GLWEAutomorphismKey<T, B> {
type Inner = MatZnxDft<T, B>;
fn inner(&self) -> &Self::Inner {
&self.key.inner()
}
fn basek(&self) -> usize {
self.key.basek()
}
fn k(&self) -> usize {
self.key.k()
}
}
impl<T, B: Backend> GLWEAutomorphismKey<T, B> {
pub fn p(&self) -> i64 {
self.p
}
pub fn digits(&self) -> usize {
self.key.digits()
}
pub fn rank(&self) -> usize {
self.key.rank()
}
pub fn rank_in(&self) -> usize {
self.key.rank_in()
}
pub fn rank_out(&self) -> usize {
self.key.rank_out()
}
}
impl<C: AsRef<[u8]>> GetRow<FFT64> for GLWEAutomorphismKey<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.key.0.data, row_i, col_j);
}
}
impl<C: AsMut<[u8]> + AsRef<[u8]>> SetRow<FFT64> for GLWEAutomorphismKey<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.key.0.data, row_i, col_j, &a.data);
}
}

View File

@@ -0,0 +1,131 @@
use backend::{Backend, FFT64, MatZnxDft, MatZnxDftAlloc, MatZnxDftOps, Module};
use crate::{FourierGLWECiphertext, GetRow, Infos, 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 = div_ceil(k, 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
);
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 = div_ceil(k, 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, rows)
}
}
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<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);
}
}

View File

@@ -0,0 +1,253 @@
use backend::{
FFT64, Module, ScalarZnx, ScalarZnxDftOps, ScalarZnxOps, Scratch, VecZnxAlloc, VecZnxDftAlloc, VecZnxOps, ZnxInfos, ZnxZero,
};
use sampling::source::Source;
use crate::{
FourierGLWESecret, GGLWECiphertext, GLWEAutomorphismKey, GLWECiphertext, GLWESecret, GLWESwitchingKey, GLWETensorKey, Infos,
ScratchCore, SetRow, div_ceil,
};
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);
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: &FourierGLWESecret<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 GLWESwitchingKey<Vec<u8>, FFT64> {
pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
GGLWECiphertext::generate_from_sk_scratch_space(module, basek, k, rank)
}
pub fn encrypt_pk_scratch_space(module: &Module<FFT64>, _basek: usize, _k: usize, _rank: usize) -> usize {
GGLWECiphertext::generate_from_pk_scratch_space(module, _basek, _k, _rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWESwitchingKey<DataSelf, FFT64> {
pub fn generate_from_sk<DataSkIn: AsRef<[u8]>, DataSkOut: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
sk_in: &GLWESecret<DataSkIn>,
sk_out: &FourierGLWESecret<DataSkOut, FFT64>,
source_xa: &mut Source,
source_xe: &mut Source,
sigma: f64,
scratch: &mut Scratch,
) {
self.0.encrypt_sk(
module,
&sk_in.data,
sk_out,
source_xa,
source_xe,
sigma,
scratch,
);
}
}
impl GLWEAutomorphismKey<Vec<u8>, FFT64> {
pub fn generate_from_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
GGLWECiphertext::generate_from_sk_scratch_space(module, basek, k, rank) + GLWESecret::bytes_of(module, rank)
}
pub fn generate_from_pk_scratch_space(module: &Module<FFT64>, _basek: usize, _k: usize, _rank: usize) -> usize {
GGLWECiphertext::generate_from_pk_scratch_space(module, _basek, _k, _rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
pub fn generate_from_sk<DataSk: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
p: i64,
sk: &GLWESecret<DataSk>,
source_xa: &mut Source,
source_xe: &mut Source,
sigma: f64,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(self.n(), module.n());
assert_eq!(sk.n(), module.n());
assert_eq!(self.rank_out(), self.rank_in());
assert_eq!(sk.rank(), self.rank());
assert!(
scratch.available()
>= GLWEAutomorphismKey::generate_from_sk_scratch_space(module, self.basek(), self.k(), self.rank()),
"scratch.available(): {} < AutomorphismKey::generate_from_sk_scratch_space(module, self.rank()={}, \
self.size()={}): {}",
scratch.available(),
self.rank(),
self.size(),
GLWEAutomorphismKey::generate_from_sk_scratch_space(module, self.basek(), self.k(), self.rank())
)
}
let (mut sk_out_dft, scratch_1) = scratch.tmp_fourier_sk(module, sk.rank());
{
let (mut sk_out, _) = scratch_1.tmp_sk(module, sk.rank());
(0..self.rank()).for_each(|i| {
module.scalar_znx_automorphism(
module.galois_element_inv(p),
&mut sk_out.data,
i,
&sk.data,
i,
);
});
sk_out_dft.set(module, &sk_out);
}
self.key.generate_from_sk(
module,
&sk,
&sk_out_dft,
source_xa,
source_xe,
sigma,
scratch_1,
);
self.p = p;
}
}
impl GLWETensorKey<Vec<u8>, FFT64> {
pub fn generate_from_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
GLWESecret::bytes_of(module, 1)
+ FourierGLWESecret::bytes_of(module, 1)
+ GLWESwitchingKey::encrypt_sk_scratch_space(module, basek, k, rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWETensorKey<DataSelf, FFT64> {
pub fn generate_from_sk<DataSk: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
sk: &FourierGLWESecret<DataSk, FFT64>,
source_xa: &mut Source,
source_xe: &mut Source,
sigma: f64,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(self.rank(), sk.rank());
assert_eq!(self.n(), module.n());
assert_eq!(sk.n(), module.n());
}
let rank: usize = self.rank();
let (mut sk_ij, scratch1) = scratch.tmp_sk(module, 1);
let (mut sk_ij_dft, scratch2) = scratch1.tmp_fourier_sk(module, 1);
(0..rank).for_each(|i| {
(i..rank).for_each(|j| {
module.svp_apply(&mut sk_ij_dft.data, 0, &sk.data, i, &sk.data, j);
module.scalar_znx_idft(&mut sk_ij.data, 0, &sk_ij_dft.data, 0, scratch2);
self.at_mut(i, j)
.generate_from_sk(module, &sk_ij, sk, source_xa, source_xe, sigma, scratch2);
});
})
}
}

View File

@@ -0,0 +1,162 @@
use backend::{FFT64, Module, Scratch, ZnxZero};
use crate::{FourierGLWECiphertext, GGSWCiphertext, GLWEAutomorphismKey, GLWESwitchingKey, GetRow, Infos, ScratchCore, SetRow};
impl GLWESwitchingKey<Vec<u8>, FFT64> {
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 tmp_in: usize = FourierGLWECiphertext::bytes_of(module, basek, k_in, rank);
let tmp_out: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank);
let ggsw: usize = FourierGLWECiphertext::external_product_scratch_space(module, basek, k_out, k_in, k_ggsw, digits, rank);
tmp_in + tmp_out + ggsw
}
pub fn external_product_inplace_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_ggsw: usize,
digits: usize,
rank: usize,
) -> usize {
let tmp: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank);
let ggsw: usize =
FourierGLWECiphertext::external_product_inplace_scratch_space(module, basek, k_out, k_ggsw, digits, rank);
tmp + ggsw
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWESwitchingKey<DataSelf, FFT64> {
pub fn external_product<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
lhs: &GLWESwitchingKey<DataLhs, FFT64>,
rhs: &GGSWCiphertext<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(
self.rank_in(),
lhs.rank_in(),
"ksk_out input rank: {} != ksk_in input rank: {}",
self.rank_in(),
lhs.rank_in()
);
assert_eq!(
lhs.rank_out(),
rhs.rank(),
"ksk_in output rank: {} != ggsw rank: {}",
self.rank_out(),
rhs.rank()
);
assert_eq!(
self.rank_out(),
rhs.rank(),
"ksk_out output rank: {} != ggsw rank: {}",
self.rank_out(),
rhs.rank()
);
}
let (mut tmp_in, scratch1) = scratch.tmp_glwe_fourier(module, lhs.basek(), lhs.k(), lhs.rank());
let (mut tmp_out, scratch2) = scratch1.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
(0..self.rank_in()).for_each(|col_i| {
(0..self.rows()).for_each(|row_j| {
lhs.get_row(module, row_j, col_i, &mut tmp_in);
tmp_out.external_product(module, &tmp_in, rhs, scratch2);
self.set_row(module, row_j, col_i, &tmp_out);
});
});
tmp_out.data.zero();
(self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
(0..self.rank_in()).for_each(|col_j| {
self.set_row(module, row_i, col_j, &tmp_out);
});
});
}
pub fn external_product_inplace<DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
rhs: &GGSWCiphertext<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(
self.rank_out(),
rhs.rank(),
"ksk_out output rank: {} != ggsw rank: {}",
self.rank_out(),
rhs.rank()
);
}
let (mut tmp, scratch1) = scratch.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
println!("tmp: {}", tmp.size());
(0..self.rank_in()).for_each(|col_i| {
(0..self.rows()).for_each(|row_j| {
self.get_row(module, row_j, col_i, &mut tmp);
tmp.external_product_inplace(module, rhs, scratch1);
self.set_row(module, row_j, col_i, &tmp);
});
});
}
}
impl GLWEAutomorphismKey<Vec<u8>, FFT64> {
pub fn external_product_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_in: usize,
ggsw_k: usize,
digits: usize,
rank: usize,
) -> usize {
GLWESwitchingKey::external_product_scratch_space(module, basek, k_out, k_in, ggsw_k, digits, rank)
}
pub fn external_product_inplace_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
ggsw_k: usize,
digits: usize,
rank: usize,
) -> usize {
GLWESwitchingKey::external_product_inplace_scratch_space(module, basek, k_out, ggsw_k, digits, rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
pub fn external_product<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
lhs: &GLWEAutomorphismKey<DataLhs, FFT64>,
rhs: &GGSWCiphertext<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
self.key.external_product(module, &lhs.key, rhs, scratch);
}
pub fn external_product_inplace<DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
rhs: &GGSWCiphertext<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
self.key.external_product_inplace(module, rhs, scratch);
}
}

163
core/src/gglwe/keyswitch.rs Normal file
View File

@@ -0,0 +1,163 @@
use backend::{FFT64, Module, Scratch, ZnxZero};
use crate::{FourierGLWECiphertext, GLWEAutomorphismKey, GLWESwitchingKey, GetRow, Infos, ScratchCore, SetRow};
impl GLWEAutomorphismKey<Vec<u8>, FFT64> {
pub fn keyswitch_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_in: usize,
k_ksk: usize,
digits: usize,
rank: usize,
) -> usize {
GLWESwitchingKey::keyswitch_scratch_space(module, basek, k_out, k_in, k_ksk, digits, rank, rank)
}
pub fn keyswitch_inplace_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_ksk: usize,
digits: usize,
rank: usize,
) -> usize {
GLWESwitchingKey::keyswitch_inplace_scratch_space(module, basek, k_out, k_ksk, digits, rank)
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWEAutomorphismKey<DataSelf, FFT64> {
pub fn keyswitch<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
lhs: &GLWEAutomorphismKey<DataLhs, FFT64>,
rhs: &GLWESwitchingKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
self.key.keyswitch(module, &lhs.key, rhs, scratch);
}
pub fn keyswitch_inplace<DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
rhs: &GLWEAutomorphismKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
self.key.keyswitch_inplace(module, &rhs.key, scratch);
}
}
impl GLWESwitchingKey<Vec<u8>, FFT64> {
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 {
let tmp_in: usize = FourierGLWECiphertext::bytes_of(module, basek, k_in, rank_in);
let tmp_out: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank_out);
let ksk: usize =
FourierGLWECiphertext::keyswitch_scratch_space(module, basek, k_out, k_in, k_ksk, digits, rank_in, rank_out);
tmp_in + tmp_out + ksk
}
pub fn keyswitch_inplace_scratch_space(
module: &Module<FFT64>,
basek: usize,
k_out: usize,
k_ksk: usize,
digits: usize,
rank: usize,
) -> usize {
let tmp: usize = FourierGLWECiphertext::bytes_of(module, basek, k_out, rank);
let ksk: usize = FourierGLWECiphertext::keyswitch_inplace_scratch_space(module, basek, k_out, k_ksk, digits, rank);
tmp + ksk
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWESwitchingKey<DataSelf, FFT64> {
pub fn keyswitch<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
lhs: &GLWESwitchingKey<DataLhs, FFT64>,
rhs: &GLWESwitchingKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(
self.rank_in(),
lhs.rank_in(),
"ksk_out input rank: {} != ksk_in input rank: {}",
self.rank_in(),
lhs.rank_in()
);
assert_eq!(
lhs.rank_out(),
rhs.rank_in(),
"ksk_in output rank: {} != ksk_apply input rank: {}",
self.rank_out(),
rhs.rank_in()
);
assert_eq!(
self.rank_out(),
rhs.rank_out(),
"ksk_out output rank: {} != ksk_apply output rank: {}",
self.rank_out(),
rhs.rank_out()
);
}
let (mut tmp_in, scratch1) = scratch.tmp_glwe_fourier(module, lhs.basek(), lhs.k(), lhs.rank());
let (mut tmp_out, scratch2) = scratch1.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
(0..self.rank_in()).for_each(|col_i| {
(0..self.rows()).for_each(|row_j| {
lhs.get_row(module, row_j, col_i, &mut tmp_in);
tmp_out.keyswitch(module, &tmp_in, rhs, scratch2);
self.set_row(module, row_j, col_i, &tmp_out);
});
});
tmp_out.data.zero();
(self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
(0..self.rank_in()).for_each(|col_j| {
self.set_row(module, row_i, col_j, &tmp_out);
});
});
}
pub fn keyswitch_inplace<DataRhs: AsRef<[u8]>>(
&mut self,
module: &Module<FFT64>,
rhs: &GLWESwitchingKey<DataRhs, FFT64>,
scratch: &mut Scratch,
) {
#[cfg(debug_assertions)]
{
assert_eq!(
self.rank_out(),
rhs.rank_out(),
"ksk_out output rank: {} != ksk_apply output rank: {}",
self.rank_out(),
rhs.rank_out()
);
}
let (mut tmp, scratch1) = scratch.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
(0..self.rank_in()).for_each(|col_i| {
(0..self.rows()).for_each(|row_j| {
self.get_row(module, row_j, col_i, &mut tmp);
tmp.keyswitch_inplace(module, rhs, scratch1);
self.set_row(module, row_j, col_i, &tmp);
});
});
}
}

View File

@@ -0,0 +1,91 @@
use backend::{Backend, FFT64, MatZnxDft, MatZnxDftOps, Module};
use crate::{FourierGLWECiphertext, GGLWECiphertext, GetRow, Infos, SetRow};
pub struct GLWESwitchingKey<Data, B: Backend>(pub(crate) GGLWECiphertext<Data, B>);
impl GLWESwitchingKey<Vec<u8>, FFT64> {
pub fn alloc(
module: &Module<FFT64>,
basek: usize,
k: usize,
rows: usize,
digits: usize,
rank_in: usize,
rank_out: usize,
) -> Self {
GLWESwitchingKey(GGLWECiphertext::alloc(
module, basek, k, rows, digits, rank_in, rank_out,
))
}
pub fn bytes_of(
module: &Module<FFT64>,
basek: usize,
k: usize,
rows: usize,
digits: usize,
rank_in: usize,
rank_out: usize,
) -> usize {
GGLWECiphertext::<Vec<u8>, FFT64>::bytes_of(module, basek, k, rows, digits, rank_in, rank_out)
}
}
impl<T, B: Backend> Infos for GLWESwitchingKey<T, B> {
type Inner = MatZnxDft<T, B>;
fn inner(&self) -> &Self::Inner {
self.0.inner()
}
fn basek(&self) -> usize {
self.0.basek()
}
fn k(&self) -> usize {
self.0.k()
}
}
impl<T, B: Backend> GLWESwitchingKey<T, B> {
pub fn rank(&self) -> usize {
self.0.data.cols_out() - 1
}
pub fn rank_in(&self) -> usize {
self.0.data.cols_in()
}
pub fn rank_out(&self) -> usize {
self.0.data.cols_out() - 1
}
pub fn digits(&self) -> usize {
self.0.digits()
}
}
impl<C: AsRef<[u8]>> GetRow<FFT64> for GLWESwitchingKey<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.0.data, row_i, col_j);
}
}
impl<C: AsMut<[u8]> + AsRef<[u8]>> SetRow<FFT64> for GLWESwitchingKey<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.0.data, row_i, col_j, &a.data);
}
}

16
core/src/gglwe/mod.rs Normal file
View File

@@ -0,0 +1,16 @@
pub mod automorphism;
pub mod automorphism_key;
pub mod ciphertext;
pub mod encryption;
pub mod external_product;
pub mod keyswitch;
pub mod keyswitch_key;
pub mod tensor_key;
pub use automorphism_key::GLWEAutomorphismKey;
pub use ciphertext::GGLWECiphertext;
pub use keyswitch_key::GLWESwitchingKey;
pub use tensor_key::GLWETensorKey;
#[cfg(test)]
mod test_fft64;

View File

@@ -0,0 +1,81 @@
use backend::{Backend, FFT64, MatZnxDft, Module};
use crate::{GLWESwitchingKey, Infos};
pub struct GLWETensorKey<C, B: Backend> {
pub(crate) keys: Vec<GLWESwitchingKey<C, B>>,
}
impl GLWETensorKey<Vec<u8>, FFT64> {
pub fn alloc(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self {
let mut keys: Vec<GLWESwitchingKey<Vec<u8>, FFT64>> = Vec::new();
let pairs: usize = (((rank + 1) * rank) >> 1).max(1);
(0..pairs).for_each(|_| {
keys.push(GLWESwitchingKey::alloc(
module, basek, k, rows, digits, 1, rank,
));
});
Self { keys: keys }
}
pub fn bytes_of(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize {
let pairs: usize = (((rank + 1) * rank) >> 1).max(1);
pairs * GLWESwitchingKey::<Vec<u8>, FFT64>::bytes_of(module, basek, k, rows, digits, 1, rank)
}
}
impl<T, B: Backend> Infos for GLWETensorKey<T, B> {
type Inner = MatZnxDft<T, B>;
fn inner(&self) -> &Self::Inner {
&self.keys[0].inner()
}
fn basek(&self) -> usize {
self.keys[0].basek()
}
fn k(&self) -> usize {
self.keys[0].k()
}
}
impl<T, B: Backend> GLWETensorKey<T, B> {
pub fn rank(&self) -> usize {
self.keys[0].rank()
}
pub fn rank_in(&self) -> usize {
self.keys[0].rank_in()
}
pub fn rank_out(&self) -> usize {
self.keys[0].rank_out()
}
pub fn digits(&self) -> usize {
self.keys[0].digits()
}
}
impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GLWETensorKey<DataSelf, FFT64> {
// Returns a mutable reference to GLWESwitchingKey_{s}(s[i] * s[j])
pub fn at_mut(&mut self, mut i: usize, mut j: usize) -> &mut GLWESwitchingKey<DataSelf, FFT64> {
if i > j {
std::mem::swap(&mut i, &mut j);
};
let rank: usize = self.rank();
&mut self.keys[i * rank + j - (i * (i + 1) / 2)]
}
}
impl<DataSelf: AsRef<[u8]>> GLWETensorKey<DataSelf, FFT64> {
// Returns a reference to GLWESwitchingKey_{s}(s[i] * s[j])
pub fn at(&self, mut i: usize, mut j: usize) -> &GLWESwitchingKey<DataSelf, FFT64> {
if i > j {
std::mem::swap(&mut i, &mut j);
};
let rank: usize = self.rank();
&self.keys[i * rank + j - (i * (i + 1) / 2)]
}
}

View File

@@ -0,0 +1,274 @@
use backend::{FFT64, Module, ScalarZnxOps, ScratchOwned, Stats, VecZnxOps};
use sampling::source::Source;
use crate::{
FourierGLWECiphertext, FourierGLWESecret, GLWEAutomorphismKey, GLWEPlaintext, GLWESecret, GetRow, Infos, div_ceil,
noise::log2_std_noise_gglwe_product,
};
#[test]
fn automorphism() {
let log_n: usize = 8;
let basek: usize = 12;
let k_in: usize = 60;
let k_out: usize = 40;
let digits: usize = k_in.div_ceil(basek);
let sigma: f64 = 3.2;
(1..4).for_each(|rank| {
(2..digits + 1).for_each(|di| {
println!("test automorphism digits: {} rank: {}", di, rank);
let k_apply: usize = (digits + di) * basek;
test_automorphism(-1, 5, log_n, basek, di, k_in, k_out, k_apply, sigma, rank);
});
});
}
#[test]
fn automorphism_inplace() {
let log_n: usize = 8;
let basek: usize = 12;
let k_in: usize = 60;
let digits: usize = k_in.div_ceil(basek);
let sigma: f64 = 3.2;
(1..4).for_each(|rank| {
(2..digits + 1).for_each(|di| {
println!("test automorphism digits: {} rank: {}", di, rank);
let k_apply: usize = (digits + di) * basek;
test_automorphism_inplace(-1, 5, log_n, basek, di, k_in, k_apply, sigma, rank);
});
});
}
fn test_automorphism(
p0: i64,
p1: i64,
log_n: usize,
basek: usize,
digits: usize,
k_in: usize,
k_out: usize,
k_apply: usize,
sigma: f64,
rank: usize,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
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: GLWEAutomorphismKey<Vec<u8>, FFT64> =
GLWEAutomorphismKey::alloc(&module, basek, k_in, rows_in, digits_in, rank);
let mut auto_key_out: GLWEAutomorphismKey<Vec<u8>, FFT64> =
GLWEAutomorphismKey::alloc(&module, basek, k_out, rows_in, digits_in, rank);
let mut auto_key_apply: GLWEAutomorphismKey<Vec<u8>, FFT64> =
GLWEAutomorphismKey::alloc(&module, 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 = ScratchOwned::new(
GLWEAutomorphismKey::generate_from_sk_scratch_space(&module, basek, k_apply, rank)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_out)
| GLWEAutomorphismKey::automorphism_scratch_space(&module, basek, k_out, k_in, k_apply, digits, rank),
);
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
// gglwe_{s1}(s0) = s0 -> s1
auto_key_in.generate_from_sk(
&module,
p0,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
auto_key_apply.generate_from_sk(
&module,
p1,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
auto_key_out.automorphism(&module, &auto_key_in, &auto_key_apply, scratch.borrow());
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_out, rank);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
let mut sk_auto: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
(0..rank).for_each(|i| {
module.scalar_znx_automorphism(
module.galois_element_inv(p0 * p1),
&mut sk_auto.data,
i,
&sk.data,
i,
);
});
let sk_auto_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::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.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.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 = pt.data.std(0, basek).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
module.n() 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
);
});
});
}
fn test_automorphism_inplace(
p0: i64,
p1: i64,
log_n: usize,
basek: usize,
digits: usize,
k_in: usize,
k_apply: usize,
sigma: f64,
rank: usize,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
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: GLWEAutomorphismKey<Vec<u8>, FFT64> =
GLWEAutomorphismKey::alloc(&module, basek, k_in, rows_in, digits_in, rank);
let mut auto_key_apply: GLWEAutomorphismKey<Vec<u8>, FFT64> =
GLWEAutomorphismKey::alloc(&module, 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 = ScratchOwned::new(
GLWEAutomorphismKey::generate_from_sk_scratch_space(&module, basek, k_apply, rank)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_in)
| GLWEAutomorphismKey::automorphism_inplace_scratch_space(&module, basek, k_in, k_apply, digits, rank),
);
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
// gglwe_{s1}(s0) = s0 -> s1
auto_key.generate_from_sk(
&module,
p0,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
auto_key_apply.generate_from_sk(
&module,
p1,
&sk,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
auto_key.automorphism_inplace(&module, &auto_key_apply, scratch.borrow());
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_in, rank);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_in);
let mut sk_auto: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
(0..rank).for_each(|i| {
module.scalar_znx_automorphism(
module.galois_element_inv(p0 * p1),
&mut sk_auto.data,
i,
&sk.data,
i,
);
});
let sk_auto_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_auto);
(0..auto_key.rank_in()).for_each(|col_i| {
(0..auto_key.rows()).for_each(|row_i| {
auto_key.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.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 = pt.data.std(0, basek).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
module.n() 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,676 @@
use backend::{FFT64, Module, ScalarZnx, ScalarZnxAlloc, ScalarZnxToMut, ScratchOwned, Stats, VecZnxOps, ZnxViewMut};
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},
};
#[test]
fn encrypt_sk() {
let log_n: usize = 8;
let basek: usize = 12;
let k_ksk: usize = 54;
let digits: usize = k_ksk / basek;
(1..4).for_each(|rank_in| {
(1..4).for_each(|rank_out| {
(1..digits + 1).for_each(|di| {
println!(
"test encrypt_sk digits: {} ranks: ({} {})",
di, rank_in, rank_out
);
test_encrypt_sk(log_n, basek, k_ksk, di, rank_in, rank_out, 3.2);
});
});
});
}
#[test]
fn key_switch() {
let log_n: usize = 8;
let basek: usize = 12;
let k_in: usize = 60;
let digits: usize = k_in.div_ceil(basek);
(1..4).for_each(|rank_in_s0s1| {
(1..4).for_each(|rank_out_s0s1| {
(1..4).for_each(|rank_out_s1s2| {
(1..digits + 1).for_each(|di| {
let k_ksk: usize = k_in + basek * di;
println!(
"test key_switch digits: {} ranks: ({},{},{})",
di, rank_in_s0s1, rank_out_s0s1, rank_out_s1s2
);
let k_out: usize = k_ksk; // Better capture noise.
test_key_switch(
log_n,
basek,
k_out,
k_in,
k_ksk,
di,
rank_in_s0s1,
rank_out_s0s1,
rank_out_s1s2,
3.2,
);
})
})
});
});
}
#[test]
fn key_switch_inplace() {
let log_n: usize = 8;
let basek: usize = 12;
let k_ct: usize = 60;
let digits: usize = k_ct.div_ceil(basek);
(1..4).for_each(|rank_in_s0s1| {
(1..4).for_each(|rank_out_s0s1| {
(1..digits + 1).for_each(|di| {
let k_ksk: usize = k_ct + basek * di;
println!(
"test key_switch_inplace digits: {} ranks: ({},{})",
di, rank_in_s0s1, rank_out_s0s1
);
test_key_switch_inplace(
log_n,
basek,
k_ct,
k_ksk,
di,
rank_in_s0s1,
rank_out_s0s1,
3.2,
);
});
});
});
}
#[test]
fn external_product() {
let log_n: usize = 8;
let basek: usize = 12;
let k_in: usize = 60;
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| {
let k_ggsw: usize = k_in + basek * di;
println!(
"test external_product digits: {} ranks: ({} {})",
di, rank_in, rank_out
);
let k_out: usize = k_in; // Better capture noise.
test_external_product(
log_n, basek, k_out, k_in, k_ggsw, di, rank_in, rank_out, 3.2,
);
});
});
});
}
#[test]
fn external_product_inplace() {
let log_n: usize = 5;
let basek: usize = 12;
let k_ct: usize = 60;
let digits: usize = k_ct.div_ceil(basek);
(1..4).for_each(|rank_in| {
(1..4).for_each(|rank_out| {
(1..digits).for_each(|di| {
let k_ggsw: usize = k_ct + basek * di;
println!(
"test external_product_inplace digits: {} ranks: ({} {})",
di, rank_in, rank_out
);
test_external_product_inplace(log_n, basek, k_ct, k_ggsw, di, rank_in, rank_out, 3.2);
});
});
});
}
fn test_encrypt_sk(log_n: usize, basek: usize, k_ksk: usize, digits: usize, rank_in: usize, rank_out: usize, sigma: f64) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = (k_ksk - digits * basek) / (digits * basek);
let mut ksk: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_in, rank_out);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
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 = ScratchOwned::new(
GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ksk),
);
let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
sk_in.fill_ternary_prob(0.5, &mut source_xs);
let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
sk_out.fill_ternary_prob(0.5, &mut source_xs);
let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
ksk.generate_from_sk(
&module,
&sk_in,
&sk_out_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut ct_glwe_fourier: FourierGLWECiphertext<Vec<u8>, FFT64> =
FourierGLWECiphertext::alloc(&module, basek, k_ksk, rank_out);
(0..ksk.rank_in()).for_each(|col_i| {
(0..ksk.rows()).for_each(|row_i| {
ksk.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
ct_glwe_fourier.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits - 1) + row_i * digits,
&sk_in.data,
col_i,
);
let std_pt: f64 = pt.data.std(0, basek) * (k_ksk as f64).exp2();
assert!((sigma - std_pt).abs() <= 0.5, "{} {}", sigma, std_pt);
});
});
}
fn test_key_switch(
log_n: usize,
basek: usize,
k_out: usize,
k_in: usize,
k_ksk: usize,
digits: usize,
rank_in_s0s1: usize,
rank_out_s0s1: usize,
rank_out_s1s2: usize,
sigma: f64,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = k_in.div_ceil(basek * digits);
let digits_in: usize = 1;
let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
&module,
basek,
k_in,
rows,
digits_in,
rank_in_s0s1,
rank_out_s0s1,
);
let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
&module,
basek,
k_ksk,
rows,
digits,
rank_out_s0s1,
rank_out_s1s2,
);
let mut ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = GLWESwitchingKey::alloc(
&module,
basek,
k_out,
rows,
digits_in,
rank_in_s0s1,
rank_out_s1s2,
);
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 = ScratchOwned::new(
GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_in_s0s1 | rank_out_s0s1)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_out)
| GLWESwitchingKey::keyswitch_scratch_space(
&module,
basek,
k_out,
k_in,
k_ksk,
digits,
ct_gglwe_s1s2.rank_in(),
ct_gglwe_s1s2.rank_out(),
),
);
let mut sk0: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in_s0s1);
sk0.fill_ternary_prob(0.5, &mut source_xs);
let mut sk1: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out_s0s1);
sk1.fill_ternary_prob(0.5, &mut source_xs);
let sk1_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk1);
let mut sk2: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out_s1s2);
sk2.fill_ternary_prob(0.5, &mut source_xs);
let sk2_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk2);
// gglwe_{s1}(s0) = s0 -> s1
ct_gglwe_s0s1.generate_from_sk(
&module,
&sk0,
&sk1_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
ct_gglwe_s1s2.generate_from_sk(
&module,
&sk1,
&sk2_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
ct_gglwe_s0s2.keyswitch(&module, &ct_gglwe_s0s1, &ct_gglwe_s1s2, scratch.borrow());
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> =
FourierGLWECiphertext::alloc(&module, basek, k_out, rank_out_s1s2);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
(0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
(0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk0.data,
col_i,
);
let noise_have: f64 = pt.data.std(0, basek).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
module.n() as f64,
basek * digits,
0.5,
0.5,
0f64,
sigma * sigma,
0f64,
rank_out_s0s1 as f64,
k_in,
k_ksk,
);
assert!(
(noise_have - noise_want).abs() <= 1.0,
"{} {}",
noise_have,
noise_want
);
});
});
}
fn test_key_switch_inplace(
log_n: usize,
basek: usize,
k_ct: usize,
k_ksk: usize,
digits: usize,
rank_in: usize,
rank_out: usize,
sigma: f64,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = k_ct.div_ceil(basek * digits);
let digits_in: usize = 1;
let mut ct_gglwe_s0s1: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
let mut ct_gglwe_s1s2: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_ksk, rows, digits, rank_out, rank_out);
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 = ScratchOwned::new(
GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ksk, rank_out)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ksk)
| GLWESwitchingKey::keyswitch_inplace_scratch_space(&module, basek, k_ct, k_ksk, digits, rank_out),
);
let mut sk0: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
sk0.fill_ternary_prob(0.5, &mut source_xs);
let mut sk1: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
sk1.fill_ternary_prob(0.5, &mut source_xs);
let sk1_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk1);
let mut sk2: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
sk2.fill_ternary_prob(0.5, &mut source_xs);
let sk2_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk2);
// gglwe_{s1}(s0) = s0 -> s1
ct_gglwe_s0s1.generate_from_sk(
&module,
&sk0,
&sk1_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s2}(s1) -> s1 -> s2
ct_gglwe_s1s2.generate_from_sk(
&module,
&sk1,
&sk2_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
ct_gglwe_s0s1.keyswitch_inplace(&module, &ct_gglwe_s1s2, scratch.borrow());
let ct_gglwe_s0s2: GLWESwitchingKey<Vec<u8>, FFT64> = ct_gglwe_s0s1;
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_ct, rank_out);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
(0..ct_gglwe_s0s2.rank_in()).for_each(|col_i| {
(0..ct_gglwe_s0s2.rows()).for_each(|row_i| {
ct_gglwe_s0s2.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.decrypt(&module, &mut pt, &sk2_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk0.data,
col_i,
);
let noise_have: f64 = pt.data.std(0, basek).log2();
let noise_want: f64 = log2_std_noise_gglwe_product(
module.n() as f64,
basek * digits,
0.5,
0.5,
0f64,
sigma * sigma,
0f64,
rank_out as f64,
k_ct,
k_ksk,
);
assert!(
(noise_have - noise_want).abs() <= 1.0,
"{} {}",
noise_have,
noise_want
);
});
});
}
fn test_external_product(
log_n: usize,
basek: usize,
k_out: usize,
k_in: usize,
k_ggsw: usize,
digits: usize,
rank_in: usize,
rank_out: usize,
sigma: f64,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = k_in.div_ceil(basek * digits);
let digits_in: usize = 1;
let mut ct_gglwe_in: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_in, rows, digits_in, rank_in, rank_out);
let mut ct_gglwe_out: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_out, rows, digits_in, rank_in, rank_out);
let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
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 = ScratchOwned::new(
GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_in, rank_out)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_out)
| GLWESwitchingKey::external_product_scratch_space(&module, basek, k_out, k_in, k_ggsw, digits, rank_out)
| GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
);
let r: usize = 1;
pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
sk_in.fill_ternary_prob(0.5, &mut source_xs);
let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
sk_out.fill_ternary_prob(0.5, &mut source_xs);
let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
// gglwe_{s1}(s0) = s0 -> s1
ct_gglwe_in.generate_from_sk(
&module,
&sk_in,
&sk_out_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
ct_rgsw.encrypt_sk(
&module,
&pt_rgsw,
&sk_out_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow());
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_out, rank_out);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_out);
(0..rank_in).for_each(|i| {
module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
});
(0..rank_in).for_each(|col_i| {
(0..ct_gglwe_out.rows()).for_each(|row_i| {
ct_gglwe_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk_in.data,
col_i,
);
let noise_have: f64 = pt.data.std(0, basek).log2();
let var_gct_err_lhs: f64 = sigma * sigma;
let var_gct_err_rhs: f64 = 0f64;
let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
let var_a0_err: f64 = sigma * sigma;
let var_a1_err: f64 = 1f64 / 12f64;
let noise_want: f64 = noise_ggsw_product(
module.n() as f64,
basek * digits,
0.5,
var_msg,
var_a0_err,
var_a1_err,
var_gct_err_lhs,
var_gct_err_rhs,
rank_out as f64,
k_in,
k_ggsw,
);
assert!(
(noise_have - noise_want).abs() <= 1.0,
"{} {}",
noise_have,
noise_want
);
});
});
}
fn test_external_product_inplace(
log_n: usize,
basek: usize,
k_ct: usize,
k_ggsw: usize,
digits: usize,
rank_in: usize,
rank_out: usize,
sigma: f64,
) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = k_ct.div_ceil(basek * digits);
let digits_in: usize = 1;
let mut ct_gglwe: GLWESwitchingKey<Vec<u8>, FFT64> =
GLWESwitchingKey::alloc(&module, basek, k_ct, rows, digits_in, rank_in, rank_out);
let mut ct_rgsw: GGSWCiphertext<Vec<u8>, FFT64> = GGSWCiphertext::alloc(&module, basek, k_ggsw, rows, digits, rank_out);
let mut pt_rgsw: ScalarZnx<Vec<u8>> = module.new_scalar_znx(1);
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 = ScratchOwned::new(
GLWESwitchingKey::encrypt_sk_scratch_space(&module, basek, k_ct, rank_out)
| FourierGLWECiphertext::decrypt_scratch_space(&module, basek, k_ct)
| GLWESwitchingKey::external_product_inplace_scratch_space(&module, basek, k_ct, k_ggsw, digits, rank_out)
| GGSWCiphertext::encrypt_sk_scratch_space(&module, basek, k_ggsw, rank_out),
);
let r: usize = 1;
pt_rgsw.to_mut().raw_mut()[r] = 1; // X^{r}
let mut sk_in: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_in);
sk_in.fill_ternary_prob(0.5, &mut source_xs);
let mut sk_out: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank_out);
sk_out.fill_ternary_prob(0.5, &mut source_xs);
let sk_out_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk_out);
// gglwe_{s1}(s0) = s0 -> s1
ct_gglwe.generate_from_sk(
&module,
&sk_in,
&sk_out_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
ct_rgsw.encrypt_sk(
&module,
&pt_rgsw,
&sk_out_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
// gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k)
ct_gglwe.external_product_inplace(&module, &ct_rgsw, scratch.borrow());
let mut ct_glwe_dft: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k_ct, rank_out);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct);
(0..rank_in).for_each(|i| {
module.vec_znx_rotate_inplace(r as i64, &mut sk_in.data, i); // * X^{r}
});
(0..rank_in).for_each(|col_i| {
(0..ct_gglwe.rows()).for_each(|row_i| {
ct_gglwe.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
ct_glwe_dft.decrypt(&module, &mut pt, &sk_out_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(
&mut pt.data,
0,
(digits_in - 1) + row_i * digits_in,
&sk_in.data,
col_i,
);
let noise_have: f64 = pt.data.std(0, basek).log2();
let var_gct_err_lhs: f64 = sigma * sigma;
let var_gct_err_rhs: f64 = 0f64;
let var_msg: f64 = 1f64 / module.n() as f64; // X^{k}
let var_a0_err: f64 = sigma * sigma;
let var_a1_err: f64 = 1f64 / 12f64;
let noise_want: f64 = noise_ggsw_product(
module.n() as f64,
basek * digits,
0.5,
var_msg,
var_a0_err,
var_a1_err,
var_gct_err_lhs,
var_gct_err_rhs,
rank_out as f64,
k_ct,
k_ggsw,
);
assert!(
(noise_have - noise_want).abs() <= 1.0,
"{} {}",
noise_have,
noise_want
);
});
});
}

View File

@@ -0,0 +1,3 @@
pub mod automorphism_key;
pub mod gglwe;
pub mod tensor_key;

View File

@@ -0,0 +1,69 @@
use backend::{FFT64, Module, ScalarZnxDftOps, ScratchOwned, Stats, VecZnxOps};
use sampling::source::Source;
use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWEPlaintext, GLWESecret, GLWETensorKey, GetRow, Infos};
#[test]
fn encrypt_sk() {
let log_n: usize = 8;
(1..4).for_each(|rank| {
println!("test encrypt_sk rank: {}", rank);
test_encrypt_sk(log_n, 16, 54, 3.2, rank);
});
}
fn test_encrypt_sk(log_n: usize, basek: usize, k: usize, sigma: f64, rank: usize) {
let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
let rows: usize = k / basek;
let mut tensor_key: GLWETensorKey<Vec<u8>, FFT64> = GLWETensorKey::alloc(&module, basek, k, rows, 1, 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 = ScratchOwned::new(GLWETensorKey::generate_from_sk_scratch_space(
&module,
basek,
tensor_key.k(),
rank,
));
let mut sk: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, rank);
sk.fill_ternary_prob(0.5, &mut source_xs);
let sk_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::from(&module, &sk);
tensor_key.generate_from_sk(
&module,
&sk_dft,
&mut source_xa,
&mut source_xe,
sigma,
scratch.borrow(),
);
let mut ct_glwe_fourier: FourierGLWECiphertext<Vec<u8>, FFT64> = FourierGLWECiphertext::alloc(&module, basek, k, rank);
let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k);
let mut sk_ij_dft: FourierGLWESecret<Vec<u8>, FFT64> = FourierGLWESecret::alloc(&module, 1);
let mut sk_ij: GLWESecret<Vec<u8>> = GLWESecret::alloc(&module, 1);
(0..rank).for_each(|i| {
(0..rank).for_each(|j| {
module.svp_apply(&mut sk_ij_dft.data, 0, &sk_dft.data, i, &sk_dft.data, j);
module.scalar_znx_idft(&mut sk_ij.data, 0, &sk_ij_dft.data, 0, scratch.borrow());
(0..tensor_key.rank_in()).for_each(|col_i| {
(0..tensor_key.rows()).for_each(|row_i| {
tensor_key
.at(i, j)
.get_row(&module, row_i, col_i, &mut ct_glwe_fourier);
ct_glwe_fourier.decrypt(&module, &mut pt, &sk_dft, scratch.borrow());
module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk_ij.data, col_i);
let std_pt: f64 = pt.data.std(0, basek) * (k as f64).exp2();
assert!((sigma - std_pt).abs() <= 0.5, "{} {}", sigma, std_pt);
});
});
})
})
}