Skip to content

Commit e188a8c

Browse files
committed
Implement uniform sampling for NTT Polynomials
1 parent cdc34dd commit e188a8c

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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

Comments
 (0)