poulpy/poulpy-cpu-avx/examples/rlwe_encrypt.rs
poulpy-cpu-avx/examples/rlwe_encrypt.rsRaw
1use itertools::izip;
2
3#[cfg(all(
4 feature = "enable-avx",
5 target_arch = "x86_64",
6 target_feature = "avx2",
7 target_feature = "fma"
8))]
9use poulpy_cpu_avx::FFT64Avx as BackendImpl;
10#[cfg(not(all(
11 feature = "enable-avx",
12 target_arch = "x86_64",
13 target_feature = "avx2",
14 target_feature = "fma"
15)))]
16use poulpy_cpu_ref::FFT64Ref as BackendImpl;
17
18use poulpy_hal::{
19 api::{
20 ModuleNew, ScratchOwnedAlloc, ScratchOwnedBorrow, SvpApplyDftToDftInplace, SvpPPolAlloc, SvpPrepare, VecZnxAddNormal,
21 VecZnxBigAddSmallInplace, VecZnxBigAlloc, VecZnxBigNormalize, VecZnxBigNormalizeTmpBytes, VecZnxBigSubSmallNegateInplace,
22 VecZnxDftAlloc, VecZnxDftApply, VecZnxFillUniform, VecZnxIdftApplyTmpA, VecZnxNormalizeInplace,
23 },
24 layouts::{Module, ScalarZnx, ScratchOwned, SvpPPol, VecZnx, VecZnxBig, VecZnxDft, ZnxInfos},
25 source::Source,
26};
27
28fn main() {
29 let n: usize = 16;
30 let base2k: usize = 18;
31 let ct_size: usize = 3;
32 let msg_size: usize = 2;
33 let log_scale: usize = msg_size * base2k - 5;
34 let module: Module<BackendImpl> = Module::<BackendImpl>::new(n as u64);
35
36 let mut scratch: ScratchOwned<BackendImpl> = ScratchOwned::<BackendImpl>::alloc(module.vec_znx_big_normalize_tmp_bytes());
37
38 let seed: [u8; 32] = [0; 32];
39 let mut source: Source = Source::new(seed);
40
41 // s <- Z_{-1, 0, 1}[X]/(X^{N}+1)
42 let mut s: ScalarZnx<Vec<u8>> = ScalarZnx::alloc(module.n(), 1);
43 s.fill_ternary_prob(0, 0.5, &mut source);
44
45 // Buffer to store s in the DFT domain
46 let mut s_dft: SvpPPol<Vec<u8>, BackendImpl> = module.svp_ppol_alloc(s.cols());
47
48 // s_dft <- DFT(s)
49 module.svp_prepare(&mut s_dft, 0, &s, 0);
50
51 // Allocates a VecZnx with two columns: ct=(0, 0)
52 let mut ct: VecZnx<Vec<u8>> = VecZnx::alloc(
53 module.n(),
54 2, // Number of columns
55 ct_size, // Number of small poly per column
56 );
57
58 // Fill the second column with random values: ct = (0, a)
59 module.vec_znx_fill_uniform(base2k, &mut ct, 1, &mut source);
60
61 let mut buf_dft: VecZnxDft<Vec<u8>, BackendImpl> = module.vec_znx_dft_alloc(1, ct_size);
62
63 module.vec_znx_dft_apply(1, 0, &mut buf_dft, 0, &ct, 1);
64
65 // Applies DFT(ct[1]) * DFT(s)
66 module.svp_apply_dft_to_dft_inplace(
67 &mut buf_dft, // DFT(ct[1] * s)
68 0, // Selects the first column of res
69 &s_dft, // DFT(s)
70 0, // Selects the first column of s_dft
71 );
72
73 // Alias scratch space (VecZnxDft<B> is always at least as big as VecZnxBig<B>)
74
75 // BIG(ct[1] * s) <- IDFT(DFT(ct[1] * s)) (not normalized)
76 let mut buf_big: VecZnxBig<Vec<u8>, BackendImpl> = module.vec_znx_big_alloc(1, ct_size);
77 module.vec_znx_idft_apply_tmpa(&mut buf_big, 0, &mut buf_dft, 0);
78
79 // Creates a plaintext: VecZnx with 1 column
80 let mut m = VecZnx::alloc(
81 module.n(),
82 1, // Number of columns
83 msg_size, // Number of small polynomials
84 );
85 let mut want: Vec<i64> = vec![0; n];
86 want.iter_mut().for_each(|x| *x = source.next_u64n(16, 15) as i64);
87 m.encode_vec_i64(base2k, 0, log_scale, &want);
88 module.vec_znx_normalize_inplace(base2k, &mut m, 0, scratch.borrow());
89
90 // m - BIG(ct[1] * s)
91 module.vec_znx_big_sub_small_negate_inplace(
92 &mut buf_big,
93 0, // Selects the first column of the receiver
94 &m,
95 0, // Selects the first column of the message
96 );
97
98 // Normalizes back to VecZnx
99 // ct[0] <- m - BIG(c1 * s)
100 module.vec_znx_big_normalize(
101 &mut ct,
102 base2k,
103 0,
104 0, // Selects the first column of ct (ct[0])
105 &buf_big,
106 base2k,
107 0, // Selects the first column of buf_big
108 scratch.borrow(),
109 );
110
111 // Add noise to ct[0]
112 // ct[0] <- ct[0] + e
113 module.vec_znx_add_normal(
114 base2k,
115 &mut ct,
116 0, // Selects the first column of ct (ct[0])
117 base2k * ct_size, // Scaling of the noise: 2^{-base2k * limbs}
118 &mut source,
119 3.2, // Standard deviation
120 3.2 * 6.0, // Truncatation bound
121 );
122
123 // Final ciphertext: ct = (-a * s + m + e, a)
124
125 // Decryption
126
127 // DFT(ct[1] * s)
128 module.vec_znx_dft_apply(1, 0, &mut buf_dft, 0, &ct, 1);
129 module.svp_apply_dft_to_dft_inplace(
130 &mut buf_dft,
131 0, // Selects the first column of res.
132 &s_dft,
133 0,
134 );
135
136 // BIG(c1 * s) = IDFT(DFT(c1 * s))
137 module.vec_znx_idft_apply_tmpa(&mut buf_big, 0, &mut buf_dft, 0);
138
139 // BIG(c1 * s) + ct[0]
140 module.vec_znx_big_add_small_inplace(&mut buf_big, 0, &ct, 0);
141
142 // m + e <- BIG(ct[1] * s + ct[0])
143 let mut res = VecZnx::alloc(module.n(), 1, ct_size);
144 module.vec_znx_big_normalize(&mut res, base2k, 0, 0, &buf_big, base2k, 0, scratch.borrow());
145
146 // have = m * 2^{log_scale} + e
147 let mut have: Vec<i64> = vec![i64::default(); n];
148 res.decode_vec_i64(base2k, 0, ct_size * base2k, &mut have);
149 let scale: f64 = (1 << (res.size() * base2k - log_scale)) as f64;
150 izip!(want.iter(), have.iter()).enumerate().for_each(|(i, (a, b))| {
151 println!("{}: {} {}", i, a, (*b as f64) / scale);
152 });
153}