|
| 1 | +// Copyright © 2025 Niklas Siemer |
| 2 | +// |
| 3 | +// This file is part of qFALL-math. |
| 4 | +// |
| 5 | +// qFALL-math is free software: you can redistribute it and/or modify it under |
| 6 | +// the terms of the Mozilla Public License Version 2.0 as published by the |
| 7 | +// Mozilla Foundation. See <https://mozilla.org/en-US/MPL/2.0/>. |
| 8 | + |
| 9 | +//! This module contains algorithms for sampling according to the uniform distribution. |
| 10 | +
|
| 11 | +use crate::{ |
| 12 | + integer::Z, |
| 13 | + integer_mod_q::NTTPolynomialRingZq, |
| 14 | + utils::{index::evaluate_index, sample::uniform::UniformIntegerSampler}, |
| 15 | +}; |
| 16 | +use std::fmt::Display; |
| 17 | + |
| 18 | +impl NTTPolynomialRingZq { |
| 19 | + /// Generates a [`NTTPolynomialRingZq`] instance with maximum degree `nr_entries` |
| 20 | + /// and entries chosen uniform at random in `[0, modulus)`. |
| 21 | + /// |
| 22 | + /// The internally used uniform at random chosen bytes are generated |
| 23 | + /// by [`ThreadRng`](rand::rngs::ThreadRng), which uses ChaCha12 and |
| 24 | + /// is considered cryptographically secure. |
| 25 | + /// |
| 26 | + /// Parameters: |
| 27 | + /// - `nr_entries`: specifies the largest number of sampled entries |
| 28 | + /// - `modulus`: specifies the modulus of the values and thus, |
| 29 | + /// the interval size over which is sampled |
| 30 | + /// |
| 31 | + /// Returns a fresh [`NTTPolynomialRingZq`] instance of length `nr_entries` with entries |
| 32 | + /// chosen uniform at random in `[0, modulus)`. |
| 33 | + /// |
| 34 | + /// # Examples |
| 35 | + /// ``` |
| 36 | + /// use qfall_math::integer_mod_q::NTTPolynomialRingZq; |
| 37 | + /// |
| 38 | + /// let sample = NTTPolynomialRingZq::sample_uniform(3, 17); |
| 39 | + /// ``` |
| 40 | + /// |
| 41 | + /// # Panics ... |
| 42 | + /// - if `modulus` is smaller than `2`. |
| 43 | + /// - the `nr_entries` is negative or it does not fit into an [`i64`]. |
| 44 | + pub fn sample_uniform( |
| 45 | + nr_entries: impl TryInto<i64> + Display + Copy, |
| 46 | + modulus: impl Into<Z>, |
| 47 | + ) -> Self { |
| 48 | + let max_degree = evaluate_index(nr_entries) |
| 49 | + .expect("`nr_entries` can't be smaller negative and must fit into an i64."); |
| 50 | + let interval_size = modulus.into(); |
| 51 | + assert!(interval_size > 1); |
| 52 | + |
| 53 | + let mut uis = UniformIntegerSampler::init(&interval_size).unwrap(); |
| 54 | + |
| 55 | + let vector = (0..max_degree).map(|_| uis.sample()).collect(); |
| 56 | + Self { poly: vector } |
| 57 | + } |
| 58 | +} |
| 59 | + |
| 60 | +#[cfg(test)] |
| 61 | +mod test_sample_uniform { |
| 62 | + use crate::{ |
| 63 | + integer::Z, |
| 64 | + integer_mod_q::{Modulus, NTTPolynomialRingZq}, |
| 65 | + }; |
| 66 | + |
| 67 | + /// Checks whether the boundaries of the interval are kept for small moduli. |
| 68 | + #[test] |
| 69 | + fn boundaries_kept_small() { |
| 70 | + let modulus = Z::from(17); |
| 71 | + |
| 72 | + let poly = NTTPolynomialRingZq::sample_uniform(32, &modulus); |
| 73 | + |
| 74 | + for i in 0..32 { |
| 75 | + let sample = &poly.poly[i]; |
| 76 | + assert!(&Z::ZERO <= sample); |
| 77 | + assert!(sample < &modulus); |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + /// Checks whether the boundaries of the interval are kept for large moduli. |
| 82 | + #[test] |
| 83 | + fn boundaries_kept_large() { |
| 84 | + let modulus = Z::from(i64::MAX); |
| 85 | + |
| 86 | + let poly = NTTPolynomialRingZq::sample_uniform(256, &modulus); |
| 87 | + |
| 88 | + for i in 0..256 { |
| 89 | + let sample = &poly.poly[i]; |
| 90 | + assert!(&Z::ZERO <= sample); |
| 91 | + assert!(sample < &modulus); |
| 92 | + } |
| 93 | + } |
| 94 | + |
| 95 | + /// Checks whether the number of coefficients is correct. |
| 96 | + #[test] |
| 97 | + fn nr_coeffs() { |
| 98 | + let degrees = [1, 3, 7, 15, 32, 120]; |
| 99 | + for degree in degrees { |
| 100 | + let res = NTTPolynomialRingZq::sample_uniform(degree, u64::MAX); |
| 101 | + |
| 102 | + assert_eq!( |
| 103 | + degree, |
| 104 | + res.poly.len(), |
| 105 | + "Could fail with probability 1/{}.", |
| 106 | + u64::MAX |
| 107 | + ); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + /// Checks whether providing an invalid interval/ modulus results in an error. |
| 112 | + #[test] |
| 113 | + #[should_panic] |
| 114 | + fn invalid_modulus_negative() { |
| 115 | + let _ = NTTPolynomialRingZq::sample_uniform(1, i64::MIN); |
| 116 | + } |
| 117 | + |
| 118 | + /// Checks whether providing an invalid interval/ modulus results in an error. |
| 119 | + #[test] |
| 120 | + #[should_panic] |
| 121 | + fn invalid_modulus_one() { |
| 122 | + let _ = NTTPolynomialRingZq::sample_uniform(1, 1); |
| 123 | + } |
| 124 | + |
| 125 | + /// Checks whether providing a length smaller than `1` results in an error. |
| 126 | + #[test] |
| 127 | + #[should_panic] |
| 128 | + fn invalid_max_degree() { |
| 129 | + let _ = NTTPolynomialRingZq::sample_uniform(-1, 15); |
| 130 | + let _ = NTTPolynomialRingZq::sample_uniform(i64::MIN, 15); |
| 131 | + } |
| 132 | + |
| 133 | + /// Checks whether `sample_uniform` is available for all types |
| 134 | + /// implementing [`Into<Z>`], i.e. u8, u16, u32, u64, i8, ... |
| 135 | + #[test] |
| 136 | + fn availability() { |
| 137 | + let modulus = Modulus::from(10); |
| 138 | + let z = Z::from(10); |
| 139 | + |
| 140 | + let _ = NTTPolynomialRingZq::sample_uniform(1u64, 10u16); |
| 141 | + let _ = NTTPolynomialRingZq::sample_uniform(1i64, 10u32); |
| 142 | + let _ = NTTPolynomialRingZq::sample_uniform(1u8, 10u64); |
| 143 | + let _ = NTTPolynomialRingZq::sample_uniform(1u16, 10i8); |
| 144 | + let _ = NTTPolynomialRingZq::sample_uniform(1u32, 10i16); |
| 145 | + let _ = NTTPolynomialRingZq::sample_uniform(1i32, 10i32); |
| 146 | + let _ = NTTPolynomialRingZq::sample_uniform(1i16, 10i64); |
| 147 | + let _ = NTTPolynomialRingZq::sample_uniform(1i8, &z); |
| 148 | + let _ = NTTPolynomialRingZq::sample_uniform(1, z); |
| 149 | + let _ = NTTPolynomialRingZq::sample_uniform(1, &modulus); |
| 150 | + let _ = NTTPolynomialRingZq::sample_uniform(1, modulus); |
| 151 | + } |
| 152 | +} |
0 commit comments