Skip to main content

poulpy_core/layouts/compressed/
ggsw.rs

1use poulpy_hal::{
2    layouts::{
3        Backend, Data, FillUniform, HostDataMut, HostDataRef, MatZnx, MatZnxToBackendMut, MatZnxToBackendRef, Module, ReaderFrom,
4        WriterTo, mat_znx_at_backend_mut_from_mut, mat_znx_at_backend_ref_from_ref, mat_znx_backend_mut_from_mut,
5        mat_znx_backend_ref_from_mut,
6    },
7    source::Source,
8};
9
10use crate::layouts::{
11    Base2K, Degree, Dnum, Dsize, GGSWInfos, GGSWToBackendMut, GLWEInfos, LWEInfos, Rank, TorusPrecision,
12    compressed::{
13        GLWECompressed, GLWECompressedBackendMut, GLWECompressedBackendRef, GLWECompressedViewMut, GLWECompressedViewRef,
14        GLWEDecompress,
15    },
16};
17use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
18use poulpy_hal::layouts::ZnxWord;
19use std::{
20    fmt,
21    ops::{Deref, DerefMut},
22};
23
24/// Seed-compressed GGSW (gadget GSW) ciphertext layout.
25///
26/// Stores only the body components of a [`GGSW`] ciphertext; the mask
27/// polynomials are regenerated deterministically from 32-byte PRNG
28/// seeds during decompression.
29#[derive(PartialEq, Eq, Clone)]
30pub struct GGSWCompressed<D: Data, W: ZnxWord> {
31    pub(crate) data: MatZnx<D, W>,
32    pub(crate) k_aux: TorusPrecision,
33    pub(crate) base2k: Base2K,
34    pub(crate) dsize: Dsize,
35    pub(crate) rank: Rank,
36    pub(crate) seed: Vec<[u8; 32]>,
37}
38
39pub struct GGSWCompressedBackendRef<'a, BE: Backend + 'a> {
40    inner: GGSWCompressed<BE::BufRef<'a>, BE::ZnxWord>,
41}
42
43impl<'a, BE: Backend + 'a> GGSWCompressedBackendRef<'a, BE> {
44    pub fn from_inner(inner: GGSWCompressed<BE::BufRef<'a>, BE::ZnxWord>) -> Self {
45        Self { inner }
46    }
47
48    pub fn into_inner(self) -> GGSWCompressed<BE::BufRef<'a>, BE::ZnxWord> {
49        self.inner
50    }
51
52    pub fn at_view(&self, row: usize, col: usize) -> GLWECompressedViewRef<'_, BE> {
53        GLWECompressedViewRef::from_inner(ggsw_compressed_at_backend_ref_from_ref::<BE>(&self.inner, row, col))
54    }
55}
56
57impl<'a, BE: Backend + 'a> Deref for GGSWCompressedBackendRef<'a, BE> {
58    type Target = GGSWCompressed<BE::BufRef<'a>, BE::ZnxWord>;
59
60    fn deref(&self) -> &Self::Target {
61        &self.inner
62    }
63}
64
65pub struct GGSWCompressedBackendMut<'a, BE: Backend + 'a> {
66    inner: GGSWCompressed<BE::BufMut<'a>, BE::ZnxWord>,
67}
68
69impl<'a, BE: Backend + 'a> GGSWCompressedBackendMut<'a, BE> {
70    pub fn from_inner(inner: GGSWCompressed<BE::BufMut<'a>, BE::ZnxWord>) -> Self {
71        Self { inner }
72    }
73
74    pub fn into_inner(self) -> GGSWCompressed<BE::BufMut<'a>, BE::ZnxWord> {
75        self.inner
76    }
77
78    pub fn at_view_mut(&mut self, row: usize, col: usize) -> GLWECompressedViewMut<'_, BE> {
79        GLWECompressedViewMut::from_inner(ggsw_compressed_at_backend_mut_from_mut::<BE>(&mut self.inner, row, col))
80    }
81}
82
83impl<'a, BE: Backend + 'a> Deref for GGSWCompressedBackendMut<'a, BE> {
84    type Target = GGSWCompressed<BE::BufMut<'a>, BE::ZnxWord>;
85
86    fn deref(&self) -> &Self::Target {
87        &self.inner
88    }
89}
90
91impl<'a, BE: Backend + 'a> DerefMut for GGSWCompressedBackendMut<'a, BE> {
92    fn deref_mut(&mut self) -> &mut Self::Target {
93        &mut self.inner
94    }
95}
96
97impl<'a, BE: Backend + 'a> LWEInfos for GGSWCompressedBackendRef<'a, BE> {
98    fn base2k(&self) -> Base2K {
99        self.inner.base2k()
100    }
101
102    fn n(&self) -> Degree {
103        self.inner.n()
104    }
105
106    fn max_size(&self) -> usize {
107        self.inner.max_size()
108    }
109
110    fn k(&self) -> TorusPrecision {
111        self.inner.k()
112    }
113}
114
115impl<'a, BE: Backend + 'a> GLWEInfos for GGSWCompressedBackendRef<'a, BE> {
116    fn rank(&self) -> Rank {
117        self.inner.rank()
118    }
119}
120
121impl<'a, BE: Backend + 'a> GGSWInfos for GGSWCompressedBackendRef<'a, BE> {
122    fn k_aux(&self) -> TorusPrecision {
123        self.inner.k_aux()
124    }
125
126    fn dnum(&self) -> Dnum {
127        self.inner.dnum()
128    }
129
130    fn dsize(&self) -> Dsize {
131        self.inner.dsize()
132    }
133}
134
135impl<'a, BE: Backend + 'a> LWEInfos for GGSWCompressedBackendMut<'a, BE> {
136    fn base2k(&self) -> Base2K {
137        self.inner.base2k()
138    }
139
140    fn n(&self) -> Degree {
141        self.inner.n()
142    }
143
144    fn max_size(&self) -> usize {
145        self.inner.max_size()
146    }
147
148    fn k(&self) -> TorusPrecision {
149        self.inner.k()
150    }
151}
152
153impl<'a, BE: Backend + 'a> GLWEInfos for GGSWCompressedBackendMut<'a, BE> {
154    fn rank(&self) -> Rank {
155        self.inner.rank()
156    }
157}
158
159impl<'a, BE: Backend + 'a> GGSWInfos for GGSWCompressedBackendMut<'a, BE> {
160    fn k_aux(&self) -> TorusPrecision {
161        self.inner.k_aux()
162    }
163
164    fn dnum(&self) -> Dnum {
165        self.inner.dnum()
166    }
167
168    fn dsize(&self) -> Dsize {
169        self.inner.dsize()
170    }
171}
172
173impl<'a, BE: Backend + 'a> GGSWCompressedSeedMut for GGSWCompressedBackendMut<'a, BE> {
174    fn seed_mut(&mut self) -> &mut Vec<[u8; 32]> {
175        &mut self.inner.seed
176    }
177}
178
179/// Provides mutable access to the PRNG seeds of a compressed GGSW.
180pub trait GGSWCompressedSeedMut {
181    /// Returns a mutable reference to the vector of 32-byte PRNG seeds.
182    fn seed_mut(&mut self) -> &mut Vec<[u8; 32]>;
183}
184
185impl<D: Data, W: ZnxWord> GGSWCompressedSeedMut for GGSWCompressed<D, W> {
186    fn seed_mut(&mut self) -> &mut Vec<[u8; 32]> {
187        &mut self.seed
188    }
189}
190
191/// Provides read access to the PRNG seeds of a compressed GGSW.
192pub trait GGSWCompressedSeed {
193    /// Returns a reference to the vector of 32-byte PRNG seeds.
194    fn seed(&self) -> &Vec<[u8; 32]>;
195}
196
197impl<D: HostDataRef, W: ZnxWord> GGSWCompressedSeed for GGSWCompressed<D, W> {
198    fn seed(&self) -> &Vec<[u8; 32]> {
199        &self.seed
200    }
201}
202
203impl<D: Data, W: ZnxWord> LWEInfos for GGSWCompressed<D, W> {
204    fn n(&self) -> Degree {
205        Degree(self.data.n() as u32)
206    }
207
208    fn base2k(&self) -> Base2K {
209        self.base2k
210    }
211
212    fn max_size(&self) -> usize {
213        crate::layouts::key_size(self.base2k, self.dnum(), self.dsize, self.k_aux)
214    }
215
216    fn k(&self) -> TorusPrecision {
217        crate::layouts::key_k(self.base2k, self.dnum(), self.dsize, self.k_aux)
218    }
219}
220impl<D: Data, W: ZnxWord> GLWEInfos for GGSWCompressed<D, W> {
221    fn rank(&self) -> Rank {
222        self.rank
223    }
224}
225
226impl<D: Data, W: ZnxWord> GGSWInfos for GGSWCompressed<D, W> {
227    fn k_aux(&self) -> TorusPrecision {
228        self.k_aux
229    }
230
231    fn dsize(&self) -> Dsize {
232        self.dsize
233    }
234
235    fn dnum(&self) -> Dnum {
236        Dnum(self.data.rows() as u32)
237    }
238}
239
240impl<D: HostDataRef, W: ZnxWord> fmt::Debug for GGSWCompressed<D, W> {
241    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242        write!(f, "{}", self.data)
243    }
244}
245
246impl<D: HostDataRef, W: ZnxWord> fmt::Display for GGSWCompressed<D, W> {
247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248        write!(
249            f,
250            "(GGSWCompressed: base2k={} k={} dsize={}) {}",
251            self.base2k,
252            self.k(),
253            self.dsize,
254            self.data
255        )
256    }
257}
258
259impl<D: HostDataMut, W: ZnxWord> FillUniform for GGSWCompressed<D, W> {
260    fn fill_uniform(&mut self, log_bound: usize, source: &mut Source) {
261        self.data.fill_uniform(log_bound, source);
262    }
263}
264
265impl<D: Data, W: ZnxWord> GGSWCompressed<D, W> {
266    /// Allocates a new compressed GGSW by copying parameters from an existing info provider.
267    pub(crate) fn alloc_from_infos<B: Backend<OwnedBuf = D, ZnxWord = W>, A>(infos: &A) -> Self
268    where
269        A: GGSWInfos,
270    {
271        Self::alloc::<B>(
272            infos.n(),
273            infos.base2k(),
274            infos.dnum(),
275            infos.dsize(),
276            infos.k_aux(),
277            infos.rank(),
278        )
279    }
280
281    /// Allocates a new compressed GGSW with the given parameters.
282    pub(crate) fn alloc<B: Backend<OwnedBuf = D, ZnxWord = W>>(
283        n: Degree,
284        base2k: Base2K,
285        dnum: Dnum,
286        dsize: Dsize,
287        k_aux: TorusPrecision,
288        rank: Rank,
289    ) -> Self {
290        let size: usize = crate::layouts::key_size(base2k, dnum, dsize, k_aux);
291
292        GGSWCompressed {
293            data: MatZnx::from_data(
294                B::alloc_zeroed_bytes(B::bytes_of_mat_znx(n.into(), dnum.into(), (rank + 1).into(), 1, size)),
295                n.into(),
296                dnum.into(),
297                (rank + 1).into(),
298                1,
299                size,
300            ),
301            k_aux,
302            base2k,
303            dsize,
304            rank,
305            seed: vec![[0u8; 32]; dnum.as_usize() * (rank.as_usize() + 1)],
306        }
307    }
308
309    /// Returns the serialized byte size by copying parameters from an existing info provider.
310    pub fn bytes_of_from_infos<A>(infos: &A) -> usize
311    where
312        A: GGSWInfos,
313    {
314        Self::bytes_of(
315            infos.n(),
316            infos.base2k(),
317            infos.dnum(),
318            infos.dsize(),
319            infos.k_aux(),
320            infos.rank(),
321        )
322    }
323
324    /// Returns the serialized byte size for a compressed GGSW with the given parameters.
325    pub fn bytes_of(n: Degree, base2k: Base2K, dnum: Dnum, dsize: Dsize, k_aux: TorusPrecision, rank: Rank) -> usize {
326        let size: usize = crate::layouts::key_size(base2k, dnum, dsize, k_aux);
327
328        MatZnx::<Vec<u8>, W>::bytes_of(n.into(), dnum.into(), (rank + 1).into(), 1, size)
329    }
330}
331
332impl<D: HostDataRef, W: ZnxWord> GGSWCompressed<D, W> {
333    /// Returns an immutably-borrowed compressed GLWE at the given row and column.
334    pub fn at(&self, row: usize, col: usize) -> GLWECompressed<&[u8], W> {
335        let rank: usize = self.rank().into();
336        GLWECompressed {
337            data: self.data.at(row, col),
338            k: self.k(),
339            base2k: self.base2k,
340            rank: self.rank,
341            seed: self.seed[row * (rank + 1) + col],
342        }
343    }
344}
345
346impl<D: HostDataMut, W: ZnxWord> GGSWCompressed<D, W> {
347    /// Returns a mutably-borrowed compressed GLWE at the given row and column.
348    pub fn at_mut(&mut self, row: usize, col: usize) -> GLWECompressed<&mut [u8], W> {
349        let rank: usize = self.rank().into();
350        let k = self.k();
351        let seed = self.seed[row * (rank + 1) + col];
352        GLWECompressed {
353            data: self.data.at_mut(row, col),
354            k,
355            base2k: self.base2k,
356            rank: self.rank,
357            seed,
358        }
359    }
360}
361
362impl<D: HostDataMut, W: ZnxWord> ReaderFrom for GGSWCompressed<D, W> {
363    fn read_from<R: std::io::Read>(&mut self, reader: &mut R) -> std::io::Result<()> {
364        self.k_aux = TorusPrecision(reader.read_u32::<LittleEndian>()?);
365        self.base2k = Base2K(reader.read_u32::<LittleEndian>()?);
366        self.dsize = Dsize(reader.read_u32::<LittleEndian>()?);
367        self.rank = Rank(reader.read_u32::<LittleEndian>()?);
368        let seed_len: usize = reader.read_u32::<LittleEndian>()? as usize;
369        self.seed = vec![[0u8; 32]; seed_len];
370        for s in &mut self.seed {
371            reader.read_exact(s)?;
372        }
373        self.data.read_from(reader)
374    }
375}
376
377impl<D: HostDataRef, W: ZnxWord> WriterTo for GGSWCompressed<D, W> {
378    fn write_to<Wr: std::io::Write>(&self, writer: &mut Wr) -> std::io::Result<()> {
379        writer.write_u32::<LittleEndian>(self.k_aux.into())?;
380        writer.write_u32::<LittleEndian>(self.base2k.into())?;
381        writer.write_u32::<LittleEndian>(self.dsize.into())?;
382        writer.write_u32::<LittleEndian>(self.rank.into())?;
383        writer.write_u32::<LittleEndian>(self.seed.len() as u32)?;
384        for s in &self.seed {
385            writer.write_all(s)?;
386        }
387        self.data.write_to(writer)
388    }
389}
390
391/// Trait for decompressing a [`GGSWCompressed`] into a standard [`GGSW`].
392///
393/// Iterates over every (row, column) entry, decompressing each
394/// compressed GLWE individually via [`GLWEDecompress`].
395pub trait GGSWDecompress
396where
397    Self: GLWEDecompress,
398{
399    /// Decompresses `other` into `res`.
400    fn decompress_ggsw<R, O>(&self, res: &mut R, other: &O)
401    where
402        R: GGSWToBackendMut<Self::Backend> + GGSWInfos,
403        O: GGSWCompressedToBackendRef<Self::Backend> + GGSWInfos,
404    {
405        let mut res = res.to_backend_mut();
406        let other = other.to_backend_ref();
407
408        assert_eq!(res.rank(), other.rank());
409        let dnum: usize = res.dnum().into();
410        let rank: usize = res.rank().into();
411
412        for row_i in 0..dnum {
413            for col_j in 0..rank + 1 {
414                let mut dst = res.at_view_mut(row_i, col_j);
415                let src = other.at_view(row_i, col_j);
416                self.decompress_glwe(&mut dst, &src);
417            }
418        }
419    }
420}
421
422impl<B: Backend> GGSWDecompress for Module<B> where Self: GLWEDecompress {}
423
424// module-only API: decompression is provided by `GGSWDecompress` on `Module`.
425
426pub trait GGSWCompressedToBackendRef<BE: Backend> {
427    fn to_backend_ref(&self) -> GGSWCompressedBackendRef<'_, BE>;
428}
429
430impl<BE: Backend> GGSWCompressedToBackendRef<BE> for GGSWCompressed<BE::OwnedBuf, BE::ZnxWord> {
431    fn to_backend_ref(&self) -> GGSWCompressedBackendRef<'_, BE> {
432        GGSWCompressedBackendRef::from_inner(GGSWCompressed {
433            k_aux: self.k_aux(),
434            base2k: self.base2k(),
435            dsize: self.dsize(),
436            rank: self.rank(),
437            seed: self.seed.clone(),
438            data: <MatZnx<BE::OwnedBuf, BE::ZnxWord> as MatZnxToBackendRef<BE>>::to_backend_ref(&self.data),
439        })
440    }
441}
442
443impl<'b, BE: Backend + 'b> GGSWCompressedToBackendRef<BE> for &GGSWCompressed<BE::BufRef<'b>, BE::ZnxWord> {
444    fn to_backend_ref(&self) -> GGSWCompressedBackendRef<'_, BE> {
445        GGSWCompressedBackendRef::from_inner(GGSWCompressed {
446            k_aux: self.k_aux(),
447            base2k: self.base2k(),
448            dsize: self.dsize(),
449            rank: self.rank(),
450            seed: self.seed.clone(),
451            data: poulpy_hal::layouts::mat_znx_backend_ref_from_ref::<BE>(&self.data),
452        })
453    }
454}
455
456impl<'b, BE: Backend + 'b> GGSWCompressedToBackendRef<BE> for &mut GGSWCompressed<BE::BufMut<'b>, BE::ZnxWord> {
457    fn to_backend_ref(&self) -> GGSWCompressedBackendRef<'_, BE> {
458        GGSWCompressedBackendRef::from_inner(GGSWCompressed {
459            k_aux: self.k_aux(),
460            base2k: self.base2k(),
461            dsize: self.dsize(),
462            rank: self.rank(),
463            seed: self.seed.clone(),
464            data: mat_znx_backend_ref_from_mut::<BE>(&self.data),
465        })
466    }
467}
468
469pub trait GGSWCompressedToBackendMut<BE: Backend>: GGSWCompressedToBackendRef<BE> {
470    fn to_backend_mut(&mut self) -> GGSWCompressedBackendMut<'_, BE>;
471}
472
473impl<BE: Backend> GGSWCompressedToBackendMut<BE> for GGSWCompressed<BE::OwnedBuf, BE::ZnxWord> {
474    fn to_backend_mut(&mut self) -> GGSWCompressedBackendMut<'_, BE> {
475        GGSWCompressedBackendMut::from_inner(GGSWCompressed {
476            k_aux: self.k_aux(),
477            base2k: self.base2k(),
478            dsize: self.dsize(),
479            rank: self.rank(),
480            seed: self.seed.clone(),
481            data: <MatZnx<BE::OwnedBuf, BE::ZnxWord> as MatZnxToBackendMut<BE>>::to_backend_mut(&mut self.data),
482        })
483    }
484}
485
486impl<'b, BE: Backend + 'b> GGSWCompressedToBackendMut<BE> for &mut GGSWCompressed<BE::BufMut<'b>, BE::ZnxWord> {
487    fn to_backend_mut(&mut self) -> GGSWCompressedBackendMut<'_, BE> {
488        GGSWCompressedBackendMut::from_inner(GGSWCompressed {
489            k_aux: self.k_aux(),
490            base2k: self.base2k(),
491            dsize: self.dsize(),
492            rank: self.rank(),
493            seed: self.seed.clone(),
494            data: mat_znx_backend_mut_from_mut::<BE>(&mut self.data),
495        })
496    }
497}
498
499fn ggsw_compressed_at_backend_mut_from_mut<'a, 'b, BE: Backend>(
500    ggsw: &'a mut GGSWCompressed<BE::BufMut<'b>, BE::ZnxWord>,
501    row: usize,
502    col: usize,
503) -> GLWECompressedBackendMut<'a, BE> {
504    let rank: usize = ggsw.rank().into();
505    let k = ggsw.k();
506    let seed = ggsw.seed[row * (rank + 1) + col];
507    let base2k = ggsw.base2k;
508    let rank_field = ggsw.rank;
509    GLWECompressed {
510        data: mat_znx_at_backend_mut_from_mut::<BE>(&mut ggsw.data, row, col),
511        k,
512        base2k,
513        rank: rank_field,
514        seed,
515    }
516}
517
518fn ggsw_compressed_at_backend_ref_from_ref<'a, 'b, BE: Backend>(
519    ggsw: &'a GGSWCompressed<BE::BufRef<'b>, BE::ZnxWord>,
520    row: usize,
521    col: usize,
522) -> GLWECompressedBackendRef<'a, BE> {
523    let rank: usize = ggsw.rank().into();
524    GLWECompressed {
525        data: mat_znx_at_backend_ref_from_ref::<BE>(&ggsw.data, row, col),
526        k: ggsw.k(),
527        base2k: ggsw.base2k,
528        rank: ggsw.rank,
529        seed: ggsw.seed[row * (rank + 1) + col],
530    }
531}