phantom-zone/src/rgsw.rs
src/rgsw.rsRaw
1use std::{
2 clone,
3 fmt::Debug,
4 marker::PhantomData,
5 ops::{Div, Neg, Sub},
6};
7
8use itertools::{izip, Itertools};
9use num_traits::{PrimInt, ToPrimitive, Zero};
10
11use crate::{
12 backend::{ArithmeticOps, VectorOps},
13 decomposer::{self, Decomposer},
14 ntt::{self, Ntt, NttInit},
15 random::{DefaultSecureRng, NewWithSeed, RandomGaussianDist, RandomUniformDist},
16 utils::{fill_random_ternary_secret_with_hamming_weight, TryConvertFrom, WithLocal},
17 Matrix, MatrixEntity, MatrixMut, Row, RowEntity, RowMut, Secret,
18};
19
20pub struct SeededAutoKey<M, S>
21where
22 M: Matrix,
23{
24 data: M,
25 seed: S,
26 modulus: M::MatElement,
27}
28
29impl<M: Matrix + MatrixEntity, S> SeededAutoKey<M, S> {
30 fn from_raw(data: M, seed: S, modulus: M::MatElement) -> Self {
31 assert!(data.dimension().0 % 3 == 0);
32
33 SeededAutoKey {
34 data,
35 seed,
36 modulus,
37 }
38 }
39
40 fn empty(ring_size: usize, d_rgsw: usize, seed: S, modulus: M::MatElement) -> Self {
41 SeededAutoKey {
42 data: M::zeros(d_rgsw, ring_size),
43 seed,
44 modulus: modulus,
45 }
46 }
47}
48
49pub struct AutoKeyEvaluationDomain<M, R, N> {
50 data: M,
51 _phantom: PhantomData<(R, N)>,
52}
53
54impl<
55 M: MatrixMut + MatrixEntity,
56 R: RandomUniformDist<[M::MatElement], Parameters = M::MatElement> + NewWithSeed,
57 N: NttInit<Element = M::MatElement> + Ntt<Element = M::MatElement>,
58 > From<&SeededAutoKey<M, R::Seed>> for AutoKeyEvaluationDomain<M, R, N>
59where
60 <M as Matrix>::R: RowMut,
61 M::MatElement: Copy,
62 R::Seed: Clone,
63{
64 fn from(value: &SeededAutoKey<M, R::Seed>) -> Self {
65 let (d, ring_size) = value.data.dimension();
66 let mut data = M::zeros(2 * d, ring_size);
67
68 // sample RLWE'_A(-s(X^k))
69 let mut p_rng = R::new_with_seed(value.seed.clone());
70 data.iter_rows_mut().take(d).for_each(|r| {
71 RandomUniformDist::random_fill(&mut p_rng, &value.modulus, r.as_mut());
72 });
73
74 // copy over RLWE'_B(-s(X^k))
75 izip!(data.iter_rows_mut().skip(d), value.data.iter_rows()).for_each(|(to_r, from_r)| {
76 to_r.as_mut().copy_from_slice(from_r.as_ref());
77 });
78
79 // send RLWE'(-s(X^k)) polynomials to evaluation domain
80 let ntt_op = N::new(value.modulus, ring_size);
81 data.iter_rows_mut()
82 .for_each(|r| ntt_op.forward(r.as_mut()));
83
84 AutoKeyEvaluationDomain {
85 data,
86 _phantom: PhantomData,
87 }
88 }
89}
90
91pub struct RgswCiphertext<M: Matrix> {
92 data: M,
93 modulus: M::MatElement,
94}
95
96pub struct SeededRgswCiphertext<M, S>
97where
98 M: Matrix,
99{
100 pub(crate) data: M,
101 seed: S,
102 modulus: M::MatElement,
103}
104
105impl<M: Matrix + MatrixEntity, S> SeededRgswCiphertext<M, S> {
106 fn from_raw(data: M, seed: S, modulus: M::MatElement) -> SeededRgswCiphertext<M, S> {
107 assert!(data.dimension().0 % 3 == 0);
108
109 SeededRgswCiphertext {
110 data,
111 seed,
112 modulus,
113 }
114 }
115
116 pub(crate) fn empty(
117 ring_size: usize,
118 d_rgsw: usize,
119 seed: S,
120 modulus: M::MatElement,
121 ) -> SeededRgswCiphertext<M, S> {
122 SeededRgswCiphertext {
123 data: M::zeros(d_rgsw * 3, ring_size),
124 seed,
125 modulus: modulus,
126 }
127 }
128}
129
130impl<M: Debug + Matrix, S: Debug> Debug for SeededRgswCiphertext<M, S>
131where
132 M::MatElement: Debug,
133{
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 f.debug_struct("SeededRgswCiphertext")
136 .field("data", &self.data)
137 .field("seed", &self.seed)
138 .field("modulus", &self.modulus)
139 .finish()
140 }
141}
142
143pub struct RgswCiphertextEvaluationDomain<M, R, N> {
144 pub(crate) data: M,
145 _phantom: PhantomData<(R, N)>,
146}
147
148impl<
149 M: MatrixMut + MatrixEntity,
150 R: NewWithSeed + RandomUniformDist<[M::MatElement], Parameters = M::MatElement>,
151 N: NttInit<Element = M::MatElement> + Ntt<Element = M::MatElement> + Debug,
152 > From<&SeededRgswCiphertext<M, R::Seed>> for RgswCiphertextEvaluationDomain<M, R, N>
153where
154 <M as Matrix>::R: RowMut,
155 M::MatElement: Copy,
156 R::Seed: Clone,
157 M: Debug,
158{
159 fn from(value: &SeededRgswCiphertext<M, R::Seed>) -> Self {
160 let d = value.data.dimension().0.div(3);
161
162 let mut data = M::zeros(4 * d, value.data.dimension().1);
163
164 // copy RLWE'(-sm)
165 izip!(data.iter_rows_mut().take(2 * d), value.data.iter_rows()).for_each(
166 |(to_ri, from_ri)| {
167 to_ri.as_mut().copy_from_slice(from_ri.as_ref());
168 },
169 );
170
171 // sample A polynomials of RLWE'(m) - RLWE'A(m)
172 // TODO(Jay): Do we want to be generic over RandomGenerator used here? I think
173 // not.
174 let mut p_rng = R::new_with_seed(value.seed.clone());
175 izip!(data.iter_rows_mut().skip(2 * d).take(d))
176 .for_each(|ri| p_rng.random_fill(&value.modulus, ri.as_mut()));
177
178 // RLWE'_B(m)
179 izip!(
180 data.iter_rows_mut().skip(3 * d),
181 value.data.iter_rows().skip(2 * d)
182 )
183 .for_each(|(to_ri, from_ri)| {
184 to_ri.as_mut().copy_from_slice(from_ri.as_ref());
185 });
186
187 // Send polynomials to evaluation domain
188 let ring_size = data.dimension().1;
189 let nttop = N::new(value.modulus, ring_size);
190 data.iter_rows_mut()
191 .for_each(|ri| nttop.forward(ri.as_mut()));
192
193 Self {
194 data: data,
195 _phantom: PhantomData,
196 }
197 }
198}
199
200impl<
201 M: MatrixMut + MatrixEntity,
202 R,
203 N: NttInit<Element = M::MatElement> + Ntt<Element = M::MatElement>,
204 > From<&RgswCiphertext<M>> for RgswCiphertextEvaluationDomain<M, R, N>
205where
206 <M as Matrix>::R: RowMut,
207 M::MatElement: Copy,
208 M: Debug,
209{
210 fn from(value: &RgswCiphertext<M>) -> Self {
211 assert!(value.data.dimension().0 % 4 == 0);
212 let d = value.data.dimension().0.div(4);
213
214 let mut data = M::zeros(4 * d, value.data.dimension().1);
215
216 // copy RLWE'(-sm)
217 izip!(data.iter_rows_mut().take(2 * d), value.data.iter_rows()).for_each(
218 |(to_ri, from_ri)| {
219 to_ri.as_mut().copy_from_slice(from_ri.as_ref());
220 },
221 );
222
223 // copy RLWE'(m)
224 izip!(
225 data.iter_rows_mut().skip(2 * d),
226 value.data.iter_rows().skip(2 * d)
227 )
228 .for_each(|(to_ri, from_ri)| {
229 to_ri.as_mut().copy_from_slice(from_ri.as_ref());
230 });
231
232 // Send polynomials to evaluation domain
233 let ring_size = data.dimension().1;
234 let nttop = N::new(value.modulus, ring_size);
235 data.iter_rows_mut()
236 .for_each(|ri| nttop.forward(ri.as_mut()));
237
238 Self {
239 data: data,
240 _phantom: PhantomData,
241 }
242 }
243}
244
245impl<M: Debug, R, N> Debug for RgswCiphertextEvaluationDomain<M, R, N> {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 f.debug_struct("RgswCiphertextEvaluationDomain")
248 .field("data", &self.data)
249 .field("_phantom", &self._phantom)
250 .finish()
251 }
252}
253
254impl<M: Matrix, R, N> Matrix for RgswCiphertextEvaluationDomain<M, R, N> {
255 type MatElement = M::MatElement;
256 type R = M::R;
257
258 fn dimension(&self) -> (usize, usize) {
259 self.data.dimension()
260 }
261}
262
263impl<M: Matrix, R, N> AsRef<[M::R]> for RgswCiphertextEvaluationDomain<M, R, N> {
264 fn as_ref(&self) -> &[M::R] {
265 self.data.as_ref()
266 }
267}
268
269pub struct SeededRlweCiphertext<R, S>
270where
271 R: Row,
272{
273 pub(crate) data: R,
274 pub(crate) seed: S,
275 pub(crate) modulus: R::Element,
276}
277
278impl<R: RowEntity, S> SeededRlweCiphertext<R, S> {
279 pub(crate) fn empty(ring_size: usize, seed: S, modulus: R::Element) -> Self {
280 SeededRlweCiphertext {
281 data: R::zeros(ring_size),
282 seed,
283 modulus,
284 }
285 }
286}
287
288pub struct RlweCiphertext<M, Rng> {
289 pub(crate) data: M,
290 pub(crate) is_trivial: bool,
291 _phatom: PhantomData<Rng>,
292}
293
294impl<M, Rng> RlweCiphertext<M, Rng> {
295 pub(crate) fn from_raw(data: M, is_trivial: bool) -> Self {
296 RlweCiphertext {
297 data,
298 is_trivial,
299 _phatom: PhantomData,
300 }
301 }
302}
303
304impl<M: Matrix, Rng> Matrix for RlweCiphertext<M, Rng> {
305 type MatElement = M::MatElement;
306 type R = M::R;
307
308 fn dimension(&self) -> (usize, usize) {
309 self.data.dimension()
310 }
311}
312
313impl<M: MatrixMut, Rng> MatrixMut for RlweCiphertext<M, Rng> where <M as Matrix>::R: RowMut {}
314
315impl<M: Matrix, Rng> AsRef<[<M as Matrix>::R]> for RlweCiphertext<M, Rng> {
316 fn as_ref(&self) -> &[<M as Matrix>::R] {
317 self.data.as_ref()
318 }
319}
320
321impl<M: MatrixMut, Rng> AsMut<[<M as Matrix>::R]> for RlweCiphertext<M, Rng>
322where
323 <M as Matrix>::R: RowMut,
324{
325 fn as_mut(&mut self) -> &mut [<M as Matrix>::R] {
326 self.data.as_mut()
327 }
328}
329
330impl<M, Rng> IsTrivial for RlweCiphertext<M, Rng> {
331 fn is_trivial(&self) -> bool {
332 self.is_trivial
333 }
334 fn set_not_trivial(&mut self) {
335 self.is_trivial = false;
336 }
337}
338
339impl<R: Row, M: MatrixEntity<R = R, MatElement = R::Element> + MatrixMut, Rng: NewWithSeed>
340 From<&SeededRlweCiphertext<R, Rng::Seed>> for RlweCiphertext<M, Rng>
341where
342 Rng::Seed: Clone,
343 Rng: RandomUniformDist<[M::MatElement], Parameters = M::MatElement>,
344 <M as Matrix>::R: RowMut,
345 R::Element: Copy,
346{
347 fn from(value: &SeededRlweCiphertext<R, Rng::Seed>) -> Self {
348 let mut data = M::zeros(2, value.data.as_ref().len());
349
350 // sample a
351 let mut p_rng = Rng::new_with_seed(value.seed.clone());
352 RandomUniformDist::random_fill(&mut p_rng, &value.modulus, data.get_row_mut(0));
353
354 data.get_row_mut(1).copy_from_slice(value.data.as_ref());
355
356 RlweCiphertext {
357 data,
358 is_trivial: false,
359 _phatom: PhantomData,
360 }
361 }
362}
363
364pub trait IsTrivial {
365 fn is_trivial(&self) -> bool;
366 fn set_not_trivial(&mut self);
367}
368
369pub struct SeededRlwePublicKey<Ro: Row, S> {
370 data: Ro,
371 seed: S,
372 modulus: Ro::Element,
373}
374
375impl<Ro: RowEntity, S> SeededRlwePublicKey<Ro, S> {
376 pub(crate) fn empty(ring_size: usize, seed: S, modulus: Ro::Element) -> Self {
377 Self {
378 data: Ro::zeros(ring_size),
379 seed,
380 modulus,
381 }
382 }
383}
384
385pub struct RlwePublicKey<M, R> {
386 data: M,
387 _phantom: PhantomData<R>,
388}
389
390impl<
391 M: MatrixMut + MatrixEntity,
392 Rng: NewWithSeed + RandomUniformDist<[M::MatElement], Parameters = M::MatElement>,
393 > From<&SeededRlwePublicKey<M::R, Rng::Seed>> for RlwePublicKey<M, Rng>
394where
395 <M as Matrix>::R: RowMut,
396 M::MatElement: Copy,
397 Rng::Seed: Copy,
398{
399 fn from(value: &SeededRlwePublicKey<M::R, Rng::Seed>) -> Self {
400 let mut data = M::zeros(2, value.data.as_ref().len());
401
402 // sample a
403 let mut p_rng = Rng::new_with_seed(value.seed);
404 RandomUniformDist::random_fill(&mut p_rng, &value.modulus, data.get_row_mut(0));
405
406 // copy over b
407 data.get_row_mut(1).copy_from_slice(value.data.as_ref());
408
409 Self {
410 data,
411 _phantom: PhantomData,
412 }
413 }
414}
415
416#[derive(Clone)]
417pub struct RlweSecret {
418 pub(crate) values: Vec<i32>,
419}
420
421impl Secret for RlweSecret {
422 type Element = i32;
423 fn values(&self) -> &[Self::Element] {
424 &self.values
425 }
426}
427
428impl RlweSecret {
429 pub fn random(hw: usize, n: usize) -> RlweSecret {
430 DefaultSecureRng::with_local_mut(|rng| {
431 let mut out = vec![0i32; n];
432 fill_random_ternary_secret_with_hamming_weight(&mut out, hw, rng);
433
434 RlweSecret { values: out }
435 })
436 }
437}
438
439pub(crate) fn generate_auto_map(ring_size: usize, k: isize) -> (Vec<usize>, Vec<bool>) {
440 assert!(k & 1 == 1, "Auto {k} must be odd");
441
442 let k = if k < 0 {
443 // k is -ve, return k%(2*N)
444 (2 * ring_size) - (k.abs() as usize % (2 * ring_size))
445 } else {
446 k as usize
447 };
448 let (auto_map_index, auto_sign_index): (Vec<usize>, Vec<bool>) = (0..ring_size)
449 .into_iter()
450 .map(|i| {
451 let mut to_index = (i * k) % (2 * ring_size);
452 let mut sign = true;
453
454 // wrap around. false implies negative
455 if to_index >= ring_size {
456 to_index = to_index - ring_size;
457 sign = false;
458 }
459
460 (to_index, sign)
461 })
462 .unzip();
463 (auto_map_index, auto_sign_index)
464}
465
466/// Generates RLWE Key switching key to key switch ciphertext RLWE_{from_s}(m)
467/// to RLWE_{to_s}(m).
468///
469/// Key switching equals
470/// \sum decompose(c_1)_i * RLWE_{to_s}(\beta^i -from_s)
471/// Hence, key switchin key equals RLWE'(-from_s) = RLWE(-from_s), RLWE(beta^1
472/// -from_s), ..., RLWE(beta^{d-1} -from_s).
473///
474/// - ksk_out: Output Key switching key. Key switching key stores only part B
475/// polynomials of ksk RLWE ciphertexts (i.e. RLWE'_B(-from_s)) in coefficient
476/// domain
477/// - neg_from_s: Negative of secret polynomial to key switch from
478/// - to_s: secret polynomial to key switch to.
479pub(crate) fn rlwe_ksk_gen<
480 Mmut: MatrixMut + MatrixEntity,
481 ModOp: ArithmeticOps<Element = Mmut::MatElement> + VectorOps<Element = Mmut::MatElement>,
482 NttOp: Ntt<Element = Mmut::MatElement>,
483 R: RandomGaussianDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
484 PR: RandomUniformDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
485>(
486 ksk_out: &mut Mmut,
487 neg_from_s: Mmut::R,
488 mut to_s: Mmut::R,
489 gadget_vector: &[Mmut::MatElement],
490 mod_op: &ModOp,
491 ntt_op: &NttOp,
492 p_rng: &mut PR,
493 rng: &mut R,
494) where
495 <Mmut as Matrix>::R: RowMut,
496{
497 let ring_size = neg_from_s.as_ref().len();
498 let d = gadget_vector.len();
499 assert!(ksk_out.dimension() == (d, ring_size));
500
501 let q = ArithmeticOps::modulus(mod_op);
502
503 ntt_op.forward(to_s.as_mut());
504
505 // RLWE'_{to_s}(-from_s)
506 let mut part_a = {
507 let mut a = Mmut::zeros(d, ring_size);
508 a.iter_rows_mut()
509 .for_each(|ai| RandomUniformDist::random_fill(p_rng, &q, ai.as_mut()));
510 a
511 };
512 izip!(
513 part_a.iter_rows_mut(),
514 ksk_out.iter_rows_mut(),
515 gadget_vector.iter(),
516 )
517 .for_each(|(ai, bi, beta_i)| {
518 // si * ai
519 ntt_op.forward(ai.as_mut());
520 mod_op.elwise_mul_mut(ai.as_mut(), to_s.as_ref());
521 ntt_op.backward(ai.as_mut());
522
523 // ei + to_s*ai
524 RandomGaussianDist::random_fill(rng, &q, bi.as_mut());
525 mod_op.elwise_add_mut(bi.as_mut(), ai.as_ref());
526
527 // beta_i * -from_s
528 // use ai as scratch space
529 mod_op.elwise_scalar_mul(ai.as_mut(), neg_from_s.as_ref(), beta_i);
530
531 // bi = ei + to_s*ai + beta_i*-from_s
532 mod_op.elwise_add_mut(bi.as_mut(), ai.as_ref());
533 });
534}
535
536pub(crate) fn galois_key_gen<
537 Mmut: MatrixMut + MatrixEntity,
538 ModOp: ArithmeticOps<Element = Mmut::MatElement> + VectorOps<Element = Mmut::MatElement>,
539 NttOp: Ntt<Element = Mmut::MatElement>,
540 S,
541 R: RandomGaussianDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
542 PR: RandomUniformDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
543>(
544 ksk_out: &mut Mmut,
545 s: &[S],
546 auto_k: isize,
547 gadget_vector: &[Mmut::MatElement],
548 mod_op: &ModOp,
549 ntt_op: &NttOp,
550 p_rng: &mut PR,
551 rng: &mut R,
552) where
553 <Mmut as Matrix>::R: RowMut,
554 Mmut::R: TryConvertFrom<[S], Parameters = Mmut::MatElement> + RowEntity,
555 Mmut::MatElement: Copy + Sub<Output = Mmut::MatElement>,
556{
557 let ring_size = s.len();
558 let (auto_map_index, auto_map_sign) = generate_auto_map(ring_size, auto_k);
559
560 let q = ArithmeticOps::modulus(mod_op);
561
562 // s(X) -> -s(X^k)
563 let s = Mmut::R::try_convert_from(s, &q);
564 let mut neg_s_auto = Mmut::R::zeros(s.as_ref().len());
565 izip!(s.as_ref(), auto_map_index.iter(), auto_map_sign.iter()).for_each(
566 |(el, to_index, sign)| {
567 // if sign is +ve (true), then negate because we need -s(X) (i.e. do the
568 // opposite than the usual case)
569 if *sign {
570 neg_s_auto.as_mut()[*to_index] = q - *el;
571 } else {
572 neg_s_auto.as_mut()[*to_index] = *el;
573 }
574 },
575 );
576
577 // Ksk from -s(X^k) to s(X)
578 rlwe_ksk_gen(
579 ksk_out,
580 neg_s_auto,
581 s,
582 gadget_vector,
583 mod_op,
584 ntt_op,
585 p_rng,
586 rng,
587 );
588}
589
590pub(crate) fn routine<R: RowMut, ModOp: VectorOps<Element = R::Element>>(
591 write_to_row: &mut [R::Element],
592 matrix_a: &[R],
593 matrix_b: &[R],
594 mod_op: &ModOp,
595) {
596 izip!(matrix_a.iter(), matrix_b.iter()).for_each(|(a, b)| {
597 mod_op.elwise_fma_mut(write_to_row, a.as_ref(), b.as_ref());
598 });
599}
600
601/// Decomposes ring polynomial r(X) into d polynomials using decomposer into
602/// output matrix decomp_r
603///
604/// Note that decomposition of r(X) requires decomposition of each of
605/// coefficients.
606///
607/// - decomp_r: must have dimensions d x ring_size. i^th decomposed polynomial
608/// will be stored at i^th row.
609pub(crate) fn decompose_r<R: RowMut, D: Decomposer<Element = R::Element>>(
610 r: &[R::Element],
611 decomp_r: &mut [R],
612 decomposer: &D,
613) where
614 R::Element: Copy,
615{
616 let ring_size = r.len();
617 let d = decomposer.d();
618
619 for ri in 0..ring_size {
620 let el_decomposed = decomposer.decompose(&r[ri]);
621 for j in 0..d {
622 decomp_r[j].as_mut()[ri] = el_decomposed[j];
623 }
624 }
625}
626
627/// Sends RLWE_{s}(X) -> RLWE_{s}(X^k) where k is some galois element
628pub(crate) fn galois_auto<
629 MT: Matrix + IsTrivial + MatrixMut,
630 Mmut: MatrixMut<MatElement = MT::MatElement>,
631 ModOp: ArithmeticOps<Element = MT::MatElement> + VectorOps<Element = MT::MatElement>,
632 NttOp: Ntt<Element = MT::MatElement>,
633 D: Decomposer<Element = MT::MatElement>,
634>(
635 rlwe_in: &mut MT,
636 ksk: &Mmut,
637 scratch_matrix_dplus2_ring: &mut Mmut,
638 auto_map_index: &[usize],
639 auto_map_sign: &[bool],
640 mod_op: &ModOp,
641 ntt_op: &NttOp,
642 decomposer: &D,
643) where
644 <Mmut as Matrix>::R: RowMut,
645 <MT as Matrix>::R: RowMut,
646 MT::MatElement: Copy + Zero,
647{
648 let d = decomposer.d();
649
650 let (scratch_matrix_d_ring, tmp_rlwe_out) = scratch_matrix_dplus2_ring.split_at_row_mut(d);
651
652 // send b(X) -> b(X^k)
653 izip!(
654 rlwe_in.get_row(1),
655 auto_map_index.iter(),
656 auto_map_sign.iter()
657 )
658 .for_each(|(el_in, to_index, sign)| {
659 if !*sign {
660 tmp_rlwe_out[1].as_mut()[*to_index] = mod_op.neg(el_in);
661 } else {
662 tmp_rlwe_out[1].as_mut()[*to_index] = *el_in;
663 // scratch_matrix_dplus2_ring.set(d + 1, *to_index, *el_in);
664 }
665 });
666
667 if !rlwe_in.is_trivial() {
668 // send a(X) -> a(X^k) and decompose a(X^k)
669 izip!(
670 rlwe_in.get_row(0),
671 auto_map_index.iter(),
672 auto_map_sign.iter()
673 )
674 .for_each(|(el_in, to_index, sign)| {
675 let el_out = if !*sign { mod_op.neg(el_in) } else { *el_in };
676
677 let el_out_decomposed = decomposer.decompose(&el_out);
678 for j in 0..d {
679 scratch_matrix_d_ring[j].as_mut()[*to_index] = el_out_decomposed[j];
680 }
681 });
682
683 // transform decomposed a(X^k) to evaluation domain
684 scratch_matrix_d_ring.iter_mut().for_each(|r| {
685 ntt_op.forward(r.as_mut());
686 });
687
688 // RLWE(m^k) = a', b'; RLWE(m) = a, b
689 // key switch: (a * RLWE'(s(X^k)))
690 let (ksk_a, ksk_b) = ksk.split_at_row(d);
691 tmp_rlwe_out[0].as_mut().fill(Mmut::MatElement::zero());
692 // a' = decomp<a> * RLWE'_A(s(X^k))
693 routine(
694 tmp_rlwe_out[0].as_mut(),
695 scratch_matrix_d_ring,
696 ksk_a,
697 mod_op,
698 );
699 // send b(X^k) to evaluation domain
700 ntt_op.forward(tmp_rlwe_out[1].as_mut());
701 // b' = b(X^k)
702 // b' += decomp<a(X^k)> * RLWE'_B(s(X^k))
703 routine(
704 tmp_rlwe_out[1].as_mut(),
705 scratch_matrix_d_ring,
706 ksk_b,
707 mod_op,
708 );
709
710 // transform RLWE(m^k) to coefficient domain
711 tmp_rlwe_out
712 .iter_mut()
713 .for_each(|r| ntt_op.backward(r.as_mut()));
714
715 rlwe_in
716 .get_row_mut(0)
717 .copy_from_slice(tmp_rlwe_out[0].as_ref());
718 }
719
720 rlwe_in
721 .get_row_mut(1)
722 .copy_from_slice(tmp_rlwe_out[1].as_ref());
723}
724
725/// Returns RLWE(m0m1) = RLWE(m0) x RGSW(m1). Mutates rlwe_in inplace to equal
726/// RLWE(m0m1)
727///
728/// - rlwe_in: is RLWE(m0) with polynomials in coefficient domain
729/// - rgsw_in: is RGSW(m1) with polynomials in evaluation domain
730/// - scratch_matrix_d_ring: is a matrix of dimension (d_rgsw, ring_size) used
731/// as scratch space to store decomposed Ring elements temporarily
732pub(crate) fn rlwe_by_rgsw<
733 Mmut: MatrixMut,
734 MT: Matrix<MatElement = Mmut::MatElement> + MatrixMut<MatElement = Mmut::MatElement> + IsTrivial,
735 D: Decomposer<Element = Mmut::MatElement>,
736 ModOp: VectorOps<Element = Mmut::MatElement>,
737 NttOp: Ntt<Element = Mmut::MatElement>,
738>(
739 rlwe_in: &mut MT,
740 rgsw_in: &Mmut,
741 scratch_matrix_dplus2_ring: &mut Mmut,
742 decomposer: &D,
743 ntt_op: &NttOp,
744 mod_op: &ModOp,
745) where
746 Mmut::MatElement: Copy + Zero,
747 <Mmut as Matrix>::R: RowMut,
748 <MT as Matrix>::R: RowMut,
749{
750 let d_rgsw = decomposer.d();
751 assert!(scratch_matrix_dplus2_ring.dimension() == (d_rgsw + 2, rlwe_in.dimension().1));
752 assert!(rgsw_in.dimension() == (d_rgsw * 4, rlwe_in.dimension().1));
753
754 // decomposed RLWE x RGSW
755 let (rlwe_dash_nsm, rlwe_dash_m) = rgsw_in.split_at_row(d_rgsw * 2);
756 let (scratch_matrix_d_ring, scratch_rlwe_out) =
757 scratch_matrix_dplus2_ring.split_at_row_mut(d_rgsw);
758 scratch_rlwe_out[0].as_mut().fill(Mmut::MatElement::zero());
759 scratch_rlwe_out[1].as_mut().fill(Mmut::MatElement::zero());
760 // RLWE_in = a_in, b_in; RLWE_out = a_out, b_out
761 if !rlwe_in.is_trivial() {
762 // a_in = 0 when RLWE_in is trivial RLWE ciphertext
763 // decomp<a_in>
764 decompose_r(rlwe_in.get_row_slice(0), scratch_matrix_d_ring, decomposer);
765 scratch_matrix_d_ring
766 .iter_mut()
767 .for_each(|r| ntt_op.forward(r.as_mut()));
768 // a_out += decomp<a_in> \cdot RLWE_A'(-sm)
769 routine(
770 scratch_rlwe_out[0].as_mut(),
771 scratch_matrix_d_ring.as_ref(),
772 &rlwe_dash_nsm[..d_rgsw],
773 mod_op,
774 );
775 // b_out += decomp<a_in> \cdot RLWE_B'(-sm)
776 routine(
777 scratch_rlwe_out[1].as_mut(),
778 scratch_matrix_d_ring.as_ref(),
779 &rlwe_dash_nsm[d_rgsw..],
780 mod_op,
781 );
782 }
783 // decomp<b_in>
784 decompose_r(rlwe_in.get_row_slice(1), scratch_matrix_d_ring, decomposer);
785 scratch_matrix_d_ring
786 .iter_mut()
787 .for_each(|r| ntt_op.forward(r.as_mut()));
788 // a_out += decomp<b_in> \cdot RLWE_A'(m)
789 routine(
790 scratch_rlwe_out[0].as_mut(),
791 scratch_matrix_d_ring.as_ref(),
792 &rlwe_dash_m[..d_rgsw],
793 mod_op,
794 );
795 // b_out += decomp<b_in> \cdot RLWE_B'(m)
796 routine(
797 scratch_rlwe_out[1].as_mut(),
798 scratch_matrix_d_ring.as_ref(),
799 &rlwe_dash_m[d_rgsw..],
800 mod_op,
801 );
802
803 // transform rlwe_out to coefficient domain
804 scratch_rlwe_out
805 .iter_mut()
806 .for_each(|r| ntt_op.backward(r.as_mut()));
807
808 rlwe_in
809 .get_row_mut(0)
810 .copy_from_slice(scratch_rlwe_out[0].as_mut());
811 rlwe_in
812 .get_row_mut(1)
813 .copy_from_slice(scratch_rlwe_out[1].as_mut());
814 rlwe_in.set_not_trivial();
815}
816
817/// Inplace mutates rlwe_0_eval_domain to equal RGSW(m0m1) = RGSW(m0)xRGSW(m1)
818/// in evaluation domain
819///
820/// - rgsw_0_eval_domain: RGSW(m0) in evaluation domain
821/// - rgsw_1: RGSW(m1)
822/// - scratch_matrix_d_plus_rgsw_by_ring: scratch space matrix of size
823/// (d+(d*4))xring_size, where d equals d_rgsw
824pub(crate) fn rgsw_by_rgsw_inplace<
825 Mmut: MatrixMut,
826 D: Decomposer<Element = Mmut::MatElement>,
827 ModOp: VectorOps<Element = Mmut::MatElement>,
828 NttOp: Ntt<Element = Mmut::MatElement>,
829>(
830 rgsw_0_eval_domain: &mut Mmut,
831 rgsw_1: &Mmut,
832 decomposer: &D,
833 scratch_matrix_d_plus_rgsw_by_ring: &mut Mmut,
834 ntt_op: &NttOp,
835 mod_op: &ModOp,
836) where
837 <Mmut as Matrix>::R: RowMut,
838 Mmut::MatElement: Copy + Zero,
839{
840 let d_rgsw = decomposer.d();
841 assert!(rgsw_0_eval_domain.dimension().0 == 4 * d_rgsw);
842 let ring_size = rgsw_0_eval_domain.dimension().1;
843 assert!(rgsw_1.dimension() == (4 * d_rgsw, ring_size));
844 assert!(scratch_matrix_d_plus_rgsw_by_ring.dimension() == (d_rgsw + (d_rgsw * 4), ring_size));
845
846 let (decomp_r_space, rgsw_space) = scratch_matrix_d_plus_rgsw_by_ring.split_at_row_mut(d_rgsw);
847
848 // zero rgsw_space
849 rgsw_space
850 .iter_mut()
851 .for_each(|ri| ri.as_mut().fill(Mmut::MatElement::zero()));
852 let (rlwe_dash_space_nsm, rlwe_dash_space_m) = rgsw_space.split_at_mut(d_rgsw * 2);
853 let (rlwe_dash_space_nsm_parta, rlwe_dash_space_nsm_partb) =
854 rlwe_dash_space_nsm.split_at_mut(d_rgsw);
855 let (rlwe_dash_space_m_parta, rlwe_dash_space_m_partb) = rlwe_dash_space_m.split_at_mut(d_rgsw);
856
857 let (rgsw0_nsm, rgsw0_m) = rgsw_0_eval_domain.split_at_row(d_rgsw * 2);
858 let (rgsw1_nsm, rgsw1_m) = rgsw_1.split_at_row(d_rgsw * 2);
859
860 // RGSW x RGSW
861 izip!(
862 rgsw1_nsm.iter().take(d_rgsw).chain(rgsw1_m).take(d_rgsw),
863 rgsw1_nsm.iter().skip(d_rgsw).chain(rgsw1_m).skip(d_rgsw),
864 rlwe_dash_space_nsm_parta
865 .iter_mut()
866 .chain(rlwe_dash_space_m_parta),
867 rlwe_dash_space_nsm_partb
868 .iter_mut()
869 .chain(rlwe_dash_space_m_partb),
870 )
871 .for_each(|(rlwe_a, rlwe_b, rlwe_out_a, rlwe_out_b)| {
872 // Part A
873 decompose_r(rlwe_a.as_ref(), decomp_r_space.as_mut(), decomposer);
874 decomp_r_space
875 .iter_mut()
876 .for_each(|ri| ntt_op.forward(ri.as_mut()));
877 routine(
878 rlwe_out_a.as_mut(),
879 decomp_r_space,
880 &rgsw0_nsm[..d_rgsw],
881 mod_op,
882 );
883 routine(
884 rlwe_out_b.as_mut(),
885 decomp_r_space,
886 &rgsw0_nsm[d_rgsw..],
887 mod_op,
888 );
889
890 // Part B
891 decompose_r(rlwe_b.as_ref(), decomp_r_space.as_mut(), decomposer);
892 decomp_r_space
893 .iter_mut()
894 .for_each(|ri| ntt_op.forward(ri.as_mut()));
895 routine(
896 rlwe_out_a.as_mut(),
897 decomp_r_space,
898 &rgsw0_m[..d_rgsw],
899 mod_op,
900 );
901 routine(
902 rlwe_out_b.as_mut(),
903 decomp_r_space,
904 &rgsw0_m[d_rgsw..],
905 mod_op,
906 );
907 });
908
909 // copy over RGSW(m0m1) into RGSW(m0)
910 izip!(rgsw_0_eval_domain.iter_rows_mut(), rgsw_space.iter())
911 .for_each(|(to_ri, from_ri)| to_ri.as_mut().copy_from_slice(from_ri.as_ref()))
912}
913
914/// Encrypts message m as a RGSW ciphertext.
915///
916/// - m_eval: is `m` is evaluation domain
917/// - out_rgsw: RGSW(m) is stored as single matrix of dimension (d_rgsw * 3,
918/// ring_size). The matrix has the following structure [RLWE'_A(-sm) ||
919/// RLWE'_B(-sm) || RLWE'_B(m)]^T and RLWE'_A(m) is generated via seed (where
920/// p_rng is assumed to be seeded with seed)
921pub(crate) fn secret_key_encrypt_rgsw<
922 Mmut: MatrixMut + MatrixEntity,
923 S,
924 R: RandomGaussianDist<[Mmut::MatElement], Parameters = Mmut::MatElement>
925 + RandomUniformDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
926 PR: RandomUniformDist<[Mmut::MatElement], Parameters = Mmut::MatElement>,
927 ModOp: VectorOps<Element = Mmut::MatElement>,
928 NttOp: Ntt<Element = Mmut::MatElement>,
929>(
930 out_rgsw: &mut Mmut,
931 m: &Mmut::R,
932 gadget_vector: &[Mmut::MatElement],
933 s: &[S],
934 mod_op: &ModOp,
935 ntt_op: &NttOp,
936 p_rng: &mut PR,
937 rng: &mut R,
938) where
939 <Mmut as Matrix>::R: RowMut + RowEntity + TryConvertFrom<[S], Parameters = Mmut::MatElement>,
940 Mmut::MatElement: Copy,
941{
942 let d = gadget_vector.len();
943 let q = mod_op.modulus();
944 let ring_size = s.len();
945 assert!(out_rgsw.dimension() == (d * 3, ring_size));
946 assert!(m.as_ref().len() == ring_size);
947
948 // RLWE(-sm), RLWE(m)
949 let (rlwe_dash_nsm, b_rlwe_dash_m) = out_rgsw.split_at_row_mut(d * 2);
950
951 let mut s_eval = Mmut::R::try_convert_from(s, &q);
952 ntt_op.forward(s_eval.as_mut());
953
954 let mut scratch_space = Mmut::R::zeros(ring_size);
955
956 // RLWE'(-sm)
957 let (a_rlwe_dash_nsm, b_rlwe_dash_nsm) = rlwe_dash_nsm.split_at_mut(d);
958 izip!(
959 a_rlwe_dash_nsm.iter_mut(),
960 b_rlwe_dash_nsm.iter_mut(),
961 gadget_vector.iter()
962 )
963 .for_each(|(ai, bi, beta_i)| {
964 // Sample a_i
965 RandomUniformDist::random_fill(rng, &q, ai.as_mut());
966
967 // a_i * s
968 scratch_space.as_mut().copy_from_slice(ai.as_ref());
969 ntt_op.forward(scratch_space.as_mut());
970 mod_op.elwise_mul_mut(scratch_space.as_mut(), s_eval.as_ref());
971 ntt_op.backward(scratch_space.as_mut());
972
973 // b_i = e_i + a_i * s
974 RandomGaussianDist::random_fill(rng, &q, bi.as_mut());
975 mod_op.elwise_add_mut(bi.as_mut(), scratch_space.as_ref());
976
977 // a_i + \beta_i * m
978 mod_op.elwise_scalar_mul(scratch_space.as_mut(), m.as_ref(), beta_i);
979 mod_op.elwise_add_mut(ai.as_mut(), scratch_space.as_ref());
980 });
981
982 // RLWE(m)
983 let mut a_rlwe_dash_m = {
984 // polynomials of part A of RLWE'(m) are sampled from seed
985 let mut a = Mmut::zeros(d, ring_size);
986 a.iter_rows_mut()
987 .for_each(|ai| RandomUniformDist::random_fill(p_rng, &q, ai.as_mut()));
988 a
989 };
990
991 izip!(
992 a_rlwe_dash_m.iter_rows_mut(),
993 b_rlwe_dash_m.iter_mut(),
994 gadget_vector.iter()
995 )
996 .for_each(|(ai, bi, beta_i)| {
997 // ai * s
998 ntt_op.forward(ai.as_mut());
999 mod_op.elwise_mul_mut(ai.as_mut(), s_eval.as_ref());
1000 ntt_op.backward(ai.as_mut());
1001
1002 // beta_i * m
1003 mod_op.elwise_scalar_mul(scratch_space.as_mut(), m.as_ref(), beta_i);
1004
1005 // Sample e_i
1006 RandomGaussianDist::random_fill(rng, &q, bi.as_mut());
1007 // e_i + beta_i * m + ai*s
1008 mod_op.elwise_add_mut(bi.as_mut(), scratch_space.as_ref());
1009 mod_op.elwise_add_mut(bi.as_mut(), ai.as_ref());
1010 });
1011}
1012
1013pub(crate) fn public_key_encrypt_rgsw<
1014 Mmut: MatrixMut + MatrixEntity,
1015 M: Matrix<MatElement = Mmut::MatElement>,
1016 R: RandomGaussianDist<[Mmut::MatElement], Parameters = Mmut::MatElement>
1017 + RandomUniformDist<[u8], Parameters = u8>
1018 + RandomUniformDist<usize, Parameters = usize>,
1019 ModOp: VectorOps<Element = Mmut::MatElement>,
1020 NttOp: Ntt<Element = Mmut::MatElement>,
1021>(
1022 out_rgsw: &mut Mmut,
1023 m: &[M::MatElement],
1024 public_key: &M,
1025 gadget_vector: &[Mmut::MatElement],
1026 mod_op: &ModOp,
1027 ntt_op: &NttOp,
1028 rng: &mut R,
1029) where
1030 <Mmut as Matrix>::R: RowMut + RowEntity + TryConvertFrom<[i32], Parameters = Mmut::MatElement>,
1031 Mmut::MatElement: Copy,
1032{
1033 let ring_size = public_key.dimension().1;
1034 let d = gadget_vector.len();
1035 assert!(public_key.dimension().0 == 2);
1036 assert!(out_rgsw.dimension() == (d * 4, ring_size));
1037
1038 let mut pk_eval = Mmut::zeros(2, ring_size);
1039 izip!(pk_eval.iter_rows_mut(), public_key.iter_rows()).for_each(|(to_i, from_i)| {
1040 to_i.as_mut().copy_from_slice(from_i.as_ref());
1041 ntt_op.forward(to_i.as_mut());
1042 });
1043 let p0 = pk_eval.get_row_slice(0);
1044 let p1 = pk_eval.get_row_slice(1);
1045
1046 let q = mod_op.modulus();
1047
1048 // RGSW(m) = RLWE'(-sm), RLWE(m)
1049 let (rlwe_dash_nsm, rlwe_dash_m) = out_rgsw.split_at_row_mut(2 * d);
1050
1051 // RLWE(-sm)
1052 let (rlwe_dash_nsm_parta, rlwe_dash_nsm_partb) = rlwe_dash_nsm.split_at_mut(d);
1053 izip!(
1054 rlwe_dash_nsm_parta.iter_mut(),
1055 rlwe_dash_nsm_partb.iter_mut(),
1056 gadget_vector.iter()
1057 )
1058 .for_each(|(ai, bi, beta_i)| {
1059 // sample ephemeral secret u_i
1060 let mut u = vec![0i32; ring_size];
1061 fill_random_ternary_secret_with_hamming_weight(u.as_mut(), ring_size >> 1, rng);
1062 let mut u_eval = Mmut::R::try_convert_from(u.as_ref(), &q);
1063 ntt_op.forward(u_eval.as_mut());
1064
1065 let mut u_eval_copy = Mmut::R::zeros(ring_size);
1066 u_eval_copy.as_mut().copy_from_slice(u_eval.as_ref());
1067
1068 // p0 * u
1069 mod_op.elwise_mul_mut(u_eval.as_mut(), p0.as_ref());
1070 // p1 * u
1071 mod_op.elwise_mul_mut(u_eval_copy.as_mut(), p1.as_ref());
1072 ntt_op.backward(u_eval.as_mut());
1073 ntt_op.backward(u_eval_copy.as_mut());
1074
1075 // sample error
1076 RandomGaussianDist::random_fill(rng, &q, ai.as_mut());
1077 RandomGaussianDist::random_fill(rng, &q, bi.as_mut());
1078
1079 // a = p0*u+e0
1080 mod_op.elwise_add_mut(ai.as_mut(), u_eval.as_ref());
1081 // b = p1*u+e1
1082 mod_op.elwise_add_mut(bi.as_mut(), u_eval_copy.as_ref());
1083
1084 // a = p0*u + e0 + \beta*m
1085 // use u_eval as scratch
1086 mod_op.elwise_scalar_mul(u_eval.as_mut(), m.as_ref(), beta_i);
1087 mod_op.elwise_add_mut(ai.as_mut(), u_eval.as_ref());
1088 });
1089
1090 // RLWE(m)
1091 let (rlwe_dash_m_parta, rlwe_dash_m_partb) = rlwe_dash_m.split_at_mut(d);
1092 izip!(
1093 rlwe_dash_m_parta.iter_mut(),
1094 rlwe_dash_m_partb.iter_mut(),
1095 gadget_vector.iter()
1096 )
1097 .for_each(|(ai, bi, beta_i)| {
1098 // sample ephemeral secret u_i
1099 let mut u = vec![0i32; ring_size];
1100 fill_random_ternary_secret_with_hamming_weight(u.as_mut(), ring_size >> 1, rng);
1101 let mut u_eval = Mmut::R::try_convert_from(u.as_ref(), &q);
1102 ntt_op.forward(u_eval.as_mut());
1103
1104 let mut u_eval_copy = Mmut::R::zeros(ring_size);
1105 u_eval_copy.as_mut().copy_from_slice(u_eval.as_ref());
1106
1107 // p0 * u
1108 mod_op.elwise_mul_mut(u_eval.as_mut(), p0.as_ref());
1109 // p1 * u
1110 mod_op.elwise_mul_mut(u_eval_copy.as_mut(), p1.as_ref());
1111 ntt_op.backward(u_eval.as_mut());
1112 ntt_op.backward(u_eval_copy.as_mut());
1113
1114 // sample error
1115 RandomGaussianDist::random_fill(rng, &q, ai.as_mut());
1116 RandomGaussianDist::random_fill(rng, &q, bi.as_mut());
1117
1118 // a = p0*u+e0
1119 mod_op.elwise_add_mut(ai.as_mut(), u_eval.as_ref());
1120 // b = p1*u+e1
1121 mod_op.elwise_add_mut(bi.as_mut(), u_eval_copy.as_ref());
1122
1123 // b = p1*u + e0 + \beta*m
1124 // use u_eval as scratch
1125 mod_op.elwise_scalar_mul(u_eval.as_mut(), m.as_ref(), beta_i);
1126 mod_op.elwise_add_mut(bi.as_mut(), u_eval.as_ref());
1127 });
1128}
1129
1130/// Encrypt polynomial m(X) as RLWE ciphertext.
1131///
1132/// - rlwe_out: returned RLWE ciphertext RLWE(m) in coefficient domain. RLWE
1133/// ciphertext is a matirx with first row consiting polynomial `a` and the
1134/// second rows consting polynomial `b`
1135pub(crate) fn secret_key_encrypt_rlwe<
1136 Ro: Row + RowMut + RowEntity,
1137 ModOp: VectorOps<Element = Ro::Element>,
1138 NttOp: Ntt<Element = Ro::Element>,
1139 S,
1140 R: RandomGaussianDist<[Ro::Element], Parameters = Ro::Element>,
1141 PR: RandomUniformDist<[Ro::Element], Parameters = Ro::Element>,
1142>(
1143 m: &Ro,
1144 b_rlwe_out: &mut Ro,
1145 s: &[S],
1146 mod_op: &ModOp,
1147 ntt_op: &NttOp,
1148 p_rng: &mut PR,
1149 rng: &mut R,
1150) where
1151 Ro: TryConvertFrom<[S], Parameters = Ro::Element> + Debug,
1152{
1153 let ring_size = s.len();
1154 assert!(m.as_ref().len() == ring_size);
1155 assert!(b_rlwe_out.as_ref().len() == ring_size);
1156
1157 let q = mod_op.modulus();
1158
1159 // sample a
1160 let mut a = {
1161 let mut a = Ro::zeros(ring_size);
1162 RandomUniformDist::random_fill(p_rng, &q, a.as_mut());
1163 a
1164 };
1165
1166 // s * a
1167 let mut sa = Ro::try_convert_from(s, &q);
1168 ntt_op.forward(sa.as_mut());
1169 ntt_op.forward(a.as_mut());
1170 mod_op.elwise_mul_mut(sa.as_mut(), a.as_ref());
1171 ntt_op.backward(sa.as_mut());
1172
1173 // sample e
1174 RandomGaussianDist::random_fill(rng, &q, b_rlwe_out.as_mut());
1175 mod_op.elwise_add_mut(b_rlwe_out.as_mut(), m.as_ref());
1176 mod_op.elwise_add_mut(b_rlwe_out.as_mut(), sa.as_ref());
1177}
1178
1179/// Generates RLWE public key
1180pub(crate) fn gen_rlwe_public_key<
1181 Ro: RowMut + RowEntity,
1182 S,
1183 ModOp: VectorOps<Element = Ro::Element>,
1184 NttOp: Ntt<Element = Ro::Element>,
1185 PRng: RandomUniformDist<[Ro::Element], Parameters = Ro::Element>,
1186 Rng: RandomGaussianDist<[Ro::Element], Parameters = Ro::Element>,
1187>(
1188 part_b_out: &mut Ro,
1189 s: &[S],
1190 ntt_op: &NttOp,
1191 mod_op: &ModOp,
1192 p_rng: &mut PRng,
1193 rng: &mut Rng,
1194) where
1195 Ro: TryConvertFrom<[S], Parameters = Ro::Element>,
1196{
1197 let ring_size = s.len();
1198 assert!(part_b_out.as_ref().len() == ring_size);
1199
1200 let q = mod_op.modulus();
1201
1202 // sample a
1203 let mut a = {
1204 let mut tmp = Ro::zeros(ring_size);
1205 RandomUniformDist::random_fill(p_rng, &q, tmp.as_mut());
1206 tmp
1207 };
1208 ntt_op.forward(a.as_mut());
1209
1210 // s*a
1211 let mut sa = Ro::try_convert_from(s, &q);
1212 ntt_op.forward(sa.as_mut());
1213 mod_op.elwise_mul_mut(sa.as_mut(), a.as_ref());
1214 ntt_op.backward(sa.as_mut());
1215
1216 // s*a + e
1217 RandomGaussianDist::random_fill(rng, &q, part_b_out.as_mut());
1218 mod_op.elwise_add_mut(part_b_out.as_mut(), sa.as_ref());
1219}
1220
1221/// Decrypts degree 1 RLWE ciphertext RLWE(m) and returns m
1222///
1223/// - rlwe_ct: input degree 1 ciphertext RLWE(m).
1224pub(crate) fn decrypt_rlwe<
1225 R: RowMut,
1226 M: Matrix<MatElement = R::Element>,
1227 ModOp: VectorOps<Element = R::Element>,
1228 NttOp: Ntt<Element = R::Element>,
1229 S,
1230>(
1231 rlwe_ct: &M,
1232 s: &[S],
1233 m_out: &mut R,
1234 ntt_op: &NttOp,
1235 mod_op: &ModOp,
1236) where
1237 R: TryConvertFrom<[S], Parameters = R::Element>,
1238 R::Element: Copy,
1239{
1240 let ring_size = s.len();
1241 assert!(rlwe_ct.dimension() == (2, ring_size));
1242 assert!(m_out.as_ref().len() == ring_size);
1243
1244 // transform a to evluation form
1245 m_out.as_mut().copy_from_slice(rlwe_ct.get_row_slice(0));
1246 ntt_op.forward(m_out.as_mut());
1247
1248 // -s*a
1249 let mut s = R::try_convert_from(&s, &mod_op.modulus());
1250 ntt_op.forward(s.as_mut());
1251 mod_op.elwise_mul_mut(m_out.as_mut(), s.as_ref());
1252 mod_op.elwise_neg_mut(m_out.as_mut());
1253 ntt_op.backward(m_out.as_mut());
1254
1255 // m+e = b - s*a
1256 mod_op.elwise_add_mut(m_out.as_mut(), rlwe_ct.get_row_slice(1));
1257}
1258
1259// Measures noise in degree 1 RLWE ciphertext against encoded ideal message
1260// encoded_m
1261pub(crate) fn measure_noise<
1262 Mmut: MatrixMut + Matrix,
1263 ModOp: VectorOps<Element = Mmut::MatElement>,
1264 NttOp: Ntt<Element = Mmut::MatElement>,
1265 S,
1266>(
1267 rlwe_ct: &Mmut,
1268 encoded_m_ideal: &Mmut::R,
1269 ntt_op: &NttOp,
1270 mod_op: &ModOp,
1271 s: &[S],
1272) -> f64
1273where
1274 <Mmut as Matrix>::R: RowMut,
1275 Mmut::R: RowEntity + TryConvertFrom<[S], Parameters = Mmut::MatElement>,
1276 Mmut::MatElement: PrimInt + ToPrimitive + Debug,
1277{
1278 let ring_size = s.len();
1279 assert!(rlwe_ct.dimension() == (2, ring_size));
1280 assert!(encoded_m_ideal.as_ref().len() == ring_size);
1281
1282 // -(s * a)
1283 let q = VectorOps::modulus(mod_op);
1284 let mut s = Mmut::R::try_convert_from(s, &q);
1285 ntt_op.forward(s.as_mut());
1286 let mut a = Mmut::R::zeros(ring_size);
1287 a.as_mut().copy_from_slice(rlwe_ct.get_row_slice(0));
1288 ntt_op.forward(a.as_mut());
1289 mod_op.elwise_mul_mut(s.as_mut(), a.as_ref());
1290 mod_op.elwise_neg_mut(s.as_mut());
1291 ntt_op.backward(s.as_mut());
1292
1293 // m+e = b - s*a
1294 let mut m_plus_e = s;
1295 mod_op.elwise_add_mut(m_plus_e.as_mut(), rlwe_ct.get_row_slice(1));
1296
1297 // difference
1298 mod_op.elwise_sub_mut(m_plus_e.as_mut(), encoded_m_ideal.as_ref());
1299
1300 let mut max_diff_bits = f64::MIN;
1301 m_plus_e.as_ref().iter().for_each(|v| {
1302 let mut v = *v;
1303
1304 if v >= (q >> 1) {
1305 // v is -ve
1306 v = q - v;
1307 }
1308
1309 let bits = (v.to_f64().unwrap()).log2();
1310
1311 if max_diff_bits < bits {
1312 max_diff_bits = bits;
1313 }
1314 });
1315
1316 return max_diff_bits;
1317}
1318
1319#[cfg(test)]
1320mod tests {
1321 use std::{ops::Mul, vec};
1322
1323 use itertools::{izip, Itertools};
1324 use rand::{thread_rng, Rng};
1325
1326 use crate::{
1327 backend::{ArithmeticOps, ModInit, ModularOpsU64},
1328 decomposer::{gadget_vector, DefaultDecomposer},
1329 ntt::{self, Ntt, NttBackendU64, NttInit},
1330 random::{DefaultSecureRng, NewWithSeed, RandomUniformDist},
1331 rgsw::{
1332 gen_rlwe_public_key, measure_noise, public_key_encrypt_rgsw, AutoKeyEvaluationDomain,
1333 RgswCiphertext, RgswCiphertextEvaluationDomain, RlweCiphertext, RlwePublicKey,
1334 SeededAutoKey, SeededRgswCiphertext, SeededRlweCiphertext, SeededRlwePublicKey,
1335 },
1336 utils::{generate_prime, negacyclic_mul},
1337 Matrix, Secret,
1338 };
1339
1340 use super::{
1341 decrypt_rlwe, galois_auto, galois_key_gen, generate_auto_map, rgsw_by_rgsw_inplace,
1342 rlwe_by_rgsw, secret_key_encrypt_rgsw, secret_key_encrypt_rlwe, RlweSecret,
1343 };
1344
1345 #[test]
1346 fn rlwe_encrypt_decryption() {
1347 let logq = 50;
1348 let logp = 2;
1349 let ring_size = 1 << 4;
1350 let q = generate_prime(logq, ring_size, 1u64 << logq).unwrap();
1351 let p = 1u64 << logp;
1352
1353 let mut rng = DefaultSecureRng::new();
1354
1355 let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize);
1356
1357 // sample m0
1358 let mut m0 = vec![0u64; ring_size as usize];
1359 RandomUniformDist::<[u64]>::random_fill(&mut rng, &(1u64 << logp), m0.as_mut_slice());
1360
1361 let ntt_op = NttBackendU64::new(q, ring_size as usize);
1362 let mod_op = ModularOpsU64::new(q);
1363
1364 // encrypt m0
1365 let mut rlwe_seed = [0u8; 32];
1366 rng.fill_bytes(&mut rlwe_seed);
1367 let mut seeded_rlwe_in_ct =
1368 SeededRlweCiphertext::<_, [u8; 32]>::empty(ring_size as usize, rlwe_seed, q);
1369 let mut p_rng = DefaultSecureRng::new_with_seed(rlwe_seed);
1370 let encoded_m = m0
1371 .iter()
1372 .map(|v| (((*v as f64) * q as f64) / (p as f64)).round() as u64)
1373 .collect_vec();
1374 secret_key_encrypt_rlwe(
1375 &encoded_m,
1376 &mut seeded_rlwe_in_ct.data,
1377 s.values(),
1378 &mod_op,
1379 &ntt_op,
1380 &mut p_rng,
1381 &mut rng,
1382 );
1383 let rlwe_in_ct =
1384 RlweCiphertext::<Vec<Vec<u64>>, DefaultSecureRng>::from(&seeded_rlwe_in_ct);
1385
1386 let mut encoded_m_back = vec![0u64; ring_size as usize];
1387 decrypt_rlwe(
1388 &rlwe_in_ct,
1389 s.values(),
1390 &mut encoded_m_back,
1391 &ntt_op,
1392 &mod_op,
1393 );
1394 let m_back = encoded_m_back
1395 .iter()
1396 .map(|v| (((*v as f64 * p as f64) / q as f64).round() as u64) % p)
1397 .collect_vec();
1398 assert_eq!(m0, m_back);
1399 }
1400
1401 #[test]
1402 fn rlwe_by_rgsw_works() {
1403 let logq = 50;
1404 let logp = 2;
1405 let ring_size = 1 << 9;
1406 let q = generate_prime(logq, ring_size, 1u64 << logq).unwrap();
1407 let p = 1u64 << logp;
1408 let d_rgsw = 9;
1409 let logb = 5;
1410
1411 let mut rng = DefaultSecureRng::new_seeded([0u8; 32]);
1412
1413 let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize);
1414
1415 let mut m0 = vec![0u64; ring_size as usize];
1416 RandomUniformDist::<[u64]>::random_fill(&mut rng, &(1u64 << logp), m0.as_mut_slice());
1417 let mut m1 = vec![0u64; ring_size as usize];
1418 m1[thread_rng().gen_range(0..ring_size) as usize] = 1;
1419
1420 let ntt_op = NttBackendU64::new(q, ring_size as usize);
1421 let mod_op = ModularOpsU64::new(q);
1422
1423 // Encrypt m1 as RGSW(m1)
1424 let rgsw_ct = {
1425 //TODO(Jay): Figure out better way to test secret key and public key variant of
1426 // RGSW ciphertext encryption within the same test
1427
1428 if false {
1429 // RGSW(m1) encryption using secret key
1430 let mut rgsw_seed = [0u8; 32];
1431 rng.fill_bytes(&mut rgsw_seed);
1432 let mut seeded_rgsw_ct = SeededRgswCiphertext::<Vec<Vec<u64>>, [u8; 32]>::empty(
1433 ring_size as usize,
1434 d_rgsw,
1435 rgsw_seed,
1436 q,
1437 );
1438 let mut p_rng = DefaultSecureRng::new_seeded(rgsw_seed);
1439 let gadget_vector = gadget_vector(logq, logb, d_rgsw);
1440 secret_key_encrypt_rgsw(
1441 &mut seeded_rgsw_ct.data,
1442 &m1,
1443 &gadget_vector,
1444 s.values(),
1445 &mod_op,
1446 &ntt_op,
1447 &mut p_rng,
1448 &mut rng,
1449 );
1450 RgswCiphertextEvaluationDomain::<_, DefaultSecureRng, NttBackendU64>::from(
1451 &seeded_rgsw_ct,
1452 )
1453 } else {
1454 // RGSW(m1) encryption using public key
1455
1456 // first create public key
1457 let mut pk_seed = [0u8; 32];
1458 rng.fill_bytes(&mut pk_seed);
1459 let mut pk_prng = DefaultSecureRng::new_seeded(pk_seed);
1460 let mut seeded_pk =
1461 SeededRlwePublicKey::<Vec<u64>, _>::empty(ring_size as usize, pk_seed, q);
1462 gen_rlwe_public_key(
1463 &mut seeded_pk.data,
1464 s.values(),
1465 &ntt_op,
1466 &mod_op,
1467 &mut pk_prng,
1468 &mut rng,
1469 );
1470 let pk = RlwePublicKey::<Vec<Vec<u64>>, DefaultSecureRng>::from(&seeded_pk);
1471
1472 // public key encrypt RGSW(m1)
1473 let mut rgsw_ct = vec![vec![0u64; ring_size as usize]; d_rgsw * 4];
1474 let gadget_vector = gadget_vector(logq, logb, d_rgsw);
1475 public_key_encrypt_rgsw(
1476 &mut rgsw_ct,
1477 &m1,
1478 &pk.data,
1479 &gadget_vector,
1480 &mod_op,
1481 &ntt_op,
1482 &mut rng,
1483 );
1484
1485 RgswCiphertextEvaluationDomain::<_, DefaultSecureRng, NttBackendU64>::from(
1486 &RgswCiphertext {
1487 data: rgsw_ct,
1488 modulus: q,
1489 },
1490 )
1491 }
1492 };
1493
1494 // Encrypt m0 as RLWE(m0)
1495 let mut rlwe_in_ct = {
1496 let mut rlwe_seed = [0u8; 32];
1497 rng.fill_bytes(&mut rlwe_seed);
1498 let mut seeded_rlwe_in_ct =
1499 SeededRlweCiphertext::<_, [u8; 32]>::empty(ring_size as usize, rlwe_seed, q);
1500 let mut p_rng = DefaultSecureRng::new_seeded(rlwe_seed);
1501 let encoded_m = m0
1502 .iter()
1503 .map(|v| (((*v as f64) * q as f64) / (p as f64)).round() as u64)
1504 .collect_vec();
1505 secret_key_encrypt_rlwe(
1506 &encoded_m,
1507 &mut seeded_rlwe_in_ct.data,
1508 s.values(),
1509 &mod_op,
1510 &ntt_op,
1511 &mut p_rng,
1512 &mut rng,
1513 );
1514
1515 RlweCiphertext::<Vec<Vec<u64>>, DefaultSecureRng>::from(&seeded_rlwe_in_ct)
1516 };
1517
1518 // RLWE(m0m1) = RLWE(m0) x RGSW(m1)
1519 let mut scratch_space = vec![vec![0u64; ring_size as usize]; d_rgsw + 2];
1520 let decomposer = DefaultDecomposer::new(q, logb, d_rgsw);
1521 rlwe_by_rgsw(
1522 &mut rlwe_in_ct,
1523 &rgsw_ct.data,
1524 &mut scratch_space,
1525 &decomposer,
1526 &ntt_op,
1527 &mod_op,
1528 );
1529
1530 // Decrypt RLWE(m0m1)
1531 let mut encoded_m0m1_back = vec![0u64; ring_size as usize];
1532 decrypt_rlwe(
1533 &rlwe_in_ct,
1534 s.values(),
1535 &mut encoded_m0m1_back,
1536 &ntt_op,
1537 &mod_op,
1538 );
1539 let m0m1_back = encoded_m0m1_back
1540 .iter()
1541 .map(|v| (((*v as f64 * p as f64) / (q as f64)).round() as u64) % p)
1542 .collect_vec();
1543
1544 let mul_mod = |v0: &u64, v1: &u64| (v0 * v1) % p;
1545 let m0m1 = negacyclic_mul(&m0, &m1, mul_mod, p);
1546 assert!(
1547 m0m1 == m0m1_back,
1548 "Expected {:?} \n Got {:?}",
1549 m0m1,
1550 m0m1_back
1551 );
1552 }
1553
1554 fn _pk_encrypt_rgsw(
1555 m: &[u64],
1556 public_key: &RlwePublicKey<Vec<Vec<u64>>, DefaultSecureRng>,
1557 gadget_vector: &[u64],
1558 mod_op: &ModularOpsU64,
1559 ntt_op: &NttBackendU64,
1560 ) -> RgswCiphertext<Vec<Vec<u64>>> {
1561 let (_, ring_size) = Matrix::dimension(&public_key.data);
1562 let d_rgsw = gadget_vector.len();
1563
1564 let mut rng = DefaultSecureRng::new();
1565
1566 assert!(m.len() == ring_size);
1567
1568 // public key encrypt RGSW(m1)
1569 let mut rgsw_ct = vec![vec![0u64; ring_size]; d_rgsw * 4];
1570 public_key_encrypt_rgsw(
1571 &mut rgsw_ct,
1572 m,
1573 &public_key.data,
1574 gadget_vector,
1575 mod_op,
1576 ntt_op,
1577 &mut rng,
1578 );
1579
1580 RgswCiphertext {
1581 data: rgsw_ct,
1582 modulus: mod_op.modulus(),
1583 }
1584 }
1585
1586 #[test]
1587 fn rgsw_by_rgsw() {
1588 let logq = 50;
1589 let logp = 2;
1590 let ring_size = 1 << 4;
1591 let q = generate_prime(logq, ring_size, 1u64 << logq).unwrap();
1592 let p = 1u64 << logp;
1593 let d_rgsw = 10;
1594 let logb = 5;
1595
1596 let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize);
1597
1598 let mut rng = DefaultSecureRng::new();
1599 let ntt_op = NttBackendU64::new(q, ring_size as usize);
1600 let mod_op = ModularOpsU64::new(q);
1601 let gadget_vector = gadget_vector(logq, logb, d_rgsw);
1602 let decomposer = DefaultDecomposer::new(q, logb, d_rgsw);
1603
1604 // Public Key
1605 let public_key = {
1606 let mut pk_seed = [0u8; 32];
1607 rng.fill_bytes(&mut pk_seed);
1608 let mut pk_prng = DefaultSecureRng::new_seeded(pk_seed);
1609 let mut seeded_pk =
1610 SeededRlwePublicKey::<Vec<u64>, _>::empty(ring_size as usize, pk_seed, q);
1611 gen_rlwe_public_key(
1612 &mut seeded_pk.data,
1613 s.values(),
1614 &ntt_op,
1615 &mod_op,
1616 &mut pk_prng,
1617 &mut rng,
1618 );
1619 RlwePublicKey::<Vec<Vec<u64>>, DefaultSecureRng>::from(&seeded_pk)
1620 };
1621
1622 let mut m0 = vec![0u64; ring_size as usize];
1623 m0[thread_rng().gen_range(0..ring_size) as usize] = 1;
1624 let mut m1 = vec![0u64; ring_size as usize];
1625 m1[thread_rng().gen_range(0..ring_size) as usize] = 1;
1626
1627 // RGSW(m0)
1628 let rgsw_m0 = _pk_encrypt_rgsw(&m0, &public_key, &gadget_vector, &mod_op, &ntt_op);
1629 // RGSW(m1)
1630 let rgsw_m1 = _pk_encrypt_rgsw(&m0, &public_key, &gadget_vector, &mod_op, &ntt_op);
1631
1632 let mut rgsw_m0_eval =
1633 RgswCiphertextEvaluationDomain::<_, DefaultSecureRng, NttBackendU64>::from(&rgsw_m0);
1634
1635 let mut scratch_matrix_d_plus_rgsw_by_ring =
1636 vec![vec![0u64; ring_size as usize]; d_rgsw + (d_rgsw * 4)];
1637 rgsw_by_rgsw_inplace(
1638 &mut rgsw_m0_eval.data,
1639 &rgsw_m1.data,
1640 &decomposer,
1641 &mut scratch_matrix_d_plus_rgsw_by_ring,
1642 &ntt_op,
1643 &mod_op,
1644 );
1645 dbg!(&rgsw_m0_eval.data);
1646
1647 // RLWE(m0m1)
1648 let mut rlwe_m0m1 = vec![vec![0u64; ring_size as usize]; 2];
1649 rlwe_m0m1[0].copy_from_slice(rgsw_m0_eval.get_row_slice(2 * d_rgsw));
1650 rlwe_m0m1[1].copy_from_slice(rgsw_m0_eval.get_row_slice(3 * d_rgsw));
1651 rlwe_m0m1.iter_mut().for_each(|ri| ntt_op.backward(ri));
1652
1653 // m0m1
1654 let mul = |a: &u64, b: &u64| ((*a as u128 * *b as u128) % q as u128) as u64;
1655 let m0m1 = negacyclic_mul(&m0, &m1, mul, q);
1656
1657 let noise = measure_noise(&rlwe_m0m1, &m0m1, &ntt_op, &mod_op, s.values());
1658 dbg!(noise);
1659 }
1660
1661 #[test]
1662 fn galois_auto_works() {
1663 let logq = 50;
1664 let ring_size = 1 << 4;
1665 let q = generate_prime(logq, 2 * ring_size, 1u64 << logq).unwrap();
1666 let logp = 3;
1667 let p = 1u64 << logp;
1668 let d_rgsw = 10;
1669 let logb = 5;
1670
1671 let mut rng = DefaultSecureRng::new();
1672 let s = RlweSecret::random((ring_size >> 1) as usize, ring_size as usize);
1673
1674 let mut m = vec![0u64; ring_size as usize];
1675 RandomUniformDist::random_fill(&mut rng, &p, m.as_mut_slice());
1676 let encoded_m = m
1677 .iter()
1678 .map(|v| (((*v as f64 * q as f64) / (p as f64)).round() as u64))
1679 .collect_vec();
1680
1681 let ntt_op = NttBackendU64::new(q, ring_size as usize);
1682 let mod_op = ModularOpsU64::new(q);
1683
1684 // RLWE_{s}(m)
1685 let mut seed_rlwe = [0u8; 32];
1686 rng.fill_bytes(&mut seed_rlwe);
1687 let mut seeded_rlwe_m = SeededRlweCiphertext::empty(ring_size as usize, seed_rlwe, q);
1688 let mut p_rng = DefaultSecureRng::new_seeded(seed_rlwe);
1689 secret_key_encrypt_rlwe(
1690 &encoded_m,
1691 &mut seeded_rlwe_m.data,
1692 s.values(),
1693 &mod_op,
1694 &ntt_op,
1695 &mut p_rng,
1696 &mut rng,
1697 );
1698 let mut rlwe_m = RlweCiphertext::<Vec<Vec<u64>>, DefaultSecureRng>::from(&seeded_rlwe_m);
1699
1700 let auto_k = -5;
1701
1702 // Generate galois key to key switch from s^k to s
1703 let mut seed_auto = [0u8; 32];
1704 rng.fill_bytes(&mut seed_auto);
1705 let mut seeded_auto_key = SeededAutoKey::empty(ring_size as usize, d_rgsw, seed_auto, q);
1706 let mut p_rng = DefaultSecureRng::new_seeded(seed_auto);
1707 let gadget_vector = gadget_vector(logq, logb, d_rgsw);
1708 galois_key_gen(
1709 &mut seeded_auto_key.data,
1710 s.values(),
1711 auto_k,
1712 &gadget_vector,
1713 &mod_op,
1714 &ntt_op,
1715 &mut p_rng,
1716 &mut rng,
1717 );
1718 let auto_key =
1719 AutoKeyEvaluationDomain::<Vec<Vec<u64>>, DefaultSecureRng, NttBackendU64>::from(
1720 &seeded_auto_key,
1721 );
1722
1723 // Send RLWE_{s}(m) -> RLWE_{s}(m^k)
1724 let mut scratch_space = vec![vec![0u64; ring_size as usize]; d_rgsw + 2];
1725 let (auto_map_index, auto_map_sign) = generate_auto_map(ring_size as usize, auto_k);
1726 let decomposer = DefaultDecomposer::new(q, logb, d_rgsw);
1727 galois_auto(
1728 &mut rlwe_m,
1729 &auto_key.data,
1730 &mut scratch_space,
1731 &auto_map_index,
1732 &auto_map_sign,
1733 &mod_op,
1734 &ntt_op,
1735 &decomposer,
1736 );
1737
1738 let rlwe_m_k = rlwe_m;
1739
1740 // Decrypt RLWE_{s}(m^k) and check
1741 let mut encoded_m_k_back = vec![0u64; ring_size as usize];
1742 decrypt_rlwe(
1743 &rlwe_m_k,
1744 s.values(),
1745 &mut encoded_m_k_back,
1746 &ntt_op,
1747 &mod_op,
1748 );
1749 let m_k_back = encoded_m_k_back
1750 .iter()
1751 .map(|v| (((*v as f64 * p as f64) / q as f64).round() as u64) % p)
1752 .collect_vec();
1753
1754 let mut m_k = vec![0u64; ring_size as usize];
1755 // Send \delta m -> \delta m^k
1756 izip!(m.iter(), auto_map_index.iter(), auto_map_sign.iter()).for_each(
1757 |(v, to_index, sign)| {
1758 if !*sign {
1759 m_k[*to_index] = (p - *v) % p;
1760 } else {
1761 m_k[*to_index] = *v;
1762 }
1763 },
1764 );
1765
1766 {
1767 let encoded_m_k = m_k
1768 .iter()
1769 .map(|v| ((*v as f64 * q as f64) / p as f64).round() as u64)
1770 .collect_vec();
1771
1772 let noise = measure_noise(&rlwe_m_k, &encoded_m_k, &ntt_op, &mod_op, s.values());
1773 println!("Ksk noise: {noise}");
1774 }
1775
1776 // FIXME(Jay): Galios autormophism will incur high error unless we fix in
1777 // accurate decomoposition of Decomposer when q is prime
1778 assert_eq!(m_k_back, m_k);
1779 // dbg!(m_k_back, m_k, q);
1780 }
1781}