1use crate::modular::center;
13use crate::ntt::NttTables;
14use crate::params::Params;
15use crate::poly::Poly;
16use crate::sampling::{sample_gaussian, sample_ternary, sample_uniform};
17use rand::Rng;
18
19#[derive(Clone, Debug)]
21pub struct SecretKey {
22 pub poly: Poly,
23}
24
25#[derive(Clone, Debug)]
27pub struct PublicKey {
28 pub a: Poly,
29 pub b: Poly,
30}
31
32#[derive(Clone, Debug)]
34pub struct Ciphertext {
35 pub c0: Poly,
36 pub c1: Poly,
37}
38
39pub fn keygen<R: Rng>(params: &Params, ntt: &NttTables, rng: &mut R) -> (SecretKey, PublicKey) {
41 let s = sample_ternary(params, rng);
42 let a = sample_uniform(params, rng);
43 let e = sample_gaussian(params, rng);
44
45 let as_prod = ntt.mul(&a, &s);
47 let b = e.sub(&as_prod, params);
48
49 (SecretKey { poly: s }, PublicKey { a, b })
50}
51
52pub fn encrypt<R: Rng>(
54 params: &Params,
55 ntt: &NttTables,
56 pk: &PublicKey,
57 plaintext: &Poly,
58 rng: &mut R,
59) -> Ciphertext {
60 let u = sample_ternary(params, rng);
61 let e1 = sample_gaussian(params, rng);
62 let e2 = sample_gaussian(params, rng);
63
64 let delta = params.q / params.t;
66
67 let m_scaled = plaintext.scalar_mul(delta, params);
69
70 let bu = ntt.mul(&pk.b, &u);
72 let c0 = bu.add(&e1, params).add(&m_scaled, params);
73
74 let au = ntt.mul(&pk.a, &u);
76 let c1 = au.add(&e2, params);
77
78 Ciphertext { c0, c1 }
79}
80
81pub fn decrypt(params: &Params, ntt: &NttTables, sk: &SecretKey, ct: &Ciphertext) -> Poly {
83 let c1s = ntt.mul(&ct.c1, &sk.poly);
85 let phase = ct.c0.add(&c1s, params);
86
87 let coeffs = phase
89 .coeffs
90 .iter()
91 .map(|&c| {
92 let centered = center(c, params.q);
94 let scaled = (centered as f64 * params.t as f64) / params.q as f64;
96 let rounded = scaled.round() as i64;
97 let result = rounded.rem_euclid(params.t as i64) as u64;
98 result
99 })
100 .collect();
101
102 Poly { coeffs }
103}
104
105pub fn add(params: &Params, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
107 Ciphertext {
108 c0: ct1.c0.add(&ct2.c0, params),
109 c1: ct1.c1.add(&ct2.c1, params),
110 }
111}
112
113pub fn sub(params: &Params, ct1: &Ciphertext, ct2: &Ciphertext) -> Ciphertext {
115 Ciphertext {
116 c0: ct1.c0.sub(&ct2.c0, params),
117 c1: ct1.c1.sub(&ct2.c1, params),
118 }
119}
120
121pub fn scalar_mul(params: &Params, ct: &Ciphertext, scalar: u64) -> Ciphertext {
123 Ciphertext {
124 c0: ct.c0.scalar_mul(scalar, params),
125 c1: ct.c1.scalar_mul(scalar, params),
126 }
127}
128
129pub fn encrypt_u64<R: Rng>(
131 params: &Params,
132 ntt: &NttTables,
133 pk: &PublicKey,
134 value: u64,
135 rng: &mut R,
136) -> Ciphertext {
137 let m = Poly::constant(value % params.t, params.n);
138 encrypt(params, ntt, pk, &m, rng)
139}
140
141pub fn decrypt_u64(params: &Params, ntt: &NttTables, sk: &SecretKey, ct: &Ciphertext) -> u64 {
143 let m = decrypt(params, ntt, sk, ct);
144 m.coeffs[0]
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use rand::SeedableRng;
151 use rand::rngs::StdRng;
152
153 fn setup() -> (Params, NttTables, StdRng) {
154 let p = Params::test_small();
157 let ntt = NttTables::new(&p);
158 let rng = StdRng::seed_from_u64(42);
159 (p, ntt, rng)
160 }
161
162 #[test]
163 fn test_encrypt_decrypt_roundtrip() {
164 let (p, ntt, mut rng) = setup();
165 let (sk, pk) = keygen(&p, &ntt, &mut rng);
166
167 let m = Poly::from_coeffs(&[5 % p.t, 3 % p.t, 7 % p.t, 1], p.n);
169 let ct = encrypt(&p, &ntt, &pk, &m, &mut rng);
170 let m_dec = decrypt(&p, &ntt, &sk, &ct);
171
172 for i in 0..4 {
173 assert_eq!(m.coeffs[i], m_dec.coeffs[i], "mismatch at {i}");
174 }
175 }
176
177 #[test]
178 fn test_encrypt_decrypt_u64() {
179 let (p, ntt, mut rng) = setup();
180 let (sk, pk) = keygen(&p, &ntt, &mut rng);
181
182 for val in 0..p.t {
183 let ct = encrypt_u64(&p, &ntt, &pk, val, &mut rng);
184 let dec = decrypt_u64(&p, &ntt, &sk, &ct);
185 assert_eq!(val, dec, "roundtrip failed for {val}");
186 }
187 }
188
189 #[test]
190 fn test_homomorphic_add() {
191 let (p, ntt, mut rng) = setup();
192 let (sk, pk) = keygen(&p, &ntt, &mut rng);
193
194 let a = 3u64;
195 let b = 5u64;
196 let ct_a = encrypt_u64(&p, &ntt, &pk, a, &mut rng);
197 let ct_b = encrypt_u64(&p, &ntt, &pk, b, &mut rng);
198 let ct_sum = add(&p, &ct_a, &ct_b);
199 let dec = decrypt_u64(&p, &ntt, &sk, &ct_sum);
200 assert_eq!((a + b) % p.t, dec);
201 }
202
203 #[test]
204 fn test_scalar_mul_ciphertext() {
205 let (p, ntt, mut rng) = setup();
206 let (sk, pk) = keygen(&p, &ntt, &mut rng);
207
208 let val = 2u64;
209 let scalar = 3u64;
210 let ct = encrypt_u64(&p, &ntt, &pk, val, &mut rng);
211 let ct_scaled = scalar_mul(&p, &ct, scalar);
212 let dec = decrypt_u64(&p, &ntt, &sk, &ct_scaled);
213 assert_eq!((val * scalar) % p.t, dec);
214 }
215
216 #[test]
217 fn test_homomorphic_weighted_sum() {
218 let (p, ntt, mut rng) = setup();
219 let (sk, pk) = keygen(&p, &ntt, &mut rng);
220
221 let a_val = 2u64;
222 let b_val = 3u64;
223 let c_val = 1u64;
224
225 let ct_a = encrypt_u64(&p, &ntt, &pk, a_val, &mut rng);
226 let ct_b = encrypt_u64(&p, &ntt, &pk, b_val, &mut rng);
227 let ct_c = encrypt_u64(&p, &ntt, &pk, c_val, &mut rng);
228
229 let s1 = scalar_mul(&p, &ct_a, 3);
231 let s2 = scalar_mul(&p, &ct_b, 2);
232 let s3 = scalar_mul(&p, &ct_c, 1);
233
234 let sum = add(&p, &add(&p, &s1, &s2), &s3);
235 let dec = decrypt_u64(&p, &ntt, &sk, &sum);
236 let expected = (3 * a_val + 2 * b_val + 1 * c_val) % p.t;
237 assert_eq!(expected, dec);
238 }
239
240 #[test]
241 fn test_multiple_seeds() {
242 let (p, ntt, _) = setup();
243
244 for seed in 0..20u64 {
245 let mut rng = StdRng::seed_from_u64(seed);
246 let (sk, pk) = keygen(&p, &ntt, &mut rng);
247 let val = seed % p.t;
248 let ct = encrypt_u64(&p, &ntt, &pk, val, &mut rng);
249 let dec = decrypt_u64(&p, &ntt, &sk, &ct);
250 assert_eq!(val, dec, "failed for seed {seed}");
251 }
252 }
253}