arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/rlwe/src/elem_rlwe.rs
rlwe/src/elem_rlwe.rsRaw
  1use base2k::{
  2    AddNormal, Backend, FFT64, FillUniform, MatZnxDft, MatZnxDftToRef, Module, ScalarZnxAlloc, ScalarZnxDft, ScalarZnxDftAlloc,
  3    ScalarZnxDftOps, ScalarZnxDftToRef, Scratch, VecZnx, VecZnxAlloc, VecZnxBigAlloc, VecZnxBigOps, VecZnxBigScratch, VecZnxDft,
  4    VecZnxDftAlloc, VecZnxDftOps, VecZnxDftToMut, VecZnxDftToRef, VecZnxOps, VecZnxToMut, VecZnxToRef, ZnxInfos,
  5};
  6use sampling::source::Source;
  7
  8use crate::{
  9    elem::Infos,
 10    elem_grlwe::GRLWECt,
 11    keys::{PublicKey, SecretDistribution, SecretKeyDft},
 12    utils::derive_size,
 13};
 14
 15pub struct RLWECt<C> {
 16    pub data: VecZnx<C>,
 17    pub log_base2k: usize,
 18    pub log_k: usize,
 19}
 20
 21impl RLWECt<Vec<u8>> {
 22    pub fn new<B: Backend>(module: &Module<B>, log_base2k: usize, log_k: usize) -> Self {
 23        Self {
 24            data: module.new_vec_znx(2, derive_size(log_base2k, log_k)),
 25            log_base2k: log_base2k,
 26            log_k: log_k,
 27        }
 28    }
 29}
 30
 31impl<T> Infos for RLWECt<T> {
 32    type Inner = VecZnx<T>;
 33
 34    fn inner(&self) -> &Self::Inner {
 35        &self.data
 36    }
 37
 38    fn log_base2k(&self) -> usize {
 39        self.log_base2k
 40    }
 41
 42    fn log_k(&self) -> usize {
 43        self.log_k
 44    }
 45}
 46
 47impl<C> VecZnxToMut for RLWECt<C>
 48where
 49    VecZnx<C>: VecZnxToMut,
 50{
 51    fn to_mut(&mut self) -> VecZnx<&mut [u8]> {
 52        self.data.to_mut()
 53    }
 54}
 55
 56impl<C> VecZnxToRef for RLWECt<C>
 57where
 58    VecZnx<C>: VecZnxToRef,
 59{
 60    fn to_ref(&self) -> VecZnx<&[u8]> {
 61        self.data.to_ref()
 62    }
 63}
 64
 65impl<C> RLWECt<C>
 66where
 67    VecZnx<C>: VecZnxToRef,
 68{
 69    #[allow(dead_code)]
 70    pub(crate) fn dft<R>(&self, module: &Module<FFT64>, res: &mut RLWECtDft<R, FFT64>)
 71    where
 72        VecZnxDft<R, FFT64>: VecZnxDftToMut<FFT64> + ZnxInfos,
 73    {
 74        #[cfg(debug_assertions)]
 75        {
 76            assert_eq!(self.cols(), 2);
 77            assert_eq!(res.cols(), 2);
 78            assert_eq!(self.log_base2k(), res.log_base2k())
 79        }
 80
 81        module.vec_znx_dft(res, 0, self, 0);
 82        module.vec_znx_dft(res, 1, self, 1);
 83    }
 84}
 85
 86pub struct RLWEPt<C> {
 87    pub data: VecZnx<C>,
 88    pub log_base2k: usize,
 89    pub log_k: usize,
 90}
 91
 92impl<T> Infos for RLWEPt<T> {
 93    type Inner = VecZnx<T>;
 94
 95    fn inner(&self) -> &Self::Inner {
 96        &self.data
 97    }
 98
 99    fn log_base2k(&self) -> usize {
100        self.log_base2k
101    }
102
103    fn log_k(&self) -> usize {
104        self.log_k
105    }
106}
107
108impl<C> VecZnxToMut for RLWEPt<C>
109where
110    VecZnx<C>: VecZnxToMut,
111{
112    fn to_mut(&mut self) -> VecZnx<&mut [u8]> {
113        self.data.to_mut()
114    }
115}
116
117impl<C> VecZnxToRef for RLWEPt<C>
118where
119    VecZnx<C>: VecZnxToRef,
120{
121    fn to_ref(&self) -> VecZnx<&[u8]> {
122        self.data.to_ref()
123    }
124}
125
126impl RLWEPt<Vec<u8>> {
127    pub fn new<B: Backend>(module: &Module<B>, log_base2k: usize, log_k: usize) -> Self {
128        Self {
129            data: module.new_vec_znx(1, derive_size(log_base2k, log_k)),
130            log_base2k: log_base2k,
131            log_k: log_k,
132        }
133    }
134}
135
136pub struct RLWECtDft<C, B: Backend> {
137    pub data: VecZnxDft<C, B>,
138    pub log_base2k: usize,
139    pub log_k: usize,
140}
141
142impl<B: Backend> RLWECtDft<Vec<u8>, B> {
143    pub fn new(module: &Module<B>, log_base2k: usize, log_k: usize) -> Self {
144        Self {
145            data: module.new_vec_znx_dft(2, derive_size(log_base2k, log_k)),
146            log_base2k: log_base2k,
147            log_k: log_k,
148        }
149    }
150}
151
152impl<T, B: Backend> Infos for RLWECtDft<T, B> {
153    type Inner = VecZnxDft<T, B>;
154
155    fn inner(&self) -> &Self::Inner {
156        &self.data
157    }
158
159    fn log_base2k(&self) -> usize {
160        self.log_base2k
161    }
162
163    fn log_k(&self) -> usize {
164        self.log_k
165    }
166}
167
168impl<C, B: Backend> VecZnxDftToMut<B> for RLWECtDft<C, B>
169where
170    VecZnxDft<C, B>: VecZnxDftToMut<B>,
171{
172    fn to_mut(&mut self) -> VecZnxDft<&mut [u8], B> {
173        self.data.to_mut()
174    }
175}
176
177impl<C, B: Backend> VecZnxDftToRef<B> for RLWECtDft<C, B>
178where
179    VecZnxDft<C, B>: VecZnxDftToRef<B>,
180{
181    fn to_ref(&self) -> VecZnxDft<&[u8], B> {
182        self.data.to_ref()
183    }
184}
185
186impl<C> RLWECtDft<C, FFT64>
187where
188    VecZnxDft<C, FFT64>: VecZnxDftToRef<FFT64>,
189{
190    #[allow(dead_code)]
191    pub(crate) fn idft_scratch_space(module: &Module<FFT64>, size: usize) -> usize {
192        module.bytes_of_vec_znx(2, size) + (module.vec_znx_big_normalize_tmp_bytes() | module.vec_znx_idft_tmp_bytes())
193    }
194
195    pub(crate) fn idft<R>(&self, module: &Module<FFT64>, res: &mut RLWECt<R>, scratch: &mut Scratch)
196    where
197        VecZnx<R>: VecZnxToMut + ZnxInfos,
198    {
199        #[cfg(debug_assertions)]
200        {
201            assert_eq!(self.cols(), 2);
202            assert_eq!(res.cols(), 2);
203            assert_eq!(self.log_base2k(), res.log_base2k())
204        }
205
206        let min_size: usize = self.size().min(res.size());
207
208        let (mut res_big, scratch1) = scratch.tmp_vec_znx_big(module, 2, min_size);
209
210        module.vec_znx_idft(&mut res_big, 0, &self.data, 0, scratch1);
211        module.vec_znx_idft(&mut res_big, 1, &self.data, 1, scratch1);
212        module.vec_znx_big_normalize(self.log_base2k(), res, 0, &res_big, 0, scratch1);
213        module.vec_znx_big_normalize(self.log_base2k(), res, 1, &res_big, 1, scratch1);
214    }
215}
216
217impl RLWECt<Vec<u8>> {
218    pub fn encrypt_sk_scratch_space<B: Backend>(module: &Module<B>, size: usize) -> usize {
219        (module.vec_znx_big_normalize_tmp_bytes() | module.bytes_of_vec_znx_dft(1, size)) + module.bytes_of_vec_znx_big(1, size)
220    }
221
222    pub fn encrypt_pk_scratch_space<B: Backend>(module: &Module<B>, pk_size: usize) -> usize {
223        ((module.bytes_of_vec_znx_dft(1, pk_size) + module.bytes_of_vec_znx_big(1, pk_size)) | module.bytes_of_scalar_znx(1))
224            + module.bytes_of_scalar_znx_dft(1)
225            + module.vec_znx_big_normalize_tmp_bytes()
226    }
227
228    pub fn decrypt_scratch_space<B: Backend>(module: &Module<B>, size: usize) -> usize {
229        (module.vec_znx_big_normalize_tmp_bytes() | module.bytes_of_vec_znx_dft(1, size)) + module.bytes_of_vec_znx_big(1, size)
230    }
231}
232
233pub fn encrypt_rlwe_sk<C, P, S>(
234    module: &Module<FFT64>,
235    ct: &mut RLWECt<C>,
236    pt: Option<(&RLWEPt<P>, usize)>,
237    sk_dft: &SecretKeyDft<S, FFT64>,
238    source_xa: &mut Source,
239    source_xe: &mut Source,
240    sigma: f64,
241    bound: f64,
242    scratch: &mut Scratch,
243) where
244    VecZnx<C>: VecZnxToMut + VecZnxToRef,
245    VecZnx<P>: VecZnxToRef,
246    ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
247{
248    let log_base2k: usize = ct.log_base2k();
249    let log_k: usize = ct.log_k();
250    let size: usize = ct.size();
251
252    // c1 = a
253    ct.data.fill_uniform(log_base2k, 1, size, source_xa);
254
255    let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, size);
256
257    {
258        let (mut c0_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, size);
259        module.vec_znx_dft(&mut c0_dft, 0, ct, 1);
260
261        // c0_dft = DFT(a) * DFT(s)
262        module.svp_apply_inplace(&mut c0_dft, 0, sk_dft, 0);
263
264        // c0_big = IDFT(c0_dft)
265        module.vec_znx_idft_tmp_a(&mut c0_big, 0, &mut c0_dft, 0);
266    }
267
268    // c0_big = m - c0_big
269    if let Some((pt, col)) = pt {
270        match col {
271            0 => module.vec_znx_big_sub_small_b_inplace(&mut c0_big, 0, pt, 0),
272            1 => {
273                module.vec_znx_big_negate_inplace(&mut c0_big, 0);
274                module.vec_znx_add_inplace(ct, 1, pt, 0);
275                module.vec_znx_normalize_inplace(log_base2k, ct, 1, scratch_1);
276            }
277            _ => panic!("invalid target column: {}", col),
278        }
279    } else {
280        module.vec_znx_big_negate_inplace(&mut c0_big, 0);
281    }
282    // c0_big += e
283    c0_big.add_normal(log_base2k, 0, log_k, source_xe, sigma, bound);
284
285    // c0 = norm(c0_big = -as + m + e)
286    module.vec_znx_big_normalize(log_base2k, ct, 0, &c0_big, 0, scratch_1);
287}
288
289pub fn decrypt_rlwe<P, C, S>(
290    module: &Module<FFT64>,
291    pt: &mut RLWEPt<P>,
292    ct: &RLWECt<C>,
293    sk_dft: &SecretKeyDft<S, FFT64>,
294    scratch: &mut Scratch,
295) where
296    VecZnx<P>: VecZnxToMut + VecZnxToRef,
297    VecZnx<C>: VecZnxToRef,
298    ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
299{
300    let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, ct.size()); // TODO optimize size when pt << ct
301
302    {
303        let (mut c0_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, ct.size()); // TODO optimize size when pt << ct
304        module.vec_znx_dft(&mut c0_dft, 0, ct, 1);
305
306        // c0_dft = DFT(a) * DFT(s)
307        module.svp_apply_inplace(&mut c0_dft, 0, sk_dft, 0);
308
309        // c0_big = IDFT(c0_dft)
310        module.vec_znx_idft_tmp_a(&mut c0_big, 0, &mut c0_dft, 0);
311    }
312
313    // c0_big = (a * s) + (-a * s + m + e) = BIG(m + e)
314    module.vec_znx_big_add_small_inplace(&mut c0_big, 0, ct, 0);
315
316    // pt = norm(BIG(m + e))
317    module.vec_znx_big_normalize(ct.log_base2k(), pt, 0, &mut c0_big, 0, scratch_1);
318
319    pt.log_base2k = ct.log_base2k();
320    pt.log_k = pt.log_k().min(ct.log_k());
321}
322
323impl<C> RLWECt<C> {
324    pub fn encrypt_sk<P, S>(
325        &mut self,
326        module: &Module<FFT64>,
327        pt: Option<&RLWEPt<P>>,
328        sk_dft: &SecretKeyDft<S, FFT64>,
329        source_xa: &mut Source,
330        source_xe: &mut Source,
331        sigma: f64,
332        bound: f64,
333        scratch: &mut Scratch,
334    ) where
335        VecZnx<C>: VecZnxToMut + VecZnxToRef,
336        VecZnx<P>: VecZnxToRef,
337        ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
338    {
339        if let Some(pt) = pt {
340            encrypt_rlwe_sk(
341                module,
342                self,
343                Some((pt, 0)),
344                sk_dft,
345                source_xa,
346                source_xe,
347                sigma,
348                bound,
349                scratch,
350            )
351        } else {
352            encrypt_rlwe_sk::<C, P, S>(
353                module, self, None, sk_dft, source_xa, source_xe, sigma, bound, scratch,
354            )
355        }
356    }
357
358    pub fn decrypt<P, S>(
359        &self,
360        module: &Module<FFT64>,
361        pt: &mut RLWEPt<P>,
362        sk_dft: &SecretKeyDft<S, FFT64>,
363        scratch: &mut Scratch,
364    ) where
365        VecZnx<P>: VecZnxToMut + VecZnxToRef,
366        VecZnx<C>: VecZnxToRef,
367        ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
368    {
369        decrypt_rlwe(module, pt, self, sk_dft, scratch);
370    }
371
372    pub fn encrypt_pk<P, S>(
373        &mut self,
374        module: &Module<FFT64>,
375        pt: Option<&RLWEPt<P>>,
376        pk: &PublicKey<S, FFT64>,
377        source_xu: &mut Source,
378        source_xe: &mut Source,
379        sigma: f64,
380        bound: f64,
381        scratch: &mut Scratch,
382    ) where
383        VecZnx<C>: VecZnxToMut + VecZnxToRef,
384        VecZnx<P>: VecZnxToRef,
385        VecZnxDft<S, FFT64>: VecZnxDftToRef<FFT64>,
386    {
387        encrypt_rlwe_pk(
388            module, self, pt, pk, source_xu, source_xe, sigma, bound, scratch,
389        )
390    }
391}
392
393pub(crate) fn encrypt_zero_rlwe_dft_sk<C, S>(
394    module: &Module<FFT64>,
395    ct: &mut RLWECtDft<C, FFT64>,
396    sk: &SecretKeyDft<S, FFT64>,
397    source_xa: &mut Source,
398    source_xe: &mut Source,
399    sigma: f64,
400    bound: f64,
401    scratch: &mut Scratch,
402) where
403    VecZnxDft<C, FFT64>: VecZnxDftToMut<FFT64> + VecZnxDftToRef<FFT64>,
404    ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
405{
406    let log_base2k: usize = ct.log_base2k();
407    let log_k: usize = ct.log_k();
408    let size: usize = ct.size();
409
410    #[cfg(debug_assertions)]
411    {
412        match sk.dist {
413            SecretDistribution::NONE => panic!("invalid sk.dist = SecretDistribution::NONE"),
414            _ => {}
415        }
416        assert_eq!(ct.cols(), 2);
417    }
418
419    // ct[1] = DFT(a)
420    {
421        let (mut tmp_znx, _) = scratch.tmp_vec_znx(module, 1, size);
422        tmp_znx.fill_uniform(log_base2k, 0, size, source_xa);
423        module.vec_znx_dft(ct, 1, &tmp_znx, 0);
424    }
425
426    let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, size);
427
428    {
429        let (mut tmp_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, size);
430        // c0_dft = ct[1] * DFT(s)
431        module.svp_apply(&mut tmp_dft, 0, sk, 0, ct, 1);
432
433        // c0_big = IDFT(c0_dft)
434        module.vec_znx_idft_tmp_a(&mut c0_big, 0, &mut tmp_dft, 0);
435    }
436
437    // c0_big += e
438    c0_big.add_normal(log_base2k, 0, log_k, source_xe, sigma, bound);
439
440    // c0 = norm(c0_big = -as - e), NOTE: e is centered at 0.
441    let (mut tmp_znx, scratch_2) = scratch_1.tmp_vec_znx(module, 1, size);
442    module.vec_znx_big_normalize(log_base2k, &mut tmp_znx, 0, &c0_big, 0, scratch_2);
443    module.vec_znx_negate_inplace(&mut tmp_znx, 0);
444    // ct[0] = DFT(-as + e)
445    module.vec_znx_dft(ct, 0, &tmp_znx, 0);
446}
447
448impl RLWECtDft<Vec<u8>, FFT64> {
449    pub fn encrypt_zero_sk_scratch_space(module: &Module<FFT64>, size: usize) -> usize {
450        (module.bytes_of_vec_znx(1, size) | module.bytes_of_vec_znx_dft(1, size))
451            + module.bytes_of_vec_znx_big(1, size)
452            + module.bytes_of_vec_znx(1, size)
453            + module.vec_znx_big_normalize_tmp_bytes()
454    }
455
456    pub fn decrypt_scratch_space(module: &Module<FFT64>, size: usize) -> usize {
457        (module.vec_znx_big_normalize_tmp_bytes()
458            | module.bytes_of_vec_znx_dft(1, size)
459            | (module.bytes_of_vec_znx_big(1, size) + module.vec_znx_idft_tmp_bytes()))
460            + module.bytes_of_vec_znx_big(1, size)
461    }
462}
463
464pub fn decrypt_rlwe_dft<P, C, S>(
465    module: &Module<FFT64>,
466    pt: &mut RLWEPt<P>,
467    ct: &RLWECtDft<C, FFT64>,
468    sk: &SecretKeyDft<S, FFT64>,
469    scratch: &mut Scratch,
470) where
471    VecZnx<P>: VecZnxToMut + VecZnxToRef,
472    VecZnxDft<C, FFT64>: VecZnxDftToRef<FFT64>,
473    ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
474{
475    let (mut c0_big, scratch_1) = scratch.tmp_vec_znx_big(module, 1, ct.size()); // TODO optimize size when pt << ct
476
477    {
478        let (mut c0_dft, _) = scratch_1.tmp_vec_znx_dft(module, 1, ct.size()); // TODO optimize size when pt << ct
479        // c0_dft = DFT(a) * DFT(s)
480        module.svp_apply(&mut c0_dft, 0, sk, 0, ct, 1);
481        // c0_big = IDFT(c0_dft)
482        module.vec_znx_idft_tmp_a(&mut c0_big, 0, &mut c0_dft, 0);
483    }
484
485    {
486        let (mut c1_big, scratch_2) = scratch_1.tmp_vec_znx_big(module, 1, ct.size());
487        // c0_big = (a * s) + (-a * s + m + e) = BIG(m + e)
488        module.vec_znx_idft(&mut c1_big, 0, ct, 0, scratch_2);
489        module.vec_znx_big_add_inplace(&mut c0_big, 0, &c1_big, 0);
490    }
491
492    // pt = norm(BIG(m + e))
493    module.vec_znx_big_normalize(ct.log_base2k(), pt, 0, &mut c0_big, 0, scratch_1);
494
495    pt.log_base2k = ct.log_base2k();
496    pt.log_k = pt.log_k().min(ct.log_k());
497}
498
499impl<C> RLWECtDft<C, FFT64> {
500    pub(crate) fn encrypt_zero_sk<S>(
501        &mut self,
502        module: &Module<FFT64>,
503        sk_dft: &SecretKeyDft<S, FFT64>,
504        source_xa: &mut Source,
505        source_xe: &mut Source,
506        sigma: f64,
507        bound: f64,
508        scratch: &mut Scratch,
509    ) where
510        VecZnxDft<C, FFT64>: VecZnxDftToMut<FFT64> + VecZnxDftToRef<FFT64>,
511        ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
512    {
513        encrypt_zero_rlwe_dft_sk(
514            module, self, sk_dft, source_xa, source_xe, sigma, bound, scratch,
515        )
516    }
517
518    pub fn decrypt<P, S>(
519        &self,
520        module: &Module<FFT64>,
521        pt: &mut RLWEPt<P>,
522        sk_dft: &SecretKeyDft<S, FFT64>,
523        scratch: &mut Scratch,
524    ) where
525        VecZnx<P>: VecZnxToMut + VecZnxToRef,
526        VecZnxDft<C, FFT64>: VecZnxDftToRef<FFT64>,
527        ScalarZnxDft<S, FFT64>: ScalarZnxDftToRef<FFT64>,
528    {
529        decrypt_rlwe_dft(module, pt, self, sk_dft, scratch);
530    }
531
532    pub fn mul_grlwe_assign<A>(&mut self, module: &Module<FFT64>, a: &GRLWECt<A, FFT64>, scratch: &mut Scratch)
533    where
534        VecZnxDft<C, FFT64>: VecZnxDftToMut<FFT64> + VecZnxDftToRef<FFT64>,
535        MatZnxDft<A, FFT64>: MatZnxDftToRef<FFT64>,
536    {
537        a.mul_rlwe_dft_inplace(module, self, scratch);
538    }
539}
540
541pub(crate) fn encrypt_rlwe_pk<C, P, S>(
542    module: &Module<FFT64>,
543    ct: &mut RLWECt<C>,
544    pt: Option<&RLWEPt<P>>,
545    pk: &PublicKey<S, FFT64>,
546    source_xu: &mut Source,
547    source_xe: &mut Source,
548    sigma: f64,
549    bound: f64,
550    scratch: &mut Scratch,
551) where
552    VecZnx<C>: VecZnxToMut + VecZnxToRef,
553    VecZnx<P>: VecZnxToRef,
554    VecZnxDft<S, FFT64>: VecZnxDftToRef<FFT64>,
555{
556    #[cfg(debug_assertions)]
557    {
558        assert_eq!(ct.log_base2k(), pk.log_base2k());
559        assert_eq!(ct.n(), module.n());
560        assert_eq!(pk.n(), module.n());
561        if let Some(pt) = pt {
562            assert_eq!(pt.log_base2k(), pk.log_base2k());
563            assert_eq!(pt.n(), module.n());
564        }
565    }
566
567    let log_base2k: usize = pk.log_base2k();
568    let size_pk: usize = pk.size();
569
570    // Generates u according to the underlying secret distribution.
571    let (mut u_dft, scratch_1) = scratch.tmp_scalar_znx_dft(module, 1);
572
573    {
574        let (mut u, _) = scratch_1.tmp_scalar_znx(module, 1);
575        match pk.dist {
576            SecretDistribution::NONE => panic!(
577                "invalid public key: SecretDistribution::NONE, ensure it has been correctly intialized through Self::generate"
578            ),
579            SecretDistribution::TernaryFixed(hw) => u.fill_ternary_hw(0, hw, source_xu),
580            SecretDistribution::TernaryProb(prob) => u.fill_ternary_prob(0, prob, source_xu),
581            SecretDistribution::ZERO => {}
582        }
583
584        module.svp_prepare(&mut u_dft, 0, &u, 0);
585    }
586
587    let (mut tmp_big, scratch_2) = scratch_1.tmp_vec_znx_big(module, 1, size_pk); // TODO optimize size (e.g. when encrypting at low homomorphic capacity)
588    let (mut tmp_dft, scratch_3) = scratch_2.tmp_vec_znx_dft(module, 1, size_pk); // TODO optimize size (e.g. when encrypting at low homomorphic capacity)
589
590    // ct[0] = pk[0] * u + m + e0
591    module.svp_apply(&mut tmp_dft, 0, &u_dft, 0, pk, 0);
592    module.vec_znx_idft_tmp_a(&mut tmp_big, 0, &mut tmp_dft, 0);
593    tmp_big.add_normal(log_base2k, 0, pk.log_k(), source_xe, sigma, bound);
594
595    if let Some(pt) = pt {
596        module.vec_znx_big_add_small_inplace(&mut tmp_big, 0, pt, 0);
597    }
598
599    module.vec_znx_big_normalize(log_base2k, ct, 0, &tmp_big, 0, scratch_3);
600
601    // ct[1] = pk[1] * u + e1
602    module.svp_apply(&mut tmp_dft, 0, &u_dft, 0, pk, 1);
603    module.vec_znx_idft_tmp_a(&mut tmp_big, 0, &mut tmp_dft, 0);
604    tmp_big.add_normal(log_base2k, 0, pk.log_k(), source_xe, sigma, bound);
605    module.vec_znx_big_normalize(log_base2k, ct, 1, &tmp_big, 0, scratch_3);
606}