|
| 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::{MatNTTPolynomialRingZq, NTTPolynomialRingZq}, |
| 14 | + utils::index::evaluate_index, |
| 15 | +}; |
| 16 | +use std::fmt::Display; |
| 17 | + |
| 18 | +impl MatNTTPolynomialRingZq { |
| 19 | + /// Generates a [`MatNTTPolynomialRingZq`] instance with maximum degree `modulus_degree` |
| 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 | + /// - `num_rows`: defines the number of rows of the matrix |
| 28 | + /// - `num_columns`: defines the number of columns of the matrix |
| 29 | + /// - `modulus_degree`: specifies the degree of the modulus polynomial, i.e. the maximum number |
| 30 | + /// of sampled coefficients is `modulus_degree - 1` |
| 31 | + /// - `modulus`: specifies the modulus of the values and thus, |
| 32 | + /// the interval size over which is sampled |
| 33 | + /// |
| 34 | + /// Returns a fresh [`MatNTTPolynomialRingZq`] instance of length `modulus_degree` with entries |
| 35 | + /// chosen uniform at random in `[0, modulus)`. |
| 36 | + /// |
| 37 | + /// # Examples |
| 38 | + /// ``` |
| 39 | + /// use qfall_math::integer_mod_q::MatNTTPolynomialRingZq; |
| 40 | + /// |
| 41 | + /// let sample = MatNTTPolynomialRingZq::sample_uniform(3, 2, 3, 17); |
| 42 | + /// ``` |
| 43 | + /// |
| 44 | + /// # Panics ... |
| 45 | + /// - if `nr_rows` or `nr_columns` is `0`. |
| 46 | + /// - if `modulus` is smaller than `2`. |
| 47 | + /// - the `modulus_degree` is smaller than `2` or it does not fit into an [`i64`]. |
| 48 | + pub fn sample_uniform( |
| 49 | + nr_rows: usize, |
| 50 | + nr_columns: usize, |
| 51 | + modulus_degree: impl TryInto<i64> + Display + Copy, |
| 52 | + modulus: impl Into<Z>, |
| 53 | + ) -> Self { |
| 54 | + assert!(nr_rows > 0); |
| 55 | + assert!(nr_columns > 0); |
| 56 | + let modulus_degree = evaluate_index(modulus_degree) |
| 57 | + .expect("`modulus_degree` can't be smaller negative and must fit into an i64."); |
| 58 | + let interval_size = modulus.into(); |
| 59 | + assert!(interval_size > 1); |
| 60 | + |
| 61 | + let mut res = Vec::with_capacity(nr_columns); |
| 62 | + |
| 63 | + for _ in 0..nr_columns { |
| 64 | + let mut col_vec = Vec::with_capacity(nr_rows); |
| 65 | + for _ in 0..nr_rows { |
| 66 | + let ntt_poly = NTTPolynomialRingZq::sample_uniform(modulus_degree, &interval_size); |
| 67 | + col_vec.push(ntt_poly); |
| 68 | + } |
| 69 | + res.push(col_vec); |
| 70 | + } |
| 71 | + |
| 72 | + MatNTTPolynomialRingZq { matrix: res } |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +#[cfg(test)] |
| 77 | +mod test_sample_uniform { |
| 78 | + use crate::{integer::Z, integer_mod_q::MatNTTPolynomialRingZq}; |
| 79 | + |
| 80 | + /// Checks whether the boundaries of the interval are kept for small intervals. |
| 81 | + #[test] |
| 82 | + fn boundaries_kept_small() { |
| 83 | + for _ in 0..32 { |
| 84 | + let matrix = MatNTTPolynomialRingZq::sample_uniform(1, 1, 1, 17); |
| 85 | + let sample = matrix.matrix[0][0].poly[0].clone(); |
| 86 | + |
| 87 | + assert!(Z::ZERO <= sample); |
| 88 | + assert!(sample < 17); |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + /// Checks whether the boundaries of the interval are kept for large intervals. |
| 93 | + #[test] |
| 94 | + fn boundaries_kept_large() { |
| 95 | + for _ in 0..256 { |
| 96 | + let matrix = MatNTTPolynomialRingZq::sample_uniform(1, 1, 1, 17); |
| 97 | + let sample = matrix.matrix[0][0].poly[0].clone(); |
| 98 | + |
| 99 | + assert!(Z::ZERO <= sample); |
| 100 | + assert!(sample < u64::MAX); |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + /// Checks whether the number of coefficients is correct. |
| 105 | + #[test] |
| 106 | + fn nr_coeffs() { |
| 107 | + let degrees = [1, 3, 7, 15, 32, 120]; |
| 108 | + |
| 109 | + for degree in degrees { |
| 110 | + let matrix = MatNTTPolynomialRingZq::sample_uniform(1, 1, degree, 17); |
| 111 | + let poly = matrix.matrix[0][0].clone(); |
| 112 | + |
| 113 | + assert_eq!(degree, poly.poly.len(),); |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + /// Checks whether matrices with at least one dimension chosen smaller than `1` |
| 118 | + /// or too large for an [`i64`] results in an error. |
| 119 | + #[should_panic] |
| 120 | + #[test] |
| 121 | + fn false_size() { |
| 122 | + let _ = MatNTTPolynomialRingZq::sample_uniform(0, 1, 2, 3); |
| 123 | + } |
| 124 | + |
| 125 | + /// Checks whether 0 modulus polynomial is insufficient. |
| 126 | + #[test] |
| 127 | + #[should_panic] |
| 128 | + fn invalid_modulus() { |
| 129 | + let _ = MatNTTPolynomialRingZq::sample_uniform(1, 1, 1, 1); |
| 130 | + } |
| 131 | + |
| 132 | + /// Checks whether the size of uniformly random sampled matrices |
| 133 | + /// fits the specified dimensions. |
| 134 | + #[test] |
| 135 | + fn matrix_size() { |
| 136 | + let mat_0 = MatNTTPolynomialRingZq::sample_uniform(3, 3, 2, 2); |
| 137 | + let mat_1 = MatNTTPolynomialRingZq::sample_uniform(4, 1, 2, 2); |
| 138 | + let mat_2 = MatNTTPolynomialRingZq::sample_uniform(1, 5, 2, 2); |
| 139 | + let mat_3 = MatNTTPolynomialRingZq::sample_uniform(15, 20, 2, 2); |
| 140 | + |
| 141 | + assert_eq!(3, mat_0.matrix[0].len()); |
| 142 | + assert_eq!(3, mat_0.matrix.len()); |
| 143 | + assert_eq!(4, mat_1.matrix[0].len()); |
| 144 | + assert_eq!(1, mat_1.matrix.len()); |
| 145 | + assert_eq!(1, mat_2.matrix[0].len()); |
| 146 | + assert_eq!(5, mat_2.matrix.len()); |
| 147 | + assert_eq!(15, mat_3.matrix[0].len()); |
| 148 | + assert_eq!(20, mat_3.matrix.len()); |
| 149 | + } |
| 150 | +} |
0 commit comments