plat-core 0.1.0

Core types and traits for the plat FHE compute engine
Documentation
//! Random sampling for lattice-based cryptography.
//!
//! Provides discrete Gaussian sampling (for error terms) and
//! uniform sampling (for key/mask generation).

use crate::modular::mod_reduce;
use crate::params::Params;
use crate::poly::Poly;
use rand::Rng;
use rand_distr::{Distribution, Normal};

/// Sample a polynomial with coefficients from the discrete Gaussian
/// distribution centered at 0 with standard deviation σ.
pub fn sample_gaussian<R: Rng>(params: &Params, rng: &mut R) -> Poly {
    let sigma = params.sigma_x1000 as f64 / 1000.0;
    let normal = Normal::new(0.0, sigma).unwrap();
    let coeffs = (0..params.n)
        .map(|_| {
            let sample = normal.sample(rng).round() as i64;
            mod_reduce(sample, params.q)
        })
        .collect();
    Poly { coeffs }
}

/// Sample a polynomial with coefficients uniformly random in [0, q).
pub fn sample_uniform<R: Rng>(params: &Params, rng: &mut R) -> Poly {
    let coeffs = (0..params.n).map(|_| rng.gen_range(0..params.q)).collect();
    Poly { coeffs }
}

/// Sample a ternary polynomial: coefficients in {-1, 0, 1}.
/// Used for secret key generation.
pub fn sample_ternary<R: Rng>(params: &Params, rng: &mut R) -> Poly {
    let coeffs = (0..params.n)
        .map(|_| {
            let v: i64 = rng.gen_range(-1..=1);
            mod_reduce(v, params.q)
        })
        .collect();
    Poly { coeffs }
}

/// Sample a binary polynomial: coefficients in {0, 1}.
pub fn sample_binary<R: Rng>(params: &Params, rng: &mut R) -> Poly {
    let coeffs = (0..params.n)
        .map(|_| rng.gen_range(0..=1u64))
        .collect();
    Poly { coeffs }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rand::SeedableRng;
    use rand::rngs::StdRng;

    #[test]
    fn test_gaussian_in_range() {
        let p = Params::test_tiny();
        let mut rng = StdRng::seed_from_u64(42);
        let poly = sample_gaussian(&p, &mut rng);
        for &c in &poly.coeffs {
            assert!(c < p.q);
        }
        assert_eq!(poly.coeffs.len(), p.n);
    }

    #[test]
    fn test_uniform_in_range() {
        let p = Params::test_tiny();
        let mut rng = StdRng::seed_from_u64(42);
        let poly = sample_uniform(&p, &mut rng);
        for &c in &poly.coeffs {
            assert!(c < p.q);
        }
    }

    #[test]
    fn test_ternary_values() {
        let p = Params::test_tiny();
        let mut rng = StdRng::seed_from_u64(42);
        let poly = sample_ternary(&p, &mut rng);
        for &c in &poly.coeffs {
            // Should be 0, 1, or q-1 (which is -1 mod q)
            assert!(c == 0 || c == 1 || c == p.q - 1, "got {c}");
        }
    }

    #[test]
    fn test_gaussian_centered() {
        // Statistical test: mean should be approximately 0
        let p = Params::test_small();
        let mut rng = StdRng::seed_from_u64(123);
        let poly = sample_gaussian(&p, &mut rng);
        let centered: Vec<i64> = poly
            .coeffs
            .iter()
            .map(|&c| crate::modular::center(c, p.q))
            .collect();
        let mean: f64 = centered.iter().map(|&x| x as f64).sum::<f64>() / p.n as f64;
        assert!(
            mean.abs() < 2.0,
            "mean {mean} too far from 0 for Gaussian"
        );
    }
}