From 3d2459052c946a63f4049070154d7ffc14c02a02 Mon Sep 17 00:00:00 2001 From: davidrusu Date: Thu, 11 Jul 2024 12:45:09 +0400 Subject: [PATCH] Cl: nullifier proof statement (#102) * cl: nullifier proof * cl: cli action to select proof type --- cl/src/lib.rs | 8 ++ cl/src/merkle.rs | 7 +- cl/src/nullifier.rs | 6 ++ cl/src/output.rs | 6 +- goas/host/src/main.rs | 121 +++++++++++++++++++++++------ goas/methods/Cargo.toml | 4 +- goas/methods/guest/Cargo.toml | 5 +- goas/methods/nullifier/Cargo.toml | 20 +++++ goas/methods/nullifier/src/main.rs | 57 ++++++++++++++ 9 files changed, 201 insertions(+), 33 deletions(-) create mode 100644 goas/methods/nullifier/Cargo.toml create mode 100644 goas/methods/nullifier/src/main.rs diff --git a/cl/src/lib.rs b/cl/src/lib.rs index 26c3555..c50b763 100644 --- a/cl/src/lib.rs +++ b/cl/src/lib.rs @@ -9,5 +9,13 @@ pub mod nullifier; pub mod output; pub mod partial_tx; +pub use balance::{Balance, BalanceWitness}; +pub use bundle::{Bundle, BundleWitness}; +pub use input::{Input, InputWitness}; +pub use note::{NoteCommitment, NoteWitness}; +pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret}; +pub use output::{Output, OutputWitness}; +pub use partial_tx::{PartialTx, PartialTxWitness, PtxRoot}; + #[cfg(test)] mod test_util; diff --git a/cl/src/merkle.rs b/cl/src/merkle.rs index 327c0d1..a5c8eda 100644 --- a/cl/src/merkle.rs +++ b/cl/src/merkle.rs @@ -67,15 +67,14 @@ pub fn verify_path(leaf: [u8; 32], path: &[PathNode], root: [u8; 32]) -> bool { } pub fn path(leaves: [[u8; 32]; N], idx: usize) -> Vec { - let n = leaves.len(); - assert!(n.is_power_of_two()); - assert!(idx < n); + assert!(N.is_power_of_two()); + assert!(idx < N); let mut nodes = leaves; let mut path = Vec::new(); let mut idx = idx; - for h in (1..=n.ilog2()).rev() { + for h in (1..=N.ilog2()).rev() { if idx % 2 == 0 { path.push(PathNode::Right(nodes[idx + 1])); } else { diff --git a/cl/src/nullifier.rs b/cl/src/nullifier.rs index 02d097a..a54c3bf 100644 --- a/cl/src/nullifier.rs +++ b/cl/src/nullifier.rs @@ -9,6 +9,12 @@ use blake2::{Blake2s256, Digest}; use rand_core::RngCore; use serde::{Deserialize, Serialize}; +// TODO: create a nullifier witness and use it throughout. +// struct NullifierWitness { +// nf_sk: NullifierSecret, +// nonce: NullifierNonce +// } + // Maintained privately by note holder #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct NullifierSecret([u8; 16]); diff --git a/cl/src/output.rs b/cl/src/output.rs index f25c609..d484016 100644 --- a/cl/src/output.rs +++ b/cl/src/output.rs @@ -30,9 +30,13 @@ impl OutputWitness { } } + pub fn commit_note(&self) -> NoteCommitment { + self.note.commit(self.nf_pk, self.nonce) + } + pub fn commit(&self) -> Output { Output { - note_comm: self.note.commit(self.nf_pk, self.nonce), + note_comm: self.commit_note(), balance: self.note.balance(), } } diff --git a/goas/host/src/main.rs b/goas/host/src/main.rs index a8347b0..ef54db8 100644 --- a/goas/host/src/main.rs +++ b/goas/host/src/main.rs @@ -1,22 +1,19 @@ // These constants represent the RISC-V ELF and the image ID generated by risc0-build. // The ELF is used for proving and the ID is used for verification. use blake2::{Blake2s256, Digest}; -use methods::{METHOD_ELF, METHOD_ID}; -use risc0_zkvm::{default_prover, ExecutorEnv}; use common::*; -use cl::note::NoteWitness; -use cl::input::InputWitness; -use cl::output::OutputWitness; -use cl::nullifier::NullifierSecret; -use cl::partial_tx::{PartialTx, PartialTxWitness}; -use cl::merkle; +use risc0_zkvm::{default_prover, ExecutorEnv}; -fn main() { - // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` - tracing_subscriber::fmt() - .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) - .init(); +use clap::Parser; +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +enum Action { + Stf, + Nullifier, +} + +fn stf_prove_stark() { let mut rng = rand::thread_rng(); let state: State = [(0, 1000)].into_iter().collect(); @@ -29,20 +26,24 @@ fn main() { let in_state_cm = calculate_state_hash(&state); let in_journal_cm = calculate_journal_hash(&journal); - let in_state_root = merkle::node(in_state_cm, in_journal_cm); - let in_note = NoteWitness::new(1, "ZONE", in_state_root, &mut rng); + let in_state_root = cl::merkle::node(in_state_cm, in_journal_cm); + let in_note = cl::NoteWitness::new(1, "ZONE", in_state_root, &mut rng); let mut out_journal = journal.clone(); out_journal.push(zone_input); let out_state_cm = calculate_state_hash(&stf(state.clone(), zone_input)); let out_journal_cm = calculate_journal_hash(&out_journal); - let out_state_root = merkle::node(out_state_cm, out_journal_cm); - let out_note = NoteWitness::new(1, "ZONE", out_state_root, &mut rng); + let out_state_root = cl::merkle::node(out_state_cm, out_journal_cm); + let out_note = cl::NoteWitness::new(1, "ZONE", out_state_root, &mut rng); - let input = InputWitness::random(in_note, &mut rng); - let output = OutputWitness::random(out_note, NullifierSecret::random(&mut rng).commit(), &mut rng); - let ptx = PartialTx::from_witness(PartialTxWitness { + let input = cl::InputWitness::random(in_note, &mut rng); + let output = cl::OutputWitness::random( + out_note, + cl::NullifierSecret::random(&mut rng).commit(), + &mut rng, + ); + let ptx = cl::PartialTx::from_witness(cl::PartialTxWitness { inputs: vec![input.clone()], outputs: vec![output.clone()], }); @@ -78,14 +79,15 @@ fn main() { // Obtain the default prover. let prover = default_prover(); - use std::time::Instant; let start_t = Instant::now(); // Proof information by proving the specified ELF binary. // This struct contains the receipt along with statistics about execution of the guest let opts = risc0_zkvm::ProverOpts::succinct(); - let prove_info = prover.prove_with_opts(env, METHOD_ELF, &opts).unwrap(); + let prove_info = prover + .prove_with_opts(env, methods::METHOD_ELF, &opts) + .unwrap(); println!("STARK prover time: {:.2?}", start_t.elapsed()); // extract the receipt. @@ -96,7 +98,80 @@ fn main() { std::fs::write("proof.stark", bincode::serialize(&receipt).unwrap()).unwrap(); // The receipt was verified at the end of proving, but the below code is an // example of how someone else could verify this receipt. - receipt.verify(METHOD_ID).unwrap(); + receipt.verify(methods::METHOD_ID).unwrap(); +} + +fn nf_prove_stark() { + let mut rng = rand::thread_rng(); + + let nf_sk = cl::NullifierSecret::random(&mut rng); + + let output = cl::OutputWitness { + note: cl::NoteWitness { + balance: cl::BalanceWitness::random(10, "NMO", &mut rng), + death_constraint: vec![], + state: [0u8; 32], + }, + nf_pk: nf_sk.commit(), + nonce: cl::NullifierNonce::random(&mut rng), + }; + let output_cm = output.commit_note().as_bytes().to_vec(); + let cm_set = cl::merkle::padded_leaves::<64>(&[output_cm]); + let cm_root = cl::merkle::root(cm_set); + let cm_path = cl::merkle::path(cm_set, 0); + let nf = cl::Nullifier::new(nf_sk, output.nonce); + + let env = ExecutorEnv::builder() + .write(&cm_root) + .unwrap() + .write(&nf) + .unwrap() + .write(&nf_sk) + .unwrap() + .write(&output) + .unwrap() + .write(&cm_path) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = default_prover(); + + use std::time::Instant; + let start_t = Instant::now(); + + // Proof information by proving the specified ELF binary. + // This struct contains the receipt along with statistics about execution of the guest + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, methods::NULLIFIER_ELF, &opts) + .unwrap(); + + println!("STARK prover time: {:.2?}", start_t.elapsed()); + // extract the receipt. + let receipt = prove_info.receipt; + + // TODO: Implement code for retrieving receipt journal here. + + std::fs::write("proof.stark", bincode::serialize(&receipt).unwrap()).unwrap(); + // The receipt was verified at the end of proving, but the below code is an + // example of how someone else could verify this receipt. + receipt.verify(methods::NULLIFIER_ID).unwrap(); +} + +fn main() { + // Initialize tracing. In order to view logs, run `RUST_LOG=info cargo run` + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .init(); + + let action = Action::parse(); + + match action { + Action::Stf => stf_prove_stark(), + Action::Nullifier => nf_prove_stark(), + } } fn calculate_state_hash(state: &State) -> [u8; 32] { diff --git a/goas/methods/Cargo.toml b/goas/methods/Cargo.toml index 65b46f9..7160950 100644 --- a/goas/methods/Cargo.toml +++ b/goas/methods/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [build-dependencies] -risc0-build = { version = "1.0.1" } +risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["guest"] +methods = ["guest", "nullifier"] diff --git a/goas/methods/guest/Cargo.toml b/goas/methods/guest/Cargo.toml index 077d9bf..2445877 100644 --- a/goas/methods/guest/Cargo.toml +++ b/goas/methods/guest/Cargo.toml @@ -8,18 +8,17 @@ edition = "2021" [workspace] [dependencies] -risc0-zkvm = { version = "1.0.1", default-features = false, features = ['std'] } +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } blake2 = "0.10" serde = { version = "1.0", features = ["derive"] } bincode = "1" common = { path = "../../common" } cl = { path = "../../../cl" } - [patch.crates-io] # Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint # multiplication accelerator support for all downstream usages of the following crates. sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } -k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } +# k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" } diff --git a/goas/methods/nullifier/Cargo.toml b/goas/methods/nullifier/Cargo.toml new file mode 100644 index 0000000..6297c02 --- /dev/null +++ b/goas/methods/nullifier/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "nullifier" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +bincode = "1" +cl = { path = "../../../cl" } + +[patch.crates-io] +# Placing these patch statement in the workspace Cargo.toml will add RISC Zero SHA-256 and bigint +# multiplication accelerator support for all downstream usages of the following crates. +sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.8-risczero.0" } +# k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.3-risczero.0" } +crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" } +curve25519-dalek = { git = "https://github.com/risc0/curve25519-dalek", tag = "curve25519-4.1.2-risczero.0" } diff --git a/goas/methods/nullifier/src/main.rs b/goas/methods/nullifier/src/main.rs new file mode 100644 index 0000000..4ff79da --- /dev/null +++ b/goas/methods/nullifier/src/main.rs @@ -0,0 +1,57 @@ +/// Nullifier Proof +/// +/// Our goal: prove the nullifier nf was derived from a note that had previously been committed to. +/// +/// More formally, nullifier statement says: +/// for public input `nf` (nullifier) and `root_cm` (root of merkle tree over commitment set). +/// the prover has knowledge of `output = (note, nf_pk, nonce)`, `nf` and `path` s.t. that the following constraints hold +/// 0. nf_pk = hash(nf_sk) +/// 1. nf = hash(nonce||nf_sk) +/// 2. note_cm = output_commitment(output) +/// 3. verify_merkle_path(note_cm, root, path) +use cl::merkle; +use cl::nullifier::{Nullifier, NullifierSecret}; +use cl::output::OutputWitness; +use risc0_zkvm::guest::env; + +fn execute( + // public + cm_root: [u8; 32], + nf: Nullifier, + // private + nf_sk: NullifierSecret, + output: OutputWitness, + cm_path: Vec, +) { + eprintln!("start exec: {}", env::cycle_count()); + + assert_eq!(output.nf_pk, nf_sk.commit()); + eprintln!("output nullifier: {}", env::cycle_count()); + + assert_eq!(nf, Nullifier::new(nf_sk, output.nonce)); + eprintln!("nullifier: {}", env::cycle_count()); + + let cm_out = output.commit_note(); + eprintln!("out_cm: {}", env::cycle_count()); + + assert!(merkle::verify_path( + merkle::leaf(cm_out.as_bytes()), + &cm_path, + cm_root + )); + eprintln!("nullifier merkle path: {}", env::cycle_count()); +} + +fn main() { + // public input + let cm_root: [u8; 32] = env::read(); + let nf: Nullifier = env::read(); + + // private input + let nf_sk: NullifierSecret = env::read(); + let output: OutputWitness = env::read(); + let cm_path: Vec = env::read(); + + eprintln!("parse input: {}", env::cycle_count()); + execute(cm_root, nf, nf_sk, output, cm_path); +}