Skip to main content

poulpy_core/layouts/
glwe_secret.rs

1use poulpy_hal::{
2    api::VecZnxAutomorphismBackend,
3    layouts::{
4        Backend, Data, HostDataMut, HostDataRef, Module, ScalarZnx, ScalarZnxAsVecZnxBackendMut, ScalarZnxToBackendMut,
5        ScalarZnxToBackendRef, TransferFrom, VecZnx, ZnxZero, scalar_znx_as_vec_znx_backend_ref_from_ref,
6    },
7    oep::HalVecZnxImpl,
8    source::Source,
9};
10
11use crate::{
12    GetDistribution,
13    api::ModuleTransfer,
14    dist::Distribution,
15    layouts::{Base2K, Degree, GLWEInfos, LWEInfos, Rank},
16};
17
18use super::{
19    ModuleCoreAlloc,
20    lwe_secret::{LWESecret, LWESecretToBackendRef},
21};
22
23#[derive(PartialEq, Eq, Copy, Clone, Debug)]
24pub struct GLWESecretLayout {
25    pub n: Degree,
26    pub rank: Rank,
27}
28
29impl LWEInfos for GLWESecretLayout {
30    fn base2k(&self) -> Base2K {
31        Base2K(0)
32    }
33
34    fn n(&self) -> Degree {
35        self.n
36    }
37
38    fn size(&self) -> usize {
39        1
40    }
41}
42impl GLWEInfos for GLWESecretLayout {
43    fn rank(&self) -> Rank {
44        self.rank
45    }
46}
47
48#[derive(PartialEq, Eq, Clone)]
49pub struct GLWESecret<D: Data> {
50    pub(crate) data: ScalarZnx<D>,
51    pub(crate) dist: Distribution,
52}
53
54pub type GLWESecretBackendRef<'a, BE> = GLWESecret<<BE as Backend>::BufRef<'a>>;
55pub type GLWESecretBackendMut<'a, BE> = GLWESecret<<BE as Backend>::BufMut<'a>>;
56
57impl<D: Data> LWEInfos for GLWESecret<D> {
58    fn base2k(&self) -> Base2K {
59        Base2K(0)
60    }
61
62    fn n(&self) -> Degree {
63        Degree(self.data.n() as u32)
64    }
65
66    fn size(&self) -> usize {
67        1
68    }
69}
70
71impl<D: Data> LWEInfos for &mut GLWESecret<D> {
72    fn base2k(&self) -> Base2K {
73        (**self).base2k()
74    }
75
76    fn n(&self) -> Degree {
77        (**self).n()
78    }
79
80    fn size(&self) -> usize {
81        (**self).size()
82    }
83}
84
85impl<D: Data> GetDistribution for GLWESecret<D> {
86    fn dist(&self) -> &Distribution {
87        &self.dist
88    }
89}
90
91impl<D: Data> GLWEInfos for GLWESecret<D> {
92    fn rank(&self) -> Rank {
93        Rank(self.data.cols() as u32)
94    }
95}
96
97impl<D: Data> GLWEInfos for &mut GLWESecret<D> {
98    fn rank(&self) -> Rank {
99        (**self).rank()
100    }
101}
102
103impl<D: HostDataRef> GLWESecret<D> {
104    /// Copies this secret's backing bytes into an owned buffer of
105    /// backend `To`, routing via host bytes.
106    pub fn to_backend<BE, To>(&self, dst: &Module<To>) -> GLWESecret<To::OwnedBuf>
107    where
108        BE: Backend<OwnedBuf = D>,
109        To: Backend,
110        To: TransferFrom<BE>,
111    {
112        dst.upload_glwe_secret(self)
113    }
114}
115
116impl<D: Data> GLWESecret<D> {
117    /// Zero-cost rename when both backends share the same `OwnedBuf`.
118    pub fn reinterpret<To>(self) -> GLWESecret<To::OwnedBuf>
119    where
120        To: Backend<OwnedBuf = D>,
121    {
122        let n = self.data.n();
123        let cols = self.data.cols();
124        let data = self.data.data;
125        GLWESecret {
126            data: ScalarZnx::from_data(data, n, cols),
127            dist: self.dist,
128        }
129    }
130}
131
132#[expect(
133    dead_code,
134    reason = "host-owned constructors are kept for serialization and host-only staging"
135)]
136impl GLWESecret<Vec<u8>> {
137    pub(crate) fn alloc_from_infos<A>(infos: &A) -> Self
138    where
139        A: GLWEInfos,
140    {
141        Self::alloc(infos.n(), infos.rank())
142    }
143
144    pub(crate) fn alloc(n: Degree, rank: Rank) -> Self {
145        GLWESecret {
146            data: ScalarZnx::from_data(
147                poulpy_hal::layouts::HostBytesBackend::alloc_bytes(ScalarZnx::<Vec<u8>>::bytes_of(n.into(), rank.into())),
148                n.into(),
149                rank.into(),
150            ),
151            dist: Distribution::NONE,
152        }
153    }
154
155    pub fn bytes_of_from_infos<A>(infos: &A) -> usize
156    where
157        A: GLWEInfos,
158    {
159        Self::bytes_of(infos.n(), infos.rank())
160    }
161
162    pub fn bytes_of(n: Degree, rank: Rank) -> usize {
163        ScalarZnx::bytes_of(n.into(), rank.into())
164    }
165}
166
167impl<D: HostDataMut> GLWESecret<D> {
168    pub fn fill_ternary_prob(&mut self, prob: f64, source: &mut Source) {
169        (0..self.rank().into()).for_each(|i| {
170            self.data.fill_ternary_prob(i, prob, source);
171        });
172        self.dist = Distribution::TernaryProb(prob);
173    }
174
175    pub fn fill_ternary_hw(&mut self, hw: usize, source: &mut Source) {
176        (0..self.rank().into()).for_each(|i| {
177            self.data.fill_ternary_hw(i, hw, source);
178        });
179        self.dist = Distribution::TernaryFixed(hw);
180    }
181
182    pub fn fill_binary_prob(&mut self, prob: f64, source: &mut Source) {
183        (0..self.rank().into()).for_each(|i| {
184            self.data.fill_binary_prob(i, prob, source);
185        });
186        self.dist = Distribution::BinaryProb(prob);
187    }
188
189    pub fn fill_binary_hw(&mut self, hw: usize, source: &mut Source) {
190        (0..self.rank().into()).for_each(|i| {
191            self.data.fill_binary_hw(i, hw, source);
192        });
193        self.dist = Distribution::BinaryFixed(hw);
194    }
195
196    pub fn fill_binary_block(&mut self, block_size: usize, source: &mut Source) {
197        (0..self.rank().into()).for_each(|i| {
198            self.data.fill_binary_block(i, block_size, source);
199        });
200        self.dist = Distribution::BinaryBlock(block_size);
201    }
202
203    pub fn fill_zero(&mut self) {
204        self.data.zero();
205        self.dist = Distribution::ZERO;
206    }
207}
208
209pub trait GLWESecretToBackendMut<BE: Backend>: GLWESecretToBackendRef<BE> {
210    fn to_backend_mut(&mut self) -> GLWESecretBackendMut<'_, BE>;
211}
212
213impl<BE: Backend> GLWESecretToBackendMut<BE> for GLWESecret<BE::OwnedBuf> {
214    fn to_backend_mut(&mut self) -> GLWESecretBackendMut<'_, BE> {
215        GLWESecret {
216            dist: self.dist,
217            data: <ScalarZnx<BE::OwnedBuf> as ScalarZnxToBackendMut<BE>>::to_backend_mut(&mut self.data),
218        }
219    }
220}
221
222impl<'b, BE: Backend + 'b> GLWESecretToBackendMut<BE> for &mut GLWESecret<BE::BufMut<'b>> {
223    fn to_backend_mut(&mut self) -> GLWESecretBackendMut<'_, BE> {
224        let n = self.data.n();
225        let cols = self.data.cols();
226        GLWESecret {
227            dist: self.dist,
228            data: ScalarZnx::from_data(BE::view_mut_ref(&mut self.data.data), n, cols),
229        }
230    }
231}
232
233pub trait GLWESecretToBackendRef<BE: Backend> {
234    fn to_backend_ref(&self) -> GLWESecretBackendRef<'_, BE>;
235}
236
237impl<BE: Backend> GLWESecretToBackendRef<BE> for GLWESecret<BE::OwnedBuf> {
238    fn to_backend_ref(&self) -> GLWESecretBackendRef<'_, BE> {
239        GLWESecret {
240            data: <ScalarZnx<BE::OwnedBuf> as ScalarZnxToBackendRef<BE>>::to_backend_ref(&self.data),
241            dist: self.dist,
242        }
243    }
244}
245
246impl<'b, BE: Backend + 'b> GLWESecretToBackendRef<BE> for &GLWESecret<BE::BufRef<'b>> {
247    fn to_backend_ref(&self) -> GLWESecretBackendRef<'_, BE> {
248        GLWESecret {
249            data: ScalarZnx::from_data(BE::view_ref(&self.data.data), self.data.n(), self.data.cols()),
250            dist: self.dist,
251        }
252    }
253}
254
255impl<'b, BE: Backend + 'b> GLWESecretToBackendRef<BE> for &mut GLWESecret<BE::BufMut<'b>> {
256    fn to_backend_ref(&self) -> GLWESecretBackendRef<'_, BE> {
257        GLWESecret {
258            data: ScalarZnx::from_data(BE::view_ref_mut(&self.data.data), self.data.n(), self.data.cols()),
259            dist: self.dist,
260        }
261    }
262}
263
264pub trait SecretConversion<B: Backend> {
265    /// Derives the associated rank-1 `GLWESecret` from a `LWESecret` by applying
266    /// the X → X⁻¹ automorphism (k = -1). The result is the GLWE polynomial key
267    /// whose ring product with a mask decrypts LWE ciphertexts produced by
268    /// `glwe_expand_lwe`.
269    fn glwe_secret_from_lwe_secret<S>(&self, src: &S) -> GLWESecret<B::OwnedBuf>
270    where
271        S: LWESecretToBackendRef<B>;
272
273    /// Derives the associated `LWESecret` from a `GLWESecret` by applying the
274    /// X → X⁻¹ automorphism (k = -1) to each rank component.
275    ///
276    /// For a GLWE secret of degree `n` and rank `r`, the result is an LWE secret
277    /// of degree `n * r`. Each rank component is automorphed independently and
278    /// written as one contiguous `n`-coefficient block in the output key. For
279    /// rank 1, this is the inverse of `glwe_secret_from_lwe_secret`: applying
280    /// both conversions recovers the original key.
281    ///
282    /// Distribution metadata is preserved from the source secret. In particular,
283    /// fixed-weight metadata denotes the advertised fixed-weight distribution of
284    /// the source key and is not multiplied by the rank during flattening.
285    fn lwe_secret_from_glwe_secret<S>(&self, src: &S) -> LWESecret<B::OwnedBuf>
286    where
287        S: GLWESecretToBackendRef<B>;
288}
289
290impl<B: Backend + HalVecZnxImpl<B>> SecretConversion<B> for Module<B> {
291    fn glwe_secret_from_lwe_secret<S>(&self, src: &S) -> GLWESecret<B::OwnedBuf>
292    where
293        S: LWESecretToBackendRef<B>,
294    {
295        let src = src.to_backend_ref();
296        assert_eq!(src.n().as_usize(), self.n(), "LWE secret degree must equal module degree");
297        let mut res = self.glwe_secret_alloc(Rank(1));
298        res.dist = src.dist;
299        {
300            let src_vec = scalar_znx_as_vec_znx_backend_ref_from_ref::<B>(&src.data);
301            let mut res_vec = <ScalarZnx<B::OwnedBuf> as ScalarZnxAsVecZnxBackendMut<B>>::as_vec_znx_backend_mut(&mut res.data);
302            self.vec_znx_automorphism_backend(-1, &mut res_vec, 0, &src_vec, 0);
303        }
304        res
305    }
306
307    fn lwe_secret_from_glwe_secret<S>(&self, src: &S) -> LWESecret<B::OwnedBuf>
308    where
309        S: GLWESecretToBackendRef<B>,
310    {
311        let src = src.to_backend_ref();
312        let n = self.n();
313        let rank: usize = src.rank().into();
314        assert_eq!(src.n().as_usize(), n, "GLWE secret degree must equal module degree");
315        let mut res = self.lwe_secret_alloc(Degree((n * rank) as u32));
316        res.dist = src.dist;
317        {
318            let src_vec = scalar_znx_as_vec_znx_backend_ref_from_ref::<B>(&src.data);
319            let res_as_backend = <ScalarZnx<B::OwnedBuf> as ScalarZnxToBackendMut<B>>::to_backend_mut(&mut res.data);
320            let mut res_vec = VecZnx::from_data(res_as_backend.data, n, rank, 1);
321            for j in 0..rank {
322                self.vec_znx_automorphism_backend(-1, &mut res_vec, j, &src_vec, j);
323            }
324        }
325        res
326    }
327}