bootstrapping
This commit is contained in:
1 parent
c0d7b57ed7
commit
88d75eee5d
1 file changed
+246
-187
+246
-187
@@ -1,23 +1,48 @@
|
|||||||
|
//! DGHV bootstrapping by squashed decryption.
|
||||||
|
//!
|
||||||
|
//! For a ciphertext c = q*p + e (0 <= e < p/4, p odd prime) the plaintext is
|
||||||
|
//! m = (c mod 2) XOR (round(c/p) mod 2).
|
||||||
|
//! The secret 1/p is hidden as a sparse subset sum of public reals y_i in [0,2):
|
||||||
|
//! sum_{i in S} y_i = 1/p (mod 2), |S| = theta,
|
||||||
|
//! so that round(c/p) mod 2 = round(sum_{i in S} z_i) mod 2 with z_i = [c*y_i]_2 kept to n fractional bits. The z_i are public once c is known; only the selector bits s_i are secret, and they are given as ciphertexts. The sum is evaluated homomorphically with a carry-save column circuit built from elementary symmetric polynomials (the Hamming weight of a column is read off the bits of e_1, e_2, e_4, ... mod 2).
|
||||||
|
|
||||||
use crate::dghv_asym::{encrypt_bit_asym, PublicKey};
|
use crate::dghv_asym::{encrypt_bit_asym, PublicKey};
|
||||||
use crate::utils::generate_random_integer_range;
|
use crate::utils::generate_random_integer_range;
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::SeedableRng;
|
use rand::SeedableRng;
|
||||||
use rug::{Integer, Rational};
|
use rug::ops::RemRounding;
|
||||||
|
use rug::Integer;
|
||||||
|
|
||||||
/// Bootstrap key, hint vector and encrypted bits of the private key
|
/// Bootstrap key: hint vector and encrypted bits of the secret subset.
|
||||||
pub struct BootstrapKey {
|
pub struct BootstrapKey {
|
||||||
pub y: Vec<Rational>,
|
/// u_i = y_i * 2^kappa, 0 <= u_i < 2^(kappa+1) (so y_i = u_i / 2^kappa is in [0, 2)).
|
||||||
|
pub u: Vec<Integer>,
|
||||||
|
/// Encryptions of the selector bits s_i (Hamming weight theta).
|
||||||
pub enc_s: Vec<Integer>,
|
pub enc_s: Vec<Integer>,
|
||||||
|
/// Precision of the y_i.
|
||||||
|
pub kappa: u32,
|
||||||
|
/// Hamming weight of the selector vector.
|
||||||
|
pub theta: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of bits needed to write any integer in 0..=max_weight.
|
||||||
|
fn weight_bits(max_weight: usize) -> u32 {
|
||||||
|
usize::BITS - max_weight.leading_zeros()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Number of fractional bits n = ceil(log2(theta + 1)) + 3 kept for each z_i.
|
||||||
|
pub fn precision_bits(theta: usize) -> u32 {
|
||||||
|
weight_bits(theta) + 3
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates a bootstrap key
|
/// Generates a bootstrap key
|
||||||
/// pk: public key
|
/// pk: public key
|
||||||
/// sk: secret integer p
|
/// sk: secret prime p
|
||||||
/// kappa: precision parameter (bits) for y_i
|
/// kappa: precision parameter (bits) for y_i; must be >= bitlen(x_0) + 4
|
||||||
/// theta: Hamming weight of the secret support
|
/// theta: Hamming weight of the secret support
|
||||||
/// n_theta: total number of hint elements
|
/// n_theta: total number of hint elements
|
||||||
/// rho: noise parameter (encryption)
|
/// rho: noise parameter (encryption of the selector bits)
|
||||||
pub fn generate_bootstrap_key(
|
pub fn generate_bootstrap_key(
|
||||||
pk: &PublicKey,
|
pk: &PublicKey,
|
||||||
sk: &Integer,
|
sk: &Integer,
|
||||||
@@ -26,246 +51,280 @@ pub fn generate_bootstrap_key(
|
|||||||
n_theta: usize,
|
n_theta: usize,
|
||||||
rho: u32,
|
rho: u32,
|
||||||
) -> BootstrapKey {
|
) -> BootstrapKey {
|
||||||
|
assert!(
|
||||||
|
theta >= 1 && theta <= n_theta,
|
||||||
|
"need 1 <= theta <= n_theta"
|
||||||
|
);
|
||||||
|
// The error c*|1/p - xp/2^kappa| < c*2^-kappa must stay below 1/16 for every c < x_0.
|
||||||
|
assert!(
|
||||||
|
kappa >= pk.xs[0].significant_bits() + 4,
|
||||||
|
"kappa must be at least bitlen(x_0) + 4 = {}",
|
||||||
|
pk.xs[0].significant_bits() + 4
|
||||||
|
);
|
||||||
|
|
||||||
// xp = ⌊2^κ / p⌋
|
// xp = ⌊2^κ / p⌋
|
||||||
let two_k = Integer::from(1) << kappa;
|
let two_k = Integer::from(1) << kappa;
|
||||||
let xp: Integer = &two_k / sk.clone();
|
let xp = Integer::from(&two_k / sk);
|
||||||
|
let two_k1 = Integer::from(1) << (kappa + 1);
|
||||||
|
|
||||||
// support vector s, Hamming weight theta
|
// support S, |S| = theta
|
||||||
let mut rng = StdRng::from_os_rng();
|
let mut rng = StdRng::from_os_rng();
|
||||||
let mut indices: Vec<usize> = (0..n_theta).collect();
|
let mut indices: Vec<usize> = (0..n_theta).collect();
|
||||||
indices.shuffle(&mut rng);
|
indices.shuffle(&mut rng);
|
||||||
let s: Vec<usize> = indices.iter().cloned().take(theta).collect();
|
let s: Vec<usize> = indices[..theta].to_vec();
|
||||||
|
let mut in_s = vec![false; n_theta];
|
||||||
let mut s_bits = vec![0u8; n_theta];
|
|
||||||
for &i in &s {
|
for &i in &s {
|
||||||
s_bits[i] = 1;
|
in_s[i] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// u[i] = xp (mod 2^(k+1))
|
// u_i random in [0, 2^(κ+1)); the last element of S is then fixed so that
|
||||||
let mut u = vec![Integer::from(0); n_theta];
|
// sum_{i in S} u_i = xp (mod 2^(κ+1)).
|
||||||
let two_k1 = Integer::from(1) << (kappa + 1);
|
let mut u: Vec<Integer> = (0..n_theta)
|
||||||
for i in 0..n_theta {
|
.map(|_| generate_random_integer_range(0, kappa + 1))
|
||||||
if !s.contains(&i) {
|
.collect();
|
||||||
u[i] = generate_random_integer_range(0, kappa + 1);
|
let mut partial = Integer::new();
|
||||||
}
|
for &i in &s[..theta - 1] {
|
||||||
}
|
partial += &u[i];
|
||||||
if theta > 0 {
|
|
||||||
for &i in s.iter().take(theta - 1) {
|
|
||||||
u[i] = generate_random_integer_range(0, kappa + 1);
|
|
||||||
}
|
}
|
||||||
|
u[s[theta - 1]] = Integer::from(&xp - &partial).rem_euc(&two_k1);
|
||||||
|
|
||||||
let mut sum_s_minus_last = Integer::from(0);
|
let enc_s: Vec<Integer> = in_s
|
||||||
for &i in s.iter().take(theta - 1) {
|
.iter()
|
||||||
sum_s_minus_last += &u[i];
|
.map(|&b| encrypt_bit_asym(b as u8, pk, rho))
|
||||||
}
|
.collect();
|
||||||
|
|
||||||
let s_last = s[theta - 1];
|
BootstrapKey {
|
||||||
let diff = xp.clone() - &sum_s_minus_last;
|
u,
|
||||||
let mut remainder = diff % &two_k1;
|
enc_s,
|
||||||
if remainder < 0 {
|
kappa,
|
||||||
remainder += &two_k1;
|
theta,
|
||||||
}
|
}
|
||||||
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 }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// e_0..e_max_deg of `items`, computed over the integers (reduced mod `modulus`, which is an exact multiple of p, so noise is unaffected). Addition/multiplication of ciphertexts act as XOR/AND on the plaintext bits, so e_j mod 2 is the usual elementary symmetric polynomial over GF(2).
|
||||||
pub fn encrypt_and_evaluate(
|
fn elementary_symmetric(items: &[Integer], max_deg: usize, modulus: &Integer) -> Vec<Integer> {
|
||||||
c: &Integer,
|
let mut e = vec![Integer::new(); max_deg + 1];
|
||||||
bootstrap_key: &BootstrapKey,
|
e[0] = Integer::from(1);
|
||||||
precision_bits: u32,
|
for (idx, x) in items.iter().enumerate() {
|
||||||
) -> Vec<Rational> {
|
for j in (1..=max_deg.min(idx + 1)).rev() {
|
||||||
let mut result = Vec::with_capacity(bootstrap_key.y.len());
|
if e[j - 1] == 0 {
|
||||||
|
continue;
|
||||||
let scale = Integer::from(1) << precision_bits;
|
|
||||||
let scale_rat = Rational::from(&scale);
|
|
||||||
|
|
||||||
for y_i in &bootstrap_key.y {
|
|
||||||
let prod = Rational::from(c) * y_i;
|
|
||||||
let scaled = prod.clone() * &scale_rat;
|
|
||||||
|
|
||||||
let floored = scaled.clone().floor();
|
|
||||||
let frac = &scaled - floored;
|
|
||||||
|
|
||||||
result.push(frac);
|
|
||||||
}
|
}
|
||||||
|
let mut t = Integer::from(x * &e[j - 1]);
|
||||||
result
|
t += &e[j];
|
||||||
|
e[j] = t.rem_euc(modulus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bootstrap(
|
/// Recrypt: returns a fresh ciphertext of the same bit as `c`, whose noise no longer depends on the noise of `c` (it only has to satisfy 0 <= e < p/4 on input).
|
||||||
c: &Integer,
|
/// `c` may be any non-negative integer ciphertext; it is first reduced mod x_0.
|
||||||
z: &[Rational],
|
pub fn bootstrap(c: &Integer, bk: &BootstrapKey, pk: &PublicKey) -> Integer {
|
||||||
encrypted_sk_bits: &[Integer],
|
|
||||||
pk: &PublicKey,
|
|
||||||
precision_bits: u32,
|
|
||||||
) -> Integer {
|
|
||||||
let int_scale = Integer::from(1) << precision_bits;
|
|
||||||
let scale_rat = Rational::from(&int_scale);
|
|
||||||
let half_rat = Rational::from((1, 2));
|
|
||||||
let x0 = &pk.xs[0];
|
let x0 = &pk.xs[0];
|
||||||
|
let n = precision_bits(bk.theta);
|
||||||
|
let n_cols = n as usize + 1; // bit positions 0..=n of the (n+1)-bit fixed-point z_i
|
||||||
|
let c = c.clone().rem_euc(x0);
|
||||||
|
|
||||||
let mut acc = Integer::from(0);
|
// Z_i = z_i * 2^n with z_i = [c*u_i / 2^κ]_2 truncated to n fractional bits.
|
||||||
for (z_i, enc_s_i) in z.iter().zip(encrypted_sk_bits.iter()) {
|
let shift = bk.kappa - n;
|
||||||
let rounded: Integer = (z_i.clone() * &scale_rat + &half_rat).floor_ref().into();
|
let zs: Vec<Integer> = bk
|
||||||
|
.u
|
||||||
|
.iter()
|
||||||
|
.map(|u| Integer::from(&c * u).keep_bits(bk.kappa + 1) >> shift)
|
||||||
|
.collect();
|
||||||
|
|
||||||
acc += &rounded * enc_s_i;
|
// columns[t]: ciphertext bits of weight 2^t. Z_i contributes s_i at every set bit t.
|
||||||
|
// (Multiplying Enc(s_i) by the public bit Z_{i,t} just selects or drops it.)
|
||||||
|
let mut columns: Vec<Vec<Integer>> = vec![Vec::new(); n_cols];
|
||||||
|
for (z, enc) in zs.iter().zip(&bk.enc_s) {
|
||||||
|
for (t, col) in columns.iter_mut().enumerate() {
|
||||||
|
if z.get_bit(t as u32) {
|
||||||
|
col.push(enc.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
acc %= x0;
|
// Column compression, LSB first. For column t holding h ones, bit k of h is e_{2^k} mod 2 (Lucas); bit 0 is bit t of T = sum_{i in S} Z_i, bit k>0 is a carry into column t+k.
|
||||||
|
let mut carries = vec![0usize; n_cols];
|
||||||
|
let mut t_bits: Vec<Integer> = Vec::with_capacity(n_cols);
|
||||||
|
for t in 0..n_cols {
|
||||||
|
let items = std::mem::take(&mut columns[t]);
|
||||||
|
// at most theta original ones plus every carry item can be one
|
||||||
|
let reachable = (bk.theta + carries[t]).min(items.len());
|
||||||
|
if reachable == 0 {
|
||||||
|
t_bits.push(Integer::new());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let k_top = (weight_bits(reachable) - 1).min((n_cols - 1 - t) as u32);
|
||||||
|
let e = elementary_symmetric(&items, 1usize << k_top, x0);
|
||||||
|
t_bits.push(e[1].clone());
|
||||||
|
for k in 1..=k_top as usize {
|
||||||
|
columns[t + k].push(e[1 << k].clone());
|
||||||
|
carries[t + k] += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let inv_scale = int_scale
|
// round(sum z_i) mod 2 = bit_n(T) XOR bit_{n-1}(T); m = (c mod 2) XOR that.
|
||||||
.invert(x0)
|
let mut out = Integer::from(&t_bits[n_cols - 1] + &t_bits[n_cols - 2]);
|
||||||
.expect("2^precision_bits and x0 must be coprime");
|
if c.is_odd() {
|
||||||
let adjusted_sum = (acc * inv_scale) % x0;
|
out += 1u32;
|
||||||
|
}
|
||||||
let mut diff = c.clone();
|
out.rem_euc(x0)
|
||||||
diff -= &adjusted_sum;
|
|
||||||
diff.modulo(x0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dghv_asym::generate_keys;
|
|
||||||
use crate::dghv::decrypt_bit;
|
use crate::dghv::decrypt_bit;
|
||||||
|
use crate::dghv_asym::{generate_keys, PrivateKey};
|
||||||
|
|
||||||
|
// Toy parameters: tiny hint (theta = 3 of 12), 1024-bit prime.
|
||||||
|
const ETA: u32 = 1024;
|
||||||
|
const RHO: u32 = 8;
|
||||||
|
const GAMMA: u32 = 64;
|
||||||
|
const PK_SIZE: usize = 20;
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
eta: u32,
|
||||||
|
theta: usize,
|
||||||
|
n_theta: usize,
|
||||||
|
) -> (PrivateKey, PublicKey, BootstrapKey) {
|
||||||
|
let (sk, pk) = generate_keys(GAMMA, eta, RHO, PK_SIZE);
|
||||||
|
let kappa = pk.xs[0].significant_bits() + 8;
|
||||||
|
let bk = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, RHO);
|
||||||
|
(sk, pk, bk)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// bit length of the noise e = c mod p
|
||||||
|
fn noise_bits(c: &Integer, p: &Integer) -> u32 {
|
||||||
|
Integer::from(c % p).significant_bits()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bootstrap_key_generation() {
|
fn test_bootstrap_key_generation() {
|
||||||
let eta = 1024;
|
|
||||||
let rho = 10;
|
|
||||||
let theta = 10;
|
let theta = 10;
|
||||||
let n_theta = 50;
|
let n_theta = 50;
|
||||||
let kappa = 100;
|
let (sk, pk, bk) = setup(ETA, theta, n_theta);
|
||||||
|
let kappa = bk.kappa;
|
||||||
|
|
||||||
let (sk, pk) = generate_keys(rho, eta, rho, theta);
|
let s_bits: Vec<u8> = bk
|
||||||
let bootstrap_key = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, rho);
|
.enc_s
|
||||||
|
.iter()
|
||||||
let y = bootstrap_key.y;
|
.map(|c| {
|
||||||
let enc_s = bootstrap_key.enc_s;
|
|
||||||
|
|
||||||
let s_bits: Vec<u8> = enc_s.iter().map(|c| {
|
|
||||||
let m = decrypt_bit(c, &sk.p);
|
let m = decrypt_bit(c, &sk.p);
|
||||||
assert!(m == 0 || m == 1, "Decrypted bit must be 0 or 1");
|
assert!(m == 0 || m == 1);
|
||||||
m
|
m
|
||||||
}).collect();
|
})
|
||||||
let hamming_weight = s_bits.iter().filter(|&&b| b == 1).count();
|
.collect();
|
||||||
assert_eq!(hamming_weight, theta, "Hamming weight of s should be {}", theta);
|
assert_eq!(s_bits.len(), n_theta);
|
||||||
assert_eq!(s_bits.len(), n_theta, "s_bits length should be {}", n_theta);
|
assert_eq!(s_bits.iter().filter(|&&b| b == 1).count(), theta);
|
||||||
|
assert_eq!(bk.u.len(), n_theta);
|
||||||
|
assert!(kappa >= pk.xs[0].significant_bits() + 4);
|
||||||
|
|
||||||
let two_k = Integer::from(1) << kappa;
|
let two_k = Integer::from(1) << kappa;
|
||||||
let two_k1 = Integer::from(1) << (kappa + 1);
|
let two_k1 = Integer::from(1) << (kappa + 1);
|
||||||
let xp = two_k.clone() / &sk.p;
|
let xp = Integer::from(&two_k / &sk.p);
|
||||||
let u: Vec<Integer> = y.iter().map(|yi| Integer::from((yi.clone() * &two_k).floor_ref())).collect();
|
assert!(xp > 0, "xp must be non-trivial (kappa > eta)");
|
||||||
|
|
||||||
let mut sum_u_s = Integer::from(0);
|
// sum of u_i over S == xp mod 2^(κ+1)
|
||||||
for (i, &bit) in s_bits.iter().enumerate() {
|
let mut sum_u_s = Integer::new();
|
||||||
if bit == 1 {
|
for (u, &b) in bk.u.iter().zip(&s_bits) {
|
||||||
sum_u_s += &u[i];
|
if b == 1 {
|
||||||
|
sum_u_s += u;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert!(Integer::from(&sum_u_s - &xp).is_divisible(&two_k1));
|
||||||
|
|
||||||
let diff = (sum_u_s.clone() - &xp) % &two_k1;
|
for u in &bk.u {
|
||||||
assert_eq!(diff, 0, "Sum of u_i over S should equal xp mod 2^(κ+1)");
|
assert!(*u >= 0 && *u < two_k1, "0 <= u_i < 2^(κ+1)");
|
||||||
|
|
||||||
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)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, yi) in y.iter().enumerate() {
|
// regression: the hint vector used to be one repeated value (unseeded GMP RNG)
|
||||||
let expected_yi = Rational::from(&u[i]) / Rational::from(&two_k);
|
let mut sorted: Vec<&Integer> = bk.u.iter().collect();
|
||||||
assert_eq!(yi, &expected_yi, "y[{}] should equal u[{}] / 2^κ", i, i);
|
sorted.sort();
|
||||||
assert!(yi < &Rational::from(2), "y[{}] should be less than 2", i);
|
sorted.dedup();
|
||||||
}
|
assert_eq!(sorted.len(), n_theta, "u_i must be distinct");
|
||||||
|
|
||||||
assert_eq!(enc_s.len(), n_theta, "enc_s length should be {}", n_theta);
|
// |xp/2^κ - 1/p| < 2^-κ <=> 0 <= 2^κ - xp*p < p
|
||||||
|
let rem = Integer::from(&two_k - Integer::from(&xp * &sk.p));
|
||||||
let mut sum_y_s = Rational::from(0);
|
assert!(rem >= 0 && rem < sk.p);
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bootstrapping_preserve_m() {
|
fn test_bootstrapping_preserve_m() {
|
||||||
let eta = 1000; // secret key bit size
|
let (sk, pk, bk) = setup(ETA, 3, 12);
|
||||||
let rho = 30; // initial noise
|
for m in 0..=1u8 {
|
||||||
let theta = 1000; // Hamming weight of hint support
|
let mut c = encrypt_bit_asym(m, &pk, RHO);
|
||||||
let n_theta = 1000; // total hint size
|
for _ in 0..50 {
|
||||||
let kappa = 1000; // precision for y_i
|
c = bootstrap(&c, &bk, &pk);
|
||||||
|
assert_eq!(decrypt_bit(&c, &sk.p), m, "bootstrap changed the plaintext");
|
||||||
let (sk, pk) = generate_keys(rho, eta, rho, theta);
|
|
||||||
let bk = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, rho);
|
|
||||||
|
|
||||||
for m in 0..=1 {
|
|
||||||
let mut c = encrypt_bit_asym(m, &pk, rho);
|
|
||||||
for _ in 0..=100 {
|
|
||||||
|
|
||||||
let encrypted_z = encrypt_and_evaluate(&c, &bk, kappa);
|
|
||||||
c = bootstrap(&c, &encrypted_z, &bk.enc_s, &pk, kappa);
|
|
||||||
|
|
||||||
let m_boot = decrypt_bit(&c, &sk.p);
|
|
||||||
assert_eq!(
|
|
||||||
m, m_boot,
|
|
||||||
"Bootstrap failed: expected {}, got {}",
|
|
||||||
m, m_boot
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bootstrapping() {
|
fn test_bootstrapping() {
|
||||||
let eta = 1000; // secret key bit size
|
// multiply by Enc(1) then bootstrap, repeatedly: noise would blow up without recryption
|
||||||
let rho = 30; // initial noise
|
let (sk, pk, bk) = setup(ETA, 3, 12);
|
||||||
let theta = 1000; // Hamming weight of hint support
|
let c1 = encrypt_bit_asym(1u8, &pk, RHO);
|
||||||
let n_theta = 1000; // total hint size
|
for m in 0..=1u8 {
|
||||||
let kappa = 1000; // precision for y_i
|
let mut c = encrypt_bit_asym(m, &pk, RHO);
|
||||||
|
for _ in 0..50 {
|
||||||
let (sk, pk) = generate_keys(rho, eta, rho, theta);
|
c *= &c1;
|
||||||
let bk = generate_bootstrap_key(&pk, &sk.p, kappa, theta, n_theta, rho);
|
c = bootstrap(&c, &bk, &pk);
|
||||||
|
assert_eq!(decrypt_bit(&c, &sk.p), m);
|
||||||
// let c0 = encrypt_bit_asym(0u8, &pk, rho);
|
|
||||||
let c1 = encrypt_bit_asym(1u8, &pk, rho);
|
|
||||||
|
|
||||||
for m in 0..=1 {
|
|
||||||
let mut c = encrypt_bit_asym(m, &pk, rho);
|
|
||||||
for _ in 0..=100 {
|
|
||||||
// c += c0.clone();
|
|
||||||
c *= c1.clone();
|
|
||||||
|
|
||||||
let encrypted_z = encrypt_and_evaluate(&c, &bk, kappa);
|
|
||||||
c = bootstrap(&c, &encrypted_z, &bk.enc_s, &pk, kappa);
|
|
||||||
|
|
||||||
let m_boot = decrypt_bit(&c, &sk.p);
|
|
||||||
assert_eq!(
|
|
||||||
m, m_boot,
|
|
||||||
"Bootstrap failed: expected {}, got {}",
|
|
||||||
m, m_boot
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
println!("m={m}: noise after bootstrap = {} bits", noise_bits(&c, &sk.p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bootstrapping_and_xor_circuit() {
|
||||||
|
// evaluate (a AND b) XOR a on encrypted bits, bootstrapping after each gate
|
||||||
|
let (sk, pk, bk) = setup(ETA, 3, 12);
|
||||||
|
for &(a, b) in &[(0u8, 0u8), (0, 1), (1, 0), (1, 1)] {
|
||||||
|
let ca = encrypt_bit_asym(a, &pk, RHO);
|
||||||
|
let cb = encrypt_bit_asym(b, &pk, RHO);
|
||||||
|
let and = bootstrap(&Integer::from(&ca * &cb), &bk, &pk);
|
||||||
|
let xor = bootstrap(&Integer::from(&and + &ca), &bk, &pk);
|
||||||
|
assert_eq!(decrypt_bit(&and, &sk.p), a & b);
|
||||||
|
assert_eq!(decrypt_bit(&xor, &sk.p), (a & b) ^ a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bootstrapping_reduces_noise() {
|
||||||
|
let (sk, pk, bk) = setup(ETA, 3, 12);
|
||||||
|
let c1 = encrypt_bit_asym(1u8, &pk, RHO);
|
||||||
|
for m in 0..=1u8 {
|
||||||
|
// square-and-multiply a few times to build up noise (about 8x the fresh noise)
|
||||||
|
let mut c = encrypt_bit_asym(m, &pk, RHO);
|
||||||
|
for _ in 0..40 {
|
||||||
|
c *= &c1;
|
||||||
|
}
|
||||||
|
assert_eq!(decrypt_bit(&c, &sk.p), m);
|
||||||
|
let before = noise_bits(&c, &sk.p);
|
||||||
|
let fresh = bootstrap(&c, &bk, &pk);
|
||||||
|
let after = noise_bits(&fresh, &sk.p);
|
||||||
|
println!("noise before = {before} bits, after = {after} bits (p has {ETA} bits)");
|
||||||
|
assert!(after < before, "bootstrapping must reduce the noise");
|
||||||
|
assert_eq!(decrypt_bit(&fresh, &sk.p), m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bootstrapping_theta_15() {
|
||||||
|
let (sk, pk, bk) = setup(3000, 15, 60);
|
||||||
|
let c1 = encrypt_bit_asym(1u8, &pk, RHO);
|
||||||
|
for m in 0..=1u8 {
|
||||||
|
let mut c = encrypt_bit_asym(m, &pk, RHO);
|
||||||
|
for _ in 0..5 {
|
||||||
|
c *= &c1;
|
||||||
|
c = bootstrap(&c, &bk, &pk);
|
||||||
|
assert_eq!(decrypt_bit(&c, &sk.p), m);
|
||||||
|
}
|
||||||
|
println!("theta=15 noise: {} bits", noise_bits(&c, &sk.p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user