Skip to content

Commit c6d3b56

Browse files
committed
Add uniform sampler for MatNTT
1 parent a308ba9 commit c6d3b56

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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 sampling algorithms for different distributions.
10+
11+
mod uniform;
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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

Comments
 (0)