From 6f9e0b6c276661c22345722ad8e183f43d2934f0 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Fri, 9 Oct 2026 14:55:30 +0100 Subject: [PATCH 1/5] Add optional seeding for base samplers --- Cargo.toml | 4 +- benches/sample_z.rs | 11 +- .../mat_poly_over_z/sample/binomial.rs | 2 +- .../mat_poly_over_z/sample/discrete_gauss.rs | 1 + src/integer/mat_z/sample/binomial.rs | 2 +- src/integer/mat_z/sample/discrete_gauss.rs | 1 + src/integer/mat_z/sample/uniform.rs | 2 +- src/integer/poly_over_z/sample/binomial.rs | 2 +- .../poly_over_z/sample/discrete_gauss.rs | 1 + src/integer/poly_over_z/sample/uniform.rs | 2 +- src/integer/z/sample/binomial.rs | 2 +- src/integer/z/sample/discrete_gauss.rs | 1 + src/integer/z/sample/uniform.rs | 4 +- .../sample/uniform.rs | 2 +- src/integer_mod_q/mat_zq/sample/binomial.rs | 2 +- .../mat_zq/sample/discrete_gauss.rs | 1 + src/integer_mod_q/mat_zq/sample/uniform.rs | 2 +- .../ntt_polynomial_ring_zq/sample/uniform.rs | 2 +- .../poly_over_zq/sample/binomial.rs | 2 +- .../poly_over_zq/sample/discrete_gauss.rs | 1 + .../poly_over_zq/sample/uniform.rs | 2 +- src/integer_mod_q/z_q/sample/binomial.rs | 2 +- .../z_q/sample/discrete_gauss.rs | 1 + src/integer_mod_q/z_q/sample/uniform.rs | 2 +- src/utils/sample/binomial.rs | 101 +++++++-- src/utils/sample/discrete_gauss.rs | 211 ++++++++++++++++-- src/utils/sample/uniform.rs | 193 ++++++++++++++-- 27 files changed, 483 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e5b53bd..9cad3cc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "qfall-math" version = "0.2.0" edition = "2024" -rust-version = "1.87" # due to wit_bindgen dependency +rust-version = "1.86" description = "Mathematical foundations for rapid prototyping of lattice-based cryptography" readme = "README.md" homepage = "https://qfall.github.io" @@ -26,7 +26,7 @@ thiserror = "2" lazy_static = "1" probability = "0.20" derive_more = { version = "2.1", features = ["display"] } -flint3-sys = { version="3.6.0", features=["gmp-mpfr-sys"]} +flint3-sys = { version="3.6.1", features=["gmp-mpfr-sys"]} [profile.bench] debug = true diff --git a/benches/sample_z.rs b/benches/sample_z.rs index f271d3cf..cabd2d92 100644 --- a/benches/sample_z.rs +++ b/benches/sample_z.rs @@ -63,9 +63,14 @@ pub fn bench_sample_z(c: &mut Criterion) { ]; for s in gaussian_widths { - let mut dgis = - DiscreteGaussianIntegerSampler::init(center, s, 6.0, LookupTableSetting::Precompute) - .unwrap(); + let mut dgis = DiscreteGaussianIntegerSampler::init( + center, + s, + 6.0, + LookupTableSetting::Precompute, + None, + ) + .unwrap(); c.bench_function("DiscreteGauss RejectionSampling", |b| { b.iter(|| dgis.sample_z()) diff --git a/src/integer/mat_poly_over_z/sample/binomial.rs b/src/integer/mat_poly_over_z/sample/binomial.rs index 21c1050c..af645f4a 100644 --- a/src/integer/mat_poly_over_z/sample/binomial.rs +++ b/src/integer/mat_poly_over_z/sample/binomial.rs @@ -107,7 +107,7 @@ impl MatPolyOverZ { ) -> Result { let max_degree = evaluate_index(max_degree)?; let offset: Z = offset.into(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; let mut matrix = MatPolyOverZ::new(num_rows, num_cols); for row in 0..matrix.get_num_rows() { diff --git a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs index 232255ae..467be192 100644 --- a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs @@ -69,6 +69,7 @@ impl MatPolyOverZ { s, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; for row in 0..matrix.get_num_rows() { diff --git a/src/integer/mat_z/sample/binomial.rs b/src/integer/mat_z/sample/binomial.rs index 1a432846..67e3a5be 100644 --- a/src/integer/mat_z/sample/binomial.rs +++ b/src/integer/mat_z/sample/binomial.rs @@ -102,7 +102,7 @@ impl MatZ { p: impl Into, ) -> Result { let offset: Z = offset.into(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; let mut matrix = MatZ::new(num_rows, num_cols); for row in 0..matrix.get_num_rows() { diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index 2afd1197..16124017 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -62,6 +62,7 @@ impl MatZ { s, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; for row in 0..out.get_num_rows() { diff --git a/src/integer/mat_z/sample/uniform.rs b/src/integer/mat_z/sample/uniform.rs index d6830363..43984313 100644 --- a/src/integer/mat_z/sample/uniform.rs +++ b/src/integer/mat_z/sample/uniform.rs @@ -61,7 +61,7 @@ impl MatZ { let mut matrix = MatZ::new(num_rows, num_cols); let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size)?; + let mut uis = UniformIntegerSampler::init(&interval_size, None)?; for row in 0..matrix.get_num_rows() { for col in 0..matrix.get_num_columns() { let sample = uis.sample(); diff --git a/src/integer/poly_over_z/sample/binomial.rs b/src/integer/poly_over_z/sample/binomial.rs index 98ceb513..a56a18a1 100644 --- a/src/integer/poly_over_z/sample/binomial.rs +++ b/src/integer/poly_over_z/sample/binomial.rs @@ -101,7 +101,7 @@ impl PolyOverZ { let mut poly_z = PolyOverZ::default(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; for index in 0..=max_degree { let mut sample = bin_sampler.sample(); diff --git a/src/integer/poly_over_z/sample/discrete_gauss.rs b/src/integer/poly_over_z/sample/discrete_gauss.rs index dea23477..6461adc9 100644 --- a/src/integer/poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/poly_over_z/sample/discrete_gauss.rs @@ -65,6 +65,7 @@ impl PolyOverZ { &s, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; for index in 0..=max_degree { diff --git a/src/integer/poly_over_z/sample/uniform.rs b/src/integer/poly_over_z/sample/uniform.rs index c2635dc4..efff155c 100644 --- a/src/integer/poly_over_z/sample/uniform.rs +++ b/src/integer/poly_over_z/sample/uniform.rs @@ -58,7 +58,7 @@ impl PolyOverZ { let upper_bound: Z = upper_bound.into(); let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size)?; + let mut uis = UniformIntegerSampler::init(&interval_size, None)?; let mut poly_z = PolyOverZ::default(); for index in 0..=max_degree { diff --git a/src/integer/z/sample/binomial.rs b/src/integer/z/sample/binomial.rs index 6ed1d372..ab3771af 100644 --- a/src/integer/z/sample/binomial.rs +++ b/src/integer/z/sample/binomial.rs @@ -38,7 +38,7 @@ impl Z { /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) /// if `n` does not fit into an [`i64`]. pub fn sample_binomial(n: impl Into, p: impl Into) -> Result { - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; Ok(bin_sampler.sample()) } diff --git a/src/integer/z/sample/discrete_gauss.rs b/src/integer/z/sample/discrete_gauss.rs index 7c461f1d..d18091b8 100644 --- a/src/integer/z/sample/discrete_gauss.rs +++ b/src/integer/z/sample/discrete_gauss.rs @@ -57,6 +57,7 @@ impl Z { &s, unsafe { TAILCUT }, LookupTableSetting::NoLookup, + None, )?; Ok(dgis.sample_z()) diff --git a/src/integer/z/sample/uniform.rs b/src/integer/z/sample/uniform.rs index 1a380ded..bd708995 100644 --- a/src/integer/z/sample/uniform.rs +++ b/src/integer/z/sample/uniform.rs @@ -46,7 +46,7 @@ impl Z { let upper_bound: Z = upper_bound.into(); let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size)?; + let mut uis = UniformIntegerSampler::init(&interval_size, None)?; let sample = uis.sample(); Ok(&lower_bound + sample) @@ -98,7 +98,7 @@ impl Z { } let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size)?; + let mut uis = UniformIntegerSampler::init(&interval_size, None)?; let mut sample = &lower_bound + uis.sample(); // after 2 * size of interval many uniform random samples, a suitable prime should have been diff --git a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs index 641afe1f..c8f86c23 100644 --- a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs @@ -56,7 +56,7 @@ impl MatNTTPolynomialRingZq { ); let interval_size = modulus.get_q(); - let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); + let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); let vector = (0..modulus.get_degree() as usize * nr_rows * nr_columns) .map(|_| uis.sample()) diff --git a/src/integer_mod_q/mat_zq/sample/binomial.rs b/src/integer_mod_q/mat_zq/sample/binomial.rs index fff12d83..3e003bdc 100644 --- a/src/integer_mod_q/mat_zq/sample/binomial.rs +++ b/src/integer_mod_q/mat_zq/sample/binomial.rs @@ -110,7 +110,7 @@ impl MatZq { ) -> Result { let offset: Z = offset.into(); let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; let mut matrix = MatZq::new(num_rows, num_cols, &modulus); for row in 0..matrix.get_num_rows() { diff --git a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs index cfa2a10d..ee52d100 100644 --- a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs @@ -67,6 +67,7 @@ impl MatZq { &s, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; for row in 0..out.get_num_rows() { diff --git a/src/integer_mod_q/mat_zq/sample/uniform.rs b/src/integer_mod_q/mat_zq/sample/uniform.rs index 5163d7f9..1848e7d4 100644 --- a/src/integer_mod_q/mat_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_zq/sample/uniform.rs @@ -49,7 +49,7 @@ impl MatZq { modulus: impl Into, ) -> Self { let modulus: Z = modulus.into(); - let mut uis = UniformIntegerSampler::init(&modulus).unwrap(); + let mut uis = UniformIntegerSampler::init(&modulus, None).unwrap(); let mut matrix = MatZq::new(num_rows, num_cols, modulus); for row in 0..matrix.get_num_rows() { diff --git a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs index 0991c494..da3b1570 100644 --- a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs @@ -43,7 +43,7 @@ impl NTTPolynomialRingZq { let interval_size = modulus.get_q(); assert!(interval_size > 1); - let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); + let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); let vector = (0..modulus.get_degree()).map(|_| uis.sample()).collect(); Self { diff --git a/src/integer_mod_q/poly_over_zq/sample/binomial.rs b/src/integer_mod_q/poly_over_zq/sample/binomial.rs index cc7b10e7..b404cda2 100644 --- a/src/integer_mod_q/poly_over_zq/sample/binomial.rs +++ b/src/integer_mod_q/poly_over_zq/sample/binomial.rs @@ -108,7 +108,7 @@ impl PolyOverZq { let max_degree = evaluate_index(max_degree)?; let offset: Z = offset.into(); let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; let mut poly_z = PolyOverZq::from(&modulus); diff --git a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs index 81e7ff1f..80b1e55c 100644 --- a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs @@ -69,6 +69,7 @@ impl PolyOverZq { &s, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; for index in 0..=max_degree { diff --git a/src/integer_mod_q/poly_over_zq/sample/uniform.rs b/src/integer_mod_q/poly_over_zq/sample/uniform.rs index 7d291661..5e0a267c 100644 --- a/src/integer_mod_q/poly_over_zq/sample/uniform.rs +++ b/src/integer_mod_q/poly_over_zq/sample/uniform.rs @@ -59,7 +59,7 @@ impl PolyOverZq { let modulus = Modulus::from(&interval_size); let mut poly_zq = PolyOverZq::from(&modulus); - let mut uis = UniformIntegerSampler::init(&interval_size)?; + let mut uis = UniformIntegerSampler::init(&interval_size, None)?; for index in 0..=max_degree { let sample = uis.sample(); diff --git a/src/integer_mod_q/z_q/sample/binomial.rs b/src/integer_mod_q/z_q/sample/binomial.rs index 1c8e7333..01a3cf29 100644 --- a/src/integer_mod_q/z_q/sample/binomial.rs +++ b/src/integer_mod_q/z_q/sample/binomial.rs @@ -53,7 +53,7 @@ impl Zq { p: impl Into, ) -> Result { let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p)?; + let mut bin_sampler = BinomialSampler::init(n, p, None)?; let sample = bin_sampler.sample(); Ok(Zq::from((sample, modulus))) diff --git a/src/integer_mod_q/z_q/sample/discrete_gauss.rs b/src/integer_mod_q/z_q/sample/discrete_gauss.rs index 32af0751..0e114ddb 100644 --- a/src/integer_mod_q/z_q/sample/discrete_gauss.rs +++ b/src/integer_mod_q/z_q/sample/discrete_gauss.rs @@ -65,6 +65,7 @@ impl Zq { &s, unsafe { TAILCUT }, LookupTableSetting::NoLookup, + None, )?; let sample = dgis.sample_z(); diff --git a/src/integer_mod_q/z_q/sample/uniform.rs b/src/integer_mod_q/z_q/sample/uniform.rs index f173e57f..dd25dc5a 100644 --- a/src/integer_mod_q/z_q/sample/uniform.rs +++ b/src/integer_mod_q/z_q/sample/uniform.rs @@ -35,7 +35,7 @@ impl Zq { /// - if the given modulus is smaller than or equal to `1`. pub fn sample_uniform(modulus: impl Into) -> Self { let modulus: Z = modulus.into(); - let mut uis = UniformIntegerSampler::init(&modulus).unwrap(); + let mut uis = UniformIntegerSampler::init(&modulus, None).unwrap(); let random = uis.sample(); Zq::from((random, modulus)) diff --git a/src/utils/sample/binomial.rs b/src/utils/sample/binomial.rs index e7963537..fb8aa46d 100644 --- a/src/utils/sample/binomial.rs +++ b/src/utils/sample/binomial.rs @@ -9,15 +9,16 @@ //! This module includes core functionality to sample according to the //! binomial distribution. +use super::uniform::SamplerRng; use crate::{error::MathError, integer::Z, rational::Q}; -use rand::rngs::ThreadRng; use rand_distr::{Binomial, Distribution}; /// Enables sampling a [`Z`] according to the binomial distribution `Bin(n, p)`. /// /// Attributes: /// - `distr`: defines the binomial distribution with parameters `n` and `p` to sample from -/// - `rng`: defines the [`ThreadRng`] that's used to sample from `distr` +/// - `rng`: defines the [`ThreadRng`](rand::rngs::ThreadRng) or seeded +/// [`StdRng`](rand::rngs::StdRng) that's used to sample from `distr` /// /// # Examples /// ``` @@ -25,7 +26,7 @@ use rand_distr::{Binomial, Distribution}; /// let n = 2; /// let p = 0.5; /// -/// let mut bin_sampler = BinomialSampler::init(n, p).unwrap(); +/// let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); /// /// let sample = bin_sampler.sample(); /// @@ -34,17 +35,20 @@ use rand_distr::{Binomial, Distribution}; /// ``` pub struct BinomialSampler { distr: Binomial, - rng: ThreadRng, + rng: SamplerRng, } impl BinomialSampler { /// Initializes the [`BinomialSampler`] with /// - `distr` as the binomial distribution with `n` tries and success probability `p` for each try, and - /// - `rng` as a fresh [`ThreadRng`]. + /// - `rng` as a [`StdRng`](rand::rngs::StdRng) seeded with `seed` if `seed` is provided, + /// and as a fresh [`ThreadRng`](rand::rngs::ThreadRng) otherwise. /// /// Parameters: /// - `n`: specifies the number of tries /// - `p`: specifies the success probability + /// - `seed`: specifies an optional 256-bit seed for the internal [`StdRng`](rand::rngs::StdRng). + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. /// /// Returns a [`BinomialSampler`] or a [`MathError`] if `n < 0`, /// `p ∉ (0,1)`, or `n` does not fit into an [`i64`]. @@ -55,7 +59,11 @@ impl BinomialSampler { /// let n = 2; /// let p = 0.5; /// - /// let mut bin_sampler = BinomialSampler::init(n, p).unwrap(); + /// let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); + /// + /// let mut bin_sampler_seeded_0 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + /// let mut bin_sampler_seeded_1 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + /// assert_eq!(bin_sampler_seeded_0.sample(), bin_sampler_seeded_1.sample()); /// ``` /// /// # Errors and Failures @@ -65,7 +73,11 @@ impl BinomialSampler { /// if `p ∉ (0,1)`. /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) /// if `n` does not fit into an [`i64`]. - pub fn init(n: impl Into, p: impl Into) -> Result { + pub fn init( + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { let n = n.into(); let p = p.into(); @@ -86,7 +98,7 @@ impl BinomialSampler { let p = f64::from(&p); let distr = Binomial::new(n, p).unwrap(); - let rng = rand::rng(); + let rng = SamplerRng::new(seed); Ok(Self { distr, rng }) } @@ -99,7 +111,7 @@ impl BinomialSampler { /// let n = 2; /// let p = 0.5; /// - /// let mut bin_sampler = BinomialSampler::init(n, p).unwrap(); + /// let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); /// /// let sample = bin_sampler.sample(); /// @@ -121,7 +133,7 @@ mod test_binomial_sampler { fn keeps_range() { let n = 16; let p = 0.5; - let mut bin_sampler = BinomialSampler::init(n, p).unwrap(); + let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); for _ in 0..16 { let sample = bin_sampler.sample(); @@ -135,7 +147,7 @@ mod test_binomial_sampler { fn distribution() { let n = 2; let p = 0.5; - let mut bin_sampler = BinomialSampler::init(n, p).unwrap(); + let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); let mut counts = [0; 3]; // count sampled instances @@ -163,8 +175,8 @@ mod test_binomial_sampler { fn invalid_n() { let p = 0.5; - assert!(BinomialSampler::init(&Z::MINUS_ONE, p).is_err()); - assert!(BinomialSampler::init(Z::from(i64::MIN), p).is_err()); + assert!(BinomialSampler::init(&Z::MINUS_ONE, p, None).is_err()); + assert!(BinomialSampler::init(Z::from(i64::MIN), p, None).is_err()); } /// Checks whether invalid choices for p result in an error. @@ -172,9 +184,64 @@ mod test_binomial_sampler { fn invalid_p() { let n = 2; - assert!(BinomialSampler::init(n, &Q::MINUS_ONE).is_err()); - assert!(BinomialSampler::init(n, &Q::ZERO).is_err()); - assert!(BinomialSampler::init(n, &Q::ONE).is_err()); - assert!(BinomialSampler::init(n, Q::from(5)).is_err()); + assert!(BinomialSampler::init(n, &Q::MINUS_ONE, None).is_err()); + assert!(BinomialSampler::init(n, &Q::ZERO, None).is_err()); + assert!(BinomialSampler::init(n, &Q::ONE, None).is_err()); + assert!(BinomialSampler::init(n, Q::from(5), None).is_err()); + } +} + +#[cfg(test)] +mod test_binomial_sampler_seeded { + use super::{BinomialSampler, Q, Z}; + + /// Checks whether two samplers with the same seed output the same samples. + #[test] + fn same_seed_same_samples() { + let n = 1024; + let p = 0.5; + let mut bin_sampler_0 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + let mut bin_sampler_1 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + + for _ in 0..u8::MAX { + assert_eq!(bin_sampler_0.sample(), bin_sampler_1.sample()); + } + } + + /// Checks whether two samplers with different seeds output different samples. + #[test] + fn different_seed_different_samples() { + let n = 1024; + let p = 0.5; + let mut bin_sampler_0 = BinomialSampler::init(n, p, Some([0; 32])).unwrap(); + let mut bin_sampler_1 = BinomialSampler::init(n, p, Some([1; 32])).unwrap(); + + let samples_0: Vec = (0..16).map(|_| bin_sampler_0.sample()).collect(); + let samples_1: Vec = (0..16).map(|_| bin_sampler_1.sample()).collect(); + + assert_ne!(samples_0, samples_1); + } + + /// Checks whether seeded samples are kept in range. + #[test] + fn keeps_range() { + let n = 16; + let p = 0.5; + let mut bin_sampler = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + + for _ in 0..u8::MAX { + let sample = bin_sampler.sample(); + + assert!(Z::ZERO <= sample); + assert!(sample <= n); + } + } + + /// Checks whether invalid choices for n and p result in an error. + #[test] + fn invalid_parameters() { + assert!(BinomialSampler::init(&Z::MINUS_ONE, 0.5, Some([42; 32])).is_err()); + assert!(BinomialSampler::init(2, &Q::ZERO, Some([42; 32])).is_err()); + assert!(BinomialSampler::init(2, &Q::ONE, Some([42; 32])).is_err()); } } diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index bcc34009..b48ea746 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -16,7 +16,7 @@ //! In: Proceedings of the fortieth annual ACM symposium on Theory of computing. //! -use super::uniform::UniformIntegerSampler; +use super::uniform::{SamplerRng, UniformIntegerSampler}; use crate::{ error::{MathError, StringConversionError}, integer::{MatZ, Z}, @@ -66,6 +66,8 @@ pub static mut TAILCUT: f64 = 6.0; /// - `lookup_table_setting`: Specifies whether a lookup-table should be used and /// how it should be filled, i.e. lazily on-the-fly (impacting sampling time slightly) or precomputed /// - `table`: the lookup-table if one is used +/// - `rng`: defines the [`ThreadRng`](rand::rngs::ThreadRng) or seeded +/// [`StdRng`](rand::rngs::StdRng) that's used for sampling. /// /// # Examples /// ``` @@ -76,7 +78,7 @@ pub static mut TAILCUT: f64 = 6.0; /// let gaussian_parameter = 1.0; /// let tailcut = 6.0; /// -/// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::NoLookup).unwrap(); +/// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::NoLookup, None).unwrap(); /// /// let sample = dgis.sample_z(); /// ``` @@ -88,6 +90,8 @@ pub struct DiscreteGaussianIntegerSampler { pub interval_size: Z, pub lookup_table_setting: LookupTableSetting, pub table: HashMap, + #[serde(skip)] + rng: SamplerRng, } impl DiscreteGaussianIntegerSampler { @@ -97,27 +101,37 @@ impl DiscreteGaussianIntegerSampler { /// to the standard deviation `sigma * sqrt(2 * pi) = s`, /// - `lower_bound` as `⌈center - 6 * s⌉`, /// - `interval_size` as `⌊center + 6 * s⌋ - ⌈center - 6 * s⌉ + 1`, and - /// - `table` as an empty [`HashMap`] to store evaluations of the Gaussian function. + /// - `table` as an empty [`HashMap`] to store evaluations of the Gaussian function, and + /// - `rng` as a [`StdRng`](rand::rngs::StdRng) seeded with `seed` if `seed` is provided, + /// and as a fresh [`ThreadRng`](rand::rngs::ThreadRng) otherwise. /// /// Parameters: - /// - `n`: specifies the range from which is sampled /// - `center`: as the center of the discrete Gaussian to sample from /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` + /// - `tailcut`: specifies the number of Gaussian parameters `s` the interval + /// to sample from reaches from `center` to both sides + /// - `lookup_table_setting`: specifies whether a lookup-table should be used and + /// how it should be filled + /// - `seed`: specifies an optional 256-bit seed for the internal [`StdRng`](rand::rngs::StdRng). + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. /// - /// Returns a sample chosen according to the specified discrete Gaussian distribution or - /// a [`MathError`] if the specified parameters were not chosen appropriately, - /// i.e. `n > 1` or `s > 0`. + /// Returns a [`DiscreteGaussianIntegerSampler`] or a [`MathError`] + /// if the specified parameters were not chosen appropriately, + /// i.e. `tailcut < 0` or `s < 0`. /// /// # Examples /// ``` - /// use qfall_math::{integer::Z, rational::Q}; /// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; /// let center = 0.0; /// let gaussian_parameter = 1.0; /// let tailcut = 6.0; /// - /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute).unwrap(); + /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, None).unwrap(); + /// + /// let mut dgis_seeded_0 = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, Some([42; 32])).unwrap(); + /// let mut dgis_seeded_1 = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, Some([42; 32])).unwrap(); + /// assert_eq!(dgis_seeded_0.sample_z(), dgis_seeded_1.sample_z()); /// ``` /// /// # Errors and Failures @@ -128,6 +142,7 @@ impl DiscreteGaussianIntegerSampler { s: impl Into, tailcut: impl Into, lookup_table_setting: LookupTableSetting, + seed: Option<[u8; 32]>, ) -> Result { let center = center.into(); let mut s = s.into(); @@ -178,6 +193,7 @@ impl DiscreteGaussianIntegerSampler { interval_size, lookup_table_setting, table, + rng: SamplerRng::new(seed), }) } @@ -195,13 +211,14 @@ impl DiscreteGaussianIntegerSampler { /// let gaussian_parameter = 1.0; /// let tailcut = 6.0; /// - /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute).unwrap(); + /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, None).unwrap(); /// /// let sample = dgis.sample_z(); /// ``` pub fn sample_z(&mut self) -> Z { - let mut rng = rand::rng(); - let mut uis = UniformIntegerSampler::init(&self.interval_size).unwrap(); + // the clone of `self.rng` shares its state with `self.rng` + let mut uis = + UniformIntegerSampler::init_with_rng(&self.interval_size, self.rng.clone()).unwrap(); loop { // sample x in [c - s * tailcut, c + s * tailcut] let sample = &self.lower_bound + uis.sample(); @@ -224,7 +241,7 @@ impl DiscreteGaussianIntegerSampler { LookupTableSetting::Precompute => self.table.get(&sample).unwrap(), }; - let random_f64: f64 = rng.random(); + let random_f64: f64 = self.rng.random(); if evaluated_gauss_function >= &random_f64 { return sample; } @@ -408,6 +425,7 @@ pub(crate) fn sample_d_precomputed_gso( &s_2, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, )?; let z = dgis.sample_z(); @@ -441,6 +459,7 @@ mod test_discrete_gaussian_integer_sampler { &gaussian_parameter, 8.0, LookupTableSetting::FillOnTheFly, + None, ) .unwrap(); @@ -463,6 +482,7 @@ mod test_discrete_gaussian_integer_sampler { &gaussian_parameter, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, + None, ) .unwrap(); @@ -484,7 +504,8 @@ mod test_discrete_gaussian_integer_sampler { ¢er, &Q::MINUS_ONE, 6.0, - LookupTableSetting::FillOnTheFly + LookupTableSetting::FillOnTheFly, + None ) .is_err() ); @@ -493,7 +514,8 @@ mod test_discrete_gaussian_integer_sampler { ¢er, Q::from(i64::MIN), 6.0, - LookupTableSetting::FillOnTheFly + LookupTableSetting::FillOnTheFly, + None ) .is_err() ); @@ -510,7 +532,8 @@ mod test_discrete_gaussian_integer_sampler { ¢er, &gaussian_parameter, -0.1, - LookupTableSetting::FillOnTheFly + LookupTableSetting::FillOnTheFly, + None ) .is_err() ); @@ -519,7 +542,161 @@ mod test_discrete_gaussian_integer_sampler { ¢er, &gaussian_parameter, i64::MIN, - LookupTableSetting::FillOnTheFly + LookupTableSetting::FillOnTheFly, + None + ) + .is_err() + ); + } + + /// Checks whether a serialized and deserialized sampler still samples + /// from the same interval. + #[test] + fn serialize_deserialize() { + let dgis = DiscreteGaussianIntegerSampler::init( + Q::from(15), + Q::from((1, 2)), + 8.0, + LookupTableSetting::NoLookup, + Some([42; 32]), + ) + .unwrap(); + + let string = serde_json::to_string(&dgis).unwrap(); + let mut dgis: DiscreteGaussianIntegerSampler = serde_json::from_str(&string).unwrap(); + + for _ in 0..64 { + let sample = dgis.sample_z(); + + assert!(10 <= sample); + assert!(sample <= 20); + } + } +} + +#[cfg(test)] +mod test_discrete_gaussian_integer_sampler_seeded { + use super::DiscreteGaussianIntegerSampler; + use crate::{integer::Z, rational::Q, utils::sample::discrete_gauss::LookupTableSetting}; + + /// Checks whether two samplers with the same seed output the same samples + /// for every [`LookupTableSetting`]. + #[test] + fn same_seed_same_samples() { + let settings = [ + LookupTableSetting::NoLookup, + LookupTableSetting::FillOnTheFly, + LookupTableSetting::Precompute, + ]; + + for setting in settings { + let mut dgis_0 = + DiscreteGaussianIntegerSampler::init(Q::MINUS_ONE, 4, 6, setting, Some([42; 32])) + .unwrap(); + let mut dgis_1 = + DiscreteGaussianIntegerSampler::init(Q::MINUS_ONE, 4, 6, setting, Some([42; 32])) + .unwrap(); + + for _ in 0..u8::MAX { + assert_eq!(dgis_0.sample_z(), dgis_1.sample_z()); + } + } + } + + /// Checks whether two samplers with different seeds output different samples. + #[test] + fn different_seed_different_samples() { + let mut dgis_0 = DiscreteGaussianIntegerSampler::init( + 0, + 1024, + 6, + LookupTableSetting::NoLookup, + Some([0; 32]), + ) + .unwrap(); + let mut dgis_1 = DiscreteGaussianIntegerSampler::init( + 0, + 1024, + 6, + LookupTableSetting::NoLookup, + Some([1; 32]), + ) + .unwrap(); + + let samples_0: Vec = (0..16).map(|_| dgis_0.sample_z()).collect(); + let samples_1: Vec = (0..16).map(|_| dgis_1.sample_z()).collect(); + + assert_ne!(samples_0, samples_1); + } + + /// Checks whether a clone of a seeded sampler continues the stream of its original, + /// i.e. alternating between both yields the same samples as a single seeded sampler. + #[test] + fn clone_shares_state() { + let mut dgis = DiscreteGaussianIntegerSampler::init( + 0, + 16, + 6, + LookupTableSetting::NoLookup, + Some([42; 32]), + ) + .unwrap(); + let mut dgis_clone = dgis.clone(); + let mut dgis_cmp = DiscreteGaussianIntegerSampler::init( + 0, + 16, + 6, + LookupTableSetting::NoLookup, + Some([42; 32]), + ) + .unwrap(); + + for _ in 0..16 { + assert_eq!(dgis_cmp.sample_z(), dgis.sample_z()); + assert_eq!(dgis_cmp.sample_z(), dgis_clone.sample_z()); + } + } + + /// Checks whether seeded samples are kept in the correct interval. + #[test] + fn keeps_range() { + let mut dgis = DiscreteGaussianIntegerSampler::init( + Q::from(15), + Q::from((1, 2)), + 8.0, + LookupTableSetting::FillOnTheFly, + Some([42; 32]), + ) + .unwrap(); + + for _ in 0..64 { + let sample = dgis.sample_z(); + + assert!(10 <= sample); + assert!(sample <= 20); + } + } + + /// Checks whether invalid choices for `s` and `tailcut` result in an error. + #[test] + fn invalid_parameters() { + assert!( + DiscreteGaussianIntegerSampler::init( + 0, + &Q::MINUS_ONE, + 6.0, + LookupTableSetting::FillOnTheFly, + Some([42; 32]) + ) + .is_err() + ); + assert!( + DiscreteGaussianIntegerSampler::init( + 0, + 1, + -0.1, + LookupTableSetting::FillOnTheFly, + Some([42; 32]) ) .is_err() ); diff --git a/src/utils/sample/uniform.rs b/src/utils/sample/uniform.rs index a773f984..79733f2a 100644 --- a/src/utils/sample/uniform.rs +++ b/src/utils/sample/uniform.rs @@ -11,7 +11,79 @@ use crate::{error::MathError, integer::Z}; use flint3_sys::{fmpz_addmul_ui, fmpz_set_ui}; -use rand::{Rng, rngs::ThreadRng}; +use rand::{ + Rng, SeedableRng, TryCryptoRng, TryRng, + rngs::{StdRng, ThreadRng}, +}; +use std::{cell::RefCell, convert::Infallible, rc::Rc}; + +/// Defines the source of randomness used by the samplers in this module. +/// +/// A [`ThreadRng`] can not be seeded explicitly, as it is a handle to a +/// thread-local generator that is seeded and periodically reseeded by the OS. +/// Hence, seeded samplers use a [`StdRng`] instead, which uses the same +/// algorithm (ChaCha with 12 rounds) as [`ThreadRng`], but it's never reseeded. +/// +/// Cloning a [`SamplerRng`] returns a handle to the same generator for both variants, +/// i.e. a clone and its original draw from one shared stream of randomness. +/// +/// Variants: +/// - `Thread`: a handle to the [`ThreadRng`] of the current thread +/// - `Seeded`: a shared [`StdRng`] that was explicitly seeded +#[derive(Debug, Clone)] +pub(crate) enum SamplerRng { + Thread(ThreadRng), + Seeded(Rc>), +} + +impl SamplerRng { + /// Returns a [`SamplerRng`] containing a [`StdRng`] seeded with `seed` + /// if a seed is provided, and the [`ThreadRng`] of the current thread otherwise. + /// + /// Parameters: + /// - `seed`: specifies the optional 256-bit seed for the [`StdRng`] + pub(crate) fn new(seed: Option<[u8; 32]>) -> Self { + match seed { + Some(seed) => Self::Seeded(Rc::new(RefCell::new(StdRng::from_seed(seed)))), + None => Self::Thread(rand::rng()), + } + } +} + +impl Default for SamplerRng { + /// Returns a [`SamplerRng`] containing the [`ThreadRng`] of the current thread. + fn default() -> Self { + Self::new(None) + } +} + +impl TryRng for SamplerRng { + type Error = Infallible; + + fn try_next_u32(&mut self) -> Result { + match self { + Self::Thread(rng) => rng.try_next_u32(), + Self::Seeded(rng) => rng.borrow_mut().try_next_u32(), + } + } + + fn try_next_u64(&mut self) -> Result { + match self { + Self::Thread(rng) => rng.try_next_u64(), + Self::Seeded(rng) => rng.borrow_mut().try_next_u64(), + } + } + + fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> { + match self { + Self::Thread(rng) => rng.try_fill_bytes(dst), + Self::Seeded(rng) => rng.borrow_mut().try_fill_bytes(dst), + } + } +} + +// Both [`ThreadRng`] and [`StdRng`] are claimed to be cryptographically secure. +impl TryCryptoRng for SamplerRng {} /// Enables uniformly random sampling a [`Z`] in `[0, interval_size)`. /// @@ -21,14 +93,14 @@ use rand::{Rng, rngs::ThreadRng}; /// - `nr_iterations`: defines how many full samples of u32 are required /// - `upper_modulo`: is a power of two to remove superfluously sampled bits to increase /// the probability of accepting a sample to at least 1/2 -/// - `rng`: defines the [`ThreadRng`] that's used to sample uniform [u32] integers +/// - `rng`: defines the [`ThreadRng`] or seeded [`StdRng`] that's used to sample uniform [u32] integers /// /// # Examples /// ``` /// use qfall_math::{utils::sample::uniform::UniformIntegerSampler, integer::Z}; /// let interval_size = Z::from(20); /// -/// let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); +/// let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); /// /// let sample = uis.sample(); /// @@ -40,7 +112,7 @@ pub struct UniformIntegerSampler { two_pow_32: u64, nr_iterations: u32, upper_modulo: u32, - rng: ThreadRng, + rng: SamplerRng, } impl UniformIntegerSampler { @@ -49,11 +121,14 @@ impl UniformIntegerSampler { /// - `two_pow_32` as a [u64] containing 2^32 /// - `nr_iterations` as `(interval_size - 1).bits() / 32` floored /// - `upper_modulo` as 2^{(interval_size - 1).bits() mod 32} - /// - `rng` as a fresh [`ThreadRng`] + /// - `rng` as a [`StdRng`] seeded with `seed` if `seed` is provided, + /// and as a fresh [`ThreadRng`] otherwise /// /// Parameters: /// - `interval_size`: specifies the interval `[0, interval_size)` /// from which the samples are drawn + /// - `seed`: specifies an optional 256-bit seed for the internal [`StdRng`]. + /// If `None` is provided, a fresh [`ThreadRng`] is used instead. /// /// Returns a [`UniformIntegerSampler`] or a [`MathError`], /// if the interval size is chosen smaller than or equal to `1`. @@ -63,13 +138,35 @@ impl UniformIntegerSampler { /// use qfall_math::{utils::sample::uniform::UniformIntegerSampler, integer::Z}; /// let interval_size = Z::from(20); /// - /// let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); + /// let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); + /// + /// let mut uis_seeded_0 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); + /// let mut uis_seeded_1 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); + /// assert_eq!(uis_seeded_0.sample(), uis_seeded_1.sample()); /// ``` /// /// # Errors and Failures /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) /// if the interval is chosen smaller than `1`. - pub fn init(interval_size: &Z) -> Result { + pub fn init(interval_size: &Z, seed: Option<[u8; 32]>) -> Result { + Self::init_with_rng(interval_size, SamplerRng::new(seed)) + } + + /// Initializes the [`UniformIntegerSampler`] as described in [`UniformIntegerSampler::init`] + /// with `rng` as the source of randomness. + /// + /// Parameters: + /// - `interval_size`: specifies the interval `[0, interval_size)` + /// from which the samples are drawn + /// - `rng`: specifies the random number generator used as the source of randomness + /// + /// Returns a [`UniformIntegerSampler`] or a [`MathError`], + /// if the interval size is chosen smaller than or equal to `1`. + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the interval is chosen smaller than `1`. + pub(crate) fn init_with_rng(interval_size: &Z, rng: SamplerRng) -> Result { if interval_size < &Z::ONE { return Err(MathError::InvalidInterval(format!( "An invalid interval size {interval_size} was provided." @@ -91,8 +188,6 @@ impl UniformIntegerSampler { // defines how many bits will be discarded / have been sampled too much let upper_modulo = 2_u32.pow(bit_size % 32); - let rng = rand::rng(); - Ok(Self { interval_size: interval_size.clone(), two_pow_32, @@ -110,7 +205,7 @@ impl UniformIntegerSampler { /// use qfall_math::{utils::sample::uniform::UniformIntegerSampler, integer::Z}; /// let interval_size = Z::from(20); /// - /// let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); + /// let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); /// /// let sample = uis.sample(); /// @@ -140,7 +235,7 @@ impl UniformIntegerSampler { /// use qfall_math::{utils::sample::uniform::UniformIntegerSampler, integer::Z}; /// let interval = Z::from(u16::MAX) + 1; /// - /// let mut uis = UniformIntegerSampler::init(&interval).unwrap(); + /// let mut uis = UniformIntegerSampler::init(&interval, None).unwrap(); /// /// let sample = uis.sample_bits_uniform(); /// @@ -179,8 +274,8 @@ mod test_uis { let size_2 = Z::from(2); let size_7 = Z::from(7); - let mut uis_2 = UniformIntegerSampler::init(&size_2).unwrap(); - let mut uis_7 = UniformIntegerSampler::init(&size_7).unwrap(); + let mut uis_2 = UniformIntegerSampler::init(&size_2, None).unwrap(); + let mut uis_7 = UniformIntegerSampler::init(&size_7, None).unwrap(); for _ in 0..3 { let sample_2 = uis_2.sample(); @@ -199,8 +294,8 @@ mod test_uis { let size_0 = Z::from(u64::MAX); let size_1 = Z::from(u64::MAX) * 2 + 1; - let mut uis_0 = UniformIntegerSampler::init(&size_0).unwrap(); - let mut uis_1 = UniformIntegerSampler::init(&size_1).unwrap(); + let mut uis_0 = UniformIntegerSampler::init(&size_0, None).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&size_1, None).unwrap(); for _i in 0..u8::MAX { let sample_0 = uis_0.sample(); @@ -221,7 +316,7 @@ mod test_uis { for interval_size in interval_sizes { let interval = Z::from(interval_size); - let mut uis = UniformIntegerSampler::init(&interval).unwrap(); + let mut uis = UniformIntegerSampler::init(&interval, None).unwrap(); let mut samples = HashSet::new(); for _ in 0..2_u32.pow(interval_size) { @@ -240,8 +335,8 @@ mod test_uis { /// Checks whether interval sizes smaller than 2 result in an error. #[test] fn invalid_interval() { - assert!(UniformIntegerSampler::init(&Z::ZERO).is_err()); - assert!(UniformIntegerSampler::init(&Z::MINUS_ONE).is_err()); + assert!(UniformIntegerSampler::init(&Z::ZERO, None).is_err()); + assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, None).is_err()); } /// Checks whether random bit sampling doesn't fill more bits than required. @@ -251,9 +346,9 @@ mod test_uis { let size_1 = Z::from(256); let size_2 = Z::from(u32::MAX) + Z::ONE; - let mut uis_0 = UniformIntegerSampler::init(&size_0).unwrap(); - let mut uis_1 = UniformIntegerSampler::init(&size_1).unwrap(); - let mut uis_2 = UniformIntegerSampler::init(&size_2).unwrap(); + let mut uis_0 = UniformIntegerSampler::init(&size_0, None).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&size_1, None).unwrap(); + let mut uis_2 = UniformIntegerSampler::init(&size_2, None).unwrap(); for _ in 0..u8::MAX { let sample_0 = uis_0.sample_bits_uniform(); @@ -269,3 +364,59 @@ mod test_uis { } } } + +#[cfg(test)] +mod test_uis_seeded { + use super::{UniformIntegerSampler, Z}; + + /// Checks whether two samplers with the same seed output the same samples + /// for small and large interval sizes. + #[test] + fn same_seed_same_samples() { + let interval_sizes = [Z::from(2), Z::from(7), Z::from(u64::MAX) * 2 + 1]; + + for interval_size in interval_sizes { + let mut uis_0 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); + + for _ in 0..u8::MAX { + assert_eq!(uis_0.sample(), uis_1.sample()); + } + } + } + + /// Checks whether two samplers with different seeds output different samples. + #[test] + fn different_seed_different_samples() { + let interval_size = Z::from(u64::MAX); + + let mut uis_0 = UniformIntegerSampler::init(&interval_size, Some([0; 32])).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&interval_size, Some([1; 32])).unwrap(); + + let samples_0: Vec = (0..16).map(|_| uis_0.sample()).collect(); + let samples_1: Vec = (0..16).map(|_| uis_1.sample()).collect(); + + assert_ne!(samples_0, samples_1); + } + + /// Checks whether seeded samples are kept in the interval. + #[test] + fn keeps_range() { + let interval_size = Z::from(7); + let mut uis = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); + + for _ in 0..u8::MAX { + let sample = uis.sample(); + + assert!(Z::ZERO <= sample); + assert!(sample < interval_size); + } + } + + /// Checks whether interval sizes smaller than 1 result in an error. + #[test] + fn invalid_interval() { + assert!(UniformIntegerSampler::init(&Z::ZERO, Some([42; 32])).is_err()); + assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, Some([42; 32])).is_err()); + } +} From f680b5c813f20e82a0d7af1412bb376ea0c45351 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Fri, 9 Oct 2026 15:54:33 +0100 Subject: [PATCH 2/5] Further optionally seeded functions --- src/integer/mat_z/sample/discrete_gauss.rs | 4 +- .../mat_zq/sample/discrete_gauss.rs | 2 + src/utils/sample/binomial.rs | 4 +- src/utils/sample/discrete_gauss.rs | 136 ++++++++++++++---- src/utils/sample/uniform.rs | 12 +- 5 files changed, 121 insertions(+), 37 deletions(-) diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index 16124017..fc3bd513 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -116,7 +116,7 @@ impl MatZ { pub fn sample_d(basis: &MatZ, center: &MatQ, s: impl Into) -> Result { let s: Q = s.into(); - sample_d(basis, center, &s) + sample_d(basis, center, &s, None) } /// Samples a non-spherical discrete Gaussian depending on your choice of @@ -231,7 +231,7 @@ impl MatZ { ) -> Result { let s: Q = s.into(); - sample_d_precomputed_gso(basis, basis_gso, center, &s) + sample_d_precomputed_gso(basis, basis_gso, center, &s, None) } } diff --git a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs index ee52d100..4e618383 100644 --- a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs @@ -125,6 +125,7 @@ impl MatZq { &basis.get_representative_least_nonnegative_residue(), center, &s, + None, )?; Ok(MatZq::from((&sample, basis.get_mod()))) @@ -186,6 +187,7 @@ impl MatZq { basis_gso, center, &s, + None, )?; Ok(MatZq::from((&sample, basis.get_mod()))) diff --git a/src/utils/sample/binomial.rs b/src/utils/sample/binomial.rs index fb8aa46d..a700c151 100644 --- a/src/utils/sample/binomial.rs +++ b/src/utils/sample/binomial.rs @@ -34,8 +34,8 @@ use rand_distr::{Binomial, Distribution}; /// assert!(sample <= n); /// ``` pub struct BinomialSampler { - distr: Binomial, - rng: SamplerRng, + pub distr: Binomial, + pub rng: SamplerRng, } impl BinomialSampler { diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index b48ea746..71e9e96c 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -23,7 +23,7 @@ use crate::{ rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixGetSubmatrix, Pow}, }; -use rand::RngExt; +use rand::{RngExt, SeedableRng, rngs::StdRng}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -91,7 +91,7 @@ pub struct DiscreteGaussianIntegerSampler { pub lookup_table_setting: LookupTableSetting, pub table: HashMap, #[serde(skip)] - rng: SamplerRng, + pub rng: SamplerRng, } impl DiscreteGaussianIntegerSampler { @@ -293,6 +293,8 @@ pub fn gaussian_function(x: &Z, c: &Q, s: &Q) -> f64 { /// - `center`: specifies the positions of the center with peak probability /// - `s`: specifies the Gaussian parameter, which is proportional /// to the standard deviation `sigma * sqrt(2 * pi) = s` +/// - `seed`: specifies an optional 256-bit seed for the PRNG used during sampling. +/// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. /// /// Returns a vector with discrete gaussian error based on a lattice point /// as in [\[1\]](): SampleD or a [`MathError`], if the @@ -308,7 +310,7 @@ pub fn gaussian_function(x: &Z, c: &Q, s: &Q) -> f64 { /// let center = MatQ::new(5, 1); /// let gaussian_parameter = Q::ONE; /// -/// let sample = sample_d(basis, &n, ¢er, &gaussian_parameter).unwrap(); +/// let sample = sample_d(basis, &n, ¢er, &gaussian_parameter, None).unwrap(); /// ``` /// /// # Errors and Failures @@ -318,9 +320,14 @@ pub fn gaussian_function(x: &Z, c: &Q, s: &Q) -> f64 { /// if the number of rows of the `basis` and `center` differ. /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) /// if `center` is not a column vector. -pub(crate) fn sample_d(basis: &MatZ, center: &MatQ, s: &Q) -> Result { +pub(crate) fn sample_d( + basis: &MatZ, + center: &MatQ, + s: &Q, + seed: Option<[u8; 32]>, +) -> Result { let basis_gso = MatQ::from(basis).gso(); - sample_d_precomputed_gso(basis, &basis_gso, center, s) + sample_d_precomputed_gso(basis, &basis_gso, center, s, seed) } /// SampleD samples a discrete Gaussian from the lattice with `basis` using [`sample_z`] as a subroutine. @@ -336,6 +343,8 @@ pub(crate) fn sample_d(basis: &MatZ, center: &MatQ, s: &Q) -> Result): SampleD or a [`MathError`], if the @@ -353,7 +362,7 @@ pub(crate) fn sample_d(basis: &MatZ, center: &MatQ, s: &Q) -> Result, ) -> Result { let mut center = center.clone(); assert_eq!( @@ -408,6 +418,9 @@ pub(crate) fn sample_d_precomputed_gso( let mut out = MatZ::new(basis_gso.get_num_rows(), 1); + // derives an independent seed for the sampler of each dimension if a seed is provided + let mut seed_rng = seed.map(StdRng::from_seed); + for i in (0..basis_gso.get_num_columns()).rev() { // basisvector_i = b_tilde[i] let basisvector_orth_i = unsafe { basis_gso.get_column_unchecked(i) }; @@ -425,7 +438,7 @@ pub(crate) fn sample_d_precomputed_gso( &s_2, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, - None, + seed_rng.as_mut().map(|rng| rng.random()), )?; let z = dgis.sample_z(); @@ -794,8 +807,9 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + .unwrap(); } /// Ensures that `sample_d` works properly for a non-zero center. @@ -806,8 +820,9 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + .unwrap(); } /// Ensures that `sample_d` works properly for a different basis. @@ -818,8 +833,9 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let _ = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); + let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + .unwrap(); } /// Ensures that `sample_d` outputs a vector that's part of the specified lattice. @@ -834,9 +850,10 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let sample = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); + let sample = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); let sample_prec = - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter).unwrap(); + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + .unwrap(); // check whether hermite normal form of HNF(b) = HNF([b|sample_vector]) let basis_concat_sample = basis.concat_horizontal(&sample).unwrap(); @@ -890,11 +907,16 @@ mod test_sample_d { let center = MatQ::new(5, 1); let basis_gso = MatQ::from(&basis).gso(); - assert!(sample_d(&basis, ¢er, &Q::MINUS_ONE).is_err()); - assert!(sample_d(&basis, ¢er, &Q::from(i64::MIN)).is_err()); + assert!(sample_d(&basis, ¢er, &Q::MINUS_ONE, None).is_err()); + assert!(sample_d(&basis, ¢er, &Q::from(i64::MIN), None).is_err()); - assert!(sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::MINUS_ONE).is_err()); - assert!(sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::from(i64::MIN)).is_err()); + assert!( + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::MINUS_ONE, None).is_err() + ); + assert!( + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::from(i64::MIN), None) + .is_err() + ); } /// Checks whether `sample_d` returns an error if the basis and center number of rows differs. @@ -905,8 +927,9 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let res = sample_d(&basis, ¢er, &gaussian_parameter); - let res_prec = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter); + let res = sample_d(&basis, ¢er, &gaussian_parameter, None); + let res_prec = + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None); assert!(res.is_err()); assert!(res_prec.is_err()); @@ -920,8 +943,9 @@ mod test_sample_d { let gaussian_parameter = Q::ONE; let basis_gso = MatQ::from(&basis).gso(); - let res = sample_d(&basis, ¢er, &gaussian_parameter); - let res_prec = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter); + let res = sample_d(&basis, ¢er, &gaussian_parameter, None); + let res_prec = + sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None); assert!(res.is_err()); assert!(res_prec.is_err()); @@ -951,9 +975,10 @@ mod test_sample_d { len * n.log(2).unwrap().sqrt() * (n.log(2).unwrap().log(2).unwrap()); for _ in 0..20 { - let res = sample_d(&basis, ¢er, &gaussian_parameter).unwrap(); + let res = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); let res_prec = - sample_d_precomputed_gso(&basis, &orth, ¢er, &gaussian_parameter).unwrap(); + sample_d_precomputed_gso(&basis, &orth, ¢er, &gaussian_parameter, None) + .unwrap(); assert!( res.norm_eucl_sqrd().unwrap() <= gaussian_parameter.pow(2).unwrap().round() * &n, @@ -967,6 +992,63 @@ mod test_sample_d { } } + /// Ensures that `sample_d` and `sample_d_precomputed_gso` output the same vector + /// for the same seed. + #[test] + fn same_seed_same_sample() { + let basis = MatZ::from_str("[[7, 0, 1],[7, 3, 2],[0, 1, 5]]").unwrap(); + let center = MatQ::from_str("[[1/2],[-3],[5]]").unwrap(); + let gaussian_parameter = Q::from(100); + let basis_gso = MatQ::from(&basis).gso(); + + let sample_0 = sample_d(&basis, ¢er, &gaussian_parameter, Some([42; 32])).unwrap(); + let sample_1 = sample_d(&basis, ¢er, &gaussian_parameter, Some([42; 32])).unwrap(); + let sample_prec = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + Some([42; 32]), + ) + .unwrap(); + + assert_eq!(sample_0, sample_1); + assert_eq!(sample_0, sample_prec); + } + + /// Ensures that `sample_d` outputs different vectors for different seeds. + #[test] + fn different_seed_different_sample() { + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let gaussian_parameter = Q::from(1024); + + let sample_0 = sample_d(&basis, ¢er, &gaussian_parameter, Some([0; 32])).unwrap(); + let sample_1 = sample_d(&basis, ¢er, &gaussian_parameter, Some([1; 32])).unwrap(); + + assert_ne!(sample_0, sample_1); + } + + /// Ensures that seeded samples are still part of the specified lattice. + #[test] + fn seeded_point_of_lattice() { + use crate::traits::MatrixGetEntry; + + let basis = MatZ::from_str("[[7, 0],[7, 3]]").unwrap(); + let center = MatQ::new(2, 1); + let gaussian_parameter = Q::from(10); + + let sample = sample_d(&basis, ¢er, &gaussian_parameter, Some([42; 32])).unwrap(); + + // the basis is invertible, i.e. sample is a lattice point iff + // its coefficients w.r.t. the basis are integral + let coefficients = MatQ::from(&basis).inverse().unwrap() * MatQ::from(&sample); + for i in 0..coefficients.get_num_rows() { + let entry: Q = coefficients.get_entry(i, 0).unwrap(); + assert_eq!(entry.get_denominator(), Z::ONE); + } + } + /// Ensure that an orthogonalized base with not matching rows panics. #[test] #[should_panic] @@ -976,7 +1058,7 @@ mod test_sample_d { let center = MatQ::new(&n, 1); let false_gso = MatQ::new(basis.get_num_rows() + 1, basis.get_num_columns()); - let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5)).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5), None).unwrap(); } /// Ensure that an orthogonalized base with not matching columns panics. #[test] @@ -987,6 +1069,6 @@ mod test_sample_d { let center = MatQ::new(&n, 1); let false_gso = MatQ::new(basis.get_num_rows(), basis.get_num_columns() + 1); - let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5)).unwrap(); + let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5), None).unwrap(); } } diff --git a/src/utils/sample/uniform.rs b/src/utils/sample/uniform.rs index 79733f2a..c2c312fc 100644 --- a/src/utils/sample/uniform.rs +++ b/src/utils/sample/uniform.rs @@ -31,7 +31,7 @@ use std::{cell::RefCell, convert::Infallible, rc::Rc}; /// - `Thread`: a handle to the [`ThreadRng`] of the current thread /// - `Seeded`: a shared [`StdRng`] that was explicitly seeded #[derive(Debug, Clone)] -pub(crate) enum SamplerRng { +pub enum SamplerRng { Thread(ThreadRng), Seeded(Rc>), } @@ -108,11 +108,11 @@ impl TryCryptoRng for SamplerRng {} /// assert!(sample < interval_size); /// ``` pub struct UniformIntegerSampler { - interval_size: Z, - two_pow_32: u64, - nr_iterations: u32, - upper_modulo: u32, - rng: SamplerRng, + pub interval_size: Z, + pub two_pow_32: u64, + pub nr_iterations: u32, + pub upper_modulo: u32, + pub rng: SamplerRng, } impl UniformIntegerSampler { From 3b2b2d3ce5f327ae38dd1eee7ebc670383dec870 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Fri, 9 Oct 2026 17:33:53 +0100 Subject: [PATCH 3/5] Add seeded + unseeded public functions via macro --- .../mat_poly_over_z/sample/binomial.rs | 547 ++++++++++--- .../mat_poly_over_z/sample/discrete_gauss.rs | 422 +++++++--- src/integer/mat_poly_over_z/sample/uniform.rs | 322 ++++++-- src/integer/mat_z/sample/binomial.rs | 371 ++++++--- src/integer/mat_z/sample/discrete_gauss.rs | 758 +++++++++++++----- src/integer/mat_z/sample/uniform.rs | 245 ++++-- src/integer/poly_over_z/sample/binomial.rs | 359 ++++++--- .../poly_over_z/sample/discrete_gauss.rs | 174 ++-- src/integer/poly_over_z/sample/uniform.rs | 230 ++++-- src/integer/z/sample/binomial.rs | 116 ++- src/integer/z/sample/discrete_gauss.rs | 187 +++-- src/integer/z/sample/uniform.rs | 457 ++++++++--- .../sample/uniform.rs | 175 ++-- .../mat_polynomial_ring_zq/sample/binomial.rs | 677 +++++++++++++--- .../sample/discrete_gauss.rs | 393 ++++++--- .../mat_polynomial_ring_zq/sample/uniform.rs | 238 ++++-- src/integer_mod_q/mat_zq/sample/binomial.rs | 404 +++++++--- .../mat_zq/sample/discrete_gauss.rs | 546 +++++++++---- src/integer_mod_q/mat_zq/sample/uniform.rs | 204 ++++- .../ntt_polynomial_ring_zq/sample/uniform.rs | 143 +++- .../poly_over_zq/sample/binomial.rs | 382 ++++++--- .../poly_over_zq/sample/discrete_gauss.rs | 229 ++++-- .../poly_over_zq/sample/uniform.rs | 226 ++++-- .../polynomial_ring_zq/sample/binomial.rs | 371 ++++++--- .../sample/discrete_gauss.rs | 231 ++++-- .../polynomial_ring_zq/sample/uniform.rs | 194 ++++- src/integer_mod_q/z_q/sample/binomial.rs | 130 ++- .../z_q/sample/discrete_gauss.rs | 207 +++-- src/integer_mod_q/z_q/sample/uniform.rs | 175 +++- src/macros.rs | 1 + src/macros/seeded.rs | 147 ++++ src/rational/mat_q/rounding.rs | 120 ++- src/rational/mat_q/sample/gauss.rs | 299 +++++-- src/rational/poly_over_q/rounding.rs | 126 ++- src/rational/poly_over_q/sample/gauss.rs | 190 +++-- src/rational/q/rounding.rs | 101 ++- src/rational/q/sample/gauss.rs | 130 ++- src/utils/sample.rs | 19 + src/utils/sample/binomial.rs | 2 +- src/utils/sample/discrete_gauss.rs | 49 +- src/utils/sample/uniform.rs | 13 +- 41 files changed, 7823 insertions(+), 2487 deletions(-) create mode 100644 src/macros/seeded.rs diff --git a/src/integer/mat_poly_over_z/sample/binomial.rs b/src/integer/mat_poly_over_z/sample/binomial.rs index af645f4a..c86352fb 100644 --- a/src/integer/mat_poly_over_z/sample/binomial.rs +++ b/src/integer/mat_poly_over_z/sample/binomial.rs @@ -12,6 +12,7 @@ use crate::{ error::MathError, integer::{MatPolyOverZ, PolyOverZ, Z}, + macros::seeded::seedable_function, rational::Q, traits::{MatrixDimensions, MatrixSetEntry, SetCoefficient}, utils::{index::evaluate_index, sample::binomial::BinomialSampler}, @@ -19,113 +20,139 @@ use crate::{ use std::fmt::Display; impl MatPolyOverZ { - /// Outputs a [`MatPolyOverZ`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, - /// i.e. the maximum number of coefficients any polynomial in the matrix can have - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatPolyOverZ`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatPolyOverZ; - /// - /// let sample = MatPolyOverZ::sample_binomial(2, 2, 5, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0` or `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolyOverZ::new`]. - pub fn sample_binomial( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - max_degree: impl TryInto + Display, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(num_rows, num_cols, max_degree, 0, n, p) - } - - /// Outputs a [`MatPolyOverZ`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, - /// i.e. the maximum number of coefficients any polynomial in the matrix can have - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatPolyOverZ`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatPolyOverZ; - /// - /// let sample = MatPolyOverZ::sample_binomial_with_offset(2, 2, 5, -1, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0` or `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolyOverZ::new`]. - pub fn sample_binomial_with_offset( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - max_degree: impl TryInto + Display, - offset: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree)?; - let offset: Z = offset.into(); - let mut bin_sampler = BinomialSampler::init(n, p, None)?; - let mut matrix = MatPolyOverZ::new(num_rows, num_cols); - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let mut poly_z = PolyOverZ::default(); - - for index in 0..=max_degree { - let mut sample = bin_sampler.sample(); - sample += &offset; - unsafe { poly_z.set_coeff_unchecked(index, sample) }; + seedable_function!( + /// Outputs a [`MatPolyOverZ`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, + /// i.e. the maximum number of coefficients any polynomial in the matrix can have + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatPolyOverZ`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatPolyOverZ; + /// + #[unseeded] + /// let sample = MatPolyOverZ::sample_binomial(2, 2, 5, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatPolyOverZ::sample_binomial_seeded(2, 2, 5, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0` or `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolyOverZ::new`]. + pub(crate) fn sample_binomial( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + max_degree: impl TryInto + Display, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded( + num_rows, num_cols, max_degree, 0, n, p, seed, + ) + } + ); + + seedable_function!( + /// Outputs a [`MatPolyOverZ`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, + /// i.e. the maximum number of coefficients any polynomial in the matrix can have + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatPolyOverZ`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatPolyOverZ; + /// + #[unseeded] + /// let sample = MatPolyOverZ::sample_binomial_with_offset(2, 2, 5, -1, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatPolyOverZ::sample_binomial_with_offset_seeded(2, 2, 5, -1, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0` or `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolyOverZ::new`]. + pub(crate) fn sample_binomial_with_offset( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + max_degree: impl TryInto + Display, + offset: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree)?; + let offset: Z = offset.into(); + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; + let mut matrix = MatPolyOverZ::new(num_rows, num_cols); + + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let mut poly_z = PolyOverZ::default(); + + for index in 0..=max_degree { + let mut sample = bin_sampler.sample(); + sample += &offset; + unsafe { poly_z.set_coeff_unchecked(index, sample) }; + } + + unsafe { matrix.set_entry_unchecked(row, col, poly_z) }; } - - unsafe { matrix.set_entry_unchecked(row, col, poly_z) }; } - } - Ok(matrix) - } + Ok(matrix) + } + ); } #[cfg(test)] @@ -222,6 +249,114 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{MatPolyOverZ, Q, Z}; + use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let matrix = + MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 2, 0.5, test_seed()).unwrap(); + let entry = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= poly); + assert!(poly <= 2); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = + MatPolyOverZ::sample_binomial_seeded(1, 1, degree, 256, 0.99999, test_seed()) + .unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree(), + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatPolyOverZ::sample_binomial_seeded(0, 3, 0, 1, 0.5, test_seed()); + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = MatPolyOverZ::sample_binomial_seeded(1, 1, -1, 2, 0.5, test_seed()); + let res_1 = MatPolyOverZ::sample_binomial_seeded(1, 1, i64::MIN, 2, 0.5, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u16, 1u32, 7u16, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u32, 1u64, 7u32, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u64, 1i8, 7u64, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i8, 1i16, 7i8, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i16, 1i32, 7i16, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i32, 1i64, 7i32, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i64, Z::ONE, 7i64, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 1u8, 0.5f32, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, Z::ZERO, 1u8, 0.5f64, test_seed()); + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = MatPolyOverZ::sample_binomial_seeded(3, 3, 0, 1, 0.5, test_seed()).unwrap(); + let mat_1 = MatPolyOverZ::sample_binomial_seeded(4, 1, 0, 1, 0.5, test_seed()).unwrap(); + let mat_2 = MatPolyOverZ::sample_binomial_seeded(1, 5, 0, 1, 0.5, test_seed()).unwrap(); + let mat_3 = MatPolyOverZ::sample_binomial_seeded(15, 20, 0, 1, 0.5, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatPolyOverZ; + let sample_0 = MatPolyOverZ::sample_binomial_seeded(2, 2, 5, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = MatPolyOverZ::sample_binomial_seeded(2, 2, 5, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatPolyOverZ, Q, Z}; @@ -316,3 +451,207 @@ mod test_sample_binomial_with_offset { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{MatPolyOverZ, Q, Z}; + use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let matrix = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0, -1, 2, 0.5, test_seed()) + .unwrap(); + let entry = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); + + assert!(Z::MINUS_ONE <= poly); + assert!(poly <= Z::ONE); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + degree, + -1, + 256, + 0.99999, + test_seed(), + ) + .unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree(), + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded(0, 3, 0, 0, 1, 0.5, test_seed()); + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, -1, -1, 2, 0.5, test_seed()); + let res_1 = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + i64::MIN, + -1, + 2, + 0.5, + test_seed(), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0u8, -1, 1u16, 7u8, test_seed()); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0u16, + 0, + 1u32, + 7u16, + test_seed(), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0u32, + Z::ONE, + 1u64, + 7u32, + test_seed(), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0u64, + Z::MINUS_ONE, + 1i8, + 7u64, + test_seed(), + ); + let _ = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0i8, -1, 1i16, 7i8, test_seed()); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0i16, + -1, + 1i32, + 7i16, + test_seed(), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0i32, + -1, + 1i64, + 7i32, + test_seed(), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0i64, + -1, + Z::ONE, + 7i64, + test_seed(), + ); + let _ = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0, -1, 1u8, 0.5f32, test_seed()); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + Z::ZERO, + -1, + 1u8, + 0.5f64, + test_seed(), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0, + -1, + 1, + Q::from((1, 2)), + test_seed(), + ); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = + MatPolyOverZ::sample_binomial_with_offset_seeded(3, 3, 0, -1, 1, 0.5, test_seed()) + .unwrap(); + let mat_1 = + MatPolyOverZ::sample_binomial_with_offset_seeded(4, 1, 0, -1, 1, 0.5, test_seed()) + .unwrap(); + let mat_2 = + MatPolyOverZ::sample_binomial_with_offset_seeded(1, 5, 0, -1, 1, 0.5, test_seed()) + .unwrap(); + let mat_3 = + MatPolyOverZ::sample_binomial_with_offset_seeded(15, 20, 0, -1, 1, 0.5, test_seed()) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatPolyOverZ; + let sample_0 = + MatPolyOverZ::sample_binomial_with_offset_seeded(2, 2, 5, -1, 2, 0.5, [42; 32]) + .unwrap(); + let sample_1 = + MatPolyOverZ::sample_binomial_with_offset_seeded(2, 2, 5, -1, 2, 0.5, [42; 32]) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs index 467be192..160cdea0 100644 --- a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs @@ -11,6 +11,7 @@ use crate::{ error::MathError, integer::{MatPolyOverZ, MatZ, PolyOverZ}, + macros::seeded::seedable_function, rational::{PolyOverQ, Q}, traits::{ Concatenate, FromCoefficientEmbedding, IntoCoefficientEmbedding, MatrixDimensions, @@ -24,136 +25,161 @@ use crate::{ use std::fmt::Display; impl MatPolyOverZ { - /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry - /// sampled independently according to the discrete Gaussian distribution, - /// using [`PolyOverZ::sample_discrete_gauss`]. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a [`MatPolyOverZ`] with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatPolyOverZ; - /// - /// let matrix = MatPolyOverZ::sample_discrete_gauss(3, 1, 5, 0, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolyOverZ::new`]. - /// - if `max_degree` is negative, or does not fit into an [`i64`]. - pub fn sample_discrete_gauss( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - max_degree: impl TryInto + Display, - center: impl Into, - s: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree).unwrap(); - let mut matrix = MatPolyOverZ::new(num_rows, num_cols); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - center, - s, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - )?; - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let mut entry = PolyOverZ::default(); - for index in 0..=max_degree { - let sample = dgis.sample_z(); - unsafe { entry.set_coeff_unchecked(index, &sample) }; - } + seedable_function!( + /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry + /// sampled independently according to the discrete Gaussian distribution, + /// using [`PolyOverZ::sample_discrete_gauss`]. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a [`MatPolyOverZ`] with each entry sampled independently from the + /// specified discrete Gaussian distribution or an error if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatPolyOverZ; + /// + #[unseeded] + /// let matrix = MatPolyOverZ::sample_discrete_gauss(3, 1, 5, 0, 1.25f32).unwrap(); + #[seeded] + /// let matrix = MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolyOverZ::new`]. + /// - if `max_degree` is negative, or does not fit into an [`i64`]. + pub(crate) fn sample_discrete_gauss( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + max_degree: impl TryInto + Display, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree).unwrap(); + let mut matrix = MatPolyOverZ::new(num_rows, num_cols); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + center, + s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seed, + )?; - unsafe { matrix.set_entry_unchecked(row, col, entry) }; + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let mut entry = PolyOverZ::default(); + for index in 0..=max_degree { + let sample = dgis.sample_z(); + unsafe { entry.set_coeff_unchecked(index, &sample) }; + } + + unsafe { matrix.set_entry_unchecked(row, col, entry) }; + } } + + Ok(matrix) } + ); - Ok(matrix) - } + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis. Hence, the callee is + /// responsible for making sure that `basis` provides a suitable basis. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `k`: the maximal length the polynomial can have + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a vector of polynomials sampled according to the + /// discrete Gaussian distribution or an error if the basis is not a row vector, + /// `s < 0`, or the number of rows of the `basis` and `center` differ. + /// + /// # Example + /// ``` + /// use qfall_math::{ + /// integer::MatPolyOverZ, + /// rational::PolyOverQ, + /// }; + /// use std::str::FromStr; + /// + /// let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap(); + /// let center = vec![PolyOverQ::default()]; + /// + #[unseeded] + /// let sample = MatPolyOverZ::sample_d(&basis, 3, ¢er, 10.5_f64).unwrap(); + #[seeded] + /// let sample = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), + /// if the basis is not a row vector. + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + /// + /// # Panics ... + /// - if the polynomials have higher length than the provided upper bound `k` + pub(crate) fn sample_d( + basis: &Self, + k: impl Into, + center: &[PolyOverQ], + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let k = k.into(); + // use coefficient embedding and then call sampleD for the matrix representation + let basis_embedded = basis.into_coefficient_embedding(k); - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis. Hence, the callee is - /// responsible for making sure that `basis` provides a suitable basis. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `k`: the maximal length the polynomial can have - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a vector of polynomials sampled according to the - /// discrete Gaussian distribution or an error if the basis is not a row vector, - /// `s < 0`, or the number of rows of the `basis` and `center` differ. - /// - /// # Example - /// ``` - /// use qfall_math::{ - /// integer::MatPolyOverZ, - /// rational::PolyOverQ, - /// }; - /// use std::str::FromStr; - /// - /// let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap(); - /// let center = vec![PolyOverQ::default()]; - /// - /// let sample = MatPolyOverZ::sample_d(&basis, 3, ¢er, 10.5_f64).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), - /// if the basis is not a row vector. - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - /// - /// # Panics ... - /// - if the polynomials have higher length than the provided upper bound `k` - pub fn sample_d( - basis: &Self, - k: impl Into, - center: &[PolyOverQ], - s: impl Into, - ) -> Result { - let k = k.into(); - // use coefficient embedding and then call sampleD for the matrix representation - let basis_embedded = basis.into_coefficient_embedding(k); - - // use coefficient embedding to get center - let mut center_embedded = center[0].into_coefficient_embedding(k); - for row in center.iter().skip(1) { - let c_row = row.into_coefficient_embedding(k); - center_embedded = center_embedded.concat_vertical(&c_row)?; - } + // use coefficient embedding to get center + let mut center_embedded = center[0].into_coefficient_embedding(k); + for row in center.iter().skip(1) { + let c_row = row.into_coefficient_embedding(k); + center_embedded = center_embedded.concat_vertical(&c_row)?; + } - let sample = MatZ::sample_d(&basis_embedded, ¢er_embedded, s)?; + let sample = + MatZ::sample_d_optionally_seeded(&basis_embedded, ¢er_embedded, s, seed)?; - Ok(MatPolyOverZ::from_coefficient_embedding((&sample, k - 1))) - } + Ok(MatPolyOverZ::from_coefficient_embedding((&sample, k - 1))) + } + ); } #[cfg(test)] @@ -203,6 +229,69 @@ mod test_sample_discrete_gauss { } } +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::MatPolyOverZ, traits::MatrixGetEntry}; + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 3, 128, 1u16, 2, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(1, 3, 128, 1u8, 2.0, test_seed()); + let _ = + MatPolyOverZ::sample_discrete_gauss_seeded(2_i64, 3, 128, 1u32, 2.0_f64, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i32, 3, 128, 1u64, 1, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i16, 3, 128, 1i64, 3_u64, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i8, 3, 128, 1i32, 1, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u64, 3, 128, 1i16, 1, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u32, 3, 128, 1i8, 1, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u16, 3, 128, 1i64, 2, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u8, 3, 128, -2, 3, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(1, 3, 128, 4, 3, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(3, 3, 128, 1.25f64, 3, test_seed()); + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(4, 3, 128, 15.75f32, 3, test_seed()); + } + + /// Ensures that the resulting entries have correct degree. + #[test] + fn correct_degree_entries() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = + MatPolyOverZ::sample_discrete_gauss_seeded(1, 1, degree, i64::MAX, 1, test_seed()) + .unwrap(); + + assert_eq!( + res.get_entry(0, 0).unwrap().get_degree(), + degree, + "Could fail with negligible probability." + ); + } + } + + /// Checks whether the maximum degree needs to be at least 0. + #[test] + #[should_panic] + fn invalid_max_degree() { + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 2, -1, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatPolyOverZ; + let sample_0 = + MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap(); + let sample_1 = + MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_d { use crate::{ @@ -271,3 +360,90 @@ mod test_sample_d { let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 15.75f32); } } + +#[cfg(test)] +mod test_sample_d_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatPolyOverZ, MatZ, Z}, + rational::{PolyOverQ, Q}, + traits::IntoCoefficientEmbedding, + }; + use std::str::FromStr; + + /// Ensure that the sample is from the base. + #[test] + fn ensure_sampled_from_base() { + let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); + let center = vec![PolyOverQ::default()]; + + for _ in 0..10 { + let sample = + MatPolyOverZ::sample_d_seeded(&base, 3, ¢er, 10.5_f64, test_seed()).unwrap(); + let sample_vec = sample.into_coefficient_embedding(3); + let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap(); + + assert_eq!(Z::ZERO, sample_vec.dot_product(&orthogonal).unwrap()); + } + } + + /// Ensure that the sample is from the base for higher dimensional bases. + #[test] + fn ensure_sampled_from_base_higher_dimension() { + let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1],[3 0 1 -1, 1 1],[0, 0]]").unwrap(); + let center = vec![ + PolyOverQ::default(), + PolyOverQ::default(), + PolyOverQ::default(), + ]; + + let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap(); + for _ in 0..10 { + let sample = + MatPolyOverZ::sample_d_seeded(&base, 3, ¢er, 10.5_f64, test_seed()).unwrap(); + let sample_embedded = sample.into_coefficient_embedding(3); + + assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded); + } + } + + /// Checks whether `sample_d` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let basis = MatPolyOverZ::from_str("[[1 1, 2 0 1, 3 0 0 1]]").unwrap(); + let center = vec![PolyOverQ::default()]; + let n = Z::from(1024); + let s = Q::ONE; + + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u8, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u32, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u64, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i32, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i16, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i8, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, &n, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, &s, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1.25f64, test_seed()); + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 15.75f32, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::{integer::MatPolyOverZ, rational::PolyOverQ}; + use std::str::FromStr; + let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap(); + let center = vec![PolyOverQ::default()]; + let sample_0 = + MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap(); + let sample_1 = + MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/mat_poly_over_z/sample/uniform.rs b/src/integer/mat_poly_over_z/sample/uniform.rs index 6523c1cd..52389b58 100644 --- a/src/integer/mat_poly_over_z/sample/uniform.rs +++ b/src/integer/mat_poly_over_z/sample/uniform.rs @@ -11,72 +11,100 @@ use crate::{ error::MathError, integer::{MatPolyOverZ, PolyOverZ, Z}, + macros::seeded::seedable_function, traits::{MatrixDimensions, MatrixSetEntry}, utils::index::evaluate_index, + utils::sample::uniform::SamplerRng, }; use std::fmt::Display; impl MatPolyOverZ { - /// Outputs a [`MatPolyOverZ`] instance with polynomials as entries, - /// whose coefficients were chosen uniform at random in `[lower_bound, upper_bound)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, - /// i.e. the maximum number of coefficients any polynomial in the matrix can have - /// - `lower_bound`: specifies the included lower bound of the - /// interval over which is sampled - /// - `upper_bound`: specifies the excluded upper bound of the - /// interval over which is sampled - /// - /// Returns a new [`MatPolyOverZ`] instance with polynomials as entries, - /// whose coefficients were chosen uniformly at random in - /// `[lower_bound, upper_bound)` or a [`MathError`] - /// if the interval was chosen too small or the `max_degree` of the polynomials - /// is negative or too large to fit into [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatPolyOverZ; - /// - /// let matrix = MatPolyOverZ::sample_uniform(3, 3, 5, 17, 26).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `upper_bound` isn't at least larger than `lower_bound`. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolyOverZ::new`]. - pub fn sample_uniform( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - max_degree: impl TryInto + Display, - lower_bound: impl Into, - upper_bound: impl Into, - ) -> Result { - let lower_bound: Z = lower_bound.into(); - let upper_bound: Z = upper_bound.into(); - let max_degree = evaluate_index(max_degree)?; - let mut matrix = MatPolyOverZ::new(num_rows, num_cols); - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let sample = PolyOverZ::sample_uniform(max_degree, &lower_bound, &upper_bound)?; - unsafe { matrix.set_entry_unchecked(row, col, sample) }; + seedable_function!( + /// Outputs a [`MatPolyOverZ`] instance with polynomials as entries, + /// whose coefficients were chosen uniform at random in `[lower_bound, upper_bound)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `max_degree`: specifies the maximum length of all polynomials in the matrix, + /// i.e. the maximum number of coefficients any polynomial in the matrix can have + /// - `lower_bound`: specifies the included lower bound of the + /// interval over which is sampled + /// - `upper_bound`: specifies the excluded upper bound of the + /// interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatPolyOverZ`] instance with polynomials as entries, + /// whose coefficients were chosen uniformly at random in + /// `[lower_bound, upper_bound)` or a [`MathError`] + /// if the interval was chosen too small or the `max_degree` of the polynomials + /// is negative or too large to fit into [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatPolyOverZ; + /// + #[unseeded] + /// let matrix = MatPolyOverZ::sample_uniform(3, 3, 5, 17, 26).unwrap(); + #[seeded] + /// let matrix = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `upper_bound` isn't at least larger than `lower_bound`. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolyOverZ::new`]. + pub(crate) fn sample_uniform( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + max_degree: impl TryInto + Display, + lower_bound: impl Into, + upper_bound: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut rng = SamplerRng::new(seed); + + let lower_bound: Z = lower_bound.into(); + let upper_bound: Z = upper_bound.into(); + let max_degree = evaluate_index(max_degree)?; + let mut matrix = MatPolyOverZ::new(num_rows, num_cols); + + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let sample = PolyOverZ::sample_uniform_optionally_seeded( + max_degree, + &lower_bound, + &upper_bound, + rng.derive_seed(), + )?; + unsafe { matrix.set_entry_unchecked(row, col, sample) }; + } } - } - Ok(matrix) - } + Ok(matrix) + } + ); } #[cfg(test)] @@ -211,3 +239,181 @@ mod test_sample_uniform { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatPolyOverZ, Z}, + integer_mod_q::Modulus, + }; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let matrix = MatPolyOverZ::sample_uniform_seeded( + 1, + 1, + 0, + &lower_bound, + &upper_bound, + test_seed(), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(lower_bound <= coeff); + assert!(coeff < upper_bound); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let matrix = MatPolyOverZ::sample_uniform_seeded( + 1, + 1, + 0, + &lower_bound, + &upper_bound, + test_seed(), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(lower_bound <= coeff); + assert!(coeff < upper_bound); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = + MatPolyOverZ::sample_uniform_seeded(1, 1, degree, 1, 15, test_seed()).unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); + + assert_eq!(degree, poly.get_degree()); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too large for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let _ = + MatPolyOverZ::sample_uniform_seeded(0, 3, 1, &lower_bound, &upper_bound, test_seed()); + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + fn invalid_interval() { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let mat_0 = MatPolyOverZ::sample_uniform_seeded(3, 3, 0, &lb_0, &upper_bound, test_seed()); + let mat_1 = MatPolyOverZ::sample_uniform_seeded(4, 1, 0, &lb_1, &upper_bound, test_seed()); + let mat_2 = MatPolyOverZ::sample_uniform_seeded(1, 5, 0, &lb_2, &upper_bound, test_seed()); + + assert!(mat_0.is_err()); + assert!(mat_1.is_err()); + assert!(mat_2.is_err()); + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let lower_bound = Z::from(0); + let upper_bound = Z::from(15); + + let res_0 = + MatPolyOverZ::sample_uniform_seeded(1, 1, -1, &lower_bound, &upper_bound, test_seed()); + let res_1 = MatPolyOverZ::sample_uniform_seeded( + 1, + 1, + i64::MIN, + &lower_bound, + &upper_bound, + test_seed(), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u8, 0u16, 7u8, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u16, 0u32, 7u16, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u32, 0u64, 7u32, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u64, 0i8, 7u64, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i8, 0i16, 7i8, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i16, 0i32, 7i16, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i32, 0i64, 7i32, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i64, &Z::ZERO, 7i64, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0, 0u8, &modulus, test_seed()); + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, Z::ZERO, 0, &z, test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let mat_0 = + MatPolyOverZ::sample_uniform_seeded(3, 3, 0, &lower_bound, &upper_bound, test_seed()) + .unwrap(); + let mat_1 = + MatPolyOverZ::sample_uniform_seeded(4, 1, 0, &lower_bound, &upper_bound, test_seed()) + .unwrap(); + let mat_2 = + MatPolyOverZ::sample_uniform_seeded(1, 5, 0, &lower_bound, &upper_bound, test_seed()) + .unwrap(); + let mat_3 = + MatPolyOverZ::sample_uniform_seeded(15, 20, 0, &lower_bound, &upper_bound, test_seed()) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatPolyOverZ; + let sample_0 = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap(); + let sample_1 = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/mat_z/sample/binomial.rs b/src/integer/mat_z/sample/binomial.rs index 67e3a5be..f26e2305 100644 --- a/src/integer/mat_z/sample/binomial.rs +++ b/src/integer/mat_z/sample/binomial.rs @@ -12,6 +12,7 @@ use crate::{ error::MathError, integer::{MatZ, Z}, + macros::seeded::seedable_function, rational::Q, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::binomial::BinomialSampler, @@ -19,102 +20,126 @@ use crate::{ use std::fmt::Display; impl MatZ { - /// Outputs a [`MatZ`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatZ`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatZ; - /// - /// let sample = MatZ::sample_binomial(2, 2, 5, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZ::new`]. - pub fn sample_binomial( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(num_rows, num_cols, 0, n, p) - } - - /// Outputs a [`MatZ`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatZ`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatZ; - /// - /// let sample = MatZ::sample_binomial_with_offset(2, 2, -1, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZ::new`]. - pub fn sample_binomial_with_offset( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - offset: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let offset: Z = offset.into(); - let mut bin_sampler = BinomialSampler::init(n, p, None)?; - let mut matrix = MatZ::new(num_rows, num_cols); - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let mut sample = bin_sampler.sample(); - sample += &offset; - unsafe { matrix.set_entry_unchecked(row, col, sample) }; - } + seedable_function!( + /// Outputs a [`MatZ`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZ`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatZ; + /// + #[unseeded] + /// let sample = MatZ::sample_binomial(2, 2, 5, 0.5).unwrap(); + #[seeded] + /// let sample = MatZ::sample_binomial_seeded(2, 2, 5, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZ::new`]. + pub(crate) fn sample_binomial( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded(num_rows, num_cols, 0, n, p, seed) } + ); - Ok(matrix) - } + seedable_function!( + /// Outputs a [`MatZ`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZ`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatZ; + /// + #[unseeded] + /// let sample = MatZ::sample_binomial_with_offset(2, 2, -1, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatZ::sample_binomial_with_offset_seeded(2, 2, -1, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZ::new`]. + pub(crate) fn sample_binomial_with_offset( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + offset: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let offset: Z = offset.into(); + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; + let mut matrix = MatZ::new(num_rows, num_cols); + + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let mut sample = bin_sampler.sample(); + sample += &offset; + unsafe { matrix.set_entry_unchecked(row, col, sample) }; + } + } + + Ok(matrix) + } + ); } #[cfg(test)] @@ -183,6 +208,83 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{MatZ, Q, Z}; + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + for _ in 0..8 { + let matrix = MatZ::sample_binomial_seeded(1, 1, 2, 0.5, test_seed()).unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample <= 2); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatZ::sample_binomial_seeded(0, 3, 1, 0.5, test_seed()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = MatZ::sample_binomial_seeded(1, 1, 1u16, 7u8, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1u32, 7u16, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1u64, 7u32, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1i8, 7u64, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1i16, 7i8, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1i32, 7i16, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1i64, 7i32, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, Z::ONE, 7i64, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1u8, 0.5f32, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1u8, 0.5f64, test_seed()); + let _ = MatZ::sample_binomial_seeded(1, 1, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = MatZ::sample_binomial_seeded(3, 3, 1, 0.5, test_seed()).unwrap(); + let mat_1 = MatZ::sample_binomial_seeded(4, 1, 1, 0.5, test_seed()).unwrap(); + let mat_2 = MatZ::sample_binomial_seeded(1, 5, 1, 0.5, test_seed()).unwrap(); + let mat_3 = MatZ::sample_binomial_seeded(15, 20, 1, 0.5, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatZ; + let sample_0 = MatZ::sample_binomial_seeded(2, 2, 5, 0.5, [42; 32]).unwrap(); + let sample_1 = MatZ::sample_binomial_seeded(2, 2, 5, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatZ, Q, Z}; @@ -248,3 +350,84 @@ mod test_sample_binomial_with_offset { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{MatZ, Q, Z}; + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + for _ in 0..8 { + let matrix = + MatZ::sample_binomial_with_offset_seeded(1, 1, -1, 2, 0.5, test_seed()).unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(Z::MINUS_ONE <= sample); + assert!(sample <= Z::ONE); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatZ::sample_binomial_with_offset_seeded(0, 3, 0, 1, 0.5, test_seed()); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u16, 1u32, 7u16, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u32, 1u64, 7u32, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u64, 1i8, 7u64, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i8, 1i16, 7i8, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i16, 1i32, 7i16, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i64, 1i64, 7i32, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, Z::ONE, 7i64, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1u8, 0.5f32, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1u8, 0.5f64, test_seed()); + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = MatZ::sample_binomial_with_offset_seeded(3, 3, 0, 1, 0.5, test_seed()).unwrap(); + let mat_1 = MatZ::sample_binomial_with_offset_seeded(4, 1, 0, 1, 0.5, test_seed()).unwrap(); + let mat_2 = MatZ::sample_binomial_with_offset_seeded(1, 5, 0, 1, 0.5, test_seed()).unwrap(); + let mat_3 = + MatZ::sample_binomial_with_offset_seeded(15, 20, 0, 1, 0.5, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatZ; + let sample_0 = + MatZ::sample_binomial_with_offset_seeded(2, 2, -1, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + MatZ::sample_binomial_with_offset_seeded(2, 2, -1, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index fc3bd513..41e983c8 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -11,228 +11,290 @@ use crate::{ error::MathError, integer::MatZ, + macros::seeded::seedable_function, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::discrete_gauss::{ DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT, sample_d, sample_d_precomputed_gso, }, + utils::sample::uniform::SamplerRng, }; use std::fmt::Display; impl MatZ { - /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry - /// sampled independently according to the discrete Gaussian distribution, - /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a matrix with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatZ; - /// - /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZ::new`]. - pub fn sample_discrete_gauss( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - center: impl Into, - s: impl Into, - ) -> Result { - let mut out = Self::new(num_rows, num_cols); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - center, - s, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - )?; - - for row in 0..out.get_num_rows() { - for col in 0..out.get_num_columns() { - let sample = dgis.sample_z(); - unsafe { out.set_entry_unchecked(row, col, sample) }; + seedable_function!( + /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry + /// sampled independently according to the discrete Gaussian distribution, + /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a matrix with each entry sampled independently from the + /// specified discrete Gaussian distribution or an error if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatZ; + /// + #[unseeded] + /// let sample = MatZ::sample_discrete_gauss(3, 1, 0, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZ::new`]. + pub(crate) fn sample_discrete_gauss( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut out = Self::new(num_rows, num_cols); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + center, + s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seed, + )?; + + for row in 0..out.get_num_rows() { + for col in 0..out.get_num_columns() { + let sample = dgis.sample_z(); + unsafe { out.set_entry_unchecked(row, col, sample) }; + } } - } - - Ok(out) - } - - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis. Hence, the callee is - /// responsible for making sure that `basis` provides a suitable basis. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, - /// or if `center` is not a column vector. - /// - /// # Examples - /// ``` - /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}}; - /// let basis = MatZ::identity(5, 5); - /// let center = MatQ::new(5, 1); - /// - /// let sample = MatZ::sample_d(&basis, ¢er, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) - /// if `center` is not a column vector. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_d(basis: &MatZ, center: &MatQ, s: impl Into) -> Result { - let s: Q = s.into(); - - sample_d(basis, center, &s, None) - } - /// Samples a non-spherical discrete Gaussian depending on your choice of - /// `sigma_sqrt` using the standard basis and center `0`. - /// - /// Parameters: - /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix - /// with which the *intermediate* continuous Gaussian is sampled before - /// the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)` - /// where sigma is the target covariance matrix. The root can be computed using - /// the [`MatQ::cholesky_decomposition`]. - /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`]. - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatZ; - /// use qfall_math::rational::{Q, MatQ}; - /// use std::str::FromStr; - /// use crate::qfall_math::traits::Pow; - /// - /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); - /// let r = Q::from(4); - /// - /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); - /// - /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if the `r < 0`. - /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix) - /// if the matrix is not symmetric. - /// - /// This function implements SampleD according to Algorithm 1. in \[2\]. - /// - \[2\] Peikert, Chris. - /// "An efficient and parallel Gaussian sampler for lattices. - /// In Annual Cryptology Conference, pp. 80-97. Berlin, Heidelberg: Springer - /// Berlin Heidelberg, 2010. - /// - pub fn sample_d_common_non_spherical( - sigma_sqrt: &MatQ, - r: impl Into, - ) -> Result { - if !sigma_sqrt.is_square() { - return Err(MathError::NoSquareMatrix("The covariance matrix has to be square, otherwise no discrete Gaussian can be defined.".to_string())); + Ok(out) } - let r = r.into(); - - // sample a continuous Gaussian centered around `0` in every dimension with - // gaussian parameter `1`. - let d_1 = MatQ::sample_gauss_same_center(sigma_sqrt.get_num_columns(), 1, 0, 1)?; - - // compute a continuous Gaussian centered around `0` in every dimension with - // covariance matrix `b_2` (the cholesky decomposition we computed) - let x_2 = sigma_sqrt * d_1; - - // perform randomized rounding - x_2.randomized_rounding(r) - } - - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis or whether `basis_gso` is - /// actually the gso of `basis`. Hence, the callee is responsible for making sure - /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `basis_gso`: specifies the precomputed gso for `basis` - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, - /// or if `center` is not a column vector. - /// - /// # Examples - /// ``` - /// use qfall_math::{integer::{MatZ, Z}, rational::{MatQ, Q}}; - /// let basis = MatZ::identity(5, 5); - /// let center = MatQ::new(5, 1); - /// let basis_gso = MatQ::from(&basis).gso(); - /// - /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) - /// if `center` is not a column vector. - /// - /// # Panics ... - /// - if the number of rows/columns of `basis_gso` and `basis` mismatch. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_d_precomputed_gso( - basis: &MatZ, - basis_gso: &MatQ, - center: &MatQ, - s: impl Into, - ) -> Result { - let s: Q = s.into(); - - sample_d_precomputed_gso(basis, basis_gso, center, &s, None) - } + ); + + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis. Hence, the callee is + /// responsible for making sure that `basis` provides a suitable basis. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a lattice vector sampled according to the discrete Gaussian distribution + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, + /// or if `center` is not a column vector. + /// + /// # Examples + /// ``` + /// use qfall_math::{integer::MatZ, rational::MatQ}; + /// let basis = MatZ::identity(5, 5); + /// let center = MatQ::new(5, 1); + /// + #[unseeded] + /// let sample = MatZ::sample_d(&basis, ¢er, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZ::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) + /// if `center` is not a column vector. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_d( + basis: &MatZ, + center: &MatQ, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let s: Q = s.into(); + + sample_d(basis, center, &s, seed) + } + ); + + seedable_function!( + /// Samples a non-spherical discrete Gaussian depending on your choice of + /// `sigma_sqrt` using the standard basis and center `0`. + /// + /// Parameters: + /// - `sigma_sqrt`: specifies the positive definite Gaussian covariance matrix + /// with which the *intermediate* continuous Gaussian is sampled before + /// the randomized rounding is applied. Here `sigma_sqrt = sqrt(sigma^2 - r^2*I)` + /// where sigma is the target covariance matrix. The root can be computed using + /// the [`MatQ::cholesky_decomposition`]. + /// - `r`: specifies the rounding parameter for [`MatQ::randomized_rounding`]. + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a lattice vector sampled according to the discrete Gaussian distribution. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatZ; + /// use qfall_math::rational::{Q, MatQ}; + /// use std::str::FromStr; + /// use qfall_math::traits::Pow; + /// + /// let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); + /// let r = Q::from(4); + /// + /// let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); + /// + #[unseeded] + /// let sample = MatZ::sample_d_common_non_spherical(&sigma_sqrt.cholesky_decomposition(), r).unwrap(); + #[seeded] + /// let sample = MatZ::sample_d_common_non_spherical_seeded(&sigma_sqrt.cholesky_decomposition(), r, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if the `r < 0`. + /// - Returns a [`MathError`] of type [`NoSquareMatrix`](MathError::NoSquareMatrix) + /// if the matrix is not symmetric. + /// + /// This function implements SampleD according to Algorithm 1. in \[2\]. + /// - \[2\] Peikert, Chris. + /// "An efficient and parallel Gaussian sampler for lattices. + /// In Annual Cryptology Conference, pp. 80-97. Berlin, Heidelberg: Springer + /// Berlin Heidelberg, 2010. + /// + pub(crate) fn sample_d_common_non_spherical( + sigma_sqrt: &MatQ, + r: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut rng = SamplerRng::new(seed); + + if !sigma_sqrt.is_square() { + return Err(MathError::NoSquareMatrix("The covariance matrix has to be square, otherwise no discrete Gaussian can be defined.".to_string())); + } + let r = r.into(); + + // sample a continuous Gaussian centered around `0` in every dimension with + // gaussian parameter `1`. + let d_1 = MatQ::sample_gauss_same_center_optionally_seeded( + sigma_sqrt.get_num_columns(), + 1, + 0, + 1, + rng.derive_seed(), + )?; + + // compute a continuous Gaussian centered around `0` in every dimension with + // covariance matrix `b_2` (the cholesky decomposition we computed) + let x_2 = sigma_sqrt * d_1; + + // perform randomized rounding + x_2.randomized_rounding_optionally_seeded(r, rng.derive_seed()) + } + ); + + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis or whether `basis_gso` is + /// actually the gso of `basis`. Hence, the callee is responsible for making sure + /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `basis_gso`: specifies the precomputed gso for `basis` + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a lattice vector sampled according to the discrete Gaussian distribution + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, + /// or if `center` is not a column vector. + /// + /// # Examples + /// ``` + /// use qfall_math::{integer::MatZ, rational::MatQ}; + /// let basis = MatZ::identity(5, 5); + /// let center = MatQ::new(5, 1); + /// let basis_gso = MatQ::from(&basis).gso(); + /// + #[unseeded] + /// let sample = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) + /// if `center` is not a column vector. + /// + /// # Panics ... + /// - if the number of rows/columns of `basis_gso` and `basis` mismatch. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_d_precomputed_gso( + basis: &MatZ, + basis_gso: &MatQ, + center: &MatQ, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let s: Q = s.into(); + + sample_d_precomputed_gso(basis, basis_gso, center, &s, seed) + } + ); } #[cfg(test)] @@ -271,6 +333,53 @@ mod test_sample_discrete_gauss { } } +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatZ, Z}, + rational::Q, + }; + + // This function only allows for a broader availability, which is tested here. + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let n = Z::from(1024); + let center = Q::ZERO; + let s = Q::ONE; + + let _ = MatZ::sample_discrete_gauss_seeded(2u64, 3i8, ¢er, 1u16, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(3u8, 2i16, ¢er, 1u8, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1u32, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1u64, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i64, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i32, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i16, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i8, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i64, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, &n, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, &s, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, 1, &s, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, 2.25, &s, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1.25f64, test_seed()); + let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 15.75f32, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatZ; + let sample_0 = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap(); + let sample_1 = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_d { use crate::{ @@ -333,6 +442,119 @@ mod test_sample_d { } } +#[cfg(test)] +mod test_sample_d_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatZ, Z}, + rational::{MatQ, Q}, + }; + + // Appropriate inputs were tested in utils and thus omitted here. + // This function only allows for a broader availability, which is tested here. + + /// Checks whether `sample_d` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let basis = MatZ::identity(5, 5); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u8, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u32, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u64, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i32, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i16, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i8, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, &n, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, &s, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1.25f64, test_seed()); + let _ = MatZ::sample_d_seeded(&basis, ¢er, 15.75f32, test_seed()); + } + + /// Checks whether `sample_d_precomputed_gso` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability_prec_gso() { + let basis = MatZ::identity(5, 5); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + let basis_gso = MatQ::from(&basis); + + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u16, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u8, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u32, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u64, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i32, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i16, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i8, test_seed()); + let _ = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); + let _ = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &n, test_seed()); + let _ = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &s, test_seed()); + let _ = MatZ::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 1.25f64, + test_seed(), + ); + let _ = MatZ::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 15.75f32, + test_seed(), + ); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample_d() { + use crate::{integer::MatZ, rational::MatQ}; + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let sample_0 = MatZ::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + let sample_1 = MatZ::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample_d_precomputed_gso() { + use crate::{integer::MatZ, rational::MatQ}; + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let basis_gso = MatQ::from(&basis).gso(); + let sample_0 = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); + let sample_1 = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_d_common_non_spherical { use crate::{ @@ -400,3 +622,121 @@ mod test_sample_d_common_non_spherical { assert!(sample_2.is_column_vector()); } } + +#[cfg(test)] +mod test_sample_d_common_non_spherical_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatZ, Z}, + rational::{MatQ, Q}, + traits::{MatrixDimensions, Pow}, + }; + use std::str::FromStr; + + /// Checks whether `sample_d_common_non_spherical` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + /// or [`Into`], i.e. MatQ, MatZ + #[test] + fn availability() { + let r = Q::from(8); + let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix = + (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition(); + + let _ = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed()).unwrap(); + + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u16, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u32, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u64, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i8, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i16, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i32, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i64, test_seed()) + .unwrap(); + let _ = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, Q::from(8), test_seed()) + .unwrap(); + let _ = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, Z::from(8), test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8f32, test_seed()) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8f64, test_seed()) + .unwrap(); + } + + /// Checks whether the function panics if a non-square matrix is provided. + /// anymore + #[test] + fn not_square() { + let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); + + assert!( + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed()).is_err() + ); + } + + /// Checks whether the function returns an error if `r` is too small. + #[test] + fn too_small_parameters() { + let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); + + assert!( + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, -1, test_seed()) + .is_err() + ); + } + + /// Checks whether the dimension of the output matches the provided covariance matrix + #[test] + fn correct_dimensions() { + let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); + + let sample_1 = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix_1, 8, test_seed()) + .unwrap(); + let sample_2 = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix_2, 8, test_seed()) + .unwrap(); + + assert_eq!(2, sample_1.get_num_rows()); + assert!(sample_1.is_column_vector()); + assert_eq!(3, sample_2.get_num_rows()); + assert!(sample_2.is_column_vector()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatZ; + use crate::rational::{MatQ, Q}; + use crate::traits::Pow; + use std::str::FromStr; + let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); + let r = Q::from(4); + let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); + let sample_0 = MatZ::sample_d_common_non_spherical_seeded( + &sigma_sqrt.cholesky_decomposition(), + &r, + [42; 32], + ) + .unwrap(); + let sample_1 = MatZ::sample_d_common_non_spherical_seeded( + &sigma_sqrt.cholesky_decomposition(), + &r, + [42; 32], + ) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/mat_z/sample/uniform.rs b/src/integer/mat_z/sample/uniform.rs index 43984313..b60dc068 100644 --- a/src/integer/mat_z/sample/uniform.rs +++ b/src/integer/mat_z/sample/uniform.rs @@ -11,66 +11,86 @@ use crate::{ error::MathError, integer::{MatZ, Z}, + macros::seeded::seedable_function, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::uniform::UniformIntegerSampler, }; use std::fmt::Display; impl MatZ { - /// Outputs a [`MatZ`] instance with entries chosen uniform at random - /// in `[lower_bound, upper_bound)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `lower_bound`: specifies the included lower bound of the - /// interval over which is sampled - /// - `upper_bound`: specifies the excluded upper bound of the - /// interval over which is sampled - /// - /// Returns a new [`MatZ`] instance with entries chosen - /// uniformly at random in `[lower_bound, upper_bound)` or a [`MathError`] - /// if the dimensions of the matrix or the interval were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::MatZ; - /// - /// let matrix = MatZ::sample_uniform(3, 3, 17, 26).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `upper_bound` isn't at least larger than `lower_bound`. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZ::new`]. - pub fn sample_uniform( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - lower_bound: impl Into, - upper_bound: impl Into, - ) -> Result { - let lower_bound: Z = lower_bound.into(); - let upper_bound: Z = upper_bound.into(); - let mut matrix = MatZ::new(num_rows, num_cols); - - let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size, None)?; - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let sample = uis.sample(); - unsafe { matrix.set_entry_unchecked(row, col, &lower_bound + sample) }; + seedable_function!( + /// Outputs a [`MatZ`] instance with entries chosen uniform at random + /// in `[lower_bound, upper_bound)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `lower_bound`: specifies the included lower bound of the + /// interval over which is sampled + /// - `upper_bound`: specifies the excluded upper bound of the + /// interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZ`] instance with entries chosen + /// uniformly at random in `[lower_bound, upper_bound)` or a [`MathError`] + /// if the dimensions of the matrix or the interval were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::MatZ; + /// + #[unseeded] + /// let matrix = MatZ::sample_uniform(3, 3, 17, 26).unwrap(); + #[seeded] + /// let matrix = MatZ::sample_uniform_seeded(3, 3, 17, 26, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `upper_bound` isn't at least larger than `lower_bound`. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZ::new`]. + pub(crate) fn sample_uniform( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + lower_bound: impl Into, + upper_bound: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let lower_bound: Z = lower_bound.into(); + let upper_bound: Z = upper_bound.into(); + let mut matrix = MatZ::new(num_rows, num_cols); + + let interval_size = &upper_bound - &lower_bound; + let mut uis = UniformIntegerSampler::init(&interval_size, seed)?; + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let sample = uis.sample(); + unsafe { matrix.set_entry_unchecked(row, col, &lower_bound + sample) }; + } } - } - Ok(matrix) - } + Ok(matrix) + } + ); } #[cfg(test)] @@ -176,3 +196,124 @@ mod test_sample_uniform { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatZ, Z}, + integer_mod_q::Modulus, + }; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let matrix = + MatZ::sample_uniform_seeded(1, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let matrix = + MatZ::sample_uniform_seeded(1, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too large for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let _ = MatZ::sample_uniform_seeded(0, 3, &lower_bound, &upper_bound, test_seed()); + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + fn invalid_interval() { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let mat_0 = MatZ::sample_uniform_seeded(3, 3, &lb_0, &upper_bound, test_seed()); + let mat_1 = MatZ::sample_uniform_seeded(4, 1, &lb_1, &upper_bound, test_seed()); + let mat_2 = MatZ::sample_uniform_seeded(1, 5, &lb_2, &upper_bound, test_seed()); + + assert!(mat_0.is_err()); + assert!(mat_1.is_err()); + assert!(mat_2.is_err()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = MatZ::sample_uniform_seeded(1, 1, 0u16, 7u8, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0u32, 7u16, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0u64, 7u32, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0i8, 7u64, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0i16, 7i8, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0i32, 7i16, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0i64, 7i32, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, &Z::ZERO, 7i64, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0u8, &modulus, test_seed()); + let _ = MatZ::sample_uniform_seeded(1, 1, 0, &z, test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let mat_0 = + MatZ::sample_uniform_seeded(3, 3, &lower_bound, &upper_bound, test_seed()).unwrap(); + let mat_1 = + MatZ::sample_uniform_seeded(4, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); + let mat_2 = + MatZ::sample_uniform_seeded(1, 5, &lower_bound, &upper_bound, test_seed()).unwrap(); + let mat_3 = + MatZ::sample_uniform_seeded(15, 20, &lower_bound, &upper_bound, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::MatZ; + let sample_0 = MatZ::sample_uniform_seeded(3, 3, 17, 26, [42; 32]).unwrap(); + let sample_1 = MatZ::sample_uniform_seeded(3, 3, 17, 26, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/poly_over_z/sample/binomial.rs b/src/integer/poly_over_z/sample/binomial.rs index a56a18a1..e94536cf 100644 --- a/src/integer/poly_over_z/sample/binomial.rs +++ b/src/integer/poly_over_z/sample/binomial.rs @@ -11,6 +11,7 @@ use crate::{ error::MathError, integer::{PolyOverZ, Z}, + macros::seeded::seedable_function, rational::Q, traits::SetCoefficient, utils::{index::evaluate_index, sample::binomial::BinomialSampler}, @@ -18,99 +19,123 @@ use crate::{ use std::fmt::Display; impl PolyOverZ { - /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p`. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolyOverZ`] instance with each value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or `max_degree` is negative or does not into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::PolyOverZ; - /// - /// let sample = PolyOverZ::sample_binomial(2, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - pub fn sample_binomial( - max_degree: impl TryInto + Display, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(max_degree, 0, n, p) - } - - /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolyOverZ`] instance with each value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or `max_degree` is negative or does not into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::PolyOverZ; - /// - /// let sample = PolyOverZ::sample_binomial_with_offset(2, -1, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - pub fn sample_binomial_with_offset( - max_degree: impl TryInto + Display, - offset: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree)?; - let offset: Z = offset.into(); - - let mut poly_z = PolyOverZ::default(); - - let mut bin_sampler = BinomialSampler::init(n, p, None)?; - - for index in 0..=max_degree { - let mut sample = bin_sampler.sample(); - sample += &offset; - unsafe { poly_z.set_coeff_unchecked(index, sample) }; + seedable_function!( + /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p`. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZ`] instance with each value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or `max_degree` is negative or does not into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::PolyOverZ; + /// + #[unseeded] + /// let sample = PolyOverZ::sample_binomial(2, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + pub(crate) fn sample_binomial( + max_degree: impl TryInto + Display, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded(max_degree, 0, n, p, seed) } + ); - Ok(poly_z) - } + seedable_function!( + /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZ`] instance with each value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or `max_degree` is negative or does not into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::PolyOverZ; + /// + #[unseeded] + /// let sample = PolyOverZ::sample_binomial_with_offset(2, -1, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + pub(crate) fn sample_binomial_with_offset( + max_degree: impl TryInto + Display, + offset: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree)?; + let offset: Z = offset.into(); + + let mut poly_z = PolyOverZ::default(); + + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; + + for index in 0..=max_degree { + let mut sample = bin_sampler.sample(); + sample += &offset; + unsafe { poly_z.set_coeff_unchecked(index, sample) }; + } + + Ok(poly_z) + } + ); } #[cfg(test)] @@ -177,6 +202,81 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{PolyOverZ, Q, Z}; + use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let poly = PolyOverZ::sample_binomial_seeded(0, 2, 0.5, test_seed()).unwrap(); + let sample = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample <= 2); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_binomial_seeded(degree, 256, 0.99999, test_seed()).unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = PolyOverZ::sample_binomial_seeded(-1, 2, 0.5, test_seed()); + let res_1 = PolyOverZ::sample_binomial_seeded(i64::MIN, 2, 0.5, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = PolyOverZ::sample_binomial_seeded(1, 1u16, 7u8, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1u32, 7u16, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1u64, 7u32, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1i8, 7u64, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1i16, 7i8, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1i32, 7i16, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1i64, 7i32, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, Z::ONE, 7i64, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1u8, 0.5f32, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1u8, 0.5f64, test_seed()); + let _ = PolyOverZ::sample_binomial_seeded(1, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::PolyOverZ; + let sample_0 = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{PolyOverZ, Q, Z}; @@ -236,3 +336,80 @@ mod test_sample_binomial_with_offset { let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1, Q::from((1, 2))); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{PolyOverZ, Q, Z}; + use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let poly = + PolyOverZ::sample_binomial_with_offset_seeded(0, -1, 2, 0.5, test_seed()).unwrap(); + let sample = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample); + assert!(sample <= Z::ONE); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_binomial_with_offset_seeded(degree, 1, 2, 0.5, test_seed()) + .unwrap(); + + assert_eq!(degree, res.get_degree()); + } + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = PolyOverZ::sample_binomial_with_offset_seeded(-1, -1, 2, 0.5, test_seed()); + let res_1 = + PolyOverZ::sample_binomial_with_offset_seeded(i64::MIN, -1, 2, 0.5, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u16, 7u8, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u32, 7u16, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u64, 7u32, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i8, 7u64, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i16, 7i8, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i32, 7i16, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i64, 7i32, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, Z::ONE, 7i64, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u8, 0.5f32, test_seed()); + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u8, 0.5f64, test_seed()); + let _ = + PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::PolyOverZ; + let sample_0 = + PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/poly_over_z/sample/discrete_gauss.rs b/src/integer/poly_over_z/sample/discrete_gauss.rs index 6461adc9..dd07e2e2 100644 --- a/src/integer/poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/poly_over_z/sample/discrete_gauss.rs @@ -12,6 +12,7 @@ use crate::{ error::MathError, integer::PolyOverZ, + macros::seeded::seedable_function, rational::Q, traits::SetCoefficient, utils::{ @@ -22,58 +23,70 @@ use crate::{ use std::fmt::Display; impl PolyOverZ { - /// Initializes a new [`PolyOverZ`] with maximum degree `max_degree` - /// and with each entry sampled independently according to the - /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). - /// - /// Parameters: - /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a fresh [`PolyOverZ`] instance of maximum degree `max_degree` - /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::PolyOverZ; - /// - /// let sample = PolyOverZ::sample_discrete_gauss(2, 0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if `max_degree` is negative, or does not fit into an [`i64`]. - pub fn sample_discrete_gauss( - max_degree: impl TryInto + Display, - center: impl Into, - s: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree).unwrap(); - - let center = center.into(); - let s = s.into(); - let mut poly = PolyOverZ::default(); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &s, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - )?; - - for index in 0..=max_degree { - let sample = dgis.sample_z(); - unsafe { poly.set_coeff_unchecked(index, &sample) }; + seedable_function!( + /// Initializes a new [`PolyOverZ`] with maximum degree `max_degree` + /// and with each entry sampled independently according to the + /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). + /// + /// Parameters: + /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZ`] should have + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZ`] instance of maximum degree `max_degree` + /// with coefficients chosen independently according the discrete Gaussian distribution or + /// a [`MathError`] if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::PolyOverZ; + /// + #[unseeded] + /// let sample = PolyOverZ::sample_discrete_gauss(2, 0, 1).unwrap(); + #[seeded] + /// let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if `max_degree` is negative, or does not fit into an [`i64`]. + pub(crate) fn sample_discrete_gauss( + max_degree: impl TryInto + Display, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree).unwrap(); + + let center = center.into(); + let s = s.into(); + let mut poly = PolyOverZ::default(); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seed, + )?; + + for index in 0..=max_degree { + let sample = dgis.sample_z(); + unsafe { poly.set_coeff_unchecked(index, &sample) }; + } + Ok(poly) } - Ok(poly) - } + ); } #[cfg(test)] @@ -122,3 +135,62 @@ mod test_sample_discrete_gauss { let _ = PolyOverZ::sample_discrete_gauss(-1, 0, 1).unwrap(); } } + +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::PolyOverZ, rational::Q}; + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let center = Q::ZERO; + let s = Q::ONE; + + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1u8, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u16, 0f64, 1u16, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u32, 0f32, 1u32, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u64, 0f64, 1u64, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1i8, 0f32, 1i8, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1i8, 0f32, 1i16, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1i16, 0f32, 1i32, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1i32, 0f64, 1i64, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1i64, center, s, test_seed()); + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1f64, test_seed()); + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = + PolyOverZ::sample_discrete_gauss_seeded(degree, i64::MAX, 1, test_seed()).unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } + } + + /// Checks whether the maximum degree needs to be at least 0. + #[test] + #[should_panic] + fn invalid_max_degree() { + let _ = PolyOverZ::sample_discrete_gauss_seeded(-1, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::PolyOverZ; + let sample_0 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + let sample_1 = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/poly_over_z/sample/uniform.rs b/src/integer/poly_over_z/sample/uniform.rs index efff155c..7027e5e1 100644 --- a/src/integer/poly_over_z/sample/uniform.rs +++ b/src/integer/poly_over_z/sample/uniform.rs @@ -11,62 +11,82 @@ use crate::{ error::MathError, integer::{PolyOverZ, Z}, + macros::seeded::seedable_function, traits::SetCoefficient, utils::{index::evaluate_index, sample::uniform::UniformIntegerSampler}, }; use std::fmt::Display; impl PolyOverZ { - /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and coefficients - /// chosen uniform at random in `[lower_bound, upper_bound)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `lower_bound`: specifies the included lower bound of the - /// interval over which is sampled - /// - `upper_bound`: specifies the excluded upper bound of the - /// interval over which is sampled - /// - /// Returns a fresh [`PolyOverZ`] instance of length `max_degree` with coefficients - /// chosen uniform at random in `[lower_bound, upper_bound)` or a [`MathError`] - /// if the `max_degree` was smaller than `0` or the provided interval was chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::{PolyOverZ, Z}; - /// - /// let sample = PolyOverZ::sample_uniform(3, 17, 26).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `upper_bound` isn't at least larger than `lower_bound`. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - pub fn sample_uniform( - max_degree: impl TryInto + Display, - lower_bound: impl Into, - upper_bound: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree)?; - let lower_bound: Z = lower_bound.into(); - let upper_bound: Z = upper_bound.into(); - - let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size, None)?; - - let mut poly_z = PolyOverZ::default(); - for index in 0..=max_degree { - let sample = uis.sample(); - unsafe { poly_z.set_coeff_unchecked(index, &lower_bound + sample) }; + seedable_function!( + /// Generates a [`PolyOverZ`] instance of maximum degree `max_degree` and coefficients + /// chosen uniform at random in `[lower_bound, upper_bound)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `lower_bound`: specifies the included lower bound of the + /// interval over which is sampled + /// - `upper_bound`: specifies the excluded upper bound of the + /// interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZ`] instance of length `max_degree` with coefficients + /// chosen uniform at random in `[lower_bound, upper_bound)` or a [`MathError`] + /// if the `max_degree` was smaller than `0` or the provided interval was chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::PolyOverZ; + /// + #[unseeded] + /// let sample = PolyOverZ::sample_uniform(3, 17, 26).unwrap(); + #[seeded] + /// let sample = PolyOverZ::sample_uniform_seeded(3, 17, 26, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `upper_bound` isn't at least larger than `lower_bound`. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + pub(crate) fn sample_uniform( + max_degree: impl TryInto + Display, + lower_bound: impl Into, + upper_bound: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree)?; + let lower_bound: Z = lower_bound.into(); + let upper_bound: Z = upper_bound.into(); + + let interval_size = &upper_bound - &lower_bound; + let mut uis = UniformIntegerSampler::init(&interval_size, seed)?; + + let mut poly_z = PolyOverZ::default(); + for index in 0..=max_degree { + let sample = uis.sample(); + unsafe { poly_z.set_coeff_unchecked(index, &lower_bound + sample) }; + } + Ok(poly_z) } - Ok(poly_z) - } + ); } #[cfg(test)] @@ -166,3 +186,115 @@ mod test_sample_uniform { let _ = PolyOverZ::sample_uniform(1, 0, &z); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{PolyOverZ, Z}, + integer_mod_q::Modulus, + traits::GetCoefficient, + }; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + let poly_z = + PolyOverZ::sample_uniform_seeded(32, &lower_bound, &upper_bound, test_seed()).unwrap(); + + for i in 0..32 { + let sample = poly_z.get_coeff(i).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + + let poly_z = + PolyOverZ::sample_uniform_seeded(256, &lower_bound, &upper_bound, test_seed()).unwrap(); + + for i in 0..256 { + let sample = poly_z.get_coeff(i).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_uniform_seeded(degree, 1, 15, test_seed()).unwrap(); + + assert_eq!(degree, res.get_degree()); + } + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + fn invalid_interval() { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = PolyOverZ::sample_uniform_seeded(1, &lb_0, &upper_bound, test_seed()); + let res_1 = PolyOverZ::sample_uniform_seeded(1, &lb_1, &upper_bound, test_seed()); + let res_2 = PolyOverZ::sample_uniform_seeded(1, &lb_2, &upper_bound, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let lower_bound = Z::from(0); + let upper_bound = Z::from(15); + + let res_0 = PolyOverZ::sample_uniform_seeded(-1, &lower_bound, &upper_bound, test_seed()); + let res_1 = + PolyOverZ::sample_uniform_seeded(i64::MIN, &lower_bound, &upper_bound, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = PolyOverZ::sample_uniform_seeded(1u64, 0u16, 7u8, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1i64, 0u32, 7u16, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1u8, 0u64, 7u32, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1u16, 0i8, 7u64, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1u32, 0i16, 7i8, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1i32, 0i32, 7i16, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1i16, 0i64, 7i32, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1i8, &Z::ZERO, 7i64, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1, 0u8, &modulus, test_seed()); + let _ = PolyOverZ::sample_uniform_seeded(1, 0, &z, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::PolyOverZ; + let sample_0 = PolyOverZ::sample_uniform_seeded(3, 17, 26, [42; 32]).unwrap(); + let sample_1 = PolyOverZ::sample_uniform_seeded(3, 17, 26, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/z/sample/binomial.rs b/src/integer/z/sample/binomial.rs index ab3771af..7be4dde7 100644 --- a/src/integer/z/sample/binomial.rs +++ b/src/integer/z/sample/binomial.rs @@ -9,39 +9,57 @@ //! This module contains algorithms for sampling //! according to the binomial distribution. -use crate::{error::MathError, integer::Z, rational::Q, utils::sample::binomial::BinomialSampler}; +use crate::{ + error::MathError, integer::Z, macros::seeded::seedable_function, rational::Q, + utils::sample::binomial::BinomialSampler, +}; impl Z { - /// Chooses a [`Z`] instance according to the binomial distribution - /// parameterized by `n` and `p`. - /// - /// Parameters: - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`Z`] instance with a value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, or `n` does not fit into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::Z; - /// - /// let sample = Z::sample_binomial(2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - pub fn sample_binomial(n: impl Into, p: impl Into) -> Result { - let mut bin_sampler = BinomialSampler::init(n, p, None)?; + seedable_function!( + /// Chooses a [`Z`] instance according to the binomial distribution + /// parameterized by `n` and `p`. + /// + /// Parameters: + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`Z`] instance with a value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, or `n` does not fit into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::Z; + /// + #[unseeded] + /// let sample = Z::sample_binomial(2, 0.5).unwrap(); + #[seeded] + /// let sample = Z::sample_binomial_seeded(2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + pub(crate) fn sample_binomial( + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; - Ok(bin_sampler.sample()) - } + Ok(bin_sampler.sample()) + } + ); } #[cfg(test)] @@ -70,3 +88,41 @@ mod test_sample_binomial { let _ = Z::sample_binomial(1, Q::from((1, 2))); } } + +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{Q, Z}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = Z::sample_binomial_seeded(1u16, 7u8, test_seed()); + let _ = Z::sample_binomial_seeded(1u32, 7u16, test_seed()); + let _ = Z::sample_binomial_seeded(1u64, 7u32, test_seed()); + let _ = Z::sample_binomial_seeded(1i8, 7u64, test_seed()); + let _ = Z::sample_binomial_seeded(1i16, 7i8, test_seed()); + let _ = Z::sample_binomial_seeded(1i32, 7i16, test_seed()); + let _ = Z::sample_binomial_seeded(1i64, 7i32, test_seed()); + let _ = Z::sample_binomial_seeded(Z::ONE, 7i64, test_seed()); + let _ = Z::sample_binomial_seeded(1u8, 0.5f32, test_seed()); + let _ = Z::sample_binomial_seeded(1u8, 0.5f64, test_seed()); + let _ = Z::sample_binomial_seeded(1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::Z; + let sample_0 = Z::sample_binomial_seeded(2, 0.5, [42; 32]).unwrap(); + let sample_1 = Z::sample_binomial_seeded(2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/z/sample/discrete_gauss.rs b/src/integer/z/sample/discrete_gauss.rs index d18091b8..ca771224 100644 --- a/src/integer/z/sample/discrete_gauss.rs +++ b/src/integer/z/sample/discrete_gauss.rs @@ -11,57 +11,73 @@ use crate::{ error::MathError, integer::Z, + macros::seeded::seedable_function, rational::Q, utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, }; impl Z { - /// Chooses a [`Z`] instance according to the discrete Gaussian distribution - /// in `[center - ⌈6 * s⌉ , center + ⌊6 * s⌋ ]`. - /// - /// This function samples discrete Gaussians according to the definition of - /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). - /// - /// Parameters: - /// - `n`: specifies the range from which is sampled - /// - `center`: specifies the position of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately, i.e. `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::Z; - /// - /// let sample = Z::sample_discrete_gauss(0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// This function implements SampleZ according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_discrete_gauss(center: impl Into, s: impl Into) -> Result { - let center: Q = center.into(); - let s: Q = s.into(); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &s, - unsafe { TAILCUT }, - LookupTableSetting::NoLookup, - None, - )?; - - Ok(dgis.sample_z()) - } + seedable_function!( + /// Chooses a [`Z`] instance according to the discrete Gaussian distribution + /// in `[center - ⌈6 * s⌉ , center + ⌊6 * s⌋ ]`. + /// + /// This function samples discrete Gaussians according to the definition of + /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). + /// + /// Parameters: + /// - `n`: specifies the range from which is sampled + /// - `center`: specifies the position of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns new [`Z`] sample chosen according to the specified discrete Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately, i.e. `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::Z; + /// + #[unseeded] + /// let sample = Z::sample_discrete_gauss(0, 1).unwrap(); + #[seeded] + /// let sample = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// This function implements SampleZ according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_discrete_gauss( + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let center: Q = center.into(); + let s: Q = s.into(); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::NoLookup, + seed, + )?; + + Ok(dgis.sample_z()) + } + ); } #[cfg(test)] @@ -131,3 +147,82 @@ mod test_sample_discrete_gauss { } } } + +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::Z, rational::Q}; + + // Appropriate interval and invalid inputs were tested in + // utils::sample::discrete_gauss::test_sample_z and are thus omitted here. + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let z = Z::from(2); + let q = Q::from(2); + + let _ = Z::sample_discrete_gauss_seeded(7u8, 1u16, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7u16, 1u8, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7u32, 1u32, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7u64, 1u64, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7i8, 1i64, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7i16, 1i32, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7i32, 1i16, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(7i64, 1i8, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(&q, 1i64, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(0i8, &z, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(&z, &q, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(1f32, 1f64, test_seed()); + let _ = Z::sample_discrete_gauss_seeded(1f64, 1f32, test_seed()); + } + + /// Roughly checks the collected samples are distributed + /// according to the discrete Gaussian distribution. + /// + /// This test could possibly fail for a truly uniform distribution + /// with probability smaller than 1/1000. + #[test] + fn distribution() { + let mut counts = [0; 20]; + // count sampled instances + for _ in 0..200 { + let sample = Z::sample_discrete_gauss_seeded(10, 2, test_seed()).unwrap(); + let sample_int = i64::try_from(&sample).unwrap() as usize; + counts[sample_int] += 1; + } + + let expl_text = String::from("This test can fail with probability close to 0. + It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. + If this happens, rerun the tests several times and check whether this issue comes up again."); + + // Check that the sampled occurrences roughly look + // like a discrete Gaussian distribution + assert!(counts[10] > 70, "{expl_text}"); + assert!(counts[10] < 130, "{expl_text}"); + assert!(counts[9] > 20, "{expl_text}"); + assert!(counts[9] < 70, "{expl_text}"); + assert!(counts[11] > 20, "{expl_text}"); + assert!(counts[11] < 70, "{expl_text}"); + assert!(counts[8] < 20, "{expl_text}"); + assert!(counts[12] < 20, "{expl_text}"); + for count in counts.iter().take(8) { + assert!(count < &10, "{expl_text}"); + } + for count in counts.iter().skip(13) { + assert!(count < &10, "{expl_text}"); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::Z; + let sample_0 = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap(); + let sample_1 = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer/z/sample/uniform.rs b/src/integer/z/sample/uniform.rs index bd708995..fa1fe326 100644 --- a/src/integer/z/sample/uniform.rs +++ b/src/integer/z/sample/uniform.rs @@ -8,115 +8,156 @@ //! This module contains algorithms for sampling according to the uniform distribution. -use crate::{error::MathError, integer::Z, utils::sample::uniform::UniformIntegerSampler}; +use crate::{ + error::MathError, integer::Z, macros::seeded::seedable_function, + utils::sample::uniform::UniformIntegerSampler, +}; impl Z { - /// Chooses a [`Z`] instance uniformly at random - /// in `[lower_bound, upper_bound)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `lower_bound`: specifies the included lower bound of the - /// interval over which is sampled - /// - `upper_bound`: specifies the excluded upper bound of the - /// interval over which is sampled - /// - /// Returns a fresh [`Z`] instance with a - /// uniform random value in `[lower_bound, upper_bound)` or a [`MathError`] - /// if the provided interval was chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::Z; - /// - /// let sample = Z::sample_uniform(&17, &26).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `upper_bound` isn't at least larger than `lower_bound`. - pub fn sample_uniform( - lower_bound: impl Into, - upper_bound: impl Into, - ) -> Result { - let lower_bound: Z = lower_bound.into(); - let upper_bound: Z = upper_bound.into(); - - let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size, None)?; - - let sample = uis.sample(); - Ok(&lower_bound + sample) - } - - /// Chooses a prime [`Z`] instance uniformly at random - /// in `[lower_bound, upper_bound)`. If after 2 * `n` steps, where `n` denotes the size of - /// the interval, no suitable prime was found, the algorithm aborts. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `lower_bound`: specifies the included lower bound of the - /// interval over which is sampled - /// - `upper_bound`: specifies the excluded upper bound of the - /// interval over which is sampled - /// - /// Returns a fresh [`Z`] instance with a - /// uniform random value in `[lower_bound, upper_bound)`. Otherwise, a [`MathError`] - /// if the provided interval was chosen too small, no prime could be found in the interval, - /// or the provided `lower_bound` was negative. - /// - /// # Examples - /// ``` - /// use qfall_math::integer::Z; - /// - /// let prime = Z::sample_prime_uniform(1, 100).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `upper_bound` isn't at least larger than `lower_bound` - /// , or if no prime could be found in the specified interval. - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `lower_bound` is negative as primes are always positive. - pub fn sample_prime_uniform( - lower_bound: impl Into, - upper_bound: impl Into, - ) -> Result { - let lower_bound: Z = lower_bound.into(); - let upper_bound: Z = upper_bound.into(); - - if lower_bound < Z::ZERO { - return Err(MathError::InvalidIntegerInput(format!( - "Primes are always positive. Hence, sample_prime_uniform expects a positive lower_bound. - The provided one is {lower_bound}."))); + seedable_function!( + /// Chooses a [`Z`] instance uniformly at random + /// in `[lower_bound, upper_bound)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `lower_bound`: specifies the included lower bound of the + /// interval over which is sampled + /// - `upper_bound`: specifies the excluded upper bound of the + /// interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`Z`] instance with a + /// uniform random value in `[lower_bound, upper_bound)` or a [`MathError`] + /// if the provided interval was chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::Z; + /// + #[unseeded] + /// let sample = Z::sample_uniform(17, 26).unwrap(); + #[seeded] + /// let sample = Z::sample_uniform_seeded(17, 26, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `upper_bound` isn't at least larger than `lower_bound`. + pub(crate) fn sample_uniform( + lower_bound: impl Into, + upper_bound: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let lower_bound: Z = lower_bound.into(); + let upper_bound: Z = upper_bound.into(); + + let interval_size = &upper_bound - &lower_bound; + let mut uis = UniformIntegerSampler::init(&interval_size, seed)?; + + let sample = uis.sample(); + Ok(&lower_bound + sample) } + ); + + seedable_function!( + /// Chooses a prime [`Z`] instance uniformly at random + /// in `[lower_bound, upper_bound)`. If after 2 * `n` steps, where `n` denotes the size of + /// the interval, no suitable prime was found, the algorithm aborts. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `lower_bound`: specifies the included lower bound of the + /// interval over which is sampled + /// - `upper_bound`: specifies the excluded upper bound of the + /// interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`Z`] instance with a + /// uniform random value in `[lower_bound, upper_bound)`. Otherwise, a [`MathError`] + /// if the provided interval was chosen too small, no prime could be found in the interval, + /// or the provided `lower_bound` was negative. + /// + /// # Examples + /// ``` + /// use qfall_math::integer::Z; + /// + #[unseeded] + /// let prime = Z::sample_prime_uniform(1, 100).unwrap(); + #[seeded] + /// let prime = Z::sample_prime_uniform_seeded(1, 100, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `upper_bound` isn't at least larger than `lower_bound` + /// , or if no prime could be found in the specified interval. + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `lower_bound` is negative as primes are always positive. + pub(crate) fn sample_prime_uniform( + lower_bound: impl Into, + upper_bound: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let lower_bound: Z = lower_bound.into(); + let upper_bound: Z = upper_bound.into(); + + if lower_bound < Z::ZERO { + return Err(MathError::InvalidIntegerInput(format!( + "Primes are always positive. Hence, sample_prime_uniform expects a positive lower_bound. + The provided one is {lower_bound}."))); + } - let interval_size = &upper_bound - &lower_bound; - let mut uis = UniformIntegerSampler::init(&interval_size, None)?; - let mut sample = &lower_bound + uis.sample(); - - // after 2 * size of interval many uniform random samples, a suitable prime should have been - // found with high probability, if there is one prime in the interval - let mut steps = 2 * &interval_size; - while !sample.is_prime() { - if steps == Z::ZERO { - return Err(MathError::InvalidInterval(format!( - "After sampling O({interval_size}) times uniform at random in the interval [{lower_bound}, {upper_bound}[ - no prime was found. It is very likely, that no prime exists in this interval. - Please choose the interval larger."))); + let interval_size = &upper_bound - &lower_bound; + let mut uis = UniformIntegerSampler::init(&interval_size, seed)?; + let mut sample = &lower_bound + uis.sample(); + + // after 2 * size of interval many uniform random samples, a suitable prime should have been + // found with high probability, if there is one prime in the interval + let mut steps = 2 * &interval_size; + while !sample.is_prime() { + if steps == Z::ZERO { + return Err(MathError::InvalidInterval(format!( + "After sampling O({interval_size}) times uniform at random in the interval [{lower_bound}, {upper_bound}[ + no prime was found. It is very likely, that no prime exists in this interval. + Please choose the interval larger."))); + } + sample = &lower_bound + uis.sample(); + steps -= 1; } - sample = &lower_bound + uis.sample(); - steps -= 1; - } - Ok(sample) - } + Ok(sample) + } + ); } #[cfg(test)] @@ -208,6 +249,107 @@ mod test_sample_uniform { } } +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::Z, integer_mod_q::Modulus}; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let sample = Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let sample = Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + fn invalid_interval() { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = Z::sample_uniform_seeded(&lb_0, &upper_bound, test_seed()); + let res_1 = Z::sample_uniform_seeded(&lb_1, &upper_bound, test_seed()); + let res_2 = Z::sample_uniform_seeded(&lb_2, &upper_bound, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Z::sample_uniform_seeded(0u16, 7u8, test_seed()); + let _ = Z::sample_uniform_seeded(0u32, 7u16, test_seed()); + let _ = Z::sample_uniform_seeded(0u64, 7u32, test_seed()); + let _ = Z::sample_uniform_seeded(0i8, 7u64, test_seed()); + let _ = Z::sample_uniform_seeded(0i16, 7i8, test_seed()); + let _ = Z::sample_uniform_seeded(0i32, 7i16, test_seed()); + let _ = Z::sample_uniform_seeded(0i64, 7i32, test_seed()); + let _ = Z::sample_uniform_seeded(&Z::ZERO, 7i64, test_seed()); + let _ = Z::sample_uniform_seeded(0u8, &modulus, test_seed()); + let _ = Z::sample_uniform_seeded(0, &z, test_seed()); + } + + /// Roughly checks the uniformity of the distribution. + /// This test could possibly fail for a truly uniform distribution + /// with probability smaller than 1/1000. + #[test] + fn uniformity() { + let lower_bound = Z::ZERO; + let upper_bound = Z::from(5); + let mut counts = [0; 5]; + // count sampled instances + for _ in 0..1000 { + let sample_z = + Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); + let sample_int = i64::try_from(&sample_z).unwrap() as usize; + counts[sample_int] += 1; + } + + // Check that every sampled integer was sampled roughly the same time + // this could possibly fail for true uniform randomness with probability + for count in counts { + assert!(count > 150, "This test can fail with probability close to 0. + It fails if the sampled occurrences do not look like a typical uniform random distribution. + If this happens, rerun the tests several times and check whether this issue comes up again."); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::Z; + let sample_0 = Z::sample_uniform_seeded(17, 26, [42; 32]).unwrap(); + let sample_1 = Z::sample_uniform_seeded(17, 26, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_prime_uniform { use crate::{integer::Z, integer_mod_q::Modulus}; @@ -303,3 +445,112 @@ mod test_sample_prime_uniform { let _ = Z::sample_prime_uniform(0, &z); } } + +#[cfg(test)] +mod test_sample_prime_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::Z, integer_mod_q::Modulus}; + + /// Checks whether `sample_prime_uniform` outputs a prime sample every time. + #[test] + fn is_prime() { + for _ in 0..8 { + let sample = Z::sample_prime_uniform_seeded(20, i64::MAX, test_seed()).unwrap(); + assert!(sample.is_prime(), "Can fail with probability ~2^-60."); + } + } + + /// Checks whether `sample_prime_uniform` returns an error if + /// no prime exists in the specified interval. + #[test] + fn no_prime_exists() { + let res_0 = Z::sample_prime_uniform_seeded(14, 17, test_seed()); + // there is no prime between u64::MAX - 57 and u64::MAX + let res_1 = Z::sample_prime_uniform_seeded(u64::MAX - 57, u64::MAX, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether a negative lower bound result in an error. + #[test] + fn negative_lower_bound() { + let res_0 = Z::sample_prime_uniform_seeded(-1, 10, test_seed()); + let res_1 = Z::sample_prime_uniform_seeded(-200, 1, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let sample = + Z::sample_prime_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let lower_bound = Z::ZERO; + let upper_bound = Z::from(i64::MAX); + for _ in 0..256 { + let sample = + Z::sample_prime_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + fn invalid_interval() { + let lb_0 = Z::from(i64::MIN) - Z::ONE; + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = Z::sample_prime_uniform_seeded(&lb_0, &upper_bound, test_seed()); + let res_1 = Z::sample_prime_uniform_seeded(&lb_1, &upper_bound, test_seed()); + let res_2 = Z::sample_prime_uniform_seeded(&lb_2, &upper_bound, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } + + /// Checks whether `sample_prime_uniform` is available for types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Z::sample_prime_uniform_seeded(0u16, 7u8, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0u32, 7u16, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0u64, 7u32, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0i8, 7u64, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0i16, 7i8, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0i32, 7i16, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0i64, 7i32, test_seed()); + let _ = Z::sample_prime_uniform_seeded(&Z::ZERO, 7i64, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0u8, &modulus, test_seed()); + let _ = Z::sample_prime_uniform_seeded(0, &z, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer::Z; + let sample_0 = Z::sample_prime_uniform_seeded(1, 100, [42; 32]).unwrap(); + let sample_1 = Z::sample_prime_uniform_seeded(1, 100, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs index c8f86c23..896e8cb6 100644 --- a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs @@ -10,64 +10,84 @@ use crate::{ integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}, + macros::seeded::seedable_function, utils::sample::uniform::UniformIntegerSampler, }; impl MatNTTPolynomialRingZq { - /// Generates a [`MatNTTPolynomialRingZq`] instance with maximum degree `modulus_degree` - /// and entries chosen uniform at random in `[0, modulus)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `num_rows`: defines the number of rows of the matrix - /// - `num_columns`: defines the number of columns of the matrix - /// - `modulus_degree`: specifies the degree of the modulus polynomial, i.e. the maximum number - /// of sampled coefficients is `modulus_degree - 1` - /// - `modulus`: specifies the modulus of the values and thus, - /// the interval size over which is sampled - /// - /// Returns a fresh [`MatNTTPolynomialRingZq`] instance of length `modulus_degree` with entries - /// chosen uniform at random in `[0, modulus)`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - /// modulus.set_ntt_unchecked(64); - /// - /// let sample = MatNTTPolynomialRingZq::sample_uniform(3, 2, &modulus); - /// ``` - /// - /// # Panics ... - /// - if `nr_rows` or `nr_columns` is `0`. - pub fn sample_uniform( - nr_rows: usize, - nr_columns: usize, - modulus: &ModulusPolynomialRingZq, - ) -> Self { - assert!(nr_rows > 0, "Number of rows needs to be larger than 0."); - assert!( - nr_columns > 0, - "Number of columns needs to be larger than 0." - ); - let interval_size = modulus.get_q(); - - let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); - - let vector = (0..modulus.get_degree() as usize * nr_rows * nr_columns) - .map(|_| uis.sample()) - .collect(); - Self { - matrix: vector, - nr_rows, - nr_columns, - modulus: modulus.clone(), + seedable_function!( + /// Generates a [`MatNTTPolynomialRingZq`] instance with maximum degree `modulus_degree` + /// and entries chosen uniform at random in `[0, modulus)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `num_rows`: defines the number of rows of the matrix + /// - `num_columns`: defines the number of columns of the matrix + /// - `modulus_degree`: specifies the degree of the modulus polynomial, i.e. the maximum number + /// of sampled coefficients is `modulus_degree - 1` + /// - `modulus`: specifies the modulus of the values and thus, + /// the interval size over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`MatNTTPolynomialRingZq`] instance of length `modulus_degree` with entries + /// chosen uniform at random in `[0, modulus)`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + /// modulus.set_ntt_unchecked(64); + /// + #[unseeded] + /// let sample = MatNTTPolynomialRingZq::sample_uniform(3, 2, &modulus); + #[seeded] + /// let sample = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); + /// ``` + /// + /// # Panics ... + /// - if `nr_rows` or `nr_columns` is `0`. + pub(crate) fn sample_uniform( + nr_rows: usize, + nr_columns: usize, + modulus: &ModulusPolynomialRingZq, + seed: Option<[u8; 32]>, + ) -> Self { + assert!(nr_rows > 0, "Number of rows needs to be larger than 0."); + assert!( + nr_columns > 0, + "Number of columns needs to be larger than 0." + ); + let interval_size = modulus.get_q(); + + let mut uis = UniformIntegerSampler::init(&interval_size, seed).unwrap(); + + let vector = (0..modulus.get_degree() as usize * nr_rows * nr_columns) + .map(|_| uis.sample()) + .collect(); + Self { + matrix: vector, + nr_rows, + nr_columns, + modulus: modulus.clone(), + } } - } + ); } #[cfg(test)] @@ -104,3 +124,52 @@ mod test_sample_uniform { let _ = MatNTTPolynomialRingZq::sample_uniform(0, 1, &modulus); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}, + }; + use std::str::FromStr; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept() { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + for _ in 0..32 { + let matrix = MatNTTPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let sample = matrix.matrix[0].clone(); + + assert!(Z::ZERO <= sample); + assert!(sample < 257); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too large for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + let _ = MatNTTPolynomialRingZq::sample_uniform_seeded(0, 1, &modulus, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + let sample_0 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); + let sample_1 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs index 3e1ee70e..00c6c4de 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs @@ -13,117 +13,145 @@ use crate::{ error::MathError, integer::{MatPolyOverZ, Z}, integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + macros::seeded::seedable_function, rational::Q, }; use std::fmt::Display; impl MatPolynomialRingZq { - /// Outputs a [`MatPolynomialRingZq`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatPolynomialRingZq`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - /// - /// let sample = MatPolynomialRingZq::sample_binomial(2, 2, &modulus, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0` or `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolynomialRingZq::new`]. - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. - pub fn sample_binomial( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(num_rows, num_cols, modulus, 0, n, p) - } - - /// Outputs a [`MatPolynomialRingZq`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatPolynomialRingZq`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - /// - /// let sample = MatPolynomialRingZq::sample_binomial_with_offset(2, 2, &modulus, -1, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0` or `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolynomialRingZq::new`]. - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0`. - pub fn sample_binomial_with_offset( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - offset: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let modulus = modulus.into(); - assert!( - modulus.get_degree() > 0, - "ModulusPolynomial of degree 0 is insufficient to sample over." - ); + seedable_function!( + /// Outputs a [`MatPolynomialRingZq`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatPolynomialRingZq`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = MatPolynomialRingZq::sample_binomial(2, 2, &modulus, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatPolynomialRingZq::sample_binomial_seeded(2, 2, &modulus, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0` or `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolynomialRingZq::new`]. + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. + pub(crate) fn sample_binomial( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded( + num_rows, num_cols, modulus, 0, n, p, seed, + ) + } + ); + + seedable_function!( + /// Outputs a [`MatPolynomialRingZq`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatPolynomialRingZq`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = MatPolynomialRingZq::sample_binomial_with_offset(2, 2, &modulus, -1, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatPolynomialRingZq::sample_binomial_with_offset_seeded(2, 2, &modulus, -1, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0` or `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolynomialRingZq::new`]. + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0`. + pub(crate) fn sample_binomial_with_offset( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + offset: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus = modulus.into(); + assert!( + modulus.get_degree() > 0, + "ModulusPolynomial of degree 0 is insufficient to sample over." + ); - let matrix = MatPolyOverZ::sample_binomial_with_offset( - num_rows, - num_cols, - modulus.get_degree() - 1, - offset, - n, - p, - )?; - Ok(MatPolynomialRingZq::from((matrix, modulus))) - } + let matrix = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + num_rows, + num_cols, + modulus.get_degree() - 1, + offset, + n, + p, + seed, + )?; + Ok(MatPolynomialRingZq::from((matrix, modulus))) + } + ); } #[cfg(test)] @@ -236,6 +264,163 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; + use crate::utils::sample::test_seed; + use crate::{ + integer::PolyOverZ, + integer_mod_q::PolyOverZq, + traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}, + }; + use std::str::FromStr; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let matrix = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 2, 0.5, test_seed()) + .unwrap(); + let entry: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= poly); + assert!(poly <= 2); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_binomial_seeded( + 1, + 1, + modulus, + 256, + 0.99999, + test_seed(), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_seeded(0, 3, modulus, 1, 0.5, test_seed()); + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, modulus, 1, 0.5, test_seed()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u16, 7u8, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u32, 7u16, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u64, 7u32, test_seed()); + let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i8, 7u64, test_seed()); + let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i16, 7i8, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i32, 7i16, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i64, 7i32, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, Z::ONE, 7i64, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u8, 0.5f32, test_seed()); + let _ = + MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u8, 0.5f64, test_seed()); + let _ = MatPolynomialRingZq::sample_binomial_seeded( + 1, + 1, + &modulus, + 1, + Q::from((1, 2)), + test_seed(), + ); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let mat_0 = + MatPolynomialRingZq::sample_binomial_seeded(3, 3, &modulus, 1, 0.5, test_seed()) + .unwrap(); + let mat_1 = + MatPolynomialRingZq::sample_binomial_seeded(4, 1, &modulus, 1, 0.5, test_seed()) + .unwrap(); + let mat_2 = + MatPolynomialRingZq::sample_binomial_seeded(1, 5, &modulus, 1, 0.5, test_seed()) + .unwrap(); + let mat_3 = + MatPolynomialRingZq::sample_binomial_seeded(15, 20, &modulus, 1, 0.5, test_seed()) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample_0 = + MatPolynomialRingZq::sample_binomial_seeded(2, 2, &modulus, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + MatPolynomialRingZq::sample_binomial_seeded(2, 2, &modulus, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; @@ -355,3 +540,285 @@ mod test_sample_binomial_with_offset { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; + use crate::utils::sample::test_seed; + use crate::{ + integer::PolyOverZ, + integer_mod_q::PolyOverZq, + traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}, + }; + use std::str::FromStr; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let matrix = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 2, + 0.5, + test_seed(), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let value = poly.get_coeff(0).unwrap(); + + assert!(16 <= value || value <= Z::ONE); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + modulus, + 1, + 256, + 0.99999, + test_seed(), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!(degree, poly.get_degree() + 1,); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 0, + 3, + modulus, + -1, + 1, + 0.5, + test_seed(), + ); + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + modulus, + -1, + 1, + 0.5, + test_seed(), + ); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1u16, + 7u8, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + 0, + 1u32, + 7u16, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1u64, + 7u32, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1i8, + 7u64, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1i16, + 7i8, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1i32, + 7i16, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1i64, + 7i32, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + Z::ONE, + 7i64, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1u8, + 0.5f32, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + 1, + 1u8, + 0.5f64, + test_seed(), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + Z::ONE, + 1, + Q::from((1, 2)), + test_seed(), + ); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let mat_0 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 3, + 3, + &modulus, + -1, + 1, + 0.5, + test_seed(), + ) + .unwrap(); + let mat_1 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 4, + 1, + &modulus, + -1, + 1, + 0.5, + test_seed(), + ) + .unwrap(); + let mat_2 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 5, + &modulus, + -1, + 1, + 0.5, + test_seed(), + ) + .unwrap(); + let mat_3 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 15, + 20, + &modulus, + -1, + 1, + 0.5, + test_seed(), + ) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample_0 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 2, 2, &modulus, -1, 2, 0.5, [42; 32], + ) + .unwrap(); + let sample_1 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 2, 2, &modulus, -1, 2, 0.5, [42; 32], + ) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs index b3e1bd3f..74534d7f 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs @@ -12,126 +12,153 @@ use crate::{ error::MathError, integer::MatPolyOverZ, integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + macros::seeded::seedable_function, rational::{PolyOverQ, Q}, }; use std::fmt::Display; impl MatPolynomialRingZq { - /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry - /// sampled independently according to the discrete Gaussian distribution, - /// using [`PolynomialRingZq::sample_discrete_gauss`](crate::integer_mod_q::PolynomialRingZq::sample_discrete_gauss). - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the Modulus for the matrix and the maximum degree - /// any discrete Gaussian polynomial can have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a [`MatPolynomialRingZq`] with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); - /// - /// let matrix = MatPolynomialRingZq::sample_discrete_gauss(3, 1, &modulus, 0, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolynomialRingZq::new`]. - /// - if `modulus` is not a valid choice for a [`ModulusPolynomialRingZq`], see - /// [`ModulusPolynomialRingZq::from`] for further information. - pub fn sample_discrete_gauss( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let modulus: ModulusPolynomialRingZq = modulus.into(); - let sample = MatPolyOverZ::sample_discrete_gauss( - num_rows, - num_cols, - modulus.get_degree() - 1, - center, - s, - )?; - Ok(MatPolynomialRingZq::from((&sample, modulus))) - } + seedable_function!( + /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry + /// sampled independently according to the discrete Gaussian distribution, + /// using [`PolynomialRingZq::sample_discrete_gauss`](crate::integer_mod_q::PolynomialRingZq::sample_discrete_gauss). + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the Modulus for the matrix and the maximum degree + /// any discrete Gaussian polynomial can have + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a [`MatPolynomialRingZq`] with each entry sampled independently from the + /// specified discrete Gaussian distribution or an error if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); + /// + #[unseeded] + /// let matrix = MatPolynomialRingZq::sample_discrete_gauss(3, 1, &modulus, 0, 1.25f32).unwrap(); + #[seeded] + /// let matrix = MatPolynomialRingZq::sample_discrete_gauss_seeded(3, 1, &modulus, 0, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolynomialRingZq::new`]. + /// - if `modulus` is not a valid choice for a [`ModulusPolynomialRingZq`], see + /// [`ModulusPolynomialRingZq::from`] for further information. + pub(crate) fn sample_discrete_gauss( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus: ModulusPolynomialRingZq = modulus.into(); + let sample = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + num_rows, + num_cols, + modulus.get_degree() - 1, + center, + s, + seed, + )?; + Ok(MatPolynomialRingZq::from((&sample, modulus))) + } + ); - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis. Hence, the callee is - /// responsible for making sure that `basis` provides a suitable basis. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `k`: the maximal length the polynomial can have - /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a vector of polynomials sampled according to the - /// discrete Gaussian distribution or an error if the basis is not a row vector, - /// `s < 0`, or the number of rows of the `basis` and `center` differ. - /// - /// # Example - /// ``` - /// use qfall_math::{ - /// integer::{MatPolyOverZ, Z}, - /// integer_mod_q::{ - /// MatPolynomialRingZq, - /// ModulusPolynomialRingZq, - /// }, - /// rational::{PolyOverQ, Q}, - /// }; - /// use std::str::FromStr; - /// - /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - /// let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); - /// let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); - /// let center = vec![PolyOverQ::default()]; - /// let s = Q::from(8); - /// - /// let sample = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, s); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), if the basis is not a row vector - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - /// - /// # Panics ... - /// - if the polynomials have higher length than the provided upper bound `k` - pub fn sample_d( - basis: &Self, - k: impl Into, - center: &[PolyOverQ], - s: impl Into, - ) -> Result { - let sample = MatPolyOverZ::sample_d(&basis.matrix, k, center, s)?; - Ok(MatPolynomialRingZq::from((&sample, &basis.get_mod()))) - } + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis. Hence, the callee is + /// responsible for making sure that `basis` provides a suitable basis. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `k`: the maximal length the polynomial can have + /// - `n`: specifies the range from which [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss) samples + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a vector of polynomials sampled according to the + /// discrete Gaussian distribution or an error if the basis is not a row vector, + /// `s < 0`, or the number of rows of the `basis` and `center` differ. + /// + /// # Examples + /// ``` + /// use qfall_math::{ + /// integer::MatPolyOverZ, + /// integer_mod_q::{ + /// MatPolynomialRingZq, + /// ModulusPolynomialRingZq, + /// }, + /// rational::{PolyOverQ, Q}, + /// }; + /// use std::str::FromStr; + /// + /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + /// let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); + /// let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); + /// let center = vec![PolyOverQ::default()]; + /// let s = Q::from(8); + /// + #[unseeded] + /// let sample = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, s).unwrap(); + #[seeded] + /// let sample = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, s, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`VectorFunctionCalledOnNonVector`](MathError::VectorFunctionCalledOnNonVector), if the basis is not a row vector + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + /// + /// # Panics ... + /// - if the polynomials have higher length than the provided upper bound `k` + pub(crate) fn sample_d( + basis: &Self, + k: impl Into, + center: &[PolyOverQ], + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let sample = + MatPolyOverZ::sample_d_optionally_seeded(&basis.matrix, k, center, s, seed)?; + Ok(MatPolynomialRingZq::from((&sample, &basis.get_mod()))) + } + ); } #[cfg(test)] @@ -164,6 +191,60 @@ mod test_sample_discrete_gauss { } } +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::PolyOverZ, + integer_mod_q::{MatPolynomialRingZq, MatZq, ModulusPolynomialRingZq}, + traits::{FromCoefficientEmbedding, MatrixGetEntry, MatrixSetEntry}, + }; + + /// Ensures that the resulting entries have correct degree. + #[test] + fn correct_degree_entries() { + let degrees = [1, 2, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut coeff_emb_mod = MatZq::new(degree + 1, 1, i64::MAX); + coeff_emb_mod.set_entry(0, 0, 1).unwrap(); + coeff_emb_mod.set_entry(degree, 0, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from_coefficient_embedding(&coeff_emb_mod); + let res = MatPolynomialRingZq::sample_discrete_gauss_seeded( + 1, + 1, + modulus, + i32::MAX, + 1, + test_seed(), + ) + .unwrap(); + + let entry: PolyOverZ = res.get_entry(0, 0).unwrap(); + assert_eq!( + entry.get_degree(), + degree - 1, + "Could fail with negligible probability." + ); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); + let sample_0 = + MatPolynomialRingZq::sample_discrete_gauss_seeded(3, 1, &modulus, 0, 1.25f32, [42; 32]) + .unwrap(); + let sample_1 = + MatPolynomialRingZq::sample_discrete_gauss_seeded(3, 1, &modulus, 0, 1.25f32, [42; 32]) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_d { use crate::{ @@ -222,3 +303,87 @@ mod test_sample_d { let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 15.75f32); } } + +#[cfg(test)] +mod test_sample_d_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::{MatPolyOverZ, PolyOverZ, Z}, + integer_mod_q::{MatPolynomialRingZq, MatZq, ModulusPolynomialRingZq, Zq}, + rational::{PolyOverQ, Q}, + traits::{IntoCoefficientEmbedding, MatrixGetEntry}, + }; + use std::str::FromStr; + + /// Ensure that the sample is from the base. + #[test] + fn ensure_sampled_from_base() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); + let base = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; + + for _ in 0..10 { + let sample = + MatPolynomialRingZq::sample_d_seeded(&base, 3, ¢er, 20.5_f64, test_seed()) + .unwrap(); + let sample: PolyOverZ = sample.get_entry(0, 0).unwrap(); + let sample_vec = MatZq::from((&sample.into_coefficient_embedding(3), 17)); + let orthogonal = MatZq::from_str("[[0],[1],[1]] mod 17").unwrap(); + + assert_eq!( + Zq::from((0, 17)), + sample_vec.dot_product(&orthogonal).unwrap() + ) + } + } + + /// Checks whether `sample_d` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); + let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; + let n = Z::from(1024); + let s = Q::from(8); + + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u8, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u32, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u64, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i32, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i16, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i8, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &n, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &s, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1.25f64, test_seed()); + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 15.75f32, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::{ + integer::MatPolyOverZ, + integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + rational::{PolyOverQ, Q}, + }; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); + let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; + let s = Q::from(8); + let sample_0 = + MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &s, [42; 32]).unwrap(); + let sample_1 = + MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &s, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs index 2a4502cb..a6e8226c 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs @@ -11,60 +11,81 @@ use crate::{ integer::MatPolyOverZ, integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + macros::seeded::seedable_function, }; use std::fmt::Display; impl MatPolynomialRingZq { - /// Outputs a [`MatPolynomialRingZq`] instance with polynomials as entries, - /// whose coefficients were chosen uniform at random in `[0, modulus.get_q())`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - /// Returns a fresh [`MatPolynomialRingZq`] instance of length `modulus.get_degree() - 1` - /// with coefficients chosen uniform at random in `[0, modulus.get_q())`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - /// - /// let matrix = MatPolynomialRingZq::sample_uniform(2, 2, &modulus); - /// ``` - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatPolynomialRingZq::new`]. - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0`. - pub fn sample_uniform( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - ) -> Self { - let modulus = modulus.into(); - assert!( - modulus.get_degree() > 0, - "ModulusPolynomial of degree 0 is insufficient to sample over." - ); - - let matrix = MatPolyOverZ::sample_uniform( - num_rows, - num_cols, - modulus.get_degree() - 1, - 0, - modulus.get_q(), - ) - .unwrap(); - MatPolynomialRingZq::from((matrix, modulus)) - } + seedable_function!( + /// Outputs a [`MatPolynomialRingZq`] instance with polynomials as entries, + /// whose coefficients were chosen uniform at random in `[0, modulus.get_q())`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`MatPolynomialRingZq`] instance of length `modulus.get_degree() - 1` + /// with coefficients chosen uniform at random in `[0, modulus.get_q())`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + /// + #[unseeded] + /// let matrix = MatPolynomialRingZq::sample_uniform(2, 2, &modulus); + #[seeded] + /// let matrix = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); + /// ``` + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatPolynomialRingZq::new`]. + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0`. + pub(crate) fn sample_uniform( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + seed: Option<[u8; 32]>, + ) -> Self { + let modulus = modulus.into(); + assert!( + modulus.get_degree() > 0, + "ModulusPolynomial of degree 0 is insufficient to sample over." + ); + + let matrix = MatPolyOverZ::sample_uniform_optionally_seeded( + num_rows, + num_cols, + modulus.get_degree() - 1, + 0, + modulus.get_q(), + seed, + ) + .unwrap(); + MatPolynomialRingZq::from((matrix, modulus)) + } + ); } #[cfg(test)] @@ -170,3 +191,120 @@ mod test_sample_uniform { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::integer::PolyOverZ; + use crate::integer_mod_q::PolyOverZq; + use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}; + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + }; + use std::str::FromStr; + + /// Checks whether the boundaries of the interval are kept for small intervals. + #[test] + fn boundaries_kept_small() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= coeff); + assert!(coeff < 17); + } + } + + /// Checks whether the boundaries of the interval are kept for large intervals. + #[test] + fn boundaries_kept_large() { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= coeff); + assert!(coeff < u64::MAX); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too large for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_uniform_seeded(0, 3, &modulus, test_seed()); + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let mat_0 = MatPolynomialRingZq::sample_uniform_seeded(3, 3, &modulus, test_seed()); + let mat_1 = MatPolynomialRingZq::sample_uniform_seeded(4, 1, &modulus, test_seed()); + let mat_2 = MatPolynomialRingZq::sample_uniform_seeded(1, 5, &modulus, test_seed()); + let mat_3 = MatPolynomialRingZq::sample_uniform_seeded(15, 20, &modulus, test_seed()); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample_0 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); + let sample_1 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_zq/sample/binomial.rs b/src/integer_mod_q/mat_zq/sample/binomial.rs index 3e003bdc..11705ba0 100644 --- a/src/integer_mod_q/mat_zq/sample/binomial.rs +++ b/src/integer_mod_q/mat_zq/sample/binomial.rs @@ -13,6 +13,7 @@ use crate::{ error::MathError, integer::Z, integer_mod_q::{MatZq, Modulus}, + macros::seeded::seedable_function, rational::Q, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::binomial::BinomialSampler, @@ -20,110 +21,136 @@ use crate::{ use std::fmt::Display; impl MatZq { - /// Outputs a [`MatZq`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the [`Modulus`] of the new [`MatZq`] instance - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatZq`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::MatZq; - /// - /// let sample = MatZq::sample_binomial(2, 2, 7, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZq::new`]. - /// - if `modulus` is smaller than `2`. - pub fn sample_binomial( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(num_rows, num_cols, 0, modulus, n, p) - } + seedable_function!( + /// Outputs a [`MatZq`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the [`Modulus`] of the new [`MatZq`] instance + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZq`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::MatZq; + /// + #[unseeded] + /// let sample = MatZq::sample_binomial(2, 2, 7, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatZq::sample_binomial_seeded(2, 2, 7, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZq::new`]. + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_binomial( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded( + num_rows, num_cols, 0, modulus, n, p, seed, + ) + } + ); - /// Outputs a [`MatZq`] instance with entries chosen according to the binomial - /// distribution parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `modulus`: specifies the [`Modulus`] of the new [`MatZq`] instance - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a new [`MatZq`] instance with entries chosen - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or the dimensions of the matrix were chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::MatZq; - /// - /// let sample = MatZq::sample_binomial_with_offset(2, 2, -1, 7, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided number of rows and columns are not suited to create a matrix. - /// For further information see [`MatZq::new`]. - /// - if `modulus` is smaller than `2`. - pub fn sample_binomial_with_offset( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - offset: impl Into, - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let offset: Z = offset.into(); - let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p, None)?; - let mut matrix = MatZq::new(num_rows, num_cols, &modulus); - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let mut sample = bin_sampler.sample(); - sample += &offset; - sample = sample % &modulus; - unsafe { matrix.set_entry_unchecked(row, col, sample) }; + seedable_function!( + /// Outputs a [`MatZq`] instance with entries chosen according to the binomial + /// distribution parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `modulus`: specifies the [`Modulus`] of the new [`MatZq`] instance + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZq`] instance with entries chosen + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or the dimensions of the matrix were chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::MatZq; + /// + #[unseeded] + /// let sample = MatZq::sample_binomial_with_offset(2, 2, -1, 7, 2, 0.5).unwrap(); + #[seeded] + /// let sample = MatZq::sample_binomial_with_offset_seeded(2, 2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided number of rows and columns are not suited to create a matrix. + /// For further information see [`MatZq::new`]. + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_binomial_with_offset( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + offset: impl Into, + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let offset: Z = offset.into(); + let modulus: Modulus = modulus.into(); + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; + let mut matrix = MatZq::new(num_rows, num_cols, &modulus); + + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let mut sample = bin_sampler.sample(); + sample += &offset; + sample = sample % &modulus; + unsafe { matrix.set_entry_unchecked(row, col, sample) }; + } } - } - Ok(matrix) - } + Ok(matrix) + } + ); } #[cfg(test)] @@ -200,6 +227,91 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{MatZq, Q, Z}; + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let matrix = MatZq::sample_binomial_seeded(1, 1, 7, 2, 0.5, test_seed()).unwrap(); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } + } + + /// Checks if entries are properly reduced. + #[test] + fn reduced() { + let matrix = MatZq::sample_binomial_seeded(1, 1, 3, 7, 0.5, test_seed()).unwrap(); + let value: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= value && value <= 3) + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatZq::sample_binomial_seeded(0, 3, 7, 1, 0.5, test_seed()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let _ = MatZq::sample_binomial_seeded(1, 1, 7u8, 1u16, 7u8, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7u16, 1u32, 7u16, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7u32, 1u64, 7u32, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7u64, 1i8, 7u64, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7i8, 1i16, 7i8, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7i16, 1i32, 7i16, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7i32, 1i64, 7i32, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7i64, Z::ONE, 7i64, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1u8, 0.5f32, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1u8, 0.5f64, test_seed()); + let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = MatZq::sample_binomial_seeded(3, 3, 7, 1, 0.5, test_seed()).unwrap(); + let mat_1 = MatZq::sample_binomial_seeded(4, 1, 7, 1, 0.5, test_seed()).unwrap(); + let mat_2 = MatZq::sample_binomial_seeded(1, 5, 7, 1, 0.5, test_seed()).unwrap(); + let mat_3 = MatZq::sample_binomial_seeded(15, 20, 7, 1, 0.5, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::MatZq; + let sample_0 = MatZq::sample_binomial_seeded(2, 2, 7, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = MatZq::sample_binomial_seeded(2, 2, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatZq, Q, Z}; @@ -265,3 +377,93 @@ mod test_sample_binomial_with_offset { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{MatZq, Q, Z}; + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let matrix = + MatZq::sample_binomial_with_offset_seeded(1, 1, -1, 7, 2, 0.5, test_seed()) + .unwrap(); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too big for an [`i64`] results in an error. + #[should_panic] + #[test] + fn false_size() { + let _ = MatZq::sample_binomial_with_offset_seeded(0, 3, 0, 7, 1, 0.5, test_seed()); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0u8, 7u8, 1u16, 7u8, test_seed()); + let _ = + MatZq::sample_binomial_with_offset_seeded(1, 1, 0u16, 7u16, 1u32, 7u16, test_seed()); + let _ = + MatZq::sample_binomial_with_offset_seeded(1, 1, 0u32, 7u32, 1u64, 7u32, test_seed()); + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0u64, 7u64, 1i8, 7u64, test_seed()); + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0i8, 7i8, 1i16, 7i8, test_seed()); + let _ = + MatZq::sample_binomial_with_offset_seeded(1, 1, 0i16, 7i16, 1i32, 7i16, test_seed()); + let _ = + MatZq::sample_binomial_with_offset_seeded(1, 1, 0i64, 7i32, 1i64, 7i32, test_seed()); + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7i64, Z::ONE, 7i64, test_seed()); + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1u8, 0.5f32, test_seed()); + let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1u8, 0.5f64, test_seed()); + let _ = + MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let mat_0 = + MatZq::sample_binomial_with_offset_seeded(3, 3, 0, 7, 1, 0.5, test_seed()).unwrap(); + let mat_1 = + MatZq::sample_binomial_with_offset_seeded(4, 1, 0, 7, 1, 0.5, test_seed()).unwrap(); + let mat_2 = + MatZq::sample_binomial_with_offset_seeded(1, 5, 0, 7, 1, 0.5, test_seed()).unwrap(); + let mat_3 = + MatZq::sample_binomial_with_offset_seeded(15, 20, 0, 7, 1, 0.5, test_seed()).unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::MatZq; + let sample_0 = + MatZq::sample_binomial_with_offset_seeded(2, 2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + MatZq::sample_binomial_with_offset_seeded(2, 2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs index 4e618383..e584af88 100644 --- a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs @@ -11,6 +11,7 @@ use crate::{ error::MathError, integer_mod_q::{MatZq, Modulus}, + macros::seeded::seedable_function, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::discrete_gauss::{ @@ -21,177 +22,217 @@ use crate::{ use std::fmt::Display; impl MatZq { - /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry - /// sampled independently according to the discrete Gaussian distribution, - /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a matrix with each entry sampled independently from the - /// specified discrete Gaussian distribution or an error if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::MatZq; - /// - /// let sample = MatZq::sample_discrete_gauss(3, 1, 83, 0, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if the provided number of rows and columns or the modulus are not suited to create a matrix. - /// For further information see [`MatZq::new`]. - /// - if the provided `modulus < 2`. - pub fn sample_discrete_gauss( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let modulus: Modulus = modulus.into(); - let center: Q = center.into(); - let s: Q = s.into(); - let mut out = Self::new(num_rows, num_cols, &modulus); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &s, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - )?; - - for row in 0..out.get_num_rows() { - for col in 0..out.get_num_columns() { - let mut sample = dgis.sample_z(); - sample = sample % &modulus; - unsafe { out.set_entry_unchecked(row, col, sample) }; + seedable_function!( + /// Initializes a new matrix with dimensions `num_rows` x `num_columns` and with each entry + /// sampled independently according to the discrete Gaussian distribution, + /// using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a matrix with each entry sampled independently from the + /// specified discrete Gaussian distribution or an error if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::MatZq; + /// + #[unseeded] + /// let sample = MatZq::sample_discrete_gauss(3, 1, 83, 0, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if the provided number of rows and columns or the modulus are not suited to create a matrix. + /// For further information see [`MatZq::new`]. + /// - if the provided `modulus < 2`. + pub(crate) fn sample_discrete_gauss( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus: Modulus = modulus.into(); + let center: Q = center.into(); + let s: Q = s.into(); + let mut out = Self::new(num_rows, num_cols, &modulus); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seed, + )?; + + for row in 0..out.get_num_rows() { + for col in 0..out.get_num_columns() { + let mut sample = dgis.sample_z(); + sample = sample % &modulus; + unsafe { out.set_entry_unchecked(row, col, sample) }; + } } + + Ok(out) } + ); - Ok(out) - } + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis. Hence, the callee is + /// responsible for making sure that `basis` provides a suitable basis. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a lattice vector sampled according to the discrete Gaussian distribution + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, + /// or if `center` is not a column vector. + /// + /// # Examples + /// ``` + /// use qfall_math::{integer_mod_q::MatZq, rational::MatQ}; + /// let basis = MatZq::identity(5, 5, 17); + /// let center = MatQ::new(5, 1); + /// + #[unseeded] + /// let sample = MatZq::sample_d(&basis, ¢er, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZq::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) + /// if `center` is not a column vector. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_d( + basis: &MatZq, + center: &MatQ, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let s: Q = s.into(); - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis. Hence, the callee is - /// responsible for making sure that `basis` provides a suitable basis. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, - /// or if `center` is not a column vector. - /// - /// # Examples - /// ``` - /// use qfall_math::{integer_mod_q::MatZq, rational::MatQ}; - /// let basis = MatZq::identity(5, 5, 17); - /// let center = MatQ::new(5, 1); - /// - /// let sample = MatZq::sample_d(&basis, ¢er, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) - /// if `center` is not a column vector. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_d(basis: &MatZq, center: &MatQ, s: impl Into) -> Result { - let s: Q = s.into(); - - let sample = sample_d( - &basis.get_representative_least_nonnegative_residue(), - center, - &s, - None, - )?; - - Ok(MatZq::from((&sample, basis.get_mod()))) - } + let sample = sample_d( + &basis.get_representative_least_nonnegative_residue(), + center, + &s, + seed, + )?; - /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. - /// - /// We do not check whether `basis` is actually a basis or whether `basis_gso` is - /// actually the gso of `basis`. Hence, the callee is responsible for making sure - /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO. - /// - /// Parameters: - /// - `basis`: specifies a basis for the lattice from which is sampled - /// - `basis_gso`: specifies the precomputed gso for `basis` - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a lattice vector sampled according to the discrete Gaussian distribution - /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, - /// or if `center` is not a column vector. - /// - /// # Examples - /// ``` - /// use qfall_math::{integer::MatZ, integer_mod_q::MatZq, rational::MatQ}; - /// let basis = MatZq::identity(5, 5, 17); - /// let center = MatQ::new(5, 1); - /// let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso(); - /// - /// let sample = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) - /// if the number of rows of the `basis` and `center` differ. - /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) - /// if `center` is not a column vector. - /// - /// # Panics ... - /// - if the number of rows/columns of `basis_gso` and `basis` mismatch. - /// - /// This function implements SampleD according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_d_precomputed_gso( - basis: &MatZq, - basis_gso: &MatQ, - center: &MatQ, - s: impl Into, - ) -> Result { - let s: Q = s.into(); - - let sample = sample_d_precomputed_gso( - &basis.get_representative_least_nonnegative_residue(), - basis_gso, - center, - &s, - None, - )?; - - Ok(MatZq::from((&sample, basis.get_mod()))) - } + Ok(MatZq::from((&sample, basis.get_mod()))) + } + ); + + seedable_function!( + /// SampleD samples a discrete Gaussian from the lattice with a provided `basis`. + /// + /// We do not check whether `basis` is actually a basis or whether `basis_gso` is + /// actually the gso of `basis`. Hence, the callee is responsible for making sure + /// that `basis` provides a suitable basis and `basis_gso` is a corresponding GSO. + /// + /// Parameters: + /// - `basis`: specifies a basis for the lattice from which is sampled + /// - `basis_gso`: specifies the precomputed gso for `basis` + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a lattice vector sampled according to the discrete Gaussian distribution + /// or an error if `s < 0`, the number of rows of the `basis` and `center` differ, + /// or if `center` is not a column vector. + /// + /// # Examples + /// ``` + /// use qfall_math::{integer_mod_q::MatZq, rational::MatQ}; + /// let basis = MatZq::identity(5, 5, 17); + /// let center = MatQ::new(5, 1); + /// let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso(); + /// + #[unseeded] + /// let sample = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f32).unwrap(); + #[seeded] + /// let sample = MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// - Returns a [`MathError`] of type [`MismatchingMatrixDimension`](MathError::MismatchingMatrixDimension) + /// if the number of rows of the `basis` and `center` differ. + /// - Returns a [`MathError`] of type [`StringConversionError`](MathError::StringConversionError) + /// if `center` is not a column vector. + /// + /// # Panics ... + /// - if the number of rows/columns of `basis_gso` and `basis` mismatch. + /// + /// This function implements SampleD according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_d_precomputed_gso( + basis: &MatZq, + basis_gso: &MatQ, + center: &MatQ, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let s: Q = s.into(); + + let sample = sample_d_precomputed_gso( + &basis.get_representative_least_nonnegative_residue(), + basis_gso, + center, + &s, + seed, + )?; + + Ok(MatZq::from((&sample, basis.get_mod()))) + } + ); } #[cfg(test)] @@ -240,6 +281,63 @@ mod test_sample_discrete_gauss { } } +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{MatZq, Modulus}, + rational::Q, + traits::MatrixGetEntry, + }; + + // This function only allows for a broader availability, which is tested here. + + /// Checks if entries are properly reduced. + #[test] + fn reduced() { + let matrix = MatZq::sample_discrete_gauss_seeded(1, 1, 3, 0, 15.0, test_seed()).unwrap(); + let value: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= value && value <= 3) + } + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let n = Z::from(1024); + let center = Q::ZERO; + let s = Q::ONE; + let modulus = Modulus::from(83); + + let _ = MatZq::sample_discrete_gauss_seeded(2u64, 3i8, &modulus, 0f32, 1u16, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(3u8, 2i16, 83u8, ¢er, 1u8, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, &n, ¢er, 1u32, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83i8, ¢er, 1u64, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i64, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i32, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i16, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i8, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i64, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, &n, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, &s, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1.25f64, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, 0, 1.25f64, test_seed()); + let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, center, 15.75f32, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::MatZq; + let sample_0 = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap(); + let sample_1 = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_d { use crate::{ @@ -302,3 +400,119 @@ mod test_sample_d { let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32); } } + +#[cfg(test)] +mod test_sample_d_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::MatZq, + rational::{MatQ, Q}, + }; + + // Appropriate inputs were tested in utils and thus omitted here. + // This function only allows for a broader availability, which is tested here. + + /// Checks whether `sample_d` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let basis = MatZq::identity(5, 5, 17); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u8, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u32, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u64, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i32, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i16, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i8, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, &n, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, &s, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1.25f64, test_seed()); + let _ = MatZq::sample_d_seeded(&basis, ¢er, 15.75f32, test_seed()); + } + + /// Checks whether `sample_d_precomputed_gso` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability_prec_gso() { + let basis = MatZq::identity(5, 5, 17); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()); + + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u16, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u8, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u32, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u64, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i32, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i16, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i8, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &n, test_seed()); + let _ = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &s, test_seed()); + let _ = MatZq::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 1.25f64, + test_seed(), + ); + let _ = MatZq::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 15.75f32, + test_seed(), + ); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample_d() { + use crate::{integer_mod_q::MatZq, rational::MatQ}; + let basis = MatZq::identity(5, 5, 17); + let center = MatQ::new(5, 1); + let sample_0 = MatZq::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + let sample_1 = MatZq::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample_d_precomputed_gso() { + use crate::{integer_mod_q::MatZq, rational::MatQ}; + let basis = MatZq::identity(5, 5, 17); + let center = MatQ::new(5, 1); + let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso(); + let sample_0 = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); + let sample_1 = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/mat_zq/sample/uniform.rs b/src/integer_mod_q/mat_zq/sample/uniform.rs index 1848e7d4..de0e1e69 100644 --- a/src/integer_mod_q/mat_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_zq/sample/uniform.rs @@ -11,56 +11,76 @@ use crate::{ integer::Z, integer_mod_q::MatZq, + macros::seeded::seedable_function, traits::{MatrixDimensions, MatrixSetEntry}, utils::sample::uniform::UniformIntegerSampler, }; use std::fmt::Display; impl MatZq { - /// Outputs a [`MatZq`] instance with entries chosen uniform at random - /// in `[0, modulus)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows the new matrix should have - /// - `num_cols`: specifies the number of columns the new matrix should have - /// - `modulus`: specifies the modulus of the matrix and defines the interval - /// over which is sampled - /// - /// Returns a new [`MatZq`] instance with entries chosen - /// uniformly at random in `[0, modulus)`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::MatZq; - /// - /// let matrix = MatZq::sample_uniform(3, 3, 17); - /// ``` - /// - /// # Panics ... - /// - if the provided number of rows and columns or the modulus are not suited to create a matrix. - /// For further information see [`MatZq::new`]. - pub fn sample_uniform( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - modulus: impl Into, - ) -> Self { - let modulus: Z = modulus.into(); - let mut uis = UniformIntegerSampler::init(&modulus, None).unwrap(); - let mut matrix = MatZq::new(num_rows, num_cols, modulus); - - for row in 0..matrix.get_num_rows() { - for col in 0..matrix.get_num_columns() { - let sample = uis.sample(); - unsafe { matrix.set_entry_unchecked(row, col, sample) }; + seedable_function!( + /// Outputs a [`MatZq`] instance with entries chosen uniform at random + /// in `[0, modulus)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows the new matrix should have + /// - `num_cols`: specifies the number of columns the new matrix should have + /// - `modulus`: specifies the modulus of the matrix and defines the interval + /// over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`MatZq`] instance with entries chosen + /// uniformly at random in `[0, modulus)`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::MatZq; + /// + #[unseeded] + /// let matrix = MatZq::sample_uniform(3, 3, 17); + #[seeded] + /// let matrix = MatZq::sample_uniform_seeded(3, 3, 17, [42; 32]); + /// ``` + /// + /// # Panics ... + /// - if the provided number of rows and columns or the modulus are not suited to create a matrix. + /// For further information see [`MatZq::new`]. + pub(crate) fn sample_uniform( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + modulus: impl Into, + seed: Option<[u8; 32]>, + ) -> Self { + let modulus: Z = modulus.into(); + let mut uis = UniformIntegerSampler::init(&modulus, seed).unwrap(); + let mut matrix = MatZq::new(num_rows, num_cols, modulus); + + for row in 0..matrix.get_num_rows() { + for col in 0..matrix.get_num_columns() { + let sample = uis.sample(); + unsafe { matrix.set_entry_unchecked(row, col, sample) }; + } } - } - matrix - } + matrix + } + ); } #[cfg(test)] @@ -151,3 +171,103 @@ mod test_sample_uniform { assert_eq!(20, mat_3.get_num_columns()); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::traits::{MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{MatZq, Modulus}, + }; + + /// Checks whether the boundaries of the interval are kept for small moduli. + #[test] + fn boundaries_kept_small() { + for _ in 0..32 { + let matrix = MatZq::sample_uniform_seeded(1, 1, 17, test_seed()); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < 17); + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + #[test] + fn boundaries_kept_large() { + let modulus = Z::from(u64::MAX); + for _ in 0..256 { + let matrix = MatZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } + } + + /// Checks whether matrices with at least one dimension chosen smaller than `1` + /// or too large for an [`i64`] results in a panic. + #[should_panic] + #[test] + fn false_size() { + let modulus = Z::from(15); + + let _ = MatZq::sample_uniform_seeded(0, 3, &modulus, test_seed()); + } + + /// Checks whether providing an invalid interval/ modulus results in a panic. + #[should_panic] + #[test] + fn invalid_modulus() { + let _ = MatZq::sample_uniform_seeded(4, 1, 1, test_seed()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = MatZq::sample_uniform_seeded(1, 1, 7u8, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7u16, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7u32, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7u64, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7i8, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7i16, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7i32, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, 7i64, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); + let _ = MatZq::sample_uniform_seeded(1, 1, &z, test_seed()); + } + + /// Checks whether the size of uniformly random sampled matrices + /// fits the specified dimensions. + #[test] + fn matrix_size() { + let modulus = Z::from(15); + + let mat_0 = MatZq::sample_uniform_seeded(3, 3, &modulus, test_seed()); + let mat_1 = MatZq::sample_uniform_seeded(4, 1, &modulus, test_seed()); + let mat_2 = MatZq::sample_uniform_seeded(1, 5, &modulus, test_seed()); + let mat_3 = MatZq::sample_uniform_seeded(15, 20, &modulus, test_seed()); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::MatZq; + let sample_0 = MatZq::sample_uniform_seeded(3, 3, 17, [42; 32]); + let sample_1 = MatZq::sample_uniform_seeded(3, 3, 17, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs index da3b1570..03e04249 100644 --- a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs @@ -10,47 +10,69 @@ use crate::{ integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}, + macros::seeded::seedable_function, utils::sample::uniform::UniformIntegerSampler, }; impl NTTPolynomialRingZq { - /// Generates a [`NTTPolynomialRingZq`] instance with degree `modulus_degree - 1` - /// and entries chosen uniform at random in `[0, modulus)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `modulus_degree`: specifies the degree of the modulus polynomial, i.e. the maximum number - /// of sampled coefficients is `modulus_degree - 1` - /// - `modulus`: specifies the modulus of the values and thus, - /// the interval size over which is sampled - /// - /// Returns a fresh [`NTTPolynomialRingZq`] instance of length `modulus_degree` with entries - /// chosen uniform at random in `[0, modulus)`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{NTTPolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - /// modulus.set_ntt_unchecked(64); - /// - /// let sample = NTTPolynomialRingZq::sample_uniform(&modulus); - /// ``` - pub fn sample_uniform(modulus: &ModulusPolynomialRingZq) -> Self { - let interval_size = modulus.get_q(); - assert!(interval_size > 1); - - let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); - - let vector = (0..modulus.get_degree()).map(|_| uis.sample()).collect(); - Self { - poly: vector, - modulus: modulus.clone(), + seedable_function!( + /// Generates a [`NTTPolynomialRingZq`] instance with degree `modulus_degree - 1` + /// and entries chosen uniform at random in `[0, modulus)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `modulus_degree`: specifies the degree of the modulus polynomial, i.e. the maximum number + /// of sampled coefficients is `modulus_degree - 1` + /// - `modulus`: specifies the modulus of the values and thus, + /// the interval size over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`NTTPolynomialRingZq`] instance of length `modulus_degree` with entries + /// chosen uniform at random in `[0, modulus)`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{NTTPolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + /// modulus.set_ntt_unchecked(64); + /// + #[unseeded] + /// let sample = NTTPolynomialRingZq::sample_uniform(&modulus); + #[seeded] + /// let sample = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + /// ``` + pub(crate) fn sample_uniform( + modulus: &ModulusPolynomialRingZq, + seed: Option<[u8; 32]>, + ) -> Self { + let interval_size = modulus.get_q(); + assert!(interval_size > 1); + + let mut uis = UniformIntegerSampler::init(&interval_size, seed).unwrap(); + + let vector = (0..modulus.get_degree()).map(|_| uis.sample()).collect(); + Self { + poly: vector, + modulus: modulus.clone(), + } } - } + ); } #[cfg(test)] @@ -87,3 +109,52 @@ mod test_sample_uniform { assert_eq!(4, res.poly.len(),); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}, + }; + use std::str::FromStr; + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + let poly = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + + for i in 0..4 { + let sample = &poly.poly[i]; + assert!(&Z::ZERO <= sample); + assert!(sample < &Z::from(257)); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + let res = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + + assert_eq!(4, res.poly.len(),); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}; + use std::str::FromStr; + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + let sample_0 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + let sample_1 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/poly_over_zq/sample/binomial.rs b/src/integer_mod_q/poly_over_zq/sample/binomial.rs index b404cda2..2b254a58 100644 --- a/src/integer_mod_q/poly_over_zq/sample/binomial.rs +++ b/src/integer_mod_q/poly_over_zq/sample/binomial.rs @@ -13,6 +13,7 @@ use crate::{ error::MathError, integer::Z, integer_mod_q::{Modulus, PolyOverZq}, + macros::seeded::seedable_function, rational::Q, traits::SetCoefficient, utils::{index::evaluate_index, sample::binomial::BinomialSampler}, @@ -20,106 +21,130 @@ use crate::{ use std::fmt::Display; impl PolyOverZq { - /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p`. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolyOverZq`] instance with each value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or `max_degree` is negative or does not into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::PolyOverZq; - /// - /// let sample = PolyOverZq::sample_binomial(2, 7, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - pub fn sample_binomial( - max_degree: impl TryInto + Display, - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(max_degree, 0, modulus, n, p) - } - - /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolyOverZq`] instance with each value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], - /// or `max_degree` is negative or does not into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::PolyOverZq; - /// - /// let sample = PolyOverZq::sample_binomial_with_offset(2, -1, 7, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if `modulus` is smaller than `2`. - pub fn sample_binomial_with_offset( - max_degree: impl TryInto + Display, - offset: impl Into, - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree)?; - let offset: Z = offset.into(); - let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p, None)?; - - let mut poly_z = PolyOverZq::from(&modulus); - - for index in 0..=max_degree { - let mut sample = bin_sampler.sample(); - sample += &offset; - unsafe { poly_z.set_coeff_unchecked(index, sample) }; + seedable_function!( + /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p`. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZq`] instance with each value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or `max_degree` is negative or does not into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::PolyOverZq; + /// + #[unseeded] + /// let sample = PolyOverZq::sample_binomial(2, 7, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + pub(crate) fn sample_binomial( + max_degree: impl TryInto + Display, + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded(max_degree, 0, modulus, n, p, seed) } + ); - Ok(poly_z) - } + seedable_function!( + /// Generates a [`PolyOverZq`] instance of maximum degree `max_degree` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `modulus`: specifies the [`Modulus`] of the new [`PolyOverZq`] instance + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZq`] instance with each value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`], + /// or `max_degree` is negative or does not into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::PolyOverZq; + /// + #[unseeded] + /// let sample = PolyOverZq::sample_binomial_with_offset(2, -1, 7, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_binomial_with_offset( + max_degree: impl TryInto + Display, + offset: impl Into, + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree)?; + let offset: Z = offset.into(); + let modulus: Modulus = modulus.into(); + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; + + let mut poly_z = PolyOverZq::from(&modulus); + + for index in 0..=max_degree { + let mut sample = bin_sampler.sample(); + sample += &offset; + unsafe { poly_z.set_coeff_unchecked(index, sample) }; + } + + Ok(poly_z) + } + ); } #[cfg(test)] @@ -185,6 +210,82 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{PolyOverZq, Q, Z}; + use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let poly = PolyOverZq::sample_binomial_seeded(0, 7, 2, 0.5, test_seed()).unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = + PolyOverZq::sample_binomial_seeded(degree, u64::MAX, 256, 0.99999, test_seed()) + .unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = PolyOverZq::sample_binomial_seeded(-1, 7, 2, 0.5, test_seed()); + let res_1 = PolyOverZq::sample_binomial_seeded(i64::MIN, 7, 2, 0.5, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = PolyOverZq::sample_binomial_seeded(1, 7u8, 1u16, 7u8, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7u16, 1u32, 7u16, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7u32, 1u64, 7u32, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7u64, 1i8, 7u64, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7i8, 1i16, 7i8, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7i16, 1i32, 7i16, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7i32, 1i64, 7i32, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7i64, Z::ONE, 7i64, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1u8, 0.5f32, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1u8, 0.5f64, test_seed()); + let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::PolyOverZq; + let sample_0 = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{PolyOverZq, Q, Z}; @@ -243,3 +344,88 @@ mod test_sample_binomial_with_offset { let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7, 1, Q::from((1, 2))); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{PolyOverZq, Q, Z}; + use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + for _ in 0..8 { + let poly = + PolyOverZq::sample_binomial_with_offset_seeded(0, -1, 7, 2, 0.5, test_seed()) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = + PolyOverZq::sample_binomial_with_offset_seeded(degree, 1, 7, 2, 0.5, test_seed()) + .unwrap(); + + assert_eq!(degree, res.get_degree()); + } + } + + /// Checks whether providing a length smaller than `0` results in an error. + #[test] + fn invalid_max_degree() { + let res_0 = PolyOverZq::sample_binomial_with_offset_seeded(-1, -1, 7, 2, 0.5, test_seed()); + let res_1 = + PolyOverZq::sample_binomial_with_offset_seeded(i64::MIN, -1, 7, 2, 0.5, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_binomial_with_offset` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + #[test] + fn availability() { + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u8, 1u16, 7u8, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u16, 1u32, 7u16, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u32, 1u64, 7u32, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u64, 1i8, 7u64, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i8, 1i16, 7i8, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i16, 1i32, 7i16, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i32, 1i64, 7i32, test_seed()); + let _ = + PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i64, Z::ONE, 7i64, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7, 1u8, 0.5f32, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7, 1u8, 0.5f64, test_seed()); + let _ = PolyOverZq::sample_binomial_with_offset_seeded( + 1, + 0, + 7, + 1, + Q::from((1, 2)), + test_seed(), + ); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::PolyOverZq; + let sample_0 = + PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs index 80b1e55c..abac6bd5 100644 --- a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs @@ -12,6 +12,7 @@ use crate::{ error::MathError, integer_mod_q::{Modulus, PolyOverZq}, + macros::seeded::seedable_function, rational::Q, traits::SetCoefficient, utils::{ @@ -22,62 +23,74 @@ use crate::{ use std::fmt::Display; impl PolyOverZq { - /// Initializes a new [`PolyOverZq`] with maximum degree `max_degree` - /// and with each entry sampled independently according to the - /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). - /// - /// Parameters: - /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZq`] should have - /// - `modulus`: specififes the [`Modulus`] over which the ring of integer coefficients is defined - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a fresh [`PolyOverZq`] instance of maximum degree `max_degree` - /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::PolyOverZq; - /// - /// let sample = PolyOverZq::sample_discrete_gauss(2, 17, 0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if `max_degree` is negative, or does not fit into an [`i64`]. - /// - if `modulus` is smaller than `2`. - pub fn sample_discrete_gauss( - max_degree: impl TryInto + Display, - modulus: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree).unwrap(); - let modulus = modulus.into(); - - let center = center.into(); - let s = s.into(); - let mut poly = PolyOverZq::from(&modulus); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &s, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - )?; - - for index in 0..=max_degree { - let sample = dgis.sample_z(); - unsafe { poly.set_coeff_unchecked(index, sample) }; + seedable_function!( + /// Initializes a new [`PolyOverZq`] with maximum degree `max_degree` + /// and with each entry sampled independently according to the + /// discrete Gaussian distribution, using [`Z::sample_discrete_gauss`](crate::integer::Z::sample_discrete_gauss). + /// + /// Parameters: + /// - `max_degree`: specifies the included maximal degree the created [`PolyOverZq`] should have + /// - `modulus`: specififes the [`Modulus`] over which the ring of integer coefficients is defined + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZq`] instance of maximum degree `max_degree` + /// with coefficients chosen independently according the discrete Gaussian distribution or + /// a [`MathError`] if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::PolyOverZq; + /// + #[unseeded] + /// let sample = PolyOverZq::sample_discrete_gauss(2, 17, 0, 1).unwrap(); + #[seeded] + /// let sample = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if `max_degree` is negative, or does not fit into an [`i64`]. + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_discrete_gauss( + max_degree: impl TryInto + Display, + modulus: impl Into, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree).unwrap(); + let modulus = modulus.into(); + + let center = center.into(); + let s = s.into(); + let mut poly = PolyOverZq::from(&modulus); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seed, + )?; + + for index in 0..=max_degree { + let sample = dgis.sample_z(); + unsafe { poly.set_coeff_unchecked(index, sample) }; + } + Ok(poly) } - Ok(poly) - } + ); } #[cfg(test)] @@ -165,3 +178,109 @@ mod test_sample_discrete_gauss { let _ = PolyOverZq::sample_discrete_gauss(3, 1, 0, 1).unwrap(); } } + +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::Z, integer_mod_q::PolyOverZq, rational::Q, traits::GetCoefficient}; + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let center = Q::ZERO; + let s = Q::ONE; + + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1u8, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u16, 17u16, 0f64, 1u16, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u32, 17u32, 0f32, 1u32, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u64, 17u64, 0f64, 1u64, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1i8, 17u8, 0f32, 1i8, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1i8, 17i8, 0f32, 1i16, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1i16, 17i16, 0f32, 1i32, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1i32, 17i32, 0f64, 1i64, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1i64, 17i64, center, s, test_seed()); + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1f64, test_seed()); + } + + /// Checks whether the boundaries of the interval are kept for small moduli. + #[test] + fn boundaries_kept_small() { + let modulus = 128; + + for _ in 0..32 { + let poly = + PolyOverZq::sample_discrete_gauss_seeded(3, modulus, 15, 1, test_seed()).unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + #[test] + fn boundaries_kept_large() { + let modulus = u64::MAX; + + for _ in 0..256 { + let poly = + PolyOverZq::sample_discrete_gauss_seeded(3, modulus, 1, 1, test_seed()).unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_discrete_gauss_seeded( + degree, + u64::MAX, + i64::MAX, + 1, + test_seed(), + ) + .unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } + } + + /// Checks whether the maximum degree needs to be at least 0. + #[test] + #[should_panic] + fn invalid_max_degree() { + let _ = PolyOverZq::sample_discrete_gauss_seeded(-1, 17, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether too small modulus is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let _ = PolyOverZq::sample_discrete_gauss_seeded(3, 1, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::PolyOverZq; + let sample_0 = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap(); + let sample_1 = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/poly_over_zq/sample/uniform.rs b/src/integer_mod_q/poly_over_zq/sample/uniform.rs index 5e0a267c..0793cdc2 100644 --- a/src/integer_mod_q/poly_over_zq/sample/uniform.rs +++ b/src/integer_mod_q/poly_over_zq/sample/uniform.rs @@ -12,61 +12,81 @@ use crate::{ error::MathError, integer::Z, integer_mod_q::{Modulus, PolyOverZq}, + macros::seeded::seedable_function, traits::SetCoefficient, utils::{index::evaluate_index, sample::uniform::UniformIntegerSampler}, }; use std::fmt::Display; impl PolyOverZq { - /// Generates a [`PolyOverZq`] instance with maximum degree `max_degree` - /// and coefficients chosen uniform at random in `[0, modulus)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `max_degree`: specifies the length of the polynomial, - /// i.e. the number of coefficients - /// - `modulus`: specifies the modulus of the coefficients and thus, - /// the interval size over which is sampled - /// - /// Returns a fresh [`PolyOverZq`] instance of length `max_degree` with coefficients - /// chosen uniform at random in `[0, modulus)` or a [`MathError`] - /// if the `max_degree` was smaller than `0` or the provided `modulus` was chosen too small. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::PolyOverZq; - /// - /// let sample = PolyOverZq::sample_uniform(3, 17).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if the given `modulus` isn't larger than `1`, i.e. the interval size is at most `1`. - /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if - /// the `max_degree` is negative or it does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if `modulus` is smaller than `2`. - pub fn sample_uniform( - max_degree: impl TryInto + Display + Copy, - modulus: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree)?; - let interval_size = modulus.into(); - let modulus = Modulus::from(&interval_size); - let mut poly_zq = PolyOverZq::from(&modulus); - - let mut uis = UniformIntegerSampler::init(&interval_size, None)?; - - for index in 0..=max_degree { - let sample = uis.sample(); - unsafe { poly_zq.set_coeff_unchecked(index, sample) }; + seedable_function!( + /// Generates a [`PolyOverZq`] instance with maximum degree `max_degree` + /// and coefficients chosen uniform at random in `[0, modulus)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `max_degree`: specifies the length of the polynomial, + /// i.e. the number of coefficients + /// - `modulus`: specifies the modulus of the coefficients and thus, + /// the interval size over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverZq`] instance of length `max_degree` with coefficients + /// chosen uniform at random in `[0, modulus)` or a [`MathError`] + /// if the `max_degree` was smaller than `0` or the provided `modulus` was chosen too small. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::PolyOverZq; + /// + #[unseeded] + /// let sample = PolyOverZq::sample_uniform(3, 17).unwrap(); + #[seeded] + /// let sample = PolyOverZq::sample_uniform_seeded(3, 17, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if the given `modulus` isn't larger than `1`, i.e. the interval size is at most `1`. + /// - Returns a [`MathError`] of type [`OutOfBounds`](MathError::OutOfBounds) if + /// the `max_degree` is negative or it does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_uniform( + max_degree: impl TryInto + Display + Copy, + modulus: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree)?; + let interval_size = modulus.into(); + let modulus = Modulus::from(&interval_size); + let mut poly_zq = PolyOverZq::from(&modulus); + + let mut uis = UniformIntegerSampler::init(&interval_size, seed)?; + + for index in 0..=max_degree { + let sample = uis.sample(); + unsafe { poly_zq.set_coeff_unchecked(index, sample) }; + } + Ok(poly_zq) } - Ok(poly_zq) - } + ); } #[cfg(test)] @@ -167,3 +187,113 @@ mod test_sample_uniform { let _ = PolyOverZq::sample_uniform(1, modulus).unwrap(); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{Modulus, PolyOverZq}, + traits::GetCoefficient, + }; + + /// Checks whether the boundaries of the interval are kept for small moduli. + #[test] + fn boundaries_kept_small() { + let modulus = Z::from(17); + + let poly_zq = PolyOverZq::sample_uniform_seeded(32, &modulus, test_seed()).unwrap(); + + for i in 0..32 { + let sample: Z = poly_zq.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + #[test] + fn boundaries_kept_large() { + let modulus = Z::from(i64::MAX); + + let poly_zq = PolyOverZq::sample_uniform_seeded(256, &modulus, test_seed()).unwrap(); + + for i in 0..256 { + let sample: Z = poly_zq.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_uniform_seeded(degree, u64::MAX, test_seed()).unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "Could fail with probability 1/{}.", + u64::MAX + ); + } + } + + /// Checks whether providing an invalid interval/ modulus results in an error. + #[test] + #[should_panic] + fn invalid_modulus_negative() { + let _ = PolyOverZq::sample_uniform_seeded(1, i64::MIN, test_seed()); + } + + /// Checks whether providing an invalid interval/ modulus results in an error. + #[test] + #[should_panic] + fn invalid_modulus_one() { + let _ = PolyOverZq::sample_uniform_seeded(1, 1, test_seed()); + } + + /// Checks whether providing a length smaller than `1` results in an error. + #[test] + fn invalid_max_degree() { + let modulus = Z::from(15); + + let res_0 = PolyOverZq::sample_uniform_seeded(-1, &modulus, test_seed()); + let res_1 = PolyOverZq::sample_uniform_seeded(i64::MIN, &modulus, test_seed()); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(10); + let z = Z::from(10); + + let _ = PolyOverZq::sample_uniform_seeded(1u64, 10u16, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1i64, 10u32, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1u8, 10u64, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1u16, 10i8, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1u32, 10i16, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1i32, 10i32, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1i16, 10i64, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1i8, &z, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1, z, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1, &modulus, test_seed()).unwrap(); + let _ = PolyOverZq::sample_uniform_seeded(1, modulus, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::PolyOverZq; + let sample_0 = PolyOverZq::sample_uniform_seeded(3, 17, [42; 32]).unwrap(); + let sample_1 = PolyOverZq::sample_uniform_seeded(3, 17, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs b/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs index 15d46e9a..e7b3a872 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs @@ -13,105 +13,135 @@ use crate::{ error::MathError, integer::{PolyOverZ, Z}, integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}, + macros::seeded::seedable_function, rational::Q, }; impl PolynomialRingZq { - /// Generates a [`PolynomialRingZq`] instance of maximum degree `modulus.get_degree() - 1` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p`. - /// - /// Parameters: - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` - /// with coefficients chosen according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - /// - /// let sample = PolynomialRingZq::sample_binomial(&modulus, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. - pub fn sample_binomial( - modulus: &ModulusPolynomialRingZq, - n: impl Into, - p: impl Into, - ) -> Result { - Self::sample_binomial_with_offset(modulus, 0, n, p) - } + seedable_function!( + /// Generates a [`PolynomialRingZq`] instance of maximum degree `modulus.get_degree() - 1` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p`. + /// + /// Parameters: + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` + /// with coefficients chosen according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = PolynomialRingZq::sample_binomial(&modulus, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. + pub(crate) fn sample_binomial( + modulus: &ModulusPolynomialRingZq, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Self::sample_binomial_with_offset_optionally_seeded(modulus, 0, n, p, seed) + } + ); - /// Generates a [`PolynomialRingZq`] instance of maximum degree `modulus.get_degree() - 1` and - /// coefficients chosen according to the binomial distribution - /// parameterized by `n` and `p` with given `offset`. - /// - /// Parameters: - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `offset`: specifies an offset applied to each sample - /// collected from the binomial distribution - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` - /// with coefficients chosen according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - /// - /// let sample = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. - pub fn sample_binomial_with_offset( - modulus: &ModulusPolynomialRingZq, - offset: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - assert!( - modulus.get_degree() > 0, - "ModulusPolynomial of degree 0 is insufficient to sample over." - ); - - let poly_z = - PolyOverZ::sample_binomial_with_offset(modulus.get_degree() - 1, offset, n, p)?; - - Ok(PolynomialRingZq { - poly: poly_z, - modulus: modulus.clone(), - }) - } + seedable_function!( + /// Generates a [`PolynomialRingZq`] instance of maximum degree `modulus.get_degree() - 1` and + /// coefficients chosen according to the binomial distribution + /// parameterized by `n` and `p` with given `offset`. + /// + /// Parameters: + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + /// - `offset`: specifies an offset applied to each sample + /// collected from the binomial distribution + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` + /// with coefficients chosen according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, `n` does not fit into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5).unwrap(); + #[seeded] + /// let sample = PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. + pub(crate) fn sample_binomial_with_offset( + modulus: &ModulusPolynomialRingZq, + offset: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + assert!( + modulus.get_degree() > 0, + "ModulusPolynomial of degree 0 is insufficient to sample over." + ); + + let poly_z = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + modulus.get_degree() - 1, + offset, + n, + p, + seed, + )?; + + Ok(PolynomialRingZq { + poly: poly_z, + modulus: modulus.clone(), + }) + } + ); } #[cfg(test)] @@ -168,6 +198,77 @@ mod test_sample_binomial { } } +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{PolynomialRingZq, Z}; + use crate::utils::sample::test_seed; + use crate::{ + integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq}, + traits::{GetCoefficient, SetCoefficient}, + }; + use std::str::FromStr; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let poly = + PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, test_seed()).unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_binomial_seeded(&modulus, 256, 0.99999, test_seed()) + .unwrap(); + + assert_eq!( + modulus.get_degree(), + res.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample_0 = + PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = + PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_binomial_with_offset { use super::{PolynomialRingZq, Z}; @@ -217,3 +318,85 @@ mod test_sample_binomial_with_offset { let _ = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5); } } + +#[cfg(test)] +mod test_sample_binomial_with_offset_seeded { + use super::{PolynomialRingZq, Z}; + use crate::utils::sample::test_seed; + use crate::{ + integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq}, + traits::{GetCoefficient, SetCoefficient}, + }; + use std::str::FromStr; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether the boundaries of the interval are kept. + #[test] + fn boundaries_kept() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let poly = PolynomialRingZq::sample_binomial_with_offset_seeded( + &modulus, + -1, + 2, + 0.5, + test_seed(), + ) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_binomial_with_offset_seeded( + &modulus, + 1, + 2, + 0.5, + test_seed(), + ) + .unwrap(); + + assert_eq!(modulus.get_degree(), res.get_degree() + 1); + } + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = + PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample_0 = + PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, [42; 32]) + .unwrap(); + let sample_1 = + PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, [42; 32]) + .unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs index 6749ee34..2124fd0d 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs @@ -13,60 +13,78 @@ use crate::{ error::MathError, integer::PolyOverZ, integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}, + macros::seeded::seedable_function, rational::Q, }; impl PolynomialRingZq { - /// Initializes a new [`PolynomialRingZq`] with maximum degree `modulus.get_degree() - 1` - /// and with each entry sampled independently according to the - /// discrete Gaussian distribution. - /// - /// Parameters: - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - `center`: specifies the positions of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` - /// with coefficients chosen independently according the discrete Gaussian distribution or - /// a [`MathError`] if `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - /// - /// let sample = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. - pub fn sample_discrete_gauss( - modulus: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let modulus = modulus.into(); - assert!( - modulus.get_degree() > 0, - "ModulusPolynomial of degree 0 is insufficient to sample over." - ); - - let poly_z = PolyOverZ::sample_discrete_gauss(modulus.get_degree() - 1, center, s)?; - let mut poly_ringzq = PolynomialRingZq { - poly: poly_z, - modulus, - }; - poly_ringzq.reduce(); - - Ok(poly_ringzq) - } + seedable_function!( + /// Initializes a new [`PolynomialRingZq`] with maximum degree `modulus.get_degree() - 1` + /// and with each entry sampled independently according to the + /// discrete Gaussian distribution. + /// + /// Parameters: + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + /// - `center`: specifies the positions of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` + /// with coefficients chosen independently according the discrete Gaussian distribution or + /// a [`MathError`] if `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); + #[seeded] + /// let sample = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. + pub(crate) fn sample_discrete_gauss( + modulus: impl Into, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus = modulus.into(); + assert!( + modulus.get_degree() > 0, + "ModulusPolynomial of degree 0 is insufficient to sample over." + ); + + let poly_z = PolyOverZ::sample_discrete_gauss_optionally_seeded( + modulus.get_degree() - 1, + center, + s, + seed, + )?; + let mut poly_ringzq = PolynomialRingZq { + poly: poly_z, + modulus, + }; + poly_ringzq.reduce(); + + Ok(poly_ringzq) + } + ); } #[cfg(test)] @@ -161,3 +179,116 @@ mod test_sample_discrete_gauss { let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); } } + +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, + rational::Q, + traits::{GetCoefficient, SetCoefficient}, + }; + use std::str::FromStr; + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let center = Q::ZERO; + let s = Q::ONE; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1u8, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1u16, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1u32, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1u64, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i8, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i16, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i32, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1i64, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, center, s, test_seed()); + let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1f64, test_seed()); + } + + /// Checks whether the boundaries of the interval are kept for small moduli. + #[test] + fn boundaries_kept_small() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let poly = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 15, 1, test_seed()) + .unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + #[test] + fn boundaries_kept_large() { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let poly = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 1, 1, test_seed()) + .unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = + PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, i64::MAX, 1, test_seed()) + .unwrap(); + + assert_eq!( + res.get_degree() + 1, + modulus.get_degree(), + "Could fail with negligible probability." + ); + } + } + + /// Checks whether 0 degree modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = + PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample_0 = + PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, [42; 32]).unwrap(); + let sample_1 = + PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs index c9d01afc..c1780863 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs @@ -11,51 +11,78 @@ use crate::{ integer::PolyOverZ, integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}, + macros::seeded::seedable_function, }; impl PolynomialRingZq { - /// Generates a [`PolynomialRingZq`] instance with maximum degree `modulus.get_degree() - 1` - /// and coefficients chosen uniform at random in `[0, modulus.get_q())`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the - /// ring of polynomials modulo `modulus.get_q()` is defined - /// - /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` - /// with coefficients chosen uniform at random in `[0, modulus.get_q())`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; - /// use std::str::FromStr; - /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - /// - /// let sample = PolynomialRingZq::sample_uniform(&modulus); - /// ``` - /// - /// # Panics ... - /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. - pub fn sample_uniform(modulus: impl Into) -> Self { - let modulus = modulus.into(); - assert!( - modulus.get_degree() > 0, - "ModulusPolynomial of degree 0 is insufficient to sample over." - ); - - let poly_z = - PolyOverZ::sample_uniform(modulus.get_degree() - 1, 0, modulus.get_q()).unwrap(); - - // we do not have to reduce here, as all entries are already in the correct range - // hence directly setting is more efficient - PolynomialRingZq { - poly: poly_z, - modulus, + seedable_function!( + /// Generates a [`PolynomialRingZq`] instance with maximum degree `modulus.get_degree() - 1` + /// and coefficients chosen uniform at random in `[0, modulus.get_q())`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `modulus`: specifies the [`ModulusPolynomialRingZq`] over which the + /// ring of polynomials modulo `modulus.get_q()` is defined + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolynomialRingZq`] instance of length `modulus.get_degree() - 1` + /// with coefficients chosen uniform at random in `[0, modulus.get_q())`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::{PolynomialRingZq, ModulusPolynomialRingZq}; + /// use std::str::FromStr; + /// let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + /// + #[unseeded] + /// let sample = PolynomialRingZq::sample_uniform(&modulus); + #[seeded] + /// let sample = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + /// ``` + /// + /// # Panics ... + /// - if the provided [`ModulusPolynomialRingZq`] has degree `0` or smaller. + pub(crate) fn sample_uniform( + modulus: impl Into, + seed: Option<[u8; 32]>, + ) -> Self { + let modulus = modulus.into(); + assert!( + modulus.get_degree() > 0, + "ModulusPolynomial of degree 0 is insufficient to sample over." + ); + + let poly_z = PolyOverZ::sample_uniform_optionally_seeded( + modulus.get_degree() - 1, + 0, + modulus.get_q(), + seed, + ) + .unwrap(); + + // we do not have to reduce here, as all entries are already in the correct range + // hence directly setting is more efficient + PolynomialRingZq { + poly: poly_z, + modulus, + } } - } + ); } #[cfg(test)] @@ -129,3 +156,88 @@ mod test_sample_uniform { let _ = PolynomialRingZq::sample_uniform(&modulus); } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, + traits::{GetCoefficient, SetCoefficient}, + }; + use std::str::FromStr; + + /// Checks whether the boundaries of the interval are kept for small moduli. + #[test] + fn boundaries_kept_small() { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let poly = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + #[test] + fn boundaries_kept_large() { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let poly = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } + } + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + + assert_eq!( + res.get_degree() + 1, + modulus.get_degree(), + "Could fail with probability 1/{}.", + u64::MAX + ); + } + } + + /// Checks whether 0 modulus polynomial is insufficient. + #[test] + #[should_panic] + fn invalid_modulus() { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample_0 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + let sample_1 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/z_q/sample/binomial.rs b/src/integer_mod_q/z_q/sample/binomial.rs index 01a3cf29..b6981d4b 100644 --- a/src/integer_mod_q/z_q/sample/binomial.rs +++ b/src/integer_mod_q/z_q/sample/binomial.rs @@ -13,51 +13,64 @@ use crate::{ error::MathError, integer::Z, integer_mod_q::{Modulus, Zq}, + macros::seeded::seedable_function, rational::Q, utils::sample::binomial::BinomialSampler, }; impl Zq { - /// Chooses a [`Zq`] instance according to the binomial distribution - /// parameterized by `n` and `p`. - /// - /// Parameters: - /// - `modulus`: specifies the [`Modulus`] of the new [`Zq`] instance - /// - `n`: specifies the number of trials - /// - `p`: specifies the probability of success - /// - /// Returns a fresh [`Zq`] instance with a value sampled - /// according to the binomial distribution or a [`MathError`] - /// if `n < 0`, `p ∉ (0,1)`, or `n` does not fit into an [`i64`]. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::Zq; - /// - /// let sample = Zq::sample_binomial(7, 2, 0.5).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `n < 0`. - /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) - /// if `p ∉ (0,1)`. - /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) - /// if `n` does not fit into an [`i64`]. - /// - /// # Panics ... - /// - if `modulus` is smaller than `2`. - pub fn sample_binomial( - modulus: impl Into, - n: impl Into, - p: impl Into, - ) -> Result { - let modulus: Modulus = modulus.into(); - let mut bin_sampler = BinomialSampler::init(n, p, None)?; + seedable_function!( + /// Chooses a [`Zq`] instance according to the binomial distribution + /// parameterized by `n` and `p`. + /// + /// Parameters: + /// - `modulus`: specifies the [`Modulus`] of the new [`Zq`] instance + /// - `n`: specifies the number of trials + /// - `p`: specifies the probability of success + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`Zq`] instance with a value sampled + /// according to the binomial distribution or a [`MathError`] + /// if `n < 0`, `p ∉ (0,1)`, or `n` does not fit into an [`i64`]. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::Zq; + /// + #[unseeded] + /// let sample = Zq::sample_binomial(7, 2, 0.5).unwrap(); + #[seeded] + /// let sample = Zq::sample_binomial_seeded(7, 2, 0.5, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `n < 0`. + /// - Returns a [`MathError`] of type [`InvalidInterval`](MathError::InvalidInterval) + /// if `p ∉ (0,1)`. + /// - Returns a [`MathError`] of type [`ConversionError`](MathError::ConversionError) + /// if `n` does not fit into an [`i64`]. + /// + /// # Panics ... + /// - if `modulus` is smaller than `2`. + pub(crate) fn sample_binomial( + modulus: impl Into, + n: impl Into, + p: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus: Modulus = modulus.into(); + let mut bin_sampler = BinomialSampler::init(n, p, seed)?; - let sample = bin_sampler.sample(); - Ok(Zq::from((sample, modulus))) - } + let sample = bin_sampler.sample(); + Ok(Zq::from((sample, modulus))) + } + ); } #[cfg(test)] @@ -87,3 +100,42 @@ mod test_sample_binomial { let _ = Zq::sample_binomial(7, 1, Q::from((1, 2))); } } + +#[cfg(test)] +mod test_sample_binomial_seeded { + use super::{Q, Z, Zq}; + use crate::utils::sample::test_seed; + + // As all major tests regarding an appropriate binomial distribution, + // whether the correct interval is kept, and if the errors are thrown correctly, + // are performed in the `utils` module, we omit these tests here. + + /// Checks whether `sample_binomial` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... + /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let _ = Zq::sample_binomial_seeded(7u8, 1u16, 7u8, test_seed()); + let _ = Zq::sample_binomial_seeded(7u16, 1u32, 7u16, test_seed()); + let _ = Zq::sample_binomial_seeded(7u32, 1u64, 7u32, test_seed()); + let _ = Zq::sample_binomial_seeded(7u64, 1i8, 7u64, test_seed()); + let _ = Zq::sample_binomial_seeded(7i8, 1i16, 7i8, test_seed()); + let _ = Zq::sample_binomial_seeded(7i16, 1i32, 7i16, test_seed()); + let _ = Zq::sample_binomial_seeded(7i32, 1i64, 7i32, test_seed()); + let _ = Zq::sample_binomial_seeded(7i64, Z::ONE, 7i64, test_seed()); + let _ = Zq::sample_binomial_seeded(7, 1u8, 0.5f32, test_seed()); + let _ = Zq::sample_binomial_seeded(7, 1u8, 0.5f64, test_seed()); + let _ = Zq::sample_binomial_seeded(7, 1, Q::from((1, 2)), test_seed()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::Zq; + let sample_0 = Zq::sample_binomial_seeded(7, 2, 0.5, [42; 32]).unwrap(); + let sample_1 = Zq::sample_binomial_seeded(7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/z_q/sample/discrete_gauss.rs b/src/integer_mod_q/z_q/sample/discrete_gauss.rs index 0e114ddb..506f5de4 100644 --- a/src/integer_mod_q/z_q/sample/discrete_gauss.rs +++ b/src/integer_mod_q/z_q/sample/discrete_gauss.rs @@ -11,66 +11,79 @@ use crate::{ error::MathError, integer_mod_q::{Modulus, Zq}, + macros::seeded::seedable_function, rational::Q, utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting, TAILCUT}, }; impl Zq { - /// Chooses a [`Zq`] instance chosen according to the discrete Gaussian distribution - /// in `[center - ⌈6 * s⌉ , center + ⌊ 6 * s⌋ ]`. - /// - /// This function samples discrete Gaussians according to the definition of - /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). - /// - /// Parameters: - /// - `modulus`: specifies the modulus of the new [`Zq`] element - /// - `center`: specifies the position of the center with peak probability - /// - `s`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = s` - /// - /// Returns new [`Zq`] sample chosen according to the specified discrete Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately, i.e. `s < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::Zq; - /// - /// let sample = Zq::sample_discrete_gauss(17, 0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `s < 0`. - /// - /// # Panics ... - /// - if `modulus` is smaller than `2`. - /// - /// This function implements SampleZ according to: - /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). - /// Trapdoors for hard lattices and new cryptographic constructions. - /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. - /// - pub fn sample_discrete_gauss( - modulus: impl Into, - center: impl Into, - s: impl Into, - ) -> Result { - let modulus: Modulus = modulus.into(); - let center: Q = center.into(); - let s: Q = s.into(); - - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &s, - unsafe { TAILCUT }, - LookupTableSetting::NoLookup, - None, - )?; - - let sample = dgis.sample_z(); - Ok(Zq::from((&sample, &modulus))) - } + seedable_function!( + /// Chooses a [`Zq`] instance chosen according to the discrete Gaussian distribution + /// in `[center - ⌈6 * s⌉ , center + ⌊ 6 * s⌋ ]`. + /// + /// This function samples discrete Gaussians according to the definition of + /// SampleZ in [GPV08](https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=d9f54077d568784c786f7b1d030b00493eb3ae35). + /// + /// Parameters: + /// - `modulus`: specifies the modulus of the new [`Zq`] element + /// - `center`: specifies the position of the center with peak probability + /// - `s`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = s` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns new [`Zq`] sample chosen according to the specified discrete Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately, i.e. `s < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::Zq; + /// + #[unseeded] + /// let sample = Zq::sample_discrete_gauss(17, 0, 1).unwrap(); + #[seeded] + /// let sample = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `s < 0`. + /// + /// # Panics ... + /// - if `modulus` is smaller than `2`. + /// + /// This function implements SampleZ according to: + /// - \[1\] Gentry, Craig and Peikert, Chris and Vaikuntanathan, Vinod (2008). + /// Trapdoors for hard lattices and new cryptographic constructions. + /// In: Proceedings of the fortieth annual ACM symposium on Theory of computing. + /// + pub(crate) fn sample_discrete_gauss( + modulus: impl Into, + center: impl Into, + s: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let modulus: Modulus = modulus.into(); + let center: Q = center.into(); + let s: Q = s.into(); + + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &s, + unsafe { TAILCUT }, + LookupTableSetting::NoLookup, + seed, + )?; + + let sample = dgis.sample_z(); + Ok(Zq::from((&sample, &modulus))) + } + ); } #[cfg(test)] @@ -145,3 +158,87 @@ mod test_sample_discrete_gauss { } } } + +#[cfg(test)] +mod test_sample_discrete_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{integer::Z, integer_mod_q::Zq, rational::Q}; + + // Appropriate interval and invalid inputs were tested in + // utils::sample::discrete_gauss::test_sample_z and are thus omitted here. + + /// Checks whether `sample_discrete_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let z = Z::from(2); + let q = Q::from(2); + + let _ = Zq::sample_discrete_gauss_seeded(17u64, 7u8, 1u16, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17u16, 7u16, 1u8, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17u8, 7u32, 1u32, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17u32, 7u64, 1u64, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17i16, 7i8, 1i64, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17i8, 7i16, 1i32, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17i64, 7i32, 1i16, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17i32, 7i64, 1i8, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, q.clone(), 1i64, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(z.clone(), 0i8, z.clone(), test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, z.clone(), q.clone(), test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, 1f32, 1f64, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, 1f64, 1f32, test_seed()); + + // With references + let _ = Zq::sample_discrete_gauss_seeded(17i64, 7i32, 1i16, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17i32, 7i64, 1i8, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, &q, 1i64, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(&z, 0i8, &z, test_seed()); + let _ = Zq::sample_discrete_gauss_seeded(17, &z, &q, test_seed()); + } + + /// Roughly checks the collected samples are distributed + /// according to the discrete Gaussian distribution. + /// + /// This test could possibly fail for a truly uniform distribution + /// with probability smaller than 1/1000. + #[test] + fn distribution() { + let mut counts = [0; 20]; + // count sampled instances + for _ in 0..200 { + let sample = Zq::sample_discrete_gauss_seeded(20, 0, 2, test_seed()).unwrap(); + let sample_int = i64::try_from(&sample.get_representative_least_nonnegative_residue()) + .unwrap() as usize; + counts[sample_int] += 1; + } + + let expl_text = String::from("This test can fail with probability close to 0. + It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. + If this happens, rerun the tests several times and check whether this issue comes up again."); + + // Check that the sampled occurrences roughly look + // like a discrete Gaussian distribution + assert!(counts[0] > 70, "{expl_text}"); + assert!(counts[0] < 130, "{expl_text}"); + assert!(counts[19] > 20, "{expl_text}"); + assert!(counts[19] < 70, "{expl_text}"); + assert!(counts[1] > 20, "{expl_text}"); + assert!(counts[1] < 70, "{expl_text}"); + assert!(counts[18] < 20, "{expl_text}"); + assert!(counts[2] < 20, "{expl_text}"); + for count in counts.iter().take(18).skip(3) { + assert!(count < &10, "{expl_text}"); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::Zq; + let sample_0 = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap(); + let sample_1 = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/integer_mod_q/z_q/sample/uniform.rs b/src/integer_mod_q/z_q/sample/uniform.rs index dd25dc5a..16bd9e57 100644 --- a/src/integer_mod_q/z_q/sample/uniform.rs +++ b/src/integer_mod_q/z_q/sample/uniform.rs @@ -8,38 +8,59 @@ //! This module contains sampling algorithms for uniform random sampling. -use crate::{integer::Z, integer_mod_q::Zq, utils::sample::uniform::UniformIntegerSampler}; +use crate::{ + integer::Z, integer_mod_q::Zq, macros::seeded::seedable_function, + utils::sample::uniform::UniformIntegerSampler, +}; impl Zq { - /// Chooses a [`Zq`] instance uniformly at random in `[0, modulus)`. - /// - /// The internally used uniform at random chosen bytes are generated - /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and - /// is considered cryptographically secure. - /// - /// Parameters: - /// - `modulus`: specifies the [`Modulus`](crate::integer_mod_q::Modulus) - /// of the new [`Zq`] instance and thus the size of the interval over which is sampled - /// - /// Returns a new [`Zq`] instance with a value chosen - /// uniformly at random in `[0, modulus)`. - /// - /// # Examples - /// ``` - /// use qfall_math::integer_mod_q::Zq; - /// - /// let sample = Zq::sample_uniform(17); - /// ``` - /// - /// # Panics - /// - if the given modulus is smaller than or equal to `1`. - pub fn sample_uniform(modulus: impl Into) -> Self { - let modulus: Z = modulus.into(); - let mut uis = UniformIntegerSampler::init(&modulus, None).unwrap(); - - let random = uis.sample(); - Zq::from((random, modulus)) - } + seedable_function!( + /// Chooses a [`Zq`] instance uniformly at random in `[0, modulus)`. + /// + /// The internally used uniform at random chosen bytes are generated + #[unseeded] + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and is + #[seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed`, which uses ChaCha12 and is + #[optionally_seeded] + /// by a [`StdRng`](rand::rngs::StdRng) seeded with `seed` or by [`ThreadRng`](rand::rngs::ThreadRng) + #[optionally_seeded] + /// if `seed` is `None`. Both use ChaCha12 and are + /// considered cryptographically secure. + /// + /// Parameters: + /// - `modulus`: specifies the [`Modulus`](crate::integer_mod_q::Modulus) + /// of the new [`Zq`] instance and thus the size of the interval over which is sampled + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a new [`Zq`] instance with a value chosen + /// uniformly at random in `[0, modulus)`. + /// + /// # Examples + /// ``` + /// use qfall_math::integer_mod_q::Zq; + /// + #[unseeded] + /// let sample = Zq::sample_uniform(17); + #[seeded] + /// let sample = Zq::sample_uniform_seeded(17, [42; 32]); + /// ``` + /// + /// # Panics + /// - if the given modulus is smaller than or equal to `1`. + pub(crate) fn sample_uniform(modulus: impl Into, seed: Option<[u8; 32]>) -> Self { + let modulus: Z = modulus.into(); + let mut uis = UniformIntegerSampler::init(&modulus, seed).unwrap(); + + let random = uis.sample(); + Zq::from((random, modulus)) + } + ); } #[cfg(test)] @@ -124,3 +145,97 @@ mod test_sample_uniform { } } } + +#[cfg(test)] +mod test_sample_uniform_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + integer_mod_q::{Modulus, Zq}, + }; + + /// Checks whether the boundaries of the interval are kept for small moduli. + /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. + #[test] + fn boundaries_kept_small() { + let modulus = Z::from(17); + for _ in 0..32 { + let sample = Zq::sample_uniform_seeded(&modulus, test_seed()); + assert!(Z::ZERO <= sample.value); + assert!(sample.value < modulus); + } + } + + /// Checks whether the boundaries of the interval are kept for large moduli. + /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. + #[test] + fn boundaries_kept_large() { + let modulus = Z::from(u64::MAX); + for _ in 0..256 { + let sample = Zq::sample_uniform_seeded(&modulus, test_seed()); + assert!(Z::ZERO <= sample.value); + assert!(sample.value < modulus); + } + } + + /// Checks whether providing an invalid interval results in an error. + #[test] + #[should_panic] + fn invalid_interval() { + let modulus = Z::ZERO; + + let _ = Zq::sample_uniform_seeded(&modulus, test_seed()); + } + + /// Checks whether `sample_uniform` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + #[test] + fn availability() { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Zq::sample_uniform_seeded(7u8, test_seed()); + let _ = Zq::sample_uniform_seeded(7u16, test_seed()); + let _ = Zq::sample_uniform_seeded(7u32, test_seed()); + let _ = Zq::sample_uniform_seeded(7u64, test_seed()); + let _ = Zq::sample_uniform_seeded(7i8, test_seed()); + let _ = Zq::sample_uniform_seeded(7i16, test_seed()); + let _ = Zq::sample_uniform_seeded(7i32, test_seed()); + let _ = Zq::sample_uniform_seeded(7i64, test_seed()); + let _ = Zq::sample_uniform_seeded(&modulus, test_seed()); + let _ = Zq::sample_uniform_seeded(&z, test_seed()); + } + + /// Roughly checks the uniformity of the distribution. + /// This test could possibly fail for a truly uniform distribution + /// with probability smaller than 1/1000. + #[test] + fn uniformity() { + let modulus = Z::from(5); + let mut counts = [0; 5]; + // count sampled instances + for _ in 0..1000 { + let sample_z = Zq::sample_uniform_seeded(&modulus, test_seed()); + let sample_int = i64::try_from(&sample_z.value).unwrap() as usize; + counts[sample_int] += 1; + } + + // Check that every sampled integer was sampled roughly the same time + // this could possibly fail for true uniform randomness with probability + for count in counts { + assert!(count > 150, "This test can fail with probability close to 0. + It fails if the sampled occurrences do not look like a typical uniform random distribution. + If this happens, rerun the tests several times and check whether this issue comes up again."); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::integer_mod_q::Zq; + let sample_0 = Zq::sample_uniform_seeded(17, [42; 32]); + let sample_1 = Zq::sample_uniform_seeded(17, [42; 32]); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/macros.rs b/src/macros.rs index b5e102cf..786ca671 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -13,5 +13,6 @@ pub(crate) mod arithmetics; pub(crate) mod compare_base; pub(crate) mod for_others; pub(crate) mod from; +pub(crate) mod seeded; pub(crate) mod serialize; pub(crate) mod unsafe_passthrough; diff --git a/src/macros/seeded.rs b/src/macros/seeded.rs new file mode 100644 index 00000000..fda5a607 --- /dev/null +++ b/src/macros/seeded.rs @@ -0,0 +1,147 @@ +// Copyright 2026 Jan Niklas Siemer +// +// This file is part of qFALL-math. +// +// qFALL-math is free software: you can redistribute it and/or modify it under +// the terms of the Mozilla Public License Version 2.0 as published by the +// Mozilla Foundation. See . + +//! This module implements macros which are used to provide an unseeded and a +//! seeded variant of a function from one implementation and one doc-comment. + +/// Takes a function `name` whose last parameter is an optional seed `seed: Option<[u8; 32]>` +/// and implements +/// - the function itself as `name_optionally_seeded` with the provided visibility, +/// - the public function `name`, which has the same parameters except for `seed` +/// and forwards `None` as seed, and +/// - the public function `name_seeded`, which takes `seed: [u8; 32]` as its last parameter +/// and forwards `Some(seed)`. +/// +/// All three functions share the provided doc-comment. Every doc line (and any other attribute) +/// is added to all three functions, except for the doc lines preceded by +/// - `#[unseeded]`, which are only added to `name`, +/// - `#[seeded]`, which are only added to `name_seeded`, and +/// - `#[optionally_seeded]`, which are only added to `name_optionally_seeded`. +/// +/// Calls in examples should be marked with `#[unseeded]` or `#[seeded]`, +/// as doc-tests are also run for non-public functions, but can only access public ones. +/// +/// As the input is valid Rust syntax, `rustfmt` formats the provided function as usual +/// if the macro is called with parentheses, i.e. `seedable_function!( ... );`. +/// +/// Input parameters: +/// - the shared doc-comment possibly containing marked doc lines +/// - the function `name` with `seed: Option<[u8; 32]>` as its last parameter +/// +/// The function may also be a method taking `&self` as its first parameter. +/// +/// Returns the code of all three functions, i.e. it needs to be called inside an `impl` block. +/// +/// # Examples +/// ```compile_fail +/// impl MatZq { +/// seedable_function!( +/// /// Samples a matrix. +/// /// +/// /// Parameters: +/// /// - `num_rows`: specifies the number of rows +/// #[seeded] +/// /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling +/// #[optionally_seeded] +/// /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling +/// pub(crate) fn sample(num_rows: i64, seed: Option<[u8; 32]>) -> MatZq { +/// ... +/// } +/// ); +/// } +/// ``` +macro_rules! seedable_function { + // A doc line preceded by `#[unseeded]` is only added to the unseeded function. + (@attrs [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] #[unseeded] #[$attr:meta] $($rest:tt)*) => { + $crate::macros::seeded::seedable_function!( + @attrs [$($unseeded)* #[$attr]] [$($seeded)*] [$($optional)*] $($rest)* + ); + }; + // A doc line preceded by `#[seeded]` is only added to the seeded function. + (@attrs [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] #[seeded] #[$attr:meta] $($rest:tt)*) => { + $crate::macros::seeded::seedable_function!( + @attrs [$($unseeded)*] [$($seeded)* #[$attr]] [$($optional)*] $($rest)* + ); + }; + // A doc line preceded by `#[optionally_seeded]` is only added to the optionally seeded function. + (@attrs [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] #[optionally_seeded] #[$attr:meta] $($rest:tt)*) => { + $crate::macros::seeded::seedable_function!( + @attrs [$($unseeded)*] [$($seeded)*] [$($optional)* #[$attr]] $($rest)* + ); + }; + // Any other doc line or attribute is added to all functions. + (@attrs [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] #[$attr:meta] $($rest:tt)*) => { + $crate::macros::seeded::seedable_function!( + @attrs [$($unseeded)* #[$attr]] [$($seeded)* #[$attr]] [$($optional)* #[$attr]] $($rest)* + ); + }; + // Once all attributes are sorted, the parameters of a method taking `&self` are processed. + ( + @attrs $unseeded:tt $seeded:tt $optional:tt + $vis:vis fn $name:ident (&$receiver:ident, $($params:tt)*) -> $ret:ty $body:block + ) => { + $crate::macros::seeded::seedable_function!( + @params $unseeded $seeded $optional ($vis) $name $ret $body + [&$receiver,] [$receiver.] [] $($params)* + ); + }; + // Once all attributes are sorted, the parameters of an associated function are processed. + ( + @attrs $unseeded:tt $seeded:tt $optional:tt + $vis:vis fn $name:ident ($($params:tt)*) -> $ret:ty $body:block + ) => { + $crate::macros::seeded::seedable_function!( + @params $unseeded $seeded $optional ($vis) $name $ret $body + [] [Self::] [] $($params)* + ); + }; + // The last parameter is the optional seed, i.e. all functions are generated. + ( + @params [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] + ($vis:vis) $name:ident $ret:ty $body:block + [$($receiver:tt)*] [$($call:tt)*] + [$($arg:ident : $arg_type:ty,)*] + $seed:ident : Option<[u8; 32]> $(,)? + ) => { + paste::paste! { + $($optional)* + $vis fn [<$name _optionally_seeded>]( + $($receiver)* + $($arg: $arg_type,)* + $seed: Option<[u8; 32]>, + ) -> $ret $body + + $($unseeded)* + pub fn $name($($receiver)* $($arg: $arg_type),*) -> $ret { + $($call)*[<$name _optionally_seeded>]($($arg,)* None) + } + + $($seeded)* + pub fn [<$name _seeded>]($($receiver)* $($arg: $arg_type,)* $seed: [u8; 32]) -> $ret { + $($call)*[<$name _optionally_seeded>]($($arg,)* Some($seed)) + } + } + }; + // Any other parameter is collected one at a time, as `macro_rules!` can not + // match all but the last parameter in one go. `$vis` is wrapped in parentheses, + // as it may be empty. + ( + @params $unseeded:tt $seeded:tt $optional:tt $vis:tt $name:tt $ret:tt $body:tt + $receiver:tt $call:tt [$($collected:tt)*] $arg:ident : $arg_type:ty, $($rest:tt)* + ) => { + $crate::macros::seeded::seedable_function!( + @params $unseeded $seeded $optional $vis $name $ret $body + $receiver $call [$($collected)* $arg: $arg_type,] $($rest)* + ); + }; + ($($input:tt)*) => { + $crate::macros::seeded::seedable_function!(@attrs [] [] [] $($input)*); + }; +} + +pub(crate) use seedable_function; diff --git a/src/rational/mat_q/rounding.rs b/src/rational/mat_q/rounding.rs index efe0e686..8484cc14 100644 --- a/src/rational/mat_q/rounding.rs +++ b/src/rational/mat_q/rounding.rs @@ -12,8 +12,10 @@ use super::MatQ; use crate::{ error::MathError, integer::MatZ, + macros::seeded::seedable_function, rational::Q, traits::{MatrixDimensions, MatrixGetEntry, MatrixSetEntry}, + utils::sample::uniform::SamplerRng, }; impl MatQ { @@ -168,45 +170,63 @@ impl MatQ { out } - /// Performs the randomized rounding algorithm entrywise - /// by sampling from a discrete Gaussian over the integers shifted - /// by `self` with gaussian parameter `r`. - /// - /// Parameters: - /// - `r`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = r` - /// - /// Returns the rounded matrix as a [`MatZ`] or an error if `r < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::rational::MatQ; - /// use std::str::FromStr; - /// - /// let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - /// let rounded = value.randomized_rounding(3).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `r < 0`. - /// - /// This function implements randomized rounding according to: - /// - \[1\] Peikert, C. (2010, August). - /// An efficient and parallel Gaussian sampler for lattices. - /// In: Annual Cryptology Conference (pp. 80-97). - /// - pub fn randomized_rounding(&self, r: impl Into) -> Result { - let mut out = MatZ::new(self.get_num_rows(), self.get_num_columns()); - let r = r.into(); - for i in 0..out.get_num_rows() { - for j in 0..out.get_num_columns() { - let entry = unsafe { self.get_entry_unchecked(i, j) }.randomized_rounding(&r)?; - unsafe { out.set_entry_unchecked(i, j, entry) }; + seedable_function!( + /// Performs the randomized rounding algorithm entrywise + /// by sampling from a discrete Gaussian over the integers shifted + /// by `self` with gaussian parameter `r`. + /// + /// Parameters: + /// - `r`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = r` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns the rounded matrix as a [`MatZ`] or an error if `r < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::rational::MatQ; + /// use std::str::FromStr; + /// + /// let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + #[unseeded] + /// let rounded = value.randomized_rounding(3).unwrap(); + #[seeded] + /// let rounded = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `r < 0`. + /// + /// This function implements randomized rounding according to: + /// - \[1\] Peikert, C. (2010, August). + /// An efficient and parallel Gaussian sampler for lattices. + /// In: Annual Cryptology Conference (pp. 80-97). + /// + pub(crate) fn randomized_rounding( + &self, + r: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut rng = SamplerRng::new(seed); + + let mut out = MatZ::new(self.get_num_rows(), self.get_num_columns()); + let r = r.into(); + for i in 0..out.get_num_rows() { + for j in 0..out.get_num_columns() { + let entry = unsafe { self.get_entry_unchecked(i, j) } + .randomized_rounding_optionally_seeded(&r, rng.derive_seed())?; + unsafe { out.set_entry_unchecked(i, j, entry) }; + } } + Ok(out) } - Ok(out) - } + ); } #[cfg(test)] @@ -293,3 +313,29 @@ mod test_randomized_rounding { assert!(value.randomized_rounding(-1).is_err()); } } + +#[cfg(test)] +mod test_randomized_rounding_seeded { + use crate::rational::MatQ; + use crate::utils::sample::test_seed; + use std::str::FromStr; + + /// Ensure that a `r < 0` throws an error + #[test] + fn negative_r() { + let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::MatQ; + use std::str::FromStr; + let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + let sample_0 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + let sample_1 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/rational/mat_q/sample/gauss.rs b/src/rational/mat_q/sample/gauss.rs index 00c098fe..4e458407 100644 --- a/src/rational/mat_q/sample/gauss.rs +++ b/src/rational/mat_q/sample/gauss.rs @@ -10,8 +10,10 @@ use crate::{ error::MathError, + macros::seeded::seedable_function, rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixGetEntry, MatrixSetEntry}, + utils::sample::uniform::SamplerRng, }; use probability::{ prelude::{Gaussian, Sample}, @@ -21,101 +23,134 @@ use rand::Rng; use std::fmt::Display; impl MatQ { - /// Chooses a [`MatQ`] instance according to the continuous Gaussian distribution. - /// Here, each entry is chosen according to the provided distribution. - /// - /// Parameters: - /// - `center`: specifies the center for each entry of the matrix individually - /// - `sigma`: specifies the standard deviation - /// - /// Returns new [`MatQ`] sample chosen according to the specified continuous Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately (`sigma > 0`). - /// - /// # Examples - /// ``` - /// use qfall_math::rational::MatQ; - /// - /// let sample = MatQ::sample_gauss(&MatQ::new(5, 5), 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) - /// if `sigma <= 0`. - pub fn sample_gauss(center: &MatQ, sigma: impl Into) -> Result { - let mut out = MatQ::new(center.get_num_rows(), center.get_num_columns()); - let sigma = sigma.into(); - - for i in 0..out.get_num_rows() { - for j in 0..out.get_num_columns() { - let center_entry_ij = center.get_entry(i, j)?; - let sample = Q::sample_gauss(center_entry_ij, sigma)?; - unsafe { out.set_entry_unchecked(i, j, sample) }; + seedable_function!( + /// Chooses a [`MatQ`] instance according to the continuous Gaussian distribution. + /// Here, each entry is chosen according to the provided distribution. + /// + /// Parameters: + /// - `center`: specifies the center for each entry of the matrix individually + /// - `sigma`: specifies the standard deviation + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns new [`MatQ`] sample chosen according to the specified continuous Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately (`sigma > 0`). + /// + /// # Examples + /// ``` + /// use qfall_math::rational::MatQ; + /// + #[unseeded] + /// let sample = MatQ::sample_gauss(&MatQ::new(5, 5), 1).unwrap(); + #[seeded] + /// let sample = MatQ::sample_gauss_seeded(&MatQ::new(5, 5), 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) + /// if `sigma <= 0`. + pub(crate) fn sample_gauss( + center: &MatQ, + sigma: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut rng = SamplerRng::new(seed); + + let mut out = MatQ::new(center.get_num_rows(), center.get_num_columns()); + let sigma = sigma.into(); + + for i in 0..out.get_num_rows() { + for j in 0..out.get_num_columns() { + let center_entry_ij = center.get_entry(i, j)?; + let sample = Q::sample_gauss_optionally_seeded( + center_entry_ij, + sigma, + rng.derive_seed(), + )?; + unsafe { out.set_entry_unchecked(i, j, sample) }; + } } + + Ok(out) } + ); - Ok(out) - } + seedable_function!( + /// Chooses a [`MatQ`] instance according to the continuous Gaussian distribution. + /// Here, each entry is chosen according to the provided distribution and each entry + /// is sampled with the same center. + /// + /// Parameters: + /// - `num_rows`: specifies the number of rows of the sampled matrix + /// - `num_cols`: specifies the number of columns of the sampled matrix + /// - `center`: specifies the same center for each entry of the matrix + /// - `sigma`: specifies the standard deviation + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns new [`MatQ`] sample chosen according to the specified continuous Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately (`sigma > 0`). + /// + /// # Examples + /// ``` + /// use qfall_math::rational::{Q, MatQ}; + /// + /// let center = Q::from((5,2)); + /// + #[unseeded] + /// let sample = MatQ::sample_gauss_same_center(5, 5, ¢er, 1).unwrap(); + #[seeded] + /// let sample = MatQ::sample_gauss_same_center_seeded(5, 5, ¢er, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) + /// if `sigma <= 0`. + /// + /// # Panics ... + /// - if the number of rows or columns is negative, `0`, or does not fit into an [`i64`]. + pub(crate) fn sample_gauss_same_center( + num_rows: impl TryInto + Display, + num_cols: impl TryInto + Display, + center: impl Into, + sigma: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut out = MatQ::new(num_rows, num_cols); + let (center, sigma) = (center.into(), sigma.into()); + if sigma <= 0.0 { + return Err(MathError::NonPositive(format!( + "The sigma has to be positive and not zero, but the provided value is {sigma}." + ))); + } + let mut rng = SamplerRng::new(seed); + let mut source = source::default(rng.next_u64()); - /// Chooses a [`MatQ`] instance according to the continuous Gaussian distribution. - /// Here, each entry is chosen according to the provided distribution and each entry - /// is sampled with the same center. - /// - /// Parameters: - /// - `num_rows`: specifies the number of rows of the sampled matrix - /// - `num_cols`: specifies the number of columns of the sampled matrix - /// - `center`: specifies the same center for each entry of the matrix - /// - `sigma`: specifies the standard deviation - /// - /// Returns new [`MatQ`] sample chosen according to the specified continuous Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately (`sigma > 0`). - /// - /// # Examples - /// ``` - /// use qfall_math::rational::{Q, MatQ}; - /// - /// let center = Q::from((5,2)); - /// - /// let sample = MatQ::sample_gauss_same_center(5, 5, ¢er, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) - /// if `sigma <= 0`. - /// - /// # Panics ... - /// - if the number of rows or columns is negative, `0`, or does not fit into an [`i64`]. - pub fn sample_gauss_same_center( - num_rows: impl TryInto + Display, - num_cols: impl TryInto + Display, - center: impl Into, - sigma: impl Into, - ) -> Result { - let mut out = MatQ::new(num_rows, num_cols); - let (center, sigma) = (center.into(), sigma.into()); - if sigma <= 0.0 { - return Err(MathError::NonPositive(format!( - "The sigma has to be positive and not zero, but the provided value is {sigma}." - ))); - } - let mut rng = rand::rng(); - let mut source = source::default(rng.next_u64()); - - // Instead of sampling with a center of c, we sample with center 0 and add the - // center later. These are equivalent and this way we can sample in larger ranges - let sampler = Gaussian::new(0.0, sigma); - - for i in 0..out.get_num_rows() { - for j in 0..out.get_num_columns() { - let mut sample = Q::from(sampler.sample(&mut source)); - sample += ¢er; - unsafe { out.set_entry_unchecked(i, j, sample) }; + // Instead of sampling with a center of c, we sample with center 0 and add the + // center later. These are equivalent and this way we can sample in larger ranges + let sampler = Gaussian::new(0.0, sigma); + + for i in 0..out.get_num_rows() { + for j in 0..out.get_num_columns() { + let mut sample = Q::from(sampler.sample(&mut source)); + sample += ¢er; + unsafe { out.set_entry_unchecked(i, j, sample) }; + } } - } - Ok(out) - } + Ok(out) + } + ); } #[cfg(test)] @@ -143,6 +178,42 @@ mod test_sample_gauss { } } +#[cfg(test)] +mod test_sample_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{rational::MatQ, traits::MatrixDimensions}; + + /// Ensure that an error is returned if `sigma` is not positive + #[test] + fn non_positive_sigma() { + let center = MatQ::new(5, 5); + for sigma in [0, -1] { + assert!(MatQ::sample_gauss_seeded(¢er, sigma, test_seed()).is_err()) + } + } + + /// Ensure that the samples are of correct dimension + #[test] + fn correct_dimension() { + for (x, y) in [(5, 5), (1, 10), (10, 1)] { + let center = MatQ::new(x, y); + let sample = MatQ::sample_gauss_seeded(¢er, 1, test_seed()).unwrap(); + assert_eq!(center.get_num_rows(), sample.get_num_rows()); + assert_eq!(center.get_num_columns(), sample.get_num_columns()); + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::MatQ; + let sample_0 = MatQ::sample_gauss_seeded(&MatQ::new(5, 5), 1, [42; 32]).unwrap(); + let sample_1 = MatQ::sample_gauss_seeded(&MatQ::new(5, 5), 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} + #[cfg(test)] mod test_sample_gauss_same_center { @@ -180,3 +251,53 @@ mod test_sample_gauss_same_center { let _ = MatQ::sample_gauss_same_center(1, -1, 0, 1).unwrap(); } } + +#[cfg(test)] +mod test_sample_gauss_same_center_seeded { + use crate::utils::sample::test_seed; + + use crate::{rational::MatQ, traits::MatrixDimensions}; + + /// Ensure that an error is returned if `sigma` is not positive + #[test] + fn non_positive_sigma() { + for sigma in [0, -1] { + assert!(MatQ::sample_gauss_same_center_seeded(5, 5, 0, sigma, test_seed()).is_err()) + } + } + + /// Ensure that the samples are of correct dimension + #[test] + fn correct_dimension() { + for (x, y) in [(5, 5), (1, 10), (10, 1)] { + let sample = MatQ::sample_gauss_same_center_seeded(x, y, 0, 1, test_seed()).unwrap(); + assert_eq!(x, sample.get_num_rows()); + assert_eq!(y, sample.get_num_columns()); + } + } + + /// Ensure that a negative number of rows causes a panic + #[test] + #[should_panic] + fn negative_number_rows() { + let _ = MatQ::sample_gauss_same_center_seeded(-1, 1, 0, 1, test_seed()).unwrap(); + } + + /// Ensure that a negative number of columns causes a panic + #[test] + #[should_panic] + fn negative_number_columns() { + let _ = MatQ::sample_gauss_same_center_seeded(1, -1, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::{MatQ, Q}; + let center = Q::from((5, 2)); + let sample_0 = MatQ::sample_gauss_same_center_seeded(5, 5, ¢er, 1, [42; 32]).unwrap(); + let sample_1 = MatQ::sample_gauss_same_center_seeded(5, 5, ¢er, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/rational/poly_over_q/rounding.rs b/src/rational/poly_over_q/rounding.rs index f01b038e..45a1fb42 100644 --- a/src/rational/poly_over_q/rounding.rs +++ b/src/rational/poly_over_q/rounding.rs @@ -12,8 +12,10 @@ use super::PolyOverQ; use crate::{ error::MathError, integer::PolyOverZ, + macros::seeded::seedable_function, rational::Q, traits::{GetCoefficient, SetCoefficient}, + utils::sample::uniform::SamplerRng, }; impl PolyOverQ { @@ -93,45 +95,67 @@ impl PolyOverQ { out } - /// Performs the randomized rounding algorithm coefficient-wise - /// by sampling from a discrete Gaussian over the integers shifted - /// by `self` with gaussian parameter `r`. - /// - /// Parameters: - /// - `r`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = r` - /// - /// Returns the rounded polynomial as a [`PolyOverZ`] or an error if `r < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::rational::PolyOverQ; - /// use std::str::FromStr; - /// - /// let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - /// let rounded = value.randomized_rounding(3).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `r < 0`. - /// - /// This function implements randomized rounding according to: - /// - \[1\] Peikert, C. (2010, August). - /// An efficient and parallel Gaussian sampler for lattices. - /// In: Annual Cryptology Conference (pp. 80-97). - /// - pub fn randomized_rounding(&self, r: impl Into) -> Result { - let r = r.into(); - let mut out = - PolyOverZ::from(unsafe { self.get_coeff_unchecked(0).randomized_rounding(&r)? }); - for i in 1..self.get_degree() + 1 { - let coeff = unsafe { self.get_coeff_unchecked(i).randomized_rounding(&r)? }; - unsafe { out.set_coeff_unchecked(i, coeff) }; - } + seedable_function!( + /// Performs the randomized rounding algorithm coefficient-wise + /// by sampling from a discrete Gaussian over the integers shifted + /// by `self` with gaussian parameter `r`. + /// + /// Parameters: + /// - `r`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = r` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns the rounded polynomial as a [`PolyOverZ`] or an error if `r < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::rational::PolyOverQ; + /// use std::str::FromStr; + /// + /// let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + #[unseeded] + /// let rounded = value.randomized_rounding(3).unwrap(); + #[seeded] + /// let rounded = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `r < 0`. + /// + /// This function implements randomized rounding according to: + /// - \[1\] Peikert, C. (2010, August). + /// An efficient and parallel Gaussian sampler for lattices. + /// In: Annual Cryptology Conference (pp. 80-97). + /// + pub(crate) fn randomized_rounding( + &self, + r: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let mut rng = SamplerRng::new(seed); - Ok(out) - } + let r = r.into(); + let mut out = PolyOverZ::from(unsafe { + self.get_coeff_unchecked(0) + .randomized_rounding_optionally_seeded(&r, rng.derive_seed())? + }); + for i in 1..self.get_degree() + 1 { + let coeff = unsafe { + self.get_coeff_unchecked(i) + .randomized_rounding_optionally_seeded(&r, rng.derive_seed())? + }; + unsafe { out.set_coeff_unchecked(i, coeff) }; + } + + Ok(out) + } + ); } #[cfg(test)] @@ -218,3 +242,29 @@ mod test_randomized_rounding { assert!(value.randomized_rounding(-1).is_err()); } } + +#[cfg(test)] +mod test_randomized_rounding_seeded { + use crate::rational::PolyOverQ; + use crate::utils::sample::test_seed; + use std::str::FromStr; + + /// Ensure that a `r < 0` throws an error + #[test] + fn negative_r() { + let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::PolyOverQ; + use std::str::FromStr; + let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + let sample_0 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + let sample_1 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/rational/poly_over_q/sample/gauss.rs b/src/rational/poly_over_q/sample/gauss.rs index e86b5f14..207d5d03 100644 --- a/src/rational/poly_over_q/sample/gauss.rs +++ b/src/rational/poly_over_q/sample/gauss.rs @@ -11,9 +11,11 @@ use crate::{ error::MathError, + macros::seeded::seedable_function, rational::{PolyOverQ, Q}, traits::SetCoefficient, utils::index::evaluate_index, + utils::sample::uniform::SamplerRng, }; use probability::{ prelude::{Gaussian, Sample}, @@ -23,63 +25,75 @@ use rand::Rng; use std::fmt::Display; impl PolyOverQ { - /// Initializes a new [`PolyOverQ`] with maximum degree `max_degree` - /// and with each coefficient sampled independently according to the - /// Gaussian distribution, using [`Q::sample_gauss`]. - /// - /// Parameters: - /// - `max_degree`: specifies the included maximal degree the created [`PolyOverQ`] should have - /// - `center`: specifies the center for each coefficient of the polynomial - /// - `sigma`: specifies the standard deviation - /// - /// Returns a fresh [`PolyOverQ`] instance of maximum degree `max_degree` - /// with coefficients chosen independently according the Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately (`sigma > 0`). - /// - /// # Examples - /// ``` - /// use qfall_math::rational::PolyOverQ; - /// - /// let sample = PolyOverQ::sample_gauss(2, 0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) - /// if `sigma <= 0`. - /// - /// # Panics ... - /// - if `max_degree` is negative, or does not fit into an [`i64`]. - pub fn sample_gauss( - max_degree: impl TryInto + Display, - center: impl Into, - sigma: impl Into, - ) -> Result { - let max_degree = evaluate_index(max_degree).unwrap(); - - let center = center.into(); - let sigma: f64 = sigma.into(); - let mut poly = PolyOverQ::default(); - - if sigma <= 0.0 { - return Err(MathError::NonPositive(format!( - "The sigma has to be positive and not zero, but the provided value is {sigma}." - ))); - } - let mut rng = rand::rng(); - let mut source = source::default(rng.next_u64()); + seedable_function!( + /// Initializes a new [`PolyOverQ`] with maximum degree `max_degree` + /// and with each coefficient sampled independently according to the + /// Gaussian distribution, using [`Q::sample_gauss`]. + /// + /// Parameters: + /// - `max_degree`: specifies the included maximal degree the created [`PolyOverQ`] should have + /// - `center`: specifies the center for each coefficient of the polynomial + /// - `sigma`: specifies the standard deviation + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns a fresh [`PolyOverQ`] instance of maximum degree `max_degree` + /// with coefficients chosen independently according the Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately (`sigma > 0`). + /// + /// # Examples + /// ``` + /// use qfall_math::rational::PolyOverQ; + /// + #[unseeded] + /// let sample = PolyOverQ::sample_gauss(2, 0, 1).unwrap(); + #[seeded] + /// let sample = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) + /// if `sigma <= 0`. + /// + /// # Panics ... + /// - if `max_degree` is negative, or does not fit into an [`i64`]. + pub(crate) fn sample_gauss( + max_degree: impl TryInto + Display, + center: impl Into, + sigma: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let max_degree = evaluate_index(max_degree).unwrap(); + + let center = center.into(); + let sigma: f64 = sigma.into(); + let mut poly = PolyOverQ::default(); - // Instead of sampling with a center of c, we sample with center 0 and add the - // center later. These are equivalent and this way we can sample in larger ranges - let sampler = Gaussian::new(0.0, sigma); + if sigma <= 0.0 { + return Err(MathError::NonPositive(format!( + "The sigma has to be positive and not zero, but the provided value is {sigma}." + ))); + } + let mut rng = SamplerRng::new(seed); + let mut source = source::default(rng.next_u64()); - for index in 0..=max_degree { - let mut sample = Q::from(sampler.sample(&mut source)); - sample += ¢er; - unsafe { poly.set_coeff_unchecked(index, &sample) }; + // Instead of sampling with a center of c, we sample with center 0 and add the + // center later. These are equivalent and this way we can sample in larger ranges + let sampler = Gaussian::new(0.0, sigma); + + for index in 0..=max_degree { + let mut sample = Q::from(sampler.sample(&mut source)); + sample += ¢er; + unsafe { poly.set_coeff_unchecked(index, &sample) }; + } + Ok(poly) } - Ok(poly) - } + ); } #[cfg(test)] @@ -138,3 +152,71 @@ mod test_sample_gauss { let _ = PolyOverQ::sample_gauss(-1, 0, 1).unwrap(); } } + +#[cfg(test)] +mod test_sample_gauss_seeded { + use crate::utils::sample::test_seed; + use crate::{ + integer::Z, + rational::{PolyOverQ, Q}, + }; + + /// Checks whether `sample_gauss` is available for all types + /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... + /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + #[test] + fn availability() { + let center_q = Q::ZERO; + let center_z = Z::ZERO; + + let _ = PolyOverQ::sample_gauss_seeded(1u8, 16u8, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1u16, 16u16, 0f64, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1u32, 16u32, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1u64, 16u64, 0f64, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1i8, 16u8, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1i16, 16i16, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1i32, 16i32, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1i64, 16i64, 0f64, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1u8, center_q, 0f32, test_seed()); + let _ = PolyOverQ::sample_gauss_seeded(1u8, center_z, 0f64, test_seed()); + } + + /// Checks whether the number of coefficients is correct. + #[test] + fn nr_coeffs() { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverQ::sample_gauss_seeded(degree, i64::MAX, 1, test_seed()).unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } + } + + /// Checks is as error is returned, if sigma is less than 1. + #[test] + fn invalid_sigma() { + assert!(PolyOverQ::sample_gauss_seeded(1, 0, 0, test_seed()).is_err()); + assert!(PolyOverQ::sample_gauss_seeded(1, 0, -1, test_seed()).is_err()); + } + + /// Checks whether the maximum degree needs to be at least 0. + #[test] + #[should_panic] + fn invalid_max_degree() { + let _ = PolyOverQ::sample_gauss_seeded(-1, 0, 1, test_seed()).unwrap(); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::PolyOverQ; + let sample_0 = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + let sample_1 = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/rational/q/rounding.rs b/src/rational/q/rounding.rs index f244d55e..d0d1f72a 100644 --- a/src/rational/q/rounding.rs +++ b/src/rational/q/rounding.rs @@ -9,7 +9,7 @@ //! This module includes functionality for rounding instances of [`Q`]. use super::Q; -use crate::{error::MathError, integer::Z, traits::Distance}; +use crate::{error::MathError, integer::Z, macros::seeded::seedable_function, traits::Distance}; use flint3_sys::{ {fmpq_sgn, fmpq_simplest_between}, {fmpz_cdiv_q, fmpz_fdiv_q}, }; @@ -147,36 +147,51 @@ impl Q { out } - /// Performs the randomized rounding algorithm - /// by sampling from a discrete Gaussian over the integers shifted - /// by `self` with gaussian parameter `r`. - /// - /// Parameters: - /// - `r`: specifies the Gaussian parameter, which is proportional - /// to the standard deviation `sigma * sqrt(2 * pi) = r` - /// - /// Returns the rounded value as an [`Z`] or an error if `r < 0`. - /// - /// # Examples - /// ``` - /// use qfall_math::rational::Q; - /// - /// let value = Q::from((5, 2)); - /// let rounded = value.randomized_rounding(3).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) - /// if `r < 0`. - /// - /// This function implements randomized rounding according to: - /// - Peikert, C. (2010, August). - /// An efficient and parallel Gaussian sampler for lattices. - /// In: Annual Cryptology Conference (pp. 80-97). - /// - pub fn randomized_rounding(&self, r: impl Into) -> Result { - Z::sample_discrete_gauss(self, r) - } + seedable_function!( + /// Performs the randomized rounding algorithm + /// by sampling from a discrete Gaussian over the integers shifted + /// by `self` with gaussian parameter `r`. + /// + /// Parameters: + /// - `r`: specifies the Gaussian parameter, which is proportional + /// to the standard deviation `sigma * sqrt(2 * pi) = r` + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns the rounded value as an [`Z`] or an error if `r < 0`. + /// + /// # Examples + /// ``` + /// use qfall_math::rational::Q; + /// + /// let value = Q::from((5, 2)); + #[unseeded] + /// let rounded = value.randomized_rounding(3).unwrap(); + #[seeded] + /// let rounded = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`InvalidIntegerInput`](MathError::InvalidIntegerInput) + /// if `r < 0`. + /// + /// This function implements randomized rounding according to: + /// - Peikert, C. (2010, August). + /// An efficient and parallel Gaussian sampler for lattices. + /// In: Annual Cryptology Conference (pp. 80-97). + /// + pub(crate) fn randomized_rounding( + &self, + r: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + Z::sample_discrete_gauss_optionally_seeded(self, r, seed) + } + ); } #[cfg(test)] @@ -337,3 +352,27 @@ mod test_randomized_rounding { assert!(value.randomized_rounding(-1).is_err()); } } + +#[cfg(test)] +mod test_randomized_rounding_seeded { + use crate::rational::Q; + use crate::utils::sample::test_seed; + + /// Ensure that a `r < 0` throws an error + #[test] + fn negative_r() { + let value = Q::from((2, 3)); + assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::Q; + let value = Q::from((5, 2)); + let sample_0 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + let sample_1 = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/rational/q/sample/gauss.rs b/src/rational/q/sample/gauss.rs index b9e21de9..36f2f80a 100644 --- a/src/rational/q/sample/gauss.rs +++ b/src/rational/q/sample/gauss.rs @@ -8,7 +8,10 @@ //! This module contains sampling algorithms for gaussian distributions over [`Q`]. -use crate::{error::MathError, rational::Q}; +use crate::{ + error::MathError, macros::seeded::seedable_function, rational::Q, + utils::sample::uniform::SamplerRng, +}; use probability::{ distribution::{Gaussian, Sample}, source, @@ -16,44 +19,59 @@ use probability::{ use rand::Rng; impl Q { - /// Chooses a [`Q`] instance according to the continuous Gaussian distribution. - /// - /// Parameters: - /// - `center`: specifies the position of the center - /// - `sigma`: specifies the standard deviation - /// - /// Returns new [`Q`] sample chosen according to the specified continuous Gaussian - /// distribution or a [`MathError`] if the specified parameters were not chosen - /// appropriately (`sigma > 0`). - /// - /// # Examples - /// ``` - /// use qfall_math::rational::Q; - /// - /// let sample = Q::sample_gauss(0, 1).unwrap(); - /// ``` - /// - /// # Errors and Failures - /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) - /// if `sigma <= 0`. - pub fn sample_gauss(center: impl Into, sigma: impl Into) -> Result { - let center = center.into(); - let sigma = sigma.into(); - if sigma <= 0.0 { - return Err(MathError::NonPositive(format!( - "The sigma has to be positive and not zero, but the provided value is {sigma}." - ))); - } - let mut rng = rand::rng(); - let mut source = source::default(rng.next_u64()); + seedable_function!( + /// Chooses a [`Q`] instance according to the continuous Gaussian distribution. + /// + /// Parameters: + /// - `center`: specifies the position of the center + /// - `sigma`: specifies the standard deviation + #[seeded] + /// - `seed`: specifies the 256-bit seed of the PRNG used for sampling + #[optionally_seeded] + /// - `seed`: specifies an optional 256-bit seed of the PRNG used for sampling. + #[optionally_seeded] + /// If `None` is provided, a fresh [`ThreadRng`](rand::rngs::ThreadRng) is used instead. + /// + /// Returns new [`Q`] sample chosen according to the specified continuous Gaussian + /// distribution or a [`MathError`] if the specified parameters were not chosen + /// appropriately (`sigma > 0`). + /// + /// # Examples + /// ``` + /// use qfall_math::rational::Q; + /// + #[unseeded] + /// let sample = Q::sample_gauss(0, 1).unwrap(); + #[seeded] + /// let sample = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap(); + /// ``` + /// + /// # Errors and Failures + /// - Returns a [`MathError`] of type [`NonPositive`](MathError::NonPositive) + /// if `sigma <= 0`. + pub(crate) fn sample_gauss( + center: impl Into, + sigma: impl Into, + seed: Option<[u8; 32]>, + ) -> Result { + let center = center.into(); + let sigma = sigma.into(); + if sigma <= 0.0 { + return Err(MathError::NonPositive(format!( + "The sigma has to be positive and not zero, but the provided value is {sigma}." + ))); + } + let mut rng = SamplerRng::new(seed); + let mut source = source::default(rng.next_u64()); - // Instead of sampling with a center of c, we sample with center 0 and add the - // center later. These are equivalent and this way we can sample in larger ranges - let sampler = Gaussian::new(0.0, sigma); - let sample = center + Q::from(sampler.sample(&mut source)); + // Instead of sampling with a center of c, we sample with center 0 and add the + // center later. These are equivalent and this way we can sample in larger ranges + let sampler = Gaussian::new(0.0, sigma); + let sample = center + Q::from(sampler.sample(&mut source)); - Ok(sample) - } + Ok(sample) + } + ); } #[cfg(test)] @@ -78,3 +96,41 @@ mod test_sample_gauss { } } } + +#[cfg(test)] +mod test_sample_gauss_seeded { + use crate::rational::Q; + use crate::utils::sample::test_seed; + + /// Test correct distribution with a confidence level of 99.7% -> 3 standard + /// deviations. + #[test] + fn in_concentration_bound() { + let range = 3; + for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] { + assert!( + range * sigma + >= (Q::from(mu) - Q::sample_gauss_seeded(mu, sigma, test_seed()).unwrap()) + .abs() + ) + } + } + + /// Ensure that an error is returned if `sigma` is not positive + #[test] + fn non_positive_sigma() { + for (mu, sigma) in [(0, 0), (0, -1)] { + assert!(Q::sample_gauss_seeded(mu, sigma, test_seed()).is_err()) + } + } + + /// Checks whether the same seed results in the same sample. + #[test] + fn same_seed_same_sample() { + use crate::rational::Q; + let sample_0 = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap(); + let sample_1 = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap(); + + assert_eq!(sample_0, sample_1); + } +} diff --git a/src/utils/sample.rs b/src/utils/sample.rs index d76058fe..35cd2e29 100644 --- a/src/utils/sample.rs +++ b/src/utils/sample.rs @@ -11,3 +11,22 @@ pub mod binomial; pub mod discrete_gauss; pub mod uniform; + +/// Returns a fresh seed for every call within the same thread, starting from the +/// same sequence of seeds for every test, which allows to test seeded functions +/// repeatedly, e.g. in loops, without receiving the same sample in every iteration. +#[cfg(test)] +pub(crate) fn test_seed() -> [u8; 32] { + use std::cell::Cell; + + thread_local!(static COUNTER: Cell = const { Cell::new(0) }); + let counter = COUNTER.with(|counter| { + let value = counter.get(); + counter.set(value + 1); + value + }); + + let mut seed = [0; 32]; + seed[..8].copy_from_slice(&counter.to_le_bytes()); + seed +} diff --git a/src/utils/sample/binomial.rs b/src/utils/sample/binomial.rs index a700c151..949c317e 100644 --- a/src/utils/sample/binomial.rs +++ b/src/utils/sample/binomial.rs @@ -59,7 +59,7 @@ impl BinomialSampler { /// let n = 2; /// let p = 0.5; /// - /// let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); + /// let bin_sampler = BinomialSampler::init(n, p, None).unwrap(); /// /// let mut bin_sampler_seeded_0 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); /// let mut bin_sampler_seeded_1 = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index 71e9e96c..ef42b113 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -23,7 +23,7 @@ use crate::{ rational::{MatQ, Q}, traits::{MatrixDimensions, MatrixGetSubmatrix, Pow}, }; -use rand::{RngExt, SeedableRng, rngs::StdRng}; +use rand::RngExt; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -71,9 +71,7 @@ pub static mut TAILCUT: f64 = 6.0; /// /// # Examples /// ``` -/// use qfall_math::{integer::Z, rational::Q}; /// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; -/// let n = Z::from(1024); /// let center = 0.0; /// let gaussian_parameter = 1.0; /// let tailcut = 6.0; @@ -89,6 +87,7 @@ pub struct DiscreteGaussianIntegerSampler { pub lower_bound: Z, pub interval_size: Z, pub lookup_table_setting: LookupTableSetting, + #[serde(with = "table_as_pairs")] pub table: HashMap, #[serde(skip)] pub rng: SamplerRng, @@ -127,7 +126,7 @@ impl DiscreteGaussianIntegerSampler { /// let gaussian_parameter = 1.0; /// let tailcut = 6.0; /// - /// let mut dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, None).unwrap(); + /// let dgis = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, None).unwrap(); /// /// let mut dgis_seeded_0 = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, Some([42; 32])).unwrap(); /// let mut dgis_seeded_1 = DiscreteGaussianIntegerSampler::init(center, gaussian_parameter, tailcut, LookupTableSetting::Precompute, Some([42; 32])).unwrap(); @@ -205,7 +204,6 @@ impl DiscreteGaussianIntegerSampler { /// /// # Examples /// ``` - /// use qfall_math::{integer::Z, rational::Q}; /// use qfall_math::utils::sample::discrete_gauss::{DiscreteGaussianIntegerSampler, LookupTableSetting}; /// let center = 0.0; /// let gaussian_parameter = 1.0; @@ -249,6 +247,30 @@ impl DiscreteGaussianIntegerSampler { } } +/// Serializes and deserializes the lookup table of a [`DiscreteGaussianIntegerSampler`] +/// as a sequence of pairs, as [`Z`] can not be used as a key of a map in every format, e.g. JSON. +mod table_as_pairs { + use crate::integer::Z; + use serde::{Deserialize, Deserializer, Serializer}; + use std::collections::HashMap; + + /// Serializes `table` as a sequence of `(key, value)` pairs. + pub(super) fn serialize( + table: &HashMap, + serializer: S, + ) -> Result { + serializer.collect_seq(table.iter()) + } + + /// Deserializes a sequence of `(key, value)` pairs into a [`HashMap`]. + pub(super) fn deserialize<'de, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let pairs = Vec::<(Z, f64)>::deserialize(deserializer)?; + Ok(pairs.into_iter().collect()) + } +} + /// Computes the value of the Gaussian function for `x`. /// /// **Warning**: This functions assumes `s != 0`. @@ -419,7 +441,7 @@ pub(crate) fn sample_d_precomputed_gso( let mut out = MatZ::new(basis_gso.get_num_rows(), 1); // derives an independent seed for the sampler of each dimension if a seed is provided - let mut seed_rng = seed.map(StdRng::from_seed); + let mut rng = SamplerRng::new(seed); for i in (0..basis_gso.get_num_columns()).rev() { // basisvector_i = b_tilde[i] @@ -438,7 +460,7 @@ pub(crate) fn sample_d_precomputed_gso( &s_2, unsafe { TAILCUT }, LookupTableSetting::FillOnTheFly, - seed_rng.as_mut().map(|rng| rng.random()), + rng.derive_seed(), )?; let z = dgis.sample_z(); @@ -562,21 +584,26 @@ mod test_discrete_gaussian_integer_sampler { ); } - /// Checks whether a serialized and deserialized sampler still samples - /// from the same interval. + /// Checks whether a serialized and deserialized sampler keeps its lookup table + /// and still samples from the same interval. #[test] fn serialize_deserialize() { let dgis = DiscreteGaussianIntegerSampler::init( Q::from(15), Q::from((1, 2)), 8.0, - LookupTableSetting::NoLookup, + LookupTableSetting::Precompute, Some([42; 32]), ) .unwrap(); let string = serde_json::to_string(&dgis).unwrap(); - let mut dgis: DiscreteGaussianIntegerSampler = serde_json::from_str(&string).unwrap(); + let dgis_deserialized: DiscreteGaussianIntegerSampler = + serde_json::from_str(&string).unwrap(); + + assert!(!dgis.table.is_empty()); + assert_eq!(dgis.table, dgis_deserialized.table); + let mut dgis = dgis_deserialized; for _ in 0..64 { let sample = dgis.sample_z(); diff --git a/src/utils/sample/uniform.rs b/src/utils/sample/uniform.rs index c2c312fc..15f62c6a 100644 --- a/src/utils/sample/uniform.rs +++ b/src/utils/sample/uniform.rs @@ -12,7 +12,7 @@ use crate::{error::MathError, integer::Z}; use flint3_sys::{fmpz_addmul_ui, fmpz_set_ui}; use rand::{ - Rng, SeedableRng, TryCryptoRng, TryRng, + Rng, RngExt, SeedableRng, TryCryptoRng, TryRng, rngs::{StdRng, ThreadRng}, }; use std::{cell::RefCell, convert::Infallible, rc::Rc}; @@ -48,6 +48,15 @@ impl SamplerRng { None => Self::Thread(rand::rng()), } } + + /// Returns a fresh seed drawn from this [`SamplerRng`] if it is seeded, and `None` otherwise. + /// This allows to derive independent seeds for several subroutines of one seeded function. + pub(crate) fn derive_seed(&mut self) -> Option<[u8; 32]> { + match self { + Self::Thread(_) => None, + Self::Seeded(_) => Some(self.random()), + } + } } impl Default for SamplerRng { @@ -138,7 +147,7 @@ impl UniformIntegerSampler { /// use qfall_math::{utils::sample::uniform::UniformIntegerSampler, integer::Z}; /// let interval_size = Z::from(20); /// - /// let mut uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); + /// let uis = UniformIntegerSampler::init(&interval_size, None).unwrap(); /// /// let mut uis_seeded_0 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); /// let mut uis_seeded_1 = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); From f098b3584c7a0ec72a97055b01fd87c05ec3bbae Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Mon, 19 Oct 2026 17:36:00 +0100 Subject: [PATCH 4/5] Revise tests of (un)seeded samplers --- .../mat_poly_over_z/sample/binomial.rs | 845 ++++++++------- .../mat_poly_over_z/sample/discrete_gauss.rs | 494 +++++---- src/integer/mat_poly_over_z/sample/uniform.rs | 486 +++++---- src/integer/mat_z/sample/binomial.rs | 455 ++++---- src/integer/mat_z/sample/discrete_gauss.rs | 698 ++++++++----- src/integer/mat_z/sample/uniform.rs | 340 +++--- src/integer/poly_over_z/sample/binomial.rs | 406 +++---- .../poly_over_z/sample/discrete_gauss.rs | 180 ++-- src/integer/poly_over_z/sample/uniform.rs | 327 +++--- src/integer/z/sample/binomial.rs | 68 +- src/integer/z/sample/discrete_gauss.rs | 170 ++- src/integer/z/sample/uniform.rs | 486 ++++----- .../sample/uniform.rs | 96 +- .../mat_polynomial_ring_zq/sample/binomial.rs | 987 +++++++++--------- .../sample/discrete_gauss.rs | 334 +++--- .../mat_polynomial_ring_zq/sample/uniform.rs | 293 +++--- src/integer_mod_q/mat_zq/sample/binomial.rs | 575 ++++++---- .../mat_zq/sample/discrete_gauss.rs | 517 +++++---- src/integer_mod_q/mat_zq/sample/uniform.rs | 221 ++-- .../ntt_polynomial_ring_zq/sample/uniform.rs | 87 +- .../poly_over_zq/sample/binomial.rs | 488 +++++---- .../poly_over_zq/sample/discrete_gauss.rs | 306 +++--- .../poly_over_zq/sample/uniform.rs | 263 +++-- .../polynomial_ring_zq/sample/binomial.rs | 289 +++-- .../sample/discrete_gauss.rs | 294 +++--- .../polynomial_ring_zq/sample/uniform.rs | 185 ++-- src/integer_mod_q/z_q/sample/binomial.rs | 74 +- .../z_q/sample/discrete_gauss.rs | 247 +++-- src/integer_mod_q/z_q/sample/uniform.rs | 190 ++-- src/rational/mat_q/rounding.rs | 46 +- src/rational/mat_q/sample/gauss.rs | 182 ++-- src/rational/poly_over_q/rounding.rs | 46 +- src/rational/poly_over_q/sample/gauss.rs | 190 ++-- src/rational/q/rounding.rs | 43 +- src/rational/q/sample/gauss.rs | 66 +- src/utils/sample/binomial.rs | 123 +-- src/utils/sample/discrete_gauss.rs | 652 ++++++------ src/utils/sample/uniform.rs | 174 ++- 38 files changed, 6398 insertions(+), 5525 deletions(-) diff --git a/src/integer/mat_poly_over_z/sample/binomial.rs b/src/integer/mat_poly_over_z/sample/binomial.rs index c86352fb..1e209ebc 100644 --- a/src/integer/mat_poly_over_z/sample/binomial.rs +++ b/src/integer/mat_poly_over_z/sample/binomial.rs @@ -159,6 +159,7 @@ impl MatPolyOverZ { mod test_sample_binomial { use super::{MatPolyOverZ, Q, Z}; use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, // whether the correct interval is kept, and if the errors are thrown correctly, @@ -167,48 +168,95 @@ mod test_sample_binomial { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let matrix = MatPolyOverZ::sample_binomial(1, 1, 0, 2, 0.5).unwrap(); - let entry = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let entry = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); - assert!(Z::ZERO <= poly); - assert!(poly <= 2); + assert!(Z::ZERO <= poly); + assert!(poly <= 2); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = MatPolyOverZ::sample_binomial(1, 1, degree, 256, 0.99999).unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree(), - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + degree, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree(), + "This test can fail with probability close to 0." + ); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatPolyOverZ::sample_binomial(0, 3, 0, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 0, + 3, + 0, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = MatPolyOverZ::sample_binomial(1, 1, -1, 2, 0.5); - let res_1 = MatPolyOverZ::sample_binomial(1, 1, i64::MIN, 2, 0.5); + for seeded in [false, true] { + let res_0 = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); + let res_1 = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + i64::MIN, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -216,134 +264,156 @@ mod test_sample_binomial { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = MatPolyOverZ::sample_binomial(1, 1, 0u8, 1u16, 7u8); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0u16, 1u32, 7u16); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0u32, 1u64, 7u32); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0u64, 1i8, 7u64); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0i8, 1i16, 7i8); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0i16, 1i32, 7i16); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0i32, 1i64, 7i32); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0i64, Z::ONE, 7i64); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0, 1u8, 0.5f32); - let _ = MatPolyOverZ::sample_binomial(1, 1, Z::ZERO, 1u8, 0.5f64); - let _ = MatPolyOverZ::sample_binomial(1, 1, 0, 1, Q::from((1, 2))); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = MatPolyOverZ::sample_binomial(3, 3, 0, 1, 0.5).unwrap(); - let mat_1 = MatPolyOverZ::sample_binomial(4, 1, 0, 1, 0.5).unwrap(); - let mat_2 = MatPolyOverZ::sample_binomial(1, 5, 0, 1, 0.5).unwrap(); - let mat_3 = MatPolyOverZ::sample_binomial(15, 20, 0, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{MatPolyOverZ, Q, Z}; - use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; - use crate::utils::sample::test_seed; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let matrix = - MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 2, 0.5, test_seed()).unwrap(); - let entry = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); - - assert!(Z::ZERO <= poly); - assert!(poly <= 2); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = - MatPolyOverZ::sample_binomial_seeded(1, 1, degree, 256, 0.99999, test_seed()) - .unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree(), - "This test can fail with probability close to 0." + for seeded in [false, true] { + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0i32, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0i64, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + Z::ZERO, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + 0, + 1, + Q::from((1, 2)), + seeded.then(test_seed), ); - } - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let _ = MatPolyOverZ::sample_binomial_seeded(0, 3, 0, 1, 0.5, test_seed()); - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let res_0 = MatPolyOverZ::sample_binomial_seeded(1, 1, -1, 2, 0.5, test_seed()); - let res_1 = MatPolyOverZ::sample_binomial_seeded(1, 1, i64::MIN, 2, 0.5, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u16, 1u32, 7u16, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u32, 1u64, 7u32, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u64, 1i8, 7u64, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i8, 1i16, 7i8, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i16, 1i32, 7i16, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i32, 1i64, 7i32, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0i64, Z::ONE, 7i64, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 1u8, 0.5f32, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, Z::ZERO, 1u8, 0.5f64, test_seed()); - let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0, 1, Q::from((1, 2)), test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolyOverZ::sample_binomial_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); + } else { + let _ = MatPolyOverZ::sample_binomial(1, 1, 0u8, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = MatPolyOverZ::sample_binomial_seeded(3, 3, 0, 1, 0.5, test_seed()).unwrap(); - let mat_1 = MatPolyOverZ::sample_binomial_seeded(4, 1, 0, 1, 0.5, test_seed()).unwrap(); - let mat_2 = MatPolyOverZ::sample_binomial_seeded(1, 5, 0, 1, 0.5, test_seed()).unwrap(); - let mat_3 = MatPolyOverZ::sample_binomial_seeded(15, 20, 0, 1, 0.5, test_seed()).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let mat_0 = MatPolyOverZ::sample_binomial_optionally_seeded( + 3, + 3, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatPolyOverZ::sample_binomial_optionally_seeded( + 4, + 1, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatPolyOverZ::sample_binomial_optionally_seeded( + 1, + 5, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatPolyOverZ::sample_binomial_optionally_seeded( + 15, + 20, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -355,12 +425,25 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatPolyOverZ; + let sample = MatPolyOverZ::sample_binomial_seeded(2, 2, 5, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!( + "[[6 0 2 1 2 0 1, 6 0 1 0 1 0 1],[6 0 2 2 1 2 1, 5 1 1 1 2 1]]", + sample.to_string() + ); + } } #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatPolyOverZ, Q, Z}; use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, // whether the correct interval is kept, and if the errors are thrown correctly, @@ -369,49 +452,100 @@ mod test_sample_binomial_with_offset { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let matrix = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0, -1, 2, 0.5).unwrap(); - let entry = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + -1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let entry = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= poly); - assert!(poly <= Z::ONE); + assert!(Z::MINUS_ONE <= poly); + assert!(poly <= Z::ONE); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = - MatPolyOverZ::sample_binomial_with_offset(1, 1, degree, -1, 256, 0.99999).unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree(), - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + degree, + -1, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree(), + "This test can fail with probability close to 0." + ); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatPolyOverZ::sample_binomial_with_offset(0, 3, 0, 0, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 0, + 3, + 0, + 0, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = MatPolyOverZ::sample_binomial_with_offset(1, 1, -1, -1, 2, 0.5); - let res_1 = MatPolyOverZ::sample_binomial_with_offset(1, 1, i64::MIN, -1, 2, 0.5); + for seeded in [false, true] { + let res_0 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + -1, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); + let res_1 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + i64::MIN, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -419,228 +553,181 @@ mod test_sample_binomial_with_offset { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0u8, -1, 1u16, 7u8); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0u16, 0, 1u32, 7u16); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0u32, Z::ONE, 1u64, 7u32); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0u64, Z::MINUS_ONE, 1i8, 7u64); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0i8, -1, 1i16, 7i8); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0i16, -1, 1i32, 7i16); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0i32, -1, 1i64, 7i32); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0i64, -1, Z::ONE, 7i64); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0, -1, 1u8, 0.5f32); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, Z::ZERO, -1, 1u8, 0.5f64); - let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0, -1, 1, Q::from((1, 2))); + for seeded in [false, true] { + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u8, + -1, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u16, + 0, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u32, + Z::ONE, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u64, + Z::MINUS_ONE, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i8, + -1, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i16, + -1, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i32, + -1, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i64, + -1, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + -1, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + Z::ZERO, + -1, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + -1, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( + 1, + 1, + 0u8, + -1, + 1u16, + 7u8, + test_seed(), + ); + } else { + let _ = MatPolyOverZ::sample_binomial_with_offset(1, 1, 0u8, -1, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = MatPolyOverZ::sample_binomial_with_offset(3, 3, 0, -1, 1, 0.5).unwrap(); - let mat_1 = MatPolyOverZ::sample_binomial_with_offset(4, 1, 0, -1, 1, 0.5).unwrap(); - let mat_2 = MatPolyOverZ::sample_binomial_with_offset(1, 5, 0, -1, 1, 0.5).unwrap(); - let mat_3 = MatPolyOverZ::sample_binomial_with_offset(15, 20, 0, -1, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_binomial_with_offset_seeded { - use super::{MatPolyOverZ, Q, Z}; - use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; - use crate::utils::sample::test_seed; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let matrix = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0, -1, 2, 0.5, test_seed()) - .unwrap(); - let entry = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); - - assert!(Z::MINUS_ONE <= poly); - assert!(poly <= Z::ONE); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = MatPolyOverZ::sample_binomial_with_offset_seeded( + for seeded in [false, true] { + let mat_0 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 3, + 3, + 0, + -1, 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 4, 1, - degree, + 0, -1, - 256, - 0.99999, - test_seed(), + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 5, + 0, + -1, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatPolyOverZ::sample_binomial_with_offset_optionally_seeded( + 15, + 20, + 0, + -1, + 1, + 0.5, + seeded.then(test_seed), ) .unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); - assert_eq!( - degree, - poly.get_degree(), - "This test can fail with probability close to 0." - ); + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); } } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded(0, 3, 0, 0, 1, 0.5, test_seed()); - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let res_0 = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, -1, -1, 2, 0.5, test_seed()); - let res_1 = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - i64::MIN, - -1, - 2, - 0.5, - test_seed(), - ); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0u8, -1, 1u16, 7u8, test_seed()); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0u16, - 0, - 1u32, - 7u16, - test_seed(), - ); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0u32, - Z::ONE, - 1u64, - 7u32, - test_seed(), - ); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0u64, - Z::MINUS_ONE, - 1i8, - 7u64, - test_seed(), - ); - let _ = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0i8, -1, 1i16, 7i8, test_seed()); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0i16, - -1, - 1i32, - 7i16, - test_seed(), - ); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0i32, - -1, - 1i64, - 7i32, - test_seed(), - ); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0i64, - -1, - Z::ONE, - 7i64, - test_seed(), - ); - let _ = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 1, 0, -1, 1u8, 0.5f32, test_seed()); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - Z::ZERO, - -1, - 1u8, - 0.5f64, - test_seed(), - ); - let _ = MatPolyOverZ::sample_binomial_with_offset_seeded( - 1, - 1, - 0, - -1, - 1, - Q::from((1, 2)), - test_seed(), - ); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = - MatPolyOverZ::sample_binomial_with_offset_seeded(3, 3, 0, -1, 1, 0.5, test_seed()) - .unwrap(); - let mat_1 = - MatPolyOverZ::sample_binomial_with_offset_seeded(4, 1, 0, -1, 1, 0.5, test_seed()) - .unwrap(); - let mat_2 = - MatPolyOverZ::sample_binomial_with_offset_seeded(1, 5, 0, -1, 1, 0.5, test_seed()) - .unwrap(); - let mat_3 = - MatPolyOverZ::sample_binomial_with_offset_seeded(15, 20, 0, -1, 1, 0.5, test_seed()) - .unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } - /// Checks whether the same seed results in the same sample. #[test] fn same_seed_same_sample() { @@ -654,4 +741,18 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatPolyOverZ; + let sample = + MatPolyOverZ::sample_binomial_with_offset_seeded(2, 2, 5, -1, 2, 0.5, [42; 32]) + .unwrap(); + + assert_eq!( + "[[5 -1 1 0 1 -1, 5 -1 0 -1 0 -1],[5 -1 1 1 0 1, 6 0 0 0 1 0 -1]]", + sample.to_string() + ); + } } diff --git a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs index 160cdea0..a5af5d81 100644 --- a/src/integer/mat_poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/mat_poly_over_z/sample/discrete_gauss.rs @@ -184,6 +184,7 @@ impl MatPolyOverZ { #[cfg(test)] mod test_sample_discrete_gauss { + use crate::utils::sample::test_seed; use crate::{integer::MatPolyOverZ, traits::MatrixGetEntry}; /// Checks whether `sample_discrete_gauss` is available for all types @@ -191,92 +192,163 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let _ = MatPolyOverZ::sample_discrete_gauss(2, 3, 128, 1u16, 2); - let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 128, 1u8, 2.0); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i64, 3, 128, 1u32, 2.0_f64); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i32, 3, 128, 1u64, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i16, 3, 128, 1i64, 3_u64); - let _ = MatPolyOverZ::sample_discrete_gauss(2_i8, 3, 128, 1i32, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u64, 3, 128, 1i16, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u32, 3, 128, 1i8, 1); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u16, 3, 128, 1i64, 2); - let _ = MatPolyOverZ::sample_discrete_gauss(2_u8, 3, 128, -2, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(1, 3, 128, 4, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(3, 3, 128, 1.25f64, 3); - let _ = MatPolyOverZ::sample_discrete_gauss(4, 3, 128, 15.75f32, 3); - } - - /// Ensures that the resulting entries have correct degree. - #[test] - fn correct_degree_entries() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = MatPolyOverZ::sample_discrete_gauss(1, 1, degree, i64::MAX, 1).unwrap(); - - assert_eq!( - res.get_entry(0, 0).unwrap().get_degree(), - degree, - "Could fail with negligible probability." + for seeded in [false, true] { + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2, + 3, + 128, + 1u16, + 2, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 1, + 3, + 128, + 1u8, + 2.0, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_i64, + 3, + 128, + 1u32, + 2.0_f64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_i32, + 3, + 128, + 1u64, + 1, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_i16, + 3, + 128, + 1i64, + 3_u64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_i8, + 3, + 128, + 1i32, + 1, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_u64, + 3, + 128, + 1i16, + 1, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_u32, + 3, + 128, + 1i8, + 1, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_u16, + 3, + 128, + 1i64, + 2, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2_u8, + 3, + 128, + -2, + 3, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 1, + 3, + 128, + 4, + 3, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 3, + 3, + 128, + 1.25f64, + 3, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 4, + 3, + 128, + 15.75f32, + 3, + seeded.then(test_seed), ); - } - } - - /// Checks whether the maximum degree needs to be at least 0. - #[test] - #[should_panic] - fn invalid_max_degree() { - let _ = MatPolyOverZ::sample_discrete_gauss(2, 2, -1, 0, 1).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{integer::MatPolyOverZ, traits::MatrixGetEntry}; - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 3, 128, 1u16, 2, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(1, 3, 128, 1u8, 2.0, test_seed()); - let _ = - MatPolyOverZ::sample_discrete_gauss_seeded(2_i64, 3, 128, 1u32, 2.0_f64, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i32, 3, 128, 1u64, 1, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i16, 3, 128, 1i64, 3_u64, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_i8, 3, 128, 1i32, 1, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u64, 3, 128, 1i16, 1, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u32, 3, 128, 1i8, 1, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u16, 3, 128, 1i64, 2, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2_u8, 3, 128, -2, 3, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(1, 3, 128, 4, 3, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(3, 3, 128, 1.25f64, 3, test_seed()); - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(4, 3, 128, 15.75f32, 3, test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 3, 128, 1u16, 2, test_seed()); + } else { + let _ = MatPolyOverZ::sample_discrete_gauss(2, 3, 128, 1u16, 2); + } + } } /// Ensures that the resulting entries have correct degree. #[test] fn correct_degree_entries() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = - MatPolyOverZ::sample_discrete_gauss_seeded(1, 1, degree, i64::MAX, 1, test_seed()) - .unwrap(); - - assert_eq!( - res.get_entry(0, 0).unwrap().get_degree(), - degree, - "Could fail with negligible probability." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + degree, + i64::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + res.get_entry(0, 0).unwrap().get_degree(), + degree, + "Could fail with negligible probability." + ); + } } } /// Checks whether the maximum degree needs to be at least 0. #[test] - #[should_panic] fn invalid_max_degree() { - let _ = MatPolyOverZ::sample_discrete_gauss_seeded(2, 2, -1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatPolyOverZ::sample_discrete_gauss_optionally_seeded( + 2, + 2, + -1, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -290,79 +362,23 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } -} -#[cfg(test)] -mod test_sample_d { - use crate::{ - integer::{MatPolyOverZ, MatZ, Z}, - rational::{PolyOverQ, Q}, - traits::IntoCoefficientEmbedding, - }; - use std::str::FromStr; - - /// Ensure that the sample is from the base. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn ensure_sampled_from_base() { - let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); - let center = vec![PolyOverQ::default()]; - - for _ in 0..10 { - let sample = MatPolyOverZ::sample_d(&base, 3, ¢er, 10.5_f64).unwrap(); - let sample_vec = sample.into_coefficient_embedding(3); - let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap(); - - assert_eq!(Z::ZERO, sample_vec.dot_product(&orthogonal).unwrap()); - } - } - - /// Ensure that the sample is from the base for higher dimensional bases. - #[test] - fn ensure_sampled_from_base_higher_dimension() { - let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1],[3 0 1 -1, 1 1],[0, 0]]").unwrap(); - let center = vec![ - PolyOverQ::default(), - PolyOverQ::default(), - PolyOverQ::default(), - ]; - - let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap(); - for _ in 0..10 { - let sample = MatPolyOverZ::sample_d(&base, 3, ¢er, 10.5_f64).unwrap(); - let sample_embedded = sample.into_coefficient_embedding(3); - - assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded); - } - } + fn known_answer() { + use crate::integer::MatPolyOverZ; + let sample = + MatPolyOverZ::sample_discrete_gauss_seeded(3, 1, 5, 0, 1.25f32, [42; 32]).unwrap(); - /// Checks whether `sample_d` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let basis = MatPolyOverZ::from_str("[[1 1, 2 0 1, 3 0 0 1]]").unwrap(); - let center = vec![PolyOverQ::default()]; - let n = Z::from(1024); - let s = Q::ONE; - - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u16); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u8); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u32); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u64); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i64); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i32); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i16); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i8); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1i64); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, &n); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, &s); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1.25f64); - let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 15.75f32); + assert_eq!( + "[[4 1 -1 0 1],[3 0 0 -1],[6 -1 0 0 0 0 -1]]", + sample.to_string() + ); } } #[cfg(test)] -mod test_sample_d_seeded { +mod test_sample_d { use crate::utils::sample::test_seed; use crate::{ integer::{MatPolyOverZ, MatZ, Z}, @@ -374,36 +390,53 @@ mod test_sample_d_seeded { /// Ensure that the sample is from the base. #[test] fn ensure_sampled_from_base() { - let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); - let center = vec![PolyOverQ::default()]; - - for _ in 0..10 { - let sample = - MatPolyOverZ::sample_d_seeded(&base, 3, ¢er, 10.5_f64, test_seed()).unwrap(); - let sample_vec = sample.into_coefficient_embedding(3); - let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap(); - - assert_eq!(Z::ZERO, sample_vec.dot_product(&orthogonal).unwrap()); + for seeded in [false, true] { + let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); + let center = vec![PolyOverQ::default()]; + + for _ in 0..10 { + let sample = MatPolyOverZ::sample_d_optionally_seeded( + &base, + 3, + ¢er, + 10.5_f64, + seeded.then(test_seed), + ) + .unwrap(); + let sample_vec = sample.into_coefficient_embedding(3); + let orthogonal = MatZ::from_str("[[0],[1],[1]]").unwrap(); + + assert_eq!(Z::ZERO, sample_vec.dot_product(&orthogonal).unwrap()); + } } } /// Ensure that the sample is from the base for higher dimensional bases. #[test] fn ensure_sampled_from_base_higher_dimension() { - let base = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1],[3 0 1 -1, 1 1],[0, 0]]").unwrap(); - let center = vec![ - PolyOverQ::default(), - PolyOverQ::default(), - PolyOverQ::default(), - ]; - - let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap(); - for _ in 0..10 { - let sample = - MatPolyOverZ::sample_d_seeded(&base, 3, ¢er, 10.5_f64, test_seed()).unwrap(); - let sample_embedded = sample.into_coefficient_embedding(3); - - assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded); + for seeded in [false, true] { + let base = + MatPolyOverZ::from_str("[[1 1, 3 0 1 -1],[3 0 1 -1, 1 1],[0, 0]]").unwrap(); + let center = vec![ + PolyOverQ::default(), + PolyOverQ::default(), + PolyOverQ::default(), + ]; + + let orthogonal = MatZ::from_str("[[0, 1, 1, 0, 1, 1, 0 , 0, 0]]").unwrap(); + for _ in 0..10 { + let sample = MatPolyOverZ::sample_d_optionally_seeded( + &base, + 3, + ¢er, + 10.5_f64, + seeded.then(test_seed), + ) + .unwrap(); + let sample_embedded = sample.into_coefficient_embedding(3); + + assert_eq!(MatZ::new(1, 1), &orthogonal * &sample_embedded); + } } } @@ -412,24 +445,111 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let basis = MatPolyOverZ::from_str("[[1 1, 2 0 1, 3 0 0 1]]").unwrap(); - let center = vec![PolyOverQ::default()]; - let n = Z::from(1024); - let s = Q::ONE; - - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u8, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u32, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u64, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i32, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i16, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i8, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, &n, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, &s, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1.25f64, test_seed()); - let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 15.75f32, test_seed()); + for seeded in [false, true] { + let basis = MatPolyOverZ::from_str("[[1 1, 2 0 1, 3 0 0 1]]").unwrap(); + let center = vec![PolyOverQ::default()]; + let n = Z::from(1024); + let s = Q::ONE; + + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); + } else { + let _ = MatPolyOverZ::sample_d(&basis, 3, ¢er, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -446,4 +566,16 @@ mod test_sample_d_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::{integer::MatPolyOverZ, rational::PolyOverQ}; + use std::str::FromStr; + let basis = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1, 2 2 2]]").unwrap(); + let center = vec![PolyOverQ::default()]; + let sample = MatPolyOverZ::sample_d_seeded(&basis, 3, ¢er, 10.5_f64, [42; 32]).unwrap(); + + assert_eq!("[[3 5 -9 3]]", sample.to_string()); + } } diff --git a/src/integer/mat_poly_over_z/sample/uniform.rs b/src/integer/mat_poly_over_z/sample/uniform.rs index 52389b58..db6113c0 100644 --- a/src/integer/mat_poly_over_z/sample/uniform.rs +++ b/src/integer/mat_poly_over_z/sample/uniform.rs @@ -110,6 +110,7 @@ impl MatPolyOverZ { #[cfg(test)] mod test_sample_uniform { use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; + use crate::utils::sample::test_seed; use crate::{ integer::{MatPolyOverZ, Z}, integer_mod_q::Modulus, @@ -118,295 +119,320 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let matrix = MatPolyOverZ::sample_uniform(1, 1, 0, &lower_bound, &upper_bound).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); + for seeded in [false, true] { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let matrix = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); - assert!(lower_bound <= coeff); - assert!(coeff < upper_bound); + assert!(lower_bound <= coeff); + assert!(coeff < upper_bound); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let matrix = MatPolyOverZ::sample_uniform(1, 1, 0, &lower_bound, &upper_bound).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); + for seeded in [false, true] { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let matrix = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); - assert!(lower_bound <= coeff); - assert!(coeff < upper_bound); + assert!(lower_bound <= coeff); + assert!(coeff < upper_bound); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = MatPolyOverZ::sample_uniform(1, 1, degree, 1, 15).unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let matrix = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + degree, + 1, + 15, + seeded.then(test_seed), + ) + .unwrap(); + let poly = matrix.get_entry(0, 0).unwrap(); - assert_eq!(degree, poly.get_degree()); + assert_eq!(degree, poly.get_degree()); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too large for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let _ = MatPolyOverZ::sample_uniform(0, 3, 1, &lower_bound, &upper_bound); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 0, + 3, + 1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether providing an invalid interval results in an error. #[test] fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let mat_0 = MatPolyOverZ::sample_uniform(3, 3, 0, &lb_0, &upper_bound); - let mat_1 = MatPolyOverZ::sample_uniform(4, 1, 0, &lb_1, &upper_bound); - let mat_2 = MatPolyOverZ::sample_uniform(1, 5, 0, &lb_2, &upper_bound); + for seeded in [false, true] { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let mat_0 = MatPolyOverZ::sample_uniform_optionally_seeded( + 3, + 3, + 0, + &lb_0, + &upper_bound, + seeded.then(test_seed), + ); + let mat_1 = MatPolyOverZ::sample_uniform_optionally_seeded( + 4, + 1, + 0, + &lb_1, + &upper_bound, + seeded.then(test_seed), + ); + let mat_2 = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 5, + 0, + &lb_2, + &upper_bound, + seeded.then(test_seed), + ); - assert!(mat_0.is_err()); - assert!(mat_1.is_err()); - assert!(mat_2.is_err()); + assert!(mat_0.is_err()); + assert!(mat_1.is_err()); + assert!(mat_2.is_err()); + } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let lower_bound = Z::from(0); - let upper_bound = Z::from(15); + for seeded in [false, true] { + let lower_bound = Z::from(0); + let upper_bound = Z::from(15); - let res_0 = MatPolyOverZ::sample_uniform(1, 1, -1, &lower_bound, &upper_bound); - let res_1 = MatPolyOverZ::sample_uniform(1, 1, i64::MIN, &lower_bound, &upper_bound); + let res_0 = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + -1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); + let res_1 = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + i64::MIN, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0u8, 0u16, 7u8); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0u16, 0u32, 7u16); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0u32, 0u64, 7u32); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0u64, 0i8, 7u64); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0i8, 0i16, 7i8); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0i16, 0i32, 7i16); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0i32, 0i64, 7i32); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0i64, &Z::ZERO, 7i64); - let _ = MatPolyOverZ::sample_uniform(1, 1, 0, 0u8, &modulus); - let _ = MatPolyOverZ::sample_uniform(1, 1, Z::ZERO, 0, &z); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0u8, + 0u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0u16, + 0u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0u32, + 0u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0u64, + 0i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0i8, + 0i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0i16, + 0i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0i32, + 0i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0i64, + &Z::ZERO, + 7i64, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + 0, + 0u8, + &modulus, + seeded.then(test_seed), + ); + let _ = MatPolyOverZ::sample_uniform_optionally_seeded( + 1, + 1, + Z::ZERO, + 0, + &z, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u8, 0u16, 7u8, test_seed()); + } else { + let _ = MatPolyOverZ::sample_uniform(1, 1, 0u8, 0u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let mat_0 = MatPolyOverZ::sample_uniform(3, 3, 0, &lower_bound, &upper_bound).unwrap(); - let mat_1 = MatPolyOverZ::sample_uniform(4, 1, 0, &lower_bound, &upper_bound).unwrap(); - let mat_2 = MatPolyOverZ::sample_uniform(1, 5, 0, &lower_bound, &upper_bound).unwrap(); - let mat_3 = MatPolyOverZ::sample_uniform(15, 20, 0, &lower_bound, &upper_bound).unwrap(); + for seeded in [false, true] { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { - use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry}; - use crate::utils::sample::test_seed; - use crate::{ - integer::{MatPolyOverZ, Z}, - integer_mod_q::Modulus, - }; - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let matrix = MatPolyOverZ::sample_uniform_seeded( - 1, - 1, + let mat_0 = MatPolyOverZ::sample_uniform_optionally_seeded( + 3, + 3, 0, &lower_bound, &upper_bound, - test_seed(), + seeded.then(test_seed), ) .unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(lower_bound <= coeff); - assert!(coeff < upper_bound); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let matrix = MatPolyOverZ::sample_uniform_seeded( + let mat_1 = MatPolyOverZ::sample_uniform_optionally_seeded( + 4, 1, + 0, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatPolyOverZ::sample_uniform_optionally_seeded( 1, + 5, 0, &lower_bound, &upper_bound, - test_seed(), + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatPolyOverZ::sample_uniform_optionally_seeded( + 15, + 20, + 0, + &lower_bound, + &upper_bound, + seeded.then(test_seed), ) .unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(lower_bound <= coeff); - assert!(coeff < upper_bound); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let matrix = - MatPolyOverZ::sample_uniform_seeded(1, 1, degree, 1, 15, test_seed()).unwrap(); - let poly = matrix.get_entry(0, 0).unwrap(); - assert_eq!(degree, poly.get_degree()); + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); } } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too large for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let _ = - MatPolyOverZ::sample_uniform_seeded(0, 3, 1, &lower_bound, &upper_bound, test_seed()); - } - - /// Checks whether providing an invalid interval results in an error. - #[test] - fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let mat_0 = MatPolyOverZ::sample_uniform_seeded(3, 3, 0, &lb_0, &upper_bound, test_seed()); - let mat_1 = MatPolyOverZ::sample_uniform_seeded(4, 1, 0, &lb_1, &upper_bound, test_seed()); - let mat_2 = MatPolyOverZ::sample_uniform_seeded(1, 5, 0, &lb_2, &upper_bound, test_seed()); - - assert!(mat_0.is_err()); - assert!(mat_1.is_err()); - assert!(mat_2.is_err()); - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let lower_bound = Z::from(0); - let upper_bound = Z::from(15); - - let res_0 = - MatPolyOverZ::sample_uniform_seeded(1, 1, -1, &lower_bound, &upper_bound, test_seed()); - let res_1 = MatPolyOverZ::sample_uniform_seeded( - 1, - 1, - i64::MIN, - &lower_bound, - &upper_bound, - test_seed(), - ); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u8, 0u16, 7u8, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u16, 0u32, 7u16, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u32, 0u64, 7u32, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0u64, 0i8, 7u64, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i8, 0i16, 7i8, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i16, 0i32, 7i16, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i32, 0i64, 7i32, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0i64, &Z::ZERO, 7i64, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, 0, 0u8, &modulus, test_seed()); - let _ = MatPolyOverZ::sample_uniform_seeded(1, 1, Z::ZERO, 0, &z, test_seed()); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let mat_0 = - MatPolyOverZ::sample_uniform_seeded(3, 3, 0, &lower_bound, &upper_bound, test_seed()) - .unwrap(); - let mat_1 = - MatPolyOverZ::sample_uniform_seeded(4, 1, 0, &lower_bound, &upper_bound, test_seed()) - .unwrap(); - let mat_2 = - MatPolyOverZ::sample_uniform_seeded(1, 5, 0, &lower_bound, &upper_bound, test_seed()) - .unwrap(); - let mat_3 = - MatPolyOverZ::sample_uniform_seeded(15, 20, 0, &lower_bound, &upper_bound, test_seed()) - .unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } - /// Checks whether the same seed results in the same sample. #[test] fn same_seed_same_sample() { @@ -416,4 +442,16 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatPolyOverZ; + let sample = MatPolyOverZ::sample_uniform_seeded(3, 3, 5, 17, 26, [42; 32]).unwrap(); + + assert_eq!( + "[[6 18 19 20 20 18 22, 6 18 25 25 21 20 19, 6 20 20 23 22 22 24],[6 23 19 22 17 23 23, 6 19 20 19 24 21 22, 6 17 22 23 24 25 25],[6 22 24 24 17 21 24, 6 20 17 25 19 17 20, 6 24 18 22 17 24 18]]", + sample.to_string() + ); + } } diff --git a/src/integer/mat_z/sample/binomial.rs b/src/integer/mat_z/sample/binomial.rs index f26e2305..693ba8e4 100644 --- a/src/integer/mat_z/sample/binomial.rs +++ b/src/integer/mat_z/sample/binomial.rs @@ -146,72 +146,6 @@ impl MatZ { mod test_sample_binomial { use super::{MatZ, Q, Z}; use crate::traits::{MatrixDimensions, MatrixGetEntry}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - for _ in 0..8 { - let matrix = MatZ::sample_binomial(1, 1, 2, 0.5).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample <= 2); - } - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let _ = MatZ::sample_binomial(0, 3, 1, 0.5); - } - - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = MatZ::sample_binomial(1, 1, 1u16, 7u8); - let _ = MatZ::sample_binomial(1, 1, 1u32, 7u16); - let _ = MatZ::sample_binomial(1, 1, 1u64, 7u32); - let _ = MatZ::sample_binomial(1, 1, 1i8, 7u64); - let _ = MatZ::sample_binomial(1, 1, 1i16, 7i8); - let _ = MatZ::sample_binomial(1, 1, 1i32, 7i16); - let _ = MatZ::sample_binomial(1, 1, 1i64, 7i32); - let _ = MatZ::sample_binomial(1, 1, Z::ONE, 7i64); - let _ = MatZ::sample_binomial(1, 1, 1u8, 0.5f32); - let _ = MatZ::sample_binomial(1, 1, 1u8, 0.5f64); - let _ = MatZ::sample_binomial(1, 1, 1, Q::from((1, 2))); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = MatZ::sample_binomial(3, 3, 1, 0.5).unwrap(); - let mat_1 = MatZ::sample_binomial(4, 1, 1, 0.5).unwrap(); - let mat_2 = MatZ::sample_binomial(1, 5, 1, 0.5).unwrap(); - let mat_3 = MatZ::sample_binomial(15, 20, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{MatZ, Q, Z}; - use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, @@ -221,20 +155,29 @@ mod test_sample_binomial_seeded { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - for _ in 0..8 { - let matrix = MatZ::sample_binomial_seeded(1, 1, 2, 0.5, test_seed()).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample <= 2); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = + MatZ::sample_binomial_optionally_seeded(1, 1, 2, 0.5, seeded.then(test_seed)) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample <= 2); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatZ::sample_binomial_seeded(0, 3, 1, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = + MatZ::sample_binomial_optionally_seeded(0, 3, 1, 0.5, seeded.then(test_seed)); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -242,36 +185,71 @@ mod test_sample_binomial_seeded { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = MatZ::sample_binomial_seeded(1, 1, 1u16, 7u8, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1u32, 7u16, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1u64, 7u32, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1i8, 7u64, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1i16, 7i8, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1i32, 7i16, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1i64, 7i32, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, Z::ONE, 7i64, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1u8, 0.5f32, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1u8, 0.5f64, test_seed()); - let _ = MatZ::sample_binomial_seeded(1, 1, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1u16, 7u8, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1u32, 7u16, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1u64, 7u32, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1i8, 7u64, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1i16, 7i8, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1i32, 7i16, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1i64, 7i32, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, Z::ONE, 7i64, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1u8, 0.5f32, seeded.then(test_seed)); + let _ = + MatZ::sample_binomial_optionally_seeded(1, 1, 1u8, 0.5f64, seeded.then(test_seed)); + let _ = MatZ::sample_binomial_optionally_seeded( + 1, + 1, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_binomial_seeded(1, 1, 1u16, 7u8, test_seed()); + } else { + let _ = MatZ::sample_binomial(1, 1, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = MatZ::sample_binomial_seeded(3, 3, 1, 0.5, test_seed()).unwrap(); - let mat_1 = MatZ::sample_binomial_seeded(4, 1, 1, 0.5, test_seed()).unwrap(); - let mat_2 = MatZ::sample_binomial_seeded(1, 5, 1, 0.5, test_seed()).unwrap(); - let mat_3 = MatZ::sample_binomial_seeded(15, 20, 1, 0.5, test_seed()).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let mat_0 = + MatZ::sample_binomial_optionally_seeded(3, 3, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_1 = + MatZ::sample_binomial_optionally_seeded(4, 1, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_2 = + MatZ::sample_binomial_optionally_seeded(1, 5, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_3 = + MatZ::sample_binomial_optionally_seeded(15, 20, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -283,76 +261,19 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_binomial_with_offset { - use super::{MatZ, Q, Z}; - use crate::traits::{MatrixDimensions, MatrixGetEntry}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - for _ in 0..8 { - let matrix = MatZ::sample_binomial_with_offset(1, 1, -1, 2, 0.5).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(Z::MINUS_ONE <= sample); - assert!(sample <= Z::ONE); - } - } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn false_size() { - let _ = MatZ::sample_binomial_with_offset(0, 3, 0, 1, 0.5); - } - - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = MatZ::sample_binomial_with_offset(1, 1, 0u8, 1u16, 7u8); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0u16, 1u32, 7u16); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0u32, 1u64, 7u32); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0u64, 1i8, 7u64); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0i8, 1i16, 7i8); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0i16, 1i32, 7i16); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0i64, 1i64, 7i32); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0, Z::ONE, 7i64); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1u8, 0.5f32); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1u8, 0.5f64); - let _ = MatZ::sample_binomial_with_offset(1, 1, 0, 1, Q::from((1, 2))); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = MatZ::sample_binomial_with_offset(3, 3, 0, 1, 0.5).unwrap(); - let mat_1 = MatZ::sample_binomial_with_offset(4, 1, 0, 1, 0.5).unwrap(); - let mat_2 = MatZ::sample_binomial_with_offset(1, 5, 0, 1, 0.5).unwrap(); - let mat_3 = MatZ::sample_binomial_with_offset(15, 20, 0, 1, 0.5).unwrap(); + fn known_answer() { + use crate::integer::MatZ; + let sample = MatZ::sample_binomial_seeded(2, 2, 5, 0.5, [42; 32]).unwrap(); - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + assert_eq!("[[2, 4],[2, 5]]", sample.to_string()); } } #[cfg(test)] -mod test_sample_binomial_with_offset_seeded { +mod test_sample_binomial_with_offset { use super::{MatZ, Q, Z}; use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; @@ -364,21 +285,41 @@ mod test_sample_binomial_with_offset_seeded { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - for _ in 0..8 { - let matrix = - MatZ::sample_binomial_with_offset_seeded(1, 1, -1, 2, 0.5, test_seed()).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(Z::MINUS_ONE <= sample); - assert!(sample <= Z::ONE); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + -1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(Z::MINUS_ONE <= sample); + assert!(sample <= Z::ONE); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatZ::sample_binomial_with_offset_seeded(0, 3, 0, 1, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 0, + 3, + 0, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -386,37 +327,156 @@ mod test_sample_binomial_with_offset_seeded { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u16, 1u32, 7u16, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u32, 1u64, 7u32, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u64, 1i8, 7u64, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i8, 1i16, 7i8, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i16, 1i32, 7i16, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0i64, 1i64, 7i32, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, Z::ONE, 7i64, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1u8, 0.5f32, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1u8, 0.5f64, test_seed()); - let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i64, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_binomial_with_offset_seeded(1, 1, 0u8, 1u16, 7u8, test_seed()); + } else { + let _ = MatZ::sample_binomial_with_offset(1, 1, 0u8, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = MatZ::sample_binomial_with_offset_seeded(3, 3, 0, 1, 0.5, test_seed()).unwrap(); - let mat_1 = MatZ::sample_binomial_with_offset_seeded(4, 1, 0, 1, 0.5, test_seed()).unwrap(); - let mat_2 = MatZ::sample_binomial_with_offset_seeded(1, 5, 0, 1, 0.5, test_seed()).unwrap(); - let mat_3 = - MatZ::sample_binomial_with_offset_seeded(15, 20, 0, 1, 0.5, test_seed()).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let mat_0 = MatZ::sample_binomial_with_offset_optionally_seeded( + 3, + 3, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatZ::sample_binomial_with_offset_optionally_seeded( + 4, + 1, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatZ::sample_binomial_with_offset_optionally_seeded( + 1, + 5, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatZ::sample_binomial_with_offset_optionally_seeded( + 15, + 20, + 0, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -430,4 +490,13 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatZ; + let sample = MatZ::sample_binomial_with_offset_seeded(2, 2, -1, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!("[[-1, 1],[0, 1]]", sample.to_string()); + } } diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index 41e983c8..2ac7cd67 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -299,42 +299,6 @@ impl MatZ { #[cfg(test)] mod test_sample_discrete_gauss { - use crate::{ - integer::{MatZ, Z}, - rational::Q, - }; - - // This function only allows for a broader availability, which is tested here. - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let n = Z::from(1024); - let center = Q::ZERO; - let s = Q::ONE; - - let _ = MatZ::sample_discrete_gauss(2u64, 3i8, ¢er, 1u16); - let _ = MatZ::sample_discrete_gauss(3u8, 2i16, ¢er, 1u8); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u32); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1u64); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i32); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i16); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i8); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1i64); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &n); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, 1, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, 2.25, &s); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 1.25f64); - let _ = MatZ::sample_discrete_gauss(1, 1, ¢er, 15.75f32); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { use crate::utils::sample::test_seed; use crate::{ integer::{MatZ, Z}, @@ -348,25 +312,119 @@ mod test_sample_discrete_gauss_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let n = Z::from(1024); - let center = Q::ZERO; - let s = Q::ONE; - - let _ = MatZ::sample_discrete_gauss_seeded(2u64, 3i8, ¢er, 1u16, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(3u8, 2i16, ¢er, 1u8, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1u32, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1u64, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i64, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i32, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i16, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i8, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1i64, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, &n, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, &s, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, 1, &s, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, 2.25, &s, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 1.25f64, test_seed()); - let _ = MatZ::sample_discrete_gauss_seeded(1, 1, ¢er, 15.75f32, test_seed()); + for seeded in [false, true] { + let n = Z::from(1024); + let center = Q::ZERO; + let s = Q::ONE; + + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 2u64, + 3i8, + ¢er, + 1u16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 3u8, + 2i16, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = + MatZ::sample_discrete_gauss_optionally_seeded(1, 1, 1, &s, seeded.then(test_seed)); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 2.25, + &s, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_discrete_gauss_optionally_seeded( + 1, + 1, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_discrete_gauss_seeded(2u64, 3i8, ¢er, 1u16, test_seed()); + } else { + let _ = MatZ::sample_discrete_gauss(2u64, 3i8, ¢er, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -378,72 +436,19 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } -} -#[cfg(test)] -mod test_sample_d { - use crate::{ - integer::{MatZ, Z}, - rational::{MatQ, Q}, - }; - - // Appropriate inputs were tested in utils and thus omitted here. - // This function only allows for a broader availability, which is tested here. - - /// Checks whether `sample_d` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn availability() { - let basis = MatZ::identity(5, 5); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - - let _ = MatZ::sample_d(&basis, ¢er, 1u16); - let _ = MatZ::sample_d(&basis, ¢er, 1u8); - let _ = MatZ::sample_d(&basis, ¢er, 1u32); - let _ = MatZ::sample_d(&basis, ¢er, 1u64); - let _ = MatZ::sample_d(&basis, ¢er, 1i64); - let _ = MatZ::sample_d(&basis, ¢er, 1i32); - let _ = MatZ::sample_d(&basis, ¢er, 1i16); - let _ = MatZ::sample_d(&basis, ¢er, 1i8); - let _ = MatZ::sample_d(&basis, ¢er, 1i64); - let _ = MatZ::sample_d(&basis, ¢er, &n); - let _ = MatZ::sample_d(&basis, ¢er, &s); - let _ = MatZ::sample_d(&basis, ¢er, 1.25f64); - let _ = MatZ::sample_d(&basis, ¢er, 15.75f32); - } + fn known_answer() { + use crate::integer::MatZ; + let sample = MatZ::sample_discrete_gauss_seeded(3, 1, 0, 1.25f32, [42; 32]).unwrap(); - /// Checks whether `sample_d_precomputed_gso` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability_prec_gso() { - let basis = MatZ::identity(5, 5); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - let basis_gso = MatQ::from(&basis); - - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u8); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u32); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i32); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i16); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i8); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &n); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &s); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f64); - let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32); + assert_eq!("[[1],[-1],[0]]", sample.to_string()); } } #[cfg(test)] -mod test_sample_d_seeded { +mod test_sample_d { use crate::utils::sample::test_seed; use crate::{ integer::{MatZ, Z}, @@ -458,24 +463,35 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let basis = MatZ::identity(5, 5); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u8, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u32, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u64, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i32, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i16, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i8, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, &n, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, &s, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 1.25f64, test_seed()); - let _ = MatZ::sample_d_seeded(&basis, ¢er, 15.75f32, test_seed()); + for seeded in [false, true] { + let basis = MatZ::identity(5, 5); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u16, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u8, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u32, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1u64, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i32, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i16, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i8, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, &n, seeded.then(test_seed)); + let _ = MatZ::sample_d_optionally_seeded(&basis, ¢er, &s, seeded.then(test_seed)); + let _ = + MatZ::sample_d_optionally_seeded(&basis, ¢er, 1.25f64, seeded.then(test_seed)); + let _ = + MatZ::sample_d_optionally_seeded(&basis, ¢er, 15.75f32, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); + } else { + let _ = MatZ::sample_d(&basis, ¢er, 1u16); + } + } } /// Checks whether `sample_d_precomputed_gso` is available for all types @@ -483,46 +499,118 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability_prec_gso() { - let basis = MatZ::identity(5, 5); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - let basis_gso = MatQ::from(&basis); - - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u16, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u8, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u32, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u64, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i32, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i16, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i8, test_seed()); - let _ = - MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); - let _ = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &n, test_seed()); - let _ = MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &s, test_seed()); - let _ = MatZ::sample_d_precomputed_gso_seeded( - &basis, - &basis_gso, - ¢er, - 1.25f64, - test_seed(), - ); - let _ = MatZ::sample_d_precomputed_gso_seeded( - &basis, - &basis_gso, - ¢er, - 15.75f32, - test_seed(), - ); + for seeded in [false, true] { + let basis = MatZ::identity(5, 5); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + let basis_gso = MatQ::from(&basis); + + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatZ::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 1u16, + test_seed(), + ); + } else { + let _ = MatZ::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -553,78 +641,35 @@ mod test_sample_d_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_d_common_non_spherical { - use crate::{ - integer::{MatZ, Z}, - rational::{MatQ, Q}, - traits::{MatrixDimensions, Pow}, - }; - use std::str::FromStr; - /// Checks whether `sample_d_common_non_spherical` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - /// or [`Into`], i.e. MatQ, MatZ - #[test] - fn availability() { - let r = Q::from(8); - let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let covariance_matrix = - (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition(); - - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).unwrap(); - - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u16).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_u64).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i8).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i16).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8_i64).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Q::from(8)).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, Z::from(8)).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f32).unwrap(); - let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8f64).unwrap(); - } - - /// Checks whether the function panics if a non-square matrix is provided. - /// anymore + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn not_square() { - let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); + fn known_answer_d() { + use crate::{integer::MatZ, rational::MatQ}; + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let sample = MatZ::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); - assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, 8).is_err()); + assert_eq!("[[0],[-1],[0],[0],[0]]", sample.to_string()); } - /// Checks whether the function returns an error if `r` is too small. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn too_small_parameters() { - let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); - - assert!(MatZ::sample_d_common_non_spherical(&covariance_matrix, -1).is_err()); - } - - /// Checks whether the dimension of the output matches the provided covariance matrix - #[test] - fn correct_dimensions() { - let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); - - let sample_1 = MatZ::sample_d_common_non_spherical(&covariance_matrix_1, 8).unwrap(); - let sample_2 = MatZ::sample_d_common_non_spherical(&covariance_matrix_2, 8).unwrap(); + fn known_answer_d_precomputed_gso() { + use crate::{integer::MatZ, rational::MatQ}; + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let basis_gso = MatQ::from(&basis).gso(); + let sample = + MatZ::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); - assert_eq!(2, sample_1.get_num_rows()); - assert!(sample_1.is_column_vector()); - assert_eq!(3, sample_2.get_num_rows()); - assert!(sample_2.is_column_vector()); + assert_eq!("[[0],[-1],[0],[0],[0]]", sample.to_string()); } } #[cfg(test)] -mod test_sample_d_common_non_spherical_seeded { +mod test_sample_d_common_non_spherical { use crate::utils::sample::test_seed; use crate::{ integer::{MatZ, Z}, @@ -639,79 +684,156 @@ mod test_sample_d_common_non_spherical_seeded { /// or [`Into`], i.e. MatQ, MatZ #[test] fn availability() { - let r = Q::from(8); - let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let covariance_matrix = - (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)).cholesky_decomposition(); - - let _ = - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed()).unwrap(); + for seeded in [false, true] { + let r = Q::from(8); + let covariance_matrix = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix = (covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2)) + .cholesky_decomposition(); + + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8, + seeded.then(test_seed), + ) + .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u16, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_u16, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u32, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_u32, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_u64, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_u64, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i8, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_i8, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i16, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_i16, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i32, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_i32, + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8_i64, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8_i64, + seeded.then(test_seed), + ) .unwrap(); - let _ = - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, Q::from(8), test_seed()) - .unwrap(); - let _ = - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, Z::from(8), test_seed()) - .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8f32, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + Q::from(8), + seeded.then(test_seed), + ) .unwrap(); - let _ = MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8f64, test_seed()) + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + Z::from(8), + seeded.then(test_seed), + ) .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8f32, + seeded.then(test_seed), + ) + .unwrap(); + let _ = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8f64, + seeded.then(test_seed), + ) + .unwrap(); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = + MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed()); + } else { + let _ = MatZ::sample_d_common_non_spherical(&covariance_matrix, 8); + } + } } /// Checks whether the function panics if a non-square matrix is provided. /// anymore #[test] fn not_square() { - let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); - - assert!( - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, 8, test_seed()).is_err() - ); + for seeded in [false, true] { + let covariance_matrix = MatQ::from_str("[[100,1,1],[1,64,2]]").unwrap(); + + assert!( + MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + 8, + seeded.then(test_seed) + ) + .is_err() + ); + } } /// Checks whether the function returns an error if `r` is too small. #[test] fn too_small_parameters() { - let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); - - assert!( - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix, -1, test_seed()) + for seeded in [false, true] { + let covariance_matrix = MatQ::from_str("[[100, 1],[1, 65]]").unwrap(); + + assert!( + MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix, + -1, + seeded.then(test_seed) + ) .is_err() - ); + ); + } } /// Checks whether the dimension of the output matches the provided covariance matrix #[test] fn correct_dimensions() { - let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); - let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); - - let sample_1 = - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix_1, 8, test_seed()) - .unwrap(); - let sample_2 = - MatZ::sample_d_common_non_spherical_seeded(&covariance_matrix_2, 8, test_seed()) - .unwrap(); + for seeded in [false, true] { + let covariance_matrix_1 = MatQ::from_str("[[100,1],[1,65]]").unwrap(); + let covariance_matrix_2 = MatQ::from_str("[[100,1,0],[1,65,0],[0,0,10000]]").unwrap(); + + let sample_1 = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix_1, + 8, + seeded.then(test_seed), + ) + .unwrap(); + let sample_2 = MatZ::sample_d_common_non_spherical_optionally_seeded( + &covariance_matrix_2, + 8, + seeded.then(test_seed), + ) + .unwrap(); - assert_eq!(2, sample_1.get_num_rows()); - assert!(sample_1.is_column_vector()); - assert_eq!(3, sample_2.get_num_rows()); - assert!(sample_2.is_column_vector()); + assert_eq!(2, sample_1.get_num_rows()); + assert!(sample_1.is_column_vector()); + assert_eq!(3, sample_2.get_num_rows()); + assert!(sample_2.is_column_vector()); + } } /// Checks whether the same seed results in the same sample. @@ -739,4 +861,24 @@ mod test_sample_d_common_non_spherical_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatZ; + use crate::rational::{MatQ, Q}; + use crate::traits::Pow; + use std::str::FromStr; + let covariance_matrix = MatQ::from_str("[[100,1],[1,17]]").unwrap(); + let r = Q::from(4); + let sigma_sqrt = covariance_matrix - r.pow(2).unwrap() * MatQ::identity(2, 2); + let sample = MatZ::sample_d_common_non_spherical_seeded( + &sigma_sqrt.cholesky_decomposition(), + &r, + [42; 32], + ) + .unwrap(); + + assert_eq!("[[-19],[1]]", sample.to_string()); + } } diff --git a/src/integer/mat_z/sample/uniform.rs b/src/integer/mat_z/sample/uniform.rs index b60dc068..70d84e1f 100644 --- a/src/integer/mat_z/sample/uniform.rs +++ b/src/integer/mat_z/sample/uniform.rs @@ -95,110 +95,6 @@ impl MatZ { #[cfg(test)] mod test_sample_uniform { - use crate::traits::{MatrixDimensions, MatrixGetEntry}; - use crate::{ - integer::{MatZ, Z}, - integer_mod_q::Modulus, - }; - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let matrix = MatZ::sample_uniform(1, 1, &lower_bound, &upper_bound).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let matrix = MatZ::sample_uniform(1, 1, &lower_bound, &upper_bound).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too large for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let _ = MatZ::sample_uniform(0, 3, &lower_bound, &upper_bound); - } - - /// Checks whether providing an invalid interval results in an error. - #[test] - fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let mat_0 = MatZ::sample_uniform(3, 3, &lb_0, &upper_bound); - let mat_1 = MatZ::sample_uniform(4, 1, &lb_1, &upper_bound); - let mat_2 = MatZ::sample_uniform(1, 5, &lb_2, &upper_bound); - - assert!(mat_0.is_err()); - assert!(mat_1.is_err()); - assert!(mat_2.is_err()); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = MatZ::sample_uniform(1, 1, 0u16, 7u8); - let _ = MatZ::sample_uniform(1, 1, 0u32, 7u16); - let _ = MatZ::sample_uniform(1, 1, 0u64, 7u32); - let _ = MatZ::sample_uniform(1, 1, 0i8, 7u64); - let _ = MatZ::sample_uniform(1, 1, 0i16, 7i8); - let _ = MatZ::sample_uniform(1, 1, 0i32, 7i16); - let _ = MatZ::sample_uniform(1, 1, 0i64, 7i32); - let _ = MatZ::sample_uniform(1, 1, &Z::ZERO, 7i64); - let _ = MatZ::sample_uniform(1, 1, 0u8, &modulus); - let _ = MatZ::sample_uniform(1, 1, 0, &z); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let mat_0 = MatZ::sample_uniform(3, 3, &lower_bound, &upper_bound).unwrap(); - let mat_1 = MatZ::sample_uniform(4, 1, &lower_bound, &upper_bound).unwrap(); - let mat_2 = MatZ::sample_uniform(1, 5, &lower_bound, &upper_bound).unwrap(); - let mat_3 = MatZ::sample_uniform(15, 20, &lower_bound, &upper_bound).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; use crate::{ @@ -209,102 +105,194 @@ mod test_sample_uniform_seeded { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let matrix = - MatZ::sample_uniform_seeded(1, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let matrix = MatZ::sample_uniform_optionally_seeded( + 1, + 1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let matrix = - MatZ::sample_uniform_seeded(1, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); - let sample = matrix.get_entry(0, 0).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let matrix = MatZ::sample_uniform_optionally_seeded( + 1, + 1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let sample = matrix.get_entry(0, 0).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too large for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let _ = MatZ::sample_uniform_seeded(0, 3, &lower_bound, &upper_bound, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let _ = MatZ::sample_uniform_optionally_seeded( + 0, + 3, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether providing an invalid interval results in an error. #[test] fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let mat_0 = MatZ::sample_uniform_seeded(3, 3, &lb_0, &upper_bound, test_seed()); - let mat_1 = MatZ::sample_uniform_seeded(4, 1, &lb_1, &upper_bound, test_seed()); - let mat_2 = MatZ::sample_uniform_seeded(1, 5, &lb_2, &upper_bound, test_seed()); - - assert!(mat_0.is_err()); - assert!(mat_1.is_err()); - assert!(mat_2.is_err()); + for seeded in [false, true] { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let mat_0 = MatZ::sample_uniform_optionally_seeded( + 3, + 3, + &lb_0, + &upper_bound, + seeded.then(test_seed), + ); + let mat_1 = MatZ::sample_uniform_optionally_seeded( + 4, + 1, + &lb_1, + &upper_bound, + seeded.then(test_seed), + ); + let mat_2 = MatZ::sample_uniform_optionally_seeded( + 1, + 5, + &lb_2, + &upper_bound, + seeded.then(test_seed), + ); + + assert!(mat_0.is_err()); + assert!(mat_1.is_err()); + assert!(mat_2.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = MatZ::sample_uniform_seeded(1, 1, 0u16, 7u8, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0u32, 7u16, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0u64, 7u32, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0i8, 7u64, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0i16, 7i8, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0i32, 7i16, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0i64, 7i32, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, &Z::ZERO, 7i64, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0u8, &modulus, test_seed()); - let _ = MatZ::sample_uniform_seeded(1, 1, 0, &z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = MatZ::sample_uniform_optionally_seeded(1, 1, 0u16, 7u8, seeded.then(test_seed)); + let _ = + MatZ::sample_uniform_optionally_seeded(1, 1, 0u32, 7u16, seeded.then(test_seed)); + let _ = + MatZ::sample_uniform_optionally_seeded(1, 1, 0u64, 7u32, seeded.then(test_seed)); + let _ = MatZ::sample_uniform_optionally_seeded(1, 1, 0i8, 7u64, seeded.then(test_seed)); + let _ = MatZ::sample_uniform_optionally_seeded(1, 1, 0i16, 7i8, seeded.then(test_seed)); + let _ = + MatZ::sample_uniform_optionally_seeded(1, 1, 0i32, 7i16, seeded.then(test_seed)); + let _ = + MatZ::sample_uniform_optionally_seeded(1, 1, 0i64, 7i32, seeded.then(test_seed)); + let _ = MatZ::sample_uniform_optionally_seeded( + 1, + 1, + &Z::ZERO, + 7i64, + seeded.then(test_seed), + ); + let _ = + MatZ::sample_uniform_optionally_seeded(1, 1, 0u8, &modulus, seeded.then(test_seed)); + let _ = MatZ::sample_uniform_optionally_seeded(1, 1, 0, &z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZ::sample_uniform_seeded(1, 1, 0u16, 7u8, test_seed()); + } else { + let _ = MatZ::sample_uniform(1, 1, 0u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let lower_bound = Z::from(-15); - let upper_bound = Z::from(15); - - let mat_0 = - MatZ::sample_uniform_seeded(3, 3, &lower_bound, &upper_bound, test_seed()).unwrap(); - let mat_1 = - MatZ::sample_uniform_seeded(4, 1, &lower_bound, &upper_bound, test_seed()).unwrap(); - let mat_2 = - MatZ::sample_uniform_seeded(1, 5, &lower_bound, &upper_bound, test_seed()).unwrap(); - let mat_3 = - MatZ::sample_uniform_seeded(15, 20, &lower_bound, &upper_bound, test_seed()).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let lower_bound = Z::from(-15); + let upper_bound = Z::from(15); + + let mat_0 = MatZ::sample_uniform_optionally_seeded( + 3, + 3, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatZ::sample_uniform_optionally_seeded( + 4, + 1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatZ::sample_uniform_optionally_seeded( + 1, + 5, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatZ::sample_uniform_optionally_seeded( + 15, + 20, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -316,4 +304,16 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::MatZ; + let sample = MatZ::sample_uniform_seeded(3, 3, 17, 26, [42; 32]).unwrap(); + + assert_eq!( + "[[19, 20, 17],[19, 20, 22],[24, 24, 19]]", + sample.to_string() + ); + } } diff --git a/src/integer/poly_over_z/sample/binomial.rs b/src/integer/poly_over_z/sample/binomial.rs index e94536cf..17ee901e 100644 --- a/src/integer/poly_over_z/sample/binomial.rs +++ b/src/integer/poly_over_z/sample/binomial.rs @@ -142,6 +142,7 @@ impl PolyOverZ { mod test_sample_binomial { use super::{PolyOverZ, Q, Z}; use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, // whether the correct interval is kept, and if the errors are thrown correctly, @@ -150,37 +151,57 @@ mod test_sample_binomial { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZ::sample_binomial(0, 2, 0.5).unwrap(); - let sample = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample <= 2); + for seeded in [false, true] { + for _ in 0..8 { + let poly = + PolyOverZ::sample_binomial_optionally_seeded(0, 2, 0.5, seeded.then(test_seed)) + .unwrap(); + let sample = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample <= 2); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_binomial(degree, 256, 0.99999).unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_binomial_optionally_seeded( + degree, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "This test can fail with probability close to 0." + ); + } } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = PolyOverZ::sample_binomial(-1, 2, 0.5); - let res_1 = PolyOverZ::sample_binomial(i64::MIN, 2, 0.5); + for seeded in [false, true] { + let res_0 = + PolyOverZ::sample_binomial_optionally_seeded(-1, 2, 0.5, seeded.then(test_seed)); + let res_1 = PolyOverZ::sample_binomial_optionally_seeded( + i64::MIN, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -188,82 +209,53 @@ mod test_sample_binomial { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = PolyOverZ::sample_binomial(1, 1u16, 7u8); - let _ = PolyOverZ::sample_binomial(1, 1u32, 7u16); - let _ = PolyOverZ::sample_binomial(1, 1u64, 7u32); - let _ = PolyOverZ::sample_binomial(1, 1i8, 7u64); - let _ = PolyOverZ::sample_binomial(1, 1i16, 7i8); - let _ = PolyOverZ::sample_binomial(1, 1i32, 7i16); - let _ = PolyOverZ::sample_binomial(1, 1i64, 7i32); - let _ = PolyOverZ::sample_binomial(1, Z::ONE, 7i64); - let _ = PolyOverZ::sample_binomial(1, 1u8, 0.5f32); - let _ = PolyOverZ::sample_binomial(1, 1u8, 0.5f64); - let _ = PolyOverZ::sample_binomial(1, 1, Q::from((1, 2))); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{PolyOverZ, Q, Z}; - use crate::traits::GetCoefficient; - use crate::utils::sample::test_seed; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZ::sample_binomial_seeded(0, 2, 0.5, test_seed()).unwrap(); - let sample = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample <= 2); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_binomial_seeded(degree, 256, 0.99999, test_seed()).unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "This test can fail with probability close to 0." + for seeded in [false, true] { + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1u16, 7u8, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1u32, 7u16, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1u64, 7u32, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1i8, 7u64, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1i16, 7i8, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1i32, 7i16, seeded.then(test_seed)); + let _ = + PolyOverZ::sample_binomial_optionally_seeded(1, 1i64, 7i32, seeded.then(test_seed)); + let _ = PolyOverZ::sample_binomial_optionally_seeded( + 1, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_optionally_seeded( + 1, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_optionally_seeded( + 1, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_optionally_seeded( + 1, + 1, + Q::from((1, 2)), + seeded.then(test_seed), ); - } - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let res_0 = PolyOverZ::sample_binomial_seeded(-1, 2, 0.5, test_seed()); - let res_1 = PolyOverZ::sample_binomial_seeded(i64::MIN, 2, 0.5, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = PolyOverZ::sample_binomial_seeded(1, 1u16, 7u8, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1u32, 7u16, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1u64, 7u32, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1i8, 7u64, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1i16, 7i8, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1i32, 7i16, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1i64, 7i32, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, Z::ONE, 7i64, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1u8, 0.5f32, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1u8, 0.5f64, test_seed()); - let _ = PolyOverZ::sample_binomial_seeded(1, 1, Q::from((1, 2)), test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZ::sample_binomial_seeded(1, 1u16, 7u8, test_seed()); + } else { + let _ = PolyOverZ::sample_binomial(1, 1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -275,70 +267,19 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } -} -#[cfg(test)] -mod test_sample_binomial_with_offset { - use super::{PolyOverZ, Q, Z}; - use crate::traits::GetCoefficient; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZ::sample_binomial_with_offset(0, -1, 2, 0.5).unwrap(); - let sample = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample); - assert!(sample <= Z::ONE); - } - } - - /// Checks whether the number of coefficients is correct. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_binomial_with_offset(degree, 1, 2, 0.5).unwrap(); - - assert_eq!(degree, res.get_degree()); - } - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let res_0 = PolyOverZ::sample_binomial_with_offset(-1, -1, 2, 0.5); - let res_1 = PolyOverZ::sample_binomial_with_offset(i64::MIN, -1, 2, 0.5); + fn known_answer() { + use crate::integer::PolyOverZ; + let sample = PolyOverZ::sample_binomial_seeded(2, 2, 0.5, [42; 32]).unwrap(); - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u16, 7u8); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u32, 7u16); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u64, 7u32); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i8, 7u64); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i16, 7i8); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i32, 7i16); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1i64, 7i32); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, Z::ONE, 7i64); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u8, 0.5f32); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u8, 0.5f64); - let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1, Q::from((1, 2))); + assert_eq!("3 0 2 1", sample.to_string()); } } #[cfg(test)] -mod test_sample_binomial_with_offset_seeded { +mod test_sample_binomial_with_offset { use super::{PolyOverZ, Q, Z}; use crate::traits::GetCoefficient; use crate::utils::sample::test_seed; @@ -350,36 +291,65 @@ mod test_sample_binomial_with_offset_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let poly = - PolyOverZ::sample_binomial_with_offset_seeded(0, -1, 2, 0.5, test_seed()).unwrap(); - let sample = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample); - assert!(sample <= Z::ONE); + for seeded in [false, true] { + for _ in 0..8 { + let poly = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 0, + -1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample); + assert!(sample <= Z::ONE); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_binomial_with_offset_seeded(degree, 1, 2, 0.5, test_seed()) + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + degree, + 1, + 2, + 0.5, + seeded.then(test_seed), + ) .unwrap(); - assert_eq!(degree, res.get_degree()); + assert_eq!(degree, res.get_degree()); + } } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = PolyOverZ::sample_binomial_with_offset_seeded(-1, -1, 2, 0.5, test_seed()); - let res_1 = - PolyOverZ::sample_binomial_with_offset_seeded(i64::MIN, -1, 2, 0.5, test_seed()); + for seeded in [false, true] { + let res_0 = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + -1, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); + let res_1 = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + i64::MIN, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -387,18 +357,92 @@ mod test_sample_binomial_with_offset_seeded { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u16, 7u8, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u32, 7u16, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u64, 7u32, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i8, 7u64, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i16, 7i8, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i32, 7i16, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1i64, 7i32, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, Z::ONE, 7i64, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u8, 0.5f32, test_seed()); - let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u8, 0.5f64, test_seed()); - let _ = - PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZ::sample_binomial_with_offset_seeded(1, 0, 1u16, 7u8, test_seed()); + } else { + let _ = PolyOverZ::sample_binomial_with_offset(1, 0, 1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -412,4 +456,14 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::PolyOverZ; + let sample = + PolyOverZ::sample_binomial_with_offset_seeded(2, -1, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!("2 -1 1", sample.to_string()); + } } diff --git a/src/integer/poly_over_z/sample/discrete_gauss.rs b/src/integer/poly_over_z/sample/discrete_gauss.rs index dd07e2e2..999c9e54 100644 --- a/src/integer/poly_over_z/sample/discrete_gauss.rs +++ b/src/integer/poly_over_z/sample/discrete_gauss.rs @@ -91,6 +91,7 @@ impl PolyOverZ { #[cfg(test)] mod test_sample_discrete_gauss { + use crate::utils::sample::test_seed; use crate::{integer::PolyOverZ, rational::Q}; /// Checks whether `sample_discrete_gauss` is available for all types @@ -98,90 +99,118 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - - let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1u8); - let _ = PolyOverZ::sample_discrete_gauss(1u16, 0f64, 1u16); - let _ = PolyOverZ::sample_discrete_gauss(1u32, 0f32, 1u32); - let _ = PolyOverZ::sample_discrete_gauss(1u64, 0f64, 1u64); - let _ = PolyOverZ::sample_discrete_gauss(1i8, 0f32, 1i8); - let _ = PolyOverZ::sample_discrete_gauss(1i8, 0f32, 1i16); - let _ = PolyOverZ::sample_discrete_gauss(1i16, 0f32, 1i32); - let _ = PolyOverZ::sample_discrete_gauss(1i32, 0f64, 1i64); - let _ = PolyOverZ::sample_discrete_gauss(1i64, center, s); - let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1f64); - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_discrete_gauss(degree, i64::MAX, 1).unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." + for seeded in [false, true] { + let center = Q::ZERO; + let s = Q::ONE; + + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1u8, + 0f32, + 1u8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1u16, + 0f64, + 1u16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1u32, + 0f32, + 1u32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1u64, + 0f64, + 1u64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1i8, + 0f32, + 1i8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1i8, + 0f32, + 1i16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1i16, + 0f32, + 1i32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1i32, + 0f64, + 1i64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1i64, + center, + s, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + 1u8, + 0f32, + 1f64, + seeded.then(test_seed), ); - } - } - - /// Checks whether the maximum degree needs to be at least 0. - #[test] - #[should_panic] - fn invalid_max_degree() { - let _ = PolyOverZ::sample_discrete_gauss(-1, 0, 1).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{integer::PolyOverZ, rational::Q}; - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - - let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1u8, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1u16, 0f64, 1u16, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1u32, 0f32, 1u32, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1u64, 0f64, 1u64, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1i8, 0f32, 1i8, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1i8, 0f32, 1i16, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1i16, 0f32, 1i32, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1i32, 0f64, 1i64, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1i64, center, s, test_seed()); - let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1f64, test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZ::sample_discrete_gauss_seeded(1u8, 0f32, 1u8, test_seed()); + } else { + let _ = PolyOverZ::sample_discrete_gauss(1u8, 0f32, 1u8); + } + } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = - PolyOverZ::sample_discrete_gauss_seeded(degree, i64::MAX, 1, test_seed()).unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_discrete_gauss_optionally_seeded( + degree, + i64::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } } } /// Checks whether the maximum degree needs to be at least 0. #[test] - #[should_panic] fn invalid_max_degree() { - let _ = PolyOverZ::sample_discrete_gauss_seeded(-1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverZ::sample_discrete_gauss_optionally_seeded( + -1, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -193,4 +222,13 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::PolyOverZ; + let sample = PolyOverZ::sample_discrete_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + + assert_eq!("1 1", sample.to_string()); + } } diff --git a/src/integer/poly_over_z/sample/uniform.rs b/src/integer/poly_over_z/sample/uniform.rs index 7027e5e1..679a83b3 100644 --- a/src/integer/poly_over_z/sample/uniform.rs +++ b/src/integer/poly_over_z/sample/uniform.rs @@ -91,104 +91,6 @@ impl PolyOverZ { #[cfg(test)] mod test_sample_uniform { - use crate::{ - integer::{PolyOverZ, Z}, - integer_mod_q::Modulus, - traits::GetCoefficient, - }; - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - let poly_z = PolyOverZ::sample_uniform(32, &lower_bound, &upper_bound).unwrap(); - - for i in 0..32 { - let sample = poly_z.get_coeff(i).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - - let poly_z = PolyOverZ::sample_uniform(256, &lower_bound, &upper_bound).unwrap(); - - for i in 0..256 { - let sample = poly_z.get_coeff(i).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_uniform(degree, 1, 15).unwrap(); - - assert_eq!(degree, res.get_degree()); - } - } - - /// Checks whether providing an invalid interval results in an error. - #[test] - fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = PolyOverZ::sample_uniform(1, &lb_0, &upper_bound); - let res_1 = PolyOverZ::sample_uniform(1, &lb_1, &upper_bound); - let res_2 = PolyOverZ::sample_uniform(1, &lb_2, &upper_bound); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let lower_bound = Z::from(0); - let upper_bound = Z::from(15); - - let res_0 = PolyOverZ::sample_uniform(-1, &lower_bound, &upper_bound); - let res_1 = PolyOverZ::sample_uniform(i64::MIN, &lower_bound, &upper_bound); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = PolyOverZ::sample_uniform(1u64, 0u16, 7u8); - let _ = PolyOverZ::sample_uniform(1i64, 0u32, 7u16); - let _ = PolyOverZ::sample_uniform(1u8, 0u64, 7u32); - let _ = PolyOverZ::sample_uniform(1u16, 0i8, 7u64); - let _ = PolyOverZ::sample_uniform(1u32, 0i16, 7i8); - let _ = PolyOverZ::sample_uniform(1i32, 0i32, 7i16); - let _ = PolyOverZ::sample_uniform(1i16, 0i64, 7i32); - let _ = PolyOverZ::sample_uniform(1i8, &Z::ZERO, 7i64); - let _ = PolyOverZ::sample_uniform(1, 0u8, &modulus); - let _ = PolyOverZ::sample_uniform(1, 0, &z); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::utils::sample::test_seed; use crate::{ integer::{PolyOverZ, Z}, @@ -199,93 +101,197 @@ mod test_sample_uniform_seeded { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - let poly_z = - PolyOverZ::sample_uniform_seeded(32, &lower_bound, &upper_bound, test_seed()).unwrap(); - - for i in 0..32 { - let sample = poly_z.get_coeff(i).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + let poly_z = PolyOverZ::sample_uniform_optionally_seeded( + 32, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + + for i in 0..32 { + let sample = poly_z.get_coeff(i).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - - let poly_z = - PolyOverZ::sample_uniform_seeded(256, &lower_bound, &upper_bound, test_seed()).unwrap(); - - for i in 0..256 { - let sample = poly_z.get_coeff(i).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + + let poly_z = PolyOverZ::sample_uniform_optionally_seeded( + 256, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + + for i in 0..256 { + let sample = poly_z.get_coeff(i).unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZ::sample_uniform_seeded(degree, 1, 15, test_seed()).unwrap(); - - assert_eq!(degree, res.get_degree()); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZ::sample_uniform_optionally_seeded( + degree, + 1, + 15, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(degree, res.get_degree()); + } } } /// Checks whether providing an invalid interval results in an error. #[test] fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = PolyOverZ::sample_uniform_seeded(1, &lb_0, &upper_bound, test_seed()); - let res_1 = PolyOverZ::sample_uniform_seeded(1, &lb_1, &upper_bound, test_seed()); - let res_2 = PolyOverZ::sample_uniform_seeded(1, &lb_2, &upper_bound, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); + for seeded in [false, true] { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = PolyOverZ::sample_uniform_optionally_seeded( + 1, + &lb_0, + &upper_bound, + seeded.then(test_seed), + ); + let res_1 = PolyOverZ::sample_uniform_optionally_seeded( + 1, + &lb_1, + &upper_bound, + seeded.then(test_seed), + ); + let res_2 = PolyOverZ::sample_uniform_optionally_seeded( + 1, + &lb_2, + &upper_bound, + seeded.then(test_seed), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let lower_bound = Z::from(0); - let upper_bound = Z::from(15); - - let res_0 = PolyOverZ::sample_uniform_seeded(-1, &lower_bound, &upper_bound, test_seed()); - let res_1 = - PolyOverZ::sample_uniform_seeded(i64::MIN, &lower_bound, &upper_bound, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); + for seeded in [false, true] { + let lower_bound = Z::from(0); + let upper_bound = Z::from(15); + + let res_0 = PolyOverZ::sample_uniform_optionally_seeded( + -1, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); + let res_1 = PolyOverZ::sample_uniform_optionally_seeded( + i64::MIN, + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = PolyOverZ::sample_uniform_seeded(1u64, 0u16, 7u8, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1i64, 0u32, 7u16, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1u8, 0u64, 7u32, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1u16, 0i8, 7u64, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1u32, 0i16, 7i8, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1i32, 0i32, 7i16, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1i16, 0i64, 7i32, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1i8, &Z::ZERO, 7i64, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1, 0u8, &modulus, test_seed()); - let _ = PolyOverZ::sample_uniform_seeded(1, 0, &z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1u64, + 0u16, + 7u8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1i64, + 0u32, + 7u16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1u8, + 0u64, + 7u32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1u16, + 0i8, + 7u64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1u32, + 0i16, + 7i8, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1i32, + 0i32, + 7i16, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1i16, + 0i64, + 7i32, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1i8, + &Z::ZERO, + 7i64, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded( + 1, + 0u8, + &modulus, + seeded.then(test_seed), + ); + let _ = PolyOverZ::sample_uniform_optionally_seeded(1, 0, &z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZ::sample_uniform_seeded(1u64, 0u16, 7u8, test_seed()); + } else { + let _ = PolyOverZ::sample_uniform(1u64, 0u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -297,4 +303,13 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::PolyOverZ; + let sample = PolyOverZ::sample_uniform_seeded(3, 17, 26, [42; 32]).unwrap(); + + assert_eq!("4 19 20 17 19", sample.to_string()); + } } diff --git a/src/integer/z/sample/binomial.rs b/src/integer/z/sample/binomial.rs index 7be4dde7..19d25999 100644 --- a/src/integer/z/sample/binomial.rs +++ b/src/integer/z/sample/binomial.rs @@ -65,33 +65,6 @@ impl Z { #[cfg(test)] mod test_sample_binomial { use super::{Q, Z}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = Z::sample_binomial(1u16, 7u8); - let _ = Z::sample_binomial(1u32, 7u16); - let _ = Z::sample_binomial(1u64, 7u32); - let _ = Z::sample_binomial(1i8, 7u64); - let _ = Z::sample_binomial(1i16, 7i8); - let _ = Z::sample_binomial(1i32, 7i16); - let _ = Z::sample_binomial(1i64, 7i32); - let _ = Z::sample_binomial(Z::ONE, 7i64); - let _ = Z::sample_binomial(1u8, 0.5f32); - let _ = Z::sample_binomial(1u8, 0.5f64); - let _ = Z::sample_binomial(1, Q::from((1, 2))); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{Q, Z}; use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, @@ -103,17 +76,27 @@ mod test_sample_binomial_seeded { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = Z::sample_binomial_seeded(1u16, 7u8, test_seed()); - let _ = Z::sample_binomial_seeded(1u32, 7u16, test_seed()); - let _ = Z::sample_binomial_seeded(1u64, 7u32, test_seed()); - let _ = Z::sample_binomial_seeded(1i8, 7u64, test_seed()); - let _ = Z::sample_binomial_seeded(1i16, 7i8, test_seed()); - let _ = Z::sample_binomial_seeded(1i32, 7i16, test_seed()); - let _ = Z::sample_binomial_seeded(1i64, 7i32, test_seed()); - let _ = Z::sample_binomial_seeded(Z::ONE, 7i64, test_seed()); - let _ = Z::sample_binomial_seeded(1u8, 0.5f32, test_seed()); - let _ = Z::sample_binomial_seeded(1u8, 0.5f64, test_seed()); - let _ = Z::sample_binomial_seeded(1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = Z::sample_binomial_optionally_seeded(1u16, 7u8, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1u32, 7u16, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1u64, 7u32, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1i8, 7u64, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1i16, 7i8, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1i32, 7i16, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1i64, 7i32, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(Z::ONE, 7i64, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1u8, 0.5f32, seeded.then(test_seed)); + let _ = Z::sample_binomial_optionally_seeded(1u8, 0.5f64, seeded.then(test_seed)); + let _ = + Z::sample_binomial_optionally_seeded(1, Q::from((1, 2)), seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Z::sample_binomial_seeded(1u16, 7u8, test_seed()); + } else { + let _ = Z::sample_binomial(1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -125,4 +108,13 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::Z; + let sample = Z::sample_binomial_seeded(2, 0.5, [42; 32]).unwrap(); + + assert_eq!("0", sample.to_string()); + } } diff --git a/src/integer/z/sample/discrete_gauss.rs b/src/integer/z/sample/discrete_gauss.rs index ca771224..a82414f4 100644 --- a/src/integer/z/sample/discrete_gauss.rs +++ b/src/integer/z/sample/discrete_gauss.rs @@ -82,74 +82,6 @@ impl Z { #[cfg(test)] mod test_sample_discrete_gauss { - use crate::{integer::Z, rational::Q}; - - // Appropriate interval and invalid inputs were tested in - // utils::sample::discrete_gauss::test_sample_z and are thus omitted here. - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let z = Z::from(2); - let q = Q::from(2); - - let _ = Z::sample_discrete_gauss(7u8, 1u16); - let _ = Z::sample_discrete_gauss(7u16, 1u8); - let _ = Z::sample_discrete_gauss(7u32, 1u32); - let _ = Z::sample_discrete_gauss(7u64, 1u64); - let _ = Z::sample_discrete_gauss(7i8, 1i64); - let _ = Z::sample_discrete_gauss(7i16, 1i32); - let _ = Z::sample_discrete_gauss(7i32, 1i16); - let _ = Z::sample_discrete_gauss(7i64, 1i8); - let _ = Z::sample_discrete_gauss(&q, 1i64); - let _ = Z::sample_discrete_gauss(0i8, &z); - let _ = Z::sample_discrete_gauss(&z, &q); - let _ = Z::sample_discrete_gauss(1f32, 1f64); - let _ = Z::sample_discrete_gauss(1f64, 1f32); - } - - /// Roughly checks the collected samples are distributed - /// according to the discrete Gaussian distribution. - /// - /// This test could possibly fail for a truly uniform distribution - /// with probability smaller than 1/1000. - #[test] - fn distribution() { - let mut counts = [0; 20]; - // count sampled instances - for _ in 0..200 { - let sample = Z::sample_discrete_gauss(10, 2).unwrap(); - let sample_int = i64::try_from(&sample).unwrap() as usize; - counts[sample_int] += 1; - } - - let expl_text = String::from("This test can fail with probability close to 0. - It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. - If this happens, rerun the tests several times and check whether this issue comes up again."); - - // Check that the sampled occurrences roughly look - // like a discrete Gaussian distribution - assert!(counts[10] > 70, "{expl_text}"); - assert!(counts[10] < 130, "{expl_text}"); - assert!(counts[9] > 20, "{expl_text}"); - assert!(counts[9] < 70, "{expl_text}"); - assert!(counts[11] > 20, "{expl_text}"); - assert!(counts[11] < 70, "{expl_text}"); - assert!(counts[8] < 20, "{expl_text}"); - assert!(counts[12] < 20, "{expl_text}"); - for count in counts.iter().take(8) { - assert!(count < &10, "{expl_text}"); - } - for count in counts.iter().skip(13) { - assert!(count < &10, "{expl_text}"); - } - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { use crate::utils::sample::test_seed; use crate::{integer::Z, rational::Q}; @@ -161,22 +93,31 @@ mod test_sample_discrete_gauss_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let z = Z::from(2); - let q = Q::from(2); - - let _ = Z::sample_discrete_gauss_seeded(7u8, 1u16, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7u16, 1u8, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7u32, 1u32, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7u64, 1u64, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7i8, 1i64, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7i16, 1i32, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7i32, 1i16, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(7i64, 1i8, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(&q, 1i64, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(0i8, &z, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(&z, &q, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(1f32, 1f64, test_seed()); - let _ = Z::sample_discrete_gauss_seeded(1f64, 1f32, test_seed()); + for seeded in [false, true] { + let z = Z::from(2); + let q = Q::from(2); + + let _ = Z::sample_discrete_gauss_optionally_seeded(7u8, 1u16, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7u16, 1u8, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7u32, 1u32, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7u64, 1u64, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7i8, 1i64, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7i16, 1i32, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7i32, 1i16, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(7i64, 1i8, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(&q, 1i64, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(0i8, &z, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(&z, &q, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(1f32, 1f64, seeded.then(test_seed)); + let _ = Z::sample_discrete_gauss_optionally_seeded(1f64, 1f32, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Z::sample_discrete_gauss_seeded(7u8, 1u16, test_seed()); + } else { + let _ = Z::sample_discrete_gauss(7u8, 1u16); + } + } } /// Roughly checks the collected samples are distributed @@ -186,33 +127,37 @@ mod test_sample_discrete_gauss_seeded { /// with probability smaller than 1/1000. #[test] fn distribution() { - let mut counts = [0; 20]; - // count sampled instances - for _ in 0..200 { - let sample = Z::sample_discrete_gauss_seeded(10, 2, test_seed()).unwrap(); - let sample_int = i64::try_from(&sample).unwrap() as usize; - counts[sample_int] += 1; - } - - let expl_text = String::from("This test can fail with probability close to 0. + for seeded in [false, true] { + let mut counts = [0; 20]; + // count sampled instances + for _ in 0..200 { + let sample = + Z::sample_discrete_gauss_optionally_seeded(10, 2, seeded.then(test_seed)) + .unwrap(); + let sample_int = i64::try_from(&sample).unwrap() as usize; + counts[sample_int] += 1; + } + + let expl_text = String::from("This test can fail with probability close to 0. It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. If this happens, rerun the tests several times and check whether this issue comes up again."); - // Check that the sampled occurrences roughly look - // like a discrete Gaussian distribution - assert!(counts[10] > 70, "{expl_text}"); - assert!(counts[10] < 130, "{expl_text}"); - assert!(counts[9] > 20, "{expl_text}"); - assert!(counts[9] < 70, "{expl_text}"); - assert!(counts[11] > 20, "{expl_text}"); - assert!(counts[11] < 70, "{expl_text}"); - assert!(counts[8] < 20, "{expl_text}"); - assert!(counts[12] < 20, "{expl_text}"); - for count in counts.iter().take(8) { - assert!(count < &10, "{expl_text}"); - } - for count in counts.iter().skip(13) { - assert!(count < &10, "{expl_text}"); + // Check that the sampled occurrences roughly look + // like a discrete Gaussian distribution + assert!(counts[10] > 70, "{expl_text}"); + assert!(counts[10] < 130, "{expl_text}"); + assert!(counts[9] > 20, "{expl_text}"); + assert!(counts[9] < 70, "{expl_text}"); + assert!(counts[11] > 20, "{expl_text}"); + assert!(counts[11] < 70, "{expl_text}"); + assert!(counts[8] < 20, "{expl_text}"); + assert!(counts[12] < 20, "{expl_text}"); + for count in counts.iter().take(8) { + assert!(count < &10, "{expl_text}"); + } + for count in counts.iter().skip(13) { + assert!(count < &10, "{expl_text}"); + } } } @@ -225,4 +170,13 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::Z; + let sample = Z::sample_discrete_gauss_seeded(0, 1, [42; 32]).unwrap(); + + assert_eq!("1", sample.to_string()); + } } diff --git a/src/integer/z/sample/uniform.rs b/src/integer/z/sample/uniform.rs index fa1fe326..f7b6afa1 100644 --- a/src/integer/z/sample/uniform.rs +++ b/src/integer/z/sample/uniform.rs @@ -162,156 +162,95 @@ impl Z { #[cfg(test)] mod test_sample_uniform { - use crate::{integer::Z, integer_mod_q::Modulus}; - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let sample = Z::sample_uniform(&lower_bound, &upper_bound).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let sample = Z::sample_uniform(&lower_bound, &upper_bound).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether providing an invalid interval results in an error. - #[test] - fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = Z::sample_uniform(&lb_0, &upper_bound); - let res_1 = Z::sample_uniform(&lb_1, &upper_bound); - let res_2 = Z::sample_uniform(&lb_2, &upper_bound); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Z::sample_uniform(0u16, 7u8); - let _ = Z::sample_uniform(0u32, 7u16); - let _ = Z::sample_uniform(0u64, 7u32); - let _ = Z::sample_uniform(0i8, 7u64); - let _ = Z::sample_uniform(0i16, 7i8); - let _ = Z::sample_uniform(0i32, 7i16); - let _ = Z::sample_uniform(0i64, 7i32); - let _ = Z::sample_uniform(&Z::ZERO, 7i64); - let _ = Z::sample_uniform(0u8, &modulus); - let _ = Z::sample_uniform(0, &z); - } - - /// Roughly checks the uniformity of the distribution. - /// This test could possibly fail for a truly uniform distribution - /// with probability smaller than 1/1000. - #[test] - fn uniformity() { - let lower_bound = Z::ZERO; - let upper_bound = Z::from(5); - let mut counts = [0; 5]; - // count sampled instances - for _ in 0..1000 { - let sample_z = Z::sample_uniform(&lower_bound, &upper_bound).unwrap(); - let sample_int = i64::try_from(&sample_z).unwrap() as usize; - counts[sample_int] += 1; - } - - // Check that every sampled integer was sampled roughly the same time - // this could possibly fail for true uniform randomness with probability - for count in counts { - assert!(count > 150, "This test can fail with probability close to 0. - It fails if the sampled occurrences do not look like a typical uniform random distribution. - If this happens, rerun the tests several times and check whether this issue comes up again."); - } - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::utils::sample::test_seed; use crate::{integer::Z, integer_mod_q::Modulus}; /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let sample = Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let sample = Z::sample_uniform_optionally_seeded( + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); - let upper_bound = Z::from(i64::MIN); - for _ in 0..256 { - let sample = Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(i64::MIN) - Z::from(u64::MAX); + let upper_bound = Z::from(i64::MIN); + for _ in 0..256 { + let sample = Z::sample_uniform_optionally_seeded( + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether providing an invalid interval results in an error. #[test] fn invalid_interval() { - let lb_0 = Z::from(i64::MIN); - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = Z::sample_uniform_seeded(&lb_0, &upper_bound, test_seed()); - let res_1 = Z::sample_uniform_seeded(&lb_1, &upper_bound, test_seed()); - let res_2 = Z::sample_uniform_seeded(&lb_2, &upper_bound, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); + for seeded in [false, true] { + let lb_0 = Z::from(i64::MIN); + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = + Z::sample_uniform_optionally_seeded(&lb_0, &upper_bound, seeded.then(test_seed)); + let res_1 = + Z::sample_uniform_optionally_seeded(&lb_1, &upper_bound, seeded.then(test_seed)); + let res_2 = + Z::sample_uniform_optionally_seeded(&lb_2, &upper_bound, seeded.then(test_seed)); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Z::sample_uniform_seeded(0u16, 7u8, test_seed()); - let _ = Z::sample_uniform_seeded(0u32, 7u16, test_seed()); - let _ = Z::sample_uniform_seeded(0u64, 7u32, test_seed()); - let _ = Z::sample_uniform_seeded(0i8, 7u64, test_seed()); - let _ = Z::sample_uniform_seeded(0i16, 7i8, test_seed()); - let _ = Z::sample_uniform_seeded(0i32, 7i16, test_seed()); - let _ = Z::sample_uniform_seeded(0i64, 7i32, test_seed()); - let _ = Z::sample_uniform_seeded(&Z::ZERO, 7i64, test_seed()); - let _ = Z::sample_uniform_seeded(0u8, &modulus, test_seed()); - let _ = Z::sample_uniform_seeded(0, &z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Z::sample_uniform_optionally_seeded(0u16, 7u8, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0u32, 7u16, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0u64, 7u32, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0i8, 7u64, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0i16, 7i8, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0i32, 7i16, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0i64, 7i32, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(&Z::ZERO, 7i64, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0u8, &modulus, seeded.then(test_seed)); + let _ = Z::sample_uniform_optionally_seeded(0, &z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Z::sample_uniform_seeded(0u16, 7u8, test_seed()); + } else { + let _ = Z::sample_uniform(0u16, 7u8); + } + } } /// Roughly checks the uniformity of the distribution. @@ -319,23 +258,29 @@ mod test_sample_uniform_seeded { /// with probability smaller than 1/1000. #[test] fn uniformity() { - let lower_bound = Z::ZERO; - let upper_bound = Z::from(5); - let mut counts = [0; 5]; - // count sampled instances - for _ in 0..1000 { - let sample_z = - Z::sample_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); - let sample_int = i64::try_from(&sample_z).unwrap() as usize; - counts[sample_int] += 1; - } + for seeded in [false, true] { + let lower_bound = Z::ZERO; + let upper_bound = Z::from(5); + let mut counts = [0; 5]; + // count sampled instances + for _ in 0..1000 { + let sample_z = Z::sample_uniform_optionally_seeded( + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + let sample_int = i64::try_from(&sample_z).unwrap() as usize; + counts[sample_int] += 1; + } - // Check that every sampled integer was sampled roughly the same time - // this could possibly fail for true uniform randomness with probability - for count in counts { - assert!(count > 150, "This test can fail with probability close to 0. + // Check that every sampled integer was sampled roughly the same time + // this could possibly fail for true uniform randomness with probability + for count in counts { + assert!(count > 150, "This test can fail with probability close to 0. It fails if the sampled occurrences do not look like a typical uniform random distribution. If this happens, rerun the tests several times and check whether this issue comes up again."); + } } } @@ -348,115 +293,32 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_prime_uniform { - use crate::{integer::Z, integer_mod_q::Modulus}; - - /// Checks whether `sample_prime_uniform` outputs a prime sample every time. - #[test] - fn is_prime() { - for _ in 0..8 { - let sample = Z::sample_prime_uniform(20, i64::MAX).unwrap(); - assert!(sample.is_prime(), "Can fail with probability ~2^-60."); - } - } - - /// Checks whether `sample_prime_uniform` returns an error if - /// no prime exists in the specified interval. - #[test] - fn no_prime_exists() { - let res_0 = Z::sample_prime_uniform(14, 17); - // there is no prime between u64::MAX - 57 and u64::MAX - let res_1 = Z::sample_prime_uniform(u64::MAX - 57, u64::MAX); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - /// Checks whether a negative lower bound result in an error. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn negative_lower_bound() { - let res_0 = Z::sample_prime_uniform(-1, 10); - let res_1 = Z::sample_prime_uniform(-200, 1); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let sample = Z::sample_prime_uniform(&lower_bound, &upper_bound).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let lower_bound = Z::ZERO; - let upper_bound = Z::from(i64::MAX); - for _ in 0..256 { - let sample = Z::sample_prime_uniform(&lower_bound, &upper_bound).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); - } - } + fn known_answer() { + use crate::integer::Z; + let sample = Z::sample_uniform_seeded(17, 26, [42; 32]).unwrap(); - /// Checks whether providing an invalid interval results in an error. - #[test] - fn invalid_interval() { - let lb_0 = Z::from(i64::MIN) - Z::ONE; - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = Z::sample_prime_uniform(&lb_0, &upper_bound); - let res_1 = Z::sample_prime_uniform(&lb_1, &upper_bound); - let res_2 = Z::sample_prime_uniform(&lb_2, &upper_bound); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); - } - - /// Checks whether `sample_prime_uniform` is available for types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Z::sample_prime_uniform(0u16, 7u8); - let _ = Z::sample_prime_uniform(0u32, 7u16); - let _ = Z::sample_prime_uniform(0u64, 7u32); - let _ = Z::sample_prime_uniform(0i8, 7u64); - let _ = Z::sample_prime_uniform(0i16, 7i8); - let _ = Z::sample_prime_uniform(0i32, 7i16); - let _ = Z::sample_prime_uniform(0i64, 7i32); - let _ = Z::sample_prime_uniform(&Z::ZERO, 7i64); - let _ = Z::sample_prime_uniform(0u8, &modulus); - let _ = Z::sample_prime_uniform(0, &z); + assert_eq!("19", sample.to_string()); } } #[cfg(test)] -mod test_sample_prime_uniform_seeded { +mod test_sample_prime_uniform { use crate::utils::sample::test_seed; use crate::{integer::Z, integer_mod_q::Modulus}; /// Checks whether `sample_prime_uniform` outputs a prime sample every time. #[test] fn is_prime() { - for _ in 0..8 { - let sample = Z::sample_prime_uniform_seeded(20, i64::MAX, test_seed()).unwrap(); - assert!(sample.is_prime(), "Can fail with probability ~2^-60."); + for seeded in [false, true] { + for _ in 0..8 { + let sample = + Z::sample_prime_uniform_optionally_seeded(20, i64::MAX, seeded.then(test_seed)) + .unwrap(); + assert!(sample.is_prime(), "Can fail with probability ~2^-60."); + } } } @@ -464,84 +326,129 @@ mod test_sample_prime_uniform_seeded { /// no prime exists in the specified interval. #[test] fn no_prime_exists() { - let res_0 = Z::sample_prime_uniform_seeded(14, 17, test_seed()); - // there is no prime between u64::MAX - 57 and u64::MAX - let res_1 = Z::sample_prime_uniform_seeded(u64::MAX - 57, u64::MAX, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); + for seeded in [false, true] { + let res_0 = Z::sample_prime_uniform_optionally_seeded(14, 17, seeded.then(test_seed)); + // there is no prime between u64::MAX - 57 and u64::MAX + let res_1 = Z::sample_prime_uniform_optionally_seeded( + u64::MAX - 57, + u64::MAX, + seeded.then(test_seed), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether a negative lower bound result in an error. #[test] fn negative_lower_bound() { - let res_0 = Z::sample_prime_uniform_seeded(-1, 10, test_seed()); - let res_1 = Z::sample_prime_uniform_seeded(-200, 1, test_seed()); + for seeded in [false, true] { + let res_0 = Z::sample_prime_uniform_optionally_seeded(-1, 10, seeded.then(test_seed)); + let res_1 = Z::sample_prime_uniform_optionally_seeded(-200, 1, seeded.then(test_seed)); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let lower_bound = Z::from(17); - let upper_bound = Z::from(32); - for _ in 0..32 { - let sample = - Z::sample_prime_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::from(17); + let upper_bound = Z::from(32); + for _ in 0..32 { + let sample = Z::sample_prime_uniform_optionally_seeded( + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let lower_bound = Z::ZERO; - let upper_bound = Z::from(i64::MAX); - for _ in 0..256 { - let sample = - Z::sample_prime_uniform_seeded(&lower_bound, &upper_bound, test_seed()).unwrap(); - assert!(lower_bound <= sample); - assert!(sample < upper_bound); + for seeded in [false, true] { + let lower_bound = Z::ZERO; + let upper_bound = Z::from(i64::MAX); + for _ in 0..256 { + let sample = Z::sample_prime_uniform_optionally_seeded( + &lower_bound, + &upper_bound, + seeded.then(test_seed), + ) + .unwrap(); + assert!(lower_bound <= sample); + assert!(sample < upper_bound); + } } } /// Checks whether providing an invalid interval results in an error. #[test] fn invalid_interval() { - let lb_0 = Z::from(i64::MIN) - Z::ONE; - let lb_1 = Z::from(i64::MIN); - let lb_2 = Z::ZERO; - let upper_bound = Z::from(i64::MIN); - - let res_0 = Z::sample_prime_uniform_seeded(&lb_0, &upper_bound, test_seed()); - let res_1 = Z::sample_prime_uniform_seeded(&lb_1, &upper_bound, test_seed()); - let res_2 = Z::sample_prime_uniform_seeded(&lb_2, &upper_bound, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - assert!(res_2.is_err()); + for seeded in [false, true] { + let lb_0 = Z::from(i64::MIN) - Z::ONE; + let lb_1 = Z::from(i64::MIN); + let lb_2 = Z::ZERO; + let upper_bound = Z::from(i64::MIN); + + let res_0 = Z::sample_prime_uniform_optionally_seeded( + &lb_0, + &upper_bound, + seeded.then(test_seed), + ); + let res_1 = Z::sample_prime_uniform_optionally_seeded( + &lb_1, + &upper_bound, + seeded.then(test_seed), + ); + let res_2 = Z::sample_prime_uniform_optionally_seeded( + &lb_2, + &upper_bound, + seeded.then(test_seed), + ); + + assert!(res_0.is_err()); + assert!(res_1.is_err()); + assert!(res_2.is_err()); + } } /// Checks whether `sample_prime_uniform` is available for types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Z::sample_prime_uniform_seeded(0u16, 7u8, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0u32, 7u16, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0u64, 7u32, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0i8, 7u64, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0i16, 7i8, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0i32, 7i16, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0i64, 7i32, test_seed()); - let _ = Z::sample_prime_uniform_seeded(&Z::ZERO, 7i64, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0u8, &modulus, test_seed()); - let _ = Z::sample_prime_uniform_seeded(0, &z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Z::sample_prime_uniform_optionally_seeded(0u16, 7u8, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0u32, 7u16, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0u64, 7u32, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0i8, 7u64, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0i16, 7i8, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0i32, 7i16, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0i64, 7i32, seeded.then(test_seed)); + let _ = + Z::sample_prime_uniform_optionally_seeded(&Z::ZERO, 7i64, seeded.then(test_seed)); + let _ = + Z::sample_prime_uniform_optionally_seeded(0u8, &modulus, seeded.then(test_seed)); + let _ = Z::sample_prime_uniform_optionally_seeded(0, &z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Z::sample_prime_uniform_seeded(0u16, 7u8, test_seed()); + } else { + let _ = Z::sample_prime_uniform(0u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -553,4 +460,13 @@ mod test_sample_prime_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer::Z; + let sample = Z::sample_prime_uniform_seeded(1, 100, [42; 32]).unwrap(); + + assert_eq!("17", sample.to_string()); + } } diff --git a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs index 896e8cb6..7fc394d8 100644 --- a/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_ntt_polynomial_ring_zq/sample/uniform.rs @@ -92,6 +92,7 @@ impl MatNTTPolynomialRingZq { #[cfg(test)] mod test_sample_uniform { + use crate::utils::sample::test_seed; use crate::{ integer::Z, integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}, @@ -101,75 +102,84 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept() { - let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - modulus.set_ntt_unchecked(64); - - for _ in 0..32 { - let matrix = MatNTTPolynomialRingZq::sample_uniform(1, 1, &modulus); - let sample = matrix.matrix[0].clone(); - - assert!(Z::ZERO <= sample); - assert!(sample < 257); + for seeded in [false, true] { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + for _ in 0..32 { + let matrix = MatNTTPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 1, + &modulus, + seeded.then(test_seed), + ); + let sample = matrix.matrix[0].clone(); + + assert!(Z::ZERO <= sample); + assert!(sample < 257); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too large for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - modulus.set_ntt_unchecked(64); - - let _ = MatNTTPolynomialRingZq::sample_uniform(0, 1, &modulus); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let mut modulus = + ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + let _ = MatNTTPolynomialRingZq::sample_uniform_optionally_seeded( + 0, + 1, + &modulus, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}, - }; - use std::str::FromStr; - /// Checks whether the boundaries of the interval are kept for small intervals. + /// Checks whether the same seed results in the same sample. #[test] - fn boundaries_kept() { + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); + let sample_0 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); + let sample_1 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); - for _ in 0..32 { - let matrix = MatNTTPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let sample = matrix.matrix[0].clone(); - - assert!(Z::ZERO <= sample); - assert!(sample < 257); - } + assert_eq!(sample_0, sample_1); } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too large for an [`i64`] results in an error. - #[should_panic] + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn false_size() { + fn known_answer() { + use crate::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); + let sample = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); - let _ = MatNTTPolynomialRingZq::sample_uniform_seeded(0, 1, &modulus, test_seed()); + assert_eq!( + "[27, 144, 210, 163, 199, 7, 189, 211, 59, 113, 81, 240, 100, 124, 192, 16, 210, 239, 127, 158, 157, 232, 41, 40] / 5 1 0 0 0 1 mod 257", + sample.to_string() + ); } - /// Checks whether the same seed results in the same sample. + /// Checks whether the unseeded and the seeded function are available. #[test] - fn same_seed_same_sample() { + fn availability() { use crate::integer_mod_q::{MatNTTPolynomialRingZq, ModulusPolynomialRingZq}; use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); - let sample_0 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); - let sample_1 = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, [42; 32]); - assert_eq!(sample_0, sample_1); + // explicitly checks the existence of the unseeded and the seeded function + let _ = MatNTTPolynomialRingZq::sample_uniform(3, 2, &modulus); + let _ = MatNTTPolynomialRingZq::sample_uniform_seeded(3, 2, &modulus, test_seed()); } } diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs index 00c6c4de..6be68599 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/binomial.rs @@ -157,6 +157,7 @@ impl MatPolynomialRingZq { #[cfg(test)] mod test_sample_binomial { use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; + use crate::utils::sample::test_seed; use crate::{ integer::PolyOverZ, integer_mod_q::PolyOverZq, @@ -171,55 +172,97 @@ mod test_sample_binomial { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let matrix = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 2, 0.5).unwrap(); - let entry: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let matrix = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let entry: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let poly = entry.get_coeff(0).unwrap(); - assert!(Z::ZERO <= poly); - assert!(poly <= 2); + assert!(Z::ZERO <= poly); + assert!(poly <= 2); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = MatPolynomialRingZq::sample_binomial(1, 1, modulus, 256, 0.99999).unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree() + 1, - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + modulus, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial(0, 3, modulus, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 0, + 3, + modulus, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial(1, 1, modulus, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + modulus, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -227,185 +270,169 @@ mod test_sample_binomial { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u16, 7u8); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u32, 7u16); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u64, 7u32); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1i8, 7u64); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1i16, 7i8); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1i32, 7i16); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1i64, 7i32); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, Z::ONE, 7i64); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u8, 0.5f32); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u8, 0.5f64); - let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1, Q::from((1, 2))); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 1, + &modulus, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolynomialRingZq::sample_binomial_seeded( + 1, + 1, + &modulus, + 1u16, + 7u8, + test_seed(), + ); + } else { + let _ = MatPolynomialRingZq::sample_binomial(1, 1, &modulus, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let mat_0 = MatPolynomialRingZq::sample_binomial(3, 3, &modulus, 1, 0.5).unwrap(); - let mat_1 = MatPolynomialRingZq::sample_binomial(4, 1, &modulus, 1, 0.5).unwrap(); - let mat_2 = MatPolynomialRingZq::sample_binomial(1, 5, &modulus, 1, 0.5).unwrap(); - let mat_3 = MatPolynomialRingZq::sample_binomial(15, 20, &modulus, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; - use crate::utils::sample::test_seed; - use crate::{ - integer::PolyOverZ, - integer_mod_q::PolyOverZq, - traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}, - }; - use std::str::FromStr; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let matrix = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 2, 0.5, test_seed()) - .unwrap(); - let entry: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let poly = entry.get_coeff(0).unwrap(); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - assert!(Z::ZERO <= poly); - assert!(poly <= 2); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = MatPolynomialRingZq::sample_binomial_seeded( + let mat_0 = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 3, + 3, + &modulus, 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 4, 1, - modulus, - 256, - 0.99999, - test_seed(), + &modulus, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 1, + 5, + &modulus, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatPolynomialRingZq::sample_binomial_optionally_seeded( + 15, + 20, + &modulus, + 1, + 0.5, + seeded.then(test_seed), ) .unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - assert_eq!( - degree, - poly.get_degree() + 1, - "This test can fail with probability close to 0." - ); + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); } } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_seeded(0, 3, modulus, 1, 0.5, test_seed()); - } - - /// Checks whether 0 modulus polynomial is insufficient. - #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, modulus, 1, 0.5, test_seed()); - } - - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u16, 7u8, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u32, 7u16, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u64, 7u32, test_seed()); - let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i8, 7u64, test_seed()); - let _ = MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i16, 7i8, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i32, 7i16, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1i64, 7i32, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, Z::ONE, 7i64, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u8, 0.5f32, test_seed()); - let _ = - MatPolynomialRingZq::sample_binomial_seeded(1, 1, &modulus, 1u8, 0.5f64, test_seed()); - let _ = MatPolynomialRingZq::sample_binomial_seeded( - 1, - 1, - &modulus, - 1, - Q::from((1, 2)), - test_seed(), - ); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let mat_0 = - MatPolynomialRingZq::sample_binomial_seeded(3, 3, &modulus, 1, 0.5, test_seed()) - .unwrap(); - let mat_1 = - MatPolynomialRingZq::sample_binomial_seeded(4, 1, &modulus, 1, 0.5, test_seed()) - .unwrap(); - let mat_2 = - MatPolynomialRingZq::sample_binomial_seeded(1, 5, &modulus, 1, 0.5, test_seed()) - .unwrap(); - let mat_3 = - MatPolynomialRingZq::sample_binomial_seeded(15, 20, &modulus, 1, 0.5, test_seed()) - .unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } - /// Checks whether the same seed results in the same sample. #[test] fn same_seed_same_sample() { @@ -419,11 +446,27 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample = + MatPolynomialRingZq::sample_binomial_seeded(2, 2, &modulus, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!( + "[[3 0 2 1, 3 2 0 1],[2 0 1, 3 1 0 1]] / 4 1 0 0 1 mod 17", + sample.to_string() + ); + } } #[cfg(test)] mod test_sample_binomial_with_offset { use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; + use crate::utils::sample::test_seed; use crate::{ integer::PolyOverZ, integer_mod_q::PolyOverZq, @@ -438,54 +481,96 @@ mod test_sample_binomial_with_offset { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let matrix = - MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 2, 0.5) - .unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let value = poly.get_coeff(0).unwrap(); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let matrix = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let value = poly.get_coeff(0).unwrap(); - assert!(16 <= value || value <= Z::ONE); + assert!(16 <= value || value <= Z::ONE); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = - MatPolynomialRingZq::sample_binomial_with_offset(1, 1, modulus, 1, 256, 0.99999) - .unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - - assert_eq!(degree, poly.get_degree() + 1,); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + modulus, + 1, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!(degree, poly.get_degree() + 1,); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_with_offset(0, 3, modulus, -1, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 0, + 3, + modulus, + -1, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, modulus, -1, 1, 0.5); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + modulus, + -1, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -493,332 +578,218 @@ mod test_sample_binomial_with_offset { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1u16, 7u8); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, 0, 1u32, 7u16); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1u64, 7u32); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1i8, 7u64); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1i16, 7i8); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1i32, 7i16); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1i64, 7i32); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, Z::ONE, 7i64); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1u8, 0.5f32); - let _ = MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, 1, 1u8, 0.5f64); - let _ = MatPolynomialRingZq::sample_binomial_with_offset( - 1, - 1, - &modulus, - Z::ONE, - 1, - Q::from((1, 2)), - ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + 0, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + -1, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + 1, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + &modulus, + Z::ONE, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 1, + 1, + &modulus, + -1, + 1u16, + 7u8, + test_seed(), + ); + } else { + let _ = + MatPolynomialRingZq::sample_binomial_with_offset(1, 1, &modulus, -1, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let mat_0 = - MatPolynomialRingZq::sample_binomial_with_offset(3, 3, &modulus, -1, 1, 0.5).unwrap(); - let mat_1 = - MatPolynomialRingZq::sample_binomial_with_offset(4, 1, &modulus, -1, 1, 0.5).unwrap(); - let mat_2 = - MatPolynomialRingZq::sample_binomial_with_offset(1, 5, &modulus, -1, 1, 0.5).unwrap(); - let mat_3 = - MatPolynomialRingZq::sample_binomial_with_offset(15, 20, &modulus, -1, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); -#[cfg(test)] -mod test_sample_binomial_with_offset_seeded { - use super::{MatPolynomialRingZq, ModulusPolynomialRingZq, Q, Z}; - use crate::utils::sample::test_seed; - use crate::{ - integer::PolyOverZ, - integer_mod_q::PolyOverZq, - traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}, - }; - use std::str::FromStr; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let matrix = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + let mat_0 = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 3, + 3, + &modulus, + -1, 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 4, 1, &modulus, -1, - 2, + 1, 0.5, - test_seed(), + seeded.then(test_seed), ) .unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let value = poly.get_coeff(0).unwrap(); - - assert!(16 <= value || value <= Z::ONE); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + let mat_2 = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( 1, + 5, + &modulus, + -1, 1, - modulus, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatPolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + 15, + 20, + &modulus, + -1, 1, - 256, - 0.99999, - test_seed(), + 0.5, + seeded.then(test_seed), ) .unwrap(); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - assert_eq!(degree, poly.get_degree() + 1,); + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); } } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 0, - 3, - modulus, - -1, - 1, - 0.5, - test_seed(), - ); - } - - /// Checks whether 0 modulus polynomial is insufficient. - #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - modulus, - -1, - 1, - 0.5, - test_seed(), - ); - } - - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1u16, - 7u8, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - 0, - 1u32, - 7u16, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1u64, - 7u32, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1i8, - 7u64, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1i16, - 7i8, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1i32, - 7i16, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1i64, - 7i32, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - Z::ONE, - 7i64, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - -1, - 1u8, - 0.5f32, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - 1, - 1u8, - 0.5f64, - test_seed(), - ); - let _ = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 1, - &modulus, - Z::ONE, - 1, - Q::from((1, 2)), - test_seed(), - ); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. + /// Checks whether the same seed results in the same sample. #[test] - fn matrix_size() { + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let mat_0 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 3, - 3, - &modulus, - -1, - 1, - 0.5, - test_seed(), - ) - .unwrap(); - let mat_1 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 4, - 1, - &modulus, - -1, - 1, - 0.5, - test_seed(), - ) - .unwrap(); - let mat_2 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 1, - 5, - &modulus, - -1, - 1, - 0.5, - test_seed(), + let sample_0 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 2, 2, &modulus, -1, 2, 0.5, [42; 32], ) .unwrap(); - let mat_3 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 15, - 20, - &modulus, - -1, - 1, - 0.5, - test_seed(), + let sample_1 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + 2, 2, &modulus, -1, 2, 0.5, [42; 32], ) .unwrap(); - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + assert_eq!(sample_0, sample_1); } - /// Checks whether the same seed results in the same sample. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn same_seed_same_sample() { + fn known_answer() { use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let sample_0 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( - 2, 2, &modulus, -1, 2, 0.5, [42; 32], - ) - .unwrap(); - let sample_1 = MatPolynomialRingZq::sample_binomial_with_offset_seeded( + let sample = MatPolynomialRingZq::sample_binomial_with_offset_seeded( 2, 2, &modulus, -1, 2, 0.5, [42; 32], ) .unwrap(); - assert_eq!(sample_0, sample_1); + assert_eq!( + "[[2 16 1, 2 1 16],[3 16 0 16, 2 0 16]] / 4 1 0 0 1 mod 17", + sample.to_string() + ); } } diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs index 74534d7f..b7de787e 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/discrete_gauss.rs @@ -163,36 +163,6 @@ impl MatPolynomialRingZq { #[cfg(test)] mod test_sample_discrete_gauss { - use crate::{ - integer::PolyOverZ, - integer_mod_q::{MatPolynomialRingZq, MatZq, ModulusPolynomialRingZq}, - traits::{FromCoefficientEmbedding, MatrixGetEntry, MatrixSetEntry}, - }; - - /// Ensures that the resulting entries have correct degree. - #[test] - fn correct_degree_entries() { - let degrees = [1, 2, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut coeff_emb_mod = MatZq::new(degree + 1, 1, i64::MAX); - coeff_emb_mod.set_entry(0, 0, 1).unwrap(); - coeff_emb_mod.set_entry(degree, 0, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from_coefficient_embedding(&coeff_emb_mod); - let res = - MatPolynomialRingZq::sample_discrete_gauss(1, 1, modulus, i32::MAX, 1).unwrap(); - - let entry: PolyOverZ = res.get_entry(0, 0).unwrap(); - assert_eq!( - entry.get_degree(), - degree - 1, - "Could fail with negligible probability." - ); - } - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { use crate::utils::sample::test_seed; use crate::{ integer::PolyOverZ, @@ -203,28 +173,30 @@ mod test_sample_discrete_gauss_seeded { /// Ensures that the resulting entries have correct degree. #[test] fn correct_degree_entries() { - let degrees = [1, 2, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut coeff_emb_mod = MatZq::new(degree + 1, 1, i64::MAX); - coeff_emb_mod.set_entry(0, 0, 1).unwrap(); - coeff_emb_mod.set_entry(degree, 0, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from_coefficient_embedding(&coeff_emb_mod); - let res = MatPolynomialRingZq::sample_discrete_gauss_seeded( - 1, - 1, - modulus, - i32::MAX, - 1, - test_seed(), - ) - .unwrap(); + for seeded in [false, true] { + let degrees = [1, 2, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut coeff_emb_mod = MatZq::new(degree + 1, 1, i64::MAX); + coeff_emb_mod.set_entry(0, 0, 1).unwrap(); + coeff_emb_mod.set_entry(degree, 0, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from_coefficient_embedding(&coeff_emb_mod); + let res = MatPolynomialRingZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + modulus, + i32::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); - let entry: PolyOverZ = res.get_entry(0, 0).unwrap(); - assert_eq!( - entry.get_degree(), - degree - 1, - "Could fail with negligible probability." - ); + let entry: PolyOverZ = res.get_entry(0, 0).unwrap(); + assert_eq!( + entry.get_degree(), + degree - 1, + "Could fail with negligible probability." + ); + } } } @@ -243,69 +215,45 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_d { - use crate::{ - integer::{MatPolyOverZ, PolyOverZ, Z}, - integer_mod_q::{MatPolynomialRingZq, MatZq, ModulusPolynomialRingZq, Zq}, - rational::{PolyOverQ, Q}, - traits::{IntoCoefficientEmbedding, MatrixGetEntry}, - }; - use std::str::FromStr; - /// Ensure that the sample is from the base. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn ensure_sampled_from_base() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); - let base = MatPolynomialRingZq::from((&poly_mat, &modulus)); - let center = vec![PolyOverQ::default()]; - - for _ in 0..10 { - let sample = MatPolynomialRingZq::sample_d(&base, 3, ¢er, 20.5_f64).unwrap(); - let sample: PolyOverZ = sample.get_entry(0, 0).unwrap(); - let sample_vec = MatZq::from((&sample.into_coefficient_embedding(3), 17)); - let orthogonal = MatZq::from_str("[[0],[1],[1]] mod 17").unwrap(); + fn known_answer() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); + let sample = + MatPolynomialRingZq::sample_discrete_gauss_seeded(3, 1, &modulus, 0, 1.25f32, [42; 32]) + .unwrap(); - assert_eq!( - Zq::from((0, 17)), - sample_vec.dot_product(&orthogonal).unwrap() - ) - } + assert_eq!( + "[[2 1 16],[2 0 1],[0]] / 3 1 0 1 mod 17", + sample.to_string() + ); } - /// Checks whether `sample_d` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + /// Checks whether the unseeded and the seeded function are available. #[test] fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); - let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); - let center = vec![PolyOverQ::default()]; - let n = Z::from(1024); - let s = Q::from(8); + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 0 1 mod 17").unwrap(); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u16); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u8); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u32); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i32); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i16); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i8); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1i64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, &n); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, &s); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1.25f64); - let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 15.75f32); + // explicitly checks the existence of the unseeded and the seeded function + let _ = MatPolynomialRingZq::sample_discrete_gauss(3, 1, &modulus, 0, 1.25f32); + let _ = MatPolynomialRingZq::sample_discrete_gauss_seeded( + 3, + 1, + &modulus, + 0, + 1.25f32, + test_seed(), + ); } } #[cfg(test)] -mod test_sample_d_seeded { +mod test_sample_d { use crate::utils::sample::test_seed; use crate::{ integer::{MatPolyOverZ, PolyOverZ, Z}, @@ -318,23 +266,30 @@ mod test_sample_d_seeded { /// Ensure that the sample is from the base. #[test] fn ensure_sampled_from_base() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); - let base = MatPolynomialRingZq::from((&poly_mat, &modulus)); - let center = vec![PolyOverQ::default()]; + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 1 -1]]").unwrap(); + let base = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; - for _ in 0..10 { - let sample = - MatPolynomialRingZq::sample_d_seeded(&base, 3, ¢er, 20.5_f64, test_seed()) - .unwrap(); - let sample: PolyOverZ = sample.get_entry(0, 0).unwrap(); - let sample_vec = MatZq::from((&sample.into_coefficient_embedding(3), 17)); - let orthogonal = MatZq::from_str("[[0],[1],[1]] mod 17").unwrap(); + for _ in 0..10 { + let sample = MatPolynomialRingZq::sample_d_optionally_seeded( + &base, + 3, + ¢er, + 20.5_f64, + seeded.then(test_seed), + ) + .unwrap(); + let sample: PolyOverZ = sample.get_entry(0, 0).unwrap(); + let sample_vec = MatZq::from((&sample.into_coefficient_embedding(3), 17)); + let orthogonal = MatZq::from_str("[[0],[1],[1]] mod 17").unwrap(); - assert_eq!( - Zq::from((0, 17)), - sample_vec.dot_product(&orthogonal).unwrap() - ) + assert_eq!( + Zq::from((0, 17)), + sample_vec.dot_product(&orthogonal).unwrap() + ) + } } } @@ -343,26 +298,113 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); - let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); - let center = vec![PolyOverQ::default()]; - let n = Z::from(1024); - let s = Q::from(8); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); + let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; + let n = Z::from(1024); + let s = Q::from(8); + + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatPolynomialRingZq::sample_d_optionally_seeded( + &basis, + 3, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u8, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u32, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u64, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i32, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i16, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i8, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1i64, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &n, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &s, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1.25f64, test_seed()); - let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 15.75f32, test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, 1u16, test_seed()); + } else { + let _ = MatPolynomialRingZq::sample_d(&basis, 3, ¢er, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -386,4 +428,24 @@ mod test_sample_d_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::{ + integer::MatPolyOverZ, + integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, + rational::{PolyOverQ, Q}, + }; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let poly_mat = MatPolyOverZ::from_str("[[1 1, 3 0 0 1, 2 0 1]]").unwrap(); + let basis = MatPolynomialRingZq::from((&poly_mat, &modulus)); + let center = vec![PolyOverQ::default()]; + let s = Q::from(8); + let sample = + MatPolynomialRingZq::sample_d_seeded(&basis, 3, ¢er, &s, [42; 32]).unwrap(); + + assert_eq!("[[2 16 4]] / 4 1 0 0 1 mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs index a6e8226c..e6f8028a 100644 --- a/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_polynomial_ring_zq/sample/uniform.rs @@ -93,6 +93,7 @@ mod test_sample_uniform { use crate::integer::PolyOverZ; use crate::integer_mod_q::PolyOverZq; use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}; + use crate::utils::sample::test_seed; use crate::{ integer::Z, integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, @@ -102,209 +103,191 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept for small intervals. #[test] fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let matrix = MatPolynomialRingZq::sample_uniform(1, 1, &modulus); - let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(Z::ZERO <= coeff); - assert!(coeff < 17); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let matrix = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 1, + &modulus, + seeded.then(test_seed), + ); + let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= coeff); + assert!(coeff < 17); + } } } /// Checks whether the boundaries of the interval are kept for large intervals. #[test] fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let matrix = MatPolynomialRingZq::sample_uniform(1, 1, &modulus); - let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(Z::ZERO <= coeff); - assert!(coeff < u64::MAX); + for seeded in [false, true] { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let matrix = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 1, + &modulus, + seeded.then(test_seed), + ); + let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + let coeff = sample.get_coeff(0).unwrap(); + + assert!(Z::ZERO <= coeff); + assert!(coeff < u64::MAX); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = MatPolynomialRingZq::sample_uniform(1, 1, &modulus); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree() + 1, - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let matrix = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 1, + &modulus, + seeded.then(test_seed), + ); + let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); + + assert_eq!( + degree, + poly.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too large for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_uniform(0, 3, &modulus); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 0, + 3, + &modulus, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_uniform(1, 1, &modulus); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 1, + &modulus, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let mat_0 = MatPolynomialRingZq::sample_uniform(3, 3, &modulus); - let mat_1 = MatPolynomialRingZq::sample_uniform(4, 1, &modulus); - let mat_2 = MatPolynomialRingZq::sample_uniform(1, 5, &modulus); - let mat_3 = MatPolynomialRingZq::sample_uniform(15, 20, &modulus); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { - use crate::integer::PolyOverZ; - use crate::integer_mod_q::PolyOverZq; - use crate::traits::{GetCoefficient, MatrixDimensions, MatrixGetEntry, SetCoefficient}; - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}, - }; - use std::str::FromStr; - - /// Checks whether the boundaries of the interval are kept for small intervals. - #[test] - fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(Z::ZERO <= coeff); - assert!(coeff < 17); - } - } - - /// Checks whether the boundaries of the interval are kept for large intervals. - #[test] - fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let sample: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - let coeff = sample.get_coeff(0).unwrap(); - - assert!(Z::ZERO <= coeff); - assert!(coeff < u64::MAX); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let matrix = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let poly: PolyOverZ = matrix.get_entry(0, 0).unwrap(); - - assert_eq!( - degree, - poly.get_degree() + 1, - "This test can fail with probability close to 0." + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let mat_0 = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 3, + 3, + &modulus, + seeded.then(test_seed), + ); + let mat_1 = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 4, + 1, + &modulus, + seeded.then(test_seed), + ); + let mat_2 = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 1, + 5, + &modulus, + seeded.then(test_seed), ); + let mat_3 = MatPolynomialRingZq::sample_uniform_optionally_seeded( + 15, + 20, + &modulus, + seeded.then(test_seed), + ); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); } } - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too large for an [`i64`] results in an error. - #[should_panic] + /// Checks whether the same seed results in the same sample. #[test] - fn false_size() { + fn same_seed_same_sample() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample_0 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); + let sample_1 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); - let _ = MatPolynomialRingZq::sample_uniform_seeded(0, 3, &modulus, test_seed()); + assert_eq!(sample_0, sample_1); } - /// Checks whether 0 modulus polynomial is insufficient. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = MatPolynomialRingZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { + fn known_answer() { + use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; + use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + let sample = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); - let mat_0 = MatPolynomialRingZq::sample_uniform_seeded(3, 3, &modulus, test_seed()); - let mat_1 = MatPolynomialRingZq::sample_uniform_seeded(4, 1, &modulus, test_seed()); - let mat_2 = MatPolynomialRingZq::sample_uniform_seeded(1, 5, &modulus, test_seed()); - let mat_3 = MatPolynomialRingZq::sample_uniform_seeded(15, 20, &modulus, test_seed()); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + assert_eq!( + "[[3 1 3 3, 3 15 8 9],[3 10 6 10, 2 16 6]] / 4 1 0 0 1 mod 17", + sample.to_string() + ); } - /// Checks whether the same seed results in the same sample. + /// Checks whether the unseeded and the seeded function are available. #[test] - fn same_seed_same_sample() { + fn availability() { use crate::integer_mod_q::{MatPolynomialRingZq, ModulusPolynomialRingZq}; use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - let sample_0 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); - let sample_1 = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, [42; 32]); - assert_eq!(sample_0, sample_1); + // explicitly checks the existence of the unseeded and the seeded function + let _ = MatPolynomialRingZq::sample_uniform(2, 2, &modulus); + let _ = MatPolynomialRingZq::sample_uniform_seeded(2, 2, &modulus, test_seed()); } } diff --git a/src/integer_mod_q/mat_zq/sample/binomial.rs b/src/integer_mod_q/mat_zq/sample/binomial.rs index 11705ba0..a38655a3 100644 --- a/src/integer_mod_q/mat_zq/sample/binomial.rs +++ b/src/integer_mod_q/mat_zq/sample/binomial.rs @@ -157,80 +157,6 @@ impl MatZq { mod test_sample_binomial { use super::{MatZq, Q, Z}; use crate::traits::{MatrixDimensions, MatrixGetEntry}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let matrix = MatZq::sample_binomial(1, 1, 7, 2, 0.5).unwrap(); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); - } - } - - /// Checks if entries are properly reduced. - #[test] - fn reduced() { - let matrix = MatZq::sample_binomial(1, 1, 3, 7, 0.5).unwrap(); - let value: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= value && value <= 3) - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let _ = MatZq::sample_binomial(0, 3, 7, 1, 0.5); - } - - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let _ = MatZq::sample_binomial(1, 1, 7u8, 1u16, 7u8); - let _ = MatZq::sample_binomial(1, 1, 7u16, 1u32, 7u16); - let _ = MatZq::sample_binomial(1, 1, 7u32, 1u64, 7u32); - let _ = MatZq::sample_binomial(1, 1, 7u64, 1i8, 7u64); - let _ = MatZq::sample_binomial(1, 1, 7i8, 1i16, 7i8); - let _ = MatZq::sample_binomial(1, 1, 7i16, 1i32, 7i16); - let _ = MatZq::sample_binomial(1, 1, 7i32, 1i64, 7i32); - let _ = MatZq::sample_binomial(1, 1, 7i64, Z::ONE, 7i64); - let _ = MatZq::sample_binomial(1, 1, 7, 1u8, 0.5f32); - let _ = MatZq::sample_binomial(1, 1, 7, 1u8, 0.5f64); - let _ = MatZq::sample_binomial(1, 1, 7, 1, Q::from((1, 2))); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = MatZq::sample_binomial(3, 3, 7, 1, 0.5).unwrap(); - let mat_1 = MatZq::sample_binomial(4, 1, 7, 1, 0.5).unwrap(); - let mat_2 = MatZq::sample_binomial(1, 5, 7, 1, 0.5).unwrap(); - let mat_3 = MatZq::sample_binomial(15, 20, 7, 1, 0.5).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{MatZq, Q, Z}; - use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, @@ -240,27 +166,52 @@ mod test_sample_binomial_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let matrix = MatZq::sample_binomial_seeded(1, 1, 7, 2, 0.5, test_seed()).unwrap(); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } } } /// Checks if entries are properly reduced. #[test] fn reduced() { - let matrix = MatZq::sample_binomial_seeded(1, 1, 3, 7, 0.5, test_seed()).unwrap(); - let value: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= value && value <= 3) + for seeded in [false, true] { + let matrix = + MatZq::sample_binomial_optionally_seeded(1, 1, 3, 7, 0.5, seeded.then(test_seed)) + .unwrap(); + let value: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= value && value <= 3) + } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatZq::sample_binomial_seeded(0, 3, 7, 1, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatZq::sample_binomial_optionally_seeded( + 0, + 3, + 7, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -269,36 +220,132 @@ mod test_sample_binomial_seeded { /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let _ = MatZq::sample_binomial_seeded(1, 1, 7u8, 1u16, 7u8, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7u16, 1u32, 7u16, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7u32, 1u64, 7u32, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7u64, 1i8, 7u64, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7i8, 1i16, 7i8, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7i16, 1i32, 7i16, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7i32, 1i64, 7i32, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7i64, Z::ONE, 7i64, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1u8, 0.5f32, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1u8, 0.5f64, test_seed()); - let _ = MatZq::sample_binomial_seeded(1, 1, 7, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7i32, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7i64, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_optionally_seeded( + 1, + 1, + 7, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_binomial_seeded(1, 1, 7u8, 1u16, 7u8, test_seed()); + } else { + let _ = MatZq::sample_binomial(1, 1, 7u8, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = MatZq::sample_binomial_seeded(3, 3, 7, 1, 0.5, test_seed()).unwrap(); - let mat_1 = MatZq::sample_binomial_seeded(4, 1, 7, 1, 0.5, test_seed()).unwrap(); - let mat_2 = MatZq::sample_binomial_seeded(1, 5, 7, 1, 0.5, test_seed()).unwrap(); - let mat_3 = MatZq::sample_binomial_seeded(15, 20, 7, 1, 0.5, test_seed()).unwrap(); + for seeded in [false, true] { + let mat_0 = + MatZq::sample_binomial_optionally_seeded(3, 3, 7, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_1 = + MatZq::sample_binomial_optionally_seeded(4, 1, 7, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_2 = + MatZq::sample_binomial_optionally_seeded(1, 5, 7, 1, 0.5, seeded.then(test_seed)) + .unwrap(); + let mat_3 = + MatZq::sample_binomial_optionally_seeded(15, 20, 7, 1, 0.5, seeded.then(test_seed)) + .unwrap(); - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -310,76 +357,19 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_binomial_with_offset { - use super::{MatZq, Q, Z}; - use crate::traits::{MatrixDimensions, MatrixGetEntry}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - /// Checks whether the boundaries of the interval are kept. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn boundaries_kept() { - for _ in 0..8 { - let matrix = MatZq::sample_binomial_with_offset(1, 1, -1, 7, 2, 0.5).unwrap(); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); - } - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too big for an [`i64`] results in an error. - #[should_panic] - #[test] - fn false_size() { - let _ = MatZq::sample_binomial_with_offset(0, 3, 0, 7, 1, 0.5); - } - - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let _ = MatZq::sample_binomial_with_offset(1, 1, 0u8, 7u8, 1u16, 7u8); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0u16, 7u16, 1u32, 7u16); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0u32, 7u32, 1u64, 7u32); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0u64, 7u64, 1i8, 7u64); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0i8, 7i8, 1i16, 7i8); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0i16, 7i16, 1i32, 7i16); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0i64, 7i32, 1i64, 7i32); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0, 7i64, Z::ONE, 7i64); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0, 7, 1u8, 0.5f32); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0, 7, 1u8, 0.5f64); - let _ = MatZq::sample_binomial_with_offset(1, 1, 0, 7, 1, Q::from((1, 2))); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let mat_0 = MatZq::sample_binomial_with_offset(3, 3, 0, 7, 1, 0.5).unwrap(); - let mat_1 = MatZq::sample_binomial_with_offset(4, 1, 0, 7, 1, 0.5).unwrap(); - let mat_2 = MatZq::sample_binomial_with_offset(1, 5, 0, 7, 1, 0.5).unwrap(); - let mat_3 = MatZq::sample_binomial_with_offset(15, 20, 0, 7, 1, 0.5).unwrap(); + fn known_answer() { + use crate::integer_mod_q::MatZq; + let sample = MatZq::sample_binomial_seeded(2, 2, 7, 2, 0.5, [42; 32]).unwrap(); - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + assert_eq!("[[0, 2],[1, 2]] mod 7", sample.to_string()); } } #[cfg(test)] -mod test_sample_binomial_with_offset_seeded { +mod test_sample_binomial_with_offset { use super::{MatZq, Q, Z}; use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; @@ -391,21 +381,42 @@ mod test_sample_binomial_with_offset_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let matrix = - MatZq::sample_binomial_with_offset_seeded(1, 1, -1, 7, 2, 0.5, test_seed()) - .unwrap(); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + for seeded in [false, true] { + for _ in 0..8 { + let matrix = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + -1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too big for an [`i64`] results in an error. - #[should_panic] #[test] fn false_size() { - let _ = MatZq::sample_binomial_with_offset_seeded(0, 3, 0, 7, 1, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 0, + 3, + 0, + 7, + 1, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -414,45 +425,179 @@ mod test_sample_binomial_with_offset_seeded { /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0u8, 7u8, 1u16, 7u8, test_seed()); - let _ = - MatZq::sample_binomial_with_offset_seeded(1, 1, 0u16, 7u16, 1u32, 7u16, test_seed()); - let _ = - MatZq::sample_binomial_with_offset_seeded(1, 1, 0u32, 7u32, 1u64, 7u32, test_seed()); - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0u64, 7u64, 1i8, 7u64, test_seed()); - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0i8, 7i8, 1i16, 7i8, test_seed()); - let _ = - MatZq::sample_binomial_with_offset_seeded(1, 1, 0i16, 7i16, 1i32, 7i16, test_seed()); - let _ = - MatZq::sample_binomial_with_offset_seeded(1, 1, 0i64, 7i32, 1i64, 7i32, test_seed()); - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7i64, Z::ONE, 7i64, test_seed()); - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1u8, 0.5f32, test_seed()); - let _ = MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1u8, 0.5f64, test_seed()); - let _ = - MatZq::sample_binomial_with_offset_seeded(1, 1, 0, 7, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u8, + 7u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u16, + 7u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u32, + 7u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0u64, + 7u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i8, + 7i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i16, + 7i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0i64, + 7i32, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 7i64, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 7, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 7, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 1, + 0, + 7, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_binomial_with_offset_seeded( + 1, + 1, + 0u8, + 7u8, + 1u16, + 7u8, + test_seed(), + ); + } else { + let _ = MatZq::sample_binomial_with_offset(1, 1, 0u8, 7u8, 1u16, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let mat_0 = - MatZq::sample_binomial_with_offset_seeded(3, 3, 0, 7, 1, 0.5, test_seed()).unwrap(); - let mat_1 = - MatZq::sample_binomial_with_offset_seeded(4, 1, 0, 7, 1, 0.5, test_seed()).unwrap(); - let mat_2 = - MatZq::sample_binomial_with_offset_seeded(1, 5, 0, 7, 1, 0.5, test_seed()).unwrap(); - let mat_3 = - MatZq::sample_binomial_with_offset_seeded(15, 20, 0, 7, 1, 0.5, test_seed()).unwrap(); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let mat_0 = MatZq::sample_binomial_with_offset_optionally_seeded( + 3, + 3, + 0, + 7, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_1 = MatZq::sample_binomial_with_offset_optionally_seeded( + 4, + 1, + 0, + 7, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_2 = MatZq::sample_binomial_with_offset_optionally_seeded( + 1, + 5, + 0, + 7, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let mat_3 = MatZq::sample_binomial_with_offset_optionally_seeded( + 15, + 20, + 0, + 7, + 1, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -466,4 +611,14 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::MatZq; + let sample = + MatZq::sample_binomial_with_offset_seeded(2, 2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!("[[6, 1],[0, 1]] mod 7", sample.to_string()); + } } diff --git a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs index e584af88..9ae8140d 100644 --- a/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/mat_zq/sample/discrete_gauss.rs @@ -237,52 +237,6 @@ impl MatZq { #[cfg(test)] mod test_sample_discrete_gauss { - use crate::{ - integer::Z, - integer_mod_q::{MatZq, Modulus}, - rational::Q, - traits::MatrixGetEntry, - }; - - // This function only allows for a broader availability, which is tested here. - - /// Checks if entries are properly reduced. - #[test] - fn reduced() { - let matrix = MatZq::sample_discrete_gauss(1, 1, 3, 0, 15.0).unwrap(); - let value: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= value && value <= 3) - } - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let n = Z::from(1024); - let center = Q::ZERO; - let s = Q::ONE; - let modulus = Modulus::from(83); - - let _ = MatZq::sample_discrete_gauss(2u64, 3i8, &modulus, 0f32, 1u16); - let _ = MatZq::sample_discrete_gauss(3u8, 2i16, 83u8, ¢er, 1u8); - let _ = MatZq::sample_discrete_gauss(1, 1, &n, ¢er, 1u32); - let _ = MatZq::sample_discrete_gauss(1, 1, 83i8, ¢er, 1u64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i32); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i16); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i8); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1i64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, &n); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, &s); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, ¢er, 1.25f64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, 0, 1.25f64); - let _ = MatZq::sample_discrete_gauss(1, 1, 83, center, 15.75f32); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { use crate::utils::sample::test_seed; use crate::{ integer::Z, @@ -296,9 +250,19 @@ mod test_sample_discrete_gauss_seeded { /// Checks if entries are properly reduced. #[test] fn reduced() { - let matrix = MatZq::sample_discrete_gauss_seeded(1, 1, 3, 0, 15.0, test_seed()).unwrap(); - let value: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= value && value <= 3) + for seeded in [false, true] { + let matrix = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 3, + 0, + 15.0, + seeded.then(test_seed), + ) + .unwrap(); + let value: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= value && value <= 3) + } } /// Checks whether `sample_discrete_gauss` is available for all types @@ -306,25 +270,139 @@ mod test_sample_discrete_gauss_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let n = Z::from(1024); - let center = Q::ZERO; - let s = Q::ONE; - let modulus = Modulus::from(83); - - let _ = MatZq::sample_discrete_gauss_seeded(2u64, 3i8, &modulus, 0f32, 1u16, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(3u8, 2i16, 83u8, ¢er, 1u8, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, &n, ¢er, 1u32, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83i8, ¢er, 1u64, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i64, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i32, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i16, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i8, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1i64, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, &n, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, &s, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, ¢er, 1.25f64, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, 0, 1.25f64, test_seed()); - let _ = MatZq::sample_discrete_gauss_seeded(1, 1, 83, center, 15.75f32, test_seed()); + for seeded in [false, true] { + let n = Z::from(1024); + let center = Q::ZERO; + let s = Q::ONE; + let modulus = Modulus::from(83); + + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 2u64, + 3i8, + &modulus, + 0f32, + 1u16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 3u8, + 2i16, + 83u8, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + &n, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83i8, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + 0, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_discrete_gauss_optionally_seeded( + 1, + 1, + 83, + center, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_discrete_gauss_seeded( + 2u64, + 3i8, + &modulus, + 0f32, + 1u16, + test_seed(), + ); + } else { + let _ = MatZq::sample_discrete_gauss(2u64, 3i8, &modulus, 0f32, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -336,73 +414,19 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } -} -#[cfg(test)] -mod test_sample_d { - use crate::{ - integer::Z, - integer_mod_q::MatZq, - rational::{MatQ, Q}, - }; - - // Appropriate inputs were tested in utils and thus omitted here. - // This function only allows for a broader availability, which is tested here. - - /// Checks whether `sample_d` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn availability() { - let basis = MatZq::identity(5, 5, 17); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - - let _ = MatZq::sample_d(&basis, ¢er, 1u16); - let _ = MatZq::sample_d(&basis, ¢er, 1u8); - let _ = MatZq::sample_d(&basis, ¢er, 1u32); - let _ = MatZq::sample_d(&basis, ¢er, 1u64); - let _ = MatZq::sample_d(&basis, ¢er, 1i64); - let _ = MatZq::sample_d(&basis, ¢er, 1i32); - let _ = MatZq::sample_d(&basis, ¢er, 1i16); - let _ = MatZq::sample_d(&basis, ¢er, 1i8); - let _ = MatZq::sample_d(&basis, ¢er, 1i64); - let _ = MatZq::sample_d(&basis, ¢er, &n); - let _ = MatZq::sample_d(&basis, ¢er, &s); - let _ = MatZq::sample_d(&basis, ¢er, 1.25f64); - let _ = MatZq::sample_d(&basis, ¢er, 15.75f32); - } + fn known_answer() { + use crate::integer_mod_q::MatZq; + let sample = MatZq::sample_discrete_gauss_seeded(3, 1, 83, 0, 1.25f32, [42; 32]).unwrap(); - /// Checks whether `sample_d_precomputed_gso` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability_prec_gso() { - let basis = MatZq::identity(5, 5, 17); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()); - - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u8); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u32); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i32); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i16); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i8); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1i64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &n); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &s); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1.25f64); - let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 15.75f32); + assert_eq!("[[1],[82],[0]] mod 83", sample.to_string()); } } #[cfg(test)] -mod test_sample_d_seeded { +mod test_sample_d { use crate::utils::sample::test_seed; use crate::{ integer::Z, @@ -418,24 +442,46 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let basis = MatZq::identity(5, 5, 17); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u8, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u32, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u64, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i32, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i16, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i8, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1i64, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, &n, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, &s, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 1.25f64, test_seed()); - let _ = MatZq::sample_d_seeded(&basis, ¢er, 15.75f32, test_seed()); + for seeded in [false, true] { + let basis = MatZq::identity(5, 5, 17); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u16, seeded.then(test_seed)); + let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u8, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u32, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1u64, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i32, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i16, seeded.then(test_seed)); + let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i8, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1i64, seeded.then(test_seed)); + let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, &n, seeded.then(test_seed)); + let _ = MatZq::sample_d_optionally_seeded(&basis, ¢er, &s, seeded.then(test_seed)); + let _ = + MatZq::sample_d_optionally_seeded(&basis, ¢er, 1.25f64, seeded.then(test_seed)); + let _ = MatZq::sample_d_optionally_seeded( + &basis, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_d_seeded(&basis, ¢er, 1u16, test_seed()); + } else { + let _ = MatZq::sample_d(&basis, ¢er, 1u16); + } + } } /// Checks whether `sample_d_precomputed_gso` is available for all types @@ -443,48 +489,118 @@ mod test_sample_d_seeded { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability_prec_gso() { - let basis = MatZq::identity(5, 5, 17); - let n = Z::from(1024); - let center = MatQ::new(5, 1); - let s = Q::ONE; - let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()); - - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u16, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u8, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u32, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1u64, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i32, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i16, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i8, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1i64, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &n, test_seed()); - let _ = - MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, &s, test_seed()); - let _ = MatZq::sample_d_precomputed_gso_seeded( - &basis, - &basis_gso, - ¢er, - 1.25f64, - test_seed(), - ); - let _ = MatZq::sample_d_precomputed_gso_seeded( - &basis, - &basis_gso, - ¢er, - 15.75f32, - test_seed(), - ); + for seeded in [false, true] { + let basis = MatZq::identity(5, 5, 17); + let n = Z::from(1024); + let center = MatQ::new(5, 1); + let s = Q::ONE; + let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()); + + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1u64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i32, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i16, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i8, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1i64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + &n, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + &s, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 1.25f64, + seeded.then(test_seed), + ); + let _ = MatZq::sample_d_precomputed_gso_optionally_seeded( + &basis, + &basis_gso, + ¢er, + 15.75f32, + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_d_precomputed_gso_seeded( + &basis, + &basis_gso, + ¢er, + 1u16, + test_seed(), + ); + } else { + let _ = MatZq::sample_d_precomputed_gso(&basis, &basis_gso, ¢er, 1u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -515,4 +631,29 @@ mod test_sample_d_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer_d() { + use crate::{integer_mod_q::MatZq, rational::MatQ}; + let basis = MatZq::identity(5, 5, 17); + let center = MatQ::new(5, 1); + let sample = MatZq::sample_d_seeded(&basis, ¢er, 1.25f32, [42; 32]).unwrap(); + + assert_eq!("[[0],[16],[0],[0],[0]] mod 17", sample.to_string()); + } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer_d_precomputed_gso() { + use crate::{integer_mod_q::MatZq, rational::MatQ}; + let basis = MatZq::identity(5, 5, 17); + let center = MatQ::new(5, 1); + let basis_gso = MatQ::from(&basis.get_representative_least_nonnegative_residue()).gso(); + let sample = + MatZq::sample_d_precomputed_gso_seeded(&basis, &basis_gso, ¢er, 1.25f32, [42; 32]) + .unwrap(); + + assert_eq!("[[0],[16],[0],[0],[0]] mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/mat_zq/sample/uniform.rs b/src/integer_mod_q/mat_zq/sample/uniform.rs index de0e1e69..2843f8d2 100644 --- a/src/integer_mod_q/mat_zq/sample/uniform.rs +++ b/src/integer_mod_q/mat_zq/sample/uniform.rs @@ -85,95 +85,6 @@ impl MatZq { #[cfg(test)] mod test_sample_uniform { - use crate::traits::{MatrixDimensions, MatrixGetEntry}; - use crate::{ - integer::Z, - integer_mod_q::{MatZq, Modulus}, - }; - - /// Checks whether the boundaries of the interval are kept for small moduli. - #[test] - fn boundaries_kept_small() { - for _ in 0..32 { - let matrix = MatZq::sample_uniform(1, 1, 17); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < 17); - } - } - - /// Checks whether the boundaries of the interval are kept for large moduli. - #[test] - fn boundaries_kept_large() { - let modulus = Z::from(u64::MAX); - for _ in 0..256 { - let matrix = MatZq::sample_uniform(1, 1, &modulus); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); - } - } - - /// Checks whether matrices with at least one dimension chosen smaller than `1` - /// or too large for an [`i64`] results in a panic. - #[should_panic] - #[test] - fn false_size() { - let modulus = Z::from(15); - - let _ = MatZq::sample_uniform(0, 3, &modulus); - } - - /// Checks whether providing an invalid interval/ modulus results in a panic. - #[should_panic] - #[test] - fn invalid_modulus() { - let _ = MatZq::sample_uniform(4, 1, 1); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = MatZq::sample_uniform(1, 1, 7u8); - let _ = MatZq::sample_uniform(1, 1, 7u16); - let _ = MatZq::sample_uniform(1, 1, 7u32); - let _ = MatZq::sample_uniform(1, 1, 7u64); - let _ = MatZq::sample_uniform(1, 1, 7i8); - let _ = MatZq::sample_uniform(1, 1, 7i16); - let _ = MatZq::sample_uniform(1, 1, 7i32); - let _ = MatZq::sample_uniform(1, 1, 7i64); - let _ = MatZq::sample_uniform(1, 1, &modulus); - let _ = MatZq::sample_uniform(1, 1, &z); - } - - /// Checks whether the size of uniformly random sampled matrices - /// fits the specified dimensions. - #[test] - fn matrix_size() { - let modulus = Z::from(15); - - let mat_0 = MatZq::sample_uniform(3, 3, &modulus); - let mat_1 = MatZq::sample_uniform(4, 1, &modulus); - let mat_2 = MatZq::sample_uniform(1, 5, &modulus); - let mat_3 = MatZq::sample_uniform(15, 20, &modulus); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::traits::{MatrixDimensions, MatrixGetEntry}; use crate::utils::sample::test_seed; use crate::{ @@ -184,81 +95,111 @@ mod test_sample_uniform_seeded { /// Checks whether the boundaries of the interval are kept for small moduli. #[test] fn boundaries_kept_small() { - for _ in 0..32 { - let matrix = MatZq::sample_uniform_seeded(1, 1, 17, test_seed()); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < 17); + for seeded in [false, true] { + for _ in 0..32 { + let matrix = + MatZq::sample_uniform_optionally_seeded(1, 1, 17, seeded.then(test_seed)); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < 17); + } } } /// Checks whether the boundaries of the interval are kept for large moduli. #[test] fn boundaries_kept_large() { - let modulus = Z::from(u64::MAX); - for _ in 0..256 { - let matrix = MatZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let sample: Z = matrix.get_entry(0, 0).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + for seeded in [false, true] { + let modulus = Z::from(u64::MAX); + for _ in 0..256 { + let matrix = + MatZq::sample_uniform_optionally_seeded(1, 1, &modulus, seeded.then(test_seed)); + let sample: Z = matrix.get_entry(0, 0).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } } } /// Checks whether matrices with at least one dimension chosen smaller than `1` /// or too large for an [`i64`] results in a panic. - #[should_panic] #[test] fn false_size() { - let modulus = Z::from(15); - - let _ = MatZq::sample_uniform_seeded(0, 3, &modulus, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = Z::from(15); + + let _ = + MatZq::sample_uniform_optionally_seeded(0, 3, &modulus, seeded.then(test_seed)); + }); + assert!(result.is_err()); + } } /// Checks whether providing an invalid interval/ modulus results in a panic. - #[should_panic] #[test] fn invalid_modulus() { - let _ = MatZq::sample_uniform_seeded(4, 1, 1, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatZq::sample_uniform_optionally_seeded(4, 1, 1, seeded.then(test_seed)); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = MatZq::sample_uniform_seeded(1, 1, 7u8, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7u16, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7u32, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7u64, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7i8, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7i16, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7i32, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, 7i64, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, &modulus, test_seed()); - let _ = MatZq::sample_uniform_seeded(1, 1, &z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7u8, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7u16, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7u32, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7u64, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7i8, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7i16, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7i32, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, 7i64, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, &modulus, seeded.then(test_seed)); + let _ = MatZq::sample_uniform_optionally_seeded(1, 1, &z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = MatZq::sample_uniform_seeded(1, 1, 7u8, test_seed()); + } else { + let _ = MatZq::sample_uniform(1, 1, 7u8); + } + } } /// Checks whether the size of uniformly random sampled matrices /// fits the specified dimensions. #[test] fn matrix_size() { - let modulus = Z::from(15); - - let mat_0 = MatZq::sample_uniform_seeded(3, 3, &modulus, test_seed()); - let mat_1 = MatZq::sample_uniform_seeded(4, 1, &modulus, test_seed()); - let mat_2 = MatZq::sample_uniform_seeded(1, 5, &modulus, test_seed()); - let mat_3 = MatZq::sample_uniform_seeded(15, 20, &modulus, test_seed()); - - assert_eq!(3, mat_0.get_num_rows()); - assert_eq!(3, mat_0.get_num_columns()); - assert_eq!(4, mat_1.get_num_rows()); - assert_eq!(1, mat_1.get_num_columns()); - assert_eq!(1, mat_2.get_num_rows()); - assert_eq!(5, mat_2.get_num_columns()); - assert_eq!(15, mat_3.get_num_rows()); - assert_eq!(20, mat_3.get_num_columns()); + for seeded in [false, true] { + let modulus = Z::from(15); + + let mat_0 = + MatZq::sample_uniform_optionally_seeded(3, 3, &modulus, seeded.then(test_seed)); + let mat_1 = + MatZq::sample_uniform_optionally_seeded(4, 1, &modulus, seeded.then(test_seed)); + let mat_2 = + MatZq::sample_uniform_optionally_seeded(1, 5, &modulus, seeded.then(test_seed)); + let mat_3 = + MatZq::sample_uniform_optionally_seeded(15, 20, &modulus, seeded.then(test_seed)); + + assert_eq!(3, mat_0.get_num_rows()); + assert_eq!(3, mat_0.get_num_columns()); + assert_eq!(4, mat_1.get_num_rows()); + assert_eq!(1, mat_1.get_num_columns()); + assert_eq!(1, mat_2.get_num_rows()); + assert_eq!(5, mat_2.get_num_columns()); + assert_eq!(15, mat_3.get_num_rows()); + assert_eq!(20, mat_3.get_num_columns()); + } } /// Checks whether the same seed results in the same sample. @@ -270,4 +211,16 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::MatZq; + let sample = MatZq::sample_uniform_seeded(3, 3, 17, [42; 32]); + + assert_eq!( + "[[2, 16, 3],[7, 7, 16],[4, 13, 0]] mod 17", + sample.to_string() + ); + } } diff --git a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs index 03e04249..d4760b39 100644 --- a/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/ntt_polynomial_ring_zq/sample/uniform.rs @@ -77,6 +77,7 @@ impl NTTPolynomialRingZq { #[cfg(test)] mod test_sample_uniform { + use crate::utils::sample::test_seed; use crate::{ integer::Z, integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}, @@ -86,75 +87,77 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - modulus.set_ntt_unchecked(64); - - let poly = NTTPolynomialRingZq::sample_uniform(&modulus); - - for i in 0..4 { - let sample = &poly.poly[i]; - assert!(&Z::ZERO <= sample); - assert!(sample < &Z::from(257)); + for seeded in [false, true] { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); + + let poly = NTTPolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); + + for i in 0..4 { + let sample = &poly.poly[i]; + assert!(&Z::ZERO <= sample); + assert!(sample < &Z::from(257)); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); - modulus.set_ntt_unchecked(64); + for seeded in [false, true] { + let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); + modulus.set_ntt_unchecked(64); - let res = NTTPolynomialRingZq::sample_uniform(&modulus); + let res = NTTPolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); - assert_eq!(4, res.poly.len(),); + assert_eq!(4, res.poly.len(),); + } } -} -#[cfg(test)] -mod test_sample_uniform_seeded { - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}, - }; - use std::str::FromStr; - - /// Checks whether the boundaries of the interval are kept. + /// Checks whether the same seed results in the same sample. #[test] - fn boundaries_kept() { + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}; + use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); + let sample_0 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + let sample_1 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - let poly = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); - - for i in 0..4 { - let sample = &poly.poly[i]; - assert!(&Z::ZERO <= sample); - assert!(sample < &Z::from(257)); - } + assert_eq!(sample_0, sample_1); } - /// Checks whether the number of coefficients is correct. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn nr_coeffs() { + fn known_answer() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}; + use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); + let sample = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - let res = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); - - assert_eq!(4, res.poly.len(),); + assert_eq!( + "[27, 144, 210, 163] / 5 1 0 0 0 1 mod 257", + sample.to_string() + ); } - /// Checks whether the same seed results in the same sample. + /// Checks whether the unseeded and the seeded function are available. #[test] - fn same_seed_same_sample() { + fn availability() { use crate::integer_mod_q::{ModulusPolynomialRingZq, NTTPolynomialRingZq}; use std::str::FromStr; let mut modulus = ModulusPolynomialRingZq::from_str("5 1 0 0 0 1 mod 257").unwrap(); modulus.set_ntt_unchecked(64); - let sample_0 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - let sample_1 = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - assert_eq!(sample_0, sample_1); + // explicitly checks the existence of the unseeded and the seeded function + let _ = NTTPolynomialRingZq::sample_uniform(&modulus); + let _ = NTTPolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); } } diff --git a/src/integer_mod_q/poly_over_zq/sample/binomial.rs b/src/integer_mod_q/poly_over_zq/sample/binomial.rs index 2b254a58..cec89e76 100644 --- a/src/integer_mod_q/poly_over_zq/sample/binomial.rs +++ b/src/integer_mod_q/poly_over_zq/sample/binomial.rs @@ -151,6 +151,7 @@ impl PolyOverZq { mod test_sample_binomial { use super::{PolyOverZq, Q, Z}; use crate::traits::GetCoefficient; + use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, // whether the correct interval is kept, and if the errors are thrown correctly, @@ -159,36 +160,68 @@ mod test_sample_binomial { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZq::sample_binomial(0, 7, 2, 0.5).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); + for seeded in [false, true] { + for _ in 0..8 { + let poly = PolyOverZq::sample_binomial_optionally_seeded( + 0, + 7, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_binomial(degree, u64::MAX, 256, 0.99999).unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "This test can fail with probability close to 0." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_binomial_optionally_seeded( + degree, + u64::MAX, + 256, + 0.99999, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "This test can fail with probability close to 0." + ); + } } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = PolyOverZq::sample_binomial(-1, 7, 2, 0.5); - let res_1 = PolyOverZq::sample_binomial(i64::MIN, 7, 2, 0.5); + for seeded in [false, true] { + let res_0 = PolyOverZq::sample_binomial_optionally_seeded( + -1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ); + let res_1 = PolyOverZq::sample_binomial_optionally_seeded( + i64::MIN, + 7, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial` is available for all types @@ -196,83 +229,92 @@ mod test_sample_binomial { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = PolyOverZq::sample_binomial(1, 7u8, 1u16, 7u8); - let _ = PolyOverZq::sample_binomial(1, 7u16, 1u32, 7u16); - let _ = PolyOverZq::sample_binomial(1, 7u32, 1u64, 7u32); - let _ = PolyOverZq::sample_binomial(1, 7u64, 1i8, 7u64); - let _ = PolyOverZq::sample_binomial(1, 7i8, 1i16, 7i8); - let _ = PolyOverZq::sample_binomial(1, 7i16, 1i32, 7i16); - let _ = PolyOverZq::sample_binomial(1, 7i32, 1i64, 7i32); - let _ = PolyOverZq::sample_binomial(1, 7i64, Z::ONE, 7i64); - let _ = PolyOverZq::sample_binomial(1, 7, 1u8, 0.5f32); - let _ = PolyOverZq::sample_binomial(1, 7, 1u8, 0.5f64); - let _ = PolyOverZq::sample_binomial(1, 7, 1, Q::from((1, 2))); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{PolyOverZq, Q, Z}; - use crate::traits::GetCoefficient; - use crate::utils::sample::test_seed; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZq::sample_binomial_seeded(0, 7, 2, 0.5, test_seed()).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = - PolyOverZq::sample_binomial_seeded(degree, u64::MAX, 256, 0.99999, test_seed()) - .unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "This test can fail with probability close to 0." + for seeded in [false, true] { + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7i32, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7i64, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_optionally_seeded( + 1, + 7, + 1, + Q::from((1, 2)), + seeded.then(test_seed), ); - } - } - - /// Checks whether providing a length smaller than `0` results in an error. - #[test] - fn invalid_max_degree() { - let res_0 = PolyOverZq::sample_binomial_seeded(-1, 7, 2, 0.5, test_seed()); - let res_1 = PolyOverZq::sample_binomial_seeded(i64::MIN, 7, 2, 0.5, test_seed()); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = PolyOverZq::sample_binomial_seeded(1, 7u8, 1u16, 7u8, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7u16, 1u32, 7u16, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7u32, 1u64, 7u32, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7u64, 1i8, 7u64, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7i8, 1i16, 7i8, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7i16, 1i32, 7i16, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7i32, 1i64, 7i32, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7i64, Z::ONE, 7i64, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1u8, 0.5f32, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1u8, 0.5f64, test_seed()); - let _ = PolyOverZq::sample_binomial_seeded(1, 7, 1, Q::from((1, 2)), test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZq::sample_binomial_seeded(1, 7u8, 1u16, 7u8, test_seed()); + } else { + let _ = PolyOverZq::sample_binomial(1, 7u8, 1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -284,69 +326,19 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } -} -#[cfg(test)] -mod test_sample_binomial_with_offset { - use super::{PolyOverZq, Q, Z}; - use crate::traits::GetCoefficient; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - for _ in 0..8 { - let poly = PolyOverZq::sample_binomial_with_offset(0, -1, 7, 2, 0.5).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_binomial_with_offset(degree, 1, 7, 2, 0.5).unwrap(); - - assert_eq!(degree, res.get_degree()); - } - } - - /// Checks whether providing a length smaller than `0` results in an error. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn invalid_max_degree() { - let res_0 = PolyOverZq::sample_binomial_with_offset(-1, -1, 7, 2, 0.5); - let res_1 = PolyOverZq::sample_binomial_with_offset(i64::MIN, -1, 7, 2, 0.5); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } + fn known_answer() { + use crate::integer_mod_q::PolyOverZq; + let sample = PolyOverZq::sample_binomial_seeded(2, 7, 2, 0.5, [42; 32]).unwrap(); - /// Checks whether `sample_binomial_with_offset` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - #[test] - fn availability() { - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u8, 1u16, 7u8); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u16, 1u32, 7u16); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u32, 1u64, 7u32); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u64, 1i8, 7u64); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7i8, 1i16, 7i8); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7i16, 1i32, 7i16); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7i32, 1i64, 7i32); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7i64, Z::ONE, 7i64); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7, 1u8, 0.5f32); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7, 1u8, 0.5f64); - let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7, 1, Q::from((1, 2))); + assert_eq!("3 0 2 1 mod 7", sample.to_string()); } } #[cfg(test)] -mod test_sample_binomial_with_offset_seeded { +mod test_sample_binomial_with_offset { use super::{PolyOverZq, Q, Z}; use crate::traits::GetCoefficient; use crate::utils::sample::test_seed; @@ -358,37 +350,68 @@ mod test_sample_binomial_with_offset_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - for _ in 0..8 { - let poly = - PolyOverZq::sample_binomial_with_offset_seeded(0, -1, 7, 2, 0.5, test_seed()) - .unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + for seeded in [false, true] { + for _ in 0..8 { + let poly = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 0, + -1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = - PolyOverZq::sample_binomial_with_offset_seeded(degree, 1, 7, 2, 0.5, test_seed()) - .unwrap(); - - assert_eq!(degree, res.get_degree()); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + degree, + 1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(degree, res.get_degree()); + } } } /// Checks whether providing a length smaller than `0` results in an error. #[test] fn invalid_max_degree() { - let res_0 = PolyOverZq::sample_binomial_with_offset_seeded(-1, -1, 7, 2, 0.5, test_seed()); - let res_1 = - PolyOverZq::sample_binomial_with_offset_seeded(i64::MIN, -1, 7, 2, 0.5, test_seed()); + for seeded in [false, true] { + let res_0 = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + -1, + -1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ); + let res_1 = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + i64::MIN, + -1, + 7, + 2, + 0.5, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_binomial_with_offset` is available for all types @@ -396,25 +419,110 @@ mod test_sample_binomial_with_offset_seeded { /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... #[test] fn availability() { - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u8, 1u16, 7u8, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u16, 1u32, 7u16, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u32, 1u64, 7u32, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7u64, 1i8, 7u64, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i8, 1i16, 7i8, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i16, 1i32, 7i16, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i32, 1i64, 7i32, test_seed()); - let _ = - PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7i64, Z::ONE, 7i64, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7, 1u8, 0.5f32, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded(1, 0, 7, 1u8, 0.5f64, test_seed()); - let _ = PolyOverZq::sample_binomial_with_offset_seeded( - 1, - 0, - 7, - 1, - Q::from((1, 2)), - test_seed(), - ); + for seeded in [false, true] { + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7u8, + 1u16, + 7u8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7u16, + 1u32, + 7u16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7u32, + 1u64, + 7u32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7u64, + 1i8, + 7u64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7i8, + 1i16, + 7i8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7i16, + 1i32, + 7i16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7i32, + 1i64, + 7i32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7i64, + Z::ONE, + 7i64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7, + 1u8, + 0.5f32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7, + 1u8, + 0.5f64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_binomial_with_offset_optionally_seeded( + 1, + 0, + 7, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZq::sample_binomial_with_offset_seeded( + 1, + 0, + 7u8, + 1u16, + 7u8, + test_seed(), + ); + } else { + let _ = PolyOverZq::sample_binomial_with_offset(1, 0, 7u8, 1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -428,4 +536,14 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::PolyOverZq; + let sample = + PolyOverZq::sample_binomial_with_offset_seeded(2, -1, 7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!("2 6 1 mod 7", sample.to_string()); + } } diff --git a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs index abac6bd5..39e4d7df 100644 --- a/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/poly_over_zq/sample/discrete_gauss.rs @@ -95,6 +95,7 @@ impl PolyOverZq { #[cfg(test)] mod test_sample_discrete_gauss { + use crate::utils::sample::test_seed; use crate::{integer::Z, integer_mod_q::PolyOverZq, rational::Q, traits::GetCoefficient}; /// Checks whether `sample_discrete_gauss` is available for all types @@ -102,121 +103,111 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - - let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1u8); - let _ = PolyOverZq::sample_discrete_gauss(1u16, 17u16, 0f64, 1u16); - let _ = PolyOverZq::sample_discrete_gauss(1u32, 17u32, 0f32, 1u32); - let _ = PolyOverZq::sample_discrete_gauss(1u64, 17u64, 0f64, 1u64); - let _ = PolyOverZq::sample_discrete_gauss(1i8, 17u8, 0f32, 1i8); - let _ = PolyOverZq::sample_discrete_gauss(1i8, 17i8, 0f32, 1i16); - let _ = PolyOverZq::sample_discrete_gauss(1i16, 17i16, 0f32, 1i32); - let _ = PolyOverZq::sample_discrete_gauss(1i32, 17i32, 0f64, 1i64); - let _ = PolyOverZq::sample_discrete_gauss(1i64, 17i64, center, s); - let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1f64); - } - - /// Checks whether the boundaries of the interval are kept for small moduli. - #[test] - fn boundaries_kept_small() { - let modulus = 128; - - for _ in 0..32 { - let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 15, 1).unwrap(); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); - } - } - } - - /// Checks whether the boundaries of the interval are kept for large moduli. - #[test] - fn boundaries_kept_large() { - let modulus = u64::MAX; - - for _ in 0..256 { - let poly = PolyOverZq::sample_discrete_gauss(3, modulus, 1, 1).unwrap(); + for seeded in [false, true] { + let center = Q::ZERO; + let s = Q::ONE; + + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1u8, + 17u8, + 0f32, + 1u8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1u16, + 17u16, + 0f64, + 1u16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1u32, + 17u32, + 0f32, + 1u32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1u64, + 17u64, + 0f64, + 1u64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1i8, + 17u8, + 0f32, + 1i8, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1i8, + 17i8, + 0f32, + 1i16, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1i16, + 17i16, + 0f32, + 1i32, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1i32, + 17i32, + 0f64, + 1i64, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1i64, + 17i64, + center, + s, + seeded.then(test_seed), + ); + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 1u8, + 17u8, + 0f32, + 1f64, + seeded.then(test_seed), + ); - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1u8, test_seed()); + } else { + let _ = PolyOverZq::sample_discrete_gauss(1u8, 17u8, 0f32, 1u8); } } } - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_discrete_gauss(degree, u64::MAX, i64::MAX, 1).unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." - ); - } - } - - /// Checks whether the maximum degree needs to be at least 0. - #[test] - #[should_panic] - fn invalid_max_degree() { - let _ = PolyOverZq::sample_discrete_gauss(-1, 17, 0, 1).unwrap(); - } - - /// Checks whether too small modulus is insufficient. - #[test] - #[should_panic] - fn invalid_modulus() { - let _ = PolyOverZq::sample_discrete_gauss(3, 1, 0, 1).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{integer::Z, integer_mod_q::PolyOverZq, rational::Q, traits::GetCoefficient}; - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - - let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1u8, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1u16, 17u16, 0f64, 1u16, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1u32, 17u32, 0f32, 1u32, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1u64, 17u64, 0f64, 1u64, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1i8, 17u8, 0f32, 1i8, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1i8, 17i8, 0f32, 1i16, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1i16, 17i16, 0f32, 1i32, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1i32, 17i32, 0f64, 1i64, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1i64, 17i64, center, s, test_seed()); - let _ = PolyOverZq::sample_discrete_gauss_seeded(1u8, 17u8, 0f32, 1f64, test_seed()); - } - /// Checks whether the boundaries of the interval are kept for small moduli. #[test] fn boundaries_kept_small() { - let modulus = 128; - - for _ in 0..32 { - let poly = - PolyOverZq::sample_discrete_gauss_seeded(3, modulus, 15, 1, test_seed()).unwrap(); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + for seeded in [false, true] { + let modulus = 128; + + for _ in 0..32 { + let poly = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 3, + modulus, + 15, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } } } } @@ -224,16 +215,24 @@ mod test_sample_discrete_gauss_seeded { /// Checks whether the boundaries of the interval are kept for large moduli. #[test] fn boundaries_kept_large() { - let modulus = u64::MAX; - - for _ in 0..256 { - let poly = - PolyOverZq::sample_discrete_gauss_seeded(3, modulus, 1, 1, test_seed()).unwrap(); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + for seeded in [false, true] { + let modulus = u64::MAX; + + for _ in 0..256 { + let poly = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 3, + modulus, + 1, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } } } } @@ -241,37 +240,61 @@ mod test_sample_discrete_gauss_seeded { /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_discrete_gauss_seeded( - degree, - u64::MAX, - i64::MAX, - 1, - test_seed(), - ) - .unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_discrete_gauss_optionally_seeded( + degree, + u64::MAX, + i64::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } } } /// Checks whether the maximum degree needs to be at least 0. #[test] - #[should_panic] fn invalid_max_degree() { - let _ = PolyOverZq::sample_discrete_gauss_seeded(-1, 17, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + -1, + 17, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether too small modulus is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let _ = PolyOverZq::sample_discrete_gauss_seeded(3, 1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverZq::sample_discrete_gauss_optionally_seeded( + 3, + 1, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -283,4 +306,13 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::PolyOverZq; + let sample = PolyOverZq::sample_discrete_gauss_seeded(2, 17, 0, 1, [42; 32]).unwrap(); + + assert_eq!("1 1 mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/poly_over_zq/sample/uniform.rs b/src/integer_mod_q/poly_over_zq/sample/uniform.rs index 0793cdc2..0831205a 100644 --- a/src/integer_mod_q/poly_over_zq/sample/uniform.rs +++ b/src/integer_mod_q/poly_over_zq/sample/uniform.rs @@ -91,105 +91,6 @@ impl PolyOverZq { #[cfg(test)] mod test_sample_uniform { - use crate::{ - integer::Z, - integer_mod_q::{Modulus, PolyOverZq}, - traits::GetCoefficient, - }; - - /// Checks whether the boundaries of the interval are kept for small moduli. - #[test] - fn boundaries_kept_small() { - let modulus = Z::from(17); - - let poly_zq = PolyOverZq::sample_uniform(32, &modulus).unwrap(); - - for i in 0..32 { - let sample: Z = poly_zq.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); - } - } - - /// Checks whether the boundaries of the interval are kept for large moduli. - #[test] - fn boundaries_kept_large() { - let modulus = Z::from(i64::MAX); - - let poly_zq = PolyOverZq::sample_uniform(256, &modulus).unwrap(); - - for i in 0..256 { - let sample: Z = poly_zq.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_uniform(degree, u64::MAX).unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "Could fail with probability 1/{}.", - u64::MAX - ); - } - } - - /// Checks whether providing an invalid interval/ modulus results in an error. - #[test] - #[should_panic] - fn invalid_modulus_negative() { - let _ = PolyOverZq::sample_uniform(1, i64::MIN); - } - - /// Checks whether providing an invalid interval/ modulus results in an error. - #[test] - #[should_panic] - fn invalid_modulus_one() { - let _ = PolyOverZq::sample_uniform(1, 1); - } - - /// Checks whether providing a length smaller than `1` results in an error. - #[test] - fn invalid_max_degree() { - let modulus = Z::from(15); - - let res_0 = PolyOverZq::sample_uniform(-1, &modulus); - let res_1 = PolyOverZq::sample_uniform(i64::MIN, &modulus); - - assert!(res_0.is_err()); - assert!(res_1.is_err()); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(10); - let z = Z::from(10); - - let _ = PolyOverZq::sample_uniform(1u64, 10u16).unwrap(); - let _ = PolyOverZq::sample_uniform(1i64, 10u32).unwrap(); - let _ = PolyOverZq::sample_uniform(1u8, 10u64).unwrap(); - let _ = PolyOverZq::sample_uniform(1u16, 10i8).unwrap(); - let _ = PolyOverZq::sample_uniform(1u32, 10i16).unwrap(); - let _ = PolyOverZq::sample_uniform(1i32, 10i32).unwrap(); - let _ = PolyOverZq::sample_uniform(1i16, 10i64).unwrap(); - let _ = PolyOverZq::sample_uniform(1i8, &z).unwrap(); - let _ = PolyOverZq::sample_uniform(1, z).unwrap(); - let _ = PolyOverZq::sample_uniform(1, &modulus).unwrap(); - let _ = PolyOverZq::sample_uniform(1, modulus).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::utils::sample::test_seed; use crate::{ integer::Z, @@ -200,91 +101,154 @@ mod test_sample_uniform_seeded { /// Checks whether the boundaries of the interval are kept for small moduli. #[test] fn boundaries_kept_small() { - let modulus = Z::from(17); + for seeded in [false, true] { + let modulus = Z::from(17); - let poly_zq = PolyOverZq::sample_uniform_seeded(32, &modulus, test_seed()).unwrap(); + let poly_zq = + PolyOverZq::sample_uniform_optionally_seeded(32, &modulus, seeded.then(test_seed)) + .unwrap(); - for i in 0..32 { - let sample: Z = poly_zq.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + for i in 0..32 { + let sample: Z = poly_zq.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } } } /// Checks whether the boundaries of the interval are kept for large moduli. #[test] fn boundaries_kept_large() { - let modulus = Z::from(i64::MAX); + for seeded in [false, true] { + let modulus = Z::from(i64::MAX); - let poly_zq = PolyOverZq::sample_uniform_seeded(256, &modulus, test_seed()).unwrap(); + let poly_zq = + PolyOverZq::sample_uniform_optionally_seeded(256, &modulus, seeded.then(test_seed)) + .unwrap(); - for i in 0..256 { - let sample: Z = poly_zq.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus); + for i in 0..256 { + let sample: Z = poly_zq.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverZq::sample_uniform_seeded(degree, u64::MAX, test_seed()).unwrap(); - - assert_eq!( - degree, - res.get_degree(), - "Could fail with probability 1/{}.", - u64::MAX - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverZq::sample_uniform_optionally_seeded( + degree, + u64::MAX, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + degree, + res.get_degree(), + "Could fail with probability 1/{}.", + u64::MAX + ); + } } } /// Checks whether providing an invalid interval/ modulus results in an error. #[test] - #[should_panic] fn invalid_modulus_negative() { - let _ = PolyOverZq::sample_uniform_seeded(1, i64::MIN, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverZq::sample_uniform_optionally_seeded( + 1, + i64::MIN, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether providing an invalid interval/ modulus results in an error. #[test] - #[should_panic] fn invalid_modulus_one() { - let _ = PolyOverZq::sample_uniform_seeded(1, 1, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverZq::sample_uniform_optionally_seeded(1, 1, seeded.then(test_seed)); + }); + assert!(result.is_err()); + } } /// Checks whether providing a length smaller than `1` results in an error. #[test] fn invalid_max_degree() { - let modulus = Z::from(15); - - let res_0 = PolyOverZq::sample_uniform_seeded(-1, &modulus, test_seed()); - let res_1 = PolyOverZq::sample_uniform_seeded(i64::MIN, &modulus, test_seed()); + for seeded in [false, true] { + let modulus = Z::from(15); + + let res_0 = + PolyOverZq::sample_uniform_optionally_seeded(-1, &modulus, seeded.then(test_seed)); + let res_1 = PolyOverZq::sample_uniform_optionally_seeded( + i64::MIN, + &modulus, + seeded.then(test_seed), + ); - assert!(res_0.is_err()); - assert!(res_1.is_err()); + assert!(res_0.is_err()); + assert!(res_1.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(10); - let z = Z::from(10); - - let _ = PolyOverZq::sample_uniform_seeded(1u64, 10u16, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1i64, 10u32, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1u8, 10u64, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1u16, 10i8, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1u32, 10i16, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1i32, 10i32, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1i16, 10i64, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1i8, &z, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1, z, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1, &modulus, test_seed()).unwrap(); - let _ = PolyOverZq::sample_uniform_seeded(1, modulus, test_seed()).unwrap(); + for seeded in [false, true] { + let modulus = Modulus::from(10); + let z = Z::from(10); + + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1u64, 10u16, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1i64, 10u32, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1u8, 10u64, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1u16, 10i8, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1u32, 10i16, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1i32, 10i32, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1i16, 10i64, seeded.then(test_seed)) + .unwrap(); + let _ = PolyOverZq::sample_uniform_optionally_seeded(1i8, &z, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1, z, seeded.then(test_seed)).unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1, &modulus, seeded.then(test_seed)) + .unwrap(); + let _ = + PolyOverZq::sample_uniform_optionally_seeded(1, modulus, seeded.then(test_seed)) + .unwrap(); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverZq::sample_uniform_seeded(1u64, 10u16, test_seed()); + } else { + let _ = PolyOverZq::sample_uniform(1u64, 10u16); + } + } } /// Checks whether the same seed results in the same sample. @@ -296,4 +260,13 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::PolyOverZq; + let sample = PolyOverZq::sample_uniform_seeded(3, 17, [42; 32]).unwrap(); + + assert_eq!("4 2 16 3 7 mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs b/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs index e7b3a872..f1d49190 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/binomial.rs @@ -146,60 +146,6 @@ impl PolynomialRingZq { #[cfg(test)] mod test_sample_binomial { - use super::{PolynomialRingZq, Z}; - use crate::{ - integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq}, - traits::{GetCoefficient, SetCoefficient}, - }; - use std::str::FromStr; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let poly = PolynomialRingZq::sample_binomial(&modulus, 2, 0.5).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); - } - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_binomial(&modulus, 256, 0.99999).unwrap(); - - assert_eq!( - modulus.get_degree(), - res.get_degree() + 1, - "This test can fail with probability close to 0." - ); - } - } - - /// Checks whether 0 modulus polynomial is insufficient. - #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_binomial(&modulus, 2, 0.5); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { use super::{PolynomialRingZq, Z}; use crate::utils::sample::test_seed; use crate::{ @@ -215,43 +161,66 @@ mod test_sample_binomial_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let poly = - PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, test_seed()).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::ZERO <= sample || sample <= 2); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let poly = PolynomialRingZq::sample_binomial_optionally_seeded( + &modulus, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::ZERO <= sample || sample <= 2); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_binomial_seeded(&modulus, 256, 0.99999, test_seed()) + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_binomial_optionally_seeded( + &modulus, + 256, + 0.99999, + seeded.then(test_seed), + ) .unwrap(); - assert_eq!( - modulus.get_degree(), - res.get_degree() + 1, - "This test can fail with probability close to 0." - ); + assert_eq!( + modulus.get_degree(), + res.get_degree() + 1, + "This test can fail with probability close to 0." + ); + } } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_binomial_optionally_seeded( + &modulus, + 2, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -267,60 +236,33 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_binomial_with_offset { - use super::{PolynomialRingZq, Z}; - use crate::{ - integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq}, - traits::{GetCoefficient, SetCoefficient}, - }; - use std::str::FromStr; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether the boundaries of the interval are kept. - #[test] - fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let poly = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5).unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); - } - } - /// Checks whether the number of coefficients is correct. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_binomial_with_offset(&modulus, 1, 2, 0.5).unwrap(); + fn known_answer() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample = PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, [42; 32]).unwrap(); - assert_eq!(modulus.get_degree(), res.get_degree() + 1); - } + assert_eq!("2 0 2 / 3 1 2 1 mod 17", sample.to_string()); } - /// Checks whether 0 modulus polynomial is insufficient. + /// Checks whether the unseeded and the seeded function are available. #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + fn availability() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - let _ = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5); + // explicitly checks the existence of the unseeded and the seeded function + let _ = PolynomialRingZq::sample_binomial(&modulus, 2, 0.5); + let _ = PolynomialRingZq::sample_binomial_seeded(&modulus, 2, 0.5, test_seed()); } } #[cfg(test)] -mod test_sample_binomial_with_offset_seeded { +mod test_sample_binomial_with_offset { use super::{PolynomialRingZq, Z}; use crate::utils::sample::test_seed; use crate::{ @@ -336,52 +278,65 @@ mod test_sample_binomial_with_offset_seeded { /// Checks whether the boundaries of the interval are kept. #[test] fn boundaries_kept() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..8 { - let poly = PolynomialRingZq::sample_binomial_with_offset_seeded( - &modulus, - -1, - 2, - 0.5, - test_seed(), - ) - .unwrap(); - let sample: Z = poly.get_coeff(0).unwrap(); - assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..8 { + let poly = PolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + &modulus, + -1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + let sample: Z = poly.get_coeff(0).unwrap(); + assert!(Z::MINUS_ONE <= sample || sample <= Z::ONE); + } } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_binomial_with_offset_seeded( - &modulus, - 1, - 2, - 0.5, - test_seed(), - ) - .unwrap(); - - assert_eq!(modulus.get_degree(), res.get_degree() + 1); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + &modulus, + 1, + 2, + 0.5, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!(modulus.get_degree(), res.get_degree() + 1); + } } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = - PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, test_seed()); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_binomial_with_offset_optionally_seeded( + &modulus, + -1, + 2, + 0.5, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -399,4 +354,30 @@ mod test_sample_binomial_with_offset_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample = + PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, [42; 32]) + .unwrap(); + + assert_eq!("2 -1 1 / 3 1 2 1 mod 17", sample.to_string()); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + + // explicitly checks the existence of the unseeded and the seeded function + let _ = PolynomialRingZq::sample_binomial_with_offset(&modulus, -1, 2, 0.5); + let _ = + PolynomialRingZq::sample_binomial_with_offset_seeded(&modulus, -1, 2, 0.5, test_seed()); + } } diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs index 2124fd0d..c4922a04 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/discrete_gauss.rs @@ -89,6 +89,7 @@ impl PolynomialRingZq { #[cfg(test)] mod test_sample_discrete_gauss { + use crate::utils::sample::test_seed; use crate::{ integer::Z, integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, @@ -102,129 +103,106 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1u8); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1u16); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1u32); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1u64); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i8); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i16); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1i32); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f64, 1i64); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, center, s); - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1f64); - } - - /// Checks whether the boundaries of the interval are kept for small moduli. - #[test] - fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 15, 1).unwrap(); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); - } - } - } - - /// Checks whether the boundaries of the interval are kept for large moduli. - #[test] - fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let poly = PolynomialRingZq::sample_discrete_gauss(&modulus, 1, 1).unwrap(); + for seeded in [false, true] { + let center = Q::ZERO; + let s = Q::ONE; + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1u8, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f64, + 1u16, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1u32, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f64, + 1u64, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1i8, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1i16, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1i32, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f64, + 1i64, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + center, + s, + seeded.then(test_seed), + ); + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0f32, + 1f64, + seeded.then(test_seed), + ); - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolynomialRingZq::sample_discrete_gauss_seeded( + &modulus, + 0f32, + 1u8, + test_seed(), + ); + } else { + let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0f32, 1u8); } } } - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_discrete_gauss(&modulus, i64::MAX, 1).unwrap(); - - assert_eq!( - res.get_degree() + 1, - modulus.get_degree(), - "Could fail with negligible probability." - ); - } - } - - /// Checks whether 0 degree modulus polynomial is insufficient. - #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_discrete_gauss(&modulus, 0, 1).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, - rational::Q, - traits::{GetCoefficient, SetCoefficient}, - }; - use std::str::FromStr; - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let center = Q::ZERO; - let s = Q::ONE; - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1u8, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1u16, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1u32, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1u64, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i8, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i16, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1i32, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f64, 1i64, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, center, s, test_seed()); - let _ = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0f32, 1f64, test_seed()); - } - /// Checks whether the boundaries of the interval are kept for small moduli. #[test] fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let poly = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 15, 1, test_seed()) + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let poly = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 15, + 1, + seeded.then(test_seed), + ) .unwrap(); - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } } } } @@ -232,17 +210,24 @@ mod test_sample_discrete_gauss_seeded { /// Checks whether the boundaries of the interval are kept for large moduli. #[test] fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let poly = PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 1, 1, test_seed()) + for seeded in [false, true] { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let poly = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 1, + 1, + seeded.then(test_seed), + ) .unwrap(); - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } } } } @@ -250,32 +235,47 @@ mod test_sample_discrete_gauss_seeded { /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = - PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, i64::MAX, 1, test_seed()) - .unwrap(); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + i64::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); - assert_eq!( - res.get_degree() + 1, - modulus.get_degree(), - "Could fail with negligible probability." - ); + assert_eq!( + res.get_degree() + 1, + modulus.get_degree(), + "Could fail with negligible probability." + ); + } } } /// Checks whether 0 degree modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = - PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_discrete_gauss_optionally_seeded( + &modulus, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -291,4 +291,16 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample = + PolynomialRingZq::sample_discrete_gauss_seeded(&modulus, 0, 1, [42; 32]).unwrap(); + + assert_eq!("1 1 / 3 1 2 1 mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs b/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs index c1780863..01461684 100644 --- a/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs +++ b/src/integer_mod_q/polynomial_ring_zq/sample/uniform.rs @@ -87,6 +87,7 @@ impl PolynomialRingZq { #[cfg(test)] mod test_sample_uniform { + use crate::utils::sample::test_seed; use crate::{ integer::Z, integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, @@ -97,15 +98,20 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept for small moduli. #[test] fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let poly = PolynomialRingZq::sample_uniform(&modulus); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); + for seeded in [false, true] { + let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); + + for _ in 0..32 { + let poly = PolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } } } } @@ -113,16 +119,21 @@ mod test_sample_uniform { /// Checks whether the boundaries of the interval are kept for large moduli. #[test] fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let poly = PolynomialRingZq::sample_uniform(&modulus); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); + for seeded in [false, true] { + let modulus = + ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); + + for _ in 0..256 { + let poly = PolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); + + for i in 0..3 { + let sample: Z = poly.get_coeff(i).unwrap(); + assert!(Z::ZERO <= sample); + assert!(sample < modulus.get_q()); + } } } } @@ -130,114 +141,76 @@ mod test_sample_uniform { /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_uniform(&modulus); - - assert_eq!( - res.get_degree() + 1, - modulus.get_degree(), - "Could fail with probability 1/{}.", - u64::MAX - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let mut modulus = PolyOverZq::from((1, u64::MAX)); + modulus.set_coeff(degree, 1).unwrap(); + let modulus = ModulusPolynomialRingZq::from(&modulus); + + let res = PolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); + + assert_eq!( + res.get_degree() + 1, + modulus.get_degree(), + "Could fail with probability 1/{}.", + u64::MAX + ); + } } } /// Checks whether 0 modulus polynomial is insufficient. #[test] - #[should_panic] fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); - - let _ = PolynomialRingZq::sample_uniform(&modulus); - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - integer_mod_q::{ModulusPolynomialRingZq, PolyOverZq, PolynomialRingZq}, - traits::{GetCoefficient, SetCoefficient}, - }; - use std::str::FromStr; - - /// Checks whether the boundaries of the interval are kept for small moduli. - #[test] - fn boundaries_kept_small() { - let modulus = ModulusPolynomialRingZq::from_str("4 1 0 0 1 mod 17").unwrap(); - - for _ in 0..32 { - let poly = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); - } + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + + let _ = PolynomialRingZq::sample_uniform_optionally_seeded( + &modulus, + seeded.then(test_seed), + ); + }); + assert!(result.is_err()); } } - /// Checks whether the boundaries of the interval are kept for large moduli. + /// Checks whether the same seed results in the same sample. #[test] - fn boundaries_kept_large() { - let modulus = - ModulusPolynomialRingZq::from_str(&format!("4 1 0 0 1 mod {}", u64::MAX)).unwrap(); - - for _ in 0..256 { - let poly = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); - - for i in 0..3 { - let sample: Z = poly.get_coeff(i).unwrap(); - assert!(Z::ZERO <= sample); - assert!(sample < modulus.get_q()); - } - } - } + fn same_seed_same_sample() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample_0 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); + let sample_1 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let mut modulus = PolyOverZq::from((1, u64::MAX)); - modulus.set_coeff(degree, 1).unwrap(); - let modulus = ModulusPolynomialRingZq::from(&modulus); - - let res = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); - - assert_eq!( - res.get_degree() + 1, - modulus.get_degree(), - "Could fail with probability 1/{}.", - u64::MAX - ); - } + assert_eq!(sample_0, sample_1); } - /// Checks whether 0 modulus polynomial is insufficient. + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - #[should_panic] - fn invalid_modulus() { - let modulus = ModulusPolynomialRingZq::from_str("1 1 mod 17").unwrap(); + fn known_answer() { + use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; + use std::str::FromStr; + let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); + let sample = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - let _ = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); + assert_eq!("2 2 16 / 3 1 2 1 mod 17", sample.to_string()); } - /// Checks whether the same seed results in the same sample. + /// Checks whether the unseeded and the seeded function are available. #[test] - fn same_seed_same_sample() { + fn availability() { use crate::integer_mod_q::{ModulusPolynomialRingZq, PolynomialRingZq}; use std::str::FromStr; let modulus = ModulusPolynomialRingZq::from_str("3 1 2 1 mod 17").unwrap(); - let sample_0 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - let sample_1 = PolynomialRingZq::sample_uniform_seeded(&modulus, [42; 32]); - assert_eq!(sample_0, sample_1); + // explicitly checks the existence of the unseeded and the seeded function + let _ = PolynomialRingZq::sample_uniform(&modulus); + let _ = PolynomialRingZq::sample_uniform_seeded(&modulus, test_seed()); } } diff --git a/src/integer_mod_q/z_q/sample/binomial.rs b/src/integer_mod_q/z_q/sample/binomial.rs index b6981d4b..45b12bbf 100644 --- a/src/integer_mod_q/z_q/sample/binomial.rs +++ b/src/integer_mod_q/z_q/sample/binomial.rs @@ -76,34 +76,6 @@ impl Zq { #[cfg(test)] mod test_sample_binomial { use super::{Q, Z, Zq}; - - // As all major tests regarding an appropriate binomial distribution, - // whether the correct interval is kept, and if the errors are thrown correctly, - // are performed in the `utils` module, we omit these tests here. - - /// Checks whether `sample_binomial` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// and [`Into`], i.e. u8, u16, i8, i16, f32, f64, ... - /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let _ = Zq::sample_binomial(7u8, 1u16, 7u8); - let _ = Zq::sample_binomial(7u16, 1u32, 7u16); - let _ = Zq::sample_binomial(7u32, 1u64, 7u32); - let _ = Zq::sample_binomial(7u64, 1i8, 7u64); - let _ = Zq::sample_binomial(7i8, 1i16, 7i8); - let _ = Zq::sample_binomial(7i16, 1i32, 7i16); - let _ = Zq::sample_binomial(7i32, 1i64, 7i32); - let _ = Zq::sample_binomial(7i64, Z::ONE, 7i64); - let _ = Zq::sample_binomial(7, 1u8, 0.5f32); - let _ = Zq::sample_binomial(7, 1u8, 0.5f64); - let _ = Zq::sample_binomial(7, 1, Q::from((1, 2))); - } -} - -#[cfg(test)] -mod test_sample_binomial_seeded { - use super::{Q, Z, Zq}; use crate::utils::sample::test_seed; // As all major tests regarding an appropriate binomial distribution, @@ -116,17 +88,32 @@ mod test_sample_binomial_seeded { /// and [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let _ = Zq::sample_binomial_seeded(7u8, 1u16, 7u8, test_seed()); - let _ = Zq::sample_binomial_seeded(7u16, 1u32, 7u16, test_seed()); - let _ = Zq::sample_binomial_seeded(7u32, 1u64, 7u32, test_seed()); - let _ = Zq::sample_binomial_seeded(7u64, 1i8, 7u64, test_seed()); - let _ = Zq::sample_binomial_seeded(7i8, 1i16, 7i8, test_seed()); - let _ = Zq::sample_binomial_seeded(7i16, 1i32, 7i16, test_seed()); - let _ = Zq::sample_binomial_seeded(7i32, 1i64, 7i32, test_seed()); - let _ = Zq::sample_binomial_seeded(7i64, Z::ONE, 7i64, test_seed()); - let _ = Zq::sample_binomial_seeded(7, 1u8, 0.5f32, test_seed()); - let _ = Zq::sample_binomial_seeded(7, 1u8, 0.5f64, test_seed()); - let _ = Zq::sample_binomial_seeded(7, 1, Q::from((1, 2)), test_seed()); + for seeded in [false, true] { + let _ = Zq::sample_binomial_optionally_seeded(7u8, 1u16, 7u8, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7u16, 1u32, 7u16, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7u32, 1u64, 7u32, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7u64, 1i8, 7u64, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7i8, 1i16, 7i8, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7i16, 1i32, 7i16, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7i32, 1i64, 7i32, seeded.then(test_seed)); + let _ = + Zq::sample_binomial_optionally_seeded(7i64, Z::ONE, 7i64, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7, 1u8, 0.5f32, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded(7, 1u8, 0.5f64, seeded.then(test_seed)); + let _ = Zq::sample_binomial_optionally_seeded( + 7, + 1, + Q::from((1, 2)), + seeded.then(test_seed), + ); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Zq::sample_binomial_seeded(7u8, 1u16, 7u8, test_seed()); + } else { + let _ = Zq::sample_binomial(7u8, 1u16, 7u8); + } + } } /// Checks whether the same seed results in the same sample. @@ -138,4 +125,13 @@ mod test_sample_binomial_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::Zq; + let sample = Zq::sample_binomial_seeded(7, 2, 0.5, [42; 32]).unwrap(); + + assert_eq!("0 mod 7", sample.to_string()); + } } diff --git a/src/integer_mod_q/z_q/sample/discrete_gauss.rs b/src/integer_mod_q/z_q/sample/discrete_gauss.rs index 506f5de4..0e88058f 100644 --- a/src/integer_mod_q/z_q/sample/discrete_gauss.rs +++ b/src/integer_mod_q/z_q/sample/discrete_gauss.rs @@ -88,6 +88,7 @@ impl Zq { #[cfg(test)] mod test_sample_discrete_gauss { + use crate::utils::sample::test_seed; use crate::{integer::Z, integer_mod_q::Zq, rational::Q}; // Appropriate interval and invalid inputs were tested in @@ -98,104 +99,108 @@ mod test_sample_discrete_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let z = Z::from(2); - let q = Q::from(2); - - let _ = Zq::sample_discrete_gauss(17u64, 7u8, 1u16); - let _ = Zq::sample_discrete_gauss(17u16, 7u16, 1u8); - let _ = Zq::sample_discrete_gauss(17u8, 7u32, 1u32); - let _ = Zq::sample_discrete_gauss(17u32, 7u64, 1u64); - let _ = Zq::sample_discrete_gauss(17i16, 7i8, 1i64); - let _ = Zq::sample_discrete_gauss(17i8, 7i16, 1i32); - let _ = Zq::sample_discrete_gauss(17i64, 7i32, 1i16); - let _ = Zq::sample_discrete_gauss(17i32, 7i64, 1i8); - let _ = Zq::sample_discrete_gauss(17, q.clone(), 1i64); - let _ = Zq::sample_discrete_gauss(z.clone(), 0i8, z.clone()); - let _ = Zq::sample_discrete_gauss(17, z.clone(), q.clone()); - let _ = Zq::sample_discrete_gauss(17, 1f32, 1f64); - let _ = Zq::sample_discrete_gauss(17, 1f64, 1f32); - - // With references - let _ = Zq::sample_discrete_gauss(17i64, 7i32, 1i16); - let _ = Zq::sample_discrete_gauss(17i32, 7i64, 1i8); - let _ = Zq::sample_discrete_gauss(17, &q, 1i64); - let _ = Zq::sample_discrete_gauss(&z, 0i8, &z); - let _ = Zq::sample_discrete_gauss(17, &z, &q); - } + for seeded in [false, true] { + let z = Z::from(2); + let q = Q::from(2); - /// Roughly checks the collected samples are distributed - /// according to the discrete Gaussian distribution. - /// - /// This test could possibly fail for a truly uniform distribution - /// with probability smaller than 1/1000. - #[test] - fn distribution() { - let mut counts = [0; 20]; - // count sampled instances - for _ in 0..200 { - let sample = Zq::sample_discrete_gauss(20, 0, 2).unwrap(); - let sample_int = i64::try_from(&sample.get_representative_least_nonnegative_residue()) - .unwrap() as usize; - counts[sample_int] += 1; - } + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17u64, + 7u8, + 1u16, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17u16, + 7u16, + 1u8, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17u8, + 7u32, + 1u32, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17u32, + 7u64, + 1u64, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i16, + 7i8, + 1i64, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i8, + 7i16, + 1i32, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i64, + 7i32, + 1i16, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i32, + 7i64, + 1i8, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17, + q.clone(), + 1i64, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + z.clone(), + 0i8, + z.clone(), + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17, + z.clone(), + q.clone(), + seeded.then(test_seed), + ); + let _ = + Zq::sample_discrete_gauss_optionally_seeded(17, 1f32, 1f64, seeded.then(test_seed)); + let _ = + Zq::sample_discrete_gauss_optionally_seeded(17, 1f64, 1f32, seeded.then(test_seed)); - let expl_text = String::from("This test can fail with probability close to 0. - It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. - If this happens, rerun the tests several times and check whether this issue comes up again."); + // With references + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i64, + 7i32, + 1i16, + seeded.then(test_seed), + ); + let _ = Zq::sample_discrete_gauss_optionally_seeded( + 17i32, + 7i64, + 1i8, + seeded.then(test_seed), + ); + let _ = + Zq::sample_discrete_gauss_optionally_seeded(17, &q, 1i64, seeded.then(test_seed)); + let _ = + Zq::sample_discrete_gauss_optionally_seeded(&z, 0i8, &z, seeded.then(test_seed)); + let _ = Zq::sample_discrete_gauss_optionally_seeded(17, &z, &q, seeded.then(test_seed)); - // Check that the sampled occurrences roughly look - // like a discrete Gaussian distribution - assert!(counts[0] > 70, "{expl_text}"); - assert!(counts[0] < 130, "{expl_text}"); - assert!(counts[19] > 20, "{expl_text}"); - assert!(counts[19] < 70, "{expl_text}"); - assert!(counts[1] > 20, "{expl_text}"); - assert!(counts[1] < 70, "{expl_text}"); - assert!(counts[18] < 20, "{expl_text}"); - assert!(counts[2] < 20, "{expl_text}"); - for count in counts.iter().take(18).skip(3) { - assert!(count < &10, "{expl_text}"); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Zq::sample_discrete_gauss_seeded(17u64, 7u8, 1u16, test_seed()); + } else { + let _ = Zq::sample_discrete_gauss(17u64, 7u8, 1u16); + } } } -} - -#[cfg(test)] -mod test_sample_discrete_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{integer::Z, integer_mod_q::Zq, rational::Q}; - - // Appropriate interval and invalid inputs were tested in - // utils::sample::discrete_gauss::test_sample_z and are thus omitted here. - - /// Checks whether `sample_discrete_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let z = Z::from(2); - let q = Q::from(2); - - let _ = Zq::sample_discrete_gauss_seeded(17u64, 7u8, 1u16, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17u16, 7u16, 1u8, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17u8, 7u32, 1u32, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17u32, 7u64, 1u64, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17i16, 7i8, 1i64, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17i8, 7i16, 1i32, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17i64, 7i32, 1i16, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17i32, 7i64, 1i8, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, q.clone(), 1i64, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(z.clone(), 0i8, z.clone(), test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, z.clone(), q.clone(), test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, 1f32, 1f64, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, 1f64, 1f32, test_seed()); - - // With references - let _ = Zq::sample_discrete_gauss_seeded(17i64, 7i32, 1i16, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17i32, 7i64, 1i8, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, &q, 1i64, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(&z, 0i8, &z, test_seed()); - let _ = Zq::sample_discrete_gauss_seeded(17, &z, &q, test_seed()); - } /// Roughly checks the collected samples are distributed /// according to the discrete Gaussian distribution. @@ -204,31 +209,36 @@ mod test_sample_discrete_gauss_seeded { /// with probability smaller than 1/1000. #[test] fn distribution() { - let mut counts = [0; 20]; - // count sampled instances - for _ in 0..200 { - let sample = Zq::sample_discrete_gauss_seeded(20, 0, 2, test_seed()).unwrap(); - let sample_int = i64::try_from(&sample.get_representative_least_nonnegative_residue()) - .unwrap() as usize; - counts[sample_int] += 1; - } + for seeded in [false, true] { + let mut counts = [0; 20]; + // count sampled instances + for _ in 0..200 { + let sample = + Zq::sample_discrete_gauss_optionally_seeded(20, 0, 2, seeded.then(test_seed)) + .unwrap(); + let sample_int = + i64::try_from(&sample.get_representative_least_nonnegative_residue()).unwrap() + as usize; + counts[sample_int] += 1; + } - let expl_text = String::from("This test can fail with probability close to 0. + let expl_text = String::from("This test can fail with probability close to 0. It fails if the sampled occurrences do not look like a typical discrete Gaussian random distribution. If this happens, rerun the tests several times and check whether this issue comes up again."); - // Check that the sampled occurrences roughly look - // like a discrete Gaussian distribution - assert!(counts[0] > 70, "{expl_text}"); - assert!(counts[0] < 130, "{expl_text}"); - assert!(counts[19] > 20, "{expl_text}"); - assert!(counts[19] < 70, "{expl_text}"); - assert!(counts[1] > 20, "{expl_text}"); - assert!(counts[1] < 70, "{expl_text}"); - assert!(counts[18] < 20, "{expl_text}"); - assert!(counts[2] < 20, "{expl_text}"); - for count in counts.iter().take(18).skip(3) { - assert!(count < &10, "{expl_text}"); + // Check that the sampled occurrences roughly look + // like a discrete Gaussian distribution + assert!(counts[0] > 70, "{expl_text}"); + assert!(counts[0] < 130, "{expl_text}"); + assert!(counts[19] > 20, "{expl_text}"); + assert!(counts[19] < 70, "{expl_text}"); + assert!(counts[1] > 20, "{expl_text}"); + assert!(counts[1] < 70, "{expl_text}"); + assert!(counts[18] < 20, "{expl_text}"); + assert!(counts[2] < 20, "{expl_text}"); + for count in counts.iter().take(18).skip(3) { + assert!(count < &10, "{expl_text}"); + } } } @@ -241,4 +251,13 @@ mod test_sample_discrete_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::Zq; + let sample = Zq::sample_discrete_gauss_seeded(17, 0, 1, [42; 32]).unwrap(); + + assert_eq!("1 mod 17", sample.to_string()); + } } diff --git a/src/integer_mod_q/z_q/sample/uniform.rs b/src/integer_mod_q/z_q/sample/uniform.rs index 16bd9e57..ac68e825 100644 --- a/src/integer_mod_q/z_q/sample/uniform.rs +++ b/src/integer_mod_q/z_q/sample/uniform.rs @@ -65,89 +65,6 @@ impl Zq { #[cfg(test)] mod test_sample_uniform { - use crate::{ - integer::Z, - integer_mod_q::{Modulus, Zq}, - }; - - /// Checks whether the boundaries of the interval are kept for small moduli. - /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. - #[test] - fn boundaries_kept_small() { - let modulus = Z::from(17); - for _ in 0..32 { - let sample = Zq::sample_uniform(&modulus); - assert!(Z::ZERO <= sample.value); - assert!(sample.value < modulus); - } - } - - /// Checks whether the boundaries of the interval are kept for large moduli. - /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. - #[test] - fn boundaries_kept_large() { - let modulus = Z::from(u64::MAX); - for _ in 0..256 { - let sample = Zq::sample_uniform(&modulus); - assert!(Z::ZERO <= sample.value); - assert!(sample.value < modulus); - } - } - - /// Checks whether providing an invalid interval results in an error. - #[test] - #[should_panic] - fn invalid_interval() { - let modulus = Z::ZERO; - - let _ = Zq::sample_uniform(&modulus); - } - - /// Checks whether `sample_uniform` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - #[test] - fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Zq::sample_uniform(7u8); - let _ = Zq::sample_uniform(7u16); - let _ = Zq::sample_uniform(7u32); - let _ = Zq::sample_uniform(7u64); - let _ = Zq::sample_uniform(7i8); - let _ = Zq::sample_uniform(7i16); - let _ = Zq::sample_uniform(7i32); - let _ = Zq::sample_uniform(7i64); - let _ = Zq::sample_uniform(&modulus); - let _ = Zq::sample_uniform(&z); - } - - /// Roughly checks the uniformity of the distribution. - /// This test could possibly fail for a truly uniform distribution - /// with probability smaller than 1/1000. - #[test] - fn uniformity() { - let modulus = Z::from(5); - let mut counts = [0; 5]; - // count sampled instances - for _ in 0..1000 { - let sample_z = Zq::sample_uniform(&modulus); - let sample_int = i64::try_from(&sample_z.value).unwrap() as usize; - counts[sample_int] += 1; - } - - // Check that every sampled integer was sampled roughly the same time - // this could possibly fail for true uniform randomness with probability - for count in counts { - assert!(count > 150, "This test can fail with probability close to 0. - It fails if the sampled occurrences do not look like a typical uniform random distribution. - If this happens, rerun the tests several times and check whether this issue comes up again."); - } - } -} - -#[cfg(test)] -mod test_sample_uniform_seeded { use crate::utils::sample::test_seed; use crate::{ integer::Z, @@ -158,11 +75,13 @@ mod test_sample_uniform_seeded { /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. #[test] fn boundaries_kept_small() { - let modulus = Z::from(17); - for _ in 0..32 { - let sample = Zq::sample_uniform_seeded(&modulus, test_seed()); - assert!(Z::ZERO <= sample.value); - assert!(sample.value < modulus); + for seeded in [false, true] { + let modulus = Z::from(17); + for _ in 0..32 { + let sample = Zq::sample_uniform_optionally_seeded(&modulus, seeded.then(test_seed)); + assert!(Z::ZERO <= sample.value); + assert!(sample.value < modulus); + } } } @@ -170,40 +89,55 @@ mod test_sample_uniform_seeded { /// These should be protected by the sampling algorithm and [`Zq`]s instantiation. #[test] fn boundaries_kept_large() { - let modulus = Z::from(u64::MAX); - for _ in 0..256 { - let sample = Zq::sample_uniform_seeded(&modulus, test_seed()); - assert!(Z::ZERO <= sample.value); - assert!(sample.value < modulus); + for seeded in [false, true] { + let modulus = Z::from(u64::MAX); + for _ in 0..256 { + let sample = Zq::sample_uniform_optionally_seeded(&modulus, seeded.then(test_seed)); + assert!(Z::ZERO <= sample.value); + assert!(sample.value < modulus); + } } } /// Checks whether providing an invalid interval results in an error. #[test] - #[should_panic] fn invalid_interval() { - let modulus = Z::ZERO; + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let modulus = Z::ZERO; - let _ = Zq::sample_uniform_seeded(&modulus, test_seed()); + let _ = Zq::sample_uniform_optionally_seeded(&modulus, seeded.then(test_seed)); + }); + assert!(result.is_err()); + } } /// Checks whether `sample_uniform` is available for all types /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... #[test] fn availability() { - let modulus = Modulus::from(7); - let z = Z::from(7); - - let _ = Zq::sample_uniform_seeded(7u8, test_seed()); - let _ = Zq::sample_uniform_seeded(7u16, test_seed()); - let _ = Zq::sample_uniform_seeded(7u32, test_seed()); - let _ = Zq::sample_uniform_seeded(7u64, test_seed()); - let _ = Zq::sample_uniform_seeded(7i8, test_seed()); - let _ = Zq::sample_uniform_seeded(7i16, test_seed()); - let _ = Zq::sample_uniform_seeded(7i32, test_seed()); - let _ = Zq::sample_uniform_seeded(7i64, test_seed()); - let _ = Zq::sample_uniform_seeded(&modulus, test_seed()); - let _ = Zq::sample_uniform_seeded(&z, test_seed()); + for seeded in [false, true] { + let modulus = Modulus::from(7); + let z = Z::from(7); + + let _ = Zq::sample_uniform_optionally_seeded(7u8, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7u16, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7u32, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7u64, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7i8, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7i16, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7i32, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(7i64, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(&modulus, seeded.then(test_seed)); + let _ = Zq::sample_uniform_optionally_seeded(&z, seeded.then(test_seed)); + + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = Zq::sample_uniform_seeded(7u8, test_seed()); + } else { + let _ = Zq::sample_uniform(7u8); + } + } } /// Roughly checks the uniformity of the distribution. @@ -211,21 +145,24 @@ mod test_sample_uniform_seeded { /// with probability smaller than 1/1000. #[test] fn uniformity() { - let modulus = Z::from(5); - let mut counts = [0; 5]; - // count sampled instances - for _ in 0..1000 { - let sample_z = Zq::sample_uniform_seeded(&modulus, test_seed()); - let sample_int = i64::try_from(&sample_z.value).unwrap() as usize; - counts[sample_int] += 1; - } - - // Check that every sampled integer was sampled roughly the same time - // this could possibly fail for true uniform randomness with probability - for count in counts { - assert!(count > 150, "This test can fail with probability close to 0. + for seeded in [false, true] { + let modulus = Z::from(5); + let mut counts = [0; 5]; + // count sampled instances + for _ in 0..1000 { + let sample_z = + Zq::sample_uniform_optionally_seeded(&modulus, seeded.then(test_seed)); + let sample_int = i64::try_from(&sample_z.value).unwrap() as usize; + counts[sample_int] += 1; + } + + // Check that every sampled integer was sampled roughly the same time + // this could possibly fail for true uniform randomness with probability + for count in counts { + assert!(count > 150, "This test can fail with probability close to 0. It fails if the sampled occurrences do not look like a typical uniform random distribution. If this happens, rerun the tests several times and check whether this issue comes up again."); + } } } @@ -238,4 +175,13 @@ mod test_sample_uniform_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::integer_mod_q::Zq; + let sample = Zq::sample_uniform_seeded(17, [42; 32]); + + assert_eq!("2 mod 17", sample.to_string()); + } } diff --git a/src/rational/mat_q/rounding.rs b/src/rational/mat_q/rounding.rs index 8484cc14..a37a451b 100644 --- a/src/rational/mat_q/rounding.rs +++ b/src/rational/mat_q/rounding.rs @@ -303,19 +303,6 @@ mod test_round { #[cfg(test)] mod test_randomized_rounding { - use crate::rational::MatQ; - use std::str::FromStr; - - /// Ensure that a `r < 0` throws an error - #[test] - fn negative_r() { - let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - assert!(value.randomized_rounding(-1).is_err()); - } -} - -#[cfg(test)] -mod test_randomized_rounding_seeded { use crate::rational::MatQ; use crate::utils::sample::test_seed; use std::str::FromStr; @@ -323,8 +310,14 @@ mod test_randomized_rounding_seeded { /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { - let value = MatQ::from_str("[[5/2, 1]]").unwrap(); - assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + for seeded in [false, true] { + let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + assert!( + value + .randomized_rounding_optionally_seeded(-1, seeded.then(test_seed)) + .is_err() + ); + } } /// Checks whether the same seed results in the same sample. @@ -338,4 +331,27 @@ mod test_randomized_rounding_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::MatQ; + use std::str::FromStr; + let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + let sample = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!("[[3, 0]]", sample.to_string()); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::rational::MatQ; + use std::str::FromStr; + let value = MatQ::from_str("[[5/2, 1]]").unwrap(); + + // explicitly checks the existence of the unseeded and the seeded function + let _ = value.randomized_rounding(3); + let _ = value.randomized_rounding_seeded(3, test_seed()); + } } diff --git a/src/rational/mat_q/sample/gauss.rs b/src/rational/mat_q/sample/gauss.rs index 4e458407..bc807dfd 100644 --- a/src/rational/mat_q/sample/gauss.rs +++ b/src/rational/mat_q/sample/gauss.rs @@ -155,51 +155,35 @@ impl MatQ { #[cfg(test)] mod test_sample_gauss { - use crate::{rational::MatQ, traits::MatrixDimensions}; - - /// Ensure that an error is returned if `sigma` is not positive - #[test] - fn non_positive_sigma() { - let center = MatQ::new(5, 5); - for sigma in [0, -1] { - assert!(MatQ::sample_gauss(¢er, sigma).is_err()) - } - } - - /// Ensure that the samples are of correct dimension - #[test] - fn correct_dimension() { - for (x, y) in [(5, 5), (1, 10), (10, 1)] { - let center = MatQ::new(x, y); - let sample = MatQ::sample_gauss(¢er, 1).unwrap(); - assert_eq!(center.get_num_rows(), sample.get_num_rows()); - assert_eq!(center.get_num_columns(), sample.get_num_columns()); - } - } -} - -#[cfg(test)] -mod test_sample_gauss_seeded { use crate::utils::sample::test_seed; use crate::{rational::MatQ, traits::MatrixDimensions}; /// Ensure that an error is returned if `sigma` is not positive #[test] fn non_positive_sigma() { - let center = MatQ::new(5, 5); - for sigma in [0, -1] { - assert!(MatQ::sample_gauss_seeded(¢er, sigma, test_seed()).is_err()) + for seeded in [false, true] { + let center = MatQ::new(5, 5); + for sigma in [0, -1] { + assert!( + MatQ::sample_gauss_optionally_seeded(¢er, sigma, seeded.then(test_seed)) + .is_err() + ) + } } } /// Ensure that the samples are of correct dimension #[test] fn correct_dimension() { - for (x, y) in [(5, 5), (1, 10), (10, 1)] { - let center = MatQ::new(x, y); - let sample = MatQ::sample_gauss_seeded(¢er, 1, test_seed()).unwrap(); - assert_eq!(center.get_num_rows(), sample.get_num_rows()); - assert_eq!(center.get_num_columns(), sample.get_num_columns()); + for seeded in [false, true] { + for (x, y) in [(5, 5), (1, 10), (10, 1)] { + let center = MatQ::new(x, y); + let sample = + MatQ::sample_gauss_optionally_seeded(¢er, 1, seeded.then(test_seed)) + .unwrap(); + assert_eq!(center.get_num_rows(), sample.get_num_rows()); + assert_eq!(center.get_num_columns(), sample.get_num_columns()); + } } } @@ -212,48 +196,32 @@ mod test_sample_gauss_seeded { assert_eq!(sample_0, sample_1); } -} - -#[cfg(test)] -mod test_sample_gauss_same_center { - use crate::{rational::MatQ, traits::MatrixDimensions}; - - /// Ensure that an error is returned if `sigma` is not positive + /// Checks whether the seeded variant outputs the known sample for a fixed seed. #[test] - fn non_positive_sigma() { - for sigma in [0, -1] { - assert!(MatQ::sample_gauss_same_center(5, 5, 0, sigma).is_err()) - } - } + fn known_answer() { + use crate::rational::MatQ; + let sample = MatQ::sample_gauss_seeded(&MatQ::new(5, 5), 1, [42; 32]).unwrap(); - /// Ensure that the samples are of correct dimension - #[test] - fn correct_dimension() { - for (x, y) in [(5, 5), (1, 10), (10, 1)] { - let sample = MatQ::sample_gauss_same_center(x, y, 0, 1).unwrap(); - assert_eq!(x, sample.get_num_rows()); - assert_eq!(y, sample.get_num_columns()); - } + assert_eq!( + "[[-4945271350741753/2251799813685248, 1117106275816641/2251799813685248, 4434555541986695/4503599627370496, -6539644456987479/4503599627370496, -4677571318743725/9007199254740992],[3908611230479557/4503599627370496, 5573654194347613/9007199254740992, 5403302998575453/9007199254740992, 2430733040590187/9007199254740992, 1031578748721027/4503599627370496],[-4883384444105643/9007199254740992, -3764944878914815/2251799813685248, -7161261431757703/2251799813685248, -5092217727994081/9007199254740992, -2353868661553283/4503599627370496],[4063319294552583/9007199254740992, 2715651668966247/2251799813685248, -7464330407867217/9007199254740992, -8311230105715239/72057594037927936, -4329275745410667/2251799813685248],[-3100112952240901/18014398509481984, 3412362636860941/576460752303423488, -4689787695738317/4503599627370496, 3370477389923299/2251799813685248, 1145812122695171/4503599627370496]]", + sample.to_string() + ); } - /// Ensure that a negative number of rows causes a panic + /// Checks whether the unseeded and the seeded function are available. #[test] - #[should_panic] - fn negative_number_rows() { - let _ = MatQ::sample_gauss_same_center(-1, 1, 0, 1).unwrap(); - } + fn availability() { + use crate::rational::MatQ; - /// Ensure that a negative number of columns causes a panic - #[test] - #[should_panic] - fn negative_number_columns() { - let _ = MatQ::sample_gauss_same_center(1, -1, 0, 1).unwrap(); + // explicitly checks the existence of the unseeded and the seeded function + let _ = MatQ::sample_gauss(&MatQ::new(5, 5), 1); + let _ = MatQ::sample_gauss_seeded(&MatQ::new(5, 5), 1, test_seed()); } } #[cfg(test)] -mod test_sample_gauss_same_center_seeded { +mod test_sample_gauss_same_center { use crate::utils::sample::test_seed; use crate::{rational::MatQ, traits::MatrixDimensions}; @@ -261,33 +229,75 @@ mod test_sample_gauss_same_center_seeded { /// Ensure that an error is returned if `sigma` is not positive #[test] fn non_positive_sigma() { - for sigma in [0, -1] { - assert!(MatQ::sample_gauss_same_center_seeded(5, 5, 0, sigma, test_seed()).is_err()) + for seeded in [false, true] { + for sigma in [0, -1] { + assert!( + MatQ::sample_gauss_same_center_optionally_seeded( + 5, + 5, + 0, + sigma, + seeded.then(test_seed) + ) + .is_err() + ) + } } } /// Ensure that the samples are of correct dimension #[test] fn correct_dimension() { - for (x, y) in [(5, 5), (1, 10), (10, 1)] { - let sample = MatQ::sample_gauss_same_center_seeded(x, y, 0, 1, test_seed()).unwrap(); - assert_eq!(x, sample.get_num_rows()); - assert_eq!(y, sample.get_num_columns()); + for seeded in [false, true] { + for (x, y) in [(5, 5), (1, 10), (10, 1)] { + let sample = MatQ::sample_gauss_same_center_optionally_seeded( + x, + y, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + assert_eq!(x, sample.get_num_rows()); + assert_eq!(y, sample.get_num_columns()); + } } } /// Ensure that a negative number of rows causes a panic #[test] - #[should_panic] fn negative_number_rows() { - let _ = MatQ::sample_gauss_same_center_seeded(-1, 1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatQ::sample_gauss_same_center_optionally_seeded( + -1, + 1, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Ensure that a negative number of columns causes a panic #[test] - #[should_panic] fn negative_number_columns() { - let _ = MatQ::sample_gauss_same_center_seeded(1, -1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = MatQ::sample_gauss_same_center_optionally_seeded( + 1, + -1, + 0, + 1, + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -300,4 +310,28 @@ mod test_sample_gauss_same_center_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::{MatQ, Q}; + let center = Q::from((5, 2)); + let sample = MatQ::sample_gauss_same_center_seeded(5, 5, ¢er, 1, [42; 32]).unwrap(); + + assert_eq!( + "[[8525870560382491/2251799813685248, 17119947174636167/4503599627370496, 1259641011116081/1125899906842624, 39499623190823409/18014398509481984, 3385362557569009/4503599627370496],[3938234223310835/2251799813685248, 17542906212059721/4503599627370496, 16597635972078743/4503599627370496, 8266208908835155/2251799813685248, 39314002964552041/18014398509481984],[881046098121435/562949953421312, 29287177467398541/9007199254740992, 2413886157177875/562949953421312, 981581273026087/281474976710656, 43488189713909861/18014398509481984],[6127343065657779/4503599627370496, 30937068457746009/9007199254740992, 27849268867401663/9007199254740992, 2727502571816845/2251799813685248, 27040028183639271/9007199254740992],[13289162739536831/4503599627370496, 87685859291908515/36028797018963968, 93185871850127433/36028797018963968, 2111809547791415/1125899906842624, 4077230155367515/2251799813685248]]", + sample.to_string() + ); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::rational::{MatQ, Q}; + let center = Q::from((5, 2)); + + // explicitly checks the existence of the unseeded and the seeded function + let _ = MatQ::sample_gauss_same_center(5, 5, ¢er, 1); + let _ = MatQ::sample_gauss_same_center_seeded(5, 5, ¢er, 1, test_seed()); + } } diff --git a/src/rational/poly_over_q/rounding.rs b/src/rational/poly_over_q/rounding.rs index 45a1fb42..d6da965d 100644 --- a/src/rational/poly_over_q/rounding.rs +++ b/src/rational/poly_over_q/rounding.rs @@ -232,19 +232,6 @@ mod test_round { #[cfg(test)] mod test_randomized_rounding { - use crate::rational::PolyOverQ; - use std::str::FromStr; - - /// Ensure that a `r < 0` throws an error - #[test] - fn negative_r() { - let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - assert!(value.randomized_rounding(-1).is_err()); - } -} - -#[cfg(test)] -mod test_randomized_rounding_seeded { use crate::rational::PolyOverQ; use crate::utils::sample::test_seed; use std::str::FromStr; @@ -252,8 +239,14 @@ mod test_randomized_rounding_seeded { /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { - let value = PolyOverQ::from_str("2 5/2 1").unwrap(); - assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + for seeded in [false, true] { + let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + assert!( + value + .randomized_rounding_optionally_seeded(-1, seeded.then(test_seed)) + .is_err() + ); + } } /// Checks whether the same seed results in the same sample. @@ -267,4 +260,27 @@ mod test_randomized_rounding_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::PolyOverQ; + use std::str::FromStr; + let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + let sample = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!("1 3", sample.to_string()); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::rational::PolyOverQ; + use std::str::FromStr; + let value = PolyOverQ::from_str("2 5/2 1").unwrap(); + + // explicitly checks the existence of the unseeded and the seeded function + let _ = value.randomized_rounding(3); + let _ = value.randomized_rounding_seeded(3, test_seed()); + } } diff --git a/src/rational/poly_over_q/sample/gauss.rs b/src/rational/poly_over_q/sample/gauss.rs index 207d5d03..8239ae02 100644 --- a/src/rational/poly_over_q/sample/gauss.rs +++ b/src/rational/poly_over_q/sample/gauss.rs @@ -98,6 +98,7 @@ impl PolyOverQ { #[cfg(test)] mod test_sample_gauss { + use crate::utils::sample::test_seed; use crate::{ integer::Z, rational::{PolyOverQ, Q}, @@ -108,106 +109,119 @@ mod test_sample_gauss { /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... #[test] fn availability() { - let center_q = Q::ZERO; - let center_z = Z::ZERO; - - let _ = PolyOverQ::sample_gauss(1u8, 16u8, 0f32); - let _ = PolyOverQ::sample_gauss(1u16, 16u16, 0f64); - let _ = PolyOverQ::sample_gauss(1u32, 16u32, 0f32); - let _ = PolyOverQ::sample_gauss(1u64, 16u64, 0f64); - let _ = PolyOverQ::sample_gauss(1i8, 16u8, 0f32); - let _ = PolyOverQ::sample_gauss(1i16, 16i16, 0f32); - let _ = PolyOverQ::sample_gauss(1i32, 16i32, 0f32); - let _ = PolyOverQ::sample_gauss(1i64, 16i64, 0f64); - let _ = PolyOverQ::sample_gauss(1u8, center_q, 0f32); - let _ = PolyOverQ::sample_gauss(1u8, center_z, 0f64); - } - - /// Checks whether the number of coefficients is correct. - #[test] - fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverQ::sample_gauss(degree, i64::MAX, 1).unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." + for seeded in [false, true] { + let center_q = Q::ZERO; + let center_z = Z::ZERO; + + let _ = + PolyOverQ::sample_gauss_optionally_seeded(1u8, 16u8, 0f32, seeded.then(test_seed)); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1u16, + 16u16, + 0f64, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1u32, + 16u32, + 0f32, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1u64, + 16u64, + 0f64, + seeded.then(test_seed), + ); + let _ = + PolyOverQ::sample_gauss_optionally_seeded(1i8, 16u8, 0f32, seeded.then(test_seed)); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1i16, + 16i16, + 0f32, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1i32, + 16i32, + 0f32, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1i64, + 16i64, + 0f64, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1u8, + center_q, + 0f32, + seeded.then(test_seed), + ); + let _ = PolyOverQ::sample_gauss_optionally_seeded( + 1u8, + center_z, + 0f64, + seeded.then(test_seed), ); - } - } - - /// Checks is as error is returned, if sigma is less than 1. - #[test] - fn invalid_sigma() { - assert!(PolyOverQ::sample_gauss(1, 0, 0).is_err()); - assert!(PolyOverQ::sample_gauss(1, 0, -1).is_err()); - } - - /// Checks whether the maximum degree needs to be at least 0. - #[test] - #[should_panic] - fn invalid_max_degree() { - let _ = PolyOverQ::sample_gauss(-1, 0, 1).unwrap(); - } -} - -#[cfg(test)] -mod test_sample_gauss_seeded { - use crate::utils::sample::test_seed; - use crate::{ - integer::Z, - rational::{PolyOverQ, Q}, - }; - /// Checks whether `sample_gauss` is available for all types - /// implementing [`Into`], i.e. u8, u16, u32, u64, i8, ... - /// or [`Into`], i.e. u8, i16, f32, Z, Q, ... - #[test] - fn availability() { - let center_q = Q::ZERO; - let center_z = Z::ZERO; - - let _ = PolyOverQ::sample_gauss_seeded(1u8, 16u8, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1u16, 16u16, 0f64, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1u32, 16u32, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1u64, 16u64, 0f64, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1i8, 16u8, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1i16, 16i16, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1i32, 16i32, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1i64, 16i64, 0f64, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1u8, center_q, 0f32, test_seed()); - let _ = PolyOverQ::sample_gauss_seeded(1u8, center_z, 0f64, test_seed()); + // explicitly checks the existence of the unseeded and the seeded function + if seeded { + let _ = PolyOverQ::sample_gauss_seeded(1u8, 16u8, 0f32, test_seed()); + } else { + let _ = PolyOverQ::sample_gauss(1u8, 16u8, 0f32); + } + } } /// Checks whether the number of coefficients is correct. #[test] fn nr_coeffs() { - let degrees = [1, 3, 7, 15, 32, 120]; - for degree in degrees { - let res = PolyOverQ::sample_gauss_seeded(degree, i64::MAX, 1, test_seed()).unwrap(); - - assert_eq!( - res.get_degree(), - degree, - "Could fail with negligible probability." - ); + for seeded in [false, true] { + let degrees = [1, 3, 7, 15, 32, 120]; + for degree in degrees { + let res = PolyOverQ::sample_gauss_optionally_seeded( + degree, + i64::MAX, + 1, + seeded.then(test_seed), + ) + .unwrap(); + + assert_eq!( + res.get_degree(), + degree, + "Could fail with negligible probability." + ); + } } } /// Checks is as error is returned, if sigma is less than 1. #[test] fn invalid_sigma() { - assert!(PolyOverQ::sample_gauss_seeded(1, 0, 0, test_seed()).is_err()); - assert!(PolyOverQ::sample_gauss_seeded(1, 0, -1, test_seed()).is_err()); + for seeded in [false, true] { + assert!( + PolyOverQ::sample_gauss_optionally_seeded(1, 0, 0, seeded.then(test_seed)).is_err() + ); + assert!( + PolyOverQ::sample_gauss_optionally_seeded(1, 0, -1, seeded.then(test_seed)) + .is_err() + ); + } } /// Checks whether the maximum degree needs to be at least 0. #[test] - #[should_panic] fn invalid_max_degree() { - let _ = PolyOverQ::sample_gauss_seeded(-1, 0, 1, test_seed()).unwrap(); + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let _ = PolyOverQ::sample_gauss_optionally_seeded(-1, 0, 1, seeded.then(test_seed)) + .unwrap(); + }); + assert!(result.is_err()); + } } /// Checks whether the same seed results in the same sample. @@ -219,4 +233,16 @@ mod test_sample_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::PolyOverQ; + let sample = PolyOverQ::sample_gauss_seeded(2, 0, 1, [42; 32]).unwrap(); + + assert_eq!( + "3 2896371026169371/2251799813685248 5860948106209927/4503599627370496 -1555108755990479/1125899906842624", + sample.to_string() + ); + } } diff --git a/src/rational/q/rounding.rs b/src/rational/q/rounding.rs index d0d1f72a..0034f1aa 100644 --- a/src/rational/q/rounding.rs +++ b/src/rational/q/rounding.rs @@ -344,25 +344,19 @@ mod test_simplify { #[cfg(test)] mod test_randomized_rounding { use crate::rational::Q; - - /// Ensure that a `r < 0` throws an error - #[test] - fn negative_r() { - let value = Q::from((2, 3)); - assert!(value.randomized_rounding(-1).is_err()); - } -} - -#[cfg(test)] -mod test_randomized_rounding_seeded { - use crate::rational::Q; use crate::utils::sample::test_seed; /// Ensure that a `r < 0` throws an error #[test] fn negative_r() { - let value = Q::from((2, 3)); - assert!(value.randomized_rounding_seeded(-1, test_seed()).is_err()); + for seeded in [false, true] { + let value = Q::from((2, 3)); + assert!( + value + .randomized_rounding_optionally_seeded(-1, seeded.then(test_seed)) + .is_err() + ); + } } /// Checks whether the same seed results in the same sample. @@ -375,4 +369,25 @@ mod test_randomized_rounding_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::Q; + let value = Q::from((5, 2)); + let sample = value.randomized_rounding_seeded(3, [42; 32]).unwrap(); + + assert_eq!("1", sample.to_string()); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::rational::Q; + let value = Q::from((5, 2)); + + // explicitly checks the existence of the unseeded and the seeded function + let _ = value.randomized_rounding(3); + let _ = value.randomized_rounding_seeded(3, test_seed()); + } } diff --git a/src/rational/q/sample/gauss.rs b/src/rational/q/sample/gauss.rs index 36f2f80a..67f3cc9a 100644 --- a/src/rational/q/sample/gauss.rs +++ b/src/rational/q/sample/gauss.rs @@ -77,50 +77,35 @@ impl Q { #[cfg(test)] mod test_sample_gauss { use crate::rational::Q; - - /// Test correct distribution with a confidence level of 99.7% -> 3 standard - /// deviations. - #[test] - fn in_concentration_bound() { - let range = 3; - for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] { - assert!(range * sigma >= (Q::from(mu) - Q::sample_gauss(mu, sigma).unwrap()).abs()) - } - } - - /// Ensure that an error is returned if `sigma` is not positive - #[test] - fn non_positive_sigma() { - for (mu, sigma) in [(0, 0), (0, -1)] { - assert!(Q::sample_gauss(mu, sigma).is_err()) - } - } -} - -#[cfg(test)] -mod test_sample_gauss_seeded { - use crate::rational::Q; use crate::utils::sample::test_seed; /// Test correct distribution with a confidence level of 99.7% -> 3 standard /// deviations. #[test] fn in_concentration_bound() { - let range = 3; - for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] { - assert!( - range * sigma - >= (Q::from(mu) - Q::sample_gauss_seeded(mu, sigma, test_seed()).unwrap()) + for seeded in [false, true] { + let range = 3; + for (mu, sigma) in [(i64::MAX, 1), (0, 20), (i64::MIN, 100)] { + assert!( + range * sigma + >= (Q::from(mu) + - Q::sample_gauss_optionally_seeded(mu, sigma, seeded.then(test_seed)) + .unwrap()) .abs() - ) + ) + } } } /// Ensure that an error is returned if `sigma` is not positive #[test] fn non_positive_sigma() { - for (mu, sigma) in [(0, 0), (0, -1)] { - assert!(Q::sample_gauss_seeded(mu, sigma, test_seed()).is_err()) + for seeded in [false, true] { + for (mu, sigma) in [(0, 0), (0, -1)] { + assert!( + Q::sample_gauss_optionally_seeded(mu, sigma, seeded.then(test_seed)).is_err() + ) + } } } @@ -133,4 +118,23 @@ mod test_sample_gauss_seeded { assert_eq!(sample_0, sample_1); } + + /// Checks whether the seeded variant outputs the known sample for a fixed seed. + #[test] + fn known_answer() { + use crate::rational::Q; + let sample = Q::sample_gauss_seeded(0, 1, [42; 32]).unwrap(); + + assert_eq!("2896371026169371/2251799813685248", sample.to_string()); + } + + /// Checks whether the unseeded and the seeded function are available. + #[test] + fn availability() { + use crate::rational::Q; + + // explicitly checks the existence of the unseeded and the seeded function + let _ = Q::sample_gauss(0, 1); + let _ = Q::sample_gauss_seeded(0, 1, test_seed()); + } } diff --git a/src/utils/sample/binomial.rs b/src/utils/sample/binomial.rs index 949c317e..8e83d7cc 100644 --- a/src/utils/sample/binomial.rs +++ b/src/utils/sample/binomial.rs @@ -126,74 +126,78 @@ impl BinomialSampler { #[cfg(test)] mod test_binomial_sampler { use super::{BinomialSampler, Q, Z}; + use crate::utils::sample::test_seed; /// Checks whether the range is kept, /// i.e. if any result is at least 0 and smaller than or equal to `n`. #[test] fn keeps_range() { - let n = 16; - let p = 0.5; - let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); - - for _ in 0..16 { - let sample = bin_sampler.sample(); - // sample >= 0 check is not required as sample is a u64 - assert!(sample <= n); + for seeded in [false, true] { + let n = 16; + let p = 0.5; + let mut bin_sampler = BinomialSampler::init(n, p, seeded.then(test_seed)).unwrap(); + + for _ in 0..16 { + let sample = bin_sampler.sample(); + // sample >= 0 check is not required as sample is a u64 + assert!(sample <= n); + } } } /// Roughly checks that the collected samples are distributed according to the binomial distribution. #[test] fn distribution() { - let n = 2; - let p = 0.5; - let mut bin_sampler = BinomialSampler::init(n, p, None).unwrap(); - - let mut counts = [0; 3]; - // count sampled instances - for _ in 0..1000 { - let sample = u64::try_from(bin_sampler.sample()).unwrap() as usize; - counts[sample] += 1; - } - - let expl_text = String::from("This test can fail with probability close to 0. + for seeded in [false, true] { + let n = 2; + let p = 0.5; + let mut bin_sampler = BinomialSampler::init(n, p, seeded.then(test_seed)).unwrap(); + + let mut counts = [0; 3]; + // count sampled instances + for _ in 0..1000 { + let sample = u64::try_from(bin_sampler.sample()).unwrap() as usize; + counts[sample] += 1; + } + + let expl_text = String::from("This test can fail with probability close to 0. It fails if the sampled occurrences do not look like a typical binomial random distribution. If this happens, rerun the tests several times and check whether this issue comes up again."); - // Check that the sampled occurrences roughly look - // like a binomial distribution - assert!(counts[0] > 100, "{expl_text}"); - assert!(counts[0] < 400, "{expl_text}"); - assert!(counts[1] > 300, "{expl_text}"); - assert!(counts[1] < 700, "{expl_text}"); - assert!(counts[2] > 100, "{expl_text}"); - assert!(counts[2] < 400, "{expl_text}"); + // Check that the sampled occurrences roughly look + // like a binomial distribution + assert!(counts[0] > 100, "{expl_text}"); + assert!(counts[0] < 400, "{expl_text}"); + assert!(counts[1] > 300, "{expl_text}"); + assert!(counts[1] < 700, "{expl_text}"); + assert!(counts[2] > 100, "{expl_text}"); + assert!(counts[2] < 400, "{expl_text}"); + } } /// Checks whether invalid choices for n result in an error. #[test] fn invalid_n() { - let p = 0.5; + for seeded in [false, true] { + let p = 0.5; - assert!(BinomialSampler::init(&Z::MINUS_ONE, p, None).is_err()); - assert!(BinomialSampler::init(Z::from(i64::MIN), p, None).is_err()); + assert!(BinomialSampler::init(&Z::MINUS_ONE, p, seeded.then(test_seed)).is_err()); + assert!(BinomialSampler::init(Z::from(i64::MIN), p, seeded.then(test_seed)).is_err()); + } } /// Checks whether invalid choices for p result in an error. #[test] fn invalid_p() { - let n = 2; + for seeded in [false, true] { + let n = 2; - assert!(BinomialSampler::init(n, &Q::MINUS_ONE, None).is_err()); - assert!(BinomialSampler::init(n, &Q::ZERO, None).is_err()); - assert!(BinomialSampler::init(n, &Q::ONE, None).is_err()); - assert!(BinomialSampler::init(n, Q::from(5), None).is_err()); + assert!(BinomialSampler::init(n, &Q::MINUS_ONE, seeded.then(test_seed)).is_err()); + assert!(BinomialSampler::init(n, &Q::ZERO, seeded.then(test_seed)).is_err()); + assert!(BinomialSampler::init(n, &Q::ONE, seeded.then(test_seed)).is_err()); + assert!(BinomialSampler::init(n, Q::from(5), seeded.then(test_seed)).is_err()); + } } -} - -#[cfg(test)] -mod test_binomial_sampler_seeded { - use super::{BinomialSampler, Q, Z}; /// Checks whether two samplers with the same seed output the same samples. #[test] @@ -208,40 +212,13 @@ mod test_binomial_sampler_seeded { } } - /// Checks whether two samplers with different seeds output different samples. + /// Checks whether the seeded sampler outputs the known samples for a fixed seed. #[test] - fn different_seed_different_samples() { - let n = 1024; - let p = 0.5; - let mut bin_sampler_0 = BinomialSampler::init(n, p, Some([0; 32])).unwrap(); - let mut bin_sampler_1 = BinomialSampler::init(n, p, Some([1; 32])).unwrap(); - - let samples_0: Vec = (0..16).map(|_| bin_sampler_0.sample()).collect(); - let samples_1: Vec = (0..16).map(|_| bin_sampler_1.sample()).collect(); - - assert_ne!(samples_0, samples_1); - } - - /// Checks whether seeded samples are kept in range. - #[test] - fn keeps_range() { - let n = 16; - let p = 0.5; - let mut bin_sampler = BinomialSampler::init(n, p, Some([42; 32])).unwrap(); + fn known_answer() { + let mut bin_sampler = BinomialSampler::init(1024, 0.5, Some([42; 32])).unwrap(); - for _ in 0..u8::MAX { - let sample = bin_sampler.sample(); + let samples: Vec = (0..8).map(|_| bin_sampler.sample().to_string()).collect(); - assert!(Z::ZERO <= sample); - assert!(sample <= n); - } - } - - /// Checks whether invalid choices for n and p result in an error. - #[test] - fn invalid_parameters() { - assert!(BinomialSampler::init(&Z::MINUS_ONE, 0.5, Some([42; 32])).is_err()); - assert!(BinomialSampler::init(2, &Q::ZERO, Some([42; 32])).is_err()); - assert!(BinomialSampler::init(2, &Q::ONE, Some([42; 32])).is_err()); + assert_eq!("495, 492, 500, 489, 491, 510, 489, 555", samples.join(", ")); } } diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index ef42b113..43252152 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -478,6 +478,7 @@ pub(crate) fn sample_d_precomputed_gso( #[cfg(test)] mod test_discrete_gaussian_integer_sampler { use super::DiscreteGaussianIntegerSampler; + use crate::utils::sample::test_seed; use crate::{ rational::Q, utils::sample::discrete_gauss::{LookupTableSetting, TAILCUT}, @@ -486,102 +487,110 @@ mod test_discrete_gaussian_integer_sampler { /// Checks whether samples are kept in correct interval for a small interval. #[test] fn small_interval() { - let center = Q::from(15); - let gaussian_parameter = Q::from((1, 2)); + for seeded in [false, true] { + let center = Q::from(15); + let gaussian_parameter = Q::from((1, 2)); - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &gaussian_parameter, - 8.0, - LookupTableSetting::FillOnTheFly, - None, - ) - .unwrap(); + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + 8.0, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed), + ) + .unwrap(); - for _ in 0..64 { - let sample = dgis.sample_z(); + for _ in 0..64 { + let sample = dgis.sample_z(); - assert!(10 <= sample); - assert!(sample <= 20); + assert!(10 <= sample); + assert!(sample <= 20); + } } } /// Checks whether samples are kept in correct interval for a large interval. #[test] fn large_interval() { - let center = Q::MINUS_ONE; - let gaussian_parameter = Q::ONE; + for seeded in [false, true] { + let center = Q::MINUS_ONE; + let gaussian_parameter = Q::ONE; - let mut dgis = DiscreteGaussianIntegerSampler::init( - ¢er, - &gaussian_parameter, - unsafe { TAILCUT }, - LookupTableSetting::FillOnTheFly, - None, - ) - .unwrap(); + let mut dgis = DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + unsafe { TAILCUT }, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed), + ) + .unwrap(); - for _ in 0..256 { - let sample = dgis.sample_z(); + for _ in 0..256 { + let sample = dgis.sample_z(); - assert!(-64 <= sample); - assert!(sample <= 62); + assert!(-64 <= sample); + assert!(sample <= 62); + } } } /// Checks whether `sample_z` returns an error if the gaussian parameter `s < 0`. #[test] fn invalid_gaussian_parameter() { - let center = Q::ZERO; + for seeded in [false, true] { + let center = Q::ZERO; - assert!( - DiscreteGaussianIntegerSampler::init( - ¢er, - &Q::MINUS_ONE, - 6.0, - LookupTableSetting::FillOnTheFly, - None - ) - .is_err() - ); - assert!( - DiscreteGaussianIntegerSampler::init( - ¢er, - Q::from(i64::MIN), - 6.0, - LookupTableSetting::FillOnTheFly, - None - ) - .is_err() - ); + assert!( + DiscreteGaussianIntegerSampler::init( + ¢er, + &Q::MINUS_ONE, + 6.0, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed) + ) + .is_err() + ); + assert!( + DiscreteGaussianIntegerSampler::init( + ¢er, + Q::from(i64::MIN), + 6.0, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed) + ) + .is_err() + ); + } } /// Checks whether `sample_z` returns an error if `n < 0`. #[test] fn invalid_tailcut() { - let center = Q::MINUS_ONE; - let gaussian_parameter = Q::ONE; + for seeded in [false, true] { + let center = Q::MINUS_ONE; + let gaussian_parameter = Q::ONE; - assert!( - DiscreteGaussianIntegerSampler::init( - ¢er, - &gaussian_parameter, - -0.1, - LookupTableSetting::FillOnTheFly, - None - ) - .is_err() - ); - assert!( - DiscreteGaussianIntegerSampler::init( - ¢er, - &gaussian_parameter, - i64::MIN, - LookupTableSetting::FillOnTheFly, - None - ) - .is_err() - ); + assert!( + DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + -0.1, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed) + ) + .is_err() + ); + assert!( + DiscreteGaussianIntegerSampler::init( + ¢er, + &gaussian_parameter, + i64::MIN, + LookupTableSetting::FillOnTheFly, + seeded.then(test_seed) + ) + .is_err() + ); + } } /// Checks whether a serialized and deserialized sampler keeps its lookup table @@ -612,12 +621,6 @@ mod test_discrete_gaussian_integer_sampler { assert!(sample <= 20); } } -} - -#[cfg(test)] -mod test_discrete_gaussian_integer_sampler_seeded { - use super::DiscreteGaussianIntegerSampler; - use crate::{integer::Z, rational::Q, utils::sample::discrete_gauss::LookupTableSetting}; /// Checks whether two samplers with the same seed output the same samples /// for every [`LookupTableSetting`]. @@ -643,32 +646,6 @@ mod test_discrete_gaussian_integer_sampler_seeded { } } - /// Checks whether two samplers with different seeds output different samples. - #[test] - fn different_seed_different_samples() { - let mut dgis_0 = DiscreteGaussianIntegerSampler::init( - 0, - 1024, - 6, - LookupTableSetting::NoLookup, - Some([0; 32]), - ) - .unwrap(); - let mut dgis_1 = DiscreteGaussianIntegerSampler::init( - 0, - 1024, - 6, - LookupTableSetting::NoLookup, - Some([1; 32]), - ) - .unwrap(); - - let samples_0: Vec = (0..16).map(|_| dgis_0.sample_z()).collect(); - let samples_1: Vec = (0..16).map(|_| dgis_1.sample_z()).collect(); - - assert_ne!(samples_0, samples_1); - } - /// Checks whether a clone of a seeded sampler continues the stream of its original, /// i.e. alternating between both yields the same samples as a single seeded sampler. #[test] @@ -697,49 +674,21 @@ mod test_discrete_gaussian_integer_sampler_seeded { } } - /// Checks whether seeded samples are kept in the correct interval. + /// Checks whether the seeded sampler outputs the known samples for a fixed seed. #[test] - fn keeps_range() { + fn known_answer() { let mut dgis = DiscreteGaussianIntegerSampler::init( - Q::from(15), - Q::from((1, 2)), - 8.0, + Q::MINUS_ONE, + 16, + 6, LookupTableSetting::FillOnTheFly, Some([42; 32]), ) .unwrap(); - for _ in 0..64 { - let sample = dgis.sample_z(); + let samples: Vec = (0..8).map(|_| dgis.sample_z().to_string()).collect(); - assert!(10 <= sample); - assert!(sample <= 20); - } - } - - /// Checks whether invalid choices for `s` and `tailcut` result in an error. - #[test] - fn invalid_parameters() { - assert!( - DiscreteGaussianIntegerSampler::init( - 0, - &Q::MINUS_ONE, - 6.0, - LookupTableSetting::FillOnTheFly, - Some([42; 32]) - ) - .is_err() - ); - assert!( - DiscreteGaussianIntegerSampler::init( - 0, - 1, - -0.1, - LookupTableSetting::FillOnTheFly, - Some([42; 32]) - ) - .is_err() - ); + assert_eq!("3, -3, 5, 3, -12, 13, -1, 0", samples.join(", ")); } } @@ -819,6 +768,7 @@ mod test_sample_d { use super::sample_d_precomputed_gso; use crate::traits::{Concatenate, MatrixDimensions, MatrixGetSubmatrix, Pow}; use crate::utils::sample::discrete_gauss::sample_d; + use crate::utils::sample::test_seed; use crate::{ integer::{MatZ, Z}, rational::{MatQ, Q}, @@ -829,40 +779,64 @@ mod test_sample_d { /// Ensures that the doc-test compiles and runs properly. #[test] fn doc_test() { - let basis = MatZ::identity(5, 5); - let center = MatQ::new(5, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + for seeded in [false, true] { + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let _ = sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)).unwrap(); + let _ = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ) .unwrap(); + } } /// Ensures that `sample_d` works properly for a non-zero center. #[test] fn non_zero_center() { - let basis = MatZ::identity(5, 5); - let center = MatQ::identity(5, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + for seeded in [false, true] { + let basis = MatZ::identity(5, 5); + let center = MatQ::identity(5, 1); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let _ = sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)).unwrap(); + let _ = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ) .unwrap(); + } } /// Ensures that `sample_d` works properly for a different basis. #[test] fn non_identity_basis() { - let basis = MatZ::from_str("[[2, 1],[1, 2]]").unwrap(); - let center = MatQ::new(2, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let _ = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) + for seeded in [false, true] { + let basis = MatZ::from_str("[[2, 1],[1, 2]]").unwrap(); + let center = MatQ::new(2, 1); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let _ = sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)).unwrap(); + let _ = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ) .unwrap(); + } } /// Ensures that `sample_d` outputs a vector that's part of the specified lattice. @@ -872,150 +846,196 @@ mod test_sample_d { /// should become a zero vector at the end of the matrix. #[test] fn point_of_lattice() { - let basis = MatZ::from_str("[[7, 0],[7, 3]]").unwrap(); - let center = MatQ::new(2, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let sample = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); - let sample_prec = - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None) - .unwrap(); - - // check whether hermite normal form of HNF(b) = HNF([b|sample_vector]) - let basis_concat_sample = basis.concat_horizontal(&sample).unwrap(); - let basis_concat_sample_prec = basis.concat_horizontal(&sample_prec).unwrap(); - let mut hnf_basis = MatZ::new(2, 2); - unsafe { fmpz_mat_hnf(&mut hnf_basis.matrix, &basis.matrix) }; - let mut hnf_basis_concat_sample = MatZ::new(2, 3); - let mut hnf_basis_concat_sample_prec = MatZ::new(2, 3); - unsafe { - fmpz_mat_hnf( - &mut hnf_basis_concat_sample.matrix, - &basis_concat_sample.matrix, - ) - }; - unsafe { - fmpz_mat_hnf( - &mut hnf_basis_concat_sample_prec.matrix, - &basis_concat_sample_prec.matrix, + for seeded in [false, true] { + let basis = MatZ::from_str("[[7, 0],[7, 3]]").unwrap(); + let center = MatQ::new(2, 1); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let sample = + sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)).unwrap(); + let sample_prec = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), ) - }; - assert_eq!( - hnf_basis.get_column(0).unwrap(), - hnf_basis_concat_sample.get_column(0).unwrap() - ); - assert_eq!( - hnf_basis.get_column(0).unwrap(), - hnf_basis_concat_sample_prec.get_column(0).unwrap() - ); - assert_eq!( - hnf_basis.get_column(1).unwrap(), - hnf_basis_concat_sample.get_column(1).unwrap() - ); - assert_eq!( - hnf_basis.get_column(1).unwrap(), - hnf_basis_concat_sample_prec.get_column(1).unwrap() - ); - // check whether last vector is zero, i.e. was linearly dependent and part of lattice - assert!(hnf_basis_concat_sample.get_column(2).unwrap().is_zero()); - assert!( - hnf_basis_concat_sample_prec - .get_column(2) - .unwrap() - .is_zero() - ); + .unwrap(); + + // check whether hermite normal form of HNF(b) = HNF([b|sample_vector]) + let basis_concat_sample = basis.concat_horizontal(&sample).unwrap(); + let basis_concat_sample_prec = basis.concat_horizontal(&sample_prec).unwrap(); + let mut hnf_basis = MatZ::new(2, 2); + unsafe { fmpz_mat_hnf(&mut hnf_basis.matrix, &basis.matrix) }; + let mut hnf_basis_concat_sample = MatZ::new(2, 3); + let mut hnf_basis_concat_sample_prec = MatZ::new(2, 3); + unsafe { + fmpz_mat_hnf( + &mut hnf_basis_concat_sample.matrix, + &basis_concat_sample.matrix, + ) + }; + unsafe { + fmpz_mat_hnf( + &mut hnf_basis_concat_sample_prec.matrix, + &basis_concat_sample_prec.matrix, + ) + }; + assert_eq!( + hnf_basis.get_column(0).unwrap(), + hnf_basis_concat_sample.get_column(0).unwrap() + ); + assert_eq!( + hnf_basis.get_column(0).unwrap(), + hnf_basis_concat_sample_prec.get_column(0).unwrap() + ); + assert_eq!( + hnf_basis.get_column(1).unwrap(), + hnf_basis_concat_sample.get_column(1).unwrap() + ); + assert_eq!( + hnf_basis.get_column(1).unwrap(), + hnf_basis_concat_sample_prec.get_column(1).unwrap() + ); + // check whether last vector is zero, i.e. was linearly dependent and part of lattice + assert!(hnf_basis_concat_sample.get_column(2).unwrap().is_zero()); + assert!( + hnf_basis_concat_sample_prec + .get_column(2) + .unwrap() + .is_zero() + ); + } } /// Checks whether `sample_d` returns an error if the gaussian parameter `s < 0`. #[test] fn invalid_gaussian_parameter() { - let basis = MatZ::identity(5, 5); - let center = MatQ::new(5, 1); - let basis_gso = MatQ::from(&basis).gso(); + for seeded in [false, true] { + let basis = MatZ::identity(5, 5); + let center = MatQ::new(5, 1); + let basis_gso = MatQ::from(&basis).gso(); - assert!(sample_d(&basis, ¢er, &Q::MINUS_ONE, None).is_err()); - assert!(sample_d(&basis, ¢er, &Q::from(i64::MIN), None).is_err()); + assert!(sample_d(&basis, ¢er, &Q::MINUS_ONE, seeded.then(test_seed)).is_err()); + assert!(sample_d(&basis, ¢er, &Q::from(i64::MIN), seeded.then(test_seed)).is_err()); - assert!( - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::MINUS_ONE, None).is_err() - ); - assert!( - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &Q::from(i64::MIN), None) + assert!( + sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &Q::MINUS_ONE, + seeded.then(test_seed) + ) + .is_err() + ); + assert!( + sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &Q::from(i64::MIN), + seeded.then(test_seed) + ) .is_err() - ); + ); + } } /// Checks whether `sample_d` returns an error if the basis and center number of rows differs. #[test] fn mismatching_matrix_dimensions() { - let basis = MatZ::identity(3, 5); - let center = MatQ::new(4, 1); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let res = sample_d(&basis, ¢er, &gaussian_parameter, None); - let res_prec = - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None); + for seeded in [false, true] { + let basis = MatZ::identity(3, 5); + let center = MatQ::new(4, 1); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let res = sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)); + let res_prec = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ); - assert!(res.is_err()); - assert!(res_prec.is_err()); + assert!(res.is_err()); + assert!(res_prec.is_err()); + } } /// Checks whether `sample_d` returns an error if center isn't a column vector. #[test] fn center_not_column_vector() { - let basis = MatZ::identity(2, 2); - let center = MatQ::new(2, 2); - let gaussian_parameter = Q::ONE; - let basis_gso = MatQ::from(&basis).gso(); - - let res = sample_d(&basis, ¢er, &gaussian_parameter, None); - let res_prec = - sample_d_precomputed_gso(&basis, &basis_gso, ¢er, &gaussian_parameter, None); + for seeded in [false, true] { + let basis = MatZ::identity(2, 2); + let center = MatQ::new(2, 2); + let gaussian_parameter = Q::ONE; + let basis_gso = MatQ::from(&basis).gso(); + + let res = sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)); + let res_prec = sample_d_precomputed_gso( + &basis, + &basis_gso, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ); - assert!(res.is_err()); - assert!(res_prec.is_err()); + assert!(res.is_err()); + assert!(res_prec.is_err()); + } } /// Ensures that the concentration bound holds. #[test] fn concentration_bound() { - let n = Z::from(20); - let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); - let orth = MatQ::from(&basis).gso(); - let mut len = Q::ZERO; - for i in 0..orth.get_num_columns() { - let column = orth.get_column(i).unwrap(); - let column_len = column.norm_eucl_sqrd().unwrap().sqrt(); - if column_len > len { - len = column_len + for seeded in [false, true] { + let n = Z::from(20); + let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); + let orth = MatQ::from(&basis).gso(); + let mut len = Q::ZERO; + for i in 0..orth.get_num_columns() { + let column = orth.get_column(i).unwrap(); + let column_len = column.norm_eucl_sqrd().unwrap().sqrt(); + if column_len > len { + len = column_len + } } - } - let expl_text = String::from("This test can fail with probability close to 0. + let expl_text = String::from("This test can fail with probability close to 0. It fails if the length of the sampled is longer than expected. If this happens, rerun the tests several times and check whether this issue comes up again."); - let center = MatQ::new(&n, 1); - let gaussian_parameter = - len * n.log(2).unwrap().sqrt() * (n.log(2).unwrap().log(2).unwrap()); - - for _ in 0..20 { - let res = sample_d(&basis, ¢er, &gaussian_parameter, None).unwrap(); - let res_prec = - sample_d_precomputed_gso(&basis, &orth, ¢er, &gaussian_parameter, None) - .unwrap(); + let center = MatQ::new(&n, 1); + let gaussian_parameter = + len * n.log(2).unwrap().sqrt() * (n.log(2).unwrap().log(2).unwrap()); + + for _ in 0..20 { + let res = + sample_d(&basis, ¢er, &gaussian_parameter, seeded.then(test_seed)).unwrap(); + let res_prec = sample_d_precomputed_gso( + &basis, + &orth, + ¢er, + &gaussian_parameter, + seeded.then(test_seed), + ) + .unwrap(); - assert!( - res.norm_eucl_sqrd().unwrap() <= gaussian_parameter.pow(2).unwrap().round() * &n, - "{expl_text}" - ); - assert!( - res_prec.norm_eucl_sqrd().unwrap() - <= gaussian_parameter.pow(2).unwrap().round() * &n, - "{expl_text}" - ); + assert!( + res.norm_eucl_sqrd().unwrap() + <= gaussian_parameter.pow(2).unwrap().round() * &n, + "{expl_text}" + ); + assert!( + res_prec.norm_eucl_sqrd().unwrap() + <= gaussian_parameter.pow(2).unwrap().round() * &n, + "{expl_text}" + ); + } } } @@ -1043,59 +1063,59 @@ mod test_sample_d { assert_eq!(sample_0, sample_prec); } - /// Ensures that `sample_d` outputs different vectors for different seeds. + /// Ensure that an orthogonalized base with not matching rows panics. #[test] - fn different_seed_different_sample() { - let basis = MatZ::identity(5, 5); - let center = MatQ::new(5, 1); - let gaussian_parameter = Q::from(1024); - - let sample_0 = sample_d(&basis, ¢er, &gaussian_parameter, Some([0; 32])).unwrap(); - let sample_1 = sample_d(&basis, ¢er, &gaussian_parameter, Some([1; 32])).unwrap(); - - assert_ne!(sample_0, sample_1); + fn precomputed_gso_mismatching_rows() { + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let n = Z::from(20); + let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); + let center = MatQ::new(&n, 1); + let false_gso = MatQ::new(basis.get_num_rows() + 1, basis.get_num_columns()); + + let _ = sample_d_precomputed_gso( + &basis, + &false_gso, + ¢er, + &Q::from(5), + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); + } } - - /// Ensures that seeded samples are still part of the specified lattice. + /// Ensure that an orthogonalized base with not matching columns panics. #[test] - fn seeded_point_of_lattice() { - use crate::traits::MatrixGetEntry; - - let basis = MatZ::from_str("[[7, 0],[7, 3]]").unwrap(); - let center = MatQ::new(2, 1); - let gaussian_parameter = Q::from(10); - - let sample = sample_d(&basis, ¢er, &gaussian_parameter, Some([42; 32])).unwrap(); - - // the basis is invertible, i.e. sample is a lattice point iff - // its coefficients w.r.t. the basis are integral - let coefficients = MatQ::from(&basis).inverse().unwrap() * MatQ::from(&sample); - for i in 0..coefficients.get_num_rows() { - let entry: Q = coefficients.get_entry(i, 0).unwrap(); - assert_eq!(entry.get_denominator(), Z::ONE); + fn precomputed_gso_mismatching_columns() { + for seeded in [false, true] { + let result = std::panic::catch_unwind(|| { + let n = Z::from(20); + let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); + let center = MatQ::new(&n, 1); + let false_gso = MatQ::new(basis.get_num_rows(), basis.get_num_columns() + 1); + + let _ = sample_d_precomputed_gso( + &basis, + &false_gso, + ¢er, + &Q::from(5), + seeded.then(test_seed), + ) + .unwrap(); + }); + assert!(result.is_err()); } } - /// Ensure that an orthogonalized base with not matching rows panics. + /// Checks whether `sample_d` outputs the known sample for a fixed seed. #[test] - #[should_panic] - fn precomputed_gso_mismatching_rows() { - let n = Z::from(20); - let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); - let center = MatQ::new(&n, 1); - let false_gso = MatQ::new(basis.get_num_rows() + 1, basis.get_num_columns()); + fn known_answer() { + let basis = MatZ::from_str("[[7, 0, 1],[7, 3, 2],[0, 1, 5]]").unwrap(); + let center = MatQ::from_str("[[1/2],[-3],[5]]").unwrap(); - let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5), None).unwrap(); - } - /// Ensure that an orthogonalized base with not matching columns panics. - #[test] - #[should_panic] - fn precomputed_gso_mismatching_columns() { - let n = Z::from(20); - let basis = MatZ::sample_uniform(&n, &n, 0, 5000).unwrap(); - let center = MatQ::new(&n, 1); - let false_gso = MatQ::new(basis.get_num_rows(), basis.get_num_columns() + 1); + let sample = sample_d(&basis, ¢er, &Q::from(100), Some([42; 32])).unwrap(); - let _ = sample_d_precomputed_gso(&basis, &false_gso, ¢er, &Q::from(5), None).unwrap(); + assert_eq!("[[28],[-44],[-24]]", sample.to_string()); } } diff --git a/src/utils/sample/uniform.rs b/src/utils/sample/uniform.rs index 15f62c6a..2983a555 100644 --- a/src/utils/sample/uniform.rs +++ b/src/utils/sample/uniform.rs @@ -275,108 +275,115 @@ impl UniformIntegerSampler { #[cfg(test)] mod test_uis { use super::{UniformIntegerSampler, Z}; + use crate::utils::sample::test_seed; use std::collections::HashSet; /// Checks whether sampling works fine for small interval sizes. #[test] fn small_interval() { - let size_2 = Z::from(2); - let size_7 = Z::from(7); + for seeded in [false, true] { + let size_2 = Z::from(2); + let size_7 = Z::from(7); - let mut uis_2 = UniformIntegerSampler::init(&size_2, None).unwrap(); - let mut uis_7 = UniformIntegerSampler::init(&size_7, None).unwrap(); + let mut uis_2 = UniformIntegerSampler::init(&size_2, seeded.then(test_seed)).unwrap(); + let mut uis_7 = UniformIntegerSampler::init(&size_7, seeded.then(test_seed)).unwrap(); - for _ in 0..3 { - let sample_2 = uis_2.sample(); - let sample_7 = uis_7.sample(); + for _ in 0..3 { + let sample_2 = uis_2.sample(); + let sample_7 = uis_7.sample(); - assert!(Z::ZERO <= sample_2); - assert!(sample_2 < size_2); - assert!(Z::ZERO <= sample_7); - assert!(sample_7 < size_7) + assert!(Z::ZERO <= sample_2); + assert!(sample_2 < size_2); + assert!(Z::ZERO <= sample_7); + assert!(sample_7 < size_7) + } } } /// Checks whether sampling works fine for large interval sizes. #[test] fn large_interval() { - let size_0 = Z::from(u64::MAX); - let size_1 = Z::from(u64::MAX) * 2 + 1; + for seeded in [false, true] { + let size_0 = Z::from(u64::MAX); + let size_1 = Z::from(u64::MAX) * 2 + 1; - let mut uis_0 = UniformIntegerSampler::init(&size_0, None).unwrap(); - let mut uis_1 = UniformIntegerSampler::init(&size_1, None).unwrap(); + let mut uis_0 = UniformIntegerSampler::init(&size_0, seeded.then(test_seed)).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&size_1, seeded.then(test_seed)).unwrap(); - for _i in 0..u8::MAX { - let sample_0 = uis_0.sample(); - let sample_1 = uis_1.sample(); + for _i in 0..u8::MAX { + let sample_0 = uis_0.sample(); + let sample_1 = uis_1.sample(); - assert!(Z::ZERO <= sample_0); - assert!(sample_0 < size_0); - assert!(Z::ZERO <= sample_1); - assert!(sample_1 < size_1); + assert!(Z::ZERO <= sample_0); + assert!(sample_0 < size_0); + assert!(Z::ZERO <= sample_1); + assert!(sample_1 < size_1); + } } } /// Checks whether it samples from the entire interval. #[test] fn entire_interval() { - let interval_sizes = vec![6, 7, 16]; - - for interval_size in interval_sizes { - let interval = Z::from(interval_size); - - let mut uis = UniformIntegerSampler::init(&interval, None).unwrap(); - - let mut samples = HashSet::new(); - for _ in 0..2_u32.pow(interval_size) { - samples.insert(uis.sample()); + for seeded in [false, true] { + let interval_sizes = vec![6, 7, 16]; + + for interval_size in interval_sizes { + let interval = Z::from(interval_size); + + let mut uis = + UniformIntegerSampler::init(&interval, seeded.then(test_seed)).unwrap(); + + let mut samples = HashSet::new(); + for _ in 0..2_u32.pow(interval_size) { + samples.insert(uis.sample()); + } + // if len(samples) == interval_size, then every element in [0, interval_size) + // needs to be represented in samples + assert_eq!( + interval_size, + samples.len() as u32, + "This test may fail with low probability." + ); } - // if len(samples) == interval_size, then every element in [0, interval_size) - // needs to be represented in samples - assert_eq!( - interval_size, - samples.len() as u32, - "This test may fail with low probability." - ); } } /// Checks whether interval sizes smaller than 2 result in an error. #[test] fn invalid_interval() { - assert!(UniformIntegerSampler::init(&Z::ZERO, None).is_err()); - assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, None).is_err()); + for seeded in [false, true] { + assert!(UniformIntegerSampler::init(&Z::ZERO, seeded.then(test_seed)).is_err()); + assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, seeded.then(test_seed)).is_err()); + } } /// Checks whether random bit sampling doesn't fill more bits than required. #[test] fn sample_bits_uniform_necessary_nr_bytes() { - let size_0 = Z::from(8); - let size_1 = Z::from(256); - let size_2 = Z::from(u32::MAX) + Z::ONE; - - let mut uis_0 = UniformIntegerSampler::init(&size_0, None).unwrap(); - let mut uis_1 = UniformIntegerSampler::init(&size_1, None).unwrap(); - let mut uis_2 = UniformIntegerSampler::init(&size_2, None).unwrap(); - - for _ in 0..u8::MAX { - let sample_0 = uis_0.sample_bits_uniform(); - let sample_1 = uis_1.sample_bits_uniform(); - let sample_2 = uis_2.sample_bits_uniform(); - - assert!(Z::ZERO <= sample_0); - assert!(sample_0 < size_0); - assert!(Z::ZERO <= sample_1); - assert!(sample_1 < size_1); - assert!(Z::ZERO <= sample_2); - assert!(sample_2 < size_2); + for seeded in [false, true] { + let size_0 = Z::from(8); + let size_1 = Z::from(256); + let size_2 = Z::from(u32::MAX) + Z::ONE; + + let mut uis_0 = UniformIntegerSampler::init(&size_0, seeded.then(test_seed)).unwrap(); + let mut uis_1 = UniformIntegerSampler::init(&size_1, seeded.then(test_seed)).unwrap(); + let mut uis_2 = UniformIntegerSampler::init(&size_2, seeded.then(test_seed)).unwrap(); + + for _ in 0..u8::MAX { + let sample_0 = uis_0.sample_bits_uniform(); + let sample_1 = uis_1.sample_bits_uniform(); + let sample_2 = uis_2.sample_bits_uniform(); + + assert!(Z::ZERO <= sample_0); + assert!(sample_0 < size_0); + assert!(Z::ZERO <= sample_1); + assert!(sample_1 < size_1); + assert!(Z::ZERO <= sample_2); + assert!(sample_2 < size_2); + } } } -} - -#[cfg(test)] -mod test_uis_seeded { - use super::{UniformIntegerSampler, Z}; /// Checks whether two samplers with the same seed output the same samples /// for small and large interval sizes. @@ -394,38 +401,17 @@ mod test_uis_seeded { } } - /// Checks whether two samplers with different seeds output different samples. + /// Checks whether the seeded sampler outputs the known samples for a fixed seed. #[test] - fn different_seed_different_samples() { - let interval_size = Z::from(u64::MAX); - - let mut uis_0 = UniformIntegerSampler::init(&interval_size, Some([0; 32])).unwrap(); - let mut uis_1 = UniformIntegerSampler::init(&interval_size, Some([1; 32])).unwrap(); - - let samples_0: Vec = (0..16).map(|_| uis_0.sample()).collect(); - let samples_1: Vec = (0..16).map(|_| uis_1.sample()).collect(); - - assert_ne!(samples_0, samples_1); - } - - /// Checks whether seeded samples are kept in the interval. - #[test] - fn keeps_range() { - let interval_size = Z::from(7); + fn known_answer() { + let interval_size = Z::from(u64::MAX) * 2 + 1; let mut uis = UniformIntegerSampler::init(&interval_size, Some([42; 32])).unwrap(); - for _ in 0..u8::MAX { - let sample = uis.sample(); - - assert!(Z::ZERO <= sample); - assert!(sample < interval_size); - } - } + let samples: Vec = (0..4).map(|_| uis.sample().to_string()).collect(); - /// Checks whether interval sizes smaller than 1 result in an error. - #[test] - fn invalid_interval() { - assert!(UniformIntegerSampler::init(&Z::ZERO, Some([42; 32])).is_err()); - assert!(UniformIntegerSampler::init(&Z::MINUS_ONE, Some([42; 32])).is_err()); + assert_eq!( + "22821106978489223123, 9739806954439942819, 36580328303367661063, 5398446775404852435", + samples.join(", ") + ); } } From 420a89123b2736d1f2a836169ed3888d3038bdd4 Mon Sep 17 00:00:00 2001 From: jnsiemer Date: Mon, 19 Oct 2026 17:50:03 +0100 Subject: [PATCH 5/5] Address comments from review --- src/integer/mat_poly_over_z/sample/uniform.rs | 2 ++ src/integer/mat_z/sample/discrete_gauss.rs | 2 ++ src/macros/seeded.rs | 26 +++++++++++++++++++ src/rational/mat_q/rounding.rs | 2 ++ src/rational/mat_q/sample/gauss.rs | 2 ++ src/rational/poly_over_q/rounding.rs | 2 ++ src/utils/sample/discrete_gauss.rs | 3 ++- 7 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/integer/mat_poly_over_z/sample/uniform.rs b/src/integer/mat_poly_over_z/sample/uniform.rs index db6113c0..884257fe 100644 --- a/src/integer/mat_poly_over_z/sample/uniform.rs +++ b/src/integer/mat_poly_over_z/sample/uniform.rs @@ -83,6 +83,8 @@ impl MatPolyOverZ { upper_bound: impl Into, seed: Option<[u8; 32]>, ) -> Result { + // derives an independent seed for every call of a seedable function below, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); let lower_bound: Z = lower_bound.into(); diff --git a/src/integer/mat_z/sample/discrete_gauss.rs b/src/integer/mat_z/sample/discrete_gauss.rs index 2ac7cd67..cc47d985 100644 --- a/src/integer/mat_z/sample/discrete_gauss.rs +++ b/src/integer/mat_z/sample/discrete_gauss.rs @@ -204,6 +204,8 @@ impl MatZ { r: impl Into, seed: Option<[u8; 32]>, ) -> Result { + // derives an independent seed for every call of a seedable function below, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); if !sigma_sqrt.is_square() { diff --git a/src/macros/seeded.rs b/src/macros/seeded.rs index fda5a607..3b0e1da7 100644 --- a/src/macros/seeded.rs +++ b/src/macros/seeded.rs @@ -17,6 +17,8 @@ /// - the public function `name_seeded`, which takes `seed: [u8; 32]` as its last parameter /// and forwards `Some(seed)`. /// +/// The datatype `[u8; 32]` is defined by the [rand]-crate and enables random seeding with up to 256 random bits. +/// /// All three functions share the provided doc-comment. Every doc line (and any other attribute) /// is added to all three functions, except for the doc lines preceded by /// - `#[unseeded]`, which are only added to `name`, @@ -55,6 +57,30 @@ /// ); /// } /// ``` +/// +/// ## Using seedable functions and samplers inside a seedable function +/// The implementation needs to pass `seed` on to every seedable function or sampler it calls, +/// treating them as black boxes. Therefore, it calls the `name_optionally_seeded` variant of a +/// seedable function, e.g. `PolyOverZ::sample_uniform_optionally_seeded`, or passes the seed to +/// the `init` function of a sampler, e.g. [`UniformIntegerSampler::init`](crate::utils::sample::uniform::UniformIntegerSampler::init), +/// instead of calling the public functions, which would ignore `seed`. +/// - If exactly one call of a seedable function or sampler is made, `seed` is passed on directly. +/// - If several calls are made, e.g. one per entry of a matrix, every call needs to receive +/// an independent seed. Otherwise, every call would output the same sample for a provided seed. +/// These seeds are derived via `let mut rng = SamplerRng::new(seed);` and `rng.derive_seed()` +/// for each call, which returns a fresh seed drawn from `rng` if `seed` is provided +/// and `None` otherwise, i.e. every call uses a fresh [`ThreadRng`](rand::rngs::ThreadRng) then. +/// +/// ```compile_fail +/// pub(crate) fn sample_gauss(center: &MatQ, sigma: f64, seed: Option<[u8; 32]>) -> Result { +/// let mut rng = SamplerRng::new(seed); +/// ... +/// for (i, j) in entries { +/// let sample = Q::sample_gauss_optionally_seeded(center_ij, sigma, rng.derive_seed())?; +/// ... +/// } +/// } +/// ``` macro_rules! seedable_function { // A doc line preceded by `#[unseeded]` is only added to the unseeded function. (@attrs [$($unseeded:tt)*] [$($seeded:tt)*] [$($optional:tt)*] #[unseeded] #[$attr:meta] $($rest:tt)*) => { diff --git a/src/rational/mat_q/rounding.rs b/src/rational/mat_q/rounding.rs index a37a451b..d93a3056 100644 --- a/src/rational/mat_q/rounding.rs +++ b/src/rational/mat_q/rounding.rs @@ -213,6 +213,8 @@ impl MatQ { r: impl Into, seed: Option<[u8; 32]>, ) -> Result { + // derives an independent seed for every call of a seedable function below, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); let mut out = MatZ::new(self.get_num_rows(), self.get_num_columns()); diff --git a/src/rational/mat_q/sample/gauss.rs b/src/rational/mat_q/sample/gauss.rs index bc807dfd..499c1cb1 100644 --- a/src/rational/mat_q/sample/gauss.rs +++ b/src/rational/mat_q/sample/gauss.rs @@ -59,6 +59,8 @@ impl MatQ { sigma: impl Into, seed: Option<[u8; 32]>, ) -> Result { + // derives an independent seed for every call of a seedable function below, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); let mut out = MatQ::new(center.get_num_rows(), center.get_num_columns()); diff --git a/src/rational/poly_over_q/rounding.rs b/src/rational/poly_over_q/rounding.rs index d6da965d..15554629 100644 --- a/src/rational/poly_over_q/rounding.rs +++ b/src/rational/poly_over_q/rounding.rs @@ -138,6 +138,8 @@ impl PolyOverQ { r: impl Into, seed: Option<[u8; 32]>, ) -> Result { + // derives an independent seed for every call of a seedable function below, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); let r = r.into(); diff --git a/src/utils/sample/discrete_gauss.rs b/src/utils/sample/discrete_gauss.rs index 43252152..d725318f 100644 --- a/src/utils/sample/discrete_gauss.rs +++ b/src/utils/sample/discrete_gauss.rs @@ -440,7 +440,8 @@ pub(crate) fn sample_d_precomputed_gso( let mut out = MatZ::new(basis_gso.get_num_rows(), 1); - // derives an independent seed for the sampler of each dimension if a seed is provided + // derives an independent seed for the sampler of each dimension if a seed is provided, + // see the documentation of `seedable_function` in `src/macros/seeded.rs` let mut rng = SamplerRng::new(seed); for i in (0..basis_gso.get_num_columns()).rev() {