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