From 25841d04938ccbdf93e0ffe18c1a24e01f951d6d Mon Sep 17 00:00:00 2001 From: Sam Hadow Date: Wed, 21 May 2025 16:37:33 +0200 Subject: [PATCH] bootstrap key generation --- src/bootstrapping.rs | 196 +++++++++++++++++++++++++++++++------------ src/utils.rs | 9 ++ 2 files changed, 152 insertions(+), 53 deletions(-) diff --git a/src/bootstrapping.rs b/src/bootstrapping.rs index 5a14da0..b47577d 100644 --- a/src/bootstrapping.rs +++ b/src/bootstrapping.rs @@ -1,78 +1,168 @@ -use crate::dghv::decrypt_bit; -use crate::dghv_asym::{encrypt_bit_asym, PrivateKey, PublicKey}; -use rug::Integer; +use crate::dghv_asym::{encrypt_bit_asym, PublicKey}; +use crate::utils::generate_random_integer_range; +use rand::prelude::SliceRandom; +use rand::rngs::StdRng; +use rand::SeedableRng; +use rug::{Integer, Rational}; -/// Bootstrap key, encrypted bits of the private key +/// Bootstrap key, hint vector and encrypted bits of the private key pub struct BootstrapKey { - pub encrypted_bits: Vec, + pub y: Vec, + pub enc_s: Vec, } -/// Generates bootstrap key -/// bootstrap key is a Vec with each bit of the private key encrypted with the public key -pub fn generate_bootstrap_key(pk: &PublicKey, sk: &Integer, rho: u32) -> BootstrapKey { - let eta = sk.significant_bits(); - let bits = get_bits(sk, eta); - let encrypted_bits: Vec = bits - .into_iter() - .map(|bit| encrypt_bit_asym(bit, pk, rho)) - .collect(); - - BootstrapKey { encrypted_bits } -} - -/// Evaluates the decryption circuit to refresh a ciphertext (lower the noise level) -pub fn bootstrap( - ciphertext: &Integer, - bk: &BootstrapKey, +/// Generates a bootstrap key +/// pk: public key +/// sk: secret integer p +/// kappa: precision parameter (bits) for y_i +/// theta: Hamming weight of the secret support +/// n_theta: total number of hint elements +/// rho: noise parameter (encryption) +pub fn generate_bootstrap_key( pk: &PublicKey, + sk: &Integer, + kappa: u32, + theta: usize, + n_theta: usize, rho: u32, - sk: &PrivateKey, -) -> Integer { - // TODO: actual implementation - let m = decrypt_bit(ciphertext, &sk.p); - encrypt_bit_asym(m, pk, rho) +) -> BootstrapKey { + // xp = ⌊2^κ / p⌋ + let two_k = Integer::from(1) << kappa; + let xp: Integer = &two_k / sk.clone(); + + // support vector s, Hamming weight theta + let mut rng = StdRng::from_os_rng(); + let mut indices: Vec = (0..n_theta).collect(); + indices.shuffle(&mut rng); + let s: Vec = indices.iter().cloned().take(theta).collect(); + + let mut s_bits = vec![0u8; n_theta]; + for &i in &s { + s_bits[i] = 1; + } + + // u[i] = xp (mod 2^(k+1)) + let mut u = vec![Integer::from(0); n_theta]; + let two_k1 = Integer::from(1) << (kappa + 1); + for i in 0..n_theta { + if !s.contains(&i) { + u[i] = generate_random_integer_range(0, kappa + 1); + } + } + if theta > 0 { + for &i in s.iter().take(theta - 1) { + u[i] = generate_random_integer_range(0, kappa + 1); + } + + let mut sum_s_minus_last = Integer::from(0); + for &i in s.iter().take(theta - 1) { + sum_s_minus_last += &u[i]; + } + + let s_last = s[theta - 1]; + let diff = xp.clone() - &sum_s_minus_last; + let mut remainder = diff % &two_k1; + if remainder < 0 { + remainder += &two_k1; + } + u[s_last] = remainder; + } + + // y[i] = u[i] / 2^κ + let y: Vec = u.iter().map(|ui| Rational::from(ui) / Rational::from(&two_k)).collect(); + let enc_s: Vec = s_bits.iter().map(|&bit| encrypt_bit_asym(bit, &pk, rho)).collect(); + + BootstrapKey { y, enc_s } } -// extract bits from an Integer -fn get_bits(n: &Integer, num_bits: u32) -> Vec { - (0..num_bits).map(|i| n.get_bit(i) as u8).collect() -} + +// /// Evaluates the decryption circuit to refresh a ciphertext (lower the noise level) +// pub fn bootstrap( +// ciphertext: &Integer, +// bk: &BootstrapKey, +// pk: &PublicKey, +// rho: u32, +// sk: &PrivateKey, +// ) -> Integer { +// // TODO: actual implementation +// let m = decrypt_bit(ciphertext, &sk.p); +// encrypt_bit_asym(m, pk, rho) +// } #[cfg(test)] mod tests { use super::*; use crate::dghv_asym::generate_keys; + use crate::dghv::decrypt_bit; #[test] fn test_bootstrap_key_generation() { let eta = 1024; - let gamma = 10000; - let rho = 64; + let rho = 10; let theta = 10; - let (sk, pk) = generate_keys(gamma, eta, rho, theta); - let bk = generate_bootstrap_key(&pk, &sk.p, rho); + let n_theta = 50; + let kappa = 100; - assert_eq!(bk.encrypted_bits.len(), eta as usize); - for bit in bk.encrypted_bits { - assert!(bit >= Integer::from(0)); - assert!(bit < pk.xs[0]); + let (sk, pk) = generate_keys(rho, eta, rho, theta); + let bootstrap_key = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, rho); + + let y = bootstrap_key.y; + let enc_s = bootstrap_key.enc_s; + + let s_bits: Vec = enc_s.iter().map(|c| { + let m = decrypt_bit(c, &sk.p); + assert!(m == 0 || m == 1, "Decrypted bit must be 0 or 1"); + m + }).collect(); + let hamming_weight = s_bits.iter().filter(|&&b| b == 1).count(); + assert_eq!(hamming_weight, theta, "Hamming weight of s should be {}", theta); + assert_eq!(s_bits.len(), n_theta, "s_bits length should be {}", n_theta); + + let two_k = Integer::from(1) << kappa; + let two_k1 = Integer::from(1) << (kappa + 1); + let xp = two_k.clone() / &sk.p; + let u: Vec = y.iter().map(|yi| Integer::from((yi.clone() * &two_k).floor_ref())).collect(); + + let mut sum_u_s = Integer::from(0); + for (i, &bit) in s_bits.iter().enumerate() { + if bit == 1 { + sum_u_s += &u[i]; + } } - } - #[test] - fn test_bootstrapping() { - let eta = 1024; - let gamma = 10000; - let rho = 64; - let theta = 10; - let (sk, pk) = generate_keys(gamma, eta, rho, theta); - let bk = generate_bootstrap_key(&pk, &sk.p, rho); + let diff = (sum_u_s.clone() - &xp) % &two_k1; + assert_eq!(diff, 0, "Sum of u_i over S should equal xp mod 2^(κ+1)"); - let m = 1; - let c = encrypt_bit_asym(m, &pk, rho); - let bootstrapped_c = bootstrap(&c, &bk, &pk, rho, &sk); - let decrypted_m = decrypt_bit(&bootstrapped_c, &sk.p); + for ui in &u { + assert!(ui >= &0, "u_i should be non-negative"); + assert!(ui < &two_k1, "u_i should be less than 2^(κ+1)"); + } - assert_eq!(decrypted_m, m); + for (i, yi) in y.iter().enumerate() { + let expected_yi = Rational::from(&u[i]) / Rational::from(&two_k); + assert_eq!(yi, &expected_yi, "y[{}] should equal u[{}] / 2^κ", i, i); + assert!(yi < &Rational::from(2), "y[{}] should be less than 2", i); + } + + assert_eq!(enc_s.len(), n_theta, "enc_s length should be {}", n_theta); + + let mut sum_y_s = Rational::from(0); + for (i, &bit) in s_bits.iter().enumerate() { + if bit == 1 { + sum_y_s += &y[i]; + } + } + + let xp_rational = Rational::from((&xp, two_k.clone())); + let one_over_p = Rational::from(1) / Rational::from(&sk.p); + let delta_p = xp_rational - one_over_p; + let bound = Rational::from(1) / Rational::from(two_k); + + assert!( + delta_p.clone().abs() < bound, + "Error |Δp| = {} should be < 2^(-κ) = {}", + delta_p, + bound + ); } } diff --git a/src/utils.rs b/src/utils.rs index 91effc4..df80f6a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,7 @@ use rand::rngs::StdRng; use rand::Rng; use rand::SeedableRng; +use rug::rand::RandState; use rug::Integer; pub fn generate_random_odd_integer(num_bits: u32) -> Integer { @@ -24,3 +25,11 @@ pub fn generate_random_integer(num_bits: u32) -> Integer { } x } + +pub fn generate_random_integer_range(start: u32, finish: u32) -> Integer { + assert!(finish > start, "finish must be greater than start"); + let k = finish - start; + let mut rand = RandState::new(); + let i = Integer::from(Integer::random_bits(k, &mut rand)) << start; + i +}