qfall_math/integer_mod_q/z_q/sample/
uniform.rs

1// Copyright © 2023 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 sampling algorithms for uniform random sampling.
10
11use crate::{integer::Z, integer_mod_q::Zq, utils::sample::uniform::UniformIntegerSampler};
12
13impl Zq {
14    /// Chooses a [`Zq`] instance uniformly at random in `[0, modulus)`.
15    ///
16    /// The internally used uniform at random chosen bytes are generated
17    /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and
18    /// is considered cryptographically secure.
19    ///
20    /// Parameters:
21    /// - `modulus`: specifies the [`Modulus`](crate::integer_mod_q::Modulus)
22    ///   of the new [`Zq`] instance and thus the size of the interval over which is sampled
23    ///
24    /// Returns a new [`Zq`] instance with a value chosen
25    /// uniformly at random in `[0, modulus)`.
26    ///
27    /// # Examples
28    /// ```
29    /// use qfall_math::integer_mod_q::Zq;
30    ///
31    /// let sample = Zq::sample_uniform(17);
32    /// ```
33    ///
34    /// # Panics
35    /// - if the given modulus is smaller than or equal to `1`.
36    pub fn sample_uniform(modulus: impl Into<Z>) -> Self {
37        let modulus: Z = modulus.into();
38        let mut uis = UniformIntegerSampler::init(&modulus).unwrap();
39
40        let random = uis.sample();
41        Zq::from((random, modulus))
42    }
43}
44
45#[cfg(test)]
46mod test_sample_uniform {
47    use crate::{
48        integer::Z,
49        integer_mod_q::{Modulus, Zq},
50    };
51
52    /// Checks whether the boundaries of the interval are kept for small moduli.
53    /// These should be protected by the sampling algorithm and [`Zq`]s instantiation.
54    #[test]
55    fn boundaries_kept_small() {
56        let modulus = Z::from(17);
57        for _ in 0..32 {
58            let sample = Zq::sample_uniform(&modulus);
59            assert!(Z::ZERO <= sample.value);
60            assert!(sample.value < modulus);
61        }
62    }
63
64    /// Checks whether the boundaries of the interval are kept for large moduli.
65    /// These should be protected by the sampling algorithm and [`Zq`]s instantiation.
66    #[test]
67    fn boundaries_kept_large() {
68        let modulus = Z::from(u64::MAX);
69        for _ in 0..256 {
70            let sample = Zq::sample_uniform(&modulus);
71            assert!(Z::ZERO <= sample.value);
72            assert!(sample.value < modulus);
73        }
74    }
75
76    /// Checks whether providing an invalid interval results in an error.
77    #[test]
78    #[should_panic]
79    fn invalid_interval() {
80        let modulus = Z::ZERO;
81
82        let _ = Zq::sample_uniform(&modulus);
83    }
84
85    /// Checks whether `sample_uniform` is available for all types
86    /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ...
87    #[test]
88    fn availability() {
89        let modulus = Modulus::from(7);
90        let z = Z::from(7);
91
92        let _ = Zq::sample_uniform(7u8);
93        let _ = Zq::sample_uniform(7u16);
94        let _ = Zq::sample_uniform(7u32);
95        let _ = Zq::sample_uniform(7u64);
96        let _ = Zq::sample_uniform(7i8);
97        let _ = Zq::sample_uniform(7i16);
98        let _ = Zq::sample_uniform(7i32);
99        let _ = Zq::sample_uniform(7i64);
100        let _ = Zq::sample_uniform(&modulus);
101        let _ = Zq::sample_uniform(&z);
102    }
103
104    /// Roughly checks the uniformity of the distribution.
105    /// This test could possibly fail for a truly uniform distribution
106    /// with probability smaller than 1/1000.
107    #[test]
108    fn uniformity() {
109        let modulus = Z::from(5);
110        let mut counts = [0; 5];
111        // count sampled instances
112        for _ in 0..1000 {
113            let sample_z = Zq::sample_uniform(&modulus);
114            let sample_int = i64::try_from(&sample_z.value).unwrap() as usize;
115            counts[sample_int] += 1;
116        }
117
118        // Check that every sampled integer was sampled roughly the same time
119        // this could possibly fail for true uniform randomness with probability
120        for count in counts {
121            assert!(count > 150, "This test can fail with probability close to 0. 
122            It fails if the sampled occurrences do not look like a typical uniform random distribution. 
123            If this happens, rerun the tests several times and check whether this issue comes up again.");
124        }
125    }
126}