arnaucube
arnaucube/poulpyPublic Self-hosted repository
Two changes:
- Fix setting `k` in `GlweOps`
- Improve GLWEPacker API avoid accumulating beyond limit (#50)
Janmajaya Mall janmajayamall@protonmail.com
Wed, 09 Jul 2025 16:23:56 +0530
commit

64edc869d040009a055a96cdb4b1541afe0ddffc

parent

b99f43aa0fe5df37afec763afa152f7085a2987d

M backend/src/vec_znx.rs → backend/src/vec_znx.rs

@@ -111,10 +111,10 @@ .for_each(|x: &mut i64| *x &= mask)

} } - 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)); }); })
M core/src/elem.rs → core/src/elem.rs

@@ -34,7 +34,7 @@

/// 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 }
M core/src/glwe/ops.rs → 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<A, B>(&mut self, module: &Module<FFT64>, a: &A, b: &B) where A: GLWECiphertextToRef,

@@ -14,7 +14,6 @@ assert_eq!(a.n(), module.n());

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 @@ self_mut.data.zero_at(i, j);

}); }); - // 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<A>(&mut self, module: &Module<FFT64>, a: &A)

@@ -70,7 +69,9 @@ (0..a.rank() + 1).for_each(|i| {

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<A, B>(&mut self, module: &Module<FFT64>, a: &A, b: &B)

@@ -118,7 +119,7 @@ });

}); 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<A>(&mut self, module: &Module<FFT64>, a: &A)

@@ -140,7 +141,9 @@ (0..a.rank() + 1).for_each(|i| {

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<A>(&mut self, module: &Module<FFT64>, a: &A)

@@ -162,7 +165,9 @@ (0..a.rank() + 1).for_each(|i| {

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<A>(&mut self, module: &Module<FFT64>, k: i64, a: &A)

@@ -184,7 +189,9 @@ module.vec_znx_rotate(k, &mut self_mut.data, i, &a_ref.data, i);

}); 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<FFT64>, k: i64) {

@@ -209,6 +216,8 @@ {

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 @@ (0..self_mut.rank() + 1).for_each(|i| {

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<FFT64>, scratch: &mut Scratch) {

@@ -266,3 +275,19 @@ pub fn rsh_scratch_space(module: &Module<FFT64>) -> usize {

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() + } +}
M core/src/glwe/packing.rs → 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 @@ }

} /// 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 @@ pub fn galois_elements(module: &Module<FFT64>) -> Vec<i64> {

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 @@ /// * `scratch`: scratch space of size at least [Self::add_scratch_space].

pub fn add<DataA: AsRef<[u8]>, DataAK: AsRef<[u8]>>( &mut self, module: &Module<FFT64>, - res: &mut Vec<GLWECiphertext<Vec<u8>>>, a: Option<&GLWECiphertext<DataA>>, auto_keys: &HashMap<i64, GLWEAutomorphismKey<DataAK, FFT64>>, 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 @@ auto_keys,

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<DataAK: AsRef<[u8]>>( - &mut self, - module: &Module<FFT64>, - res: &mut Vec<GLWECiphertext<Vec<u8>>>, - auto_keys: &HashMap<i64, GLWEAutomorphismKey<DataAK, FFT64>>, - scratch: &mut Scratch, - ) { - if self.counter != 0 { - while self.counter != 0 { - self.add( - module, - res, - None::<&GLWECiphertext<Vec<u8>>>, - auto_keys, - scratch, - ); - } - } + /// Flush result to`res`. + pub fn flush<Data: AsMut<[u8]> + AsRef<[u8]>>(&mut self, module: &Module<FFT64>, res: &mut GLWECiphertext<Data>) { + 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(); } }
M core/src/glwe/test_fft64/packing.rs → core/src/glwe/test_fft64/packing.rs

@@ -74,8 +74,6 @@ sigma,

scratch.borrow(), ); - let mut res: Vec<GLWECiphertext<Vec<u8>>> = Vec::new(); - (0..module.n() >> log_batch).for_each(|i| { ct.encrypt_sk( &module,

@@ -90,11 +88,10 @@

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<Vec<u8>>>, &auto_keys, scratch.borrow(),

@@ -102,36 +99,29 @@ )

} }); - 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<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ct); - - res.iter().enumerate().for_each(|(i, res_i)| { - let mut data: Vec<i64> = 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); + let mut data: Vec<i64> = 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()); + res.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); - } + 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 - ); - }); + 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)]