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, ¢er, &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, ¬_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, ¬_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, ¬_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}