qfall_math/integer/mat_z/sample/discrete_gauss.rs
1// Copyright © 2023 Niklas Siemer
2//
3// This file is part of qFALL-math.
4//
5// qFALL-math 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//! This module contains algorithms for sampling according to the discrete Gaussian distribution.
10
11use crate::{
12 error::MathError,
13 integer::MatZ,
14 rational::{MatQ, Q},
15 traits::{MatrixDimensions, MatrixSetEntry},
16 utils::sample::discrete_gauss::{
17 DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT, sample_d,
18 sample_d_precomputed_gso,
19 },
20};
21use std::fmt::Display;
22
23impl MatZ {
24 /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry
25 /// sampled independently according to the discrete Gaussian distribution,
26 /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
27 ///
28 /// Parameters:
29 /// - `num_rows`: specifies the number of rows the new matrix should have
30 /// - `num_cols`: specifies the number of columns the new matrix should have
31 /// - `center`: specifies the positions of the center with peak probability
32 /// - `s`: specifies the Gaussian parameter, which is proportional
33 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
34 ///
35 /// Returns a matrix with each entry sampled independently from the
36 /// specified discrete Gaussian distribution or an error if `s < 0`.
37 ///
38 /// # Examples
39 /// ```
40 /// use qfall_math::integer::MatZ;
41 ///
42 /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap();
43 /// ```
44 ///
45 /// # Errors and Failures
46 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
47 /// if `s < 0`.
48 ///
49 /// # Panics ...
50 /// - if the provided number of rows and columns are not suited to create a matrix.
51 /// For further information see [`MatZ::new`].
52 pub fn sample_discrete_gauss(
53 num_rows: impl TryInto<i64> + Display,
54 num_cols: impl TryInto<i64> + Display,
55 center: impl Into<Q>,
56 s: impl Into<Q>,
57 ) -> Result<MatZ, MathError> {
58 let mut out = Self::new(num_rows, num_cols);
59
60 let mut dgis = DiscreteGaussianIntegerSampler::init(
61 center,
62 s,
63 unsafe { TAILCUT },
64 LookupTableSetting::FillOnTheFly,
65 )?;
66
67 for row in 0..out.get_num_rows() {
68 for col in 0..out.get_num_columns() {
69 let sample = dgis.sample_z();
70 unsafe { out.set_entry_unchecked(row, col, sample) };
71 }
72 }
73
74 Ok(out)
75 }
76
77 /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
78 ///
79 /// We do not check whether `basis` is actually a basis. Hence, the callee is
80 /// responsible for making sure that `basis` provides a suitable basis.
81 ///
82 /// Parameters:
83 /// - `basis`: specifies a basis for the lattice from which is sampled
84 /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples
85 /// - `center`: specifies the positions of the center with peak probability
86 /// - `s`: specifies the Gaussian parameter, which is proportional
87 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
88 ///
89 /// Returns a lattice vector sampled according to the discrete Gaussian distribution
90 /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
91 /// or if `center` is not a column vector.
92 ///
93 /// # Examples
94 /// ```
95 /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}};
96 /// let basis = MatZ::identity(5, 5);
97 /// let center = MatQ::new(5, 1);
98 ///
99 /// let sample = MatZ::sample_d(&basis, ¢er, 1.25f32).unwrap();
100 /// ```
101 ///
102 /// # Errors and Failures
103 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
104 /// if `s < 0`.
105 /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
106 /// if the number of rows of the `basis` and `center` differ.
107 /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
108 /// if `center` is not a column vector.
109 ///
110 /// This function implements SampleD according to:
111 /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
112 /// Trapdoors for hard lattices and new cryptographic constructions.
113 /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
114 /// <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
115 pub fn sample_d(basis: &MatZ, center: &MatQ, s: impl Into<Q>) -> Result<Self, MathError> {
116 let s: Q = s.into();
117
118 sample_d(basis, center, &s)
119 }
120
121 /// Samples a non-spherical discrete Gaussian depending on your choice of
122 /// `sigma_sqrt` using the standard basis and center `0`.
123 ///
124 /// Parameters:
125 /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix
126 /// with which the *intermediate* continuous Gaussian is sampled before
127 /// the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)`
128 /// where sigma is the target covariance matrix. The root can be computed using
129 /// the [`MatQ::cholesky_decomposition`].
130 /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`].
131 ///
132 /// Returns a lattice vector sampled according to the discrete Gaussian distribution.
133 ///
134 /// # Examples
135 /// ```
136 /// use qfall_math::integer::MatZ;
137 /// use qfall_math::rational::{Q, MatQ};
138 /// use std::str::FromStr;
139 /// use crate::qfall_math::traits::Pow;
140 ///
141 /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap();
142 /// let r = Q::from(4);
143 ///
144 /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2);
145 ///
146 /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap();
147 /// ```
148 ///
149 /// # Errors and Failures
150 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
151 /// if the `r < 0`.
152 /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix)
153 /// if the matrix is not symmetric.
154 ///
155 /// This function implements SampleD according to Algorithm 1. in \[2\].
156 /// - \[2\] Peikert, Chris.
157 /// "An efficient and parallel Gaussian sampler for lattices.
158 /// In Annual Cryptology Conference, pp. 80-97. Berlin, Heidelberg: Springer
159 /// Berlin Heidelberg, 2010.
160 /// <https://link.springer.com/chapter/10.1007/978-3-642-14623-7_5>
161 pub fn sample_d_common_non_spherical(
162 sigma_sqrt: &MatQ,
163 r: impl Into<Q>,
164 ) -> Result<Self, MathError> {
165 if !sigma_sqrt.is_square() {
166 return Err(MathError::NoSquareMatrix("The covariance matrix has to be square, otherwise no discrete Gaussian can be defined.".to_string()));
167 }
168 let r = r.into();
169
170 // sample a continuous Gaussian centered around `0` in every dimension with
171 // gaussian parameter `1`.
172 let d_1 = MatQ::sample_gauss_same_center(sigma_sqrt.get_num_columns(), 1, 0, 1)?;
173
174 // compute a continuous Gaussian centered around `0` in every dimension with
175 // covariance matrix `b_2` (the cholesky decomposition we computed)
176 let x_2 = sigma_sqrt * d_1;
177
178 // perform randomized rounding
179 x_2.randomized_rounding(r)
180 }
181
182 /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
183 ///
184 /// We do not check whether `basis` is actually a basis or whether `basis_gso` is
185 /// actually the gso of `basis`. Hence, the callee is responsible for making sure
186 /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO.
187 ///
188 /// Parameters:
189 /// - `basis`: specifies a basis for the lattice from which is sampled
190 /// - `basis_gso`: specifies the precomputed gso for `basis`
191 /// - `center`: specifies the positions of the center with peak probability
192 /// - `s`: specifies the Gaussian parameter, which is proportional
193 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
194 ///
195 /// Returns a lattice vector sampled according to the discrete Gaussian distribution
196 /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ,
197 /// or if `center` is not a column vector.
198 ///
199 /// # Examples
200 /// ```
201 /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}};
202 /// let basis = MatZ::identity(5, 5);
203 /// let center = MatQ::new(5, 1);
204 /// let basis_gso = MatQ::from(&basis).gso();
205 ///
206 /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap();
207 /// ```
208 ///
209 /// # Errors and Failures
210 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
211 /// if `s < 0`.
212 /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
213 /// if the number of rows of the `basis` and `center` differ.
214 /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError)
215 /// if `center` is not a column vector.
216 ///
217 /// # Panics ...
218 /// - if the number of rows/columns of `basis_gso` and `basis` mismatch.
219 ///
220 /// This function implements SampleD according to:
221 /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
222 /// Trapdoors for hard lattices and new cryptographic constructions.
223 /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
224 /// <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
225 pub fn sample_d_precomputed_gso(
226 basis: &MatZ,
227 basis_gso: &MatQ,
228 center: &MatQ,
229 s: impl Into<Q>,
230 ) -> Result<Self, MathError> {
231 let s: Q = s.into();
232
233 sample_d_precomputed_gso(basis, basis_gso, center, &s)
234 }
235}
236
237#[cfg(test)]
238mod test_sample_discrete_gauss {
239 use crate::{
240 integer::{MatZ, Z},
241 rational::Q,
242 };
243
244 // This function only allows for a broader availability, which is tested here.
245
246 /// Checks whether `sample_discrete_gauss` is available for all types
247 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
248 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
249 #[test]
250 fn availability() {
251 let n = Z::from(1024);
252 let center = Q::ZERO;
253 let s = Q::ONE;
254
255 let _ = MatZ::sample_discrete_gauss(2u64, 3i8, ¢er, 1u16);
256 let _ = MatZ::sample_discrete_gauss(3u8, 2i16, ¢er, 1u8);
257 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u32);
258 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u64);
259 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64);
260 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i32);
261 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i16);
262 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i8);
263 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64);
264 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &n);
265 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &s);
266 let _ = MatZ::sample_discrete_gauss(1, 1, 1, &s);
267 let _ = MatZ::sample_discrete_gauss(1, 1, 2.25, &s);
268 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1.25f64);
269 let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 15.75f32);
270 }
271}
272
273#[cfg(test)]
274mod test_sample_d {
275 use crate::{
276 integer::{MatZ, Z},
277 rational::{MatQ, Q},
278 };
279
280 // Appropriate inputs were tested in utils and thus omitted here.
281 // This function only allows for a broader availability, which is tested here.
282
283 /// Checks whether `sample_d` is available for all types
284 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
285 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
286 #[test]
287 fn availability() {
288 let basis = MatZ::identity(5, 5);
289 let n = Z::from(1024);
290 let center = MatQ::new(5, 1);
291 let s = Q::ONE;
292
293 let _ = MatZ::sample_d(&basis, ¢er, 1u16);
294 let _ = MatZ::sample_d(&basis, ¢er, 1u8);
295 let _ = MatZ::sample_d(&basis, ¢er, 1u32);
296 let _ = MatZ::sample_d(&basis, ¢er, 1u64);
297 let _ = MatZ::sample_d(&basis, ¢er, 1i64);
298 let _ = MatZ::sample_d(&basis, ¢er, 1i32);
299 let _ = MatZ::sample_d(&basis, ¢er, 1i16);
300 let _ = MatZ::sample_d(&basis, ¢er, 1i8);
301 let _ = MatZ::sample_d(&basis, ¢er, 1i64);
302 let _ = MatZ::sample_d(&basis, ¢er, &n);
303 let _ = MatZ::sample_d(&basis, ¢er, &s);
304 let _ = MatZ::sample_d(&basis, ¢er, 1.25f64);
305 let _ = MatZ::sample_d(&basis, ¢er, 15.75f32);
306 }
307
308 /// Checks whether `sample_d_precomputed_gso` is available for all types
309 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
310 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
311 #[test]
312 fn availability_prec_gso() {
313 let basis = MatZ::identity(5, 5);
314 let n = Z::from(1024);
315 let center = MatQ::new(5, 1);
316 let s = Q::ONE;
317 let basis_gso = MatQ::from(&basis);
318
319 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16);
320 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u8);
321 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u32);
322 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u64);
323 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64);
324 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i32);
325 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i16);
326 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i8);
327 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64);
328 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &n);
329 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &s);
330 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f64);
331 let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32);
332 }
333}
334
335#[cfg(test)]
336mod test_sample_d_common_non_spherical {
337 use crate::{
338 integer::{MatZ, Z},
339 rational::{MatQ, Q},
340 traits::{MatrixDimensions, Pow},
341 };
342 use std::str::FromStr;
343
344 /// Checks whether `sample_d_common_non_spherical` is available for all types
345 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
346 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
347 /// or [`Into<MatQ>`], i.e. MatQ, MatZ
348 #[test]
349 fn availability() {
350 let r = Q::from(8);
351 let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap();
352 let covariance_matrix =
353 (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition();
354
355 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).unwrap();
356
357 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u16).unwrap();
358 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u32).unwrap();
359 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u64).unwrap();
360 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i8).unwrap();
361 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i16).unwrap();
362 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i32).unwrap();
363 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i64).unwrap();
364 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Q::from(8)).unwrap();
365 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Z::from(8)).unwrap();
366 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f32).unwrap();
367 let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f64).unwrap();
368 }
369
370 /// Checks whether the function panics if a non-square matrix is provided.
371 /// anymore
372 #[test]
373 fn not_square() {
374 let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap();
375
376 assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).is_err());
377 }
378
379 /// Checks whether the function returns an error if `r` is too small.
380 #[test]
381 fn too_small_parameters() {
382 let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap();
383
384 assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, -1).is_err());
385 }
386
387 /// Checks whether the dimension of the output matches the provided covariance matrix
388 #[test]
389 fn correct_dimensions() {
390 let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap();
391 let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap();
392
393 let sample_1 = MatZ::sample_d_common_non_spherical(&covariance_matrix_1, 8).unwrap();
394 let sample_2 = MatZ::sample_d_common_non_spherical(&covariance_matrix_2, 8).unwrap();
395
396 assert_eq!(2, sample_1.get_num_rows());
397 assert!(sample_1.is_column_vector());
398 assert_eq!(3, sample_2.get_num_rows());
399 assert!(sample_2.is_column_vector());
400 }
401}