qfall_tools/primitive/psf/
gpv.rs

1// Copyright © 2023 Marvin Beckmann
2//
3// This file is part of qFALL-tools.
4//
5// qFALL-tools is free software: you can redistribute it and/or modify it under
6// the terms of the Mozilla Public License Version 2.0 as published by the
7// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>.
8
9//! Implements a GPV [`PSF`] according to [\[1\]](<../index.html#:~:text=[1]>)
10//! using G-Trapdoors to generate a short basis trapdoors.
11
12use super::PSF;
13use crate::sample::g_trapdoor::{
14    gadget_classical::gen_trapdoor, gadget_parameters::GadgetParameters,
15    short_basis_classical::gen_short_basis_for_trapdoor,
16};
17use qfall_math::{
18    integer::MatZ,
19    integer_mod_q::MatZq,
20    rational::{MatQ, Q},
21    traits::{MatrixDimensions, Pow},
22};
23use serde::{Deserialize, Serialize};
24
25/// A lattice-based implementation of a [`PSF`] according to
26/// [\[1\]](<index.html#:~:text=[1]>) using
27/// G-Trapdoors where D_n = {e ∈ Z^m | |e| <= s sqrt(m)}
28/// and R_n = Z_q^n.
29///
30/// Attributes
31/// - `gp`: Describes the gadget parameters with which the G-Trapdoor is generated
32/// - `s`: The Gaussian parameter with which is sampled
33///
34/// # Examples
35/// ```
36/// use qfall_tools::primitive::psf::PSFGPV;
37/// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
38/// use qfall_math::rational::Q;
39/// use qfall_tools::primitive::psf::PSF;
40///
41/// let psf = PSFGPV {
42///     gp: GadgetParameters::init_default(8, 64),
43///     s: Q::from(12),
44/// };
45///
46/// let (a, td) = psf.trap_gen();
47/// let domain_sample = psf.samp_d();
48/// let range_fa = psf.f_a(&a, &domain_sample);
49/// let preimage = psf.samp_p(&a, &td, &range_fa);
50///
51/// assert!(psf.check_domain(&preimage));
52/// ```
53#[derive(Serialize, Deserialize)]
54pub struct PSFGPV {
55    pub gp: GadgetParameters,
56    pub s: Q,
57}
58
59impl PSF for PSFGPV {
60    type A = MatZq;
61    type Trapdoor = (MatZ, MatQ);
62    type Domain = MatZ;
63    type Range = MatZq;
64
65    /// Computes a G-Trapdoor according to the [`GadgetParameters`]
66    /// defined in the struct.
67    /// It returns a matrix `A` together with a short base and its GSO.
68    ///
69    /// # Examples
70    /// ```
71    /// use qfall_tools::primitive::psf::PSFGPV;
72    /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
73    /// use qfall_math::rational::Q;
74    /// use qfall_tools::primitive::psf::PSF;
75    ///
76    /// let psf = PSFGPV {
77    ///     gp: GadgetParameters::init_default(8, 64),
78    ///     s: Q::from(12),
79    /// };
80    ///
81    /// let (a, (sh_b, sh_b_gso)) = psf.trap_gen();
82    /// ```
83    fn trap_gen(&self) -> (MatZq, (MatZ, MatQ)) {
84        let a_bar = MatZq::sample_uniform(&self.gp.n, &self.gp.m_bar, &self.gp.q);
85
86        let tag = MatZq::identity(&self.gp.n, &self.gp.n, &self.gp.q);
87
88        let (a, r) = gen_trapdoor(&self.gp, &a_bar, &tag).unwrap();
89
90        let short_base = gen_short_basis_for_trapdoor(&self.gp, &tag, &a, &r);
91        let short_base_gso = MatQ::from(&short_base).gso();
92
93        (a, (short_base, short_base_gso))
94    }
95
96    /// Samples in the domain using SampleD with the standard basis and center `0`.
97    ///
98    /// # Examples
99    /// ```
100    /// use qfall_tools::primitive::psf::PSFGPV;
101    /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
102    /// use qfall_math::rational::Q;
103    /// use qfall_tools::primitive::psf::PSF;
104    ///
105    /// let psf = PSFGPV {
106    ///     gp: GadgetParameters::init_default(8, 64),
107    ///     s: Q::from(12),
108    /// };
109    /// let (a, td) = psf.trap_gen();
110    ///
111    /// let domain_sample = psf.samp_d();
112    /// ```
113    fn samp_d(&self) -> MatZ {
114        let m = &self.gp.n * &self.gp.k + &self.gp.m_bar;
115        MatZ::sample_discrete_gauss(&m, 1, 0, &self.s).unwrap()
116    }
117
118    /// Samples an `e` in the domain using SampleD with a short basis that is generated
119    /// from the G-Trapdoor from the conditioned conditioned
120    /// discrete Gaussian with `f_a(a,e) = u` for a provided syndrome `u`.
121    ///
122    /// *Note*: the provided parameters `a,r,u` must fit together,
123    /// otherwise unexpected behavior such as panics may occur.
124    ///
125    /// Parameters:
126    /// - `a`: The parity-check matrix
127    /// - `short_base`: The short base for `Λ^⟂(A)`
128    /// - `short_base_gso`: The precomputed GSO of the short_base
129    /// - `u`: The syndrome from the range
130    ///
131    /// Returns a sample `e` from the domain on the conditioned discrete
132    /// Gaussian distribution `f_a(a,e) = u`.
133    ///
134    /// # Examples
135    /// ```
136    /// use qfall_tools::primitive::psf::PSFGPV;
137    /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
138    /// use qfall_math::rational::Q;
139    /// use qfall_tools::primitive::psf::PSF;
140    ///
141    /// let psf = PSFGPV {
142    ///     gp: GadgetParameters::init_default(8, 64),
143    ///     s: Q::from(12),
144    /// };
145    /// let (a, td) = psf.trap_gen();
146    /// let domain_sample = psf.samp_d();
147    /// let range_fa = psf.f_a(&a, &domain_sample);
148    ///
149    /// let preimage = psf.samp_p(&a, &td, &range_fa);
150    /// assert_eq!(range_fa, psf.f_a(&a, &preimage))
151    /// ```
152    fn samp_p(&self, a: &MatZq, (short_base, short_base_gso): &(MatZ, MatQ), u: &MatZq) -> MatZ {
153        let sol: MatZ = a
154            .solve_gaussian_elimination(u)
155            .unwrap()
156            .get_representative_least_nonnegative_residue();
157
158        let center = MatQ::from(&(-1 * &sol));
159
160        sol + MatZ::sample_d_precomputed_gso(short_base, short_base_gso, &center, &self.s).unwrap()
161    }
162
163    /// Implements the efficiently computable function `f_a` which here corresponds to
164    /// `a*sigma`. The sigma must be from the domain, i.e. D_n.
165    ///
166    /// Parameters:
167    /// - `a`: The parity-check matrix of dimensions `n x m`
168    /// - `sigma`: A column vector of length `m`
169    ///
170    /// Returns `a*sigma`
171    ///
172    /// # Examples
173    /// ```
174    /// use qfall_tools::primitive::psf::PSFGPV;
175    /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
176    /// use qfall_math::rational::Q;
177    /// use qfall_tools::primitive::psf::PSF;
178    ///
179    /// let psf = PSFGPV {
180    ///     gp: GadgetParameters::init_default(8, 64),
181    ///     s: Q::from(12),
182    /// };
183    /// let (a, td) = psf.trap_gen();
184    /// let domain_sample = psf.samp_d();
185    /// let range_fa = psf.f_a(&a, &domain_sample);
186    /// ```
187    ///
188    /// # Panics ...
189    /// - if `sigma` is not in the domain.
190    fn f_a(&self, a: &MatZq, sigma: &MatZ) -> MatZq {
191        assert!(self.check_domain(sigma));
192        a * sigma
193    }
194
195    /// Checks whether a value `sigma` is in D_n = {e ∈ Z^m | |e| <= s sqrt(m)}.
196    ///
197    /// Parameters:
198    /// - `sigma`: The value for which is checked, if it is in the domain
199    ///
200    /// Returns true, if `sigma` is in D_n.
201    ///
202    /// # Examples
203    /// ```
204    /// use qfall_tools::primitive::psf::PSF;
205    /// use qfall_tools::primitive::psf::PSFGPV;
206    /// use qfall_tools::sample::g_trapdoor::gadget_parameters::GadgetParameters;
207    /// use qfall_math::rational::Q;
208    ///
209    /// let psf = PSFGPV {
210    ///     gp: GadgetParameters::init_default(8, 64),
211    ///     s: Q::from(12),
212    /// };
213    /// let (a, td) = psf.trap_gen();
214    ///
215    /// let vector = psf.samp_d();
216    ///
217    /// assert!(psf.check_domain(&vector));
218    /// ```
219    fn check_domain(&self, sigma: &MatZ) -> bool {
220        let m = &self.gp.n * &self.gp.k + &self.gp.m_bar;
221        sigma.is_column_vector()
222            && m == sigma.get_num_rows()
223            && sigma.norm_eucl_sqrd().unwrap() <= self.s.pow(2).unwrap() * &m
224    }
225}
226
227#[cfg(test)]
228mod test_gpv_psf {
229    use super::super::gpv::PSFGPV;
230    use super::PSF;
231    use crate::sample::g_trapdoor::gadget_parameters::GadgetParameters;
232    use qfall_math::integer::MatZ;
233    use qfall_math::rational::Q;
234    use qfall_math::traits::*;
235
236    /// Ensures that `samp_d` actually computes values that are in D_n.
237    #[test]
238    fn samp_d_samples_from_dn() {
239        for (n, q) in [(5, 256), (10, 128), (15, 157)] {
240            let psf = PSFGPV {
241                gp: GadgetParameters::init_default(n, q),
242                s: Q::from(10),
243            };
244
245            for _ in 0..5 {
246                assert!(psf.check_domain(&psf.samp_d()));
247            }
248        }
249    }
250
251    /// Ensures that `samp_p` actually computes preimages that are also in the correct
252    /// domain.
253    #[test]
254    fn samp_p_preimage_and_domain() {
255        for (n, q) in [(5, 256), (6, 128)] {
256            let psf = PSFGPV {
257                gp: GadgetParameters::init_default(n, q),
258                s: Q::from(10),
259            };
260            let (a, r) = psf.trap_gen();
261            let domain_sample = psf.samp_d();
262            let range_fa = psf.f_a(&a, &domain_sample);
263
264            let preimage = psf.samp_p(&a, &r, &range_fa);
265            assert_eq!(range_fa, psf.f_a(&a, &preimage));
266            assert!(psf.check_domain(&preimage));
267        }
268    }
269
270    /// Ensures that `f_a` returns `a*sigma`.
271    #[test]
272    fn f_a_works_as_expected() {
273        for (n, q) in [(5, 256), (6, 128)] {
274            let psf = PSFGPV {
275                gp: GadgetParameters::init_default(n, q),
276                s: Q::from(10),
277            };
278            let (a, _) = psf.trap_gen();
279            let domain_sample = psf.samp_d();
280
281            assert_eq!(&a * &domain_sample, psf.f_a(&a, &domain_sample));
282        }
283    }
284
285    /// Ensures that `f_a` panics if a value is provided, that is not within the domain.
286    /// Sigma is not a vector.
287    #[test]
288    #[should_panic]
289    fn f_a_sigma_not_in_domain_matrix() {
290        let psf = PSFGPV {
291            gp: GadgetParameters::init_default(8, 128),
292            s: Q::from(10),
293        };
294        let (a, _) = psf.trap_gen();
295        let not_in_domain = MatZ::new(a.get_num_columns(), 2);
296
297        let _ = psf.f_a(&a, &not_in_domain);
298    }
299
300    /// Ensures that `f_a` panics if a value is provided, that is not within the domain.
301    /// Sigma is not of the correct length.
302    #[test]
303    #[should_panic]
304    fn f_a_sigma_not_in_domain_incorrect_length() {
305        let psf = PSFGPV {
306            gp: GadgetParameters::init_default(8, 128),
307            s: Q::from(10),
308        };
309        let (a, _) = psf.trap_gen();
310        let not_in_domain = MatZ::new(a.get_num_columns() - 1, 1);
311
312        let _ = psf.f_a(&a, &not_in_domain);
313    }
314
315    /// Ensures that `f_a` panics if a value is provided, that is not within the domain.
316    /// Sigma is too long.
317    #[test]
318    #[should_panic]
319    fn f_a_sigma_not_in_domain_too_long() {
320        let psf = PSFGPV {
321            gp: GadgetParameters::init_default(8, 128),
322            s: Q::from(10),
323        };
324        let (a, _) = psf.trap_gen();
325        let not_in_domain =
326            psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1);
327
328        let _ = psf.f_a(&a, &not_in_domain);
329    }
330
331    /// Ensures that `check_domain` works for vectors with the correct length.
332    #[test]
333    fn check_domain_as_expected() {
334        let psf = PSFGPV {
335            gp: GadgetParameters::init_default(8, 128),
336            s: Q::from(10),
337        };
338        let (a, _) = psf.trap_gen();
339        let value = psf.s.round();
340        let mut in_domain = MatZ::new(a.get_num_columns(), 1);
341        for i in 0..in_domain.get_num_rows() {
342            in_domain.set_entry(i, 0, &value).unwrap();
343        }
344
345        assert!(psf.check_domain(&MatZ::new(a.get_num_columns(), 1)));
346        assert!(psf.check_domain(&in_domain));
347    }
348
349    /// Ensures that `check_domain` returns false for values that are not in the domain.
350    #[test]
351    fn check_domain_not_in_dn() {
352        let psf = PSFGPV {
353            gp: GadgetParameters::init_default(8, 128),
354            s: Q::from(10),
355        };
356        let (a, _) = psf.trap_gen();
357
358        let matrix = MatZ::new(a.get_num_columns(), 2);
359        let too_short = MatZ::new(a.get_num_columns() - 1, 1);
360        let too_long = MatZ::new(a.get_num_columns() + 1, 1);
361        let entry_too_large =
362            psf.s.round() * a.get_num_columns() * MatZ::identity(a.get_num_columns(), 1);
363
364        assert!(!psf.check_domain(&matrix));
365        assert!(!psf.check_domain(&too_long));
366        assert!(!psf.check_domain(&too_short));
367        assert!(!psf.check_domain(&entry_too_large));
368    }
369}