Skip to main content

poulpy_core/layouts/
glwe.rs

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