arnaucube
arnaucube/poulpyPublic Self-hosted repository
poulpy/poulpy-core/src/keyswitching/ggsw_ct.rs
poulpy-core/src/keyswitching/ggsw_ct.rsRaw
  1use poulpy_hal::{
  2    api::{
  3        ScratchAvailable, VecZnxBigAddSmallInplace, VecZnxBigBytesOf, VecZnxBigNormalize, VecZnxBigNormalizeTmpBytes, VecZnxCopy,
  4        VecZnxDftAddInplace, VecZnxDftApply, VecZnxDftBytesOf, VecZnxDftCopy, VecZnxIdftApplyConsume, VecZnxIdftApplyTmpA,
  5        VecZnxNormalize, VecZnxNormalizeTmpBytes, VmpApplyDftToDft, VmpApplyDftToDftAdd, VmpApplyDftToDftTmpBytes,
  6    },
  7    layouts::{Backend, DataMut, DataRef, Module, Scratch, VecZnx, VmpPMat, ZnxInfos},
  8};
  9
 10use crate::{
 11    layouts::{
 12        GGLWE, GGLWEInfos, GGSW, GGSWInfos, GLWE, GLWEInfos, LWEInfos,
 13        prepared::{GLWESwitchingKeyPrepared, TensorKeyPrepared},
 14    },
 15    operations::GLWEOperations,
 16};
 17
 18impl GGSW<Vec<u8>> {
 19    pub(crate) fn expand_row_scratch_space<B: Backend, OUT, TSK>(module: &Module<B>, out_infos: &OUT, tsk_infos: &TSK) -> usize
 20    where
 21        OUT: GGSWInfos,
 22        TSK: GGLWEInfos,
 23        Module<B>: VecZnxDftBytesOf + VmpApplyDftToDftTmpBytes + VecZnxBigBytesOf + VecZnxNormalizeTmpBytes,
 24    {
 25        let tsk_size: usize = tsk_infos.k().div_ceil(tsk_infos.base2k()) as usize;
 26        let size_in: usize = out_infos
 27            .k()
 28            .div_ceil(tsk_infos.base2k())
 29            .div_ceil(tsk_infos.dsize().into()) as usize;
 30
 31        let tmp_dft_i: usize = module.bytes_of_vec_znx_dft((tsk_infos.rank_out() + 1).into(), tsk_size);
 32        let tmp_a: usize = module.bytes_of_vec_znx_dft(1, size_in);
 33        let vmp: usize = module.vmp_apply_dft_to_dft_tmp_bytes(
 34            tsk_size,
 35            size_in,
 36            size_in,
 37            (tsk_infos.rank_in()).into(),  // Verify if rank+1
 38            (tsk_infos.rank_out()).into(), // Verify if rank+1
 39            tsk_size,
 40        );
 41        let tmp_idft: usize = module.bytes_of_vec_znx_big(1, tsk_size);
 42        let norm: usize = module.vec_znx_normalize_tmp_bytes();
 43
 44        tmp_dft_i + ((tmp_a + vmp) | (tmp_idft + norm))
 45    }
 46
 47    #[allow(clippy::too_many_arguments)]
 48    pub fn keyswitch_scratch_space<B: Backend, OUT, IN, KEY, TSK>(
 49        module: &Module<B>,
 50        out_infos: &OUT,
 51        in_infos: &IN,
 52        apply_infos: &KEY,
 53        tsk_infos: &TSK,
 54    ) -> usize
 55    where
 56        OUT: GGSWInfos,
 57        IN: GGSWInfos,
 58        KEY: GGLWEInfos,
 59        TSK: GGLWEInfos,
 60        Module<B>:
 61            VecZnxDftBytesOf + VmpApplyDftToDftTmpBytes + VecZnxBigBytesOf + VecZnxNormalizeTmpBytes + VecZnxBigNormalizeTmpBytes,
 62    {
 63        #[cfg(debug_assertions)]
 64        {
 65            assert_eq!(apply_infos.rank_in(), apply_infos.rank_out());
 66            assert_eq!(tsk_infos.rank_in(), tsk_infos.rank_out());
 67            assert_eq!(apply_infos.rank_in(), tsk_infos.rank_in());
 68        }
 69
 70        let rank: usize = apply_infos.rank_out().into();
 71
 72        let size_out: usize = out_infos.k().div_ceil(out_infos.base2k()) as usize;
 73        let res_znx: usize = VecZnx::bytes_of(module.n(), rank + 1, size_out);
 74        let ci_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, size_out);
 75        let ks: usize = GLWE::keyswitch_scratch_space(module, out_infos, in_infos, apply_infos);
 76        let expand_rows: usize = GGSW::expand_row_scratch_space(module, out_infos, tsk_infos);
 77        let res_dft: usize = module.bytes_of_vec_znx_dft(rank + 1, size_out);
 78
 79        if in_infos.base2k() == tsk_infos.base2k() {
 80            res_znx + ci_dft + (ks | expand_rows | res_dft)
 81        } else {
 82            let a_conv: usize = VecZnx::bytes_of(
 83                module.n(),
 84                1,
 85                out_infos.k().div_ceil(tsk_infos.base2k()) as usize,
 86            ) + module.vec_znx_normalize_tmp_bytes();
 87            res_znx + ci_dft + (a_conv | ks | expand_rows | res_dft)
 88        }
 89    }
 90
 91    #[allow(clippy::too_many_arguments)]
 92    pub fn keyswitch_inplace_scratch_space<B: Backend, OUT, KEY, TSK>(
 93        module: &Module<B>,
 94        out_infos: &OUT,
 95        apply_infos: &KEY,
 96        tsk_infos: &TSK,
 97    ) -> usize
 98    where
 99        OUT: GGSWInfos,
100        KEY: GGLWEInfos,
101        TSK: GGLWEInfos,
102        Module<B>:
103            VecZnxDftBytesOf + VmpApplyDftToDftTmpBytes + VecZnxBigBytesOf + VecZnxNormalizeTmpBytes + VecZnxBigNormalizeTmpBytes,
104    {
105        GGSW::keyswitch_scratch_space(module, out_infos, out_infos, apply_infos, tsk_infos)
106    }
107}
108
109impl<DataSelf: DataMut> GGSW<DataSelf> {
110    pub fn from_gglwe<DataA, DataTsk, B: Backend>(
111        &mut self,
112        module: &Module<B>,
113        a: &GGLWE<DataA>,
114        tsk: &TensorKeyPrepared<DataTsk, B>,
115        scratch: &mut Scratch<B>,
116    ) where
117        DataA: DataRef,
118        DataTsk: DataRef,
119        Module<B>: VecZnxCopy
120            + VecZnxDftBytesOf
121            + VmpApplyDftToDftTmpBytes
122            + VecZnxBigBytesOf
123            + VecZnxNormalizeTmpBytes
124            + VecZnxDftApply<B>
125            + VecZnxDftCopy<B>
126            + VmpApplyDftToDft<B>
127            + VmpApplyDftToDftAdd<B>
128            + VecZnxDftAddInplace<B>
129            + VecZnxBigNormalize<B>
130            + VecZnxIdftApplyTmpA<B>
131            + VecZnxNormalize<B>,
132        Scratch<B>: ScratchAvailable,
133    {
134        #[cfg(debug_assertions)]
135        {
136            use crate::layouts::{GLWEInfos, LWEInfos};
137
138            assert_eq!(self.rank(), a.rank_out());
139            assert_eq!(self.dnum(), a.dnum());
140            assert_eq!(self.n(), module.n() as u32);
141            assert_eq!(a.n(), module.n() as u32);
142            assert_eq!(tsk.n(), module.n() as u32);
143        }
144        (0..self.dnum().into()).for_each(|row_i| {
145            self.at_mut(row_i, 0).copy(module, &a.at(row_i, 0));
146        });
147        self.expand_row(module, tsk, scratch);
148    }
149
150    pub fn keyswitch<DataLhs: DataRef, DataKsk: DataRef, DataTsk: DataRef, B: Backend>(
151        &mut self,
152        module: &Module<B>,
153        lhs: &GGSW<DataLhs>,
154        ksk: &GLWESwitchingKeyPrepared<DataKsk, B>,
155        tsk: &TensorKeyPrepared<DataTsk, B>,
156        scratch: &mut Scratch<B>,
157    ) where
158        Module<B>: VecZnxDftBytesOf
159            + VmpApplyDftToDftTmpBytes
160            + VecZnxBigNormalizeTmpBytes
161            + VmpApplyDftToDft<B>
162            + VmpApplyDftToDftAdd<B>
163            + VecZnxDftApply<B>
164            + VecZnxIdftApplyConsume<B>
165            + VecZnxBigAddSmallInplace<B>
166            + VecZnxBigNormalize<B>
167            + VecZnxDftBytesOf
168            + VecZnxBigBytesOf
169            + VecZnxNormalizeTmpBytes
170            + VecZnxDftCopy<B>
171            + VecZnxDftAddInplace<B>
172            + VecZnxIdftApplyTmpA<B>
173            + VecZnxNormalize<B>,
174        Scratch<B>: ScratchAvailable,
175    {
176        (0..lhs.dnum().into()).for_each(|row_i| {
177            // Key-switch column 0, i.e.
178            // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2) -> (-(a0s0' + a1s1' + a2s2') + M[i], a0, a1, a2)
179            self.at_mut(row_i, 0)
180                .keyswitch(module, &lhs.at(row_i, 0), ksk, scratch);
181        });
182        self.expand_row(module, tsk, scratch);
183    }
184
185    pub fn keyswitch_inplace<DataKsk: DataRef, DataTsk: DataRef, B: Backend>(
186        &mut self,
187        module: &Module<B>,
188        ksk: &GLWESwitchingKeyPrepared<DataKsk, B>,
189        tsk: &TensorKeyPrepared<DataTsk, B>,
190        scratch: &mut Scratch<B>,
191    ) where
192        Module<B>: VecZnxDftBytesOf
193            + VmpApplyDftToDftTmpBytes
194            + VecZnxBigNormalizeTmpBytes
195            + VmpApplyDftToDft<B>
196            + VmpApplyDftToDftAdd<B>
197            + VecZnxDftApply<B>
198            + VecZnxIdftApplyConsume<B>
199            + VecZnxBigAddSmallInplace<B>
200            + VecZnxBigNormalize<B>
201            + VecZnxDftBytesOf
202            + VecZnxBigBytesOf
203            + VecZnxNormalizeTmpBytes
204            + VecZnxDftCopy<B>
205            + VecZnxDftAddInplace<B>
206            + VecZnxIdftApplyTmpA<B>
207            + VecZnxNormalize<B>,
208        Scratch<B>: ScratchAvailable,
209    {
210        (0..self.dnum().into()).for_each(|row_i| {
211            // Key-switch column 0, i.e.
212            // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0, a1, a2) -> (-(a0s0' + a1s1' + a2s2') + M[i], a0, a1, a2)
213            self.at_mut(row_i, 0)
214                .keyswitch_inplace(module, ksk, scratch);
215        });
216        self.expand_row(module, tsk, scratch);
217    }
218
219    pub fn expand_row<DataTsk: DataRef, B: Backend>(
220        &mut self,
221        module: &Module<B>,
222        tsk: &TensorKeyPrepared<DataTsk, B>,
223        scratch: &mut Scratch<B>,
224    ) where
225        Module<B>: VecZnxDftBytesOf
226            + VmpApplyDftToDftTmpBytes
227            + VecZnxBigBytesOf
228            + VecZnxNormalizeTmpBytes
229            + VecZnxDftApply<B>
230            + VecZnxDftCopy<B>
231            + VmpApplyDftToDft<B>
232            + VmpApplyDftToDftAdd<B>
233            + VecZnxDftAddInplace<B>
234            + VecZnxBigNormalize<B>
235            + VecZnxIdftApplyTmpA<B>
236            + VecZnxNormalize<B>,
237        Scratch<B>: ScratchAvailable,
238    {
239        let basek_in: usize = self.base2k().into();
240        let basek_tsk: usize = tsk.base2k().into();
241
242        assert!(scratch.available() >= GGSW::expand_row_scratch_space(module, self, tsk));
243
244        let n: usize = self.n().into();
245        let rank: usize = self.rank().into();
246        let cols: usize = rank + 1;
247
248        let a_size: usize = (self.size() * basek_in).div_ceil(basek_tsk);
249
250        // Keyswitch the j-th row of the col 0
251        for row_i in 0..self.dnum().into() {
252            let a = &self.at(row_i, 0).data;
253
254            // Pre-compute DFT of (a0, a1, a2)
255            let (mut ci_dft, scratch_1) = scratch.take_vec_znx_dft(n, cols, a_size);
256
257            if basek_in == basek_tsk {
258                for i in 0..cols {
259                    module.vec_znx_dft_apply(1, 0, &mut ci_dft, i, a, i);
260                }
261            } else {
262                let (mut a_conv, scratch_2) = scratch_1.take_vec_znx(n, 1, a_size);
263                for i in 0..cols {
264                    module.vec_znx_normalize(basek_tsk, &mut a_conv, 0, basek_in, a, i, scratch_2);
265                    module.vec_znx_dft_apply(1, 0, &mut ci_dft, i, &a_conv, 0);
266                }
267            }
268
269            for col_j in 1..cols {
270                // Example for rank 3:
271                //
272                // Note: M is a vector (m, Bm, B^2m, B^3m, ...), so each column is
273                // actually composed of that many dnum and we focus on a specific row here
274                // implicitely given ci_dft.
275                //
276                // # Input
277                //
278                // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0    , a1    , a2    )
279                // col 1: (0, 0, 0, 0)
280                // col 2: (0, 0, 0, 0)
281                // col 3: (0, 0, 0, 0)
282                //
283                // # Output
284                //
285                // col 0: (-(a0s0 + a1s1 + a2s2) + M[i], a0       , a1       , a2       )
286                // col 1: (-(b0s0 + b1s1 + b2s2)       , b0 + M[i], b1       , b2       )
287                // col 2: (-(c0s0 + c1s1 + c2s2)       , c0       , c1 + M[i], c2       )
288                // col 3: (-(d0s0 + d1s1 + d2s2)       , d0       , d1       , d2 + M[i])
289
290                let dsize: usize = tsk.dsize().into();
291
292                let (mut tmp_dft_i, scratch_2) = scratch_1.take_vec_znx_dft(n, cols, tsk.size());
293                let (mut tmp_a, scratch_3) = scratch_2.take_vec_znx_dft(n, 1, ci_dft.size().div_ceil(dsize));
294
295                {
296                    // Performs a key-switch for each combination of s[i]*s[j], i.e. for a0, a1, a2
297                    //
298                    // # Example for col=1
299                    //
300                    // a0 * (-(f0s0 + f1s1 + f1s2) + s0^2, f0, f1, f2) = (-(a0f0s0 + a0f1s1 + a0f1s2) + a0s0^2, a0f0, a0f1, a0f2)
301                    // +
302                    // a1 * (-(g0s0 + g1s1 + g1s2) + s0s1, g0, g1, g2) = (-(a1g0s0 + a1g1s1 + a1g1s2) + a1s0s1, a1g0, a1g1, a1g2)
303                    // +
304                    // a2 * (-(h0s0 + h1s1 + h1s2) + s0s2, h0, h1, h2) = (-(a2h0s0 + a2h1s1 + a2h1s2) + a2s0s2, a2h0, a2h1, a2h2)
305                    // =
306                    // (-(x0s0 + x1s1 + x2s2) + s0(a0s0 + a1s1 + a2s2), x0, x1, x2)
307                    for col_i in 1..cols {
308                        let pmat: &VmpPMat<DataTsk, B> = &tsk.at(col_i - 1, col_j - 1).key.data; // Selects Enc(s[i]s[j])
309
310                        // Extracts a[i] and multipies with Enc(s[i]s[j])
311                        for di in 0..dsize {
312                            tmp_a.set_size((ci_dft.size() + di) / dsize);
313
314                            // Small optimization for dsize > 2
315                            // VMP produce some error e, and since we aggregate vmp * 2^{di * B}, then
316                            // we also aggregate ei * 2^{di * B}, with the largest error being ei * 2^{(dsize-1) * B}.
317                            // As such we can ignore the last dsize-2 limbs safely of the sum of vmp products.
318                            // It is possible to further ignore the last dsize-1 limbs, but this introduce
319                            // ~0.5 to 1 bit of additional noise, and thus not chosen here to ensure that the same
320                            // noise is kept with respect to the ideal functionality.
321                            tmp_dft_i.set_size(tsk.size() - ((dsize - di) as isize - 2).max(0) as usize);
322
323                            module.vec_znx_dft_copy(dsize, dsize - 1 - di, &mut tmp_a, 0, &ci_dft, col_i);
324                            if di == 0 && col_i == 1 {
325                                module.vmp_apply_dft_to_dft(&mut tmp_dft_i, &tmp_a, pmat, scratch_3);
326                            } else {
327                                module.vmp_apply_dft_to_dft_add(&mut tmp_dft_i, &tmp_a, pmat, di, scratch_3);
328                            }
329                        }
330                    }
331                }
332
333                // Adds -(sum a[i] * s[i]) + m)  on the i-th column of tmp_idft_i
334                //
335                // (-(x0s0 + x1s1 + x2s2) + a0s0s0 + a1s0s1 + a2s0s2, x0, x1, x2)
336                // +
337                // (0, -(a0s0 + a1s1 + a2s2) + M[i], 0, 0)
338                // =
339                // (-(x0s0 + x1s1 + x2s2) + s0(a0s0 + a1s1 + a2s2), x0 -(a0s0 + a1s1 + a2s2) + M[i], x1, x2)
340                // =
341                // (-(x0s0 + x1s1 + x2s2), x0 + M[i], x1, x2)
342                module.vec_znx_dft_add_inplace(&mut tmp_dft_i, col_j, &ci_dft, 0);
343                let (mut tmp_idft, scratch_3) = scratch_2.take_vec_znx_big(n, 1, tsk.size());
344                for i in 0..cols {
345                    module.vec_znx_idft_apply_tmpa(&mut tmp_idft, 0, &mut tmp_dft_i, i);
346                    module.vec_znx_big_normalize(
347                        basek_in,
348                        &mut self.at_mut(row_i, col_j).data,
349                        i,
350                        basek_tsk,
351                        &tmp_idft,
352                        0,
353                        scratch_3,
354                    );
355                }
356            }
357        }
358    }
359}