Dev serialization (#64)

* Added compressed serialization for GLWECiphertext + Ciphertext decompression

* Added compressed serialization for GGLWECiphertext & GLWESwitchingkey

* generalized automorphism test

* Removed ops on scalar_znx, replaced by as_vec_znx/as_vec_znx_mut and then call op on vec_znx

* Added tests for automorphism key encryption

* Added tensorkey compressed

* added ggsw compressed
This commit is contained in:
Jean-Philippe Bossuat
2025-08-12 17:43:28 +02:00
committed by GitHub
parent 4c59733566
commit 9aa4b1f1e2
68 changed files with 3430 additions and 1695 deletions

View File

@@ -64,7 +64,7 @@ impl<DataSelf: DataMut> GGSWCiphertext<DataSelf> {
(0..rank + 1).for_each(|col_j| {
// rlwe encrypt of vec_znx_pt into vec_znx_ct
self.at_mut(row_i, col_j).encrypt_sk_private(
self.at_mut(row_i, col_j).encrypt_sk_internal(
module,
Some((&tmp_pt, col_j)),
sk,

View File

@@ -1,6 +1,6 @@
use backend::hal::{
api::{MatZnxAlloc, MatZnxAllocBytes, VmpPMatAlloc, VmpPMatAllocBytes, VmpPMatPrepare},
layouts::{Backend, Data, DataMut, DataRef, MatZnx, Module, ReaderFrom, Scratch, VmpPMat, WriterTo},
layouts::{Backend, Data, DataMut, DataRef, MatZnx, Module, ReaderFrom, WriterTo},
};
use crate::{GLWECiphertext, Infos};
@@ -135,125 +135,3 @@ impl<D: DataRef> WriterTo for GGSWCiphertext<D> {
self.data.write_to(writer)
}
}
#[derive(PartialEq, Eq)]
pub struct GGSWCiphertextExec<D: Data, B: Backend> {
pub(crate) data: VmpPMat<D, B>,
pub(crate) basek: usize,
pub(crate) k: usize,
pub(crate) digits: usize,
}
impl<B: Backend> GGSWCiphertextExec<Vec<u8>, B> {
pub fn alloc(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self
where
Module<B>: GGSWLayoutFamily<B>,
{
let size: usize = k.div_ceil(basek);
debug_assert!(digits > 0, "invalid ggsw: `digits` == 0");
debug_assert!(
size > digits,
"invalid ggsw: ceil(k/basek): {} <= digits: {}",
size,
digits
);
assert!(
rows * digits <= size,
"invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
rows,
digits,
size
);
Self {
data: module.vmp_pmat_alloc(rows, rank + 1, rank + 1, k.div_ceil(basek)),
basek,
k: k,
digits,
}
}
pub fn bytes_of(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize
where
Module<B>: GGSWLayoutFamily<B>,
{
let size: usize = k.div_ceil(basek);
debug_assert!(
size > digits,
"invalid ggsw: ceil(k/basek): {} <= digits: {}",
size,
digits
);
assert!(
rows * digits <= size,
"invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
rows,
digits,
size
);
module.vmp_pmat_alloc_bytes(rows, rank + 1, rank + 1, size)
}
pub fn from<DataOther: DataRef>(
module: &Module<B>,
other: &GGSWCiphertext<DataOther>,
scratch: &mut Scratch<B>,
) -> GGSWCiphertextExec<Vec<u8>, B>
where
Module<B>: GGSWLayoutFamily<B>,
{
let mut ggsw_exec: GGSWCiphertextExec<Vec<u8>, B> = Self::alloc(
module,
other.basek(),
other.k(),
other.rows(),
other.digits(),
other.rank(),
);
ggsw_exec.prepare(module, other, scratch);
ggsw_exec
}
}
impl<D: Data, B: Backend> Infos for GGSWCiphertextExec<D, B> {
type Inner = VmpPMat<D, B>;
fn inner(&self) -> &Self::Inner {
&self.data
}
fn basek(&self) -> usize {
self.basek
}
fn k(&self) -> usize {
self.k
}
}
impl<D: Data, B: Backend> GGSWCiphertextExec<D, B> {
pub fn rank(&self) -> usize {
self.data.cols_out() - 1
}
pub fn digits(&self) -> usize {
self.digits
}
}
impl<DataSelf: DataMut, B: Backend> GGSWCiphertextExec<DataSelf, B> {
pub fn prepare<DataOther>(&mut self, module: &Module<B>, other: &GGSWCiphertext<DataOther>, scratch: &mut Scratch<B>)
where
DataOther: DataRef,
Module<B>: GGSWLayoutFamily<B>,
{
module.vmp_prepare(&mut self.data, &other.data, scratch);
self.k = other.k;
self.basek = other.basek;
self.digits = other.digits;
}
}

View File

@@ -0,0 +1,80 @@
use backend::hal::{
api::{MatZnxAlloc, MatZnxAllocBytes, VecZnxCopy, VecZnxFillUniform},
layouts::{Backend, Data, DataMut, DataRef, MatZnx, Module, ReaderFrom, WriterTo},
};
use crate::{GGLWECiphertextCompressed, GGSWCiphertext, Infos};
#[derive(PartialEq, Eq)]
pub struct GGSWCiphertextCompressed<D: Data> {
pub(crate) data: GGLWECiphertextCompressed<D>,
}
impl GGSWCiphertextCompressed<Vec<u8>> {
pub fn alloc<B: Backend>(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self
where
Module<B>: MatZnxAlloc,
{
GGSWCiphertextCompressed {
data: GGLWECiphertextCompressed::alloc(module, basek, k, rows, digits, rank, rank),
}
}
pub fn bytes_of<B: Backend>(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize
where
Module<B>: MatZnxAllocBytes,
{
GGLWECiphertextCompressed::bytes_of(module, basek, k, rows, digits, rank)
}
}
impl<D: Data> Infos for GGSWCiphertextCompressed<D> {
type Inner = MatZnx<D>;
fn inner(&self) -> &Self::Inner {
self.data.inner()
}
fn basek(&self) -> usize {
self.data.basek()
}
fn k(&self) -> usize {
self.data.k()
}
}
impl<D: Data> GGSWCiphertextCompressed<D> {
pub fn rank(&self) -> usize {
self.data.rank()
}
pub fn digits(&self) -> usize {
self.data.digits()
}
}
impl<D: DataMut> ReaderFrom for GGSWCiphertextCompressed<D> {
fn read_from<R: std::io::Read>(&mut self, reader: &mut R) -> std::io::Result<()> {
self.data.read_from(reader)
}
}
impl<D: DataRef> WriterTo for GGSWCiphertextCompressed<D> {
fn write_to<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
self.data.write_to(writer)
}
}
impl<D: DataMut> GGSWCiphertext<D> {
pub fn decompress<DataOther: DataRef, B: Backend>(&mut self, module: &Module<B>, other: &GGSWCiphertextCompressed<DataOther>)
where
Module<B>: VecZnxFillUniform + VecZnxCopy,
{
let rows = self.rows();
(0..rows).for_each(|row_i| {
self.at_mut(row_i, 0)
.decompress(module, &other.data.at(row_i, 0));
});
}
}

View File

@@ -0,0 +1,128 @@
use backend::hal::{
api::{VmpPMatAlloc, VmpPMatAllocBytes, VmpPMatPrepare},
layouts::{Backend, Data, DataMut, DataRef, Module, Scratch, VmpPMat},
};
use crate::{GGSWCiphertext, GGSWLayoutFamily, Infos};
#[derive(PartialEq, Eq)]
pub struct GGSWCiphertextExec<D: Data, B: Backend> {
pub(crate) data: VmpPMat<D, B>,
pub(crate) basek: usize,
pub(crate) k: usize,
pub(crate) digits: usize,
}
impl<B: Backend> GGSWCiphertextExec<Vec<u8>, B> {
pub fn alloc(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self
where
Module<B>: GGSWLayoutFamily<B>,
{
let size: usize = k.div_ceil(basek);
debug_assert!(digits > 0, "invalid ggsw: `digits` == 0");
debug_assert!(
size > digits,
"invalid ggsw: ceil(k/basek): {} <= digits: {}",
size,
digits
);
assert!(
rows * digits <= size,
"invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
rows,
digits,
size
);
Self {
data: module.vmp_pmat_alloc(rows, rank + 1, rank + 1, k.div_ceil(basek)),
basek,
k: k,
digits,
}
}
pub fn bytes_of(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize
where
Module<B>: GGSWLayoutFamily<B>,
{
let size: usize = k.div_ceil(basek);
debug_assert!(
size > digits,
"invalid ggsw: ceil(k/basek): {} <= digits: {}",
size,
digits
);
assert!(
rows * digits <= size,
"invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
rows,
digits,
size
);
module.vmp_pmat_alloc_bytes(rows, rank + 1, rank + 1, size)
}
pub fn from<DataOther: DataRef>(
module: &Module<B>,
other: &GGSWCiphertext<DataOther>,
scratch: &mut Scratch<B>,
) -> GGSWCiphertextExec<Vec<u8>, B>
where
Module<B>: GGSWLayoutFamily<B>,
{
let mut ggsw_exec: GGSWCiphertextExec<Vec<u8>, B> = Self::alloc(
module,
other.basek(),
other.k(),
other.rows(),
other.digits(),
other.rank(),
);
ggsw_exec.prepare(module, other, scratch);
ggsw_exec
}
}
impl<D: Data, B: Backend> Infos for GGSWCiphertextExec<D, B> {
type Inner = VmpPMat<D, B>;
fn inner(&self) -> &Self::Inner {
&self.data
}
fn basek(&self) -> usize {
self.basek
}
fn k(&self) -> usize {
self.k
}
}
impl<D: Data, B: Backend> GGSWCiphertextExec<D, B> {
pub fn rank(&self) -> usize {
self.data.cols_out() - 1
}
pub fn digits(&self) -> usize {
self.digits
}
}
impl<DataSelf: DataMut, B: Backend> GGSWCiphertextExec<DataSelf, B> {
pub fn prepare<DataOther>(&mut self, module: &Module<B>, other: &GGSWCiphertext<DataOther>, scratch: &mut Scratch<B>)
where
DataOther: DataRef,
Module<B>: GGSWLayoutFamily<B>,
{
module.vmp_prepare(&mut self.data, &other.data, scratch);
self.k = other.k;
self.basek = other.basek;
self.digits = other.digits;
}
}

View File

@@ -3,12 +3,16 @@ mod encryption;
mod external_product;
mod keyswitch;
mod layout;
mod layout_compressed;
mod layout_exec;
mod noise;
pub use encryption::GGSWEncryptSkFamily;
pub use keyswitch::GGSWKeySwitchFamily;
pub use layout::{GGSWCiphertext, GGSWCiphertextExec, GGSWLayoutFamily};
pub use noise::GGSWAssertNoiseFamily;
pub use encryption::*;
pub use keyswitch::*;
pub use layout::*;
pub use layout_compressed::*;
pub use layout_exec::*;
pub use noise::*;
#[cfg(test)]
mod test;

View File

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

View File

@@ -1,8 +1,8 @@
use backend::hal::{
api::{
MatZnxAlloc, ScalarZnxAlloc, ScalarZnxAllocBytes, ScalarZnxAutomorphism, ScalarZnxAutomorphismInplace, ScratchOwnedAlloc,
ScratchOwnedBorrow, VecZnxAddScalarInplace, VecZnxAlloc, VecZnxAllocBytes, VecZnxAutomorphismInplace,
VecZnxRotateInplace, VecZnxStd, VecZnxSubABInplace, VecZnxSwithcDegree, ZnxViewMut,
MatZnxAlloc, ScalarZnxAlloc, ScalarZnxAllocBytes, ScratchOwnedAlloc, ScratchOwnedBorrow, VecZnxAddScalarInplace,
VecZnxAlloc, VecZnxAllocBytes, VecZnxAutomorphism, VecZnxAutomorphismInplace, VecZnxRotateInplace, VecZnxStd,
VecZnxSubABInplace, VecZnxSwithcDegree, ZnxViewMut,
},
layouts::{Backend, Module, ScalarZnx, ScalarZnxToMut, ScratchOwned},
oep::{
@@ -321,8 +321,8 @@ pub(crate) fn test_automorphism<B: Backend>(
+ GGLWEExecLayoutFamily<B>
+ VecZnxSwithcDegree
+ VecZnxAutomorphismInplace
+ ScalarZnxAutomorphismInplace
+ ScalarZnxAutomorphism,
+ VecZnxAutomorphismInplace
+ VecZnxAutomorphism,
B: TestScratchFamily<B>,
{
let rows: usize = k_in.div_ceil(basek * digits);
@@ -393,7 +393,7 @@ pub(crate) fn test_automorphism<B: Backend>(
ct_out.automorphism(module, &ct_in, &auto_key_exec, &tsk_exec, scratch.borrow());
module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
module.vec_znx_automorphism_inplace(p, &mut pt_scalar.as_vec_znx_mut(), 0);
let max_noise = |col_j: usize| -> f64 {
noise_ggsw_keyswitch(
@@ -433,8 +433,8 @@ pub(crate) fn test_automorphism_inplace<B: Backend>(
+ GGLWEExecLayoutFamily<B>
+ VecZnxSwithcDegree
+ VecZnxAutomorphismInplace
+ ScalarZnxAutomorphismInplace
+ ScalarZnxAutomorphism,
+ VecZnxAutomorphism
+ VecZnxAutomorphismInplace,
B: TestScratchFamily<B>,
{
let rows: usize = k_ct.div_ceil(digits * basek);
@@ -501,7 +501,7 @@ pub(crate) fn test_automorphism_inplace<B: Backend>(
ct.automorphism_inplace(module, &auto_key_exec, &tsk_exec, scratch.borrow());
module.scalar_znx_automorphism_inplace(p, &mut pt_scalar, 0);
module.vec_znx_automorphism_inplace(p, &mut pt_scalar.as_vec_znx_mut(), 0);
let max_noise = |col_j: usize| -> f64 {
noise_ggsw_keyswitch(
@@ -595,7 +595,7 @@ pub(crate) fn test_external_product<B: Backend>(
ct_ggsw_lhs_out.external_product(module, &ct_ggsw_lhs_in, &ct_rhs_exec, scratch.borrow());
module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs.as_vec_znx_mut(), 0);
let var_gct_err_lhs: f64 = sigma * sigma;
let var_gct_err_rhs: f64 = 0f64;
@@ -695,7 +695,7 @@ pub(crate) fn test_external_product_inplace<B: Backend>(
ct_ggsw_lhs.external_product_inplace(module, &ct_rhs_exec, scratch.borrow());
module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs, 0);
module.vec_znx_rotate_inplace(k as i64, &mut pt_ggsw_lhs.as_vec_znx_mut(), 0);
let var_gct_err_lhs: f64 = sigma * sigma;
let var_gct_err_rhs: f64 = 0f64;

View File

@@ -1,2 +1,2 @@
mod cpu_spqlios;
mod generic_tests;
mod test_fft64;