Skip to main content

poulpy_core/layouts/
mod.rs

1//! Ciphertext, key, plaintext, and secret-key layout types.
2//!
3//! This module defines the in-memory representations for every
4//! cryptographic object manipulated by `poulpy-core`. Types are
5//! organised into three sub-layers:
6//!
7//! * **Standard** (this module) -- serialisable, platform-independent
8//!   byte layouts backed by [`poulpy_hal::layouts::VecZnx`] /
9//!   [`poulpy_hal::layouts::MatZnx`].
10//! * **[`compressed`]** -- seed-compressed variants that store only
11//!   the body; the mask is regenerated from a 32-byte PRNG seed.
12//! * **[`prepared`]** -- DFT-domain representations tied to a
13//!   specific [`poulpy_hal::layouts::Backend`], used for fast
14//!   polynomial multiplication.
15//!
16//! # Parameter newtypes
17//!
18//! Domain-specific quantities are wrapped in [`u32`]-backed newtypes
19//! generated by the `newtype_u32!` macro:
20//!
21//! | Type | Meaning |
22//! |---|---|
23//! | [`Degree`] | Ring polynomial degree *N* (always a power of two) |
24//! | [`Base2K`] | Base-2 logarithm of the limb radix used in the CRT/digit decomposition |
25//! | [`TorusPrecision`] | Total number of precision bits over the torus *T = R/Z* |
26//! | [`Rank`] | GLWE rank (number of mask polynomials; 0 for plain LWE) |
27//! | [`Dnum`] | Number of gadget-decomposition digits |
28//! | [`Dsize`] | Size (in limbs) of each gadget digit |
29
30#[macro_use]
31mod macros;
32
33mod diagonals;
34mod gglwe;
35mod gglwe_to_ggsw_key;
36mod ggsw;
37mod glwe;
38mod glwe_automorphism_key;
39mod glwe_plaintext;
40mod glwe_public_key;
41mod glwe_secret;
42mod glwe_secret_tensor;
43mod glwe_switching_key;
44mod glwe_tensor;
45mod glwe_tensor_key;
46mod glwe_to_lwe_key;
47mod key_helpers;
48mod linear_transformation;
49mod lwe;
50mod lwe_matrix;
51mod lwe_plaintext;
52mod lwe_secret;
53mod lwe_switching_key;
54mod lwe_to_glwe_key;
55mod polynomial_evaluation;
56mod scratch_views;
57
58pub mod compressed;
59pub mod prepared;
60
61pub use self::compressed::{
62    GGLWECompressed, GGLWECompressedSeed, GGLWECompressedSeedMut, GGLWECompressedToBackendMut, GGLWECompressedToBackendRef,
63    GGLWEDecompress, GGLWEToGGSWKeyCompressed, GGLWEToGGSWKeyCompressedToBackendMut, GGLWEToGGSWKeyCompressedToBackendRef,
64    GGLWEToGGSWKeyDecompress, GGSWCompressed, GGSWCompressedSeed, GGSWCompressedSeedMut, GGSWCompressedToBackendMut,
65    GGSWCompressedToBackendRef, GGSWDecompress, GLWEAutomorphismKeyCompressed, GLWEAutomorphismKeyDecompress, GLWECompressed,
66    GLWECompressedSeed, GLWECompressedSeedMut, GLWECompressedToBackendMut, GLWECompressedToBackendRef, GLWEDecompress,
67    GLWESwitchingKeyCompressed, GLWESwitchingKeyDecompress, GLWETensorKeyCompressed, GLWETensorKeyDecompress,
68    GLWEToLWESwitchingKeyCompressed, GLWEToLWESwitchingKeyDecompress, LWECompressed, LWECompressedToBackendMut,
69    LWECompressedToBackendRef, LWEDecompress, LWESwitchingKeyCompressed, LWESwitchingKeyDecompress, LWEToGLWEKeyCompressed,
70    LWEToGLWEKeyDecompress,
71};
72pub use diagonals::*;
73pub use gglwe::*;
74pub use gglwe_to_ggsw_key::*;
75pub use ggsw::*;
76pub use glwe::*;
77pub use glwe_automorphism_key::*;
78pub use glwe_plaintext::*;
79pub use glwe_public_key::*;
80pub use glwe_secret::*;
81pub use glwe_secret_tensor::*;
82pub use glwe_switching_key::*;
83pub use glwe_tensor::*;
84pub use glwe_tensor_key::*;
85pub use glwe_to_lwe_key::*;
86pub use key_helpers::*;
87pub use linear_transformation::*;
88#[cfg(test)]
89pub(crate) use linear_transformation::{linear_transformation_plan, normalize_linear_transform_diagonal};
90pub use lwe::*;
91pub use lwe_matrix::*;
92pub use lwe_plaintext::*;
93pub use lwe_secret::*;
94pub use lwe_switching_key::*;
95pub use lwe_to_glwe_key::*;
96pub use polynomial_evaluation::*;
97pub use prepared::*;
98pub use scratch_views::*;
99
100use crate::dist::Distribution;
101use poulpy_hal::layouts::{Backend, Data, MatZnx, Module, ScalarZnx, ZnxWord, vec_znx_alloc_zeroed};
102
103/// Backend-indexed ownership aliases for the non-prepared layouts.
104///
105/// These resolve to `<Layout><<BE as Backend>::OwnedBuf>` so that user
106/// code can declare types in terms of the owning backend instead of the
107/// raw storage type. On CPU backends this is just `Vec<u8>`; on future
108/// device backends it is the backend's device buffer type.
109pub type BackendGLWE<BE> = GLWE<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
110pub type BackendGGLWE<BE> = GGLWE<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
111pub type BackendGGSW<BE> = GGSW<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
112pub type BackendLWE<BE> = LWE<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
113pub type BackendLWEMatrix<BE> = LWEMatrix<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
114pub type BackendGLWESecret<BE> = GLWESecret<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
115pub type BackendLWESecret<BE> = LWESecret<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
116pub type BackendGLWEAutomorphismKey<BE> = GLWEAutomorphismKey<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
117pub type BackendGLWESwitchingKey<BE> = GLWESwitchingKey<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
118pub type BackendGLWEPlaintext<BE> = GLWEPlaintext<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
119pub type BackendLWEPlaintext<BE> = LWEPlaintext<<BE as Backend>::OwnedBuf, <BE as Backend>::ZnxWord>;
120pub type BackendGLWEPrepared<BE> = GLWEPrepared<<BE as Backend>::OwnedBuf, BE>;
121pub type BackendGGLWEPrepared<BE> = GGLWEPrepared<<BE as Backend>::OwnedBuf, BE>;
122pub type BackendGGSWPrepared<BE> = GGSWPrepared<<BE as Backend>::OwnedBuf, BE>;
123pub type BackendGLWESecretPrepared<BE> = GLWESecretPrepared<<BE as Backend>::OwnedBuf, BE>;
124pub type BackendGLWEPublicKeyPrepared<BE> = GLWEPublicKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
125pub type BackendGLWESecretTensorPrepared<BE> = GLWESecretTensorPrepared<<BE as Backend>::OwnedBuf, BE>;
126pub type BackendGLWESwitchingKeyPrepared<BE> = GLWESwitchingKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
127pub type BackendGLWEAutomorphismKeyPrepared<BE> = GLWEAutomorphismKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
128pub type BackendGLWETensorKeyPrepared<BE> = GLWETensorKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
129pub type BackendGLWEToLWEKeyPrepared<BE> = GLWEToLWEKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
130pub type BackendLWESwitchingKeyPrepared<BE> = LWESwitchingKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
131pub type BackendLWEToGLWEKeyPrepared<BE> = LWEToGLWEKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
132pub type BackendGGLWEToGGSWKeyPrepared<BE> = GGLWEToGGSWKeyPrepared<<BE as Backend>::OwnedBuf, BE>;
133
134/// Provides access to the ring polynomial degree *N*.
135pub trait GetDegree {
136    /// Returns the ring degree *N* as a [`Degree`].
137    fn ring_degree(&self) -> Degree;
138}
139
140impl<B: Backend> GetDegree for Module<B> {
141    fn ring_degree(&self) -> Degree {
142        Self::n(self).into()
143    }
144}
145
146/// Backend-native wrapper allocation helpers hung off a [`Module`].
147///
148/// This mirrors the `poulpy-hal` allocation model: callers allocate through a
149/// module/context object instead of using static layout constructors directly.
150pub trait ModuleCoreAlloc {
151    type OwnedBuf: Data;
152    type ZnxWord: ZnxWord;
153
154    fn glwe_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWE<Self::OwnedBuf, Self::ZnxWord>;
155    fn glwe_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWE<Self::OwnedBuf, Self::ZnxWord>;
156
157    /// Allocates a GLWE with exactly `size` limbs (so `max_size() == size`),
158    /// labelling `k` as the full physical width `size · base2k`. Unlike
159    /// [`Self::glwe_alloc`] (which derives `size = ceil(k / base2k)` from a target
160    /// `k`), this takes the limb count directly, decoupling the buffer size from
161    /// the effective `k` — relabel the effective `k` afterwards with [`SetK`].
162    fn glwe_alloc_with_size(&self, base2k: Base2K, size: usize, rank: Rank) -> GLWE<Self::OwnedBuf, Self::ZnxWord>;
163
164    fn gglwe_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWE<Self::OwnedBuf, Self::ZnxWord>;
165    fn gglwe_alloc(
166        &self,
167        base2k: Base2K,
168        dnum: Dnum,
169        dsize: Dsize,
170        k_aux: TorusPrecision,
171        rank_in: Rank,
172        rank_out: Rank,
173    ) -> GGLWE<Self::OwnedBuf, Self::ZnxWord>;
174
175    fn ggsw_alloc_from_infos<A: GGSWInfos>(&self, infos: &A) -> GGSW<Self::OwnedBuf, Self::ZnxWord>;
176    fn ggsw_alloc(
177        &self,
178        base2k: Base2K,
179        dnum: Dnum,
180        dsize: Dsize,
181        k_aux: TorusPrecision,
182        rank: Rank,
183    ) -> GGSW<Self::OwnedBuf, Self::ZnxWord>;
184
185    fn glwe_plaintext_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWEPlaintext<Self::OwnedBuf, Self::ZnxWord>;
186    fn glwe_plaintext_alloc(&self, base2k: Base2K, k: TorusPrecision) -> GLWEPlaintext<Self::OwnedBuf, Self::ZnxWord>;
187
188    fn glwe_secret_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWESecret<Self::OwnedBuf, Self::ZnxWord>;
189    fn glwe_secret_alloc(&self, rank: Rank) -> GLWESecret<Self::OwnedBuf, Self::ZnxWord>;
190
191    fn glwe_secret_tensor_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWESecretTensor<Self::OwnedBuf, Self::ZnxWord>;
192    fn glwe_secret_tensor_alloc(&self, rank: Rank) -> GLWESecretTensor<Self::OwnedBuf, Self::ZnxWord>;
193
194    fn glwe_tensor_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWETensor<Self::OwnedBuf, Self::ZnxWord>;
195    fn glwe_tensor_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWETensor<Self::OwnedBuf, Self::ZnxWord>;
196
197    fn glwe_public_key_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWEPublicKey<Self::OwnedBuf, Self::ZnxWord>;
198    fn glwe_public_key_alloc(
199        &self,
200        base2k: Base2K,
201        k: TorusPrecision,
202        rank: Rank,
203    ) -> GLWEPublicKey<Self::OwnedBuf, Self::ZnxWord>;
204
205    fn glwe_switching_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWESwitchingKey<Self::OwnedBuf, Self::ZnxWord>;
206    fn glwe_switching_key_alloc(
207        &self,
208        base2k: Base2K,
209        dnum: Dnum,
210        dsize: Dsize,
211        k_aux: TorusPrecision,
212        rank_in: Rank,
213        rank_out: Rank,
214    ) -> GLWESwitchingKey<Self::OwnedBuf, Self::ZnxWord>;
215
216    fn glwe_automorphism_key_alloc_from_infos<A: GGLWEInfos>(
217        &self,
218        infos: &A,
219    ) -> GLWEAutomorphismKey<Self::OwnedBuf, Self::ZnxWord>;
220    fn glwe_automorphism_key_alloc(
221        &self,
222        base2k: Base2K,
223        dnum: Dnum,
224        dsize: Dsize,
225        k_aux: TorusPrecision,
226        rank: Rank,
227    ) -> GLWEAutomorphismKey<Self::OwnedBuf, Self::ZnxWord>;
228
229    fn glwe_tensor_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWETensorKey<Self::OwnedBuf, Self::ZnxWord>;
230    fn glwe_tensor_key_alloc(
231        &self,
232        base2k: Base2K,
233        dnum: Dnum,
234        dsize: Dsize,
235        k_aux: TorusPrecision,
236        rank: Rank,
237    ) -> GLWETensorKey<Self::OwnedBuf, Self::ZnxWord>;
238
239    fn glwe_to_lwe_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWEToLWEKey<Self::OwnedBuf, Self::ZnxWord>;
240    fn glwe_to_lwe_key_alloc(
241        &self,
242        base2k: Base2K,
243        dnum: Dnum,
244        k_aux: TorusPrecision,
245        rank_in: Rank,
246    ) -> GLWEToLWEKey<Self::OwnedBuf, Self::ZnxWord>;
247
248    fn gglwe_to_ggsw_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWEToGGSWKey<Self::OwnedBuf, Self::ZnxWord>;
249    fn gglwe_to_ggsw_key_alloc(
250        &self,
251        base2k: Base2K,
252        dnum: Dnum,
253        dsize: Dsize,
254        k_aux: TorusPrecision,
255        rank: Rank,
256    ) -> GGLWEToGGSWKey<Self::OwnedBuf, Self::ZnxWord>;
257
258    fn lwe_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWE<Self::OwnedBuf, Self::ZnxWord>;
259    fn lwe_alloc(&self, n: Degree, base2k: Base2K, k: TorusPrecision) -> LWE<Self::OwnedBuf, Self::ZnxWord>;
260
261    fn lwe_matrix_alloc_from_infos<A: LWEMatrixInfos>(&self, infos: &A) -> LWEMatrix<Self::OwnedBuf, Self::ZnxWord>;
262    fn lwe_matrix_alloc(
263        &self,
264        rows: usize,
265        lwe_n: Degree,
266        base2k: Base2K,
267        k: TorusPrecision,
268    ) -> LWEMatrix<Self::OwnedBuf, Self::ZnxWord>;
269
270    fn lwe_plaintext_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWEPlaintext<Self::OwnedBuf, Self::ZnxWord>;
271    fn lwe_plaintext_alloc(&self, base2k: Base2K, k: TorusPrecision) -> LWEPlaintext<Self::OwnedBuf, Self::ZnxWord>;
272
273    fn lwe_secret_alloc(&self, n: Degree) -> LWESecret<Self::OwnedBuf, Self::ZnxWord>;
274
275    fn lwe_switching_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> LWESwitchingKey<Self::OwnedBuf, Self::ZnxWord>;
276    fn lwe_switching_key_alloc(
277        &self,
278        n: Degree,
279        base2k: Base2K,
280        dnum: Dnum,
281        k_aux: TorusPrecision,
282    ) -> LWESwitchingKey<Self::OwnedBuf, Self::ZnxWord>;
283
284    fn lwe_to_glwe_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> LWEToGLWEKey<Self::OwnedBuf, Self::ZnxWord>;
285    fn lwe_to_glwe_key_alloc(
286        &self,
287        n: Degree,
288        base2k: Base2K,
289        dnum: Dnum,
290        k_aux: TorusPrecision,
291        rank_out: Rank,
292    ) -> LWEToGLWEKey<Self::OwnedBuf, Self::ZnxWord>;
293}
294
295impl<B: Backend> ModuleCoreAlloc for Module<B> {
296    type OwnedBuf = B::OwnedBuf;
297    type ZnxWord = B::ZnxWord;
298
299    fn glwe_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWE<B::OwnedBuf, B::ZnxWord> {
300        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
301        GLWE {
302            data: vec_znx_alloc_zeroed::<B>(infos.n().as_usize(), (infos.rank() + 1).as_usize(), size),
303            k: infos.k(),
304            base2k: infos.base2k(),
305        }
306    }
307    fn glwe_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWE<B::OwnedBuf, B::ZnxWord> {
308        self.glwe_alloc_from_infos(&GLWELayout {
309            n: self.ring_degree(),
310            base2k,
311            k,
312            rank,
313        })
314    }
315
316    fn glwe_alloc_with_size(&self, base2k: Base2K, size: usize, rank: Rank) -> GLWE<B::OwnedBuf, B::ZnxWord> {
317        let n = self.ring_degree().as_usize();
318        let cols = (rank + 1).as_usize();
319        GLWE {
320            data: vec_znx_alloc_zeroed::<B>(n, cols, size),
321            k: TorusPrecision((size * base2k.as_usize()) as u32),
322            base2k,
323        }
324    }
325
326    fn gglwe_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWE<B::OwnedBuf, B::ZnxWord> {
327        let dnum = infos.dnum();
328        let size = crate::layouts::key_size(infos.base2k(), dnum, infos.dsize(), infos.k_aux());
329
330        GGLWE {
331            data: MatZnx::from_data(
332                B::alloc_zeroed_bytes(B::bytes_of_mat_znx(
333                    infos.n().as_usize(),
334                    dnum.as_usize(),
335                    infos.rank_in().as_usize(),
336                    (infos.rank_out() + 1).as_usize(),
337                    size,
338                )),
339                infos.n().as_usize(),
340                dnum.as_usize(),
341                infos.rank_in().as_usize(),
342                (infos.rank_out() + 1).as_usize(),
343                size,
344            ),
345            k_aux: infos.k_aux(),
346            base2k: infos.base2k(),
347            dsize: infos.dsize(),
348        }
349    }
350    fn gglwe_alloc(
351        &self,
352        base2k: Base2K,
353        dnum: Dnum,
354        dsize: Dsize,
355        k_aux: TorusPrecision,
356        rank_in: Rank,
357        rank_out: Rank,
358    ) -> GGLWE<B::OwnedBuf, B::ZnxWord> {
359        self.gglwe_alloc_from_infos(&GGLWELayout {
360            n: self.ring_degree(),
361            base2k,
362            dnum,
363            dsize,
364            k_aux,
365            rank_in,
366            rank_out,
367            stride: 1,
368        })
369    }
370
371    fn ggsw_alloc_from_infos<A: GGSWInfos>(&self, infos: &A) -> GGSW<B::OwnedBuf, B::ZnxWord> {
372        let dnum = infos.dnum();
373        let size = crate::layouts::key_size(infos.base2k(), dnum, infos.dsize(), infos.k_aux());
374
375        GGSW {
376            data: MatZnx::from_data(
377                B::alloc_zeroed_bytes(B::bytes_of_mat_znx(
378                    infos.n().as_usize(),
379                    dnum.as_usize(),
380                    (infos.rank() + 1).as_usize(),
381                    (infos.rank() + 1).as_usize(),
382                    size,
383                )),
384                infos.n().as_usize(),
385                dnum.as_usize(),
386                (infos.rank() + 1).as_usize(),
387                (infos.rank() + 1).as_usize(),
388                size,
389            ),
390            k_aux: infos.k_aux(),
391            base2k: infos.base2k(),
392            dsize: infos.dsize(),
393        }
394    }
395    fn ggsw_alloc(
396        &self,
397        base2k: Base2K,
398        dnum: Dnum,
399        dsize: Dsize,
400        k_aux: TorusPrecision,
401        rank: Rank,
402    ) -> GGSW<B::OwnedBuf, B::ZnxWord> {
403        self.ggsw_alloc_from_infos(&GGSWLayout {
404            n: self.ring_degree(),
405            base2k,
406            dnum,
407            dsize,
408            k_aux,
409            rank,
410        })
411    }
412
413    fn glwe_plaintext_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWEPlaintext<B::OwnedBuf, B::ZnxWord> {
414        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
415        GLWEPlaintext {
416            data: vec_znx_alloc_zeroed::<B>(infos.n().as_usize(), 1, size),
417            k: infos.k(),
418            base2k: infos.base2k(),
419        }
420    }
421    fn glwe_plaintext_alloc(&self, base2k: Base2K, k: TorusPrecision) -> GLWEPlaintext<B::OwnedBuf, B::ZnxWord> {
422        self.glwe_plaintext_alloc_from_infos(&GLWEPlaintextLayout {
423            n: self.ring_degree(),
424            base2k,
425            k,
426        })
427    }
428
429    fn glwe_secret_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWESecret<B::OwnedBuf, B::ZnxWord> {
430        GLWESecret {
431            data: ScalarZnx::from_data(
432                B::alloc_zeroed_bytes(B::bytes_of_scalar_znx(infos.n().as_usize(), infos.rank().as_usize())),
433                infos.n().as_usize(),
434                infos.rank().as_usize(),
435            ),
436            dist: Distribution::NONE,
437        }
438    }
439    fn glwe_secret_alloc(&self, rank: Rank) -> GLWESecret<B::OwnedBuf, B::ZnxWord> {
440        self.glwe_secret_alloc_from_infos(&GLWESecretLayout {
441            n: self.ring_degree(),
442            rank,
443        })
444    }
445
446    fn glwe_secret_tensor_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWESecretTensor<B::OwnedBuf, B::ZnxWord> {
447        let pairs = crate::layouts::pairs(infos.rank().as_usize());
448        GLWESecretTensor {
449            data: ScalarZnx::from_data(
450                B::alloc_zeroed_bytes(B::bytes_of_scalar_znx(infos.n().as_usize(), pairs)),
451                infos.n().as_usize(),
452                pairs,
453            ),
454            rank: infos.rank(),
455            dist: Distribution::NONE,
456        }
457    }
458    fn glwe_secret_tensor_alloc(&self, rank: Rank) -> GLWESecretTensor<B::OwnedBuf, B::ZnxWord> {
459        self.glwe_secret_tensor_alloc_from_infos(&GLWESecretLayout {
460            n: self.ring_degree(),
461            rank,
462        })
463    }
464
465    fn glwe_tensor_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWETensor<B::OwnedBuf, B::ZnxWord> {
466        let cols = infos.rank().as_usize() + 1;
467        let pairs = (((cols + 1) * cols) >> 1).max(1);
468        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
469        GLWETensor {
470            data: vec_znx_alloc_zeroed::<B>(infos.n().as_usize(), pairs, size),
471            k: infos.k(),
472            base2k: infos.base2k(),
473            rank: infos.rank(),
474        }
475    }
476    fn glwe_tensor_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWETensor<B::OwnedBuf, B::ZnxWord> {
477        self.glwe_tensor_alloc_from_infos(&GLWELayout {
478            n: self.ring_degree(),
479            base2k,
480            k,
481            rank,
482        })
483    }
484
485    fn glwe_public_key_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWEPublicKey<B::OwnedBuf, B::ZnxWord> {
486        GLWEPublicKey {
487            key: self.glwe_alloc_from_infos(infos),
488            dist: Distribution::NONE,
489        }
490    }
491    fn glwe_public_key_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWEPublicKey<B::OwnedBuf, B::ZnxWord> {
492        self.glwe_public_key_alloc_from_infos(&GLWELayout {
493            n: self.ring_degree(),
494            base2k,
495            k,
496            rank,
497        })
498    }
499
500    fn glwe_switching_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWESwitchingKey<B::OwnedBuf, B::ZnxWord> {
501        GLWESwitchingKey {
502            key: self.gglwe_alloc_from_infos(infos),
503            input_degree: Degree(0),
504            output_degree: Degree(0),
505        }
506    }
507    fn glwe_switching_key_alloc(
508        &self,
509        base2k: Base2K,
510        dnum: Dnum,
511        dsize: Dsize,
512        k_aux: TorusPrecision,
513        rank_in: Rank,
514        rank_out: Rank,
515    ) -> GLWESwitchingKey<B::OwnedBuf, B::ZnxWord> {
516        self.glwe_switching_key_alloc_from_infos(&GGLWELayout {
517            n: self.ring_degree(),
518            base2k,
519            dnum,
520            dsize,
521            k_aux,
522            rank_in,
523            rank_out,
524            stride: 1,
525        })
526    }
527
528    fn glwe_automorphism_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWEAutomorphismKey<B::OwnedBuf, B::ZnxWord> {
529        GLWEAutomorphismKey {
530            key: self.gglwe_alloc_from_infos(infos),
531            p: 0,
532        }
533    }
534    fn glwe_automorphism_key_alloc(
535        &self,
536        base2k: Base2K,
537        dnum: Dnum,
538        dsize: Dsize,
539        k_aux: TorusPrecision,
540        rank: Rank,
541    ) -> GLWEAutomorphismKey<B::OwnedBuf, B::ZnxWord> {
542        self.glwe_automorphism_key_alloc_from_infos(&GGLWELayout {
543            n: self.ring_degree(),
544            base2k,
545            dnum,
546            k_aux,
547            rank_in: rank,
548            rank_out: rank,
549            dsize,
550            stride: 1,
551        })
552    }
553
554    fn glwe_tensor_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWETensorKey<B::OwnedBuf, B::ZnxWord> {
555        GLWETensorKey(self.gglwe_alloc_from_infos(infos))
556    }
557    fn glwe_tensor_key_alloc(
558        &self,
559        base2k: Base2K,
560        dnum: Dnum,
561        dsize: Dsize,
562        k_aux: TorusPrecision,
563        rank: Rank,
564    ) -> GLWETensorKey<B::OwnedBuf, B::ZnxWord> {
565        let pairs = (((rank.0 + 1) * rank.0) >> 1).max(1);
566        self.glwe_tensor_key_alloc_from_infos(&GGLWELayout {
567            n: self.ring_degree(),
568            base2k,
569            dnum,
570            k_aux,
571            rank_in: Rank(pairs),
572            rank_out: rank,
573            dsize,
574            stride: 1,
575        })
576    }
577
578    fn glwe_to_lwe_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GLWEToLWEKey<B::OwnedBuf, B::ZnxWord> {
579        assert_eq!(infos.rank_out().0, 1, "rank_out > 1 is not supported for GLWEToLWEKey");
580        assert_eq!(infos.dsize().0, 1, "dsize > 1 is not supported for GLWEToLWEKey");
581        GLWEToLWEKey(self.glwe_switching_key_alloc_from_infos(infos))
582    }
583    fn glwe_to_lwe_key_alloc(
584        &self,
585        base2k: Base2K,
586        dnum: Dnum,
587        k_aux: TorusPrecision,
588        rank_in: Rank,
589    ) -> GLWEToLWEKey<B::OwnedBuf, B::ZnxWord> {
590        self.glwe_to_lwe_key_alloc_from_infos(&GGLWELayout {
591            n: self.ring_degree(),
592            base2k,
593            dnum,
594            k_aux,
595            rank_in,
596            rank_out: Rank(1),
597            dsize: Dsize(1),
598            stride: 1,
599        })
600    }
601
602    fn gglwe_to_ggsw_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWEToGGSWKey<B::OwnedBuf, B::ZnxWord> {
603        assert_eq!(
604            infos.rank_in(),
605            infos.rank_out(),
606            "rank_in != rank_out is not supported for GGLWEToGGSWKey"
607        );
608        GGLWEToGGSWKey {
609            keys: (0..infos.rank().as_usize())
610                .map(|_| self.gglwe_alloc_from_infos(infos))
611                .collect(),
612        }
613    }
614    fn gglwe_to_ggsw_key_alloc(
615        &self,
616        base2k: Base2K,
617        dnum: Dnum,
618        dsize: Dsize,
619        k_aux: TorusPrecision,
620        rank: Rank,
621    ) -> GGLWEToGGSWKey<B::OwnedBuf, B::ZnxWord> {
622        self.gglwe_to_ggsw_key_alloc_from_infos(&GGLWELayout {
623            n: self.ring_degree(),
624            base2k,
625            dnum,
626            k_aux,
627            rank_in: rank,
628            rank_out: rank,
629            dsize,
630            stride: 1,
631        })
632    }
633
634    fn lwe_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWE<B::OwnedBuf, B::ZnxWord> {
635        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
636        let n = infos.n().as_usize();
637        LWE {
638            body: vec_znx_alloc_zeroed::<B>(1, 1, size),
639            mask: vec_znx_alloc_zeroed::<B>(n, 1, size),
640            base2k: infos.base2k(),
641            k: infos.k(),
642        }
643    }
644    fn lwe_alloc(&self, n: Degree, base2k: Base2K, k: TorusPrecision) -> LWE<B::OwnedBuf, B::ZnxWord> {
645        self.lwe_alloc_from_infos(&LWELayout { n, base2k, k })
646    }
647
648    fn lwe_matrix_alloc_from_infos<A: LWEMatrixInfos>(&self, infos: &A) -> LWEMatrix<B::OwnedBuf, B::ZnxWord> {
649        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
650        let rows = infos.rows();
651        LWEMatrix {
652            body: vec_znx_alloc_zeroed::<B>(rows, 1, size),
653            mask: vec_znx_alloc_zeroed::<B>(rows, infos.n().as_usize(), size),
654            k: infos.k(),
655            base2k: infos.base2k(),
656        }
657    }
658    fn lwe_matrix_alloc(
659        &self,
660        rows: usize,
661        lwe_n: Degree,
662        base2k: Base2K,
663        k: TorusPrecision,
664    ) -> LWEMatrix<B::OwnedBuf, B::ZnxWord> {
665        self.lwe_matrix_alloc_from_infos(&LWEMatrixLayout {
666            rows,
667            n: lwe_n,
668            base2k,
669            k,
670        })
671    }
672
673    fn lwe_plaintext_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWEPlaintext<B::OwnedBuf, B::ZnxWord> {
674        let size = infos.k().as_usize().div_ceil(infos.base2k().as_usize());
675        LWEPlaintext {
676            data: vec_znx_alloc_zeroed::<B>(1, 1, size),
677            k: infos.k(),
678            base2k: infos.base2k(),
679        }
680    }
681    fn lwe_plaintext_alloc(&self, base2k: Base2K, k: TorusPrecision) -> LWEPlaintext<B::OwnedBuf, B::ZnxWord> {
682        let size = k.as_usize().div_ceil(base2k.as_usize());
683        LWEPlaintext {
684            data: vec_znx_alloc_zeroed::<B>(1, 1, size),
685            k,
686            base2k,
687        }
688    }
689
690    fn lwe_secret_alloc(&self, n: Degree) -> LWESecret<B::OwnedBuf, B::ZnxWord> {
691        LWESecret {
692            data: ScalarZnx::from_data(
693                B::alloc_zeroed_bytes(B::bytes_of_scalar_znx(n.as_usize(), 1)),
694                n.as_usize(),
695                1,
696            ),
697            dist: Distribution::NONE,
698        }
699    }
700
701    fn lwe_switching_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> LWESwitchingKey<B::OwnedBuf, B::ZnxWord> {
702        assert_eq!(infos.dsize().0, 1, "dsize > 1 is not supported for LWESwitchingKey");
703        assert_eq!(infos.rank_in().0, 1, "rank_in > 1 is not supported for LWESwitchingKey");
704        assert_eq!(infos.rank_out().0, 1, "rank_out > 1 is not supported for LWESwitchingKey");
705        LWESwitchingKey(self.glwe_switching_key_alloc_from_infos(infos))
706    }
707    fn lwe_switching_key_alloc(
708        &self,
709        n: Degree,
710        base2k: Base2K,
711        dnum: Dnum,
712        k_aux: TorusPrecision,
713    ) -> LWESwitchingKey<B::OwnedBuf, B::ZnxWord> {
714        self.lwe_switching_key_alloc_from_infos(&LWESwitchingKeyLayout { n, base2k, dnum, k_aux })
715    }
716
717    fn lwe_to_glwe_key_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> LWEToGLWEKey<B::OwnedBuf, B::ZnxWord> {
718        assert_eq!(infos.rank_in().0, 1, "rank_in > 1 is not supported for LWEToGLWEKey");
719        assert_eq!(infos.dsize().0, 1, "dsize > 1 is not supported for LWEToGLWEKey");
720        LWEToGLWEKey(self.glwe_switching_key_alloc_from_infos(infos))
721    }
722    fn lwe_to_glwe_key_alloc(
723        &self,
724        n: Degree,
725        base2k: Base2K,
726        dnum: Dnum,
727        k_aux: TorusPrecision,
728        rank_out: Rank,
729    ) -> LWEToGLWEKey<B::OwnedBuf, B::ZnxWord> {
730        self.lwe_to_glwe_key_alloc_from_infos(&GGLWELayout {
731            n,
732            base2k,
733            dnum,
734            k_aux,
735            rank_in: Rank(1),
736            rank_out,
737            dsize: Dsize(1),
738            stride: 1,
739        })
740    }
741}
742
743/// Host-owned compressed wrapper allocation helpers hung off a [`Module`].
744///
745/// This mirrors [`ModuleCoreAlloc`], but for seed-compressed ciphertext and
746/// key layouts.
747pub trait ModuleCoreCompressedAlloc {
748    type OwnedBuf: Data;
749    type ZnxWord: ZnxWord;
750
751    fn glwe_compressed_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWECompressed<Self::OwnedBuf, Self::ZnxWord>;
752    fn glwe_compressed_alloc(
753        &self,
754        base2k: Base2K,
755        k: TorusPrecision,
756        rank: Rank,
757    ) -> GLWECompressed<Self::OwnedBuf, Self::ZnxWord>;
758
759    fn lwe_compressed_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWECompressed<Self::OwnedBuf, Self::ZnxWord>;
760    fn lwe_compressed_alloc(&self, base2k: Base2K, k: TorusPrecision) -> LWECompressed<Self::OwnedBuf, Self::ZnxWord>;
761
762    fn gglwe_compressed_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWECompressed<Self::OwnedBuf, Self::ZnxWord>;
763    fn gglwe_compressed_alloc(
764        &self,
765        base2k: Base2K,
766        dnum: Dnum,
767        dsize: Dsize,
768        k_aux: TorusPrecision,
769        rank_in: Rank,
770        rank_out: Rank,
771    ) -> GGLWECompressed<Self::OwnedBuf, Self::ZnxWord>;
772
773    fn ggsw_compressed_alloc_from_infos<A: GGSWInfos>(&self, infos: &A) -> GGSWCompressed<Self::OwnedBuf, Self::ZnxWord>;
774    fn ggsw_compressed_alloc(
775        &self,
776        base2k: Base2K,
777        dnum: Dnum,
778        dsize: Dsize,
779        k_aux: TorusPrecision,
780        rank: Rank,
781    ) -> GGSWCompressed<Self::OwnedBuf, Self::ZnxWord>;
782
783    fn glwe_switching_key_compressed_alloc_from_infos<A: GGLWEInfos>(
784        &self,
785        infos: &A,
786    ) -> GLWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
787    fn glwe_switching_key_compressed_alloc(
788        &self,
789        base2k: Base2K,
790        dnum: Dnum,
791        dsize: Dsize,
792        k_aux: TorusPrecision,
793        rank_in: Rank,
794        rank_out: Rank,
795    ) -> GLWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
796
797    fn glwe_automorphism_key_compressed_alloc_from_infos<A: GGLWEInfos>(
798        &self,
799        infos: &A,
800    ) -> GLWEAutomorphismKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
801    fn glwe_automorphism_key_compressed_alloc(
802        &self,
803        base2k: Base2K,
804        dnum: Dnum,
805        dsize: Dsize,
806        k_aux: TorusPrecision,
807        rank: Rank,
808    ) -> GLWEAutomorphismKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
809
810    fn glwe_tensor_key_compressed_alloc_from_infos<A: GGLWEInfos>(
811        &self,
812        infos: &A,
813    ) -> GLWETensorKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
814    fn glwe_tensor_key_compressed_alloc(
815        &self,
816        base2k: Base2K,
817        dnum: Dnum,
818        dsize: Dsize,
819        k_aux: TorusPrecision,
820        rank: Rank,
821    ) -> GLWETensorKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
822
823    fn glwe_to_lwe_key_compressed_alloc_from_infos<A: GGLWEInfos>(
824        &self,
825        infos: &A,
826    ) -> GLWEToLWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
827    fn glwe_to_lwe_key_compressed_alloc(
828        &self,
829        base2k: Base2K,
830        dnum: Dnum,
831        k_aux: TorusPrecision,
832        rank_in: Rank,
833    ) -> GLWEToLWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
834
835    fn lwe_to_glwe_key_compressed_alloc_from_infos<A: GGLWEInfos>(
836        &self,
837        infos: &A,
838    ) -> LWEToGLWEKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
839    fn lwe_to_glwe_key_compressed_alloc(
840        &self,
841        n: Degree,
842        base2k: Base2K,
843        dnum: Dnum,
844        k_aux: TorusPrecision,
845        rank_out: Rank,
846    ) -> LWEToGLWEKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
847
848    fn lwe_switching_key_compressed_alloc_from_infos<A: GGLWEInfos>(
849        &self,
850        infos: &A,
851    ) -> LWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
852    fn lwe_switching_key_compressed_alloc(
853        &self,
854        n: Degree,
855        base2k: Base2K,
856        dnum: Dnum,
857        k_aux: TorusPrecision,
858    ) -> LWESwitchingKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
859
860    fn gglwe_to_ggsw_key_compressed_alloc_from_infos<A: GGLWEInfos>(
861        &self,
862        infos: &A,
863    ) -> GGLWEToGGSWKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
864    fn gglwe_to_ggsw_key_compressed_alloc(
865        &self,
866        base2k: Base2K,
867        dnum: Dnum,
868        dsize: Dsize,
869        k_aux: TorusPrecision,
870        rank: Rank,
871    ) -> GGLWEToGGSWKeyCompressed<Self::OwnedBuf, Self::ZnxWord>;
872}
873
874impl<B: Backend> ModuleCoreCompressedAlloc for Module<B> {
875    type OwnedBuf = B::OwnedBuf;
876    type ZnxWord = B::ZnxWord;
877    fn glwe_compressed_alloc_from_infos<A: GLWEInfos>(&self, infos: &A) -> GLWECompressed<B::OwnedBuf, B::ZnxWord> {
878        GLWECompressed::alloc_from_infos::<B, _>(infos)
879    }
880    fn glwe_compressed_alloc(&self, base2k: Base2K, k: TorusPrecision, rank: Rank) -> GLWECompressed<B::OwnedBuf, B::ZnxWord> {
881        GLWECompressed::alloc::<B>(self.ring_degree(), base2k, k, rank)
882    }
883
884    fn lwe_compressed_alloc_from_infos<A: LWEInfos>(&self, infos: &A) -> LWECompressed<B::OwnedBuf, B::ZnxWord> {
885        LWECompressed::alloc_from_infos::<B, _>(infos)
886    }
887    fn lwe_compressed_alloc(&self, base2k: Base2K, k: TorusPrecision) -> LWECompressed<B::OwnedBuf, B::ZnxWord> {
888        LWECompressed::alloc::<B>(base2k, k)
889    }
890
891    fn gglwe_compressed_alloc_from_infos<A: GGLWEInfos>(&self, infos: &A) -> GGLWECompressed<B::OwnedBuf, B::ZnxWord> {
892        GGLWECompressed::alloc_from_infos::<B, _>(infos)
893    }
894    fn gglwe_compressed_alloc(
895        &self,
896        base2k: Base2K,
897        dnum: Dnum,
898        dsize: Dsize,
899        k_aux: TorusPrecision,
900        rank_in: Rank,
901        rank_out: Rank,
902    ) -> GGLWECompressed<B::OwnedBuf, B::ZnxWord> {
903        GGLWECompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank_in, rank_out)
904    }
905
906    fn ggsw_compressed_alloc_from_infos<A: GGSWInfos>(&self, infos: &A) -> GGSWCompressed<B::OwnedBuf, B::ZnxWord> {
907        GGSWCompressed::alloc_from_infos::<B, _>(infos)
908    }
909    fn ggsw_compressed_alloc(
910        &self,
911        base2k: Base2K,
912        dnum: Dnum,
913        dsize: Dsize,
914        k_aux: TorusPrecision,
915        rank: Rank,
916    ) -> GGSWCompressed<B::OwnedBuf, B::ZnxWord> {
917        GGSWCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank)
918    }
919
920    fn glwe_switching_key_compressed_alloc_from_infos<A: GGLWEInfos>(
921        &self,
922        infos: &A,
923    ) -> GLWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
924        GLWESwitchingKeyCompressed::alloc_from_infos::<B, _>(infos)
925    }
926    fn glwe_switching_key_compressed_alloc(
927        &self,
928        base2k: Base2K,
929        dnum: Dnum,
930        dsize: Dsize,
931        k_aux: TorusPrecision,
932        rank_in: Rank,
933        rank_out: Rank,
934    ) -> GLWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
935        GLWESwitchingKeyCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank_in, rank_out)
936    }
937
938    fn glwe_automorphism_key_compressed_alloc_from_infos<A: GGLWEInfos>(
939        &self,
940        infos: &A,
941    ) -> GLWEAutomorphismKeyCompressed<B::OwnedBuf, B::ZnxWord> {
942        GLWEAutomorphismKeyCompressed::alloc_from_infos::<B, _>(infos)
943    }
944    fn glwe_automorphism_key_compressed_alloc(
945        &self,
946        base2k: Base2K,
947        dnum: Dnum,
948        dsize: Dsize,
949        k_aux: TorusPrecision,
950        rank: Rank,
951    ) -> GLWEAutomorphismKeyCompressed<B::OwnedBuf, B::ZnxWord> {
952        GLWEAutomorphismKeyCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank)
953    }
954
955    fn glwe_tensor_key_compressed_alloc_from_infos<A: GGLWEInfos>(
956        &self,
957        infos: &A,
958    ) -> GLWETensorKeyCompressed<B::OwnedBuf, B::ZnxWord> {
959        GLWETensorKeyCompressed::alloc_from_infos::<B, _>(infos)
960    }
961    fn glwe_tensor_key_compressed_alloc(
962        &self,
963        base2k: Base2K,
964        dnum: Dnum,
965        dsize: Dsize,
966        k_aux: TorusPrecision,
967        rank: Rank,
968    ) -> GLWETensorKeyCompressed<B::OwnedBuf, B::ZnxWord> {
969        GLWETensorKeyCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank)
970    }
971
972    fn glwe_to_lwe_key_compressed_alloc_from_infos<A: GGLWEInfos>(
973        &self,
974        infos: &A,
975    ) -> GLWEToLWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
976        GLWEToLWESwitchingKeyCompressed::alloc_from_infos::<B, _>(infos)
977    }
978    fn glwe_to_lwe_key_compressed_alloc(
979        &self,
980        base2k: Base2K,
981        dnum: Dnum,
982        k_aux: TorusPrecision,
983        rank_in: Rank,
984    ) -> GLWEToLWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
985        GLWEToLWESwitchingKeyCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, k_aux, rank_in)
986    }
987
988    fn lwe_to_glwe_key_compressed_alloc_from_infos<A: GGLWEInfos>(
989        &self,
990        infos: &A,
991    ) -> LWEToGLWEKeyCompressed<B::OwnedBuf, B::ZnxWord> {
992        LWEToGLWEKeyCompressed::alloc_from_infos::<B, _>(infos)
993    }
994    fn lwe_to_glwe_key_compressed_alloc(
995        &self,
996        n: Degree,
997        base2k: Base2K,
998        dnum: Dnum,
999        k_aux: TorusPrecision,
1000        rank_out: Rank,
1001    ) -> LWEToGLWEKeyCompressed<B::OwnedBuf, B::ZnxWord> {
1002        LWEToGLWEKeyCompressed::alloc::<B>(n, base2k, dnum, k_aux, rank_out)
1003    }
1004
1005    fn lwe_switching_key_compressed_alloc_from_infos<A: GGLWEInfos>(
1006        &self,
1007        infos: &A,
1008    ) -> LWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
1009        LWESwitchingKeyCompressed::alloc_from_infos::<B, _>(infos)
1010    }
1011    fn lwe_switching_key_compressed_alloc(
1012        &self,
1013        n: Degree,
1014        base2k: Base2K,
1015        dnum: Dnum,
1016        k_aux: TorusPrecision,
1017    ) -> LWESwitchingKeyCompressed<B::OwnedBuf, B::ZnxWord> {
1018        LWESwitchingKeyCompressed::alloc::<B>(n, base2k, dnum, k_aux)
1019    }
1020
1021    fn gglwe_to_ggsw_key_compressed_alloc_from_infos<A: GGLWEInfos>(
1022        &self,
1023        infos: &A,
1024    ) -> GGLWEToGGSWKeyCompressed<B::OwnedBuf, B::ZnxWord> {
1025        GGLWEToGGSWKeyCompressed::alloc_from_infos::<B, _>(infos)
1026    }
1027    fn gglwe_to_ggsw_key_compressed_alloc(
1028        &self,
1029        base2k: Base2K,
1030        dnum: Dnum,
1031        dsize: Dsize,
1032        k_aux: TorusPrecision,
1033        rank: Rank,
1034    ) -> GGLWEToGGSWKeyCompressed<B::OwnedBuf, B::ZnxWord> {
1035        GGLWEToGGSWKeyCompressed::alloc::<B>(self.ring_degree(), base2k, dnum, dsize, k_aux, rank)
1036    }
1037}
1038
1039/// Newtype over `u32` with arithmetic and comparisons against same type and `u32`.
1040/// Arithmetic is **saturating** (add/sub/mul) to avoid debug-overflow panics.
1041macro_rules! newtype_u32 {
1042    ($(#[$meta:meta])* $name:ident) => {
1043        $(#[$meta])*
1044        #[repr(transparent)]
1045        #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1046        pub struct $name(pub u32);
1047
1048        // ----- Conversions -----
1049        impl From<$name> for u32 {
1050            #[inline]
1051            fn from(v: $name) -> u32 {
1052                v.0
1053            }
1054        }
1055        impl From<$name> for usize {
1056            #[inline]
1057            fn from(v: $name) -> usize {
1058                v.0 as usize
1059            }
1060        }
1061
1062        impl From<u32> for $name {
1063            #[inline]
1064            fn from(v: u32) -> $name {
1065                $name(v)
1066            }
1067        }
1068        impl From<usize> for $name {
1069            #[inline]
1070            fn from(v: usize) -> $name {
1071                debug_assert!(v <= u32::MAX as usize, "{} overflow: {v} > u32::MAX", stringify!($name));
1072                $name(v as u32)
1073            }
1074        }
1075
1076        // ----- Display -----
1077        impl ::core::fmt::Display for $name {
1078            #[inline]
1079            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
1080                write!(f, "{}", self.0)
1081            }
1082        }
1083
1084        // ===== Arithmetic (same type) =====
1085        impl ::core::ops::Add for $name {
1086            type Output = $name;
1087            #[inline]
1088            fn add(self, rhs: $name) -> $name {
1089                $name(self.0.saturating_add(rhs.0))
1090            }
1091        }
1092        impl ::core::ops::Sub for $name {
1093            type Output = $name;
1094            #[inline]
1095            fn sub(self, rhs: $name) -> $name {
1096                $name(self.0.saturating_sub(rhs.0))
1097            }
1098        }
1099        impl ::core::ops::Mul for $name {
1100            type Output = $name;
1101            #[inline]
1102            fn mul(self, rhs: $name) -> $name {
1103                $name(self.0.saturating_mul(rhs.0))
1104            }
1105        }
1106
1107        // ===== Arithmetic (with u32) =====
1108        impl ::core::ops::Add<u32> for $name {
1109            type Output = $name;
1110            #[inline]
1111            fn add(self, rhs: u32) -> $name {
1112                $name(self.0.saturating_add(rhs))
1113            }
1114        }
1115        impl ::core::ops::Sub<u32> for $name {
1116            type Output = $name;
1117            #[inline]
1118            fn sub(self, rhs: u32) -> $name {
1119                $name(self.0.saturating_sub(rhs))
1120            }
1121        }
1122        impl ::core::ops::Mul<u32> for $name {
1123            type Output = $name;
1124            #[inline]
1125            fn mul(self, rhs: u32) -> $name {
1126                $name(self.0.saturating_mul(rhs))
1127            }
1128        }
1129
1130        impl $name {
1131            #[inline]
1132            pub const fn as_u32(self) -> u32 {
1133                self.0
1134            }
1135            #[inline]
1136            pub const fn as_usize(self) -> usize {
1137                self.0 as usize
1138            }
1139
1140            #[inline]
1141            pub fn div_ceil<T: Into<u32>>(self, rhs: T) -> u32 {
1142                self.0.div_ceil(rhs.into())
1143            }
1144        }
1145
1146        // Optional symmetric forms: u32 (+|-|*) $name -> $name
1147        impl ::core::ops::Add<$name> for u32 {
1148            type Output = $name;
1149            #[inline]
1150            fn add(self, rhs: $name) -> $name {
1151                $name(self.saturating_add(rhs.0))
1152            }
1153        }
1154        impl ::core::ops::Sub<$name> for u32 {
1155            type Output = $name;
1156            #[inline]
1157            fn sub(self, rhs: $name) -> $name {
1158                $name(self.saturating_sub(rhs.0))
1159            }
1160        }
1161        impl ::core::ops::Mul<$name> for u32 {
1162            type Output = $name;
1163            #[inline]
1164            fn mul(self, rhs: $name) -> $name {
1165                $name(self.saturating_mul(rhs.0))
1166            }
1167        }
1168
1169        // ===== Cross-type comparisons with u32 (both directions) =====
1170        impl ::core::cmp::PartialEq<u32> for $name {
1171            #[inline]
1172            fn eq(&self, other: &u32) -> bool {
1173                self.0 == *other
1174            }
1175        }
1176        impl ::core::cmp::PartialEq<$name> for u32 {
1177            #[inline]
1178            fn eq(&self, other: &$name) -> bool {
1179                *self == other.0
1180            }
1181        }
1182
1183        impl ::core::cmp::PartialOrd<u32> for $name {
1184            #[inline]
1185            fn partial_cmp(&self, other: &u32) -> Option<::core::cmp::Ordering> {
1186                self.0.partial_cmp(other)
1187            }
1188        }
1189        impl ::core::cmp::PartialOrd<$name> for u32 {
1190            #[inline]
1191            fn partial_cmp(&self, other: &$name) -> Option<::core::cmp::Ordering> {
1192                self.partial_cmp(&other.0)
1193            }
1194        }
1195    };
1196}
1197
1198newtype_u32!(
1199    /// Ring polynomial degree *N* (always a power of two).
1200    ///
1201    /// Wraps a [`u32`] with saturating arithmetic.
1202    Degree
1203);
1204
1205newtype_u32!(
1206    /// Torus precision in bits — the total number of significant bits
1207    /// used to represent elements of the discretised torus *T = R/Z*.
1208    ///
1209    /// Wraps a [`u32`] with saturating arithmetic.
1210    TorusPrecision
1211);
1212
1213newtype_u32!(
1214    /// Base-2 logarithm of the limb radix in the digit (CRT)
1215    /// decomposition of ciphertext coefficients.
1216    ///
1217    /// Coefficients are stored as `ceil(k / base2k)` limbs, each
1218    /// carrying `base2k` bits of precision.
1219    ///
1220    /// Wraps a [`u32`] with saturating arithmetic.
1221    Base2K
1222);
1223
1224newtype_u32!(
1225    /// Number of gadget-decomposition digits used in GGLWE / GGSW
1226    /// ciphertexts.
1227    ///
1228    /// Wraps a [`u32`] with saturating arithmetic.
1229    Dnum
1230);
1231
1232newtype_u32!(
1233    /// GLWE rank — the number of mask polynomials in a GLWE ciphertext.
1234    ///
1235    /// A rank-0 GLWE is equivalent to a plain LWE ciphertext.
1236    ///
1237    /// Wraps a [`u32`] with saturating arithmetic.
1238    Rank
1239);
1240
1241newtype_u32!(
1242    /// Size (in limbs) of each gadget-decomposition digit.
1243    ///
1244    /// Wraps a [`u32`] with saturating arithmetic.
1245    Dsize
1246);
1247
1248impl Degree {
1249    /// Returns log2(n). Assumes n is a positive power of two.
1250    pub fn log2(&self) -> usize {
1251        debug_assert!(
1252            self.0 > 0 && self.0.is_power_of_two(),
1253            "Degree::log2 requires a positive power of two, got {}",
1254            self.0
1255        );
1256        self.0.trailing_zeros() as usize
1257    }
1258}
1259
1260/// Total torus precision of a key: `dnum * dsize * base2k + k_aux`, i.e. the
1261/// gadget precision (see [`GGLWELayout::gadget_k`]) plus the auxiliary guard
1262/// region `k_aux`.
1263/// This is what `LWEInfos::k()` reports for the GGLWE/GGSW key families, so
1264/// encryption places the error at the physical bottom limb.
1265pub fn key_k(base2k: Base2K, dnum: Dnum, dsize: Dsize, k_aux: TorusPrecision) -> TorusPrecision {
1266    debug_assert!(
1267        k_aux.0 >= dsize.0 * base2k.0,
1268        "k_aux ({}) must be at least dsize * base2k ({} * {} = {}): the auxiliary guard must \
1269         cover at least one full gadget digit, otherwise operations truncate mid-digit",
1270        k_aux.0,
1271        dsize.0,
1272        base2k.0,
1273        dsize.0 * base2k.0
1274    );
1275    TorusPrecision(dnum.0 * dsize.0 * base2k.0 + k_aux.0)
1276}
1277
1278/// Number of limbs stored per row of a key: `ceil((dnum*dsize*base2k + k_aux) / base2k)`,
1279/// i.e. the `dnum * dsize` gadget limbs plus `ceil(k_aux / base2k)` auxiliary
1280/// (guard) limbs used for noise management.
1281pub fn key_size(base2k: Base2K, dnum: Dnum, dsize: Dsize, k_aux: TorusPrecision) -> usize {
1282    key_k(base2k, dnum, dsize, k_aux).0.div_ceil(base2k.0) as usize
1283}
1284
1285/// Number of key limbs an operation should process for an input ciphertext of
1286/// precision `input_k` (clamped to the key size by callers). Replaces the old
1287/// explicit `key_size`/`tsk_size` argument.
1288///
1289/// The gadget is consumed in whole digits of `dsize` limbs each, so the region
1290/// covering `input_k` must be rounded up to a digit boundary — truncating
1291/// mid-digit would drop a gadget row entirely and wreck the decomposition.
1292/// The auxiliary guard `k_aux` then adds `ceil(k_aux / base2k)` limbs on top:
1293///
1294/// ```text
1295/// digits = ceil(input_k / (dsize * base2k))
1296/// work   = digits * dsize + ceil(k_aux / base2k)
1297/// ```
1298pub fn key_work_size(base2k: Base2K, input_k: TorusPrecision, dsize: Dsize, k_aux: TorusPrecision) -> usize {
1299    let digits: u32 = GGLWELayout::dnum_for_input(base2k, input_k, dsize).0;
1300    (digits * dsize.0 + k_aux.0.div_ceil(base2k.0)) as usize
1301}
1302
1303/// Inputs used to size the key region materialized for a gadget product.
1304pub(crate) struct GadgetProductOutputSizeParams {
1305    pub(crate) key_size: usize,
1306    pub(crate) key_base2k: Base2K,
1307    pub(crate) input_k: TorusPrecision,
1308    pub(crate) output_k: TorusPrecision,
1309    pub(crate) dsize: Dsize,
1310    pub(crate) k_aux: TorusPrecision,
1311    pub(crate) product_terms: usize,
1312    pub(crate) extra_live_limbs: usize,
1313}
1314
1315/// Worst-case radix limbs occupied by one coefficient of a gadget product.
1316///
1317/// Each elementary coefficient product contributes two limbs. Accumulating
1318/// `product_terms` such products adds `ceil(log2(product_terms))` bits.
1319pub(crate) fn gadget_product_limbs(key_base2k: Base2K, product_terms: usize) -> usize {
1320    let base2k = key_base2k.as_usize();
1321    let accumulation_bits = if product_terms <= 1 {
1322        0
1323    } else {
1324        usize::BITS as usize - (product_terms - 1).leading_zeros() as usize
1325    };
1326    base2k.saturating_mul(2).saturating_add(accumulation_bits).div_ceil(base2k)
1327}
1328
1329/// Sizes the key region materialized for a gadget product and its immediate
1330/// normalization.
1331///
1332/// All lower limbs can, in principle, affect the rounded result through a
1333/// sufficiently long carry chain. This keeps a conservative practical window:
1334/// the live input/output precision, converted to the key radix, plus the
1335/// worst-case limb growth of the signed polynomial products accumulated into
1336/// one coefficient. The window is backend-independent: a dropped limb sits
1337/// below the output precision by construction, and an approximate transform's
1338/// rounding error scales with each limb's own magnitude, so it neither
1339/// reaches the retained limbs nor grows by dropping the lower ones.
1340pub(crate) fn gadget_product_output_size(params: GadgetProductOutputSizeParams) -> usize {
1341    let GadgetProductOutputSizeParams {
1342        key_size,
1343        key_base2k,
1344        input_k,
1345        output_k,
1346        dsize,
1347        k_aux,
1348        product_terms,
1349        extra_live_limbs,
1350    } = params;
1351    let work_size = key_size.min(key_work_size(key_base2k, input_k, dsize, k_aux));
1352
1353    let base2k = key_base2k.as_usize();
1354    let live_limbs = input_k
1355        .as_usize()
1356        .max(output_k.as_usize())
1357        .div_ceil(base2k)
1358        .saturating_add(extra_live_limbs);
1359    let product_limbs = gadget_product_limbs(key_base2k, product_terms);
1360    work_size.min(live_limbs.saturating_add(product_limbs))
1361}
1362
1363#[cfg(test)]
1364mod gadget_sizing_tests {
1365    use super::*;
1366
1367    #[test]
1368    fn dnum_for_input_is_the_inverse_of_gadget_k() {
1369        let (base2k, dsize) = (Base2K(52), Dsize(4));
1370        let digit = dsize.0 * base2k.0;
1371        let dnum_for = |input_k: u32| GGLWELayout::dnum_for_input(base2k, TorusPrecision(input_k), dsize);
1372        // A partial digit counts as a full one, an exact multiple does not add one.
1373        assert_eq!(dnum_for(1404), Dnum(7));
1374        assert_eq!(dnum_for(7 * digit), Dnum(7));
1375        assert_eq!(dnum_for(7 * digit + 1), Dnum(8));
1376        assert_eq!(dnum_for(1), Dnum(1));
1377        for dnum in 1..10 {
1378            let k_aux = TorusPrecision(digit + 16);
1379            let layout = GGLWELayout {
1380                n: Degree(16),
1381                base2k,
1382                dnum: Dnum(dnum),
1383                k_aux,
1384                rank_in: Rank(1),
1385                rank_out: Rank(1),
1386                dsize,
1387                stride: 1,
1388            };
1389            let gadget = layout.gadget_k();
1390            assert_eq!(gadget.0, dnum * digit);
1391            assert_eq!(dnum_for(gadget.0), Dnum(dnum));
1392            assert_eq!(key_k(base2k, Dnum(dnum), dsize, k_aux).0, gadget.0 + k_aux.0);
1393            assert_eq!(
1394                key_work_size(base2k, gadget, dsize, k_aux),
1395                (dnum * dsize.0 + k_aux.0.div_ceil(base2k.0)) as usize
1396            );
1397        }
1398    }
1399
1400    #[test]
1401    fn work_size_rounds_input_to_a_whole_digit_before_aux_limbs() {
1402        // Two 2-limb digits plus two auxiliary limbs.
1403        assert_eq!(key_work_size(Base2K(30), TorusPrecision(61), Dsize(2), TorusPrecision(31)), 6);
1404    }
1405
1406    #[test]
1407    fn output_size_converts_precision_to_the_key_radix_and_caps_at_work_size() {
1408        assert_eq!(
1409            gadget_product_output_size(GadgetProductOutputSizeParams {
1410                key_size: 12,
1411                key_base2k: Base2K(30),
1412                input_k: TorusPrecision(61),
1413                output_k: TorusPrecision(91),
1414                dsize: Dsize(2),
1415                k_aux: TorusPrecision(31),
1416                product_terms: 1,
1417                extra_live_limbs: 0,
1418            }),
1419            6
1420        );
1421    }
1422
1423    #[test]
1424    fn output_size_accounts_for_polynomial_accumulation_growth() {
1425        assert_eq!(
1426            gadget_product_output_size(GadgetProductOutputSizeParams {
1427                key_size: 12,
1428                key_base2k: Base2K(30),
1429                input_k: TorusPrecision(60),
1430                output_k: TorusPrecision(90),
1431                dsize: Dsize(1),
1432                k_aux: TorusPrecision(180),
1433                product_terms: 1,
1434                extra_live_limbs: 0,
1435            }),
1436            5
1437        );
1438        assert_eq!(
1439            gadget_product_output_size(GadgetProductOutputSizeParams {
1440                key_size: 12,
1441                key_base2k: Base2K(30),
1442                input_k: TorusPrecision(60),
1443                output_k: TorusPrecision(90),
1444                dsize: Dsize(1),
1445                k_aux: TorusPrecision(180),
1446                product_terms: 1 << 16,
1447                extra_live_limbs: 0,
1448            }),
1449            6
1450        );
1451    }
1452
1453    #[test]
1454    fn product_limbs_include_coefficient_accumulation_growth() {
1455        assert_eq!(gadget_product_limbs(Base2K(30), 1), 2);
1456        assert_eq!(gadget_product_limbs(Base2K(30), 1 << 16), 3);
1457        assert_eq!(gadget_product_limbs(Base2K(19), 1 << 20), 4);
1458    }
1459}