fhe-study/tfhe/src/tglwe.rs
tfhe/src/tglwe.rsRaw
1use anyhow::Result;
2use rand::Rng;
3use std::iter::Sum;
4use std::ops::{Add, AddAssign, Mul, Sub};
5
6use arith::{Ring, RingParam, Rq, Tn, T64, TR};
7use gfhe::{glwe, glwe::Param, GLWE};
8
9use crate::{tlwe, tlwe::TLWE};
10
11#[derive(Clone, Debug)]
12pub struct SecretKey(pub glwe::SecretKey<Tn>);
13
14impl SecretKey {
15 pub fn to_tlwe(&self, param: &Param) -> tlwe::SecretKey {
16 let s: TR<Tn> = self.0 .0.clone();
17 debug_assert_eq!(s.r.len(), param.k); // sanity check
18
19 let kn = param.k * param.ring.n;
20 let r: Vec<Vec<T64>> = s.r.iter().map(|s_i| s_i.coeffs()).collect();
21 let r: Vec<T64> = r.into_iter().flatten().collect();
22 debug_assert_eq!(r.len(), kn); // sanity check
23 tlwe::SecretKey(glwe::SecretKey::<T64>(TR::<T64>::new(kn, r)))
24 }
25}
26
27pub type PublicKey = glwe::PublicKey<Tn>;
28
29#[derive(Clone, Debug)]
30pub struct TGLWE(pub GLWE<Tn>);
31
32impl TGLWE {
33 pub fn zero(k: usize, param: &RingParam) -> Self {
34 Self(GLWE::<Tn>::zero(k, param))
35 }
36 pub fn from_plaintext(k: usize, param: &RingParam, p: Tn) -> Self {
37 Self(GLWE::<Tn>::from_plaintext(k, param, p))
38 }
39
40 pub fn new_key(mut rng: impl Rng, param: &Param) -> Result<(SecretKey, PublicKey)> {
41 let (sk_tlwe, _) = TLWE::new_key(&mut rng, ¶m.lwe())?; //param.lwe() so that it uses K*N
42 debug_assert_eq!(sk_tlwe.0 .0.r.len(), param.lwe().k); // =KN (sanity check)
43
44 let sk = sk_tlwe.to_tglwe(param);
45 let pk: PublicKey = GLWE::pk_from_sk(rng, param, sk.0.clone())?;
46 Ok((sk, pk))
47 }
48
49 pub fn encode(param: &Param, m: &Rq) -> Tn {
50 debug_assert_eq!(param.t, m.param.q); // plaintext modulus
51 let p = param.t;
52 let delta = u64::MAX / p; // floored
53 let coeffs = m.coeffs();
54 Tn {
55 param: param.ring,
56 coeffs: coeffs.iter().map(|c_i| T64(c_i.v * delta)).collect(),
57 }
58 }
59 pub fn decode(param: &Param, pt: &Tn) -> Rq {
60 let p = param.t;
61 let pt = pt.mul_div_round(p, u64::MAX);
62 Rq::from_vec_u64(¶m.pt(), pt.coeffs().iter().map(|c| c.0).collect())
63 }
64 /// encodes the given message as a TGLWE constant/public value, for using it
65 /// in ct-pt-multiplication.
66 pub fn new_const(param: &Param, m: &Rq) -> Tn {
67 debug_assert_eq!(param.t, m.param.q);
68 // don't scale up m, set the Tn element directly from m's coefficients
69 Tn {
70 param: param.ring,
71 coeffs: m.coeffs().iter().map(|c_i| T64(c_i.v)).collect(),
72 }
73 }
74
75 /// encrypts with the given SecretKey (instead of PublicKey)
76 pub fn encrypt_s(rng: impl Rng, param: &Param, sk: &SecretKey, p: &Tn) -> Result<Self> {
77 let glwe = GLWE::encrypt_s(rng, param, &sk.0, p)?;
78 Ok(Self(glwe))
79 }
80 pub fn encrypt(rng: impl Rng, param: &Param, pk: &PublicKey, p: &Tn) -> Result<Self> {
81 let glwe = GLWE::encrypt(rng, param, &pk, p)?;
82 Ok(Self(glwe))
83 }
84 pub fn decrypt(&self, sk: &SecretKey) -> Tn {
85 self.0.decrypt(&sk.0)
86 }
87
88 /// Sample extraction / Coefficient extraction
89 pub fn sample_extraction(&self, param: &Param, h: usize) -> TLWE {
90 let n = param.ring.n;
91 assert!(h < n);
92
93 let a: TR<Tn> = self.0 .0.clone();
94 // set a_{n*i+j} = a_{i, h-j} if j \in {0, h}
95 // -a_{i, n+h-j} if j \in {h+1, n-1}
96 let new_a: Vec<T64> = a
97 .iter()
98 .flat_map(|a_i| {
99 let a_i = a_i.coeffs();
100 (0..n)
101 .map(|j| if j <= h { a_i[h - j] } else { -a_i[n + h - j] })
102 .collect::<Vec<T64>>()
103 })
104 .collect::<Vec<T64>>();
105 debug_assert_eq!(new_a.len(), param.k * param.ring.n); // sanity check
106
107 TLWE(GLWE(
108 TR {
109 // TODO use constructor `new`, which will check len with k
110 k: param.k * param.ring.n,
111 r: new_a,
112 },
113 self.0 .1.coeffs()[h],
114 ))
115 }
116 pub fn left_rotate(&self, h: usize) -> Self {
117 let (a, b): (TR<Tn>, Tn) = (self.0 .0.clone(), self.0 .1.clone());
118 Self(GLWE(a.left_rotate(h), b.left_rotate(h)))
119 }
120}
121
122impl Add<TGLWE> for TGLWE {
123 type Output = Self;
124 fn add(self, other: Self) -> Self {
125 debug_assert_eq!(self.0 .0.k, other.0 .0.k);
126 debug_assert_eq!(self.0 .1.param(), other.0 .1.param());
127
128 Self(self.0 + other.0)
129 }
130}
131impl AddAssign for TGLWE {
132 fn add_assign(&mut self, other: Self) {
133 debug_assert_eq!(self.0 .0.k, other.0 .0.k);
134 debug_assert_eq!(self.0 .1.param(), other.0 .1.param());
135
136 self.0 += other.0
137 }
138}
139impl Sum<TGLWE> for TGLWE {
140 fn sum<I>(mut iter: I) -> Self
141 where
142 I: Iterator<Item = Self>,
143 {
144 let first = iter.next().unwrap();
145 iter.fold(first, |acc, e| acc + e)
146 }
147}
148
149impl Sub<TGLWE> for TGLWE {
150 type Output = Self;
151 fn sub(self, other: Self) -> Self {
152 debug_assert_eq!(self.0 .0.k, other.0 .0.k);
153 debug_assert_eq!(self.0 .1.param(), other.0 .1.param());
154
155 Self(self.0 - other.0)
156 }
157}
158
159// plaintext addition
160impl Add<Tn> for TGLWE {
161 type Output = Self;
162 fn add(self, plaintext: Tn) -> Self {
163 debug_assert_eq!(self.0 .1.param(), plaintext.param());
164
165 let a: TR<Tn> = self.0 .0;
166 let b: Tn = self.0 .1 + plaintext;
167 Self(GLWE(a, b))
168 }
169}
170// plaintext substraction
171impl Sub<Tn> for TGLWE {
172 type Output = Self;
173 fn sub(self, plaintext: Tn) -> Self {
174 debug_assert_eq!(self.0 .1.param(), plaintext.param());
175
176 let a: TR<Tn> = self.0 .0;
177 let b: Tn = self.0 .1 - plaintext;
178 Self(GLWE(a, b))
179 }
180}
181// plaintext multiplication
182impl Mul<Tn> for TGLWE {
183 type Output = Self;
184 fn mul(self, plaintext: Tn) -> Self {
185 debug_assert_eq!(self.0 .1.param(), plaintext.param());
186
187 let a: TR<Tn> = TR {
188 k: self.0 .0.k,
189 r: self.0 .0.r.iter().map(|r_i| r_i * &plaintext).collect(),
190 };
191 let b: Tn = self.0 .1 * plaintext;
192 Self(GLWE(a, b))
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use anyhow::Result;
199 use rand::distr::Uniform;
200
201 use super::*;
202
203 #[test]
204 fn test_encrypt_decrypt() -> Result<()> {
205 let param = Param {
206 err_sigma: crate::ERR_SIGMA,
207 ring: RingParam { q: u64::MAX, n: 64 },
208 k: 16,
209 t: 128, // plaintext modulus
210 };
211
212 let mut rng = rand::rng();
213 let msg_dist = Uniform::new(0_u64, param.t)?;
214
215 for _ in 0..200 {
216 let (sk, pk) = TGLWE::new_key(&mut rng, ¶m)?;
217
218 let m = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
219 let p: Tn = TGLWE::encode(¶m, &m);
220
221 let c = TGLWE::encrypt(&mut rng, ¶m, &pk, &p)?;
222 let p_recovered = c.decrypt(&sk);
223 let m_recovered = TGLWE::decode(¶m, &p_recovered);
224
225 assert_eq!(m, m_recovered);
226
227 // same but using encrypt_s (with sk instead of pk))
228 let c = TGLWE::encrypt_s(&mut rng, ¶m, &sk, &p)?;
229 let p_recovered = c.decrypt(&sk);
230 let m_recovered = TGLWE::decode(¶m, &p_recovered);
231
232 assert_eq!(m, m_recovered);
233 }
234
235 Ok(())
236 }
237
238 #[test]
239 fn test_addition() -> Result<()> {
240 let param = Param {
241 err_sigma: crate::ERR_SIGMA,
242 ring: RingParam { q: u64::MAX, n: 64 },
243 k: 16,
244 t: 128, // plaintext modulus
245 };
246
247 let mut rng = rand::rng();
248 let msg_dist = Uniform::new(0_u64, param.t)?;
249
250 for _ in 0..200 {
251 let (sk, pk) = TGLWE::new_key(&mut rng, ¶m)?;
252
253 let m1 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
254 let m2 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
255 let p1: Tn = TGLWE::encode(¶m, &m1); // plaintext
256 let p2: Tn = TGLWE::encode(¶m, &m2); // plaintext
257
258 let c1 = TGLWE::encrypt(&mut rng, ¶m, &pk, &p1)?;
259 let c2 = TGLWE::encrypt(&mut rng, ¶m, &pk, &p2)?;
260
261 let c3 = c1 + c2;
262
263 let p3_recovered = c3.decrypt(&sk);
264 let m3_recovered = TGLWE::decode(¶m, &p3_recovered);
265
266 assert_eq!((m1 + m2).remodule(param.t), m3_recovered.remodule(param.t));
267 }
268
269 Ok(())
270 }
271
272 #[test]
273 fn test_add_plaintext() -> Result<()> {
274 let param = Param {
275 err_sigma: crate::ERR_SIGMA,
276 ring: RingParam { q: u64::MAX, n: 64 },
277 k: 16,
278 t: 128, // plaintext modulus
279 };
280
281 let mut rng = rand::rng();
282 let msg_dist = Uniform::new(0_u64, param.t)?;
283
284 for _ in 0..200 {
285 let (sk, pk) = TGLWE::new_key(&mut rng, ¶m)?;
286
287 let m1 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
288 let m2 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
289 let p1: Tn = TGLWE::encode(¶m, &m1); // plaintext
290 let p2: Tn = TGLWE::encode(¶m, &m2); // plaintext
291
292 let c1 = TGLWE::encrypt(&mut rng, ¶m, &pk, &p1)?;
293
294 let c3 = c1 + p2;
295
296 let p3_recovered = c3.decrypt(&sk);
297 let m3_recovered = TGLWE::decode(¶m, &p3_recovered);
298
299 assert_eq!(m1 + m2, m3_recovered);
300 }
301
302 Ok(())
303 }
304
305 #[test]
306 fn test_mul_plaintext() -> Result<()> {
307 let param = Param {
308 err_sigma: crate::ERR_SIGMA,
309 ring: RingParam { q: u64::MAX, n: 64 },
310 k: 16,
311 t: 128, // plaintext modulus
312 };
313
314 let mut rng = rand::rng();
315 let msg_dist = Uniform::new(0_u64, param.t)?;
316
317 for _ in 0..200 {
318 let (sk, pk) = TGLWE::new_key(&mut rng, ¶m)?;
319
320 let m1 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
321 let m2 = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
322 let p1: Tn = TGLWE::encode(¶m, &m1);
323 let p2: Tn = TGLWE::new_const(¶m, &m2); // as constant/public value
324
325 let c1 = TGLWE::encrypt(&mut rng, ¶m, &pk, &p1)?;
326
327 let c3 = c1 * p2;
328
329 let p3_recovered: Tn = c3.decrypt(&sk);
330 let m3_recovered = TGLWE::decode(¶m, &p3_recovered);
331 assert_eq!((m1.to_r() * m2.to_r()).to_rq(param.t), m3_recovered);
332 }
333
334 Ok(())
335 }
336
337 #[test]
338 fn test_sample_extraction() -> Result<()> {
339 let param = Param {
340 err_sigma: crate::ERR_SIGMA,
341 ring: RingParam { q: u64::MAX, n: 64 },
342 k: 16,
343 t: 128, // plaintext modulus
344 };
345
346 let mut rng = rand::rng();
347 let msg_dist = Uniform::new(0_u64, param.t)?;
348
349 for _ in 0..20 {
350 let (sk, pk) = TGLWE::new_key(&mut rng, ¶m)?;
351 let sk_tlwe = sk.to_tlwe(¶m);
352
353 let m = Rq::rand_u64(&mut rng, msg_dist, ¶m.pt())?;
354 let p: Tn = TGLWE::encode(¶m, &m);
355
356 let c = TGLWE::encrypt(&mut rng, ¶m, &pk, &p)?;
357
358 for h in 0..param.ring.n {
359 let c_h: TLWE = c.sample_extraction(¶m, h);
360
361 let p_recovered = c_h.decrypt(&sk_tlwe);
362 let m_recovered = TLWE::decode(¶m, &p_recovered);
363 assert_eq!(m.coeffs()[h], m_recovered.coeffs()[0]);
364 }
365 }
366
367 Ok(())
368 }
369}