diff --git a/base2k/src/module.rs b/base2k/src/module.rs index 904d0ec..8ee6e4b 100644 --- a/base2k/src/module.rs +++ b/base2k/src/module.rs @@ -74,15 +74,15 @@ impl Module { } // Returns gen^-1 - pub fn galois_element_inv(&self, generator: i64) -> i64 { - if generator == 0 { + pub fn galois_element_inv(&self, gal_el: i64) -> i64 { + if gal_el == 0 { panic!("cannot invert 0") } ((mod_exp_u64( - generator.abs() as u64, + gal_el.abs() as u64, (self.cyclotomic_order() - 1) as usize, ) & (self.cyclotomic_order() - 1)) as i64) - * generator.signum() + * gal_el.signum() } } diff --git a/base2k/src/vec_znx_ops.rs b/base2k/src/vec_znx_ops.rs index f57e99f..85c2e1f 100644 --- a/base2k/src/vec_znx_ops.rs +++ b/base2k/src/vec_znx_ops.rs @@ -574,6 +574,7 @@ impl VecZnxOps for Module { #[cfg(debug_assertions)] { assert_eq!(a.n(), self.n()); + assert!(k & 1 != 0, "invalid galois element: must be odd but is {}", k); } unsafe { vec_znx::vec_znx_automorphism( diff --git a/core/src/automorphism.rs b/core/src/automorphism.rs index 8741bf9..6913fab 100644 --- a/core/src/automorphism.rs +++ b/core/src/automorphism.rs @@ -1,6 +1,7 @@ use base2k::{ Backend, FFT64, MatZnxDft, MatZnxDftOps, MatZnxDftToMut, MatZnxDftToRef, Module, ScalarZnx, ScalarZnxDftOps, ScalarZnxOps, - ScalarZnxToRef, Scratch, VecZnx, VecZnxDft, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, VecZnxOps, ZnxZero, + ScalarZnxToRef, Scratch, VecZnx, VecZnxBigAlloc, VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, + VecZnxOps, ZnxZero, }; use sampling::source::Source; @@ -20,10 +21,10 @@ pub struct AutomorphismKey { } impl AutomorphismKey, FFT64> { - pub fn new(module: &Module, basek: usize, p: i64, k: usize, rows: usize, rank: usize) -> Self { + pub fn new(module: &Module, basek: usize, k: usize, rows: usize, rank: usize) -> Self { AutomorphismKey { key: GLWESwitchingKey::new(module, basek, k, rows, rank, rank), - p: p, + p: 0, } } } @@ -127,6 +128,20 @@ impl AutomorphismKey, FFT64> { GLWESwitchingKey::keyswitch_inplace_scratch_space(module, out_size, out_rank, ksk_size) } + pub fn automorphism_scratch_space( + module: &Module, + out_size: usize, + in_size: usize, + ksk_size: usize, + rank: usize, + ) -> usize { + let tmp_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size); + let tmp_idft: usize = module.bytes_of_vec_znx_big(rank + 1, out_size); + let idft: usize = module.vec_znx_idft_tmp_bytes(); + let keyswitch: usize = GLWECiphertext::keyswitch_inplace_scratch_space(module, out_size, rank, ksk_size); + tmp_dft + tmp_idft + idft + keyswitch + } + pub fn external_product_scratch_space( module: &Module, out_size: usize, @@ -267,7 +282,7 @@ where // 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(self.p(), &mut tmp_idft_small_data, i); + module.vec_znx_automorphism_inplace(lhs.p(), &mut tmp_idft_small_data, i); }); // Wraps into ciphertext @@ -283,7 +298,7 @@ where // 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(rhs.p(), &mut tmp_idft, i); + module.vec_znx_automorphism_inplace(lhs.p(), &mut tmp_idft, i); module.vec_znx_dft(&mut tmp_dft, i, &tmp_idft, i); }); @@ -299,6 +314,8 @@ where self.set_row(module, row_i, col_j, &tmp_dft); }); }); + + self.p = (lhs.p * rhs.p) % (module.cyclotomic_order() as i64); } pub fn keyswitch( diff --git a/core/src/test_fft64/automorphism_key.rs b/core/src/test_fft64/automorphism_key.rs new file mode 100644 index 0000000..0e81578 --- /dev/null +++ b/core/src/test_fft64/automorphism_key.rs @@ -0,0 +1,117 @@ +use base2k::{FFT64, Module, ScalarZnxOps, ScalarZnxToRef, ScratchOwned, Stats, VecZnxOps, ZnxView}; +use sampling::source::Source; + +use crate::{ + automorphism::AutomorphismKey, + elem::{GetRow, Infos}, + glwe_ciphertext_fourier::GLWECiphertextFourier, + glwe_plaintext::GLWEPlaintext, + keys::{SecretKey, SecretKeyFourier}, + test_fft64::gglwe::noise_gglwe_product, +}; + +#[test] +fn automorphism() { + (1..4).for_each(|rank| { + println!("test automorphism rank: {}", rank); + test_automorphism(-1, 5, 12, 12, 60, 3.2, rank); + }); +} + +fn test_automorphism(p0: i64, p1: i64, log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank: usize) { + let module: Module = Module::::new(1 << log_n); + let rows = (k_ksk + basek - 1) / basek; + + let mut auto_key_in: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, k_ksk, rows, rank); + let mut auto_key_out: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, k_ksk, rows, rank); + let mut auto_key_apply: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, k_ksk, rows, 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( + AutomorphismKey::encrypt_sk_scratch_space(&module, rank, auto_key_in.size()) + | GLWECiphertextFourier::decrypt_scratch_space(&module, auto_key_out.size()) + | AutomorphismKey::automorphism_scratch_space( + &module, + auto_key_out.size(), + auto_key_in.size(), + auto_key_apply.size(), + rank, + ), + ); + + let mut sk: SecretKey> = SecretKey::new(&module, rank); + sk.fill_ternary_prob(0.5, &mut source_xs); + + let mut sk_dft: SecretKeyFourier, FFT64> = SecretKeyFourier::new(&module, rank); + sk_dft.dft(&module, &sk); + + // gglwe_{s1}(s0) = s0 -> s1 + auto_key_in.encrypt_sk( + &module, + p0, + &sk, + &mut source_xa, + &mut source_xe, + sigma, + scratch.borrow(), + ); + + // gglwe_{s2}(s1) -> s1 -> s2 + auto_key_apply.encrypt_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: GLWECiphertextFourier, FFT64> = GLWECiphertextFourier::new(&module, basek, k_ksk, rank); + let mut pt: GLWEPlaintext> = GLWEPlaintext::new(&module, basek, k_ksk); + + let mut sk_auto: SecretKey> = SecretKey::new(&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, i, &sk, i); + }); + + let mut sk_auto_dft: SecretKeyFourier, FFT64> = SecretKeyFourier::new(&module, rank); + sk_auto_dft.dft(&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, 0, row_i, &sk, col_i); + + let noise_have: f64 = pt.data.std(0, basek).log2(); + let noise_want: f64 = noise_gglwe_product( + module.n() as f64, + basek, + 0.5, + 0.5, + 0f64, + sigma * sigma, + 0f64, + rank as f64, + k_ksk, + k_ksk, + ); + + assert!( + (noise_have - noise_want).abs() <= 0.1, + "{} {}", + noise_have, + noise_want + ); + }); + }); +} diff --git a/core/src/test_fft64/gglwe.rs b/core/src/test_fft64/gglwe.rs index ff4bcfe..f0ccdb5 100644 --- a/core/src/test_fft64/gglwe.rs +++ b/core/src/test_fft64/gglwe.rs @@ -405,6 +405,19 @@ fn test_external_product(log_n: usize, basek: usize, k: usize, sigma: f64, rank_ // gglwe_(m) (x) RGSW_(X^k) = gglwe_(m * X^k) ct_gglwe_out.external_product(&module, &ct_gglwe_in, &ct_rgsw, scratch.borrow()); + scratch = ScratchOwned::new( + GLWESwitchingKey::encrypt_sk_scratch_space(&module, rank_out, ct_gglwe_in.size()) + | GLWECiphertextFourier::decrypt_scratch_space(&module, ct_gglwe_out.size()) + | GLWESwitchingKey::external_product_scratch_space( + &module, + ct_gglwe_out.size(), + ct_gglwe_in.size(), + ct_rgsw.size(), + rank_out, + ) + | GGSWCiphertext::encrypt_sk_scratch_space(&module, rank_out, ct_rgsw.size()), + ); + let mut ct_glwe_dft: GLWECiphertextFourier, FFT64> = GLWECiphertextFourier::new(&module, basek, k, rank_out); let mut pt: GLWEPlaintext> = GLWEPlaintext::new(&module, basek, k); diff --git a/core/src/test_fft64/glwe.rs b/core/src/test_fft64/glwe.rs index 53c06fe..687d49a 100644 --- a/core/src/test_fft64/glwe.rs +++ b/core/src/test_fft64/glwe.rs @@ -437,7 +437,7 @@ fn test_automorphism( let module: Module = Module::::new(1 << log_n); let rows: usize = (k_ct_in + basek - 1) / basek; - let mut autokey: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, p, k_autokey, rows, rank); + let mut autokey: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, k_autokey, rows, rank); let mut ct_in: GLWECiphertext> = GLWECiphertext::new(&module, basek, k_ct_in, rank); let mut ct_out: GLWECiphertext> = GLWECiphertext::new(&module, basek, k_ct_out, rank); let mut pt_want: GLWEPlaintext> = GLWEPlaintext::new(&module, basek, k_ct_in); @@ -523,19 +523,11 @@ fn automorphism_inplace() { }); } -fn test_automorphism_inplace( - log_n: usize, - basek: usize, - p: i64, - k_autokey: usize, - k_ct: usize, - rank: usize, - sigma: f64, -) { +fn test_automorphism_inplace(log_n: usize, basek: usize, p: i64, k_autokey: usize, k_ct: usize, rank: usize, sigma: f64) { let module: Module = Module::::new(1 << log_n); let rows: usize = (k_ct + basek - 1) / basek; - let mut autokey: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, p, k_autokey, rows, rank); + let mut autokey: AutomorphismKey, FFT64> = AutomorphismKey::new(&module, basek, k_autokey, rows, rank); let mut ct: GLWECiphertext> = GLWECiphertext::new(&module, basek, k_ct, rank); let mut pt_want: GLWEPlaintext> = GLWEPlaintext::new(&module, basek, k_ct); let mut pt_have: GLWEPlaintext> = GLWEPlaintext::new(&module, basek, k_ct); diff --git a/core/src/test_fft64/mod.rs b/core/src/test_fft64/mod.rs index ffaf1dc..9af0cfc 100644 --- a/core/src/test_fft64/mod.rs +++ b/core/src/test_fft64/mod.rs @@ -1,3 +1,4 @@ +mod automorphism_key; mod gglwe; mod ggsw; mod glwe;