Skip to main content

simple_bfv/
scheme.rs

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    //Utils :
13
14    //just return the noise threshold, knowing it's around Delta/2
15    pub fn noise_threshold(&self) -> u64 {
16        let delta = self.params.q / self.t;
17        delta / 2
18    }
19
20    /*Estimate noise magnitude after decryption.
21    # Warning
22    This method reveals information about the secret key and plaintext (obviously, because you HAVE to give the secret key).
23    Use ONLY for educational debugging in trusted environments. 
24     */
25    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    //The key generation. For informations about math formula, please see /docs/simple-bfv
48
49    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    //Encryption & Decryption
75
76
77    pub fn encrypt(&self, message: &BFVPlaintext, public_a: &Polynomial, public_b: &Polynomial) -> BFVCiphertext { //The encryption. Same, you'll find the explanation in /docs/simple-bfv
78        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 { //The backend decryption. Same, you'll find the explanation at /docs/simple-bfv
122        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 { //The decryption. Same, you'll find the explanation at /docs/simple-bfv
159        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    //Homomorphic operations
195
196    pub fn sum_ciphertexts(&self, first_cipher: BFVCiphertext, second_cipher: BFVCiphertext) -> BFVCiphertext { //Function to sum two ciphertexts
197        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 { //Function to sum a ciphertext and a plaintext
207        let params = &self.params;
208        let delta = params.q / self.t; 
209        let delta_m = plaintext.plain.scale(params, delta); //We first need to scale the plaintext coeffs with delta
210        let new_c0 = ciphertext.c0.sum(params, &delta_m); //Then we can sum, but only on c0, not on c1
211        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 { //Function for ciphertext * plaintext
217        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