qfall_math/integer/mat_poly_over_z/sample/discrete_gauss.rs
1// Copyright 2023 Marvin Beckmann
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::{MatPolyOverZ, MatZ, PolyOverZ},
14 macros::seeded::seedable_function,
15 rational::{PolyOverQ, Q},
16 traits::{
17 Concatenate, FromCoefficientEmbedding, IntoCoefficientEmbedding, MatrixDimensions,
18 MatrixSetEntry, SetCoefficient,
19 },
20 utils::{
21 index::evaluate_index,
22 sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
23 },
24};
25use std::fmt::Display;
26
27impl MatPolyOverZ {
28 seedable_function!(
29 /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry
30 /// sampled independently according to the discrete Gaussian distribution,
31 /// using [`PolyOverZ::sample_discrete_gauss`].
32 ///
33 /// Parameters:
34 /// - `num_rows`: specifies the number of rows the new matrix should have
35 /// - `num_cols`: specifies the number of columns the new matrix should have
36 /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have
37 /// - `center`: specifies the positions of the center with peak probability
38 /// - `s`: specifies the Gaussian parameter, which is proportional
39 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
40 #[seeded]
41 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
42 #[optionally_seeded]
43 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
44 #[optionally_seeded]
45 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
46 ///
47 /// Returns a [`MatPolyOverZ`] with each entry sampled independently from the
48 /// specified discrete Gaussian distribution or an error if `s < 0`.
49 ///
50 /// # Examples
51 /// ```
52 /// use qfall_math::integer::MatPolyOverZ;
53 ///
54 #[unseeded]
55 /// let matrix = MatPolyOverZ::sample_discrete_gauss(3, 1, 5, 0, 1.25f32).unwrap();
56 #[seeded]
57 /// let matrix = MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap();
58 /// ```
59 ///
60 /// # Errors and Failures
61 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
62 /// if `s < 0`.
63 ///
64 /// # Panics ...
65 /// - if the provided number of rows and columns are not suited to create a matrix.
66 /// For further information see [`MatPolyOverZ::new`].
67 /// - if `max_degree` is negative, or does not fit into an [`i64`].
68 pub(crate) fn sample_discrete_gauss(
69 num_rows: impl TryInto<i64> + Display,
70 num_cols: impl TryInto<i64> + Display,
71 max_degree: impl TryInto<i64> + Display,
72 center: impl Into<Q>,
73 s: impl Into<Q>,
74 seed: Option<[u8; 32]>,
75 ) -> Result<MatPolyOverZ, MathError> {
76 let max_degree = evaluate_index(max_degree).unwrap();
77 let mut matrix = MatPolyOverZ::new(num_rows, num_cols);
78
79 let mut dgis = DiscreteGaussianIntegerSampler::init(
80 center,
81 s,
82 unsafe { TAILCUT },
83 LookupTableSetting::FillOnTheFly,
84 seed,
85 )?;
86
87 for row in 0..matrix.get_num_rows() {
88 for col in 0..matrix.get_num_columns() {
89 let mut entry = PolyOverZ::default();
90 for index in 0..=max_degree {
91 let sample = dgis.sample_z();
92 unsafe { entry.set_coeff_unchecked(index, &sample) };
93 }
94
95 unsafe { matrix.set_entry_unchecked(row, col, entry) };
96 }
97 }
98
99 Ok(matrix)
100 }
101 );
102
103 seedable_function!(
104 /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`.
105 ///
106 /// We do not check whether `basis` is actually a basis. Hence, the callee is
107 /// responsible for making sure that `basis` provides a suitable basis.
108 ///
109 /// Parameters:
110 /// - `basis`: specifies a basis for the lattice from which is sampled
111 /// - `k`: the maximal length the polynomial can have
112 /// - `center`: specifies the positions of the center with peak probability
113 /// - `s`: specifies the Gaussian parameter, which is proportional
114 /// to the standard deviation `sigma * sqrt(2 * pi) = s`
115 #[seeded]
116 /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
117 #[optionally_seeded]
118 /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
119 #[optionally_seeded]
120 /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
121 ///
122 /// Returns a vector of polynomials sampled according to the
123 /// discrete Gaussian distribution or an error if the basis is not a row vector,
124 /// `s < 0`, or the number of rows of the `basis` and `center` differ.
125 ///
126 /// # Example
127 /// ```
128 /// use qfall_math::{
129 /// integer::MatPolyOverZ,
130 /// rational::PolyOverQ,
131 /// };
132 /// use std::str::FromStr;
133 ///
134 /// let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap();
135 /// let center = vec![PolyOverQ::default()];
136 ///
137 #[unseeded]
138 /// let sample = MatPolyOverZ::sample_d(&basis, 3, ¢er, 10.5_f64).unwrap();
139 #[seeded]
140 /// let sample = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap();
141 /// ```
142 ///
143 /// # Errors and Failures
144 /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector),
145 /// if the basis is not a row vector.
146 /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
147 /// if `s < 0`.
148 /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension)
149 /// if the number of rows of the `basis` and `center` differ.
150 ///
151 /// This function implements SampleD according to:
152 /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
153 /// Trapdoors for hard lattices and new cryptographic constructions.
154 /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
155 /// <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
156 ///
157 /// # Panics ...
158 /// - if the polynomials have higher length than the provided upper bound `k`
159 pub(crate) fn sample_d(
160 basis: &Self,
161 k: impl Into<i64>,
162 center: &[PolyOverQ],
163 s: impl Into<Q>,
164 seed: Option<[u8; 32]>,
165 ) -> Result<MatPolyOverZ, MathError> {
166 let k = k.into();
167 // use coefficient embedding and then call sampleD for the matrix representation
168 let basis_embedded = basis.into_coefficient_embedding(k);
169
170 // use coefficient embedding to get center
171 let mut center_embedded = center[0].into_coefficient_embedding(k);
172 for row in center.iter().skip(1) {
173 let c_row = row.into_coefficient_embedding(k);
174 center_embedded = center_embedded.concat_vertical(&c_row)?;
175 }
176
177 let sample =
178 MatZ::sample_d_optionally_seeded(&basis_embedded, ¢er_embedded, s, seed)?;
179
180 Ok(MatPolyOverZ::from_coefficient_embedding((&sample, k - 1)))
181 }
182 );
183}
184
185#[cfg(test)]
186mod test_sample_discrete_gauss {
187 use crate::utils::sample::test_seed;
188 use crate::{integer::MatPolyOverZ, traits::MatrixGetEntry};
189
190 /// Checks whether `sample_discrete_gauss` is available for all types
191 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
192 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
193 #[test]
194 fn availability() {
195 for seeded in [false, true] {
196 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
197 2,
198 3,
199 128,
200 1u16,
201 2,
202 seeded.then(test_seed),
203 );
204 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
205 1,
206 3,
207 128,
208 1u8,
209 2.0,
210 seeded.then(test_seed),
211 );
212 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
213 2_i64,
214 3,
215 128,
216 1u32,
217 2.0_f64,
218 seeded.then(test_seed),
219 );
220 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
221 2_i32,
222 3,
223 128,
224 1u64,
225 1,
226 seeded.then(test_seed),
227 );
228 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
229 2_i16,
230 3,
231 128,
232 1i64,
233 3_u64,
234 seeded.then(test_seed),
235 );
236 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
237 2_i8,
238 3,
239 128,
240 1i32,
241 1,
242 seeded.then(test_seed),
243 );
244 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
245 2_u64,
246 3,
247 128,
248 1i16,
249 1,
250 seeded.then(test_seed),
251 );
252 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
253 2_u32,
254 3,
255 128,
256 1i8,
257 1,
258 seeded.then(test_seed),
259 );
260 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
261 2_u16,
262 3,
263 128,
264 1i64,
265 2,
266 seeded.then(test_seed),
267 );
268 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
269 2_u8,
270 3,
271 128,
272 -2,
273 3,
274 seeded.then(test_seed),
275 );
276 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
277 1,
278 3,
279 128,
280 4,
281 3,
282 seeded.then(test_seed),
283 );
284 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
285 3,
286 3,
287 128,
288 1.25f64,
289 3,
290 seeded.then(test_seed),
291 );
292 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
293 4,
294 3,
295 128,
296 15.75f32,
297 3,
298 seeded.then(test_seed),
299 );
300
301 // explicitly checks the existence of the unseeded and the seeded function
302 if seeded {
303 let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 3, 128, 1u16, 2, test_seed());
304 } else {
305 let _ = MatPolyOverZ::sample_discrete_gauss(2, 3, 128, 1u16, 2);
306 }
307 }
308 }
309
310 /// Ensures that the resulting entries have correct degree.
311 #[test]
312 fn correct_degree_entries() {
313 for seeded in [false, true] {
314 let degrees = [1, 3, 7, 15, 32, 120];
315 for degree in degrees {
316 let res = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
317 1,
318 1,
319 degree,
320 i64::MAX,
321 1,
322 seeded.then(test_seed),
323 )
324 .unwrap();
325
326 assert_eq!(
327 res.get_entry(0, 0).unwrap().get_degree(),
328 degree,
329 "Could fail with negligible probability."
330 );
331 }
332 }
333 }
334
335 /// Checks whether the maximum degree needs to be at least 0.
336 #[test]
337 fn invalid_max_degree() {
338 for seeded in [false, true] {
339 let result = std::panic::catch_unwind(|| {
340 let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded(
341 2,
342 2,
343 -1,
344 0,
345 1,
346 seeded.then(test_seed),
347 )
348 .unwrap();
349 });
350 assert!(result.is_err());
351 }
352 }
353
354 /// Checks whether the same seed results in the same sample.
355 #[test]
356 fn same_seed_same_sample() {
357 use crate::integer::MatPolyOverZ;
358 let sample_0 =
359 MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap();
360 let sample_1 =
361 MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap();
362
363 assert_eq!(sample_0, sample_1);
364 }
365
366 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
367 #[test]
368 fn known_answer() {
369 use crate::integer::MatPolyOverZ;
370 let sample =
371 MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap();
372
373 assert_eq!(
374 "[[4 1 -1 0 1],[3 0 0 -1],[6 -1 0 0 0 0 -1]]",
375 sample.to_string()
376 );
377 }
378}
379
380#[cfg(test)]
381mod test_sample_d {
382 use crate::utils::sample::test_seed;
383 use crate::{
384 integer::{MatPolyOverZ, MatZ, Z},
385 rational::{PolyOverQ, Q},
386 traits::IntoCoefficientEmbedding,
387 };
388 use std::str::FromStr;
389
390 /// Ensure that the sample is from the base.
391 #[test]
392 fn ensure_sampled_from_base() {
393 for seeded in [false, true] {
394 let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap();
395 let center = vec![PolyOverQ::default()];
396
397 for _ in 0..10 {
398 let sample = MatPolyOverZ::sample_d_optionally_seeded(
399 &base,
400 3,
401 ¢er,
402 10.5_f64,
403 seeded.then(test_seed),
404 )
405 .unwrap();
406 let sample_vec = sample.into_coefficient_embedding(3);
407 let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap();
408
409 assert_eq!(Z::ZERO, sample_vec.dot_product(&orthogonal).unwrap());
410 }
411 }
412 }
413
414 /// Ensure that the sample is from the base for higher dimensional bases.
415 #[test]
416 fn ensure_sampled_from_base_higher_dimension() {
417 for seeded in [false, true] {
418 let base =
419 MatPolyOverZ::from_str("[[1 1, 3 0 1 -1],[3 0 1 -1, 1 1],[0, 0]]").unwrap();
420 let center = vec![
421 PolyOverQ::default(),
422 PolyOverQ::default(),
423 PolyOverQ::default(),
424 ];
425
426 let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap();
427 for _ in 0..10 {
428 let sample = MatPolyOverZ::sample_d_optionally_seeded(
429 &base,
430 3,
431 ¢er,
432 10.5_f64,
433 seeded.then(test_seed),
434 )
435 .unwrap();
436 let sample_embedded = sample.into_coefficient_embedding(3);
437
438 assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded);
439 }
440 }
441 }
442
443 /// Checks whether `sample_d` is available for all types
444 /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
445 /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
446 #[test]
447 fn availability() {
448 for seeded in [false, true] {
449 let basis = MatPolyOverZ::from_str("[[1 1, 2 0 1, 3 0 0 1]]").unwrap();
450 let center = vec![PolyOverQ::default()];
451 let n = Z::from(1024);
452 let s = Q::ONE;
453
454 let _ = MatPolyOverZ::sample_d_optionally_seeded(
455 &basis,
456 3,
457 ¢er,
458 1u16,
459 seeded.then(test_seed),
460 );
461 let _ = MatPolyOverZ::sample_d_optionally_seeded(
462 &basis,
463 3,
464 ¢er,
465 1u8,
466 seeded.then(test_seed),
467 );
468 let _ = MatPolyOverZ::sample_d_optionally_seeded(
469 &basis,
470 3,
471 ¢er,
472 1u32,
473 seeded.then(test_seed),
474 );
475 let _ = MatPolyOverZ::sample_d_optionally_seeded(
476 &basis,
477 3,
478 ¢er,
479 1u64,
480 seeded.then(test_seed),
481 );
482 let _ = MatPolyOverZ::sample_d_optionally_seeded(
483 &basis,
484 3,
485 ¢er,
486 1i64,
487 seeded.then(test_seed),
488 );
489 let _ = MatPolyOverZ::sample_d_optionally_seeded(
490 &basis,
491 3,
492 ¢er,
493 1i32,
494 seeded.then(test_seed),
495 );
496 let _ = MatPolyOverZ::sample_d_optionally_seeded(
497 &basis,
498 3,
499 ¢er,
500 1i16,
501 seeded.then(test_seed),
502 );
503 let _ = MatPolyOverZ::sample_d_optionally_seeded(
504 &basis,
505 3,
506 ¢er,
507 1i8,
508 seeded.then(test_seed),
509 );
510 let _ = MatPolyOverZ::sample_d_optionally_seeded(
511 &basis,
512 3,
513 ¢er,
514 1i64,
515 seeded.then(test_seed),
516 );
517 let _ = MatPolyOverZ::sample_d_optionally_seeded(
518 &basis,
519 3,
520 ¢er,
521 &n,
522 seeded.then(test_seed),
523 );
524 let _ = MatPolyOverZ::sample_d_optionally_seeded(
525 &basis,
526 3,
527 ¢er,
528 &s,
529 seeded.then(test_seed),
530 );
531 let _ = MatPolyOverZ::sample_d_optionally_seeded(
532 &basis,
533 3,
534 ¢er,
535 1.25f64,
536 seeded.then(test_seed),
537 );
538 let _ = MatPolyOverZ::sample_d_optionally_seeded(
539 &basis,
540 3,
541 ¢er,
542 15.75f32,
543 seeded.then(test_seed),
544 );
545
546 // explicitly checks the existence of the unseeded and the seeded function
547 if seeded {
548 let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed());
549 } else {
550 let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u16);
551 }
552 }
553 }
554
555 /// Checks whether the same seed results in the same sample.
556 #[test]
557 fn same_seed_same_sample() {
558 use crate::{integer::MatPolyOverZ, rational::PolyOverQ};
559 use std::str::FromStr;
560 let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap();
561 let center = vec![PolyOverQ::default()];
562 let sample_0 =
563 MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap();
564 let sample_1 =
565 MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap();
566
567 assert_eq!(sample_0, sample_1);
568 }
569
570 /// Checks whether the seeded variant outputs the known sample for a fixed seed.
571 #[test]
572 fn known_answer() {
573 use crate::{integer::MatPolyOverZ, rational::PolyOverQ};
574 use std::str::FromStr;
575 let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap();
576 let center = vec![PolyOverQ::default()];
577 let sample = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap();
578
579 assert_eq!("[[3 5 -9 3]]", sample.to_string());
580 }
581}