1use crate::{BFV, plaintext::BFVPlaintext};
2use simple_ring::{Polynomial, generate_cbd_sample, generate_uniform_polynomial, generate_small_sample, RingParams};
3
4#[derive(Debug, Clone)]
5pub struct BFVCiphertext {
6 pub c0: Polynomial,
7 pub c1: Polynomial,
8}
9
10
11impl BFV {
12 pub fn noise_threshold(&self) -> u64 {
16 let delta = self.params.q / self.t;
17 delta / 2
18 }
19
20 pub fn estimate_noise(&self, secret_key: &Polynomial, params: &RingParams, ciphertext: &BFVCiphertext) -> u64 {
26 let c1s = ciphertext.c1.mul_ntt(params, &self.ntt_precalculated, secret_key);
27 let m_prime = ciphertext.c0.sub(params, &c1s);
28
29 let mut max_noise = 0u64;
30 let q = params.q as i128;
31 let delta = (params.q / self.t) as i128;
32
33 for &coeff in m_prime.coeffs.iter() {
34 let centered = if coeff as i128 > q / 2 {
35 coeff as i128 - q
36 } else {
37 coeff as i128
38 };
39 let remainder = centered.abs() % delta;
40 let noise = remainder.min(delta - remainder) as u64;
41 if noise > max_noise { max_noise = noise; }
42 }
43 max_noise
44 }
45
46
47 pub fn generate_public_b(&self, a: &Polynomial, s: &Polynomial) -> Polynomial {
50 let params = &self.params;
51 let ntt_tables = &self.ntt_precalculated;
52 let e = generate_cbd_sample(params.n, self.eta);
53 let e = e.to_poly(params.q);
54 let mul = a.mul_ntt(params, ntt_tables, &s);
55 let b = mul.sum(params, &e);
56
57 b
58 }
59
60
61 pub fn generate_public_a(&self) -> Polynomial {
62 let params = &self.params;
63 generate_uniform_polynomial(params)
64 }
65
66
67 pub fn generate_secret_key(&self) -> Polynomial {
68 let params = &self.params;
69 let s = generate_small_sample(params);
70 s.to_poly(params.q)
71 }
72
73
74 pub fn encrypt(&self, message: &BFVPlaintext, public_a: &Polynomial, public_b: &Polynomial) -> BFVCiphertext { let params = &self.params;
79 assert_eq!(
80 message.plain.coeffs.len(),
81 params.n,
82 "Plaintext degree mismatch: expected {}, got {}",
83 params.n, message.plain.coeffs.len()
84 );
85
86 assert_eq!(
87 public_a.coeffs.len(), params.n,
88 "Public key a(x) degree mismatch"
89 );
90 assert_eq!(
91 public_b.coeffs.len(), params.n,
92 "Public key b(x) degree mismatch"
93 );
94
95 let ntt_tables = &self.ntt_precalculated;
96
97 let u = generate_small_sample(params);
98 let u = u.to_poly(params.q);
99
100 let e1 = generate_cbd_sample(params.n, self.eta);
101 let e1 = e1.to_poly(params.q);
102
103 let e2 = generate_cbd_sample(params.n, self.eta);
104 let e2 = e2.to_poly(params.q);
105
106 let delta = params.q / self.t;
107
108 let bu = public_b.mul_ntt(params, ntt_tables, &u);
109 let delta_m = message.plain.scale(params, delta);
110 let c0_temp = bu.sum(params, &e1);
111 let c0 = c0_temp.sum(params, &delta_m);
112
113 let au = public_a.mul_ntt(params, ntt_tables, &u);
114 let c1 = au.sum(params, &e2);
115
116 BFVCiphertext { c0, c1 }
117 }
118
119
120
121 pub fn backend_decrypt(&self, ciphertext: &BFVCiphertext, secret_key: &Polynomial) -> Polynomial { let params = &self.params;
123 assert_eq!(
124 ciphertext.c0.coeffs.len(), params.n,
125 "Ciphertext c0 degree mismatch"
126 );
127 assert_eq!(
128 ciphertext.c1.coeffs.len(), params.n,
129 "Ciphertext c1 degree mismatch"
130 );
131 assert_eq!(
132 secret_key.coeffs.len(), params.n,
133 "Secret key degree mismatch"
134 );
135 let ntt_tables = &self.ntt_precalculated;
136 let c1s = ciphertext.c1.mul_ntt(params, ntt_tables, secret_key);
137 let m_prime = ciphertext.c0.sub(params, &c1s);
138
139 let mut coeffs = vec![0u64; params.n];
140 let q = params.q as i128;
141 let t = self.t as i128;
142 for i in 0..params.n {
143 let raw = m_prime.coeffs[i] as i128;
144
145
146 let val = if raw > q / 2 { raw - q } else { raw };
147
148 let decoded = ((val * t + q / 2).div_euclid(q) % t + t) % t;
149
150 coeffs[i] = decoded as u64;
151 }
152
153
154 Polynomial::new(coeffs)
155 }
156
157
158 pub fn decrypt(&self, ciphertext: &BFVCiphertext, secret_key: &Polynomial) -> String { let params = &self.params;
160 assert_eq!(
161 ciphertext.c0.coeffs.len(), params.n,
162 "Ciphertext c0 degree mismatch"
163 );
164 assert_eq!(
165 ciphertext.c1.coeffs.len(), params.n,
166 "Ciphertext c1 degree mismatch"
167 );
168 assert_eq!(
169 secret_key.coeffs.len(), params.n,
170 "Secret key degree mismatch"
171 );
172 let ntt_tables = &self.ntt_precalculated;
173 let c1s = ciphertext.c1.mul_ntt(params, ntt_tables, secret_key);
174 let m_prime = ciphertext.c0.sub(params, &c1s);
175
176 let mut coeffs = vec![0u64; params.n];
177 let q = params.q as i128;
178 let t = self.t as i128;
179 for i in 0..params.n {
180 let raw = m_prime.coeffs[i] as i128;
181
182
183 let val = if raw > q / 2 { raw - q } else { raw };
184
185 let decoded = ((val * t + q / 2).div_euclid(q) % t + t) % t;
186
187 coeffs[i] = decoded as u64;
188 }
189
190
191 BFVPlaintext { plain: Polynomial::new(coeffs), len: params.n }.decode()
192 }
193
194 pub fn sum_ciphertexts(&self, first_cipher: BFVCiphertext, second_cipher: BFVCiphertext) -> BFVCiphertext { let params = &self.params;
198 let new_c0 = first_cipher.c0.sum(params, &second_cipher.c0);
199 let new_c1 = first_cipher.c1.sum(params, &second_cipher.c1);
200
201 BFVCiphertext { c0: new_c0, c1: new_c1 }
202 }
203
204
205
206 pub fn sum_ciphertext_and_plaintext(&self, ciphertext: &BFVCiphertext, plaintext: &BFVPlaintext) -> BFVCiphertext { let params = &self.params;
208 let delta = params.q / self.t;
209 let delta_m = plaintext.plain.scale(params, delta); let new_c0 = ciphertext.c0.sum(params, &delta_m); let new_c1 = ciphertext.c1.clone();
212
213 BFVCiphertext { c0: new_c0, c1: new_c1 }
214 }
215
216 pub fn mul_ciphertext_and_plaintext(&self, ciphertext: &BFVCiphertext, plaintext: &BFVPlaintext) -> BFVCiphertext { let params = &self.params;
218 let ntt_tables = &self.ntt_precalculated;
219 let new_c0 = ciphertext.c0.mul_ntt(params, ntt_tables, &plaintext.plain);
220 let new_c1 = ciphertext.c1.mul_ntt(params, ntt_tables, &plaintext.plain);
221
222 BFVCiphertext { c0: new_c0, c1: new_c1 }
223 }
224
225
226
227}
228
229
230
231
232
233
234
235
236
237