Skip to main content

qfall_math/integer/z/sample/
discrete_gauss.rs

1// Copyright 2023 Jan 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::Z,
14    macros::seeded::seedable_function,
15    rational::Q,
16    utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
17};
18
19impl Z {
20    seedable_function!(
21        /// Chooses a [`Z`] instance according to the discrete Gaussian distribution
22        /// in `[center - ⌈6 * sāŒ‰ , center + ⌊6 * sāŒ‹ ]`.
23        ///
24        /// This function samples discrete Gaussians according to the definition of
25        /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35).
26        ///
27        /// Parameters:
28        /// - `n`: specifies the range from which is sampled
29        /// - `center`: specifies the position of the center with peak probability
30        /// - `s`: specifies the Gaussian parameter, which is proportional
31        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
32        #[seeded]
33        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
34        #[optionally_seeded]
35        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
36        #[optionally_seeded]
37        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
38        ///
39        /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian
40        /// distribution or a [`MathError`] if the specified parameters were not chosen
41        /// appropriately, i.e. `s < 0`.
42        ///
43        /// # Examples
44        /// ```
45        /// use qfall_math::integer::Z;
46        ///
47        #[unseeded]
48        /// let sample = Z::sample_discrete_gauss(0, 1).unwrap();
49        #[seeded]
50        /// let sample = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap();
51        /// ```
52        ///
53        /// # Errors and Failures
54        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
55        ///   if `s < 0`.
56        ///
57        /// This function implements SampleZ according to:
58        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
59        ///   Trapdoors for hard lattices and new cryptographic constructions.
60        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
61        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
62        pub(crate) fn sample_discrete_gauss(
63            center: impl Into<Q>,
64            s: impl Into<Q>,
65            seed: Option<[u8; 32]>,
66        ) -> Result<Self, MathError> {
67            let center: Q = center.into();
68            let s: Q = s.into();
69
70            let mut dgis = DiscreteGaussianIntegerSampler::init(
71                &center,
72                &s,
73                unsafe { TAILCUT },
74                LookupTableSetting::NoLookup,
75                seed,
76            )?;
77
78            Ok(dgis.sample_z())
79        }
80    );
81}
82
83#[cfg(test)]
84mod test_sample_discrete_gauss {
85    use crate::utils::sample::test_seed;
86    use crate::{integer::Z, rational::Q};
87
88    // Appropriate interval and invalid inputs were tested in
89    // utils::sample::discrete_gauss::test_sample_z and are thus omitted here.
90
91    /// Checks whether `sample_discrete_gauss` is available for all types
92    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
93    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
94    #[test]
95    fn availability() {
96        for seeded in [false, true] {
97            let z = Z::from(2);
98            let q = Q::from(2);
99
100            let _ = Z::sample_discrete_gauss_optionally_seeded(7u8, 1u16, seeded.then(test_seed));
101            let _ = Z::sample_discrete_gauss_optionally_seeded(7u16, 1u8, seeded.then(test_seed));
102            let _ = Z::sample_discrete_gauss_optionally_seeded(7u32, 1u32, seeded.then(test_seed));
103            let _ = Z::sample_discrete_gauss_optionally_seeded(7u64, 1u64, seeded.then(test_seed));
104            let _ = Z::sample_discrete_gauss_optionally_seeded(7i8, 1i64, seeded.then(test_seed));
105            let _ = Z::sample_discrete_gauss_optionally_seeded(7i16, 1i32, seeded.then(test_seed));
106            let _ = Z::sample_discrete_gauss_optionally_seeded(7i32, 1i16, seeded.then(test_seed));
107            let _ = Z::sample_discrete_gauss_optionally_seeded(7i64, 1i8, seeded.then(test_seed));
108            let _ = Z::sample_discrete_gauss_optionally_seeded(&q, 1i64, seeded.then(test_seed));
109            let _ = Z::sample_discrete_gauss_optionally_seeded(0i8, &z, seeded.then(test_seed));
110            let _ = Z::sample_discrete_gauss_optionally_seeded(&z, &q, seeded.then(test_seed));
111            let _ = Z::sample_discrete_gauss_optionally_seeded(1f32, 1f64, seeded.then(test_seed));
112            let _ = Z::sample_discrete_gauss_optionally_seeded(1f64, 1f32, seeded.then(test_seed));
113
114            // explicitly checks the existence of the unseeded and the seeded function
115            if seeded {
116                let _ = Z::sample_discrete_gauss_seeded(7u8, 1u16, test_seed());
117            } else {
118                let _ = Z::sample_discrete_gauss(7u8, 1u16);
119            }
120        }
121    }
122
123    /// Roughly checks the collected samples are distributed
124    /// according to the discrete Gaussian distribution.
125    ///
126    /// This test could possibly fail for a truly uniform distribution
127    /// with probability smaller than 1/1000.
128    #[test]
129    fn distribution() {
130        for seeded in [false, true] {
131            let mut counts = [0; 20];
132            // count sampled instances
133            for _ in 0..200 {
134                let sample =
135                    Z::sample_discrete_gauss_optionally_seeded(10, 2, seeded.then(test_seed))
136                        .unwrap();
137                let sample_int = i64::try_from(&sample).unwrap() as usize;
138                counts[sample_int] += 1;
139            }
140
141            let expl_text = String::from("This test can fail with probability close to 0. 
142        It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. 
143        If this happens, rerun the tests several times and check whether this issue comes up again.");
144
145            // Check that the sampled occurrences roughly look
146            // like a discrete Gaussian distribution
147            assert!(counts[10] > 70, "{expl_text}");
148            assert!(counts[10] < 130, "{expl_text}");
149            assert!(counts[9] > 20, "{expl_text}");
150            assert!(counts[9] < 70, "{expl_text}");
151            assert!(counts[11] > 20, "{expl_text}");
152            assert!(counts[11] < 70, "{expl_text}");
153            assert!(counts[8] < 20, "{expl_text}");
154            assert!(counts[12] < 20, "{expl_text}");
155            for count in counts.iter().take(8) {
156                assert!(count < &10, "{expl_text}");
157            }
158            for count in counts.iter().skip(13) {
159                assert!(count < &10, "{expl_text}");
160            }
161        }
162    }
163
164    /// Checks whether the same seed results in the same sample.
165    #[test]
166    fn same_seed_same_sample() {
167        use crate::integer::Z;
168        let sample_0 = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap();
169        let sample_1 = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap();
170
171        assert_eq!(sample_0, sample_1);
172    }
173
174    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
175    #[test]
176    fn known_answer() {
177        use crate::integer::Z;
178        let sample = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap();
179
180        assert_eq!("1", sample.to_string());
181    }
182}