poulpy/core/src/ggsw_ciphertext.rs
core/src/ggsw_ciphertext.rsRaw
1use backend::{
2 Backend, FFT64, MatZnxDft, MatZnxDftAlloc, MatZnxDftOps, MatZnxDftScratch, Module, ScalarZnx, Scratch, VecZnxAlloc,
3 VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch, VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxOps, VecZnxToMut, ZnxInfos,
4 ZnxZero,
5};
6use sampling::source::Source;
7
8use crate::{
9 AutomorphismKey, GLWECiphertext, GLWECiphertextFourier, GLWESecret, GLWESwitchingKey, GetRow, Infos, ScratchCore, SetRow,
10 TensorKey, div_ceil,
11};
12
13pub struct GGSWCiphertext<C, B: Backend> {
14 pub(crate) data: MatZnxDft<C, B>,
15 pub(crate) basek: usize,
16 pub(crate) k: usize,
17 pub(crate) digits: usize,
18}
19
20impl<B: Backend> GGSWCiphertext<Vec<u8>, B> {
21 pub fn alloc(module: &Module<B>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> Self {
22 let size: usize = div_ceil(k, basek);
23 debug_assert!(
24 size > digits,
25 "invalid ggsw: ceil(k/basek): {} <= digits: {}",
26 size,
27 digits
28 );
29
30 assert!(
31 rows * digits <= size,
32 "invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
33 rows,
34 digits,
35 size
36 );
37
38 Self {
39 data: module.new_mat_znx_dft(rows, rank + 1, rank + 1, div_ceil(k, basek)),
40 basek,
41 k: k,
42 digits,
43 }
44 }
45
46 pub fn bytes_of(module: &Module<FFT64>, basek: usize, k: usize, rows: usize, digits: usize, rank: usize) -> usize {
47 let size: usize = div_ceil(k, basek);
48 debug_assert!(
49 size > digits,
50 "invalid ggsw: ceil(k/basek): {} <= digits: {}",
51 size,
52 digits
53 );
54
55 assert!(
56 rows * digits <= size,
57 "invalid ggsw: rows: {} * digits:{} > ceil(k/basek): {}",
58 rows,
59 digits,
60 size
61 );
62
63 module.bytes_of_mat_znx_dft(rows, rank + 1, rank + 1, size)
64 }
65}
66
67impl<T, B: Backend> Infos for GGSWCiphertext<T, B> {
68 type Inner = MatZnxDft<T, B>;
69
70 fn inner(&self) -> &Self::Inner {
71 &self.data
72 }
73
74 fn basek(&self) -> usize {
75 self.basek
76 }
77
78 fn k(&self) -> usize {
79 self.k
80 }
81}
82
83impl<T, B: Backend> GGSWCiphertext<T, B> {
84 pub fn rank(&self) -> usize {
85 self.data.cols_out() - 1
86 }
87
88 pub fn digits(&self) -> usize {
89 self.digits
90 }
91}
92
93impl GGSWCiphertext<Vec<u8>, FFT64> {
94 pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, basek: usize, k: usize, rank: usize) -> usize {
95 let size = div_ceil(k, basek);
96 GLWECiphertext::encrypt_sk_scratch_space(module, basek, k)
97 + module.bytes_of_vec_znx(rank + 1, size)
98 + module.bytes_of_vec_znx(1, size)
99 + module.bytes_of_vec_znx_dft(rank + 1, size)
100 }
101
102 pub(crate) fn expand_row_scratch_space(
103 module: &Module<FFT64>,
104 basek: usize,
105 self_k: usize,
106 k_tsk: usize,
107 digits: usize,
108 rank: usize,
109 ) -> usize {
110 let tsk_size: usize = div_ceil(k_tsk, basek);
111 let self_size_out: usize = div_ceil(self_k, basek);
112 let self_size_in: usize = div_ceil(self_size_out, digits);
113 let tmp_dft_i: usize = module.bytes_of_vec_znx_dft(rank + 1, tsk_size);
114 let tmp_a: usize = module.bytes_of_vec_znx_dft(1, self_size_in);
115 let vmp: usize = module.vmp_apply_tmp_bytes(
116 self_size_out,
117 self_size_in,
118 self_size_in,
119 rank,
120 rank,
121 tsk_size,
122 );
123 let tmp_idft: usize = module.bytes_of_vec_znx_big(1, tsk_size);
124 let norm: usize = module.vec_znx_big_normalize_tmp_bytes();
125 tmp_dft_i + ((tmp_a + vmp) | (tmp_idft + norm))
126 }
127
128 pub(crate) fn keyswitch_internal_col0_scratch_space(
129 module: &Module<FFT64>,
130 basek: usize,
131 k_out: usize,
132 k_in: usize,
133 k_ksk: usize,
134 digits: usize,
135 rank: usize,
136 ) -> usize {
137 GLWECiphertext::keyswitch_from_fourier_scratch_space(module, basek, k_out, k_in, k_ksk, digits, rank, rank)
138 + module.bytes_of_vec_znx_dft(rank + 1, div_ceil(k_in, basek))
139 }
140
141 pub fn keyswitch_scratch_space(
142 module: &Module<FFT64>,
143 basek: usize,
144 k_out: usize,
145 k_in: usize,
146 k_ksk: usize,
147 digits_ksk: usize,
148 k_tsk: usize,
149 digits_tsk: usize,
150 rank: usize,
151 ) -> usize {
152 let out_size: usize = div_ceil(k_out, basek);
153
154 let res_znx: usize = module.bytes_of_vec_znx(rank + 1, out_size);
155 let ci_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, out_size);
156 let ks: usize =
157 GGSWCiphertext::keyswitch_internal_col0_scratch_space(module, basek, k_out, k_in, k_ksk, digits_ksk, rank);
158 let expand_rows: usize = GGSWCiphertext::expand_row_scratch_space(module, basek, k_out, k_tsk, digits_tsk, rank);
159 let res_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, out_size);
160 res_znx + ci_dft + (ks | expand_rows | res_dft)
161 }
162
163 pub fn keyswitch_inplace_scratch_space(
164 module: &Module<FFT64>,
165 basek: usize,
166 k_out: usize,
167 k_ksk: usize,
168 digits_ksk: usize,
169 k_tsk: usize,
170 digits_tsk: usize,
171 rank: usize,
172 ) -> usize {
173 GGSWCiphertext::keyswitch_scratch_space(
174 module, basek, k_out, k_out, k_ksk, digits_ksk, k_tsk, digits_tsk, rank,
175 )
176 }
177
178 pub fn automorphism_scratch_space(
179 module: &Module<FFT64>,
180 basek: usize,
181 k_out: usize,
182 k_in: usize,
183 k_ksk: usize,
184 digits_ksk: usize,
185 k_tsk: usize,
186 digits_tsk: usize,
187 rank: usize,
188 ) -> usize {
189 let cols: usize = rank + 1;
190 let out_size: usize = div_ceil(k_out, basek);
191 let res: usize = module.bytes_of_vec_znx(cols, out_size);
192 let res_dft: usize = module.bytes_of_vec_znx_dft(cols, out_size);
193 let ci_dft: usize = module.bytes_of_vec_znx_dft(cols, out_size);
194 let ks_internal: usize =
195 GGSWCiphertext::keyswitch_internal_col0_scratch_space(module, basek, k_out, k_in, k_ksk, digits_ksk, rank);
196 let expand: usize = GGSWCiphertext::expand_row_scratch_space(module, basek, k_out, k_tsk, digits_tsk, rank);
197 res + ci_dft + (ks_internal | expand | res_dft)
198 }
199
200 pub fn automorphism_inplace_scratch_space(
201 module: &Module<FFT64>,
202 basek: usize,
203 k_out: usize,
204 k_ksk: usize,
205 digits_ksk: usize,
206 k_tsk: usize,
207 digits_tsk: usize,
208 rank: usize,
209 ) -> usize {
210 GGSWCiphertext::automorphism_scratch_space(
211 module, basek, k_out, k_out, k_ksk, digits_ksk, k_tsk, digits_tsk, rank,
212 )
213 }
214
215 pub fn external_product_scratch_space(
216 module: &Module<FFT64>,
217 basek: usize,
218 k_out: usize,
219 k_in: usize,
220 k_ggsw: usize,
221 digits: usize,
222 rank: usize,
223 ) -> usize {
224 let tmp_in: usize = GLWECiphertextFourier::bytes_of(module, basek, k_in, rank);
225 let tmp_out: usize = GLWECiphertextFourier::bytes_of(module, basek, k_out, rank);
226 let ggsw: usize = GLWECiphertextFourier::external_product_scratch_space(module, basek, k_out, k_in, k_ggsw, digits, rank);
227 tmp_in + tmp_out + ggsw
228 }
229
230 pub fn external_product_inplace_scratch_space(
231 module: &Module<FFT64>,
232 basek: usize,
233 k_out: usize,
234 k_ggsw: usize,
235 digits: usize,
236 rank: usize,
237 ) -> usize {
238 let tmp: usize = GLWECiphertextFourier::bytes_of(module, basek, k_out, rank);
239 let ggsw: usize =
240 GLWECiphertextFourier::external_product_inplace_scratch_space(module, basek, k_out, k_ggsw, digits, rank);
241 tmp + ggsw
242 }
243}
244
245impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> GGSWCiphertext<DataSelf, FFT64> {
246 pub fn encrypt_sk<DataPt: AsRef<[u8]>, DataSk: AsRef<[u8]>>(
247 &mut self,
248 module: &Module<FFT64>,
249 pt: &ScalarZnx<DataPt>,
250 sk: &GLWESecret<DataSk, FFT64>,
251 source_xa: &mut Source,
252 source_xe: &mut Source,
253 sigma: f64,
254 scratch: &mut Scratch,
255 ) {
256 #[cfg(debug_assertions)]
257 {
258 assert_eq!(self.rank(), sk.rank());
259 assert_eq!(self.n(), module.n());
260 assert_eq!(pt.n(), module.n());
261 assert_eq!(sk.n(), module.n());
262 }
263
264 let basek: usize = self.basek();
265 let k: usize = self.k();
266 let rank: usize = self.rank();
267 let digits: usize = self.digits();
268
269 let (mut tmp_pt, scratch1) = scratch.tmp_glwe_pt(module, basek, k);
270 let (mut tmp_ct, scratch2) = scratch1.tmp_glwe_ct(module, basek, k, rank);
271
272 (0..self.rows()).for_each(|row_i| {
273 tmp_pt.data.zero();
274
275 // Adds the scalar_znx_pt to the i-th limb of the vec_znx_pt
276 module.vec_znx_add_scalar_inplace(&mut tmp_pt.data, 0, (digits - 1) + row_i * digits, pt, 0);
277 module.vec_znx_normalize_inplace(basek, &mut tmp_pt.data, 0, scratch2);
278
279 (0..rank + 1).for_each(|col_j| {
280 // rlwe encrypt of vec_znx_pt into vec_znx_ct
281
282 tmp_ct.encrypt_sk_private(
283 module,
284 Some((&tmp_pt, col_j)),
285 sk,
286 source_xa,
287 source_xe,
288 sigma,
289 scratch2,
290 );
291
292 // Switch vec_znx_ct into DFT domain
293 {
294 let (mut tmp_ct_dft, _) = scratch2.tmp_glwe_fourier(module, basek, k, rank);
295 tmp_ct.dft(module, &mut tmp_ct_dft);
296 self.set_row(module, row_i, col_j, &tmp_ct_dft);
297 }
298 });
299 });
300 }
301
302 pub(crate) fn expand_row<R, DataCi: AsRef<[u8]>, DataTsk: AsRef<[u8]>>(
303 &mut self,
304 module: &Module<FFT64>,
305 col_j: usize,
306 res: &mut R,
307 ci_dft: &VecZnxDft<DataCi, FFT64>,
308 tsk: &TensorKey<DataTsk, FFT64>,
309 scratch: &mut Scratch,
310 ) where
311 R: VecZnxToMut,
312 {
313 let cols: usize = self.rank() + 1;
314
315 assert!(
316 scratch.available()
317 >= GGSWCiphertext::expand_row_scratch_space(
318 module,
319 self.basek(),
320 self.k(),
321 tsk.k(),
322 tsk.digits(),
323 tsk.rank()
324 )
325 );
326
327 // Example for rank 3:
328 //
329 // Note: M is a vector (m, Bm, B^2m, B^3m, ...), so each column is
330 // actually composed of that many rows and we focus on a specific row here
331 // implicitely given ci_dft.
332 //
333 // # Input
334 //
335 // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0 , a1 , a2 )
336 // col 1: (0, 0, 0, 0)
337 // col 2: (0, 0, 0, 0)
338 // col 3: (0, 0, 0, 0)
339 //
340 // # Output
341 //
342 // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0 , a1 , a2 )
343 // col 1: (-(b0s0 + b1s1 + b2s2) , b0 + M[i], b1 , b2 )
344 // col 2: (-(c0s0 + c1s1 + c2s2) , c0 , c1 + M[i], c2 )
345 // col 3: (-(d0s0 + d1s1 + d2s2) , d0 , d1 , d2 + M[i])
346
347 let digits: usize = tsk.digits();
348
349 let (mut tmp_dft_i, scratch1) = scratch.tmp_vec_znx_dft(module, cols, tsk.size());
350 let (mut tmp_a, scratch2) = scratch1.tmp_vec_znx_dft(module, 1, (ci_dft.size() + digits - 1) / digits);
351 let res_size: usize = res.to_mut().size();
352
353 {
354 // Performs a key-switch for each combination of s[i]*s[j], i.e. for a0, a1, a2
355 //
356 // # Example for col=1
357 //
358 // a0 * (-(f0s0 + f1s1 + f1s2) + s0^2, f0, f1, f2) = (-(a0f0s0 + a0f1s1 + a0f1s2) + a0s0^2, a0f0, a0f1, a0f2)
359 // +
360 // a1 * (-(g0s0 + g1s1 + g1s2) + s0s1, g0, g1, g2) = (-(a1g0s0 + a1g1s1 + a1g1s2) + a1s0s1, a1g0, a1g1, a1g2)
361 // +
362 // a2 * (-(h0s0 + h1s1 + h1s2) + s0s2, h0, h1, h2) = (-(a2h0s0 + a2h1s1 + a2h1s2) + a2s0s2, a2h0, a2h1, a2h2)
363 // =
364 // (-(x0s0 + x1s1 + x2s2) + s0(a0s0 + a1s1 + a2s2), x0, x1, x2)
365 (1..cols).for_each(|col_i| {
366
367 let pmat: &MatZnxDft<DataTsk, FFT64> = &tsk.at(col_i - 1, col_j - 1).0.data; // Selects Enc(s[i]s[j])
368
369 // Extracts a[i] and multipies with Enc(s[i]s[j])
370 (0..digits).for_each(|di| {
371
372 tmp_a.set_size((ci_dft.size() + di) / digits);
373
374 // Small optimization for digits > 2
375 // VMP produce some error e, and since we aggregate vmp * 2^{di * B}, then
376 // we also aggregate ei * 2^{di * B}, with the largest error being ei * 2^{(digits-1) * B}.
377 // As such we can ignore the last digits-2 limbs safely of the sum of vmp products.
378 // It is possible to further ignore the last digits-1 limbs, but this introduce
379 // ~0.5 to 1 bit of additional noise, and thus not chosen here to ensure that the same
380 // noise is kept with respect to the ideal functionality.
381 //tmp_dft_i.set_size(res_size - ((digits - di) as isize - 2).max(0) as usize);
382
383 module.vec_znx_dft_copy(digits, digits - 1 - di, &mut tmp_a, 0, ci_dft, col_i);
384 if di == 0 && col_i == 1 {
385 module.vmp_apply(&mut tmp_dft_i, &tmp_a, pmat, scratch2);
386 } else {
387 module.vmp_apply_add(&mut tmp_dft_i, &tmp_a, pmat, di, scratch2);
388 }
389 });
390 });
391 }
392
393 // Adds -(sum a[i] * s[i]) + m) on the i-th column of tmp_idft_i
394 //
395 // (-(x0s0 + x1s1 + x2s2) + a0s0s0 + a1s0s1 + a2s0s2, x0, x1, x2)
396 // +
397 // (0, -(a0s0 + a1s1 + a2s2) + M[i], 0, 0)
398 // =
399 // (-(x0s0 + x1s1 + x2s2) + s0(a0s0 + a1s1 + a2s2), x0 -(a0s0 + a1s1 + a2s2) + M[i], x1, x2)
400 // =
401 // (-(x0s0 + x1s1 + x2s2), x0 + M[i], x1, x2)
402 module.vec_znx_dft_add_inplace(&mut tmp_dft_i, col_j, ci_dft, 0);
403 let (mut tmp_idft, scratch2) = scratch1.tmp_vec_znx_big(module, 1, tsk.size());
404 (0..cols).for_each(|i| {
405 module.vec_znx_idft_tmp_a(&mut tmp_idft, 0, &mut tmp_dft_i, i);
406 module.vec_znx_big_normalize(self.basek(), res, i, &tmp_idft, 0, scratch2);
407 });
408 }
409
410 pub fn keyswitch<DataLhs: AsRef<[u8]>, DataKsk: AsRef<[u8]>, DataTsk: AsRef<[u8]>>(
411 &mut self,
412 module: &Module<FFT64>,
413 lhs: &GGSWCiphertext<DataLhs, FFT64>,
414 ksk: &GLWESwitchingKey<DataKsk, FFT64>,
415 tsk: &TensorKey<DataTsk, FFT64>,
416 scratch: &mut Scratch,
417 ) {
418 let rank: usize = self.rank();
419 let cols: usize = rank + 1;
420 let basek: usize = self.basek();
421
422 let (mut tmp_res, scratch1) = scratch.tmp_glwe_ct(module, basek, self.k(), rank);
423 let (mut ci_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols, self.size());
424
425 // Keyswitch the j-th row of the col 0
426 (0..lhs.rows()).for_each(|row_i| {
427 // Key-switch column 0, i.e.
428 // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2) -> (-(a0s0' + a1s1' + a2s2') + M[i], a0, a1, a2)
429 lhs.keyswitch_internal_col0(module, row_i, &mut tmp_res, ksk, scratch2);
430
431 // Isolates DFT(a[i])
432 (0..cols).for_each(|col_i| {
433 module.vec_znx_dft(1, 0, &mut ci_dft, col_i, &tmp_res.data, col_i);
434 });
435
436 module.vmp_prepare_row(&mut self.data, row_i, 0, &ci_dft);
437
438 // Generates
439 //
440 // col 1: (-(b0s0' + b1s1' + b2s2') , b0 + M[i], b1 , b2 )
441 // col 2: (-(c0s0' + c1s1' + c2s2') , c0 , c1 + M[i], c2 )
442 // col 3: (-(d0s0' + d1s1' + d2s2') , d0 , d1 , d2 + M[i])
443 (1..cols).for_each(|col_j| {
444 self.expand_row(module, col_j, &mut tmp_res.data, &ci_dft, tsk, scratch2);
445 let (mut tmp_res_dft, _) = scratch2.tmp_glwe_fourier(module, basek, self.k(), rank);
446 tmp_res.dft(module, &mut tmp_res_dft);
447 self.set_row(module, row_i, col_j, &tmp_res_dft);
448 });
449 })
450 }
451
452 pub fn keyswitch_inplace<DataKsk: AsRef<[u8]>, DataTsk: AsRef<[u8]>>(
453 &mut self,
454 module: &Module<FFT64>,
455 ksk: &GLWESwitchingKey<DataKsk, FFT64>,
456 tsk: &TensorKey<DataTsk, FFT64>,
457 scratch: &mut Scratch,
458 ) {
459 unsafe {
460 let self_ptr: *mut GGSWCiphertext<DataSelf, FFT64> = self as *mut GGSWCiphertext<DataSelf, FFT64>;
461 self.keyswitch(module, &*self_ptr, ksk, tsk, scratch);
462 }
463 }
464
465 pub fn automorphism<DataLhs: AsRef<[u8]>, DataAk: AsRef<[u8]>, DataTsk: AsRef<[u8]>>(
466 &mut self,
467 module: &Module<FFT64>,
468 lhs: &GGSWCiphertext<DataLhs, FFT64>,
469 auto_key: &AutomorphismKey<DataAk, FFT64>,
470 tensor_key: &TensorKey<DataTsk, FFT64>,
471 scratch: &mut Scratch,
472 ) {
473 #[cfg(debug_assertions)]
474 {
475 assert_eq!(
476 self.rank(),
477 lhs.rank(),
478 "ggsw_out rank: {} != ggsw_in rank: {}",
479 self.rank(),
480 lhs.rank()
481 );
482 assert_eq!(
483 self.rank(),
484 auto_key.rank(),
485 "ggsw_in rank: {} != auto_key rank: {}",
486 self.rank(),
487 auto_key.rank()
488 );
489 assert_eq!(
490 self.rank(),
491 tensor_key.rank(),
492 "ggsw_in rank: {} != tensor_key rank: {}",
493 self.rank(),
494 tensor_key.rank()
495 );
496 assert!(
497 scratch.available()
498 >= GGSWCiphertext::automorphism_scratch_space(
499 module,
500 self.basek(),
501 self.k(),
502 lhs.k(),
503 auto_key.k(),
504 auto_key.digits(),
505 tensor_key.k(),
506 tensor_key.digits(),
507 self.rank(),
508 )
509 )
510 };
511
512 let rank: usize = self.rank();
513 let cols: usize = rank + 1;
514 let basek: usize = self.basek();
515
516 let (mut tmp_res, scratch1) = scratch.tmp_glwe_ct(module, basek, self.k(), rank);
517 let (mut ci_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols, self.size());
518
519 // Keyswitch the j-th row of the col 0
520 (0..lhs.rows()).for_each(|row_i| {
521 // Key-switch column 0, i.e.
522 // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2) -> (-(a0pi^-1(s0) + a1pi^-1(s1) + a2pi^-1(s2)) + M[i], a0, a1, a2)
523 lhs.keyswitch_internal_col0(module, row_i, &mut tmp_res, &auto_key.key, scratch2);
524
525 // Isolates DFT(AUTO(a[i]))
526 (0..cols).for_each(|col_i| {
527 // (-(a0pi^-1(s0) + a1pi^-1(s1) + a2pi^-1(s2)) + M[i], a0, a1, a2) -> (-(a0s0 + a1s1 + a2s2) + pi(M[i]), a0, a1, a2)
528 module.vec_znx_automorphism_inplace(auto_key.p(), &mut tmp_res.data, col_i);
529 module.vec_znx_dft(1, 0, &mut ci_dft, col_i, &tmp_res.data, col_i);
530 });
531
532 module.vmp_prepare_row(&mut self.data, row_i, 0, &ci_dft);
533
534 // Generates
535 //
536 // col 1: (-(b0s0 + b1s1 + b2s2) , b0 + pi(M[i]), b1 , b2 )
537 // col 2: (-(c0s0 + c1s1 + c2s2) , c0 , c1 + pi(M[i]), c2 )
538 // col 3: (-(d0s0 + d1s1 + d2s2) , d0 , d1 , d2 + pi(M[i]))
539 (1..cols).for_each(|col_j| {
540 self.expand_row(
541 module,
542 col_j,
543 &mut tmp_res.data,
544 &ci_dft,
545 tensor_key,
546 scratch2,
547 );
548 let (mut tmp_res_dft, _) = scratch2.tmp_glwe_fourier(module, basek, self.k(), rank);
549 tmp_res.dft(module, &mut tmp_res_dft);
550 self.set_row(module, row_i, col_j, &tmp_res_dft);
551 });
552 })
553 }
554
555 pub fn automorphism_inplace<DataKsk: AsRef<[u8]>, DataTsk: AsRef<[u8]>>(
556 &mut self,
557 module: &Module<FFT64>,
558 auto_key: &AutomorphismKey<DataKsk, FFT64>,
559 tensor_key: &TensorKey<DataTsk, FFT64>,
560 scratch: &mut Scratch,
561 ) {
562 unsafe {
563 let self_ptr: *mut GGSWCiphertext<DataSelf, FFT64> = self as *mut GGSWCiphertext<DataSelf, FFT64>;
564 self.automorphism(module, &*self_ptr, auto_key, tensor_key, scratch);
565 }
566 }
567
568 pub fn external_product<DataLhs: AsRef<[u8]>, DataRhs: AsRef<[u8]>>(
569 &mut self,
570 module: &Module<FFT64>,
571 lhs: &GGSWCiphertext<DataLhs, FFT64>,
572 rhs: &GGSWCiphertext<DataRhs, FFT64>,
573 scratch: &mut Scratch,
574 ) {
575 #[cfg(debug_assertions)]
576 {
577 assert_eq!(
578 self.rank(),
579 lhs.rank(),
580 "ggsw_out rank: {} != ggsw_in rank: {}",
581 self.rank(),
582 lhs.rank()
583 );
584 assert_eq!(
585 self.rank(),
586 rhs.rank(),
587 "ggsw_in rank: {} != ggsw_apply rank: {}",
588 self.rank(),
589 rhs.rank()
590 );
591
592 assert!(
593 scratch.available()
594 >= GGSWCiphertext::external_product_scratch_space(
595 module,
596 self.basek(),
597 self.k(),
598 lhs.k(),
599 rhs.k(),
600 rhs.digits(),
601 rhs.rank()
602 )
603 )
604 }
605
606 let (mut tmp_ct_in, scratch1) = scratch.tmp_glwe_fourier(module, lhs.basek(), lhs.k(), lhs.rank());
607 let (mut tmp_ct_out, scratch2) = scratch1.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
608
609 (0..self.rank() + 1).for_each(|col_i| {
610 (0..self.rows()).for_each(|row_j| {
611 lhs.get_row(module, row_j, col_i, &mut tmp_ct_in);
612 tmp_ct_out.external_product(module, &tmp_ct_in, rhs, scratch2);
613 self.set_row(module, row_j, col_i, &tmp_ct_out);
614 });
615 });
616
617 tmp_ct_out.data.zero();
618
619 (self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
620 (0..self.rank() + 1).for_each(|col_j| {
621 self.set_row(module, row_i, col_j, &tmp_ct_out);
622 });
623 });
624 }
625
626 pub fn external_product_inplace<DataRhs: AsRef<[u8]>>(
627 &mut self,
628 module: &Module<FFT64>,
629 rhs: &GGSWCiphertext<DataRhs, FFT64>,
630 scratch: &mut Scratch,
631 ) {
632 #[cfg(debug_assertions)]
633 {
634 assert_eq!(
635 self.rank(),
636 rhs.rank(),
637 "ggsw_out rank: {} != ggsw_apply: {}",
638 self.rank(),
639 rhs.rank()
640 );
641 }
642
643 let (mut tmp_ct, scratch1) = scratch.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
644
645 (0..self.rank() + 1).for_each(|col_i| {
646 (0..self.rows()).for_each(|row_j| {
647 self.get_row(module, row_j, col_i, &mut tmp_ct);
648 tmp_ct.external_product_inplace(module, rhs, scratch1);
649 self.set_row(module, row_j, col_i, &tmp_ct);
650 });
651 });
652 }
653}
654
655impl<DataSelf: AsRef<[u8]>> GGSWCiphertext<DataSelf, FFT64> {
656 pub(crate) fn keyswitch_internal_col0<DataRes: AsMut<[u8]> + AsRef<[u8]>, DataKsk: AsRef<[u8]>>(
657 &self,
658 module: &Module<FFT64>,
659 row_i: usize,
660 res: &mut GLWECiphertext<DataRes>,
661 ksk: &GLWESwitchingKey<DataKsk, FFT64>,
662 scratch: &mut Scratch,
663 ) {
664 #[cfg(debug_assertions)]
665 {
666 assert_eq!(self.rank(), ksk.rank());
667 assert_eq!(res.rank(), ksk.rank());
668 assert!(
669 scratch.available()
670 >= GGSWCiphertext::keyswitch_internal_col0_scratch_space(
671 module,
672 self.basek(),
673 res.k(),
674 self.k(),
675 ksk.k(),
676 ksk.digits(),
677 ksk.rank()
678 )
679 )
680 }
681 let (mut tmp_dft_dft, scratch1) = scratch.tmp_glwe_fourier(module, self.basek(), self.k(), self.rank());
682 self.get_row(module, row_i, 0, &mut tmp_dft_dft);
683 res.keyswitch_from_fourier(module, &tmp_dft_dft, ksk, scratch1);
684 }
685}
686
687impl<DataSelf: AsRef<[u8]>> GetRow<FFT64> for GGSWCiphertext<DataSelf, FFT64> {
688 fn get_row<R: AsMut<[u8]> + AsRef<[u8]>>(
689 &self,
690 module: &Module<FFT64>,
691 row_i: usize,
692 col_j: usize,
693 res: &mut GLWECiphertextFourier<R, FFT64>,
694 ) {
695 module.vmp_extract_row(&mut res.data, &self.data, row_i, col_j);
696 }
697}
698
699impl<DataSelf: AsMut<[u8]> + AsRef<[u8]>> SetRow<FFT64> for GGSWCiphertext<DataSelf, FFT64> {
700 fn set_row<R: AsRef<[u8]>>(
701 &mut self,
702 module: &Module<FFT64>,
703 row_i: usize,
704 col_j: usize,
705 a: &GLWECiphertextFourier<R, FFT64>,
706 ) {
707 module.vmp_prepare_row(&mut self.data, row_i, col_j, &a.data);
708 }
709}