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, PublicKey};
use crate::dghv_asym::{encrypt_bit_asym, PrivateKey, PublicKey}; use crate::utils::generate_random_integer_range;
use rug::Integer; 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 struct BootstrapKey {
pub encrypted_bits: Vec<Integer>, pub y: Vec<Rational>,
pub enc_s: Vec<Integer>,
} }
/// Generates bootstrap key /// Generates a bootstrap key
/// bootstrap key is a Vec with each bit of the private key encrypted with the public key /// pk: public key
pub fn generate_bootstrap_key(pk: &PublicKey, sk: &Integer, rho: u32) -> BootstrapKey { /// sk: secret integer p
let eta = sk.significant_bits(); /// kappa: precision parameter (bits) for y_i
let bits = get_bits(sk, eta); /// theta: Hamming weight of the secret support
let encrypted_bits: Vec<Integer> = bits /// n_theta: total number of hint elements
.into_iter() /// rho: noise parameter (encryption)
.map(|bit| encrypt_bit_asym(bit, pk, rho)) pub fn generate_bootstrap_key(
.collect();
BootstrapKey { encrypted_bits }
}
/// Evaluates the decryption circuit to refresh a ciphertext (lower the noise level)
pub fn bootstrap(
ciphertext: &Integer,
bk: &BootstrapKey,
pk: &PublicKey, pk: &PublicKey,
sk: &Integer,
kappa: u32,
theta: usize,
n_theta: usize,
rho: u32, rho: u32,
sk: &PrivateKey, ) -> BootstrapKey {
) -> Integer { // xp = ⌊2^κ / p⌋
// TODO: actual implementation let two_k = Integer::from(1) << kappa;
let m = decrypt_bit(ciphertext, &sk.p); let xp: Integer = &two_k / sk.clone();
encrypt_bit_asym(m, pk, rho)
// 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;
}
// 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 }
} }
// extract bits from an Integer
fn get_bits(n: &Integer, num_bits: u32) -> Vec<u8> { // /// Evaluates the decryption circuit to refresh a ciphertext (lower the noise level)
(0..num_bits).map(|i| n.get_bit(i) as u8).collect() // 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::dghv_asym::generate_keys; use crate::dghv_asym::generate_keys;
use crate::dghv::decrypt_bit;
#[test] #[test]
fn test_bootstrap_key_generation() { fn test_bootstrap_key_generation() {
let eta = 1024; let eta = 1024;
let gamma = 10000; let rho = 10;
let rho = 64;
let theta = 10; let theta = 10;
let (sk, pk) = generate_keys(gamma, eta, rho, theta); let n_theta = 50;
let bk = generate_bootstrap_key(&pk, &sk.p, rho); let kappa = 100;
assert_eq!(bk.encrypted_bits.len(), eta as usize); let (sk, pk) = generate_keys(rho, eta, rho, theta);
for bit in bk.encrypted_bits { let bootstrap_key = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, rho);
assert!(bit >= Integer::from(0));
assert!(bit < pk.xs[0]); 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] let diff = (sum_u_s.clone() - &xp) % &two_k1;
fn test_bootstrapping() { assert_eq!(diff, 0, "Sum of u_i over S should equal xp mod 2^(κ+1)");
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 m = 1; for ui in &u {
let c = encrypt_bit_asym(m, &pk, rho); assert!(ui >= &0, "u_i should be non-negative");
let bootstrapped_c = bootstrap(&c, &bk, &pk, rho, &sk); assert!(ui < &two_k1, "u_i should be less than 2^(κ+1)");
let decrypted_m = decrypt_bit(&bootstrapped_c, &sk.p); }
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::rngs::StdRng;
use rand::Rng; use rand::Rng;
use rand::SeedableRng; use rand::SeedableRng;
use rug::rand::RandState;
use rug::Integer; use rug::Integer;
pub fn generate_random_odd_integer(num_bits: u32) -> 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 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
}