bootstrap key generation

This commit is contained in:
2025-05-21 16:37:33 +02:00
parent 23a203ad68
commit 25841d0493
2 changed files with 152 additions and 53 deletions

View File

@ -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<Integer>,
pub y: Vec<Rational>,
pub enc_s: Vec<Integer>,
}
/// 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<Integer> = 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<usize> = (0..n_theta).collect();
indices.shuffle(&mut rng);
let s: Vec<usize> = indices.iter().cloned().take(theta).collect();
let mut s_bits = vec![0u8; n_theta];
for &i in &s {
s_bits[i] = 1;
}
// extract bits from an Integer
fn get_bits(n: &Integer, num_bits: u32) -> Vec<u8> {
(0..num_bits).map(|i| n.get_bit(i) as u8).collect()
// 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<Rational> = u.iter().map(|ui| Rational::from(ui) / Rational::from(&two_k)).collect();
let enc_s: Vec<Integer> = s_bits.iter().map(|&bit| encrypt_bit_asym(bit, &pk, rho)).collect();
BootstrapKey { y, enc_s }
}
// /// 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<u8> = 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<Integer> = 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
);
}
}

View File

@ -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
}