Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ license = "MIT"
backend-avx = ["dep:poulpy-cpu-avx"]

[dependencies]
poulpy-core = { git = "https://github.com/phantomzone-org/poulpy", branch = "main" }
poulpy-schemes = { git = "https://github.com/phantomzone-org/poulpy", branch = "main" }
poulpy-hal = { git = "https://github.com/phantomzone-org/poulpy", branch = "main" }
poulpy-cpu-ref = { git = "https://github.com/phantomzone-org/poulpy", branch = "main" }
poulpy-cpu-avx = { git = "https://github.com/phantomzone-org/poulpy", branch = "main", optional = true }
poulpy-core = { git = "https://github.com/cedoor/poulpy.git", branch = "feat/serialization-key-types" }
poulpy-schemes = { git = "https://github.com/cedoor/poulpy.git", branch = "feat/serialization-key-types" }
poulpy-hal = { git = "https://github.com/cedoor/poulpy.git", branch = "feat/serialization-key-types" }
poulpy-cpu-ref = { git = "https://github.com/cedoor/poulpy.git", branch = "feat/serialization-key-types" }
poulpy-cpu-avx = { git = "https://github.com/cedoor/poulpy.git", branch = "feat/serialization-key-types", optional = true }
Comment thread
cedoor marked this conversation as resolved.
Outdated
getrandom = "0.3"
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ The public API is identical regardless of which backend is selected.
- [x] Add at least one runnable example in examples/: [#7](https://github.com/cedoor/squid/issues/7)
- [ ] Add tests for all existing ops: [#4](https://github.com/cedoor/squid/issues/4)
- [ ] Add rustdoc comments to all public items: [#6](https://github.com/cedoor/squid/issues/6)
- [ ] Faster tests via fixtures or deterministic keygen: [#19](https://github.com/cedoor/squid/issues/19)
- [x] Faster tests via fixtures or deterministic keygen: [#19](https://github.com/cedoor/squid/issues/19)

### Milestone 2 — Full bin_fhe Coverage: [#2](https://github.com/cedoor/squid/milestone/2)

Expand All @@ -85,7 +85,7 @@ The public API is identical regardless of which backend is selected.
- [ ] Sub-word operations: [#10](https://github.com/cedoor/squid/issues/10)
- [ ] Identity / noise refresh: [#11](https://github.com/cedoor/squid/issues/11)
- [ ] NTT backend: [#12](https://github.com/cedoor/squid/issues/12)
- [ ] Key serialization: [#13](https://github.com/cedoor/squid/issues/13)
- [x] Key serialization: [#13](https://github.com/cedoor/squid/issues/13)

### Milestone 3 — Developer Experience & Optimization: [#3](https://github.com/cedoor/squid/milestone/3)

Expand Down
93 changes: 93 additions & 0 deletions examples/serialize_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//! Write the standard-form [`squid::EvaluationKey`] blob from one OS-random keygen.
//!
//! Secret key material is not written: Poulpy no longer exposes binary I/O for
//! LWE/GLWE secrets ([poulpy#147](https://github.com/poulpy-fhe/poulpy/pull/147));
//! persist seeds or handle secrets at the app level if you need portability.
//!
//! Output: `params_test_evaluation_key.bin` under `--output-dir`.
//!
//! ```sh
//! cargo run --example serialize_keys -- --output-dir ./out
//! ```

use std::path::PathBuf;

use squid::{Context, Params};

const EVALUATION_KEY_FILE: &str = "params_test_evaluation_key.bin";

struct Args {
output_dir: PathBuf,
}

fn print_usage() {
eprintln!(
"\
Usage: serialize_keys --output-dir <DIR>

Write the standard-form evaluation key blob from one OS-random keygen (Params::test()).

File written (fixed name):
{EVALUATION_KEY_FILE}

Options:
-o, --output-dir <DIR> Directory to write the file into
-h, --help Show this help
"
);
}

fn parse_args() -> Result<Option<Args>, String> {
let mut args = std::env::args().skip(1);
let mut output_dir: Option<PathBuf> = None;

while let Some(arg) = args.next() {
match arg.as_str() {
"-h" | "--help" => return Ok(None),
"-o" | "--output-dir" => {
let path = args
.next()
.ok_or_else(|| "--output-dir requires a directory".to_string())?;
output_dir = Some(PathBuf::from(path));
}
other => return Err(format!("unknown argument: {other}")),
}
}

Ok(Some(Args {
output_dir: output_dir
.ok_or_else(|| "missing --output-dir <DIR> (or -o <DIR>)".to_string())?,
}))
}

fn main() -> std::io::Result<()> {
let args = match parse_args() {
Ok(None) => {
print_usage();
return Ok(());
}
Ok(Some(a)) => a,
Err(e) => {
eprintln!("Error: {e}");
print_usage();
std::process::exit(1);
}
};

std::fs::create_dir_all(&args.output_dir)?;

let evaluation_key = args.output_dir.join(EVALUATION_KEY_FILE);

let params = Params::test();
let mut ctx = Context::new(params);
let (_sk, ek) = ctx.keygen();

let ek_blob = ctx
.serialize_evaluation_key(&ek)
.expect("serialize evaluation key");

std::fs::write(&evaluation_key, ek_blob)?;

eprintln!("Wrote {}.", evaluation_key.display());
Ok(())
}
41 changes: 41 additions & 0 deletions src/ciphertext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,23 @@
//! It is the only ciphertext form users ever see. The prepared (DFT-domain)
//! form `FheUintPrepared` is an implementation detail that lives temporarily
//! inside [`crate::context::Context`] during operation evaluation.
//!
//! Standard-form wire encoding is [`Ciphertext::serialize`] /
//! [`Ciphertext::deserialize`] / [`crate::context::Context::serialize_ciphertext`] /
//! [`crate::context::Context::deserialize_ciphertext`] (versioned little-endian blob;
//! must be loaded with the same [`crate::context::Params`] as encryption).

use std::io;

use poulpy_core::layouts::GLWEToRef;
use poulpy_hal::layouts::WriterTo;
use poulpy_schemes::bin_fhe::bdd_arithmetic::{FheUint, UnsignedInteger};

use crate::context::Context;

/// Leading byte of [`Ciphertext::serialize`] / [`crate::context::Context::serialize_ciphertext`] blobs.
pub(crate) const CIPHERTEXT_BLOB_VERSION: u8 = 1;

/// An encrypted unsigned integer of type `T`.
///
/// `T` must implement [`UnsignedInteger`], which is satisfied by `u8`, `u16`,
Expand All @@ -25,3 +39,30 @@ use poulpy_schemes::bin_fhe::bdd_arithmetic::{FheUint, UnsignedInteger};
pub struct Ciphertext<T: UnsignedInteger> {
pub(crate) inner: FheUint<Vec<u8>, T>,
}
Comment thread
cedoor marked this conversation as resolved.

impl<T: UnsignedInteger> Ciphertext<T> {
/// Serializes the packed GLWE ciphertext (little-endian, versioned). The plaintext type `T`
/// is recorded in the blob; use the same `T` with [`Ciphertext::deserialize`].
///
/// Same as [`crate::context::Context::serialize_ciphertext`] with this value.
pub fn serialize(&self) -> io::Result<Vec<u8>> {
let mut out = Vec::new();
out.push(CIPHERTEXT_BLOB_VERSION);
out.extend_from_slice(&T::BITS.to_le_bytes());
self.inner.to_ref().write_to(&mut out)?;
Ok(out)
}

/// Restores a [`Ciphertext<T>`] from [`Ciphertext::serialize`] output for the same [`Context`]
/// [`Params`](crate::context::Params).
///
/// Same as [`crate::context::Context::deserialize_ciphertext`].
///
/// # Errors
///
/// Returns [`std::io::Error`] with kind [`InvalidData`](io::ErrorKind::InvalidData) if the
/// blob does not match `T` or this context's [`GLWE`](poulpy_core::layouts::GLWE) layout.
pub fn deserialize(ctx: &mut Context, bytes: &[u8]) -> io::Result<Self> {
ctx.deserialize_ciphertext(bytes)
}
}
Loading
Loading