expose bootstrapping command

This commit is contained in:
sam.hadow committed 2026-10-19 16:23:52 +02:00
1 parent 88d75eee5d
commit 08197da9da
3 files changed
+186 -2

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
[package]
name = "homomorphic-encryption"
name = "dghv"
version = "0.1.0"
edition = "2021"
+74
View File
@@ -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
View File
@@ -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);
}
}
}
}