expose bootstrapping command
This commit is contained in:
1 parent
88d75eee5d
commit
08197da9da
3 files changed
+186
-2
No files matched your search
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "homomorphic-encryption"
|
name = "dghv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::bootstrapping::BootstrapKey;
|
||||||
use crate::dghv_asym::{PrivateKey, PublicKey};
|
use crate::dghv_asym::{PrivateKey, PublicKey};
|
||||||
use rug::Integer;
|
use rug::Integer;
|
||||||
use std::fs::{read_to_string, File};
|
use std::fs::{read_to_string, File};
|
||||||
@@ -63,3 +64,76 @@ pub fn write_hex_to_file<P: AsRef<Path>>(path: P, hex_str: &str) -> io::Result<(
|
|||||||
writeln!(file, "{}", hex_str)?;
|
writeln!(file, "{}", hex_str)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bootstrap key file (`<prefix>.bskey`):
|
||||||
|
/// line 1: `kappa theta n` (decimal)
|
||||||
|
/// next n lines: `u_i enc_s_i` (both hex)
|
||||||
|
pub fn write_bskey<P: AsRef<Path>>(prefix: P, bk: &BootstrapKey) -> io::Result<()> {
|
||||||
|
let mut file = File::create(prefix.as_ref().with_extension("bskey"))?;
|
||||||
|
writeln!(file, "{} {} {}", bk.kappa, bk.theta, bk.u.len())?;
|
||||||
|
for (u, e) in bk.u.iter().zip(&bk.enc_s) {
|
||||||
|
writeln!(file, "{} {}", u.to_string_radix(16), e.to_string_radix(16))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_bskey<P: AsRef<Path>>(path: P) -> io::Result<BootstrapKey> {
|
||||||
|
let bad = |msg: &str| io::Error::new(io::ErrorKind::InvalidData, msg.to_string());
|
||||||
|
let content = read_to_string(path)?;
|
||||||
|
let mut lines = content.lines();
|
||||||
|
|
||||||
|
let header: Vec<&str> = lines.next().ok_or_else(|| bad("empty bootstrap key"))?.split_whitespace().collect();
|
||||||
|
if header.len() != 3 {
|
||||||
|
return Err(bad("bad bootstrap key header (expected `kappa theta n`)"));
|
||||||
|
}
|
||||||
|
let num = |s: &str| s.parse::<u32>().map_err(|_| bad("bad number in bootstrap key header"));
|
||||||
|
let kappa = num(header[0])?;
|
||||||
|
let theta = num(header[1])? as usize;
|
||||||
|
let n = num(header[2])? as usize;
|
||||||
|
|
||||||
|
let hex = |s: &str| Integer::from_str_radix(s, 16).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e));
|
||||||
|
let (mut u, mut enc_s) = (Vec::with_capacity(n), Vec::with_capacity(n));
|
||||||
|
for line in lines.filter(|l| !l.trim().is_empty()) {
|
||||||
|
let mut it = line.split_whitespace();
|
||||||
|
match (it.next(), it.next(), it.next()) {
|
||||||
|
(Some(a), Some(b), None) => {
|
||||||
|
u.push(hex(a)?);
|
||||||
|
enc_s.push(hex(b)?);
|
||||||
|
}
|
||||||
|
_ => return Err(bad("bad line in bootstrap key (expected `u enc_s`)")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if u.len() != n || theta == 0 || theta > n || kappa < crate::bootstrapping::precision_bits(theta) {
|
||||||
|
return Err(bad("inconsistent bootstrap key parameters"));
|
||||||
|
}
|
||||||
|
Ok(BootstrapKey { u, enc_s, kappa, theta })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::bootstrapping::generate_bootstrap_key;
|
||||||
|
use crate::dghv_asym::generate_keys;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bskey_roundtrip() {
|
||||||
|
let (sk, pk) = generate_keys(16, 512, 16, 10);
|
||||||
|
let bk = generate_bootstrap_key(&pk, &sk.p, pk.xs[0].significant_bits() + 8, 3, 12, 16);
|
||||||
|
let prefix = std::env::temp_dir().join(format!("dghv_test_{}", std::process::id()));
|
||||||
|
write_bskey(&prefix, &bk).unwrap();
|
||||||
|
let path = prefix.with_extension("bskey");
|
||||||
|
let back = read_bskey(&path).unwrap();
|
||||||
|
std::fs::remove_file(&path).unwrap();
|
||||||
|
assert_eq!((back.kappa, back.theta), (bk.kappa, bk.theta));
|
||||||
|
assert_eq!(back.u, bk.u);
|
||||||
|
assert_eq!(back.enc_s, bk.enc_s);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_bskey_rejects_garbage() {
|
||||||
|
let path = std::env::temp_dir().join(format!("dghv_bad_{}.bskey", std::process::id()));
|
||||||
|
std::fs::write(&path, "100 3 2\nff\n").unwrap();
|
||||||
|
assert!(read_bskey(&path).is_err());
|
||||||
|
std::fs::remove_file(&path).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
+111
-1
@@ -3,14 +3,19 @@ mod dghv;
|
|||||||
mod dghv_asym;
|
mod dghv_asym;
|
||||||
mod fileutils;
|
mod fileutils;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
use crate::bootstrapping::{bootstrap, generate_bootstrap_key};
|
||||||
use crate::dghv::{decrypt_bit, encrypt_bit, generate_secret_key};
|
use crate::dghv::{decrypt_bit, encrypt_bit, generate_secret_key};
|
||||||
use crate::dghv_asym::{encrypt_bit_asym, generate_keys};
|
use crate::dghv_asym::{encrypt_bit_asym, generate_keys};
|
||||||
use crate::fileutils::{
|
use crate::fileutils::{
|
||||||
read_hex_from_file, read_privkey, read_pubkey, write_hex_to_file, write_privkey, write_pubkey,
|
read_bskey, read_hex_from_file, read_privkey, read_pubkey, write_bskey, write_hex_to_file,
|
||||||
|
write_privkey, write_pubkey,
|
||||||
};
|
};
|
||||||
use clap::{ArgGroup, Args, Parser, Subcommand};
|
use clap::{ArgGroup, Args, Parser, Subcommand};
|
||||||
use rug::Integer;
|
use rug::Integer;
|
||||||
|
|
||||||
|
const WEIGHT_DEFAULT: usize = 5;
|
||||||
|
const HINT_DEFAULT: usize = 20;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
#[command(propagate_version = true)]
|
#[command(propagate_version = true)]
|
||||||
@@ -46,6 +51,55 @@ enum Commands {
|
|||||||
#[arg(short = 't', long, default_value_t = 10)]
|
#[arg(short = 't', long, default_value_t = 10)]
|
||||||
theta: usize,
|
theta: usize,
|
||||||
},
|
},
|
||||||
|
/// Generate a bootstrap key (<path>.bskey) from a key pair
|
||||||
|
BootstrapKeygen(BootstrapKeygenArgs),
|
||||||
|
/// Bootstrap (recrypt) a ciphertext: same bit, fresh noise
|
||||||
|
Bootstrap(BootstrapArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
struct BootstrapKeygenArgs {
|
||||||
|
/// path to public key file
|
||||||
|
#[arg(long)]
|
||||||
|
pubkey: String,
|
||||||
|
/// path to private key file
|
||||||
|
#[arg(long)]
|
||||||
|
privkey: String,
|
||||||
|
/// output path prefix for the bootstrap key
|
||||||
|
#[arg(short = 'o', long)]
|
||||||
|
path: String,
|
||||||
|
/// precision kappa of the hint (default: bit length of x_0 + 8)
|
||||||
|
#[arg(long)]
|
||||||
|
kappa: Option<u32>,
|
||||||
|
/// Hamming weight of the secret subset
|
||||||
|
#[arg(short = 'w', long, default_value_t = WEIGHT_DEFAULT)]
|
||||||
|
weight: usize,
|
||||||
|
/// number of hint elements
|
||||||
|
#[arg(short = 'm', long, default_value_t = HINT_DEFAULT)]
|
||||||
|
hint_size: usize,
|
||||||
|
/// noise parameter (use the one of keygen-pair)
|
||||||
|
#[arg(short = 'n', long, default_value_t = 80)]
|
||||||
|
noise: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(group(ArgGroup::new("cipher_source").required(true).args(&["ciphertext", "file"])))]
|
||||||
|
struct BootstrapArgs {
|
||||||
|
/// ciphertext hex string
|
||||||
|
#[arg(short = 'c', long)]
|
||||||
|
ciphertext: Option<String>,
|
||||||
|
/// path to ciphertext file
|
||||||
|
#[arg(short = 'f', long)]
|
||||||
|
file: Option<String>,
|
||||||
|
/// path to public key file
|
||||||
|
#[arg(long)]
|
||||||
|
pubkey: String,
|
||||||
|
/// path to bootstrap key file
|
||||||
|
#[arg(long)]
|
||||||
|
bskey: String,
|
||||||
|
/// optional output file for the new ciphertext (hex-encoded)
|
||||||
|
#[arg(short = 'o', long)]
|
||||||
|
output: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
@@ -147,5 +201,61 @@ fn main() {
|
|||||||
write_pubkey(&path, &pk).expect("Failed to write pubkey");
|
write_pubkey(&path, &pk).expect("Failed to write pubkey");
|
||||||
println!("keys written to {}.[privkey|pubkey]", path);
|
println!("keys written to {}.[privkey|pubkey]", path);
|
||||||
}
|
}
|
||||||
|
Commands::BootstrapKeygen(args) => {
|
||||||
|
let pk = read_pubkey(&args.pubkey).expect("Failed to load public key");
|
||||||
|
let sk = read_privkey(&args.privkey).expect("Failed to load private key");
|
||||||
|
let min_kappa = pk.xs[0].significant_bits() + 4;
|
||||||
|
let kappa = args.kappa.unwrap_or(min_kappa + 4);
|
||||||
|
if kappa < min_kappa {
|
||||||
|
eprintln!("invalid --kappa: need kappa >= {} (bit length of x_0 + 4)", min_kappa);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
if args.weight == 0 || args.weight > args.hint_size {
|
||||||
|
eprintln!("invalid parameters: need 1 <= --weight <= --hint-size");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
let bk = generate_bootstrap_key(
|
||||||
|
&pk,
|
||||||
|
&sk.p,
|
||||||
|
kappa,
|
||||||
|
args.weight,
|
||||||
|
args.hint_size,
|
||||||
|
args.noise,
|
||||||
|
);
|
||||||
|
|
||||||
|
// self-test: the parameters must be able to bootstrap a product of two fresh ciphertexts
|
||||||
|
for m in 0..=1u8 {
|
||||||
|
let c = encrypt_bit_asym(m, &pk, args.noise) * encrypt_bit_asym(1, &pk, args.noise);
|
||||||
|
let refreshed = bootstrap(&c, &bk, &pk);
|
||||||
|
if decrypt_bit(&refreshed, &sk.p) != m {
|
||||||
|
eprintln!(
|
||||||
|
"self-test failed: the noise of the bootstrapping circuit exceeds the key capacity.\n\
|
||||||
|
Use a larger --size (eta) for the key pair, or a smaller --weight."
|
||||||
|
);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write_bskey(&args.path, &bk).expect("Failed to write bootstrap key");
|
||||||
|
println!(
|
||||||
|
"bootstrap key written to {}.bskey (kappa={}, weight={}, hint size={}), self-test passed",
|
||||||
|
args.path, kappa, args.weight, args.hint_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Commands::Bootstrap(args) => {
|
||||||
|
let ct_hex = if let Some(hex_str) = args.ciphertext {
|
||||||
|
hex_str
|
||||||
|
} else {
|
||||||
|
read_hex_from_file(args.file.unwrap()).expect("Failed to read ciphertext file")
|
||||||
|
};
|
||||||
|
let ct = Integer::from_str_radix(&ct_hex, 16).expect("Invalid ciphertext");
|
||||||
|
let pk = read_pubkey(args.pubkey).expect("Failed to load public key");
|
||||||
|
let bk = read_bskey(args.bskey).expect("Failed to load bootstrap key");
|
||||||
|
let hexct = bootstrap(&ct, &bk, &pk).to_string_radix(16);
|
||||||
|
if let Some(outfile) = args.output {
|
||||||
|
write_hex_to_file(outfile, &hexct).expect("Failed to write ciphertext to file");
|
||||||
|
} else {
|
||||||
|
println!("Bootstrapped bit (hex): {}", hexct);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user