fhe-study/arith/src/ntt_u64.rs
arith/src/ntt_u64.rsRaw
1//! This file implements the wrapper on top of the ntt.rs to be able to compute
2//! the NTT for non-prime modulus, specifically for modulus 2^64 (for u64).
3
4use crate::ntt::NTT as NTT_p;
5
6// const P0: u64 = 17293822569241362433;
7
8// const P0: u64 = 4611686018427387905;
9// const P1: u64 = 4611686018326724609;
10const P0: u64 = 8070449433331580929;
11const P1: u64 = 8070450532384645121;
12// const P0: u64 = (1 << 60) - (1 << 28) + 1;
13// const P1: u64 = (1 << 60) - (1 << 27) + 1;
14// const P2: u64 = (1 << 60) - (1 << 26) + 1;
15
16#[derive(Debug)]
17pub struct NTT {}
18
19impl NTT {
20 pub fn ntt(
21 n: usize,
22 a: &Vec<u64>,
23 ) -> (
24 Vec<u64>,
25 Vec<u64>,
26 // Vec<u64>,
27 // Vec<u64>,
28 // Vec<u64>,
29 // Vec<u64>,
30 // Vec<u64>,
31 ) {
32 // TODO ensure that: a_i <P0
33
34 // apply modulus p_i
35 let a_0: Vec<u64> = a.iter().map(|a_i| (a_i % P0 + P0) % P0).collect();
36 let a_1: Vec<u64> = a.iter().map(|a_i| (a_i % P1 + P1) % P1).collect();
37 // let a_2: Vec<u64> = a.iter().map(|a_i| (a_i % P2 + P2) % P2).collect();
38 // let a_3: Vec<u64> = a.iter().map(|a_i| (a_i % P3 + P3) % P3).collect();
39 // let a_4: Vec<u64> = a.iter().map(|a_i| (a_i % P4 + P4) % P4).collect();
40 // let a_5: Vec<u64> = a.iter().map(|a_i| (a_i % P5 + P5) % P5).collect();
41 // let a_6: Vec<u64> = a.iter().map(|a_i| (a_i % P6 + P6) % P6).collect();
42
43 let r_0 = NTT_p::ntt(P0, n, &a_0);
44 let r_1 = NTT_p::ntt(P1, n, &a_1);
45 // let r_2 = NTT_p::ntt(P2, n, &a_2);
46 // let r_3 = NTT_p::ntt(P3, n, &a_3);
47 // let r_4 = NTT_p::ntt(P4, n, &a_4);
48 // let r_5 = NTT_p::ntt(P5, n, &a_5);
49 // let r_6 = NTT_p::ntt(P6, n, &a_6);
50
51 (r_0, r_1) //, r_2) //, r_3, r_4) //, r_5, r_6)
52 }
53
54 pub fn intt(
55 n: usize,
56 r: &(
57 Vec<u64>,
58 Vec<u64>,
59 // Vec<u64>,
60 // Vec<u64>,
61 // Vec<u64>,
62 // Vec<u64>,
63 // Vec<u64>,
64 ),
65 ) -> Vec<u64> {
66 let a_0 = NTT_p::intt(P0, n, &r.0);
67 let a_1 = NTT_p::intt(P1, n, &r.1);
68 // let a_2 = NTT_p::intt(P2, n, &r.2);
69 // let a_3 = NTT_p::intt(P3, n, &r.3);
70 // let a_4 = NTT_p::intt(P4, n, &r.4);
71 // let a_5 = NTT_p::intt(P5, n, &r.5);
72 // let a_6 = NTT_p::intt(P6, n, &r.6);
73
74 // Garner CRT for two moduli: combine (r1 mod p1, r2 mod p2) -> Z/(p1*p2)
75 // let inv_p1_mod_p2: u128 = inv_mod_u64(p1 % p2, p2) as u128;
76 // const INV_P1_MOD_P2: u128 = 4895217125691974194;
77
78 reconstruct(a_0, a_1) //, a_2) // , a_3, a_4) //, a_5, a_6)
79 }
80}
81
82fn reconstruct(
83 a0: Vec<u64>,
84 a1: Vec<u64>,
85 // a2: Vec<u64>,
86 // a_3: Vec<u64>,
87 // a_4: Vec<u64>,
88 // a_5: Vec<u64>,
89 // a_6: Vec<u64>,
90) -> Vec<u64> {
91 // let Q = P0 as u128 * P1 as u128;
92
93 // y_i = q/q_i
94 let y0 = ((u64::MAX as u128 + 1) / P0 as u128);
95 let y1 = ((u64::MAX as u128 + 1) / P1 as u128);
96 // let y2 = ((u64::MAX as u128 + 1) / P2 as u128) as u64;
97 // let y0: u128 = P1 as u128 * P2 as u128; // N_i =Q/P0 = P1*P2
98 // let y1: u128 = P0 as u128 * P2 as u128;
99 // let y2: u128 = P0 as u128 * P1 as u128;
100 // let y0 = (Q / P0 as u128) as u64;
101 // let y1 = (Q / P1 as u128) as u64;
102 // let y2 = ((u64::MAX as u128 + 1) / P2 as u128) as u64;
103 // let y3 = ((u64::MAX as u128 + 1) / P3 as u128) as u64;
104 // let y4 = ((u64::MAX as u128 + 1) / P4 as u128) as u64;
105
106 // y_i^-1 mod q_i = z_i
107 let z0: u128 = inv_mod(P0 as u128, y0); // M_i = N_i^-1 mod q_i
108 let z1: u128 = inv_mod(P1 as u128, y1);
109 // let z2: u128 = inv_mod(P2 as u128, y2);
110 // let y2_inv = inv_mod(P2 as u128, y2);
111 // let y3_inv = inv_mod(P3 as u128, y3);
112 // let y4_inv = inv_mod(P4 as u128, y4);
113
114 // m1 = q1^-1 mod q2
115 // aux = (a2 - a1) * m1 mod q2
116 // a = a1 + (q1 * m1) * aux
117
118 let m1 = inv_mod(P1 as u128, P0 as u128) as u64; // P0^-1 mod P1
119 let aux: Vec<u64> = itertools::zip_eq(a0.clone(), a1.clone())
120 .map(|(a0_i, a1_i)| ((a1_i - a0_i) * m1) % P1)
121 .collect();
122 let a: Vec<u64> = itertools::zip_eq(a0, aux)
123 // .map(|(a1_i, aux_i)| a1_i + (P1 * m1) * aux_i)
124 // .map(|(a0_i, aux_i)| a0_i + (P0 * m1) * aux_i)
125 .map(|(a0_i, aux_i)| a0_i + ((P0 * m1) % P1) * aux_i)
126 .collect();
127 a
128 // dbg!(a0[0] as u128);
129 // dbg!(a0[0] as u128 * y0);
130 // dbg!(a0[0] as u128 * z0);
131 // dbg!(a0[0] as u128 * y0 * z0);
132 // let a: Vec<u128> = itertools::multizip((a0, a1, a2))
133 // .map(|(a0_i, a1_i, a2_i)| {
134 // a0_i as u128 * y0 * z0 + a1_i as u128 * y1 * z1 + a2_i as u128 * y2 * z2
135 // })
136 // .collect();
137 // dbg!(&a);
138 // let Q = y2 * P2 as u128;
139 // let a: Vec<u128> = a.iter().map(|a_i| a_i % Q).collect();
140 // dbg!(&a);
141 // let q64 = 1_u128 << 64;
142 // let a: Vec<u64> = a.iter().map(|a_i| (a_i % q64) as u64).collect();
143 // a
144
145 /*
146 // x_i*z_i mod q_i
147 let r0: Vec<u64> = a_0.iter().map(|a_i| ((a_i * z0) % P0) * y0).collect();
148 let r1: Vec<u64> = a_1.iter().map(|a_i| ((a_i * z1) % P1) * y1).collect();
149 // let r0: Vec<u64> = a_0.iter().map(|a_i| ((a_i * z0) % P0) * y0).collect();
150 // let r1: Vec<u64> = a_1.iter().map(|a_i| ((a_i * z1) % P1) * y1).collect();
151 // let r2: Vec<u64> = a_2.iter().map(|a_i| ((a_i * y2_inv) % P2) * y2).collect();
152 // let r3: Vec<u64> = a_3.iter().map(|a_i| ((a_i * y3_inv) % P3) * y3).collect();
153 // let r4: Vec<u64> = a_4.iter().map(|a_i| ((a_i * y4_inv) % P4) * y4).collect();
154
155 let r: Vec<u64> = itertools::multizip((r0.iter(), r1.iter()))
156 .map(|(a, b)| a + b)
157 .collect();
158 // let r = r0;
159 //
160 dbg!(&r);
161
162 let p1p2: u128 = (P0 as u128) * (P1 as u128);
163 // let p1p2_inv: u128 = inv_mod((P0 % P1) as u128, P1) as u128;
164 let p1p2_inv: u128 = inv_mod((P0) as u128, P1) as u128;
165 dbg!(&p1p2);
166 dbg!(&p1p2_inv);
167 // let p1p2: u128 = P0 as u128 / 2; // PIHALF
168 let r = r
169 .iter()
170 .map(|c_i_u64| {
171 let c_i = *c_i_u64 as u128;
172 if c_i * 2 >= p1p2 {
173 // if c_i >= p1p2 {
174 c_i.wrapping_sub(p1p2) as u64
175 } else {
176 c_i as u64
177 }
178 })
179 .collect();
180 // let r: Vec<u64> = itertools::multizip((r0.iter(), r1.iter(), r2.iter(), r3.iter(), r4.iter()))
181 // .map(|(a, b, c, d, e)| a + b + c + d + e)
182 // .collect();
183 // let mut r = a_0 + y0_inv + a_1 * y1_inv + a_2 * y2_inv + a_3 * y3_inv + a_4 * y4_inv;
184
185 r
186 */
187}
188
189fn exp_mod(q: u128, x: u128, k: u128) -> u128 {
190 // work on u128 to avoid overflow
191 let mut r = 1u128;
192 let mut x = x.clone();
193 let mut k = k.clone();
194 x = x % q;
195 // exponentiation by square strategy
196 while k > 0 {
197 if k % 2 == 1 {
198 r = (r * x) % q;
199 }
200 x = (x * x) % q;
201 k /= 2;
202 }
203 r
204}
205/// returns x^-1 mod Q
206fn inv_mod(q: u128, x: u128) -> u128 {
207 // by Fermat's Little Theorem, x^-1 mod q \equiv x^{q-2} mod q
208 exp_mod(q, x, q - 2)
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214 use rand_distr::Distribution;
215
216 use anyhow::Result;
217
218 #[test]
219 fn test_dbg() -> Result<()> {
220 println!("{}", 1u128 << 64);
221 let n: usize = 16;
222
223 println!("{}", P0);
224 println!("{}", P1);
225 // let q = 1u128 << 64;
226 // assert!(P0 as u128 * P1 as u128 > (n as u128 * (q * q)) / 2);
227
228 // let a: Vec<u64> = vec![1u64, 2, 3, 4];
229 // let a: Vec<u64> = vec![1u64, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
230 // let a: Vec<u64> = vec![9u64, 8, 7, 6, 0, 9999, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9];
231 use rand::Rng;
232 let mut rng = rand::thread_rng();
233 let a: Vec<u64> = (0..n)
234 // .map(|_| rng.gen_range(0..=(1u64 << 57) - 1) - (1u64 << 56))
235 .map(|_| rng.gen_range(0..(1 << 61)))
236 .collect();
237
238 dbg!(a.len());
239
240 let a_ntt = NTT::ntt(n, &a);
241 dbg!(&a_ntt);
242
243 let a_intt = NTT::intt(n, &a_ntt);
244
245 dbg!(&a_intt);
246 assert_eq!(a_intt, a);
247
248 // unnecessary:
249 // let a: Vec<u64> = vec![2u64, 4, 6, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
250 // let a_ntt = NTT_p::ntt(P0, n, &a);
251 // dbg!(&a_ntt);
252 // let a_intt = NTT_p::intt(P0, n, &a_ntt);
253 // dbg!(&a_intt);
254
255 // NOTE: *n_inv is already done in the intt method.
256
257 // Multiplies the values by the inverse of the polynomial modulo the NTT modulus
258 // let n_inv = inv_mod(P0 as u128, n as u64); // n^-1 mod p0
259 // let a_new: Vec<u64> = a_0_intt
260 // .iter()
261 // .map(|a_i| ((*a_i as u128 * n_inv as u128) % P0 as u128) as u64)
262 // .collect();
263
264 // assert_eq!(a_intt, a);
265
266 Ok(())
267 }
268}