Skip to main content

qfall_math/integer_mod_q/z_q/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_mod_q::{Modulus, Zq},
14    macros::seeded::seedable_function,
15    rational::Q,
16    utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
17};
18
19impl Zq {
20    seedable_function!(
21        /// Chooses a [`Zq`] instance chosen 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        /// - `modulus`: specifies the modulus of the new [`Zq`] element
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 [`Zq`] 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_mod_q::Zq;
46        ///
47        #[unseeded]
48        /// let sample = Zq::sample_discrete_gauss(17, 0, 1).unwrap();
49        #[seeded]
50        /// let sample = Zq::sample_discrete_gauss_seeded(17, 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        /// # Panics ...
58        /// - if `modulus` is smaller than `2`.
59        ///
60        /// This function implements SampleZ according to:
61        /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008).
62        ///   Trapdoors for hard lattices and new cryptographic constructions.
63        ///   In: Proceedings of the fortieth annual ACM symposium on Theory of computing.
64        ///   <https://dl.acm.org/doi/pdf/10.1145/1374376.1374407>
65        pub(crate) fn sample_discrete_gauss(
66            modulus: impl Into<Modulus>,
67            center: impl Into<Q>,
68            s: impl Into<Q>,
69            seed: Option<[u8; 32]>,
70        ) -> Result<Self, MathError> {
71            let modulus: Modulus = modulus.into();
72            let center: Q = center.into();
73            let s: Q = s.into();
74
75            let mut dgis = DiscreteGaussianIntegerSampler::init(
76                &center,
77                &s,
78                unsafe { TAILCUT },
79                LookupTableSetting::NoLookup,
80                seed,
81            )?;
82
83            let sample = dgis.sample_z();
84            Ok(Zq::from((&sample, &modulus)))
85        }
86    );
87}
88
89#[cfg(test)]
90mod test_sample_discrete_gauss {
91    use crate::utils::sample::test_seed;
92    use crate::{integer::Z, integer_mod_q::Zq, rational::Q};
93
94    // Appropriate interval and invalid inputs were tested in
95    // utils::sample::discrete_gauss::test_sample_z and are thus omitted here.
96
97    /// Checks whether `sample_discrete_gauss` is available for all types
98    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
99    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
100    #[test]
101    fn availability() {
102        for seeded in [false, true] {
103            let z = Z::from(2);
104            let q = Q::from(2);
105
106            let _ = Zq::sample_discrete_gauss_optionally_seeded(
107                17u64,
108                7u8,
109                1u16,
110                seeded.then(test_seed),
111            );
112            let _ = Zq::sample_discrete_gauss_optionally_seeded(
113                17u16,
114                7u16,
115                1u8,
116                seeded.then(test_seed),
117            );
118            let _ = Zq::sample_discrete_gauss_optionally_seeded(
119                17u8,
120                7u32,
121                1u32,
122                seeded.then(test_seed),
123            );
124            let _ = Zq::sample_discrete_gauss_optionally_seeded(
125                17u32,
126                7u64,
127                1u64,
128                seeded.then(test_seed),
129            );
130            let _ = Zq::sample_discrete_gauss_optionally_seeded(
131                17i16,
132                7i8,
133                1i64,
134                seeded.then(test_seed),
135            );
136            let _ = Zq::sample_discrete_gauss_optionally_seeded(
137                17i8,
138                7i16,
139                1i32,
140                seeded.then(test_seed),
141            );
142            let _ = Zq::sample_discrete_gauss_optionally_seeded(
143                17i64,
144                7i32,
145                1i16,
146                seeded.then(test_seed),
147            );
148            let _ = Zq::sample_discrete_gauss_optionally_seeded(
149                17i32,
150                7i64,
151                1i8,
152                seeded.then(test_seed),
153            );
154            let _ = Zq::sample_discrete_gauss_optionally_seeded(
155                17,
156                q.clone(),
157                1i64,
158                seeded.then(test_seed),
159            );
160            let _ = Zq::sample_discrete_gauss_optionally_seeded(
161                z.clone(),
162                0i8,
163                z.clone(),
164                seeded.then(test_seed),
165            );
166            let _ = Zq::sample_discrete_gauss_optionally_seeded(
167                17,
168                z.clone(),
169                q.clone(),
170                seeded.then(test_seed),
171            );
172            let _ =
173                Zq::sample_discrete_gauss_optionally_seeded(17, 1f32, 1f64, seeded.then(test_seed));
174            let _ =
175                Zq::sample_discrete_gauss_optionally_seeded(17, 1f64, 1f32, seeded.then(test_seed));
176
177            // With references
178            let _ = Zq::sample_discrete_gauss_optionally_seeded(
179                17i64,
180                7i32,
181                1i16,
182                seeded.then(test_seed),
183            );
184            let _ = Zq::sample_discrete_gauss_optionally_seeded(
185                17i32,
186                7i64,
187                1i8,
188                seeded.then(test_seed),
189            );
190            let _ =
191                Zq::sample_discrete_gauss_optionally_seeded(17, &q, 1i64, seeded.then(test_seed));
192            let _ =
193                Zq::sample_discrete_gauss_optionally_seeded(&z, 0i8, &z, seeded.then(test_seed));
194            let _ = Zq::sample_discrete_gauss_optionally_seeded(17, &z, &q, seeded.then(test_seed));
195
196            // explicitly checks the existence of the unseeded and the seeded function
197            if seeded {
198                let _ = Zq::sample_discrete_gauss_seeded(17u64, 7u8, 1u16, test_seed());
199            } else {
200                let _ = Zq::sample_discrete_gauss(17u64, 7u8, 1u16);
201            }
202        }
203    }
204
205    /// Roughly checks the collected samples are distributed
206    /// according to the discrete Gaussian distribution.
207    ///
208    /// This test could possibly fail for a truly uniform distribution
209    /// with probability smaller than 1/1000.
210    #[test]
211    fn distribution() {
212        for seeded in [false, true] {
213            let mut counts = [0; 20];
214            // count sampled instances
215            for _ in 0..200 {
216                let sample =
217                    Zq::sample_discrete_gauss_optionally_seeded(20, 0, 2, seeded.then(test_seed))
218                        .unwrap();
219                let sample_int =
220                    i64::try_from(&sample.get_representative_least_nonnegative_residue()).unwrap()
221                        as usize;
222                counts[sample_int] += 1;
223            }
224
225            let expl_text = String::from("This test can fail with probability close to 0. 
226        It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. 
227        If this happens, rerun the tests several times and check whether this issue comes up again.");
228
229            // Check that the sampled occurrences roughly look
230            // like a discrete Gaussian distribution
231            assert!(counts[0] > 70, "{expl_text}");
232            assert!(counts[0] < 130, "{expl_text}");
233            assert!(counts[19] > 20, "{expl_text}");
234            assert!(counts[19] < 70, "{expl_text}");
235            assert!(counts[1] > 20, "{expl_text}");
236            assert!(counts[1] < 70, "{expl_text}");
237            assert!(counts[18] < 20, "{expl_text}");
238            assert!(counts[2] < 20, "{expl_text}");
239            for count in counts.iter().take(18).skip(3) {
240                assert!(count < &10, "{expl_text}");
241            }
242        }
243    }
244
245    /// Checks whether the same seed results in the same sample.
246    #[test]
247    fn same_seed_same_sample() {
248        use crate::integer_mod_q::Zq;
249        let sample_0 = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap();
250        let sample_1 = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap();
251
252        assert_eq!(sample_0, sample_1);
253    }
254
255    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
256    #[test]
257    fn known_answer() {
258        use crate::integer_mod_q::Zq;
259        let sample = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap();
260
261        assert_eq!("1 mod 17", sample.to_string());
262    }
263}