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 fn keyswitch<DataLhs, DataRhs>(
303        &mut self,
304        module: &Module<FFT64>,
305        lhs: &GLWECiphertext<DataLhs>,
306        rhs: &GLWESwitchingKey<DataRhs, FFT64>,
307        scratch: &mut Scratch,
308    ) where
309        VecZnx<DataLhs>: VecZnxToRef,
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                    >= GLWECiphertext::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        let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols_out, rhs.size()); // Todo optimise
340
341        {
342            let (mut ai_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols_in, lhs.size());
343            (0..cols_in).for_each(|col_i| {
344                module.vec_znx_dft(&mut ai_dft, col_i, lhs, col_i + 1);
345            });
346            module.vmp_apply(&mut res_dft, &ai_dft, rhs, scratch2);
347        }
348
349        let mut res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(res_dft);
350
351        module.vec_znx_big_add_small_inplace(&mut res_big, 0, lhs, 0);
352
353        (0..cols_out).for_each(|i| {
354            module.vec_znx_big_normalize(basek, self, i, &res_big, i, scratch1);
355        });
356    }
357
358    pub fn keyswitch_inplace<DataRhs>(
359        &mut self,
360        module: &Module<FFT64>,
361        rhs: &GLWESwitchingKey<DataRhs, FFT64>,
362        scratch: &mut Scratch,
363    ) where
364        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
365    {
366        unsafe {
367            let self_ptr: *mut GLWECiphertext<DataSelf> = self as *mut GLWECiphertext<DataSelf>;
368            self.keyswitch(&module, &*self_ptr, rhs, scratch);
369        }
370    }
371
372    pub fn external_product<DataLhs, DataRhs>(
373        &mut self,
374        module: &Module<FFT64>,
375        lhs: &GLWECiphertext<DataLhs>,
376        rhs: &GGSWCiphertext<DataRhs, FFT64>,
377        scratch: &mut Scratch,
378    ) where
379        VecZnx<DataLhs>: VecZnxToRef,
380        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
381    {
382        let basek: usize = self.basek();
383
384        #[cfg(debug_assertions)]
385        {
386            assert_eq!(rhs.rank(), lhs.rank());
387            assert_eq!(rhs.rank(), self.rank());
388            assert_eq!(self.basek(), basek);
389            assert_eq!(lhs.basek(), basek);
390            assert_eq!(rhs.n(), module.n());
391            assert_eq!(self.n(), module.n());
392            assert_eq!(lhs.n(), module.n());
393        }
394
395        let cols: usize = rhs.rank() + 1;
396
397        let (mut res_dft, scratch1) = scratch.tmp_vec_znx_dft(module, cols, rhs.size()); // Todo optimise
398
399        {
400            let (mut a_dft, scratch2) = scratch1.tmp_vec_znx_dft(module, cols, lhs.size());
401            (0..cols).for_each(|col_i| {
402                module.vec_znx_dft(&mut a_dft, col_i, lhs, col_i);
403            });
404            module.vmp_apply(&mut res_dft, &a_dft, rhs, scratch2);
405        }
406
407        let res_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(res_dft);
408
409        (0..cols).for_each(|i| {
410            module.vec_znx_big_normalize(basek, self, i, &res_big, i, scratch1);
411        });
412    }
413
414    pub fn external_product_inplace<DataRhs>(
415        &mut self,
416        module: &Module<FFT64>,
417        rhs: &GGSWCiphertext<DataRhs, FFT64>,
418        scratch: &mut Scratch,
419    ) where
420        MatZnxDft<DataRhs, FFT64>: MatZnxDftToRef<FFT64>,
421    {
422        unsafe {
423            let self_ptr: *mut GLWECiphertext<DataSelf> = self as *mut GLWECiphertext<DataSelf>;
424            self.external_product(&module, &*self_ptr, rhs, scratch);
425        }
426    }
427
428    pub(crate) fn encrypt_sk_private<DataPt, DataSk>(
429        &mut self,
430        module: &Module<FFT64>,
431        pt: Option<(&GLWEPlaintext<DataPt>, usize)>,
432        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
433        source_xa: &mut Source,
434        source_xe: &mut Source,
435        sigma: f64,
436        scratch: &mut Scratch,
437    ) where
438        VecZnx<DataPt>: VecZnxToRef,
439        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
440    {
441        #[cfg(debug_assertions)]
442        {
443            assert_eq!(self.rank(), sk_dft.rank());
444            assert_eq!(sk_dft.n(), module.n());
445            assert_eq!(self.n(), module.n());
446            if let Some((pt, col)) = pt {
447                assert_eq!(pt.n(), module.n());
448                assert!(col < self.rank() + 1);
449            }
450        }
451
452        let log_base2k: usize = self.basek();
453        let log_k: usize = self.k();
454        let size: usize = self.size();
455        let cols: usize = self.rank() + 1;
456
457        let (mut c0_big, scratch_1) = scratch.tmp_vec_znx(module, 1, size);
458        c0_big.zero();
459
460        {
461            // c[i] = uniform
462            // c[0] -= c[i] * s[i],
463            (1..cols).for_each(|i| {
464                let (mut ci_dft, scratch_2) = scratch_1.tmp_vec_znx_dft(module, 1, size);
465
466                // c[i] = uniform
467                self.data.fill_uniform(log_base2k, i, size, source_xa);
468
469                // c[i] = norm(IDFT(DFT(c[i]) * DFT(s[i])))
470                module.vec_znx_dft(&mut ci_dft, 0, self, i);
471                module.svp_apply_inplace(&mut ci_dft, 0, sk_dft, i - 1);
472                let ci_big: VecZnxBig<&mut [u8], FFT64> = module.vec_znx_idft_consume(ci_dft);
473
474                // use c[0] as buffer, which is overwritten later by the normalization step
475                module.vec_znx_big_normalize(log_base2k, self, 0, &ci_big, 0, scratch_2);
476
477                // c0_tmp = -c[i] * s[i] (use c[0] as buffer)
478                module.vec_znx_sub_ab_inplace(&mut c0_big, 0, self, 0);
479
480                // c[i] += m if col = i
481                if let Some((pt, col)) = pt {
482                    if i == col {
483                        module.vec_znx_add_inplace(self, i, pt, 0);
484                        module.vec_znx_normalize_inplace(log_base2k, self, i, scratch_2);
485                    }
486                }
487            });
488        }
489
490        // c[0] += e
491        c0_big.add_normal(log_base2k, 0, log_k, source_xe, sigma, sigma * SIX_SIGMA);
492
493        // c[0] += m if col = 0
494        if let Some((pt, col)) = pt {
495            if col == 0 {
496                module.vec_znx_add_inplace(&mut c0_big, 0, pt, 0);
497            }
498        }
499
500        // c[0] = norm(c[0])
501        module.vec_znx_normalize(log_base2k, self, 0, &c0_big, 0, scratch_1);
502    }
503
504    pub(crate) fn encrypt_pk_private<DataPt, DataPk>(
505        &mut self,
506        module: &Module<FFT64>,
507        pt: Option<(&GLWEPlaintext<DataPt>, usize)>,
508        pk: &GLWEPublicKey<DataPk, FFT64>,
509        source_xu: &mut Source,
510        source_xe: &mut Source,
511        sigma: f64,
512        scratch: &mut Scratch,
513    ) where
514        VecZnx<DataPt>: VecZnxToRef,
515        VecZnxDft<DataPk, FFT64>: VecZnxDftToRef<FFT64>,
516    {
517        #[cfg(debug_assertions)]
518        {
519            assert_eq!(self.basek(), pk.basek());
520            assert_eq!(self.n(), module.n());
521            assert_eq!(pk.n(), module.n());
522            assert_eq!(self.rank(), pk.rank());
523            if let Some((pt, _)) = pt {
524                assert_eq!(pt.basek(), pk.basek());
525                assert_eq!(pt.n(), module.n());
526            }
527        }
528
529        let log_base2k: usize = pk.basek();
530        let size_pk: usize = pk.size();
531        let cols: usize = self.rank() + 1;
532
533        // Generates u according to the underlying secret distribution.
534        let (mut u_dft, scratch_1) = scratch.tmp_scalar_znx_dft(module, 1);
535
536        {
537            let (mut u, _) = scratch_1.tmp_scalar_znx(module, 1);
538            match pk.dist {
539                SecretDistribution::NONE => panic!(
540                    "invalid public key: SecretDistribution::NONE, ensure it has been correctly intialized through \
541                     Self::generate"
542                ),
543                SecretDistribution::TernaryFixed(hw) => u.fill_ternary_hw(0, hw, source_xu),
544                SecretDistribution::TernaryProb(prob) => u.fill_ternary_prob(0, prob, source_xu),
545                SecretDistribution::ZERO => {}
546            }
547
548            module.svp_prepare(&mut u_dft, 0, &u, 0);
549        }
550
551        // ct[i] = pk[i] * u + ei (+ m if col = i)
552        (0..cols).for_each(|i| {
553            let (mut ci_dft, scratch_2) = scratch_1.tmp_vec_znx_dft(module, 1, size_pk);
554            // ci_dft = DFT(u) * DFT(pk[i])
555            module.svp_apply(&mut ci_dft, 0, &u_dft, 0, pk, i);
556
557            // ci_big = u * p[i]
558            let mut ci_big = module.vec_znx_idft_consume(ci_dft);
559
560            // ci_big = u * pk[i] + e
561            ci_big.add_normal(log_base2k, 0, pk.k(), source_xe, sigma, sigma * SIX_SIGMA);
562
563            // ci_big = u * pk[i] + e + m (if col = i)
564            if let Some((pt, col)) = pt {
565                if col == i {
566                    module.vec_znx_big_add_small_inplace(&mut ci_big, 0, pt, 0);
567                }
568            }
569
570            // ct[i] = norm(ci_big)
571            module.vec_znx_big_normalize(log_base2k, self, i, &ci_big, 0, scratch_2);
572        });
573    }
574}
575
576impl<DataSelf> GLWECiphertext<DataSelf>
577where
578    VecZnx<DataSelf>: VecZnxToRef,
579{
580    pub fn decrypt<DataPt, DataSk>(
581        &self,
582        module: &Module<FFT64>,
583        pt: &mut GLWEPlaintext<DataPt>,
584        sk_dft: &SecretKeyFourier<DataSk, FFT64>,
585        scratch: &mut Scratch,
586    ) where
587        VecZnx<DataPt>: VecZnxToMut,
588        ScalarZnxDft<DataSk, FFT64>: ScalarZnxDftToRef<FFT64>,
589    {
590        #[cfg(debug_assertions)]
591        {
592            assert_eq!(self.rank(), sk_dft.rank());
593            assert_eq!(self.n(), module.n());
594            assert_eq!(pt.n(), module.n());
595            assert_eq!(sk_dft.n(), module.n());
596        }
597
598        let cols: usize = self.rank() + 1;
599
600        let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, self.size()); // TODO optimize size when pt << ct
601        c0_big.zero();
602
603        {
604            (1..cols).for_each(|i| {
605                // ci_dft = DFT(a[i]) * DFT(s[i])
606                let (mut ci_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, self.size()); // TODO optimize size when pt << ct
607                module.vec_znx_dft(&mut ci_dft, 0, self, i);
608                module.svp_apply_inplace(&mut ci_dft, 0, sk_dft, i - 1);
609                let ci_big = module.vec_znx_idft_consume(ci_dft);
610
611                // c0_big += a[i] * s[i]
612                module.vec_znx_big_add_inplace(&mut c0_big, 0, &ci_big, 0);
613            });
614        }
615
616        // c0_big = (a * s) + (-a * s + m + e) = BIG(m + e)
617        module.vec_znx_big_add_small_inplace(&mut c0_big, 0, self, 0);
618
619        // pt = norm(BIG(m + e))
620        module.vec_znx_big_normalize(self.basek(), pt, 0, &mut c0_big, 0, scratch_1);
621
622        pt.basek = self.basek();
623        pt.k = pt.k().min(self.k());
624    }
625}