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}