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 ¢er,
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}