From 30b5edc5360fe15a7be233726a5f6b328c26a637 Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Fri, 4 Jul 2025 17:32:19 +0530 Subject: [PATCH 1/8] Fix `glwe/test_fft64/encrypt_sk` for case `k_pt < basek` --- core/src/glwe/test_fft64/encryption.rs | 29 ++++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/core/src/glwe/test_fft64/encryption.rs b/core/src/glwe/test_fft64/encryption.rs index 2d82575..bba9e4d 100644 --- a/core/src/glwe/test_fft64/encryption.rs +++ b/core/src/glwe/test_fft64/encryption.rs @@ -8,8 +8,10 @@ use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaint fn encrypt_sk() { let log_n: usize = 8; (1..4).for_each(|rank| { - println!("test encrypt_sk rank: {}", rank); - test_encrypt_sk(log_n, 8, 54, 30, 3.2, rank); + [2, 10, 30].iter().for_each(|k_pt| { + println!("test encrypt_sk rank: {}, k_pt: {k_pt}", rank); + test_encrypt_sk(log_n, 8, 54, *k_pt, 3.2, rank); + }); }); } @@ -51,12 +53,21 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: let sk_dft: FourierGLWESecret, FFT64> = FourierGLWESecret::from(&module, &sk); let mut data_want: Vec = vec![0i64; module.n()]; - - data_want - .iter_mut() - .for_each(|x| *x = source_xa.next_i64() & 0xFF); - - pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10); + if k_pt < 64 { + let pt_max = 1 << k_pt; + data_want.iter_mut().for_each(|x| { + let v = source_xa.next_u64n(pt_max, pt_max - 1); + *x = if v >= pt_max / 2 { + -((pt_max - v) as i64) + } else { + v as i64 + }; + }); + } else { + data_want.iter_mut().for_each(|x| *x = source_xa.next_i64()); + } + pt.data + .encode_vec_i64(0, basek, k_pt, &data_want, std::cmp::min(k_pt, 64)); ct.encrypt_sk( &module, @@ -83,7 +94,7 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: let b_scaled = (*b as f64) / scale; assert!( (*a as f64 - b_scaled).abs() < 0.1, - "{} {}", + "a={} b={}", *a as f64, b_scaled ) From 2a5e0e7dc30f3b62b02371c68e7b9f52ba93d0ed Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Fri, 4 Jul 2025 17:56:30 +0530 Subject: [PATCH 2/8] remove `OsRng` from `sample/src/source.rs` --- sampling/src/source.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/sampling/src/source.rs b/sampling/src/source.rs index c356163..fe5f641 100644 --- a/sampling/src/source.rs +++ b/sampling/src/source.rs @@ -1,6 +1,6 @@ use rand_chacha::ChaCha8Rng; use rand_chacha::rand_core::SeedableRng; -use rand_core::{OsRng, RngCore, TryRngCore}; +use rand_core::RngCore; const MAXF64: f64 = 9007199254740992.0; @@ -8,12 +8,6 @@ pub struct Source { source: ChaCha8Rng, } -pub fn new_seed() -> [u8; 32] { - let mut seed = [0u8; 32]; - OsRng.try_fill_bytes(&mut seed).unwrap(); - seed -} - impl Source { pub fn new(seed: [u8; 32]) -> Source { Source { @@ -21,14 +15,11 @@ impl Source { } } - pub fn new_seed(&mut self) -> [u8; 32] { - let mut seed: [u8; 32] = [0u8; 32]; - self.source.fill_bytes(&mut seed); - seed - } - pub fn branch(&mut self) -> Self { - Source::new(self.new_seed()) + let mut seed = [0; 32]; + self.source.fill_bytes(&mut seed); + + Source::new(seed) } #[inline(always)] From 463731948de4a9a920e1889fbe479adcbab68199 Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Sun, 6 Jul 2025 12:40:08 +0530 Subject: [PATCH 3/8] Bug fixes - fixed `encoding.rs/decode_coeff_i64` for the case `k < basek2` - `glwe/external_product.rs/external_product_scratch_space` did not allocate enough space for [`a_dft`](https://github.com/phantomzone-org/poulpy/blob/829b8be610bb518765be3eb857e58ffe09683c2d/core/src/glwe/external_product.rs#L83) when `k_out < k_ggsw`. Fixed by replacing `in_size` with `ggsw_size`. - `glwe/ops.rs/add` sets `basek` and `k`of `self` GLWECiphertext whic is invalid because the `size` which depends on `inner` data remains unchanged. - (not a fix) expose `backend` from `core`. --- backend/src/encoding.rs | 10 +++++----- core/src/glwe/external_product.rs | 2 +- core/src/glwe/ops.rs | 5 +++-- core/src/lib.rs | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/backend/src/encoding.rs b/backend/src/encoding.rs index 55bba09..27d4b8c 100644 --- a/backend/src/encoding.rs +++ b/backend/src/encoding.rs @@ -269,12 +269,12 @@ fn decode_coeff_i64>(a: &VecZnx, col_i: usize, basek: usize, k let size: usize = (k + basek - 1) / basek; let data: &[i64] = a.raw(); - let mut res: i64 = data[i]; + let mut res: i64 = 0; let rem: usize = basek - (k % basek); let slice_size: usize = a.n() * a.cols(); - (0..size).for_each(|i| { - let x: i64 = data[i * slice_size]; - if i == size - 1 && rem != basek { + (0..size).for_each(|j| { + let x: i64 = data[j * slice_size + i]; + if j == size - 1 && rem != basek { let k_rem: usize = basek - rem; res = (res << k_rem) + (x >> rem); } else { @@ -320,7 +320,7 @@ mod tests { let module: Module = Module::::new(n); let basek: usize = 17; let size: usize = 5; - for k in [size * basek - 5] { + for k in [1, basek / 2, size * basek - 5] { let mut a: VecZnx<_> = module.new_vec_znx(2, size); let mut source = Source::new([0u8; 32]); let raw: &mut [i64] = a.raw_mut(); diff --git a/core/src/glwe/external_product.rs b/core/src/glwe/external_product.rs index 3ebf339..3f826f1 100644 --- a/core/src/glwe/external_product.rs +++ b/core/src/glwe/external_product.rs @@ -18,7 +18,7 @@ impl GLWECiphertext> { 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) + let vmp: usize = module.bytes_of_vec_znx_dft(rank + 1, ggsw_size) + module.vmp_apply_tmp_bytes( out_size, in_size, diff --git a/core/src/glwe/ops.rs b/core/src/glwe/ops.rs index 2beffe6..d921583 100644 --- a/core/src/glwe/ops.rs +++ b/core/src/glwe/ops.rs @@ -14,6 +14,7 @@ pub trait GLWEOps: GLWECiphertextToMut + Infos + SetMetaData { assert_eq!(b.n(), module.n()); assert_eq!(self.n(), module.n()); assert_eq!(a.basek(), b.basek()); + assert_eq!(self.basek(), a.basek()); assert!(self.rank() >= a.rank().max(b.rank())); } @@ -46,8 +47,8 @@ pub trait GLWEOps: GLWECiphertextToMut + Infos + SetMetaData { }); }); - self.set_basek(a.basek()); - self.set_k(a.k().max(b.k())); + // self.set_basek(a.basek()); + // self.set_k(a.k().max(b.k())); } fn add_inplace(&mut self, module: &Module, a: &A) diff --git a/core/src/lib.rs b/core/src/lib.rs index ba28589..eb3cbce 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -19,6 +19,7 @@ pub use glwe::{GLWECiphertext, GLWEOps, GLWEPacker, GLWEPlaintext, GLWEPublicKey pub(crate) use glwe::{GLWECiphertextToMut, GLWECiphertextToRef}; pub use lwe::{LWECiphertext, LWESecret}; +pub use backend; pub use backend::Scratch; pub use backend::ScratchOwned; From b99f43aa0fe5df37afec763afa152f7085a2987d Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Tue, 8 Jul 2025 13:53:43 +0530 Subject: [PATCH 4/8] Make `GLWECiphertextToRef/{ToMut}` implement `Infos` --- core/src/ggsw/ciphertext.rs | 2 ++ core/src/glwe/ciphertext.rs | 4 ++-- core/src/glwe/ops.rs | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/ggsw/ciphertext.rs b/core/src/ggsw/ciphertext.rs index e52f305..13049f5 100644 --- a/core/src/ggsw/ciphertext.rs +++ b/core/src/ggsw/ciphertext.rs @@ -20,6 +20,8 @@ pub struct GGSWCiphertext { impl GGSWCiphertext, FFT64> { pub fn alloc(module: &Module, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self { 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: {}", diff --git a/core/src/glwe/ciphertext.rs b/core/src/glwe/ciphertext.rs index d0fb39c..07b4264 100644 --- a/core/src/glwe/ciphertext.rs +++ b/core/src/glwe/ciphertext.rs @@ -79,7 +79,7 @@ impl + AsRef<[u8]>> SetMetaData for GLWECiphertext GLWECiphertext<&[u8]>; } @@ -93,7 +93,7 @@ impl> GLWECiphertextToRef for GLWECiphertext { } } -pub trait GLWECiphertextToMut { +pub trait GLWECiphertextToMut: Infos { fn to_mut(&mut self) -> GLWECiphertext<&mut [u8]>; } diff --git a/core/src/glwe/ops.rs b/core/src/glwe/ops.rs index d921583..9248e85 100644 --- a/core/src/glwe/ops.rs +++ b/core/src/glwe/ops.rs @@ -2,11 +2,11 @@ use backend::{FFT64, Module, Scratch, VecZnx, VecZnxOps, ZnxZero}; use crate::{GLWECiphertext, GLWECiphertextToMut, GLWECiphertextToRef, Infos, SetMetaData}; -pub trait GLWEOps: GLWECiphertextToMut + Infos + SetMetaData { +pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { fn add(&mut self, module: &Module, a: &A, b: &B) where - A: GLWECiphertextToRef + Infos, - B: GLWECiphertextToRef + Infos, + A: GLWECiphertextToRef, + B: GLWECiphertextToRef, { #[cfg(debug_assertions)] { @@ -75,8 +75,8 @@ pub trait GLWEOps: GLWECiphertextToMut + Infos + SetMetaData { fn sub(&mut self, module: &Module, a: &A, b: &B) where - A: GLWECiphertextToRef + Infos, - B: GLWECiphertextToRef + Infos, + A: GLWECiphertextToRef, + B: GLWECiphertextToRef, { #[cfg(debug_assertions)] { @@ -230,7 +230,7 @@ pub trait GLWEOps: GLWECiphertextToMut + Infos + SetMetaData { fn normalize(&mut self, module: &Module, a: &A, scratch: &mut Scratch) where - A: GLWECiphertextToRef + Infos, + A: GLWECiphertextToRef, { #[cfg(debug_assertions)] { From 64edc869d040009a055a96cdb4b1541afe0ddffc Mon Sep 17 00:00:00 2001 From: Janmajaya Mall Date: Wed, 9 Jul 2025 16:23:56 +0530 Subject: [PATCH 5/8] Two changes: - Fix setting `k` in `GlweOps` - Improve GLWEPacker API avoid accumulating beyond limit (#50) --- backend/src/vec_znx.rs | 8 ++--- core/src/elem.rs | 2 +- core/src/glwe/ops.rs | 45 +++++++++++++++++++------ core/src/glwe/packing.rs | 52 +++++++++++------------------ core/src/glwe/test_fft64/packing.rs | 50 +++++++++++---------------- 5 files changed, 79 insertions(+), 78 deletions(-) diff --git a/backend/src/vec_znx.rs b/backend/src/vec_znx.rs index 00568dd..74f9f86 100644 --- a/backend/src/vec_znx.rs +++ b/backend/src/vec_znx.rs @@ -111,10 +111,10 @@ impl + AsRef<[u8]>> VecZnx { } } - pub fn rotate(&mut self, k: i64){ - unsafe{ - (0..self.cols()).for_each(|i|{ - (0..self.size()).for_each(|j|{ + pub fn rotate(&mut self, k: i64) { + unsafe { + (0..self.cols()).for_each(|i| { + (0..self.size()).for_each(|j| { znx::znx_rotate_inplace_i64(self.n() as u64, k, self.at_mut_ptr(i, j)); }); }) diff --git a/core/src/elem.rs b/core/src/elem.rs index 9a1de39..6e15616 100644 --- a/core/src/elem.rs +++ b/core/src/elem.rs @@ -34,7 +34,7 @@ pub trait Infos { /// Returns the number of size per polynomial. fn size(&self) -> usize { let size: usize = self.inner().size(); - debug_assert_eq!(size, self.k().div_ceil(self.basek())); + debug_assert!(size >= self.k().div_ceil(self.basek())); size } diff --git a/core/src/glwe/ops.rs b/core/src/glwe/ops.rs index 9248e85..46f6bdc 100644 --- a/core/src/glwe/ops.rs +++ b/core/src/glwe/ops.rs @@ -2,7 +2,7 @@ use backend::{FFT64, Module, Scratch, VecZnx, VecZnxOps, ZnxZero}; use crate::{GLWECiphertext, GLWECiphertextToMut, GLWECiphertextToRef, Infos, SetMetaData}; -pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { +pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { fn add(&mut self, module: &Module, a: &A, b: &B) where A: GLWECiphertextToRef, @@ -14,7 +14,6 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { assert_eq!(b.n(), module.n()); assert_eq!(self.n(), module.n()); assert_eq!(a.basek(), b.basek()); - assert_eq!(self.basek(), a.basek()); assert!(self.rank() >= a.rank().max(b.rank())); } @@ -47,8 +46,8 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { }); }); - // self.set_basek(a.basek()); - // self.set_k(a.k().max(b.k())); + self.set_basek(a.basek()); + self.set_k(set_k(self, a, b)); } fn add_inplace(&mut self, module: &Module, a: &A) @@ -70,7 +69,9 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { module.vec_znx_add_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - self.set_k(a.k().max(self.k())); + if a.rank() != 0 { + self.set_k(a.k().min(self.k())); + } } fn sub(&mut self, module: &Module, a: &A, b: &B) @@ -118,7 +119,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { }); self.set_basek(a.basek()); - self.set_k(a.k().max(b.k())); + self.set_k(set_k(self, a, b)); } fn sub_inplace_ab(&mut self, module: &Module, a: &A) @@ -140,7 +141,9 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { module.vec_znx_sub_ab_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - self.set_k(a.k().max(self.k())); + if a.rank() != 0 { + self.set_k(a.k().min(self.k())); + } } fn sub_inplace_ba(&mut self, module: &Module, a: &A) @@ -162,7 +165,9 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { module.vec_znx_sub_ba_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - self.set_k(a.k().max(self.k())); + if a.rank() != 0 { + self.set_k(a.k().min(self.k())); + } } fn rotate(&mut self, module: &Module, k: i64, a: &A) @@ -184,7 +189,9 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { }); self.set_basek(a.basek()); - self.set_k(a.k()); + if a.rank() != 0 { + self.set_k(a.k().min(self.k())); + } } fn rotate_inplace(&mut self, module: &Module, k: i64) { @@ -209,6 +216,8 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { assert_eq!(self.n(), module.n()); assert_eq!(a.n(), module.n()); assert_eq!(self.rank(), a.rank()); + assert_eq!(self.k(), a.k()); + assert_eq!(self.basek(), a.basek()); } let self_mut: &mut GLWECiphertext<&mut [u8]> = &mut self.to_mut(); @@ -246,7 +255,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData { module.vec_znx_normalize(a.basek(), &mut self_mut.data, i, &a_ref.data, i, scratch); }); self.set_basek(a.basek()); - self.set_k(a.k()); + self.set_k(a.k().min(self.k())); } fn normalize_inplace(&mut self, module: &Module, scratch: &mut Scratch) { @@ -266,3 +275,19 @@ impl GLWECiphertext> { VecZnx::rsh_scratch_space(module.n()) } } + +// c = op(a, b) +fn set_k(c: &impl Infos, a: &impl Infos, b: &impl Infos) -> usize { + if a.rank() != 0 || b.rank() != 0 { + let k = if a.rank() == 0 { + b.k() + } else if b.rank() == 0 { + a.k() + } else { + a.k().min(b.k()) + }; + k.min(c.k()) + } else { + c.k() + } +} diff --git a/core/src/glwe/packing.rs b/core/src/glwe/packing.rs index 3496994..bfd7f36 100644 --- a/core/src/glwe/packing.rs +++ b/core/src/glwe/packing.rs @@ -1,7 +1,7 @@ use crate::{GLWEAutomorphismKey, GLWECiphertext, GLWEOps, Infos, ScratchCore}; use std::collections::HashMap; -use backend::{FFT64, Module, Scratch}; +use backend::{FFT64, Module, Scratch, VecZnxOps}; /// [StreamPacker] enables only the fly GLWE packing /// with constant memory of Log(N) ciphertexts. @@ -65,7 +65,7 @@ impl GLWEPacker { } /// Implicit reset of the internal state (to be called before a new packing procedure). - pub fn reset(&mut self) { + fn reset(&mut self) { for i in 0..self.accumulators.len() { self.accumulators[i].value = false; self.accumulators[i].control = false; @@ -82,9 +82,7 @@ impl GLWEPacker { GLWECiphertext::trace_galois_elements(module) } - /// Adds a GLWE ciphertext to the [StreamPacker]. And propagates - /// intermediate results among the [Accumulator]s. - /// + /// Adds a GLWE ciphertext to the [StreamPacker]. /// #Arguments /// /// * `module`: static backend FFT tables. @@ -96,11 +94,16 @@ impl GLWEPacker { pub fn add, DataAK: AsRef<[u8]>>( &mut self, module: &Module, - res: &mut Vec>>, a: Option<&GLWECiphertext>, auto_keys: &HashMap>, scratch: &mut Scratch, ) { + assert!( + self.counter < module.n(), + "Packing limit of {} reached", + module.n() >> self.log_batch + ); + pack_core( module, a, @@ -110,35 +113,18 @@ impl GLWEPacker { scratch, ); self.counter += 1 << self.log_batch; - if self.counter == module.n() { - res.push( - self.accumulators[module.log_n() - self.log_batch - 1] - .data - .clone(), - ); - self.reset(); - } } - /// Flushes all accumlators and appends the result to `res`. - pub fn flush>( - &mut self, - module: &Module, - res: &mut Vec>>, - auto_keys: &HashMap>, - scratch: &mut Scratch, - ) { - if self.counter != 0 { - while self.counter != 0 { - self.add( - module, - res, - None::<&GLWECiphertext>>, - auto_keys, - scratch, - ); - } - } + /// Flush result to`res`. + pub fn flush + AsRef<[u8]>>(&mut self, module: &Module, res: &mut GLWECiphertext) { + assert!(self.counter == module.n()); + // Copy result GLWE into res GLWE + res.copy( + module, + &self.accumulators[module.log_n() - self.log_batch - 1].data, + ); + + self.reset(); } } diff --git a/core/src/glwe/test_fft64/packing.rs b/core/src/glwe/test_fft64/packing.rs index 0e9ee71..f747697 100644 --- a/core/src/glwe/test_fft64/packing.rs +++ b/core/src/glwe/test_fft64/packing.rs @@ -74,8 +74,6 @@ fn apply() { scratch.borrow(), ); - let mut res: Vec>> = Vec::new(); - (0..module.n() >> log_batch).for_each(|i| { ct.encrypt_sk( &module, @@ -90,11 +88,10 @@ fn apply() { pt.rotate_inplace(&module, -(1 << log_batch)); // X^-batch * pt if reverse_bits_msb(i, log_n as u32) % 5 == 0 { - packer.add(&module, &mut res, Some(&ct), &auto_keys, scratch.borrow()); + packer.add(&module, Some(&ct), &auto_keys, scratch.borrow()); } else { packer.add( &module, - &mut res, None::<&GLWECiphertext>>, &auto_keys, scratch.borrow(), @@ -102,36 +99,29 @@ fn apply() { } }); - packer.flush(&module, &mut res, &auto_keys, scratch.borrow()); - packer.reset(); + let mut res = GLWECiphertext::alloc(&module, basek, k_ct, rank); + packer.flush(&module, &mut res); let mut pt_want: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_ct); - - res.iter().enumerate().for_each(|(i, res_i)| { - let mut data: Vec = vec![0i64; module.n()]; - data.iter_mut().enumerate().for_each(|(i, x)| { - if i % 5 == 0 { - *x = reverse_bits_msb(i, log_n as u32) as i64; - } - }); - pt_want.data.encode_vec_i64(0, basek, pt_k, &data, 32); - - res_i.decrypt(&module, &mut pt, &sk_dft, scratch.borrow()); - - if i & 1 == 0 { - pt.sub_inplace_ab(&module, &pt_want); - } else { - pt.add_inplace(&module, &pt_want); + let mut data: Vec = vec![0i64; module.n()]; + data.iter_mut().enumerate().for_each(|(i, x)| { + if i % 5 == 0 { + *x = reverse_bits_msb(i, log_n as u32) as i64; } - - let noise_have = pt.data.std(0, basek).log2(); - // println!("noise_have: {}", noise_have); - assert!( - noise_have < -((k_ct - basek) as f64), - "noise: {}", - noise_have - ); }); + pt_want.data.encode_vec_i64(0, basek, pt_k, &data, 32); + + res.decrypt(&module, &mut pt, &sk_dft, scratch.borrow()); + + pt.sub_inplace_ab(&module, &pt_want); + + let noise_have = pt.data.std(0, basek).log2(); + // println!("noise_have: {}", noise_have); + assert!( + noise_have < -((k_ct - basek) as f64), + "noise: {}", + noise_have + ); } #[inline(always)] From ff84e7e859256d1d40f39b63a07a42c494a907a9 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Tue, 15 Jul 2025 19:51:21 +0200 Subject: [PATCH 6/8] Revert "Fix `glwe/test_fft64/encrypt_sk` for case `k_pt < basek`" This reverts commit 30b5edc5360fe15a7be233726a5f6b328c26a637. --- core/src/glwe/test_fft64/encryption.rs | 29 ++++++++------------------ 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/core/src/glwe/test_fft64/encryption.rs b/core/src/glwe/test_fft64/encryption.rs index bba9e4d..2d82575 100644 --- a/core/src/glwe/test_fft64/encryption.rs +++ b/core/src/glwe/test_fft64/encryption.rs @@ -8,10 +8,8 @@ use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaint fn encrypt_sk() { let log_n: usize = 8; (1..4).for_each(|rank| { - [2, 10, 30].iter().for_each(|k_pt| { - println!("test encrypt_sk rank: {}, k_pt: {k_pt}", rank); - test_encrypt_sk(log_n, 8, 54, *k_pt, 3.2, rank); - }); + println!("test encrypt_sk rank: {}", rank); + test_encrypt_sk(log_n, 8, 54, 30, 3.2, rank); }); } @@ -53,21 +51,12 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: let sk_dft: FourierGLWESecret, FFT64> = FourierGLWESecret::from(&module, &sk); let mut data_want: Vec = vec![0i64; module.n()]; - if k_pt < 64 { - let pt_max = 1 << k_pt; - data_want.iter_mut().for_each(|x| { - let v = source_xa.next_u64n(pt_max, pt_max - 1); - *x = if v >= pt_max / 2 { - -((pt_max - v) as i64) - } else { - v as i64 - }; - }); - } else { - data_want.iter_mut().for_each(|x| *x = source_xa.next_i64()); - } - pt.data - .encode_vec_i64(0, basek, k_pt, &data_want, std::cmp::min(k_pt, 64)); + + data_want + .iter_mut() + .for_each(|x| *x = source_xa.next_i64() & 0xFF); + + pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10); ct.encrypt_sk( &module, @@ -94,7 +83,7 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: let b_scaled = (*b as f64) / scale; assert!( (*a as f64 - b_scaled).abs() < 0.1, - "a={} b={}", + "{} {}", *a as f64, b_scaled ) From a8e8743b5038a3995e15aaefcca6b5c51fd9b222 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Tue, 15 Jul 2025 20:09:37 +0200 Subject: [PATCH 7/8] Various fixes --- core/src/blind_rotation/cggi.rs | 5 +-- core/src/glwe/ops.rs | 40 ++++++++++-------- core/src/glwe/packing.rs | 2 +- core/src/glwe/test_fft64/encryption.rs | 57 ++++++++------------------ 4 files changed, 43 insertions(+), 61 deletions(-) diff --git a/core/src/blind_rotation/cggi.rs b/core/src/blind_rotation/cggi.rs index 2cf1d88..d9ada60 100644 --- a/core/src/blind_rotation/cggi.rs +++ b/core/src/blind_rotation/cggi.rs @@ -1,7 +1,6 @@ use backend::{ - FFT64, MatZnxDftOps, MatZnxDftScratch, Module, ScalarZnxDft, ScalarZnxDftAlloc, ScalarZnxDftOps, - Scratch, VecZnxAlloc, VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, ZnxView, - ZnxViewMut, ZnxZero, + FFT64, MatZnxDftOps, MatZnxDftScratch, Module, ScalarZnxDft, ScalarZnxDftAlloc, ScalarZnxDftOps, Scratch, VecZnxAlloc, + VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, ZnxView, ZnxViewMut, ZnxZero, }; use itertools::izip; diff --git a/core/src/glwe/ops.rs b/core/src/glwe/ops.rs index 46f6bdc..48034a5 100644 --- a/core/src/glwe/ops.rs +++ b/core/src/glwe/ops.rs @@ -47,7 +47,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { }); self.set_basek(a.basek()); - self.set_k(set_k(self, a, b)); + self.set_k(set_k_binary(self, a, b)); } fn add_inplace(&mut self, module: &Module, a: &A) @@ -69,9 +69,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { module.vec_znx_add_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - if a.rank() != 0 { - self.set_k(a.k().min(self.k())); - } + self.set_k(set_k_unary(self, a)) } fn sub(&mut self, module: &Module, a: &A, b: &B) @@ -119,7 +117,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { }); self.set_basek(a.basek()); - self.set_k(set_k(self, a, b)); + self.set_k(set_k_binary(self, a, b)); } fn sub_inplace_ab(&mut self, module: &Module, a: &A) @@ -141,9 +139,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { module.vec_znx_sub_ab_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - if a.rank() != 0 { - self.set_k(a.k().min(self.k())); - } + self.set_k(set_k_unary(self, a)) } fn sub_inplace_ba(&mut self, module: &Module, a: &A) @@ -165,9 +161,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { module.vec_znx_sub_ba_inplace(&mut self_mut.data, i, &a_ref.data, i); }); - if a.rank() != 0 { - self.set_k(a.k().min(self.k())); - } + self.set_k(set_k_unary(self, a)) } fn rotate(&mut self, module: &Module, k: i64, a: &A) @@ -189,9 +183,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { }); self.set_basek(a.basek()); - if a.rank() != 0 { - self.set_k(a.k().min(self.k())); - } + self.set_k(set_k_unary(self, a)) } fn rotate_inplace(&mut self, module: &Module, k: i64) { @@ -216,8 +208,6 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { assert_eq!(self.n(), module.n()); assert_eq!(a.n(), module.n()); assert_eq!(self.rank(), a.rank()); - assert_eq!(self.k(), a.k()); - assert_eq!(self.basek(), a.basek()); } let self_mut: &mut GLWECiphertext<&mut [u8]> = &mut self.to_mut(); @@ -227,7 +217,7 @@ pub trait GLWEOps: GLWECiphertextToMut + SetMetaData + Sized { module.vec_znx_copy(&mut self_mut.data, i, &a_ref.data, i); }); - self.set_k(a.k()); + self.set_k(a.k().min(self.size() * self.basek())); self.set_basek(a.basek()); } @@ -277,17 +267,31 @@ impl GLWECiphertext> { } // c = op(a, b) -fn set_k(c: &impl Infos, a: &impl Infos, b: &impl Infos) -> usize { +fn set_k_binary(c: &impl Infos, a: &impl Infos, b: &impl Infos) -> usize { + // If either operands is a ciphertext if a.rank() != 0 || b.rank() != 0 { + // If a is a plaintext (but b ciphertext) let k = if a.rank() == 0 { b.k() + // If b is a plaintext (but a ciphertext) } else if b.rank() == 0 { a.k() + // If a & b are both ciphertexts } else { a.k().min(b.k()) }; k.min(c.k()) + // If a & b are both plaintexts } else { c.k() } } + +// a = op(a, b) +fn set_k_unary(a: &impl Infos, b: &impl Infos) -> usize { + if a.rank() != 0 || b.rank() != 0 { + a.k().min(b.k()) + } else { + a.k() + } +} diff --git a/core/src/glwe/packing.rs b/core/src/glwe/packing.rs index bfd7f36..f38504b 100644 --- a/core/src/glwe/packing.rs +++ b/core/src/glwe/packing.rs @@ -1,7 +1,7 @@ use crate::{GLWEAutomorphismKey, GLWECiphertext, GLWEOps, Infos, ScratchCore}; use std::collections::HashMap; -use backend::{FFT64, Module, Scratch, VecZnxOps}; +use backend::{FFT64, Module, Scratch}; /// [StreamPacker] enables only the fly GLWE packing /// with constant memory of Log(N) ciphertexts. diff --git a/core/src/glwe/test_fft64/encryption.rs b/core/src/glwe/test_fft64/encryption.rs index 2d82575..55cd5a9 100644 --- a/core/src/glwe/test_fft64/encryption.rs +++ b/core/src/glwe/test_fft64/encryption.rs @@ -1,8 +1,7 @@ -use backend::{Decoding, Encoding, FFT64, Module, ScratchOwned, Stats, VecZnxOps, ZnxZero}; -use itertools::izip; +use backend::{FFT64, FillUniform, Module, ScratchOwned, Stats}; use sampling::source::Source; -use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEPlaintext, GLWEPublicKey, GLWESecret, Infos}; +use crate::{FourierGLWECiphertext, FourierGLWESecret, GLWECiphertext, GLWEOps, GLWEPlaintext, GLWEPublicKey, GLWESecret, Infos}; #[test] fn encrypt_sk() { @@ -35,7 +34,8 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: let module: Module = Module::::new(1 << log_n); let mut ct: GLWECiphertext> = GLWECiphertext::alloc(&module, basek, k_ct, rank); - let mut pt: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_pt); + let mut pt_want: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_pt); + let mut pt_have: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_pt); let mut source_xs: Source = Source::new([0u8; 32]); let mut source_xe: Source = Source::new([0u8; 32]); @@ -50,17 +50,13 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: sk.fill_ternary_prob(0.5, &mut source_xs); let sk_dft: FourierGLWESecret, FFT64> = FourierGLWESecret::from(&module, &sk); - let mut data_want: Vec = vec![0i64; module.n()]; - - data_want - .iter_mut() - .for_each(|x| *x = source_xa.next_i64() & 0xFF); - - pt.data.encode_vec_i64(0, basek, k_pt, &data_want, 10); + pt_want + .data + .fill_uniform(basek, 0, pt_want.size(), &mut source_xa); ct.encrypt_sk( &module, - &pt, + &pt_want, &sk_dft, &mut source_xa, &mut source_xe, @@ -68,26 +64,14 @@ fn test_encrypt_sk(log_n: usize, basek: usize, k_ct: usize, k_pt: usize, sigma: scratch.borrow(), ); - pt.data.zero(); + ct.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow()); - ct.decrypt(&module, &mut pt, &sk_dft, scratch.borrow()); + pt_want.sub_inplace_ab(&module, &pt_have); - let mut data_have: Vec = vec![0i64; module.n()]; + let noise_have: f64 = pt_want.data.std(0, basek) * (ct.k() as f64).exp2(); + let noise_want: f64 = sigma; - pt.data - .decode_vec_i64(0, basek, pt.size() * basek, &mut data_have); - - // TODO: properly assert the decryption noise through std(dec(ct) - pt) - let scale: f64 = (1 << (pt.size() * basek - k_pt)) as f64; - izip!(data_want.iter(), data_have.iter()).for_each(|(a, b)| { - let b_scaled = (*b as f64) / scale; - assert!( - (*a as f64 - b_scaled).abs() < 0.1, - "{} {}", - *a as f64, - b_scaled - ) - }); + assert!(noise_have <= noise_want + 0.2); } fn test_encrypt_zero_sk(log_n: usize, basek: usize, k_ct: usize, sigma: f64, rank: usize) { @@ -127,6 +111,7 @@ fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: let module: Module = Module::::new(1 << log_n); let mut ct: GLWECiphertext> = GLWECiphertext::alloc(&module, basek, k_ct, rank); + let mut pt_have = GLWEPlaintext::alloc(&module, basek, k_ct); let mut pt_want: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_ct); let mut source_xs: Source = Source::new([0u8; 32]); @@ -147,13 +132,9 @@ fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: | GLWECiphertext::encrypt_pk_scratch_space(&module, basek, pk.k()), ); - let mut data_want: Vec = vec![0i64; module.n()]; - - data_want - .iter_mut() - .for_each(|x| *x = source_xa.next_i64() & 0); - - pt_want.data.encode_vec_i64(0, basek, k_ct, &data_want, 10); + pt_want + .data + .fill_uniform(basek, 0, pt_want.size(), &mut source_xa); ct.encrypt_pk( &module, @@ -165,11 +146,9 @@ fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: scratch.borrow(), ); - let mut pt_have: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_ct); - ct.decrypt(&module, &mut pt_have, &sk_dft, scratch.borrow()); - module.vec_znx_sub_ab_inplace(&mut pt_want.data, 0, &pt_have.data, 0); + pt_want.sub_inplace_ab(&module, &pt_have); let noise_have: f64 = pt_want.data.std(0, basek).log2(); let noise_want: f64 = ((((rank as f64) + 1.0) * module.n() as f64 * 0.5 * sigma * sigma).sqrt()).log2() - (k_ct as f64); From 07a1d7227ebf499cec649594a3f10f393b9e83eb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Bossuat Date: Tue, 15 Jul 2025 20:16:38 +0200 Subject: [PATCH 8/8] More fixes --- core/src/glwe/external_product.rs | 24 ++++++++++++------------ core/src/glwe/test_fft64/encryption.rs | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/src/glwe/external_product.rs b/core/src/glwe/external_product.rs index e765b85..a95c5ac 100644 --- a/core/src/glwe/external_product.rs +++ b/core/src/glwe/external_product.rs @@ -2,7 +2,7 @@ use backend::{ FFT64, MatZnxDftOps, MatZnxDftScratch, Module, Scratch, VecZnxBig, VecZnxBigOps, VecZnxDftAlloc, VecZnxDftOps, VecZnxScratch, }; -use crate::{FourierGLWECiphertext, GGSWCiphertext, GLWECiphertext, Infos}; +use crate::{GGSWCiphertext, GLWECiphertext, Infos}; impl GLWECiphertext> { pub fn external_product_scratch_space( @@ -14,21 +14,21 @@ impl GLWECiphertext> { digits: usize, rank: usize, ) -> usize { - let res_dft: usize = FourierGLWECiphertext::bytes_of(module, basek, k_ggsw, rank); 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, ggsw_size) - + module.vmp_apply_tmp_bytes( - out_size, - in_size, - in_size, // rows - rank + 1, // cols in - rank + 1, // cols out - ggsw_size, - ); + let res_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, ggsw_size); + let a_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size); + let vmp: usize = module.vmp_apply_tmp_bytes( + out_size, + in_size, + in_size, // rows + rank + 1, // cols in + rank + 1, // cols out + ggsw_size, + ); let normalize: usize = module.vec_znx_normalize_tmp_bytes(); - res_dft + (vmp | normalize) + res_dft + a_dft + (vmp | normalize) } pub fn external_product_inplace_scratch_space( diff --git a/core/src/glwe/test_fft64/encryption.rs b/core/src/glwe/test_fft64/encryption.rs index 55cd5a9..7909cb3 100644 --- a/core/src/glwe/test_fft64/encryption.rs +++ b/core/src/glwe/test_fft64/encryption.rs @@ -111,7 +111,7 @@ fn test_encrypt_pk(log_n: usize, basek: usize, k_ct: usize, k_pk: usize, sigma: let module: Module = Module::::new(1 << log_n); let mut ct: GLWECiphertext> = GLWECiphertext::alloc(&module, basek, k_ct, rank); - let mut pt_have = GLWEPlaintext::alloc(&module, basek, k_ct); + let mut pt_have: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_ct); let mut pt_want: GLWEPlaintext> = GLWEPlaintext::alloc(&module, basek, k_ct); let mut source_xs: Source = Source::new([0u8; 32]);