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]
|
||||
name = "homomorphic-encryption"
|
||||
name = "dghv"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::bootstrapping::BootstrapKey;
|
||||
use crate::dghv_asym::{PrivateKey, PublicKey};
|
||||
use rug::Integer;
|
||||
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)?;
|
||||
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 fileutils;
|
||||
mod utils;
|
||||
use crate::bootstrapping::{bootstrap, generate_bootstrap_key};
|
||||
use crate::dghv::{decrypt_bit, encrypt_bit, generate_secret_key};
|
||||
use crate::dghv_asym::{encrypt_bit_asym, generate_keys};
|
||||
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 rug::Integer;
|
||||
|
||||
const WEIGHT_DEFAULT: usize = 5;
|
||||
const HINT_DEFAULT: usize = 20;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(propagate_version = true)]
|
||||
@@ -46,6 +51,55 @@ enum Commands {
|
||||
#[arg(short = 't', long, default_value_t = 10)]
|
||||
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)]
|
||||
@@ -147,5 +201,61 @@ fn main() {
|
||||
write_pubkey(&path, &pk).expect("Failed to write pubkey");
|
||||
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