Skip to main content

plat_core/
sampling.rs

1//! Random sampling for lattice-based cryptography.
2//!
3//! Provides discrete Gaussian sampling (for error terms) and
4//! uniform sampling (for key/mask generation).
5
6use crate::modular::mod_reduce;
7use crate::params::Params;
8use crate::poly::Poly;
9use rand::Rng;
10use rand_distr::{Distribution, Normal};
11
12/// Sample a polynomial with coefficients from the discrete Gaussian
13/// distribution centered at 0 with standard deviation σ.
14pub fn sample_gaussian<R: Rng>(params: &Params, rng: &mut R) -> Poly {
15    let sigma = params.sigma_x1000 as f64 / 1000.0;
16    let normal = Normal::new(0.0, sigma).unwrap();
17    let coeffs = (0..params.n)
18        .map(|_| {
19            let sample = normal.sample(rng).round() as i64;
20            mod_reduce(sample, params.q)
21        })
22        .collect();
23    Poly { coeffs }
24}
25
26/// Sample a polynomial with coefficients uniformly random in [0, q).
27pub fn sample_uniform<R: Rng>(params: &Params, rng: &mut R) -> Poly {
28    let coeffs = (0..params.n).map(|_| rng.gen_range(0..params.q)).collect();
29    Poly { coeffs }
30}
31
32/// Sample a ternary polynomial: coefficients in {-1, 0, 1}.
33/// Used for secret key generation.
34pub fn sample_ternary<R: Rng>(params: &Params, rng: &mut R) -> Poly {
35    let coeffs = (0..params.n)
36        .map(|_| {
37            let v: i64 = rng.gen_range(-1..=1);
38            mod_reduce(v, params.q)
39        })
40        .collect();
41    Poly { coeffs }
42}
43
44/// Sample a binary polynomial: coefficients in {0, 1}.
45pub fn sample_binary<R: Rng>(params: &Params, rng: &mut R) -> Poly {
46    let coeffs = (0..params.n)
47        .map(|_| rng.gen_range(0..=1u64))
48        .collect();
49    Poly { coeffs }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use rand::SeedableRng;
56    use rand::rngs::StdRng;
57
58    #[test]
59    fn test_gaussian_in_range() {
60        let p = Params::test_tiny();
61        let mut rng = StdRng::seed_from_u64(42);
62        let poly = sample_gaussian(&p, &mut rng);
63        for &c in &poly.coeffs {
64            assert!(c < p.q);
65        }
66        assert_eq!(poly.coeffs.len(), p.n);
67    }
68
69    #[test]
70    fn test_uniform_in_range() {
71        let p = Params::test_tiny();
72        let mut rng = StdRng::seed_from_u64(42);
73        let poly = sample_uniform(&p, &mut rng);
74        for &c in &poly.coeffs {
75            assert!(c < p.q);
76        }
77    }
78
79    #[test]
80    fn test_ternary_values() {
81        let p = Params::test_tiny();
82        let mut rng = StdRng::seed_from_u64(42);
83        let poly = sample_ternary(&p, &mut rng);
84        for &c in &poly.coeffs {
85            // Should be 0, 1, or q-1 (which is -1 mod q)
86            assert!(c == 0 || c == 1 || c == p.q - 1, "got {c}");
87        }
88    }
89
90    #[test]
91    fn test_gaussian_centered() {
92        // Statistical test: mean should be approximately 0
93        let p = Params::test_small();
94        let mut rng = StdRng::seed_from_u64(123);
95        let poly = sample_gaussian(&p, &mut rng);
96        let centered: Vec<i64> = poly
97            .coeffs
98            .iter()
99            .map(|&c| crate::modular::center(c, p.q))
100            .collect();
101        let mean: f64 = centered.iter().map(|&x| x as f64).sum::<f64>() / p.n as f64;
102        assert!(
103            mean.abs() < 2.0,
104            "mean {mean} too far from 0 for Gaussian"
105        );
106    }
107}