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