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