poulpy/core/src/test_fft64/automorphism_key.rs
core/src/test_fft64/automorphism_key.rsRaw
1use backend::{FFT64, Module, ScalarZnxOps, ScratchOwned, Stats, VecZnxOps};
2use sampling::source::Source;
3
4use crate::{
5 AutomorphismKey, GLWECiphertextFourier, GLWEPlaintext, GLWESecret, GetRow, Infos,
6 test_fft64::gglwe::log2_std_noise_gglwe_product,
7};
8
9#[test]
10fn automorphism() {
11 (1..4).for_each(|rank| {
12 println!("test automorphism rank: {}", rank);
13 test_automorphism(-1, 5, 12, 12, 60, 3.2, rank);
14 });
15}
16
17#[test]
18fn automorphism_inplace() {
19 (1..4).for_each(|rank| {
20 println!("test automorphism_inplace rank: {}", rank);
21 test_automorphism_inplace(-1, 5, 12, 12, 60, 3.2, rank);
22 });
23}
24
25fn test_automorphism(p0: i64, p1: i64, log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank: usize) {
26 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
27 let rows = (k_ksk + basek - 1) / basek;
28
29 let mut auto_key_in: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, rank);
30 let mut auto_key_out: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, rank);
31 let mut auto_key_apply: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, rank);
32
33 let mut source_xs: Source = Source::new([0u8; 32]);
34 let mut source_xe: Source = Source::new([0u8; 32]);
35 let mut source_xa: Source = Source::new([0u8; 32]);
36
37 let mut scratch: ScratchOwned = ScratchOwned::new(
38 AutomorphismKey::generate_from_sk_scratch_space(&module, basek, k_ksk, rank)
39 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk)
40 | AutomorphismKey::automorphism_scratch_space(&module, basek, k_ksk, k_ksk, k_ksk, rank),
41 );
42
43 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
44 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
45
46 // gglwe_{s1}(s0) = s0 -> s1
47 auto_key_in.generate_from_sk(
48 &module,
49 p0,
50 &sk,
51 &mut source_xa,
52 &mut source_xe,
53 sigma,
54 scratch.borrow(),
55 );
56
57 // gglwe_{s2}(s1) -> s1 -> s2
58 auto_key_apply.generate_from_sk(
59 &module,
60 p1,
61 &sk,
62 &mut source_xa,
63 &mut source_xe,
64 sigma,
65 scratch.borrow(),
66 );
67
68 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
69 auto_key_out.automorphism(&module, &auto_key_in, &auto_key_apply, scratch.borrow());
70
71 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank);
72 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
73
74 let mut sk_auto: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
75 sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
76 (0..rank).for_each(|i| {
77 module.scalar_znx_automorphism(
78 module.galois_element_inv(p0 * p1),
79 &mut sk_auto.data,
80 i,
81 &sk.data,
82 i,
83 );
84 });
85
86 sk_auto.prep_fourier(&module);
87
88 (0..auto_key_out.rank_in()).for_each(|col_i| {
89 (0..auto_key_out.rows()).for_each(|row_i| {
90 auto_key_out.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
91
92 ct_glwe_dft.decrypt(&module, &mut pt, &sk_auto, scratch.borrow());
93 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk.data, col_i);
94
95 let noise_have: f64 = pt.data.std(0, basek).log2();
96 let noise_want: f64 = log2_std_noise_gglwe_product(
97 module.n() as f64,
98 basek,
99 0.5,
100 0.5,
101 0f64,
102 sigma * sigma,
103 0f64,
104 rank as f64,
105 k_ksk,
106 k_ksk,
107 );
108
109 assert!(
110 (noise_have - noise_want).abs() <= 0.1,
111 "{} {}",
112 noise_have,
113 noise_want
114 );
115 });
116 });
117}
118
119fn test_automorphism_inplace(p0: i64, p1: i64, log_n: usize, basek: usize, k_ksk: usize, sigma: f64, rank: usize) {
120 let module: Module<FFT64> = Module::<FFT64>::new(1 << log_n);
121 let rows: usize = (k_ksk + basek - 1) / basek;
122
123 let mut auto_key: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, rank);
124 let mut auto_key_apply: AutomorphismKey<Vec<u8>, FFT64> = AutomorphismKey::alloc(&module, basek, k_ksk, rows, rank);
125
126 let mut source_xs: Source = Source::new([0u8; 32]);
127 let mut source_xe: Source = Source::new([0u8; 32]);
128 let mut source_xa: Source = Source::new([0u8; 32]);
129
130 let mut scratch: ScratchOwned = ScratchOwned::new(
131 AutomorphismKey::generate_from_sk_scratch_space(&module, basek, k_ksk, rank)
132 | GLWECiphertextFourier::decrypt_scratch_space(&module, basek, k_ksk)
133 | AutomorphismKey::automorphism_inplace_scratch_space(&module, basek, k_ksk, k_ksk, rank),
134 );
135
136 let mut sk: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
137 sk.fill_ternary_prob(&module, 0.5, &mut source_xs);
138
139 // gglwe_{s1}(s0) = s0 -> s1
140 auto_key.generate_from_sk(
141 &module,
142 p0,
143 &sk,
144 &mut source_xa,
145 &mut source_xe,
146 sigma,
147 scratch.borrow(),
148 );
149
150 // gglwe_{s2}(s1) -> s1 -> s2
151 auto_key_apply.generate_from_sk(
152 &module,
153 p1,
154 &sk,
155 &mut source_xa,
156 &mut source_xe,
157 sigma,
158 scratch.borrow(),
159 );
160
161 // gglwe_{s1}(s0) (x) gglwe_{s2}(s1) = gglwe_{s2}(s0)
162 auto_key.automorphism_inplace(&module, &auto_key_apply, scratch.borrow());
163
164 let mut ct_glwe_dft: GLWECiphertextFourier<Vec<u8>, FFT64> = GLWECiphertextFourier::alloc(&module, basek, k_ksk, rank);
165 let mut pt: GLWEPlaintext<Vec<u8>> = GLWEPlaintext::alloc(&module, basek, k_ksk);
166
167 let mut sk_auto: GLWESecret<Vec<u8>, FFT64> = GLWESecret::alloc(&module, rank);
168 sk_auto.fill_zero(); // Necessary to avoid panic of unfilled sk
169 (0..rank).for_each(|i| {
170 module.scalar_znx_automorphism(
171 module.galois_element_inv(p0 * p1),
172 &mut sk_auto.data,
173 i,
174 &sk.data,
175 i,
176 );
177 });
178
179 sk_auto.prep_fourier(&module);
180
181 (0..auto_key.rank_in()).for_each(|col_i| {
182 (0..auto_key.rows()).for_each(|row_i| {
183 auto_key.get_row(&module, row_i, col_i, &mut ct_glwe_dft);
184
185 ct_glwe_dft.decrypt(&module, &mut pt, &sk_auto, scratch.borrow());
186 module.vec_znx_sub_scalar_inplace(&mut pt.data, 0, row_i, &sk.data, col_i);
187
188 let noise_have: f64 = pt.data.std(0, basek).log2();
189 let noise_want: f64 = log2_std_noise_gglwe_product(
190 module.n() as f64,
191 basek,
192 0.5,
193 0.5,
194 0f64,
195 sigma * sigma,
196 0f64,
197 rank as f64,
198 k_ksk,
199 k_ksk,
200 );
201
202 assert!(
203 (noise_have - noise_want).abs() <= 0.1,
204 "{} {}",
205 noise_have,
206 noise_want
207 );
208 });
209 });
210}