Skip to main content

qfall_math/integer_mod_q/poly_over_zq/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
10//! to the discrete Gaussian distribution.
11
12use crate::{
13    error::MathError,
14    integer_mod_q::{Modulus, PolyOverZq},
15    macros::seeded::seedable_function,
16    rational::Q,
17    traits::SetCoefficient,
18    utils::{
19        index::evaluate_index,
20        sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT},
21    },
22};
23use std::fmt::Display;
24
25impl PolyOverZq {
26    seedable_function!(
27        /// Initializes a new [`PolyOverZq`] with maximum degree `max_degree`
28        /// and with each entry sampled independently according to the
29        /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss).
30        ///
31        /// Parameters:
32        /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZq`] should have
33        /// - `modulus`: specififes the [`Modulus`] over which the ring of integer coefficients is defined
34        /// - `center`: specifies the positions of the center with peak probability
35        /// - `s`: specifies the Gaussian parameter, which is proportional
36        ///   to the standard deviation `sigma * sqrt(2 * pi) = s`
37        #[seeded]
38        /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling
39        #[optionally_seeded]
40        /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling.
41        #[optionally_seeded]
42        ///   If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead.
43        ///
44        /// Returns a fresh [`PolyOverZq`] instance of maximum degree `max_degree`
45        /// with coefficients chosen independently according the discrete Gaussian distribution or
46        /// a [`MathError`] if `s < 0`.
47        ///
48        /// # Examples
49        /// ```
50        /// use qfall_math::integer_mod_q::PolyOverZq;
51        ///
52        #[unseeded]
53        /// let sample = PolyOverZq::sample_discrete_gauss(2, 17, 0, 1).unwrap();
54        #[seeded]
55        /// let sample = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap();
56        /// ```
57        ///
58        /// # Errors and Failures
59        /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput)
60        ///   if `s < 0`.
61        ///
62        /// # Panics ...
63        /// - if `max_degree` is negative, or does not fit into an [`i64`].
64        /// - if `modulus` is smaller than `2`.
65        pub(crate) fn sample_discrete_gauss(
66            max_degree: impl TryInto<i64> + Display,
67            modulus: impl Into<Modulus>,
68            center: impl Into<Q>,
69            s: impl Into<Q>,
70            seed: Option<[u8; 32]>,
71        ) -> Result<Self, MathError> {
72            let max_degree = evaluate_index(max_degree).unwrap();
73            let modulus = modulus.into();
74
75            let center = center.into();
76            let s = s.into();
77            let mut poly = PolyOverZq::from(&modulus);
78
79            let mut dgis = DiscreteGaussianIntegerSampler::init(
80                &center,
81                &s,
82                unsafe { TAILCUT },
83                LookupTableSetting::FillOnTheFly,
84                seed,
85            )?;
86
87            for index in 0..=max_degree {
88                let sample = dgis.sample_z();
89                unsafe { poly.set_coeff_unchecked(index, sample) };
90            }
91            Ok(poly)
92        }
93    );
94}
95
96#[cfg(test)]
97mod test_sample_discrete_gauss {
98    use crate::utils::sample::test_seed;
99    use crate::{integer::Z, integer_mod_q::PolyOverZq, rational::Q, traits::GetCoefficient};
100
101    /// Checks whether `sample_discrete_gauss` is available for all types
102    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
103    /// or [`Into<Q>`], i.e. u8, i16, f32, Z, Q, ...
104    #[test]
105    fn availability() {
106        for seeded in [false, true] {
107            let center = Q::ZERO;
108            let s = Q::ONE;
109
110            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
111                1u8,
112                17u8,
113                0f32,
114                1u8,
115                seeded.then(test_seed),
116            );
117            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
118                1u16,
119                17u16,
120                0f64,
121                1u16,
122                seeded.then(test_seed),
123            );
124            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
125                1u32,
126                17u32,
127                0f32,
128                1u32,
129                seeded.then(test_seed),
130            );
131            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
132                1u64,
133                17u64,
134                0f64,
135                1u64,
136                seeded.then(test_seed),
137            );
138            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
139                1i8,
140                17u8,
141                0f32,
142                1i8,
143                seeded.then(test_seed),
144            );
145            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
146                1i8,
147                17i8,
148                0f32,
149                1i16,
150                seeded.then(test_seed),
151            );
152            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
153                1i16,
154                17i16,
155                0f32,
156                1i32,
157                seeded.then(test_seed),
158            );
159            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
160                1i32,
161                17i32,
162                0f64,
163                1i64,
164                seeded.then(test_seed),
165            );
166            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
167                1i64,
168                17i64,
169                center,
170                s,
171                seeded.then(test_seed),
172            );
173            let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
174                1u8,
175                17u8,
176                0f32,
177                1f64,
178                seeded.then(test_seed),
179            );
180
181            // explicitly checks the existence of the unseeded and the seeded function
182            if seeded {
183                let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1u8, test_seed());
184            } else {
185                let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1u8);
186            }
187        }
188    }
189
190    /// Checks whether the boundaries of the interval are kept for small moduli.
191    #[test]
192    fn boundaries_kept_small() {
193        for seeded in [false, true] {
194            let modulus = 128;
195
196            for _ in 0..32 {
197                let poly = PolyOverZq::sample_discrete_gauss_optionally_seeded(
198                    3,
199                    modulus,
200                    15,
201                    1,
202                    seeded.then(test_seed),
203                )
204                .unwrap();
205
206                for i in 0..3 {
207                    let sample: Z = poly.get_coeff(i).unwrap();
208                    assert!(Z::ZERO <= sample);
209                    assert!(sample < modulus);
210                }
211            }
212        }
213    }
214
215    /// Checks whether the boundaries of the interval are kept for large moduli.
216    #[test]
217    fn boundaries_kept_large() {
218        for seeded in [false, true] {
219            let modulus = u64::MAX;
220
221            for _ in 0..256 {
222                let poly = PolyOverZq::sample_discrete_gauss_optionally_seeded(
223                    3,
224                    modulus,
225                    1,
226                    1,
227                    seeded.then(test_seed),
228                )
229                .unwrap();
230
231                for i in 0..3 {
232                    let sample: Z = poly.get_coeff(i).unwrap();
233                    assert!(Z::ZERO <= sample);
234                    assert!(sample < modulus);
235                }
236            }
237        }
238    }
239
240    /// Checks whether the number of coefficients is correct.
241    #[test]
242    fn nr_coeffs() {
243        for seeded in [false, true] {
244            let degrees = [1, 3, 7, 15, 32, 120];
245            for degree in degrees {
246                let res = PolyOverZq::sample_discrete_gauss_optionally_seeded(
247                    degree,
248                    u64::MAX,
249                    i64::MAX,
250                    1,
251                    seeded.then(test_seed),
252                )
253                .unwrap();
254
255                assert_eq!(
256                    res.get_degree(),
257                    degree,
258                    "Could fail with negligible probability."
259                );
260            }
261        }
262    }
263
264    /// Checks whether the maximum degree needs to be at least 0.
265    #[test]
266    fn invalid_max_degree() {
267        for seeded in [false, true] {
268            let result = std::panic::catch_unwind(|| {
269                let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
270                    -1,
271                    17,
272                    0,
273                    1,
274                    seeded.then(test_seed),
275                )
276                .unwrap();
277            });
278            assert!(result.is_err());
279        }
280    }
281
282    /// Checks whether too small modulus is insufficient.
283    #[test]
284    fn invalid_modulus() {
285        for seeded in [false, true] {
286            let result = std::panic::catch_unwind(|| {
287                let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded(
288                    3,
289                    1,
290                    0,
291                    1,
292                    seeded.then(test_seed),
293                )
294                .unwrap();
295            });
296            assert!(result.is_err());
297        }
298    }
299
300    /// Checks whether the same seed results in the same sample.
301    #[test]
302    fn same_seed_same_sample() {
303        use crate::integer_mod_q::PolyOverZq;
304        let sample_0 = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap();
305        let sample_1 = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap();
306
307        assert_eq!(sample_0, sample_1);
308    }
309
310    /// Checks whether the seeded variant outputs the known sample for a fixed seed.
311    #[test]
312    fn known_answer() {
313        use crate::integer_mod_q::PolyOverZq;
314        let sample = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap();
315
316        assert_eq!("1  1 mod 17", sample.to_string());
317    }
318}