Skip to main content

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, &center, 10.5_f64).unwrap();
139        #[seeded]
140        /// let sample = MatPolyOverZ::sample_d_seeded(&basis, 3, &center, 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, &center_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                    &center,
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                    &center,
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                &center,
458                1u16,
459                seeded.then(test_seed),
460            );
461            let _ = MatPolyOverZ::sample_d_optionally_seeded(
462                &basis,
463                3,
464                &center,
465                1u8,
466                seeded.then(test_seed),
467            );
468            let _ = MatPolyOverZ::sample_d_optionally_seeded(
469                &basis,
470                3,
471                &center,
472                1u32,
473                seeded.then(test_seed),
474            );
475            let _ = MatPolyOverZ::sample_d_optionally_seeded(
476                &basis,
477                3,
478                &center,
479                1u64,
480                seeded.then(test_seed),
481            );
482            let _ = MatPolyOverZ::sample_d_optionally_seeded(
483                &basis,
484                3,
485                &center,
486                1i64,
487                seeded.then(test_seed),
488            );
489            let _ = MatPolyOverZ::sample_d_optionally_seeded(
490                &basis,
491                3,
492                &center,
493                1i32,
494                seeded.then(test_seed),
495            );
496            let _ = MatPolyOverZ::sample_d_optionally_seeded(
497                &basis,
498                3,
499                &center,
500                1i16,
501                seeded.then(test_seed),
502            );
503            let _ = MatPolyOverZ::sample_d_optionally_seeded(
504                &basis,
505                3,
506                &center,
507                1i8,
508                seeded.then(test_seed),
509            );
510            let _ = MatPolyOverZ::sample_d_optionally_seeded(
511                &basis,
512                3,
513                &center,
514                1i64,
515                seeded.then(test_seed),
516            );
517            let _ = MatPolyOverZ::sample_d_optionally_seeded(
518                &basis,
519                3,
520                &center,
521                &n,
522                seeded.then(test_seed),
523            );
524            let _ = MatPolyOverZ::sample_d_optionally_seeded(
525                &basis,
526                3,
527                &center,
528                &s,
529                seeded.then(test_seed),
530            );
531            let _ = MatPolyOverZ::sample_d_optionally_seeded(
532                &basis,
533                3,
534                &center,
535                1.25f64,
536                seeded.then(test_seed),
537            );
538            let _ = MatPolyOverZ::sample_d_optionally_seeded(
539                &basis,
540                3,
541                &center,
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, &center, 1u16, test_seed());
549            } else {
550                let _ = MatPolyOverZ::sample_d(&basis, 3, &center, 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, &center, 10.5_f64, [42; 32]).unwrap();
564        let sample_1 =
565            MatPolyOverZ::sample_d_seeded(&basis, 3, &center, 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, &center, 10.5_f64, [42; 32]).unwrap();
578
579        assert_eq!("[[3  5 -9 3]]", sample.to_string());
580    }
581}