Skip to main content

helpers_sampling/
helpers_sampling.rs

1// MIT License
2//
3// Copyright (c) 2026 Raja Lehtihet & Wael El Oraiby
4//
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11//
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14//
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23//! Demonstrates all sampling helper entrypoints in one executable.
24//!
25//! Run with:
26//! `cargo run --example helpers_sampling`
27//!
28//! What this example does:
29//! 1. Builds a ring context for `R_q = Z_q[x] / (x^16 + 1)`.
30//! 2. Seeds a deterministic test RNG so output is reproducible.
31//! 3. Samples:
32//!    - uniform coefficients in `Z_q`,
33//!    - centered-binomial noise (eta=3),
34//!    - Gaussian-like integer noise (sigma=2.5),
35//!    - a raw bounded integer via rejection sampling.
36//! 4. Prints a compact summary.
37
38mod common;
39
40use common::sampling::{
41    sample_cbd_poly_example, sample_discrete_gaussian_poly_example,
42    sample_rejection_u64_below_example, sample_uniform_poly_example,
43};
44use core::convert::Infallible;
45use nc_polynomial::RingContext;
46use rand_core::TryCryptoRng;
47use rand_core::TryRng;
48
49#[derive(Debug, Clone)]
50struct ExampleRng {
51    state: u64,
52}
53
54impl ExampleRng {
55    /// Creates a deterministic RNG state. This is only for examples/tests.
56    fn new(seed: u64) -> Self {
57        Self { state: seed }
58    }
59
60    /// xorshift64* step: simple, fast, reproducible.
61    ///
62    /// This is not intended to be a production CSPRNG. We only need determinism
63    /// for stable example output.
64    fn step(&mut self) -> u64 {
65        let mut x = self.state;
66        x ^= x >> 12;
67        x ^= x << 25;
68        x ^= x >> 27;
69        self.state = x;
70        x.wrapping_mul(0x2545_F491_4F6C_DD1D)
71    }
72}
73
74impl TryRng for ExampleRng {
75    type Error = Infallible;
76
77    fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
78        Ok(self.step() as u32)
79    }
80
81    fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
82        Ok(self.step())
83    }
84
85    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Self::Error> {
86        let mut offset = 0;
87        while offset < dest.len() {
88            let word = self.step().to_le_bytes();
89            let chunk = (dest.len() - offset).min(8);
90            dest[offset..offset + chunk].copy_from_slice(&word[..chunk]);
91            offset += chunk;
92        }
93        Ok(())
94    }
95}
96
97impl TryCryptoRng for ExampleRng {}
98
99fn main() {
100    // Configure modulus polynomial `x^16 + 1`.
101    //
102    // We allocate `max_degree + 1` coefficients and set:
103    // - constant term: 1
104    // - x^16 term: 1
105    // - all intermediate terms: 0
106    let mut modulus_poly = vec![0_u64; 16 + 1];
107    modulus_poly[0] = 1;
108    modulus_poly[16] = 1;
109    // Build validated ring context with NTT-friendly modulus and primitive root.
110    let ctx =
111        RingContext::from_parts(16, 998_244_353, &modulus_poly, 3).expect("context should build");
112
113    // Deterministic seed makes this executable reproducible.
114    let mut rng = ExampleRng::new(0xA11C_E123_0000_0001);
115
116    // Uniform sample in `R_q`: every coefficient is sampled independently in `[0, q)`.
117    let uniform =
118        sample_uniform_poly_example(&ctx, &mut rng).expect("uniform sampling should work");
119    // Centered-binomial sample: each coefficient is small noise around 0.
120    let cbd = sample_cbd_poly_example(&ctx, 3, &mut rng).expect("CBD sampling should work");
121    // Gaussian-like sample from Box-Muller + integer rounding.
122    let gaussian =
123        sample_discrete_gaussian_poly_example(&ctx, 2.5, &mut rng).expect("gaussian should work");
124    // Raw integer rejection sample in `[0, 1000)`.
125    let below = sample_rejection_u64_below_example(1_000, &mut rng).expect("rejection should work");
126
127    // Print concise observable properties so this can be used as a smoke run.
128    println!("uniform_degree={:?}", uniform.degree());
129    println!("cbd_degree={:?}", cbd.degree());
130    println!("gaussian_degree={:?}", gaussian.degree());
131    println!("sample_below_1000={below}");
132}