Skip to main content

poulpy_core/layouts/
glwe.rs

1use poulpy_hal::{
2    layouts::{
3        Backend, Data, FillUniform, HostDataMut, HostDataRef, Module, ReaderFrom, ToOwnedDeep, TransferFrom, VecZnx,
4        VecZnxToBackendMut, VecZnxToBackendRef, WriterTo,
5    },
6    source::Source,
7};
8
9use crate::api::ModuleTransfer;
10use crate::layouts::{Base2K, Degree, LWEInfos, Rank, SetLWEInfos, TorusPrecision};
11use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
12use std::fmt;
13
14/// Trait providing the parameter accessors for a GLWE (Generalised LWE) ciphertext.
15///
16/// A GLWE ciphertext is a polynomial-ring LWE ciphertext consisting of
17/// a body polynomial and `rank` mask polynomials, all defined over `Z[X]/(X^n + 1)`.
18/// Extends [`LWEInfos`] with the GLWE rank.
19pub trait GLWEInfos
20where
21    Self: LWEInfos,
22{
23    /// Returns the GLWE rank (number of mask polynomials).
24    fn rank(&self) -> Rank;
25    /// Returns a plain-data [`GLWELayout`] snapshot of the current parameters.
26    fn glwe_layout(&self) -> GLWELayout {
27        GLWELayout {
28            n: self.n(),
29            base2k: self.base2k(),
30            k: self.max_k(),
31            rank: self.rank(),
32        }
33    }
34}
35
36/// Plain-data snapshot of the parameters that describe a [`GLWE`] ciphertext.
37#[derive(PartialEq, Eq, Copy, Clone, Debug)]
38pub struct GLWELayout {
39    /// Ring degree.
40    pub n: Degree,
41    /// Base-2-log of the limb width.
42    pub base2k: Base2K,
43    /// Torus precision.
44    pub k: TorusPrecision,
45    /// Number of mask polynomials.
46    pub rank: Rank,
47}
48
49impl LWEInfos for GLWELayout {
50    fn n(&self) -> Degree {
51        self.n
52    }
53
54    fn base2k(&self) -> Base2K {
55        self.base2k
56    }
57
58    fn size(&self) -> usize {
59        self.k.as_usize().div_ceil(self.base2k.as_usize())
60    }
61}
62
63impl GLWEInfos for GLWELayout {
64    fn rank(&self) -> Rank {
65        self.rank
66    }
67}
68
69/// A GLWE (Generalised LWE) ciphertext over the polynomial ring `Z[X]/(X^n + 1)`.
70///
71/// Wraps a [`VecZnx`] with `rank + 1` columns: the first column is the body
72/// polynomial, and the remaining `rank` columns are the mask polynomials.
73///
74/// `D: Data` is the storage backend (e.g. `Vec<u8>`, `&[u8]`, `&mut [u8]`).
75#[derive(PartialEq, Eq, Clone)]
76pub struct GLWE<D: Data> {
77    pub(crate) data: VecZnx<D>,
78    pub(crate) base2k: Base2K,
79}
80
81pub type GLWEBackendRef<'a, BE> = GLWE<<BE as Backend>::BufRef<'a>>;
82pub type GLWEBackendMut<'a, BE> = GLWE<<BE as Backend>::BufMut<'a>>;
83
84impl<D: Data> SetLWEInfos for GLWE<D> {
85    fn set_base2k(&mut self, base2k: Base2K) {
86        self.base2k = base2k
87    }
88}
89
90impl<D: Data> SetLWEInfos for &mut GLWE<D> {
91    fn set_base2k(&mut self, base2k: Base2K) {
92        self.base2k = base2k
93    }
94}
95
96impl<D: Data> GLWE<D> {
97    /// Returns a shared reference to the underlying [`VecZnx`].
98    pub fn data(&self) -> &VecZnx<D> {
99        &self.data
100    }
101
102    /// Returns the allocated limb capacity, which can exceed the active `size()`
103    /// after a precision-consuming rescale.
104    pub fn max_size(&self) -> usize {
105        self.data.max_size()
106    }
107}
108
109impl<D: Data> GLWE<D> {
110    /// Returns a mutable reference to the underlying [`VecZnx`].
111    pub fn data_mut(&mut self) -> &mut VecZnx<D> {
112        &mut self.data
113    }
114}
115
116impl<D: Data> LWEInfos for GLWE<D> {
117    fn base2k(&self) -> Base2K {
118        self.base2k
119    }
120
121    fn n(&self) -> Degree {
122        Degree(self.data.n() as u32)
123    }
124
125    fn size(&self) -> usize {
126        self.data.size()
127    }
128}
129
130impl<D: Data> LWEInfos for &GLWE<D> {
131    fn base2k(&self) -> Base2K {
132        self.base2k
133    }
134
135    fn n(&self) -> Degree {
136        Degree(self.data.n() as u32)
137    }
138
139    fn size(&self) -> usize {
140        self.data.size()
141    }
142}
143
144impl<D: Data> LWEInfos for &mut GLWE<D> {
145    fn base2k(&self) -> Base2K {
146        self.base2k
147    }
148
149    fn n(&self) -> Degree {
150        Degree(self.data.n() as u32)
151    }
152
153    fn size(&self) -> usize {
154        self.data.size()
155    }
156}
157
158impl<D: Data> GLWEInfos for GLWE<D> {
159    fn rank(&self) -> Rank {
160        Rank(self.data.cols() as u32 - 1)
161    }
162}
163
164impl<D: Data> GLWEInfos for &GLWE<D> {
165    fn rank(&self) -> Rank {
166        Rank(self.data.cols() as u32 - 1)
167    }
168}
169
170impl<D: Data> GLWEInfos for &mut GLWE<D> {
171    fn rank(&self) -> Rank {
172        Rank(self.data.cols() as u32 - 1)
173    }
174}
175
176impl<D: HostDataRef> ToOwnedDeep for GLWE<D> {
177    type Owned = GLWE<Vec<u8>>;
178    fn to_owned_deep(&self) -> Self::Owned {
179        GLWE {
180            data: self.data.to_owned_deep(),
181            base2k: self.base2k,
182        }
183    }
184}
185
186impl<D: HostDataRef> GLWE<D> {
187    /// Copies this ciphertext's backing bytes into an owned buffer of
188    /// backend `To`, routing via host bytes.
189    ///
190    /// `BE` is the backend that produced `self`; `To` is the destination.
191    pub fn to_backend<BE, To>(&self, dst: &Module<To>) -> GLWE<To::OwnedBuf>
192    where
193        BE: Backend<OwnedBuf = D>,
194        To: Backend,
195        To: TransferFrom<BE>,
196    {
197        dst.upload_glwe(self)
198    }
199}
200
201impl<D: Data> GLWE<D> {
202    /// Rebuilds this backend-owned ciphertext as a host-owned [`GLWE<Vec<u8>>`].
203    pub fn to_host_owned<BE>(&self) -> GLWE<Vec<u8>>
204    where
205        BE: Backend<OwnedBuf = D>,
206    {
207        GLWE {
208            data: self.data.to_host_owned::<BE>(),
209            base2k: self.base2k,
210        }
211    }
212
213    /// Formats this backend-owned ciphertext through the existing host [`fmt::Display`] implementation.
214    pub fn display_host<BE>(&self) -> String
215    where
216        BE: Backend<OwnedBuf = D>,
217    {
218        self.to_host_owned::<BE>().to_string()
219    }
220}
221
222impl<D: Data> GLWE<D> {
223    /// Zero-cost rename when both backends share the same `OwnedBuf`.
224    pub fn reinterpret<To>(self) -> GLWE<To::OwnedBuf>
225    where
226        To: Backend<OwnedBuf = D>,
227    {
228        let shape = self.data.shape();
229        let data = self.data.data;
230        GLWE {
231            data: VecZnx::from_data_with_max_size(data, shape.n(), shape.cols(), shape.size(), shape.max_size()),
232            base2k: self.base2k,
233        }
234    }
235}
236
237impl<D: HostDataRef> fmt::Debug for GLWE<D> {
238    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
239        write!(f, "{self}")
240    }
241}
242
243impl<D: HostDataRef> fmt::Display for GLWE<D> {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        write!(f, "GLWE: base2k={} k={}: {}", self.base2k().0, self.max_k().0, self.data)
246    }
247}
248
249impl<D: HostDataMut> FillUniform for GLWE<D> {
250    fn fill_uniform(&mut self, log_bound: usize, source: &mut Source) {
251        self.data.fill_uniform(log_bound, source);
252    }
253}
254
255impl GLWE<Vec<u8>> {
256    /// Allocates a new [`GLWE`] with the given parameters.
257    pub(crate) fn alloc_from_infos<A>(infos: &A) -> Self
258    where
259        A: GLWEInfos,
260    {
261        Self::alloc(infos.n(), infos.base2k(), infos.max_k(), infos.rank())
262    }
263
264    /// Allocates a new [`GLWE`] with the given parameters.
265    ///
266    /// * `n` -- ring degree.
267    /// * `base2k` -- base-2-log of the limb width.
268    /// * `k` -- torus precision.
269    /// * `rank` -- number of mask polynomials.
270    pub(crate) fn alloc(n: Degree, base2k: Base2K, k: TorusPrecision, rank: Rank) -> Self {
271        let size: usize = k.0.div_ceil(base2k.0) as usize;
272        GLWE {
273            data: VecZnx::from_data(
274                poulpy_hal::layouts::HostBytesBackend::alloc_bytes(VecZnx::<Vec<u8>>::bytes_of(
275                    n.into(),
276                    (rank + 1).into(),
277                    size,
278                )),
279                n.into(),
280                (rank + 1).into(),
281                size,
282            ),
283            base2k,
284        }
285    }
286
287    /// Returns the byte count required for a [`GLWE`] with the given parameters.
288    pub fn bytes_of_from_infos<A>(infos: &A) -> usize
289    where
290        A: GLWEInfos,
291    {
292        Self::bytes_of(infos.n(), infos.base2k(), infos.max_k(), infos.rank())
293    }
294
295    /// Returns the byte count required for a [`GLWE`] with the given parameters.
296    ///
297    /// * `n` -- ring degree.
298    /// * `base2k` -- base-2-log of the limb width.
299    /// * `k` -- torus precision.
300    /// * `rank` -- number of mask polynomials.
301    pub fn bytes_of(n: Degree, base2k: Base2K, k: TorusPrecision, rank: Rank) -> usize {
302        VecZnx::bytes_of(n.into(), (rank + 1).into(), k.0.div_ceil(base2k.0) as usize)
303    }
304
305    /// Reallocates the backing buffer so capacity matches `size` limb count.
306    pub fn reallocate_limbs(&mut self, size: usize) {
307        self.data.reallocate_limbs(size);
308    }
309}
310
311impl<D: HostDataMut> ReaderFrom for GLWE<D> {
312    /// Deserialises a [`GLWE`] in little-endian binary format.
313    fn read_from<R: std::io::Read>(&mut self, reader: &mut R) -> std::io::Result<()> {
314        self.base2k = Base2K(reader.read_u32::<LittleEndian>()?);
315        self.data.read_from(reader)?;
316        Ok(())
317    }
318}
319
320impl<D: HostDataRef> WriterTo for GLWE<D> {
321    /// Serialises the [`GLWE`] in little-endian binary format.
322    fn write_to<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
323        writer.write_u32::<LittleEndian>(self.base2k.0)?;
324        self.data.write_to(writer)
325    }
326}
327
328pub trait GLWEToBackendRef<BE: Backend>: Sized {
329    fn to_backend_ref(&self) -> GLWEBackendRef<'_, BE>;
330}
331
332impl<BE: Backend, D: Data> GLWEToBackendRef<BE> for GLWE<D>
333where
334    VecZnx<D>: VecZnxToBackendRef<BE>,
335{
336    fn to_backend_ref(&self) -> GLWEBackendRef<'_, BE> {
337        GLWE {
338            base2k: self.base2k,
339            data: self.data.to_backend_ref(),
340        }
341    }
342}
343
344pub fn glwe_backend_ref_from_ref<'a, 'b, BE: Backend>(glwe: &'a GLWE<BE::BufRef<'b>>) -> GLWEBackendRef<'a, BE> {
345    GLWE {
346        base2k: glwe.base2k,
347        data: poulpy_hal::layouts::vec_znx_backend_ref_from_ref::<BE>(&glwe.data),
348    }
349}
350
351impl<'b, BE: Backend + 'b> GLWEToBackendRef<BE> for &GLWE<BE::BufRef<'b>> {
352    fn to_backend_ref(&self) -> GLWEBackendRef<'_, BE> {
353        glwe_backend_ref_from_ref::<BE>(self)
354    }
355}
356
357pub fn glwe_backend_ref_from_mut<'a, 'b, BE: Backend>(glwe: &'a GLWE<BE::BufMut<'b>>) -> GLWEBackendRef<'a, BE> {
358    GLWE {
359        base2k: glwe.base2k,
360        data: poulpy_hal::layouts::vec_znx_backend_ref_from_mut::<BE>(&glwe.data),
361    }
362}
363
364pub trait GLWEToBackendMut<BE: Backend>: GLWEToBackendRef<BE> {
365    fn to_backend_mut(&mut self) -> GLWEBackendMut<'_, BE>;
366}
367
368impl<BE: Backend, D: Data> GLWEToBackendMut<BE> for GLWE<D>
369where
370    VecZnx<D>: VecZnxToBackendRef<BE> + VecZnxToBackendMut<BE>,
371{
372    fn to_backend_mut(&mut self) -> GLWEBackendMut<'_, BE> {
373        GLWE {
374            base2k: self.base2k,
375            data: self.data.to_backend_mut(),
376        }
377    }
378}
379
380impl<'b, BE: Backend + 'b> GLWEToBackendRef<BE> for &mut GLWE<BE::BufMut<'b>> {
381    fn to_backend_ref(&self) -> GLWEBackendRef<'_, BE> {
382        glwe_backend_ref_from_mut::<BE>(self)
383    }
384}
385
386impl<'b, BE: Backend + 'b> GLWEToBackendMut<BE> for &mut GLWE<BE::BufMut<'b>> {
387    fn to_backend_mut(&mut self) -> GLWEBackendMut<'_, BE> {
388        glwe_backend_mut_from_mut::<BE>(self)
389    }
390}
391
392pub fn glwe_backend_mut_from_mut<'a, 'b, BE: Backend>(glwe: &'a mut GLWE<BE::BufMut<'b>>) -> GLWEBackendMut<'a, BE> {
393    GLWE {
394        base2k: glwe.base2k,
395        data: poulpy_hal::layouts::vec_znx_backend_mut_from_mut::<BE>(&mut glwe.data),
396    }
397}