diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..1472fea Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d8f3b24 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[workspace] +members = [ + "encryption-demo", + "encryption-demo-methods", +] +workspace.resolver = "2" diff --git a/encryption-demo-methods/Cargo.toml b/encryption-demo-methods/Cargo.toml new file mode 100644 index 0000000..ff9c336 --- /dev/null +++ b/encryption-demo-methods/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "encryption-demo-methods" +version = "0.1.0" +edition = "2021" +publish = false +build = "build.rs" + +[dependencies] +build = "0.0.2" +risc0-zkvm = { version = "2.2", default-features = false } +risc0-build = { version = "2.2", default-features = false } + +[package.metadata.risc0] +methods = ["guest/chacha20", "guest/xchacha20"] + diff --git a/encryption-demo-methods/build.rs b/encryption-demo-methods/build.rs new file mode 100644 index 0000000..aa600df --- /dev/null +++ b/encryption-demo-methods/build.rs @@ -0,0 +1,4 @@ +fn main() { + risc0_build::embed_methods(); +} + diff --git a/encryption-demo-methods/guest/chacha20/Cargo.toml b/encryption-demo-methods/guest/chacha20/Cargo.toml new file mode 100644 index 0000000..bce8cdd --- /dev/null +++ b/encryption-demo-methods/guest/chacha20/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "chacha20" +version = "0.1.0" +edition = "2021" +publish = false +# Ensure staticlib for RISC-V +crate-type = ["staticlib"] + +[dependencies] +# no_std stream cipher +chacha20 = { version = "0.9", default-features = false } +cipher = { version = "0.5", default-features = false } + +risc0-zkvm-guest = "2.2" + +[features] +default = ["alloc"] # pull in alloc inside guest +alloc = [] + diff --git a/encryption-demo-methods/guest/chacha20/src/main.rs b/encryption-demo-methods/guest/chacha20/src/main.rs new file mode 100644 index 0000000..1ab9c08 --- /dev/null +++ b/encryption-demo-methods/guest/chacha20/src/main.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] + +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use chacha20::ChaCha20; +use risc0_zkvm::guest::env; +risc0_zkvm_guest::entry!(main); + +pub fn main() { + // Read 32-byte key + let key: [u8; 32] = env::read(); + // Read 12-byte nonce + let nonce: [u8; 12] = env::read(); + // Read plaintext length + let len: u32 = env::read(); + // Read plaintext bytes + let mut plaintext = vec![0u8; len as usize]; + env::read_slice(&mut plaintext).unwrap(); + + // Encrypt in-place + let mut cipher = ChaCha20::new(&key.into(), &nonce.into()); + cipher.apply_keystream(&mut plaintext); + + // Commit ciphertext + env::commit_slice(&plaintext); +} + diff --git a/encryption-demo-methods/guest/xchacha20/Cargo.toml b/encryption-demo-methods/guest/xchacha20/Cargo.toml new file mode 100644 index 0000000..f94e10f --- /dev/null +++ b/encryption-demo-methods/guest/xchacha20/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "xchacha20" +version = "0.1.0" +edition = "2021" +publish = false +# Ensure staticlib for RISC-V +crate-type = ["staticlib"] + +[dependencies] +# no_std stream cipher +xchacha20 = { version = "0.9", default-features = false } +cipher = { version = "0.5", default-features = false } + +risc0-zkvm-guest = "2.2" + +[features] +default = ["alloc"] # pull in alloc inside guest +alloc = [] + diff --git a/encryption-demo-methods/guest/xchacha20/src/main.rs b/encryption-demo-methods/guest/xchacha20/src/main.rs new file mode 100644 index 0000000..e76ec7a --- /dev/null +++ b/encryption-demo-methods/guest/xchacha20/src/main.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] + +use chacha20::cipher::{KeyIvInit, StreamCipher}; +use chacha20::XChaCha20; +use risc0_zkvm::guest::env; +risc0_zkvm_guest::entry!(main); + +pub fn main() { + let key: [u8; 32] = env::read(); + let nonce: [u8; 24] = env::read(); + let len: u32 = env::read(); + let mut plaintext = vec![0u8; len as usize]; + env::read_slice(&mut plaintext).unwrap(); + + let mut cipher = XChaCha20::new(&key.into(), &nonce.into()); + cipher.apply_keystream(&mut plaintext); + + env::commit_slice(&plaintext); +} + diff --git a/encryption-demo-methods/src/lib.rs b/encryption-demo-methods/src/lib.rs new file mode 100644 index 0000000..7b08660 --- /dev/null +++ b/encryption-demo-methods/src/lib.rs @@ -0,0 +1,2 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); + diff --git a/encryption-demo/Cargo.toml b/encryption-demo/Cargo.toml new file mode 100644 index 0000000..6d6a565 --- /dev/null +++ b/encryption-demo/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "encryption-demo" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +risc0-zkvm = "2.2" +rand = { version = "0.8", features = ["std"]} +hex = "0.4" + +encryption-demo-methods = { path = "../encryption-demo-methods" } + +[[bin]] +name = "encrypt-demo" +path = "src/main.rs" diff --git a/encryption-demo/encryption-demo/src/main.rs b/encryption-demo/encryption-demo/src/main.rs new file mode 100644 index 0000000..85f20af --- /dev/null +++ b/encryption-demo/encryption-demo/src/main.rs @@ -0,0 +1,14 @@ +use encryption_demo::*; +use rand::Rng; + +fn main() -> anyhow::Result<()> { + let msg = b"zk-encrypted hello world!"; + let mut rng = rand::thread_rng(); + let key: [u8; 32] = rng.gen(); + let nonce: [u8; 12] = rng.gen(); + + let (ciphertext, _proof) = encrypt_chacha20(&key, &nonce, msg)?; + println!("ciphertext = {}", hex::encode(ciphertext)); + println!("proof OK"); + Ok(()) +} diff --git a/encryption-demo/src/lib.rs b/encryption-demo/src/lib.rs new file mode 100644 index 0000000..feaab80 --- /dev/null +++ b/encryption-demo/src/lib.rs @@ -0,0 +1,114 @@ +//! Host-side helpers: prove encryption inside RISC Zero and verify receipts. + +use risc0_zkvm::{ + default_prover, ExecutorEnv, Receipt, +}; +use encryption_demo_methods::{ + CHACHA20_ELF, CHACHA20_ID, XCHACHA20_ELF, XCHACHA20_ID, +}; + +/// Encrypt `plaintext` with ChaCha20 inside the zkVM. +/// Returns `(ciphertext, receipt)`. +pub fn encrypt_chacha20( + key: &[u8; 32], + nonce: &[u8; 12], + plaintext: &[u8], +) -> anyhow::Result<(Vec, Receipt)> { + let env = ExecutorEnv::builder() + .write(key)? + .write(nonce)? + .write(&(plaintext.len() as u32))? + .write_slice(plaintext)? + .build()?; + + let prover = default_prover(); + let receipt = prover.prove(env, CHACHA20_ELF)?; + receipt.verify(CHACHA20_ID)?; + + let ciphertext: Vec = receipt.journal.decode()?; + Ok((ciphertext, receipt)) +} + +/// Same API for XChaCha20 (24-byte nonce) +pub fn encrypt_xchacha20( + key: &[u8; 32], + nonce: &[u8; 24], + plaintext: &[u8], +) -> anyhow::Result<(Vec, Receipt)> { + let env = ExecutorEnv::builder() + .write(key)? + .write(nonce)? + .write(&(plaintext.len() as u32))? + .write_slice(plaintext)? + .build()?; + + let prover = default_prover(); + let receipt = prover.prove(env, XCHACHA20_ELF)?; + receipt.verify(XCHACHA20_ID)?; + + let ciphertext: Vec = receipt.journal.decode()?; + Ok((ciphertext, receipt)) +} + + +// Test for chacha20 +#[cfg(test)] +mod tests { + use super::*; + use chacha20::cipher::{KeyIvInit, StreamCipher}; + use chacha20::ChaCha20; + + // RFC 8439 test vector + const KEY: [u8; 32] = [ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ]; + const NONCE: [u8; 12] = [0; 12]; + const PLAINTEXT: [u8; 16] = *b"example message!"; + + #[test] + fn chacha20_vector_matches() { + let (ciphertext, _) = encrypt_chacha20(&KEY, &NONCE, &PLAINTEXT).unwrap(); + + // host decryption + let mut buf = ciphertext.clone(); + let mut cipher = ChaCha20::new(&KEY.into(), &NONCE.into()); + cipher.apply_keystream(&mut buf); + + assert_eq!(&buf, &PLAINTEXT); + } +} + + +// Tests for Xchacha20 +#[cfg(test)] +mod tests { + use super::*; + use xchacha20::cipher::{KeyIvInit, StreamCipher}; + use xchacha20::xChaCha20; + + // RFC 8439 test vector + const KEY: [u8; 32] = [ + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ]; + const NONCE: [u8; 12] = [0; 12]; + const PLAINTEXT: [u8; 16] = *b"example message!"; + + #[test] + fn xchacha20_vector_matches() { + let (ciphertext, _) = encrypt_xchacha20(&KEY, &NONCE, &PLAINTEXT).unwrap(); + + // host decryption + let mut buf = ciphertext.clone(); + let mut cipher = xChaCha20::new(&KEY.into(), &NONCE.into()); + cipher.apply_keystream(&mut buf); + + assert_eq!(&buf, &PLAINTEXT); + } +} + diff --git a/encryption-demo/src/main.rs b/encryption-demo/src/main.rs new file mode 100644 index 0000000..1a4baf5 --- /dev/null +++ b/encryption-demo/src/main.rs @@ -0,0 +1 @@ + diff --git a/risc0-selective-privacy-poc/.DS_Store b/risc0-selective-privacy-poc/.DS_Store new file mode 100644 index 0000000..a84a518 Binary files /dev/null and b/risc0-selective-privacy-poc/.DS_Store differ