Skip to main content

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}