arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/core/src/ggsw_ciphertext.rs
core/src/ggsw_ciphertext.rsRaw
  1use base2k::{
  2    Backend, FFT64, MatZnxDft, MatZnxDftAlloc, MatZnxDftOps, MatZnxDftScratch, MatZnxDftToMut, MatZnxDftToRef, Module, ScalarZnx,
  3    ScalarZnxDft, ScalarZnxDftToRef, ScalarZnxToRef, Scratch, VecZnxAlloc, VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch,
  4    VecZnxDftAlloc, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, VecZnxOps, ZnxInfos, ZnxZero,
  5};
  6use sampling::source::Source;
  7
  8use crate::{
  9    automorphism::AutomorphismKey,
 10    elem::{GetRow, Infos, SetRow},
 11    glwe_ciphertext::GLWECiphertext,
 12    glwe_ciphertext_fourier::GLWECiphertextFourier,
 13    glwe_plaintext::GLWEPlaintext,
 14    keys::SecretKeyFourier,
 15    keyswitch_key::GLWESwitchingKey,
 16    tensor_key::TensorKey,
 17    utils::derive_size,
 18};
 19
 20pub struct GGSWCiphertext<C, B: Backend> {
 21    pub data: MatZnxDft<C, B>,
 22    pub basek: usize,
 23    pub k: usize,
 24}
 25
 26impl<B: Backend> GGSWCiphertext<Vec<u8>, B> {
 27    pub fn new(module: &Module<B>, basek: usize, k: usize, rows: usize, rank: usize) -> Self {
 28        Self {
 29            data: module.new_mat_znx_dft(rows, rank + 1, rank + 1, derive_size(basek, k)),
 30            basek: basek,
 31            k: k,
 32        }
 33    }
 34}
 35
 36impl<T, B: Backend> Infos for GGSWCiphertext<T, B> {
 37    type Inner = MatZnxDft<T, B>;
 38
 39    fn inner(&self) -> &Self::Inner {
 40        &self.data
 41    }
 42
 43    fn basek(&self) -> usize {
 44        self.basek
 45    }
 46
 47    fn k(&self) -> usize {
 48        self.k
 49    }
 50}
 51
 52impl<T, B: Backend> GGSWCiphertext<T, B> {
 53    pub fn rank(&self) -> usize {
 54        self.data.cols_out() - 1
 55    }
 56}
 57
 58impl<C, B: Backend> MatZnxDftToMut<B> for GGSWCiphertext<C, B>
 59where
 60    MatZnxDft<C, B>: MatZnxDftToMut<B>,
 61{
 62    fn to_mut(&mut self) -> MatZnxDft<&mut [u8], B> {
 63        self.data.to_mut()
 64    }
 65}
 66
 67impl<C, B: Backend> MatZnxDftToRef<B> for GGSWCiphertext<C, B>
 68where
 69    MatZnxDft<C, B>: MatZnxDftToRef<B>,
 70{
 71    fn to_ref(&self) -> MatZnxDft<&[u8], B> {
 72        self.data.to_ref()
 73    }
 74}
 75
 76impl GGSWCiphertext<Vec<u8>, FFT64> {
 77    pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, rank: usize, size: usize) -> usize {
 78        GLWECiphertext::encrypt_sk_scratch_space(module, size)
 79            + module.bytes_of_vec_znx(rank + 1, size)
 80            + module.bytes_of_vec_znx(1, size)
 81            + module.bytes_of_vec_znx_dft(rank + 1, size)
 82    }
 83
 84    pub fn keyswitch_scratch_space(
 85        module: &Module<FFT64>,
 86        out_size: usize,
 87        in_size: usize,
 88        ksk_size: usize,
 89        tsk_size: usize,
 90        rank: usize,
 91    ) -> usize {
 92        let tmp_dft_out: usize = module.bytes_of_vec_znx_dft(rank + 1, out_size);
 93        let vmp_ksk: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size)
 94            + GLWECiphertextFourier::keyswitch_scratch_space(module, out_size, rank, in_size, rank, ksk_size);
 95        let tmp_c0: usize = module.bytes_of_vec_znx_big(1, out_size);
 96        let tmp_dft_i: usize = module.bytes_of_vec_znx_dft(rank + 1, tsk_size);
 97        let vmp_tsk: usize = module.bytes_of_vec_znx_dft(1, out_size)
 98            + module.vmp_apply_tmp_bytes(out_size, out_size, rank + 1, rank + 1, rank + 1, tsk_size);
 99        let tmp_idft: usize = module.bytes_of_vec_znx_big(1, tsk_size);
100        let tmp_znx_small: usize = module.bytes_of_vec_znx(1, out_size);
101        let norm: usize = module.vec_znx_big_normalize_tmp_bytes();
102        tmp_dft_out + (vmp_ksk | (tmp_c0 + tmp_dft_i + (vmp_tsk | (tmp_idft + tmp_znx_small + norm))))
103    }
104
105    pub fn automorphism_scratch_space(
106        module: &Module<FFT64>,
107        out_size: usize,
108        in_size: usize,
109        auto_key_size: usize,
110        rank: usize,
111    ) -> usize {
112        let tmp_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, auto_key_size);
113        let tmp_idft: usize = module.bytes_of_vec_znx(rank + 1, out_size);
114        let vmp: usize =
115            GLWECiphertext::keyswitch_from_fourier_scratch_space(module, out_size, rank, in_size, rank, auto_key_size);
116        tmp_dft + tmp_idft + vmp
117    }
118
119    pub fn external_product_scratch_space(
120        module: &Module<FFT64>,
121        out_size: usize,
122        in_size: usize,
123        ggsw_size: usize,
124        rank: usize,
125    ) -> usize {
126        let tmp_in: usize = module.bytes_of_vec_znx_dft(rank + 1, in_size);
127        let tmp_out: usize = module.bytes_of_vec_znx_dft(rank + 1, out_size);
128        let ggsw: usize = GLWECiphertextFourier::external_product_scratch_space(module, out_size, in_size, ggsw_size, rank);
129        tmp_in + tmp_out + ggsw
130    }
131
132    pub fn external_product_inplace_scratch_space(
133        module: &Module<FFT64>,
134        out_size: usize,
135        ggsw_size: usize,
136        rank: usize,
137    ) -> usize {
138        let tmp: usize = module.bytes_of_vec_znx_dft(rank + 1, out_size);
139        let ggsw: usize = GLWECiphertextFourier::external_product_inplace_scratch_space(module, out_size, ggsw_size, rank);
140        tmp + ggsw
141    }
142}
143
144impl<DataSelf> GGSWCiphertext<DataSelf, FFT64>
145where
146    MatZnxDft<DataSelf, FFT64>: MatZnxDftToMut<FFT64> + MatZnxDftToRef<FFT64>,
147{
148    pub fn encrypt_sk<DataPt, DataSk>(
149        &mut self,
150        module: &Module<FFT64>,
151        pt: &ScalarZnx<DataPt>,
152        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
153        source_xa: &mut Source,
154        source_xe: &mut Source,
155        sigma: f64,
156        scratch: &mut Scratch,
157    ) where
158        ScalarZnx<DataPt>: ScalarZnxToRef,
159        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
160    {
161        #[cfg(debug_assertions)]
162        {
163            assert_eq!(self.rank(), sk_dft.rank());
164            assert_eq!(self.n(), module.n());
165            assert_eq!(pt.n(), module.n());
166            assert_eq!(sk_dft.n(), module.n());
167        }
168
169        let size: usize = self.size();
170        let basek: usize = self.basek();
171        let k: usize = self.k();
172        let cols: usize = self.rank() + 1;
173
174        let (tmp_znx_pt, scratch_1) = scratch.tmp_vec_znx(module, 1, size);
175        let (tmp_znx_ct, scrach_2) = scratch_1.tmp_vec_znx(module, cols, size);
176
177        let mut vec_znx_pt: GLWEPlaintext<&mut [u8]> = GLWEPlaintext {
178            data: tmp_znx_pt,
179            basek: basek,
180            k: k,
181        };
182
183        let mut vec_znx_ct: GLWECiphertext<&mut [u8]> = GLWECiphertext {
184            data: tmp_znx_ct,
185            basek: basek,
186            k,
187        };
188
189        (0..self.rows()).for_each(|row_j| {
190            vec_znx_pt.data.zero();
191
192            // Adds the scalar_znx_pt to the i-th limb of the vec_znx_pt
193            module.vec_znx_add_scalar_inplace(&mut vec_znx_pt, 0, row_j, pt, 0);
194            module.vec_znx_normalize_inplace(basek, &mut vec_znx_pt, 0, scrach_2);
195
196            (0..cols).for_each(|col_i| {
197                // rlwe encrypt of vec_znx_pt into vec_znx_ct
198
199                vec_znx_ct.encrypt_sk_private(
200                    module,
201                    Some((&vec_znx_pt, col_i)),
202                    sk_dft,
203                    source_xa,
204                    source_xe,
205                    sigma,
206                    scrach_2,
207                );
208
209                // Switch vec_znx_ct into DFT domain
210                {
211                    let (mut vec_znx_dft_ct, _) = scrach_2.tmp_vec_znx_dft(module, cols, size);
212
213                    (0..cols).for_each(|i| {
214                        module.vec_znx_dft(&mut vec_znx_dft_ct, i, &vec_znx_ct, i);
215                    });
216
217                    module.vmp_prepare_row(self, row_j, col_i, &vec_znx_dft_ct);
218                }
219            });
220        });
221    }
222
223    pub fn keyswitch<DataLhs, DataRhs0, DataRhs1>(
224        &mut self,
225        module: &Module<FFT64>,
226        lhs: &GGSWCiphertext<DataLhs, FFT64>,
227        ksk: &GLWESwitchingKey<DataRhs0, FFT64>,
228        tsk: &TensorKey<DataRhs1, FFT64>,
229        scratch: &mut Scratch,
230    ) where
231        MatZnxDft<DataLhs, FFT64>: MatZnxDftToRef<FFT64>,
232        MatZnxDft<DataRhs0, FFT64>: MatZnxDftToRef<FFT64>,
233        MatZnxDft<DataRhs1, FFT64>: MatZnxDftToRef<FFT64>,
234    {
235        #[cfg(debug_assertions)]
236        {
237            assert_eq!(
238                self.rank(),
239                lhs.rank(),
240                "ggsw_out rank: {} != ggsw_in rank: {}",
241                self.rank(),
242                lhs.rank()
243            );
244            assert_eq!(
245                lhs.rank(),
246                ksk.rank(),
247                "ggsw_in rank: {} != ksk rank: {}",
248                lhs.rank(),
249                ksk.rank()
250            );
251            assert_eq!(
252                lhs.rank(),
253                tsk.rank(),
254                "ggsw_in rank: {} != tsk rank: {}",
255                lhs.rank(),
256                tsk.rank()
257            );
258        }
259
260        let cols: usize = self.rank() + 1;
261
262        // Example for rank 3:
263        //
264        // Note: M is a vector (m, Bm, B^2m, B^3m, ...), so each column is
265        // actually composed of that many rows.
266        //
267        // # Input
268        //
269        // col 0: (-(a0s0 + a1s1 + a2s2) + M, a0    , a1    , a2    )
270        // col 1: (-(b0s0 + b1s1 + b2s2)    , b0 + M, b1    , b2    )
271        // col 2: (-(c0s0 + c1s1 + c2s2)    , c0    , c1 + M, c2    )
272        // col 3: (-(d0s0 + d1s1 + d2s2)    , d0    , d1    , d2 + M)
273        //
274        // # Output
275        //
276        // col 0: (-(a0s0' + a1s1' + a2s2') + M, a0    , a1    , a2    )
277        // col 1: (-(b0s0' + b1s1' + b2s2')    , b0 + M, b1    , b2    )
278        // col 2: (-(c0s0' + c1s1' + c2s2')    , c0    , c1 + M, c2    )
279        // col 3: (-(d0s0' + d1s1' + d2s2')    , d0    , d1    , d2 + M)
280        (0..self.rows()).for_each(|row_j| {
281            let (tmp_dft_out_data, scratch1) = scratch.tmp_vec_znx_dft(module, self.rank() + 1, self.size());
282
283            let mut tmp_dft_out: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
284                data: tmp_dft_out_data,
285                basek: lhs.basek(),
286                k: lhs.k(),
287            };
288
289            {
290                let (tmp_dft_in_data, scratch2) = scratch1.tmp_vec_znx_dft(module, lhs.rank() + 1, lhs.size());
291
292                let mut tmp_dft_in: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
293                    data: tmp_dft_in_data,
294                    basek: lhs.basek(),
295                    k: lhs.k(),
296                };
297
298                // 1) Applies key-switching to GGSW[i][0]: (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2)
299                lhs.get_row(module, row_j, 0, &mut tmp_dft_in);
300                // (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2) -> (-(a0s0' + a1s1' + a2s2') + M[i], a0, a1, a2)
301                tmp_dft_out.keyswitch(module, &tmp_dft_in, ksk, scratch2);
302                self.set_row(module, row_j, 0, &tmp_dft_out);
303            }
304
305            // 2) Isolates IDFT(-(a0s0' + a1s1' + a2s2') + M[i])
306            let (mut tmp_c0_data, scratch2) = scratch1.tmp_vec_znx_big(module, 1, self.size());
307            module.vec_znx_idft_tmp_a(&mut tmp_c0_data, 0, &mut tmp_dft_out, 0);
308
309            // 3) Expands the i-th row of the other columns using the tensor key
310            // col 1: (-(b0s0' + b1s1' + b2s2')    , b0 + M[i], b1       , b2       ) = KS_{s0's0', s0's1', s0's2'}(a0) + (0, -(a0s0' + a1s1' + a2s2') + M[i], 0, 0)
311            // col 2: (-(c0s0' + c1s1' + c2s2')    , c0       , c1 + M[i], c2       ) = KS_{s1's0', s1's1', s1's2'}(a1) + (0, 0, -(a0s0' + a1s1' + a2s2') + M[i], 0)
312            // col 3: (-(d0s0' + d1s1' + d2s2')    , d0       , d1       , d2 + M[i]) = KS_{s2's0', s2's1', s2's2'}(a2) + (0, 0, 0, -(a0s0' + a1s1' + a2s2') + M[i])
313            (1..cols).for_each(|col_i| {
314                let (tmp_dft_i_data, scratch3) = scratch2.tmp_vec_znx_dft(module, cols, tsk.size());
315                let mut tmp_dft_i: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
316                    data: tmp_dft_i_data,
317                    basek: lhs.basek(),
318                    k: lhs.k(),
319                };
320
321                // 5) Performs a key-switch for each combination of s[i]*s[j], i.e. for a0, a1, a2
322                //
323                // # Example for col=1
324                //
325                // a0 * (-(f0s0 + f1s1 + f1s2) + s0^2, f0, f1, f2) = (-(a0f0s0 + a0f1s1 + a0f1s2) + a0s0^2, a0f0, a0f1, a0f2)
326                // +
327                // a1 * (-(g0s0 + g1s1 + g1s2) + s0s1, g0, g1, g2) = (-(a1g0s0 + a1g1s1 + a1g1s2) + a1s0s1, a1g0, a1g1, a1g2)
328                // +
329                // a2 * (-(h0s0 + h1s1 + h1s2) + s0s2, h0, h1, h2) = (-(a2h0s0 + a2h1s1 + a2h1s2) + a2s0s2, a2h0, a2h1, a2h2)
330                // =
331                // (-(x0s0' + x1s1' + x2s2') + s0'(a0s0' + a1s1' + a2s2'), x0, x1, x2)
332                (1..cols).for_each(|col_j| {
333                    // Extracts a[i] and multipies with Enc(s'[i]s'[j])
334                    let (mut tmp_dft_col_data, scratch4) = scratch3.tmp_vec_znx_dft(module, 1, self.size());
335                    tmp_dft_col_data.extract_column(0, &tmp_dft_out.data, col_j);
336
337                    if col_j == 1 {
338                        module.vmp_apply(
339                            &mut tmp_dft_i,
340                            &tmp_dft_col_data,
341                            tsk.at(col_i - 1, col_j - 1), // Selects Enc(s'[i]s'[j])
342                            scratch4,
343                        );
344                    } else {
345                        module.vmp_apply_add(
346                            &mut tmp_dft_i,
347                            &tmp_dft_col_data,
348                            tsk.at(col_i - 1, col_j - 1), // Selects Enc(s'[i]s'[j])
349                            scratch4,
350                        );
351                    }
352                });
353
354                // Adds -(sum a[i] * s[i]) + m)  on the i-th column of tmp_idft_i
355                //
356                // (-(x0s0' + x1s1' + x2s2') + a0s0's0' + a1s0's1' + a2s0's2', x0, x1, x2)
357                // +
358                // (0, -(a0s0' + a1s1' + a2s2') + M[i], 0, 0)
359                // =
360                // (-(x0s0' + x1s1' + x2s2') + s0'(a0s0' + a1s1' + a2s2'), x0 -(a0s0' + a1s1' + a2s2') + M[i], x1, x2)
361                // =
362                // (-(x0s0' + x1s1' + x2s2'), x0 + M[i], x1, x2)
363                {
364                    let (mut tmp_idft, scratch3) = scratch3.tmp_vec_znx_big(module, 1, tsk.size());
365                    let (mut tmp_znx_small, scratch5) = scratch3.tmp_vec_znx(module, 1, self.size());
366                    (0..cols).for_each(|i| {
367                        module.vec_znx_idft_tmp_a(&mut tmp_idft, 0, &mut tmp_dft_i, i);
368                        if i == col_i {
369                            module.vec_znx_big_add_inplace(&mut tmp_idft, 0, &tmp_c0_data, 0);
370                        }
371                        module.vec_znx_big_normalize(self.basek(), &mut tmp_znx_small, 0, &tmp_idft, 0, scratch5);
372                        module.vec_znx_dft(&mut tmp_dft_i, i, &tmp_znx_small, 0);
373                    });
374                }
375
376                // Stores (-(x0s0' + x1s1' + x2s2'), x0 + M[i], x1, x2)
377                self.set_row(module, row_j, col_i, &tmp_dft_i);
378            })
379        })
380    }
381
382    pub fn automorphism<DataLhs, DataRhs>(
383        &mut self,
384        module: &Module<FFT64>,
385        lhs: &GGSWCiphertext<DataLhs, FFT64>,
386        rhs: &AutomorphismKey<DataRhs, FFT64>,
387        scratch: &mut Scratch,
388    ) where
389        MatZnxDft<DataLhs, FFT64>: MatZnxDftToRef<FFT64>,
390        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
391    {
392        #[cfg(debug_assertions)]
393        {
394            assert_eq!(
395                self.rank(),
396                lhs.rank(),
397                "ggsw_out rank: {} != ggsw_in rank: {}",
398                self.rank(),
399                lhs.rank()
400            );
401            assert_eq!(
402                self.rank(),
403                rhs.rank(),
404                "ggsw_in rank: {} != auto_key rank: {}",
405                self.rank(),
406                rhs.rank()
407            );
408        };
409        let cols: usize = self.rank() + 1;
410
411        let (tmp_dft_data, scratch1) = scratch.tmp_vec_znx_dft(module, cols, rhs.size()); //TODO optimize
412
413        let mut tmp_dft: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
414            data: tmp_dft_data,
415            basek: lhs.basek(),
416            k: lhs.k(),
417        };
418
419        let (tmp_idft_data, scratch2) = scratch1.tmp_vec_znx(module, cols, self.size());
420
421        let mut tmp_idft: GLWECiphertext<&mut [u8]> = GLWECiphertext::<&mut [u8]> {
422            data: tmp_idft_data,
423            basek: self.basek(),
424            k: self.k(),
425        };
426
427        (0..cols).for_each(|col_i| {
428            (0..self.rows()).for_each(|row_j| {
429                lhs.get_row(module, row_j, col_i, &mut tmp_dft);
430                tmp_idft.keyswitch_from_fourier(module, &tmp_dft, &rhs.key, scratch2);
431                (0..cols).for_each(|i| {
432                    module.vec_znx_automorphism_inplace(rhs.p(), &mut tmp_idft, i);
433                });
434                self.set_row(module, row_j, col_i, &tmp_dft);
435            });
436        });
437
438        tmp_dft.data.zero();
439
440        (self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
441            (0..self.rank() + 1).for_each(|col_j| {
442                self.set_row(module, row_i, col_j, &tmp_dft);
443            });
444        });
445    }
446
447    pub fn external_product<DataLhs, DataRhs>(
448        &mut self,
449        module: &Module<FFT64>,
450        lhs: &GGSWCiphertext<DataLhs, FFT64>,
451        rhs: &GGSWCiphertext<DataRhs, FFT64>,
452        scratch: &mut Scratch,
453    ) where
454        MatZnxDft<DataLhs, FFT64>: MatZnxDftToRef<FFT64>,
455        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
456    {
457        #[cfg(debug_assertions)]
458        {
459            assert_eq!(
460                self.rank(),
461                lhs.rank(),
462                "ggsw_out rank: {} != ggsw_in rank: {}",
463                self.rank(),
464                lhs.rank()
465            );
466            assert_eq!(
467                self.rank(),
468                rhs.rank(),
469                "ggsw_in rank: {} != ggsw_apply rank: {}",
470                self.rank(),
471                rhs.rank()
472            );
473        }
474
475        let (tmp_in_data, scratch1) = scratch.tmp_vec_znx_dft(module, lhs.rank() + 1, lhs.size());
476
477        let mut tmp_in: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
478            data: tmp_in_data,
479            basek: lhs.basek(),
480            k: lhs.k(),
481        };
482
483        let (tmp_out_data, scratch2) = scratch1.tmp_vec_znx_dft(module, self.rank() + 1, self.size());
484
485        let mut tmp_out: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
486            data: tmp_out_data,
487            basek: self.basek(),
488            k: self.k(),
489        };
490
491        (0..self.rank() + 1).for_each(|col_i| {
492            (0..self.rows()).for_each(|row_j| {
493                lhs.get_row(module, row_j, col_i, &mut tmp_in);
494                tmp_out.external_product(module, &tmp_in, rhs, scratch2);
495                self.set_row(module, row_j, col_i, &tmp_out);
496            });
497        });
498
499        tmp_out.data.zero();
500
501        (self.rows().min(lhs.rows())..self.rows()).for_each(|row_i| {
502            (0..self.rank() + 1).for_each(|col_j| {
503                self.set_row(module, row_i, col_j, &tmp_out);
504            });
505        });
506    }
507
508    pub fn external_product_inplace<DataRhs>(
509        &mut self,
510        module: &Module<FFT64>,
511        rhs: &GGSWCiphertext<DataRhs, FFT64>,
512        scratch: &mut Scratch,
513    ) where
514        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
515    {
516        #[cfg(debug_assertions)]
517        {
518            assert_eq!(
519                self.rank(),
520                rhs.rank(),
521                "ggsw_out rank: {} != ggsw_apply: {}",
522                self.rank(),
523                rhs.rank()
524            );
525        }
526
527        let (tmp_data, scratch1) = scratch.tmp_vec_znx_dft(module, self.rank() + 1, self.size());
528
529        let mut tmp: GLWECiphertextFourier<&mut [u8], FFT64> = GLWECiphertextFourier::<&mut [u8], FFT64> {
530            data: tmp_data,
531            basek: self.basek(),
532            k: self.k(),
533        };
534
535        (0..self.rank() + 1).for_each(|col_i| {
536            (0..self.rows()).for_each(|row_j| {
537                self.get_row(module, row_j, col_i, &mut tmp);
538                tmp.external_product_inplace(module, rhs, scratch1);
539                self.set_row(module, row_j, col_i, &tmp);
540            });
541        });
542    }
543}
544
545impl<DataSelf> GetRow<FFT64> for GGSWCiphertext<DataSelf, FFT64>
546where
547    MatZnxDft<DataSelf, FFT64>: MatZnxDftToRef<FFT64>,
548{
549    fn get_row<R>(&self, module: &Module<FFT64>, row_i: usize, col_j: usize, res: &mut R)
550    where
551        R: VecZnxDftToMut<FFT64>,
552    {
553        module.vmp_extract_row(res, self, row_i, col_j);
554    }
555}
556
557impl<DataSelf> SetRow<FFT64> for GGSWCiphertext<DataSelf, FFT64>
558where
559    MatZnxDft<DataSelf, FFT64>: MatZnxDftToMut<FFT64>,
560{
561    fn set_row<R>(&mut self, module: &Module<FFT64>, row_i: usize, col_j: usize, a: &R)
562    where
563        R: VecZnxDftToRef<FFT64>,
564    {
565        module.vmp_prepare_row(self, row_i, col_j, a);
566    }
567}