arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/core/src/glwe_ciphertext.rs
core/src/glwe_ciphertext.rsRaw
  1use base2k::{
  2    AddNormal, Backend, FFT64, FillUniform, MatZnxDft, MatZnxDftOps, MatZnxDftScratch, MatZnxDftToRef, Module, ScalarZnxAlloc,
  3    ScalarZnxDft, ScalarZnxDftAlloc, ScalarZnxDftOps, ScalarZnxDftToRef, Scratch, VecZnx, VecZnxAlloc, VecZnxBig, VecZnxBigAlloc,
  4    VecZnxBigOps, VecZnxBigScratch, VecZnxDft, VecZnxDftAlloc, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, VecZnxOps,
  5    VecZnxToMut, VecZnxToRef, ZnxInfos, ZnxZero,
  6};
  7use sampling::source::Source;
  8
  9use crate::{
 10    SIX_SIGMA,
 11    automorphism::AutomorphismKey,
 12    elem::Infos,
 13    ggsw_ciphertext::GGSWCiphertext,
 14    glwe_ciphertext_fourier::GLWECiphertextFourier,
 15    glwe_plaintext::GLWEPlaintext,
 16    keys::{GLWEPublicKey, SecretDistribution, SecretKeyFourier},
 17    keyswitch_key::GLWESwitchingKey,
 18    utils::derive_size,
 19};
 20
 21pub struct GLWECiphertext<C> {
 22    pub data: VecZnx<C>,
 23    pub basek: usize,
 24    pub k: usize,
 25}
 26
 27impl GLWECiphertext<Vec<u8>> {
 28    pub fn new<B: Backend>(module: &Module<B>, basek: usize, k: usize, rank: usize) -> Self {
 29        Self {
 30            data: module.new_vec_znx(rank + 1, derive_size(basek, k)),
 31            basek,
 32            k,
 33        }
 34    }
 35}
 36
 37impl<T> Infos for GLWECiphertext<T> {
 38    type Inner = VecZnx<T>;
 39
 40    fn inner(&self) -> &Self::Inner {
 41        &self.data
 42    }
 43
 44    fn basek(&self) -> usize {
 45        self.basek
 46    }
 47
 48    fn k(&self) -> usize {
 49        self.k
 50    }
 51}
 52
 53impl<T> GLWECiphertext<T> {
 54    pub fn rank(&self) -> usize {
 55        self.cols() - 1
 56    }
 57}
 58
 59impl<C> VecZnxToMut for GLWECiphertext<C>
 60where
 61    VecZnx<C>: VecZnxToMut,
 62{
 63    fn to_mut(&mut self) -> VecZnx<&mut [u8]> {
 64        self.data.to_mut()
 65    }
 66}
 67
 68impl<C> VecZnxToRef for GLWECiphertext<C>
 69where
 70    VecZnx<C>: VecZnxToRef,
 71{
 72    fn to_ref(&self) -> VecZnx<&[u8]> {
 73        self.data.to_ref()
 74    }
 75}
 76
 77impl<C> GLWECiphertext<C>
 78where
 79    VecZnx<C>: VecZnxToRef,
 80{
 81    #[allow(dead_code)]
 82    pub(crate) fn dft<R>(&self, module: &Module<FFT64>, res: &mut GLWECiphertextFourier<R, FFT64>)
 83    where
 84        VecZnxDft<R, FFT64>: VecZnxDftToMut<FFT64> + ZnxInfos,
 85    {
 86        #[cfg(debug_assertions)]
 87        {
 88            assert_eq!(self.rank(), res.rank());
 89            assert_eq!(self.basek(), res.basek())
 90        }
 91
 92        (0..self.rank() + 1).for_each(|i| {
 93            module.vec_znx_dft(res, i, self, i);
 94        })
 95    }
 96}
 97
 98impl GLWECiphertext<Vec<u8>> {
 99    pub fn encrypt_sk_scratch_space(module: &Module<FFT64>, ct_size: usize) -> usize {
100        module.vec_znx_big_normalize_tmp_bytes()
101            + module.bytes_of_vec_znx_dft(1, ct_size)
102            + module.bytes_of_vec_znx_big(1, ct_size)
103    }
104    pub fn encrypt_pk_scratch_space(module: &Module<FFT64>, pk_size: usize) -> usize {
105        ((module.bytes_of_vec_znx_dft(1, pk_size) + module.bytes_of_vec_znx_big(1, pk_size)) | module.bytes_of_scalar_znx(1))
106            + module.bytes_of_scalar_znx_dft(1)
107            + module.vec_znx_big_normalize_tmp_bytes()
108    }
109
110    pub fn decrypt_scratch_space(module: &Module<FFT64>, ct_size: usize) -> usize {
111        (module.vec_znx_big_normalize_tmp_bytes() | module.bytes_of_vec_znx_dft(1, ct_size))
112            + module.bytes_of_vec_znx_big(1, ct_size)
113    }
114
115    pub fn keyswitch_scratch_space(
116        module: &Module<FFT64>,
117        out_size: usize,
118        out_rank: usize,
119        in_size: usize,
120        in_rank: usize,
121        ksk_size: usize,
122    ) -> usize {
123        let res_dft: usize = module.bytes_of_vec_znx_dft(out_rank + 1, ksk_size);
124        let vmp: usize = module.vmp_apply_tmp_bytes(
125            out_size,
126            in_size,
127            in_size,
128            in_rank + 1,
129            out_rank + 1,
130            ksk_size,
131        ) + module.bytes_of_vec_znx_dft(in_rank, in_size);
132        let normalize: usize = module.vec_znx_big_normalize_tmp_bytes();
133
134        return res_dft + (vmp | normalize);
135    }
136
137    pub fn keyswitch_inplace_scratch_space(module: &Module<FFT64>, out_size: usize, out_rank: usize, ksk_size: usize) -> usize {
138        GLWECiphertext::keyswitch_scratch_space(module, out_size, out_rank, out_size, out_rank, ksk_size)
139    }
140
141    pub fn automorphism_scratch_space(
142        module: &Module<FFT64>,
143        out_size: usize,
144        out_rank: usize,
145        in_size: usize,
146        autokey_size: usize,
147    ) -> usize {
148        GLWECiphertext::keyswitch_scratch_space(module, out_size, out_rank, in_size, out_rank, autokey_size)
149    }
150
151    pub fn automorphism_inplace_scratch_space(
152        module: &Module<FFT64>,
153        out_size: usize,
154        out_rank: usize,
155        autokey_size: usize,
156    ) -> usize {
157        GLWECiphertext::keyswitch_scratch_space(module, out_size, out_rank, out_size, out_rank, autokey_size)
158    }
159
160    pub fn external_product_scratch_space(
161        module: &Module<FFT64>,
162        out_size: usize,
163        out_rank: usize,
164        in_size: usize,
165        ggsw_size: usize,
166    ) -> usize {
167        let res_dft: usize = module.bytes_of_vec_znx_dft(out_rank + 1, ggsw_size);
168        let vmp: usize = module.bytes_of_vec_znx_dft(out_rank + 1, in_size)
169            + module.vmp_apply_tmp_bytes(
170                out_size,
171                in_size,
172                in_size,      // rows
173                out_rank + 1, // cols in
174                out_rank + 1, // cols out
175                ggsw_size,
176            );
177        let normalize: usize = module.vec_znx_big_normalize_tmp_bytes();
178
179        res_dft + (vmp | normalize)
180    }
181
182    pub fn external_product_inplace_scratch_space(
183        module: &Module<FFT64>,
184        out_size: usize,
185        out_rank: usize,
186        ggsw_size: usize,
187    ) -> usize {
188        GLWECiphertext::external_product_scratch_space(module, out_size, out_rank, out_size, ggsw_size)
189    }
190}
191
192impl<DataSelf> GLWECiphertext<DataSelf>
193where
194    VecZnx<DataSelf>: VecZnxToMut + VecZnxToRef,
195{
196    pub fn encrypt_sk<DataPt, DataSk>(
197        &mut self,
198        module: &Module<FFT64>,
199        pt: &GLWEPlaintext<DataPt>,
200        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
201        source_xa: &mut Source,
202        source_xe: &mut Source,
203        sigma: f64,
204        scratch: &mut Scratch,
205    ) where
206        VecZnx<DataPt>: VecZnxToRef,
207        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
208    {
209        self.encrypt_sk_private(
210            module,
211            Some((pt, 0)),
212            sk_dft,
213            source_xa,
214            source_xe,
215            sigma,
216            scratch,
217        );
218    }
219
220    pub fn encrypt_zero_sk<DataSk>(
221        &mut self,
222        module: &Module<FFT64>,
223        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
224        source_xa: &mut Source,
225        source_xe: &mut Source,
226        sigma: f64,
227        scratch: &mut Scratch,
228    ) where
229        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
230    {
231        self.encrypt_sk_private(module, None, sk_dft, source_xa, source_xe, sigma, scratch);
232    }
233
234    pub fn encrypt_pk<DataPt, DataPk>(
235        &mut self,
236        module: &Module<FFT64>,
237        pt: &GLWEPlaintext<DataPt>,
238        pk: &GLWEPublicKey<DataPk, FFT64>,
239        source_xu: &mut Source,
240        source_xe: &mut Source,
241        sigma: f64,
242        scratch: &mut Scratch,
243    ) where
244        VecZnx<DataPt>: VecZnxToRef,
245        VecZnxDft<DataPk, FFT64>: VecZnxDftToRef<FFT64>,
246    {
247        self.encrypt_pk_private(
248            module,
249            Some((pt, 0)),
250            pk,
251            source_xu,
252            source_xe,
253            sigma,
254            scratch,
255        );
256    }
257
258    pub fn encrypt_zero_pk<DataPk>(
259        &mut self,
260        module: &Module<FFT64>,
261        pk: &GLWEPublicKey<DataPk, FFT64>,
262        source_xu: &mut Source,
263        source_xe: &mut Source,
264        sigma: f64,
265        scratch: &mut Scratch,
266    ) where
267        VecZnxDft<DataPk, FFT64>: VecZnxDftToRef<FFT64>,
268    {
269        self.encrypt_pk_private(module, None, pk, source_xu, source_xe, sigma, scratch);
270    }
271
272    pub fn automorphism<DataLhs, DataRhs>(
273        &mut self,
274        module: &Module<FFT64>,
275        lhs: &GLWECiphertext<DataLhs>,
276        rhs: &AutomorphismKey<DataRhs, FFT64>,
277        scratch: &mut Scratch,
278    ) where
279        VecZnx<DataLhs>: VecZnxToRef,
280        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
281    {
282        self.keyswitch(module, lhs, &rhs.key, scratch);
283        //(0..self.rank() + 1).for_each(|i| {
284        //    module.vec_znx_automorphism_inplace(rhs.p(), self, i);
285        //})
286    }
287
288    pub fn automorphism_inplace<DataRhs>(
289        &mut self,
290        module: &Module<FFT64>,
291        rhs: &AutomorphismKey<DataRhs, FFT64>,
292        scratch: &mut Scratch,
293    ) where
294        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
295    {
296        self.keyswitch_inplace(module, &rhs.key, scratch);
297        (0..self.rank() + 1).for_each(|i| {
298            module.vec_znx_automorphism_inplace(rhs.p(), self, i);
299        })
300    }
301
302    pub(crate) fn keyswitch_from_fourier<DataLhs, DataRhs>(
303        &mut self,
304        module: &Module<FFT64>,
305        lhs: &GLWECiphertextFourier<DataLhs, FFT64>,
306        rhs: &GLWESwitchingKey<DataRhs, FFT64>,
307        scratch: &mut Scratch,
308    ) where
309        VecZnxDft<DataLhs, FFT64>: VecZnxDftToRef<FFT64>,
310        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
311    {
312        let basek: usize = self.basek();
313
314        #[cfg(debug_assertions)]
315        {
316            assert_eq!(lhs.rank(), rhs.rank_in());
317            assert_eq!(self.rank(), rhs.rank_out());
318            assert_eq!(self.basek(), basek);
319            assert_eq!(lhs.basek(), basek);
320            assert_eq!(rhs.n(), module.n());
321            assert_eq!(self.n(), module.n());
322            assert_eq!(lhs.n(), module.n());
323            assert!(
324                scratch.available()
325                    >= GLWECiphertextFourier::keyswitch_scratch_space(
326                        module,
327                        self.size(),
328                        self.rank(),
329                        lhs.size(),
330                        lhs.rank(),
331                        rhs.size(),
332                    )
333            );
334        }
335
336        let cols_in: usize = rhs.rank_in();
337        let cols_out: usize = rhs.rank_out() + 1;
338
339        // Buffer of the result of VMP in DFT
340        let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols_out, rhs.size()); // Todo optimise
341
342        {
343            // Applies VMP
344            let (mut ai_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols_in, lhs.size());
345            (0..cols_in).for_each(|col_i| {
346                module.vec_znx_dft_copy(&mut ai_dft, col_i, lhs, col_i + 1);
347            });
348            module.vmp_apply(&mut res_dft, &ai_dft, rhs, scratch2);
349        }
350
351        // Switches result of VMP outside of DFT
352        let mut res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume::<&mut [u8]>(res_dft);
353
354        {
355            // Switches lhs 0-th outside of DFT domain and adds on
356            let (mut a0_big, scratch2) = scratch1.tmp_vec_znx_big(module, 1, lhs.size());
357            module.vec_znx_idft(&mut a0_big, 0, lhs, 0, scratch2);
358            module.vec_znx_big_add_inplace(&mut res_big, 0, &a0_big, 0);
359        }
360
361        (0..cols_out).for_each(|i| {
362            module.vec_znx_big_normalize(basek, self, i, &res_big, i, scratch1);
363        });
364    }
365
366    pub fn keyswitch<DataLhs, DataRhs>(
367        &mut self,
368        module: &Module<FFT64>,
369        lhs: &GLWECiphertext<DataLhs>,
370        rhs: &GLWESwitchingKey<DataRhs, FFT64>,
371        scratch: &mut Scratch,
372    ) where
373        VecZnx<DataLhs>: VecZnxToRef,
374        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
375    {
376        let basek: usize = self.basek();
377
378        #[cfg(debug_assertions)]
379        {
380            assert_eq!(lhs.rank(), rhs.rank_in());
381            assert_eq!(self.rank(), rhs.rank_out());
382            assert_eq!(self.basek(), basek);
383            assert_eq!(lhs.basek(), basek);
384            assert_eq!(rhs.n(), module.n());
385            assert_eq!(self.n(), module.n());
386            assert_eq!(lhs.n(), module.n());
387            assert!(
388                scratch.available()
389                    >= GLWECiphertext::keyswitch_scratch_space(
390                        module,
391                        self.size(),
392                        self.rank(),
393                        lhs.size(),
394                        lhs.rank(),
395                        rhs.size(),
396                    )
397            );
398        }
399
400        let cols_in: usize = rhs.rank_in();
401        let cols_out: usize = rhs.rank_out() + 1;
402
403        let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols_out, rhs.size()); // Todo optimise
404
405        {
406            let (mut ai_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols_in, lhs.size());
407            (0..cols_in).for_each(|col_i| {
408                module.vec_znx_dft(&mut ai_dft, col_i, lhs, col_i + 1);
409            });
410            module.vmp_apply(&mut res_dft, &ai_dft, rhs, scratch2);
411        }
412
413        let mut res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(res_dft);
414
415        module.vec_znx_big_add_small_inplace(&mut res_big, 0, lhs, 0);
416
417        (0..cols_out).for_each(|i| {
418            module.vec_znx_big_normalize(basek, self, i, &res_big, i, scratch1);
419        });
420    }
421
422    pub fn keyswitch_inplace<DataRhs>(
423        &mut self,
424        module: &Module<FFT64>,
425        rhs: &GLWESwitchingKey<DataRhs, FFT64>,
426        scratch: &mut Scratch,
427    ) where
428        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
429    {
430        unsafe {
431            let self_ptr: *mut GLWECiphertext<DataSelf> = self as *mut GLWECiphertext<DataSelf>;
432            self.keyswitch(&module, &*self_ptr, rhs, scratch);
433        }
434    }
435
436    pub fn external_product<DataLhs, DataRhs>(
437        &mut self,
438        module: &Module<FFT64>,
439        lhs: &GLWECiphertext<DataLhs>,
440        rhs: &GGSWCiphertext<DataRhs, FFT64>,
441        scratch: &mut Scratch,
442    ) where
443        VecZnx<DataLhs>: VecZnxToRef,
444        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
445    {
446        let basek: usize = self.basek();
447
448        #[cfg(debug_assertions)]
449        {
450            assert_eq!(rhs.rank(), lhs.rank());
451            assert_eq!(rhs.rank(), self.rank());
452            assert_eq!(self.basek(), basek);
453            assert_eq!(lhs.basek(), basek);
454            assert_eq!(rhs.n(), module.n());
455            assert_eq!(self.n(), module.n());
456            assert_eq!(lhs.n(), module.n());
457        }
458
459        let cols: usize = rhs.rank() + 1;
460
461        let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols, rhs.size()); // Todo optimise
462
463        {
464            let (mut a_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols, lhs.size());
465            (0..cols).for_each(|col_i| {
466                module.vec_znx_dft(&mut a_dft, col_i, lhs, col_i);
467            });
468            module.vmp_apply(&mut res_dft, &a_dft, rhs, scratch2);
469        }
470
471        let res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(res_dft);
472
473        (0..cols).for_each(|i| {
474            module.vec_znx_big_normalize(basek, self, i, &res_big, i, scratch1);
475        });
476    }
477
478    pub fn external_product_inplace<DataRhs>(
479        &mut self,
480        module: &Module<FFT64>,
481        rhs: &GGSWCiphertext<DataRhs, FFT64>,
482        scratch: &mut Scratch,
483    ) where
484        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
485    {
486        unsafe {
487            let self_ptr: *mut GLWECiphertext<DataSelf> = self as *mut GLWECiphertext<DataSelf>;
488            self.external_product(&module, &*self_ptr, rhs, scratch);
489        }
490    }
491
492    pub(crate) fn encrypt_sk_private<DataPt, DataSk>(
493        &mut self,
494        module: &Module<FFT64>,
495        pt: Option<(&GLWEPlaintext<DataPt>, usize)>,
496        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
497        source_xa: &mut Source,
498        source_xe: &mut Source,
499        sigma: f64,
500        scratch: &mut Scratch,
501    ) where
502        VecZnx<DataPt>: VecZnxToRef,
503        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
504    {
505        #[cfg(debug_assertions)]
506        {
507            assert_eq!(self.rank(), sk_dft.rank());
508            assert_eq!(sk_dft.n(), module.n());
509            assert_eq!(self.n(), module.n());
510            if let Some((pt, col)) = pt {
511                assert_eq!(pt.n(), module.n());
512                assert!(col < self.rank() + 1);
513            }
514        }
515
516        let log_base2k: usize = self.basek();
517        let log_k: usize = self.k();
518        let size: usize = self.size();
519        let cols: usize = self.rank() + 1;
520
521        let (mut c0_big, scratch_1) = scratch.tmp_vec_znx(module, 1, size);
522        c0_big.zero();
523
524        {
525            // c[i] = uniform
526            // c[0] -= c[i] * s[i],
527            (1..cols).for_each(|i| {
528                let (mut ci_dft, scratch_2) = scratch_1.tmp_vec_znx_dft(module, 1, size);
529
530                // c[i] = uniform
531                self.data.fill_uniform(log_base2k, i, size, source_xa);
532
533                // c[i] = norm(IDFT(DFT(c[i]) * DFT(s[i])))
534                module.vec_znx_dft(&mut ci_dft, 0, self, i);
535                module.svp_apply_inplace(&mut ci_dft, 0, sk_dft, i - 1);
536                let ci_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(ci_dft);
537
538                // use c[0] as buffer, which is overwritten later by the normalization step
539                module.vec_znx_big_normalize(log_base2k, self, 0, &ci_big, 0, scratch_2);
540
541                // c0_tmp = -c[i] * s[i] (use c[0] as buffer)
542                module.vec_znx_sub_ab_inplace(&mut c0_big, 0, self, 0);
543
544                // c[i] += m if col = i
545                if let Some((pt, col)) = pt {
546                    if i == col {
547                        module.vec_znx_add_inplace(self, i, pt, 0);
548                        module.vec_znx_normalize_inplace(log_base2k, self, i, scratch_2);
549                    }
550                }
551            });
552        }
553
554        // c[0] += e
555        c0_big.add_normal(log_base2k, 0, log_k, source_xe, sigma, sigma * SIX_SIGMA);
556
557        // c[0] += m if col = 0
558        if let Some((pt, col)) = pt {
559            if col == 0 {
560                module.vec_znx_add_inplace(&mut c0_big, 0, pt, 0);
561            }
562        }
563
564        // c[0] = norm(c[0])
565        module.vec_znx_normalize(log_base2k, self, 0, &c0_big, 0, scratch_1);
566    }
567
568    pub(crate) fn encrypt_pk_private<DataPt, DataPk>(
569        &mut self,
570        module: &Module<FFT64>,
571        pt: Option<(&GLWEPlaintext<DataPt>, usize)>,
572        pk: &GLWEPublicKey<DataPk, FFT64>,
573        source_xu: &mut Source,
574        source_xe: &mut Source,
575        sigma: f64,
576        scratch: &mut Scratch,
577    ) where
578        VecZnx<DataPt>: VecZnxToRef,
579        VecZnxDft<DataPk, FFT64>: VecZnxDftToRef<FFT64>,
580    {
581        #[cfg(debug_assertions)]
582        {
583            assert_eq!(self.basek(), pk.basek());
584            assert_eq!(self.n(), module.n());
585            assert_eq!(pk.n(), module.n());
586            assert_eq!(self.rank(), pk.rank());
587            if let Some((pt, _)) = pt {
588                assert_eq!(pt.basek(), pk.basek());
589                assert_eq!(pt.n(), module.n());
590            }
591        }
592
593        let log_base2k: usize = pk.basek();
594        let size_pk: usize = pk.size();
595        let cols: usize = self.rank() + 1;
596
597        // Generates u according to the underlying secret distribution.
598        let (mut u_dft, scratch_1) = scratch.tmp_scalar_znx_dft(module, 1);
599
600        {
601            let (mut u, _) = scratch_1.tmp_scalar_znx(module, 1);
602            match pk.dist {
603                SecretDistribution::NONE => panic!(
604                    "invalid public key: SecretDistribution::NONE, ensure it has been correctly intialized through \
605                     Self::generate"
606                ),
607                SecretDistribution::TernaryFixed(hw) => u.fill_ternary_hw(0, hw, source_xu),
608                SecretDistribution::TernaryProb(prob) => u.fill_ternary_prob(0, prob, source_xu),
609                SecretDistribution::ZERO => {}
610            }
611
612            module.svp_prepare(&mut u_dft, 0, &u, 0);
613        }
614
615        // ct[i] = pk[i] * u + ei (+ m if col = i)
616        (0..cols).for_each(|i| {
617            let (mut ci_dft, scratch_2) = scratch_1.tmp_vec_znx_dft(module, 1, size_pk);
618            // ci_dft = DFT(u) * DFT(pk[i])
619            module.svp_apply(&mut ci_dft, 0, &u_dft, 0, pk, i);
620
621            // ci_big = u * p[i]
622            let mut ci_big = module.vec_znx_idft_consume(ci_dft);
623
624            // ci_big = u * pk[i] + e
625            ci_big.add_normal(log_base2k, 0, pk.k(), source_xe, sigma, sigma * SIX_SIGMA);
626
627            // ci_big = u * pk[i] + e + m (if col = i)
628            if let Some((pt, col)) = pt {
629                if col == i {
630                    module.vec_znx_big_add_small_inplace(&mut ci_big, 0, pt, 0);
631                }
632            }
633
634            // ct[i] = norm(ci_big)
635            module.vec_znx_big_normalize(log_base2k, self, i, &ci_big, 0, scratch_2);
636        });
637    }
638}
639
640impl<DataSelf> GLWECiphertext<DataSelf>
641where
642    VecZnx<DataSelf>: VecZnxToRef,
643{
644    pub fn decrypt<DataPt, DataSk>(
645        &self,
646        module: &Module<FFT64>,
647        pt: &mut GLWEPlaintext<DataPt>,
648        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
649        scratch: &mut Scratch,
650    ) where
651        VecZnx<DataPt>: VecZnxToMut,
652        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
653    {
654        #[cfg(debug_assertions)]
655        {
656            assert_eq!(self.rank(), sk_dft.rank());
657            assert_eq!(self.n(), module.n());
658            assert_eq!(pt.n(), module.n());
659            assert_eq!(sk_dft.n(), module.n());
660        }
661
662        let cols: usize = self.rank() + 1;
663
664        let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, self.size()); // TODO optimize size when pt << ct
665        c0_big.zero();
666
667        {
668            (1..cols).for_each(|i| {
669                // ci_dft = DFT(a[i]) * DFT(s[i])
670                let (mut ci_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, self.size()); // TODO optimize size when pt << ct
671                module.vec_znx_dft(&mut ci_dft, 0, self, i);
672                module.svp_apply_inplace(&mut ci_dft, 0, sk_dft, i - 1);
673                let ci_big = module.vec_znx_idft_consume(ci_dft);
674
675                // c0_big += a[i] * s[i]
676                module.vec_znx_big_add_inplace(&mut c0_big, 0, &ci_big, 0);
677            });
678        }
679
680        // c0_big = (a * s) + (-a * s + m + e) = BIG(m + e)
681        module.vec_znx_big_add_small_inplace(&mut c0_big, 0, self, 0);
682
683        // pt = norm(BIG(m + e))
684        module.vec_znx_big_normalize(self.basek(), pt, 0, &mut c0_big, 0, scratch_1);
685
686        pt.basek = self.basek();
687        pt.k = pt.k().min(self.k());
688    }
689}