Skip to main content

plat_core/
params.rs

1//! Parameter sets for lattice-based FHE.
2//!
3//! We use NTT-friendly primes: q ≡ 1 (mod 2N) so that primitive 2N-th roots
4//! of unity exist in Z_q.
5
6use crate::modular::mod_pow;
7
8/// FHE parameter set.
9#[derive(Debug, Clone, Copy)]
10pub struct Params {
11    /// Ring dimension (must be a power of 2).
12    pub n: usize,
13    /// Ciphertext modulus (must be prime, q ≡ 1 mod 2N).
14    pub q: u64,
15    /// Gaussian error standard deviation (as fixed-point × 1000).
16    pub sigma_x1000: u64,
17    /// Plaintext modulus for encoding.
18    pub t: u64,
19}
20
21impl Params {
22    /// Research-grade parameters: N=2048, ~100-bit security.
23    /// q is an NTT-friendly prime: q ≡ 1 (mod 2*2048 = 4096).
24    pub fn research_2048() -> Self {
25        // q = 2^40 - 2^14 + 1 = 1099511611393 — but let's use a known good one.
26        // q = 132120577 (prime, ≡ 1 mod 4096, fits in u64 comfortably for research)
27        // Actually for research quality let's use a larger prime for noise headroom.
28        // q = 1152921504606830593 (2^60 - 2^14 + 1) — too large for comfortable NTT.
29        //
30        // Practical choice: q = 0xFFFFFFFF00000001 = 2^64 - 2^32 + 1 (Goldilocks prime)
31        // This is NTT-friendly with N up to 2^31.
32        // But it's close to u64::MAX, risking overflow in u128 multiplications — fine.
33        //
34        // Simpler: q = 132120577 = 2^27 - 2^13 + 1, prime, ≡ 1 mod 4096.
35        // This gives ~27 bits of modulus, enough for depth-1 circuits with research params.
36        Self {
37            n: 2048,
38            q: 132120577,
39            sigma_x1000: 3200, // σ = 3.2
40            t: 65537,          // plaintext modulus, prime
41        }
42    }
43
44    /// Small parameters for fast testing: N=256.
45    /// q/t ratio ≈ 48, giving about 5 bits of noise budget per coefficient.
46    pub fn test_small() -> Self {
47        // q = 12289 is a classic lattice prime, 12289 ≡ 1 mod 512.
48        // t = 256: q/t ≈ 48, enough for small noise and a few additions.
49        // σ = 1.0 to keep noise small for testing.
50        Self {
51            n: 256,
52            q: 12289,
53            sigma_x1000: 1000, // σ = 1.0
54            t: 17,             // small plaintext modulus for adequate q/t ratio (~723)
55        }
56    }
57
58    /// Tiny parameters for unit tests only: N=64.
59    pub fn test_tiny() -> Self {
60        // q = 769, t = 5, q/t ≈ 153. σ = 1.0.
61        // 769 - 1 = 768 = 2^8 * 3. 2N = 128. 768 / 128 = 6 ✓
62        Self {
63            n: 64,
64            q: 769,
65            sigma_x1000: 1000, // σ = 1.0
66            t: 5,
67        }
68    }
69
70    /// Find a primitive 2N-th root of unity modulo q.
71    pub fn ntt_root(&self) -> u64 {
72        let two_n = (2 * self.n) as u64;
73        // q - 1 must be divisible by 2N
74        assert!(
75            (self.q - 1) % two_n == 0,
76            "q - 1 = {} is not divisible by 2N = {two_n}",
77            self.q - 1
78        );
79        // Find generator: try small values
80        let exp = (self.q - 1) / two_n;
81        for g in 2..self.q {
82            let w = mod_pow(g, exp, self.q);
83            if w != 1 && mod_pow(w, self.n as u64, self.q) != 1 {
84                // w is a primitive 2N-th root if w^N ≡ -1 mod q
85                let wn = mod_pow(w, self.n as u64, self.q);
86                if wn == self.q - 1 {
87                    return w;
88                }
89            }
90        }
91        panic!("no primitive 2N-th root of unity found");
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_tiny_root() {
101        let p = Params::test_tiny();
102        let w = p.ntt_root();
103        let wn = mod_pow(w, p.n as u64, p.q);
104        assert_eq!(wn, p.q - 1, "w^N should equal -1 mod q");
105        let w2n = mod_pow(w, (2 * p.n) as u64, p.q);
106        assert_eq!(w2n, 1, "w^(2N) should equal 1 mod q");
107    }
108
109    #[test]
110    fn test_small_root() {
111        let p = Params::test_small();
112        let w = p.ntt_root();
113        let wn = mod_pow(w, p.n as u64, p.q);
114        assert_eq!(wn, p.q - 1);
115    }
116}