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