1#![forbid(unsafe_code)]
2mod scheme;
3mod config;
4mod plaintext;
5mod find_parameters;
6
7pub use crate::config::BFV as BFV;
8pub use crate::plaintext::BFVPlaintext;
9pub use crate::find_parameters::find_valid_q as find_valid_q;
10
11#[cfg(feature = "parallel")]
12use rayon::prelude::*;
13
14pub const VERSION: &str = env!("CARGO_PKG_VERSION");
15
16
17
18
19#[cfg(test)]
23use std::time::*;
24
25#[cfg(test)]
26fn generate_keys(bfv: &BFV) -> (simple_ring::Polynomial, simple_ring::Polynomial, simple_ring::Polynomial) {
28 let s = bfv.generate_secret_key();
29 let a = bfv.generate_public_a();
30 let b = bfv.generate_public_b(&a, &s);
31 (a, b, s)
32}
33
34#[cfg(test)]
35fn assert_plaintext_eq(got: &simple_ring::Polynomial, expected: &simple_ring::Polynomial, n_check: usize) {
37 for i in 0..n_check.min(got.coeffs.len()) {
38 assert_eq!(
39 got.coeffs[i], expected.coeffs[i],
40 "Coefficient mismatch at index {}: got {}, expected {}",
41 i, got.coeffs[i], expected.coeffs[i]
42 );
43 }
44}
45
46
47#[test]
48fn basic() {
50 let bfv = BFV::for_medium();
51 let (public_a, public_b, secret_s) = generate_keys(&bfv);
52
53 let message = BFVPlaintext::new(
54 "Hello, this is BFV scheme. This basic test is here to ensure that we can encrypt special characters, as #{[|@*$ù%§! and others, like µ~'</&œ. If you want to see it totally, use for_large or for_medium.",
55 &bfv
56 );
57
58 let start = Instant::now();
59
60 let ciphertext = bfv.encrypt(&message, &public_a, &public_b);
61 let decrypted = bfv.decrypt(&ciphertext, &secret_s);
62
63 println!("Recovered: {}", decrypted);
64 println!("Elapsed: {:?}", start.elapsed());
65
66 assert_eq!(
67 bfv.backend_decrypt(&ciphertext, &secret_s).coeffs,
68 message.plain.coeffs,
69 "Decryption mismatch"
70 );
71}
72
73
74#[test]
77fn test_cipher_addition() {
78 let bfv = BFV::for_test();
79 let (pk_a, pk_b, sk) = generate_keys(&bfv);
80 let n = bfv.params.n;
81
82 let s1 = String::from_utf8(vec![126u8; n.min(50)]).unwrap();
83 let s2 = String::from_utf8(vec![126u8; n.min(50)]).unwrap();
84
85 let pt1 = BFVPlaintext::new(&s1, &bfv);
86 let pt2 = BFVPlaintext::new(&s2, &bfv);
87
88 let ct1 = bfv.encrypt(&pt1, &pk_a, &pk_b);
89 let ct2 = bfv.encrypt(&pt2, &pk_a, &pk_b);
90
91 let ct_sum = bfv.sum_ciphertexts(ct1, ct2);
92 let result = bfv.backend_decrypt(&ct_sum, &sk);
93
94 for i in 0..n.min(50) {
95 assert_eq!(result.coeffs[i], 252, "Addition failed at index {}", i);
96 }
97
98 let s3 = String::from_utf8(vec![64u8; 50]).unwrap();
99 let pt3 = BFVPlaintext::new(&s3, &bfv);
100 let pt4 = BFVPlaintext::new(&s3, &bfv);
101
102 let ct3 = bfv.encrypt(&pt3, &pk_a, &pk_b);
103 let ct4 = bfv.encrypt(&pt4, &pk_a, &pk_b);
104
105 let ct_sum2 = bfv.sum_ciphertexts(ct3, ct4);
106 let result2 = bfv.backend_decrypt(&ct_sum2, &sk);
107
108 for i in 0..50 {
109 assert_eq!(result2.coeffs[i], 128, "Addition failed at index {}", i);
110 }
111}
112
113#[test]
114fn test_add_cipher_plain() {
115 let bfv = BFV::for_test();
116 let (pk_a, pk_b, sk) = generate_keys(&bfv);
117 let n = bfv.params.n;
118
119 let pt1 = BFVPlaintext::new_from_coeffs(vec![60u64; n], &bfv);
120 let pt2 = BFVPlaintext::new_from_coeffs(vec![30u64; n], &bfv);
121
122 let ct1 = bfv.encrypt(&pt1, &pk_a, &pk_b);
123 let result = bfv.backend_decrypt(
124 &bfv.sum_ciphertext_and_plaintext(&ct1, &pt2),
125 &sk
126 );
127
128 for i in 0..n.min(50) {
129 assert_eq!(result.coeffs[i], 90, "C+P addition failed at index {}", i);
130 }
131}
132
133#[test]
134fn test_mul_cipher_plain() {
135 let bfv = BFV::for_test();
136 let (pk_a, pk_b, sk) = generate_keys(&bfv);
137 let n = bfv.params.n;
138
139 let pt1 = BFVPlaintext::new_from_coeffs(vec![10u64; n], &bfv);
140
141 let mut scalar = vec![0u64; n];
142 scalar[0] = 3;
143 let pt_scalar = BFVPlaintext::new_from_coeffs(scalar, &bfv);
144
145 let ct1 = bfv.encrypt(&pt1, &pk_a, &pk_b);
146
147 let result = bfv.backend_decrypt(
148 &bfv.mul_ciphertext_and_plaintext(&ct1, &pt_scalar),
149 &sk
150 );
151
152 for i in 0..n.min(50) {
153 assert_eq!(result.coeffs[i], 30, "C*P multiplication failed at index {}", i);
154 }
155}
156
157#[test]
158fn test_add_then_mul() {
159 let bfv = BFV::for_test();
160 let (pk_a, pk_b, sk) = generate_keys(&bfv);
161
162 let pt_a = BFVPlaintext::new_from_coeffs(vec![20u64; bfv.params.n], &bfv);
163 let pt_b = BFVPlaintext::new_from_coeffs(vec![10u64; bfv.params.n], &bfv);
164
165 let mut scalar = vec![0u64; bfv.params.n];
166 scalar[0] = 3;
167 let pt_s = BFVPlaintext::new_from_coeffs(scalar, &bfv);
168
169 let ct_sum = bfv.sum_ciphertexts(
170 bfv.encrypt(&pt_a, &pk_a, &pk_b),
171 bfv.encrypt(&pt_b, &pk_a, &pk_b),
172 );
173
174 let ct_res = bfv.mul_ciphertext_and_plaintext(&ct_sum, &pt_s);
175 let result = bfv.backend_decrypt(&ct_res, &sk);
176
177 for i in 0..50 {
178 assert_eq!(result.coeffs[i], 90, "(A+B)*C failed at index {}", i);
179 }
180}
181
182#[test]
183fn test_mul_then_add() {
184 let bfv = BFV::for_test();
185 let (pk_a, pk_b, sk) = generate_keys(&bfv);
186
187 let pt5 = BFVPlaintext::new_from_coeffs(vec![5u64; bfv.params.n], &bfv);
188 let pt7 = BFVPlaintext::new_from_coeffs(vec![7u64; bfv.params.n], &bfv);
189
190 let mut s = vec![0u64; bfv.params.n];
191 s[0] = 4;
192 let pt_s = BFVPlaintext::new_from_coeffs(s, &bfv);
193
194 let ct_mul = bfv.mul_ciphertext_and_plaintext(
195 &bfv.encrypt(&pt5, &pk_a, &pk_b),
196 &pt_s,
197 );
198
199 let ct_res = bfv.sum_ciphertexts(ct_mul, bfv.encrypt(&pt7, &pk_a, &pk_b));
200 let result = bfv.backend_decrypt(&ct_res, &sk);
201
202 for i in 0..50 {
203 assert_eq!(result.coeffs[i], 27, "A*B+C failed at index {}", i);
204 }
205}
206
207#[test]
208fn test_chained_additions() {
209 let bfv = BFV::for_test();
210 let (pk_a, pk_b, sk) = generate_keys(&bfv);
211
212 let pt = BFVPlaintext::new_from_coeffs(vec![1u64; bfv.params.n], &bfv);
213
214 let mut acc = bfv.encrypt(&pt, &pk_a, &pk_b);
215
216 for _ in 1..10 {
217 acc = bfv.sum_ciphertexts(
218 acc,
219 bfv.encrypt(&pt, &pk_a, &pk_b)
220 );
221 }
222
223 let result = bfv.backend_decrypt(&acc, &sk);
224
225 for i in 0..50 {
226 assert_eq!(result.coeffs[i], 10, "Chained addition failed at index {}", i);
227 }
228}
229
230#[test]
231fn test_edge_zero() {
232 let bfv = BFV::for_test();
233 let (pk_a, pk_b, sk) = generate_keys(&bfv);
234
235 let pt_x = BFVPlaintext::new_from_coeffs(vec![128u64; bfv.params.n], &bfv);
236 let pt_z = BFVPlaintext::new_from_coeffs(vec![0u64; bfv.params.n], &bfv);
237
238 let ct = bfv.sum_ciphertexts(
239 bfv.encrypt(&pt_x, &pk_a, &pk_b),
240 bfv.encrypt(&pt_z, &pk_a, &pk_b),
241 );
242
243 let result = bfv.backend_decrypt(&ct, &sk);
244
245 for i in 0..50 {
246 assert_eq!(result.coeffs[i], 128, "Zero addition failed at index {}", i);
247 }
248}
249
250#[test]
251fn test_edge_max_value() {
252 let bfv = BFV::for_test();
253 let (pk_a, pk_b, sk) = generate_keys(&bfv);
254
255 let pt = BFVPlaintext::new_from_coeffs(vec![255u64; bfv.params.n], &bfv);
256 let ct = bfv.encrypt(&pt, &pk_a, &pk_b);
257 let result = bfv.backend_decrypt(&ct, &sk);
258
259 for i in 0..50 {
260 assert_eq!(result.coeffs[i], 255, "Max value decryption failed at index {}", i);
261 }
262}
263
264#[test]
265fn test_edge_add_one_to_max() {
266 let bfv = BFV::for_test();
267 let (pk_a, pk_b, sk) = generate_keys(&bfv);
268
269 let t = bfv.t;
270
271 let pt_max = BFVPlaintext::new_from_coeffs(vec![t - 1; bfv.params.n], &bfv);
272 let pt_one = BFVPlaintext::new_from_coeffs(vec![1u64; bfv.params.n], &bfv);
273
274 let ct = bfv.sum_ciphertexts(
275 bfv.encrypt(&pt_max, &pk_a, &pk_b),
276 bfv.encrypt(&pt_one, &pk_a, &pk_b),
277 );
278
279 let result = bfv.backend_decrypt(&ct, &sk);
280
281 for i in 0..50 {
282 assert_eq!(result.coeffs[i], 0, "Overflow wrap failed at index {}", i);
283 }
284}
285
286#[test]
289fn test_roundtrip_full_coeffs() {
290 let bfv = BFV::for_test();
291 let (pk_a, pk_b, sk) = generate_keys(&bfv);
292 let n = bfv.params.n;
293
294 let coeffs: Vec<u64> = (0..n).map(|i| (i % 200) as u64).collect();
295 let plaintext = BFVPlaintext::new_from_coeffs(coeffs.clone(), &bfv);
296
297 let ciphertext = bfv.encrypt(&plaintext, &pk_a, &pk_b);
298 let recovered = bfv.backend_decrypt(&ciphertext, &sk);
299
300 assert_eq!(recovered.coeffs, plaintext.plain.coeffs, "Full roundtrip failed");
301}
302
303#[test]
304fn test_roundtrip_random_small_values() {
305 let bfv = BFV::for_test();
306 let (pk_a, pk_b, sk) = generate_keys(&bfv);
307 let n = bfv.params.n;
308
309 let mut rng = std::collections::hash_map::DefaultHasher::new();
310 let coeffs: Vec<u64> = (0..n)
311 .map(|i| {
312 use std::hash::{Hash, Hasher};
313 i.hash(&mut rng);
314 (rng.finish() % (bfv.t / 2)) as u64
315 })
316 .collect();
317
318 let plaintext = BFVPlaintext::new_from_coeffs(coeffs.clone(), &bfv);
319 let ciphertext = bfv.encrypt(&plaintext, &pk_a, &pk_b);
320 let recovered = bfv.backend_decrypt(&ciphertext, &sk);
321
322 assert_plaintext_eq(&recovered, &plaintext.plain, n);
323}
324
325#[test]
326fn test_roundtrip_binary_plaintext() {
327 let bfv = BFV::for_test();
328 let (pk_a, pk_b, sk) = generate_keys(&bfv);
329 let n = bfv.params.n;
330
331 let coeffs: Vec<u64> = (0..n).map(|i| (i % 2) as u64).collect();
333 let plaintext = BFVPlaintext::new_from_coeffs(coeffs.clone(), &bfv);
334
335 let ciphertext = bfv.encrypt(&plaintext, &pk_a, &pk_b);
336 let recovered = bfv.backend_decrypt(&ciphertext, &sk);
337
338 assert_eq!(recovered.coeffs, plaintext.plain.coeffs, "Binary roundtrip failed");
339}
340
341#[test]
344fn test_noise_budget_additions() {
345 let bfv = BFV::for_medium();
346 let (pk_a, pk_b, sk) = generate_keys(&bfv);
347 let n = bfv.params.n;
348
349 let pt = BFVPlaintext::new_from_coeffs(vec![1u64; n], &bfv);
350 let mut ct = bfv.encrypt(&pt, &pk_a, &pk_b);
351
352 let mut success_count = 0;
353 for i in 1..=100 {
354 ct = bfv.sum_ciphertexts(ct.clone(), ct.clone());
355 let result = bfv.backend_decrypt(&ct, &sk);
356
357 let expected = (1u64 << i.min(7)) % bfv.t;
358
359 if result.coeffs[0] == expected {
360 success_count = i;
361 } else {
362 println!("Decryption failed after {} additions: got {}, expected {}",
363 i, result.coeffs[0], expected);
364 break;
365 }
366 }
367
368 println!("Successful additions before noise failure: {}", success_count);
369 assert!(success_count >= 5, "Noise budget too small: failed after {} additions", success_count);
370}
371
372#[test]
373fn test_noise_budget_multiplications() {
374 let bfv = BFV::for_large();
375 let (pk_a, pk_b, sk) = generate_keys(&bfv);
376 let n = bfv.params.n;
377
378 let mut m = vec![0u64; n];
379 m[0] = 2;
380 let pt = BFVPlaintext::new_from_coeffs(m, &bfv);
381
382 let mut scalar = vec![0u64; n];
383 scalar[0] = 2;
384 let pt_scalar = BFVPlaintext::new_from_coeffs(scalar, &bfv);
385
386 let mut ct = bfv.encrypt(&pt, &pk_a, &pk_b);
387
388 let mut success_count = 0;
389 for i in 1..=15 {
390 ct = bfv.mul_ciphertext_and_plaintext(&ct, &pt_scalar);
391 let result = bfv.backend_decrypt(&ct, &sk);
392
393 let expected = (2u64.pow(i as u32 + 1)) % bfv.t;
394
395 if result.coeffs[0] == expected {
396 success_count = i;
397 } else {
398 println!("Decryption failed after {} multiplications: got {}, expected {}",
399 i, result.coeffs[0], expected);
400 break;
401 }
402 }
403
404 println!("Successful multiplications before noise failure: {}", success_count);
405 assert!(success_count >= 8, "Failed after {} multiplications", success_count);
406}
407
408#[test]
409fn test_noise_budget_mixed_operations() {
410 let bfv = BFV::for_test();
411 let (pk_a, pk_b, sk) = generate_keys(&bfv);
412 let n = bfv.params.n;
413
414 let pt_base = BFVPlaintext::new_from_coeffs(vec![3u64; n], &bfv);
415 let pt_add = BFVPlaintext::new_from_coeffs(vec![1u64; n], &bfv);
416 let pt_mul = BFVPlaintext::new_from_coeffs(vec![2u64; n], &bfv);
417
418 let mut ct = bfv.encrypt(&pt_base, &pk_a, &pk_b);
419 let mut operations = 0;
420
421 for i in 0..15 {
422 if i % 2 == 0 {
423 ct = bfv.sum_ciphertexts(ct.clone(), bfv.encrypt(&pt_add, &pk_a, &pk_b));
424 } else {
425 ct = bfv.mul_ciphertext_and_plaintext(&ct, &pt_mul);
426 }
427 operations += 1;
428
429 let result = bfv.backend_decrypt(&ct, &sk);
430 assert!(result.coeffs[0] < bfv.t, "Decryption overflow after {} ops", operations);
431 }
432
433 println!("Completed {} mixed operations without decryption failure", operations);
434}
435
436#[test]
439fn test_noise_estimation_growth() {
440 let bfv = BFV::for_test();
441 let (pk_a, pk_b, sk) = generate_keys(&bfv);
442 let n = bfv.params.n;
443
444 let pt = BFVPlaintext::new_from_coeffs(vec![1u64; n], &bfv);
445 let mut ct = bfv.encrypt(&pt, &pk_a, &pk_b);
446
447 let mut noises = Vec::new();
448 for i in 0..15 {
449 let noise = bfv.estimate_noise(&sk, &bfv.params, &ct);
450 noises.push(noise);
451 println!("After {} additions, estimated noise: {}", i, noise);
452 ct = bfv.sum_ciphertexts(ct.clone(), ct.clone());
453 }
454
455 assert!(noises[10] >= noises[0], "Noise should grow with operations");
456}
457
458#[test]
459fn test_noise_vs_delta_threshold() {
460 let bfv = BFV::for_test();
461 let (pk_a, pk_b, sk) = generate_keys(&bfv);
462 let n = bfv.params.n;
463
464 let delta = bfv.params.q / bfv.t;
465 let threshold = delta / 2;
466
467 let pt = BFVPlaintext::new_from_coeffs(vec![1u64; n], &bfv);
468 let mut ct = bfv.encrypt(&pt, &pk_a, &pk_b);
469
470 for i in 0..50 {
471 let noise = bfv.estimate_noise(&sk, &bfv.params, &ct);
472
473 if noise < threshold {
474 let result = bfv.backend_decrypt(&ct, &sk);
476 assert!(result.coeffs[0] < bfv.t, "Decryption failed while noise < threshold");
477 } else {
478 println!("Noise exceeded threshold ({}) after {} additions: {}", threshold, i, noise);
479 break;
480 }
481
482 ct = bfv.sum_ciphertexts(ct.clone(), ct.clone());
483 }
484}
485
486
487#[test]
490#[should_panic(expected = "NTT requires n to be a power of 2, got 1028 ! For explanations, please read '/docs/simple-ring.pdf")]
491fn test_invalid_n_not_power_of_two() {
492 let _ = simple_ring::RingParams::new(1028, 786_433, 1);
493}
494
495#[test]
496fn test_valid_configurations() {
497 BFV::for_test();
498 BFV::for_medium();
499 BFV::for_large();
500}
501
502#[test]
503fn test_parameter_consistency() {
504 let bfv = BFV::for_test();
505
506 assert!(bfv.params.n.is_power_of_two());
507 assert!(bfv.t >= 2 && bfv.t < bfv.params.q);
508 assert!(bfv.eta >= 1 && bfv.eta <= 16);
509
510 assert_eq!(bfv.ntt_precalculated.twiddles.len(), bfv.params.n);
511 assert_eq!(bfv.ntt_precalculated.twiddles_inv.len(), bfv.params.n);
512}
513
514#[test]
517#[ignore] fn test_performance_encrypt_decrypt() {
519 let bfv = BFV::for_large();
520 let (pk_a, pk_b, sk) = generate_keys(&bfv);
521
522 let message = BFVPlaintext::new_from_coeffs(vec![42u64; bfv.params.n], &bfv);
523
524 let start = Instant::now();
525 let ct = bfv.encrypt(&message, &pk_a, &pk_b);
526 let enc_time = start.elapsed();
527
528 let start = Instant::now();
529 let _ = bfv.backend_decrypt(&ct, &sk);
530 let dec_time = start.elapsed();
531
532 println!("Encrypt time (n=4096): {:?}", enc_time);
533 println!("Decrypt time (n=4096): {:?}", dec_time);
534
535 assert!(enc_time.as_millis() < 100, "Encryption too slow: {:?}", enc_time);
537 assert!(dec_time.as_millis() < 100, "Decryption too slow: {:?}", dec_time);
538}
539
540#[test]
541#[ignore]
542fn test_performance_homomorphic_add() {
543 let bfv = BFV::for_large();
544 let (pk_a, pk_b, _sk) = generate_keys(&bfv);
545
546 let pt = BFVPlaintext::new_from_coeffs(vec![1u64; bfv.params.n], &bfv);
547 let ct1 = bfv.encrypt(&pt, &pk_a, &pk_b);
548 let ct2 = bfv.encrypt(&pt, &pk_a, &pk_b);
549
550 let start = Instant::now();
551 let _ = bfv.sum_ciphertexts(ct1, ct2);
552 let add_time = start.elapsed();
553
554 println!("Homomorphic add time (n=4096): {:?}", add_time);
555 assert!(add_time.as_millis() < 50, "Addition too slow: {:?}", add_time);
556}
557
558
559#[test]
562fn test_empty_plaintext() { let bfv = BFV::for_test();
564 let (pk_a, pk_b, sk) = generate_keys(&bfv);
565
566 let pt = BFVPlaintext::new_from_coeffs(vec![0u64; bfv.params.n], &bfv);
567 let ct = bfv.encrypt(&pt, &pk_a, &pk_b);
568 let result = bfv.backend_decrypt(&ct, &sk);
569
570 for i in 0..50 {
571 assert_eq!(result.coeffs[i], 0, "Empty plaintext decryption failed");
572 }
573}
574
575#[test]
576fn test_single_coefficient_nonzero() {
577 let bfv = BFV::for_test();
578 let (pk_a, pk_b, sk) = generate_keys(&bfv);
579 let n = bfv.params.n;
580
581 let mut coeffs = vec![0u64; n];
582 coeffs[n/2] = 123;
583
584 let pt = BFVPlaintext::new_from_coeffs(coeffs.clone(), &bfv);
585 let ct = bfv.encrypt(&pt, &pk_a, &pk_b);
586 let result = bfv.backend_decrypt(&ct, &sk);
587
588 assert_eq!(result.coeffs[n/2], 123, "Single coefficient decryption failed");
589}
590
591#[test]
592fn test_deterministic_encryption_with_same_randomness() {
593 let bfv = BFV::for_test();
596 let (pk_a, pk_b, sk) = generate_keys(&bfv);
597
598 let pt = BFVPlaintext::new_from_coeffs(vec![42u64; bfv.params.n], &bfv);
599
600 let ct1 = bfv.encrypt(&pt, &pk_a, &pk_b);
601 let ct2 = bfv.encrypt(&pt, &pk_a, &pk_b);
602
603 assert_ne!(ct1.c0.coeffs, ct2.c0.coeffs, "Encryption should be probabilistic");
604
605 let res1 = bfv.backend_decrypt(&ct1, &sk);
606 let res2 = bfv.backend_decrypt(&ct2, &sk);
607 assert_eq!(res1.coeffs, res2.coeffs, "Decryption should be deterministic");
608}
609