Cl: nullifier proof statement (#102)

* cl: nullifier proof

* cl: cli action to select proof type
This commit is contained in:
davidrusu 2024-07-11 12:45:09 +04:00 committed by GitHub
parent 2c7c483707
commit 3d2459052c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 201 additions and 33 deletions

View File

@ -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;

View File

@ -67,15 +67,14 @@ pub fn verify_path(leaf: [u8; 32], path: &[PathNode], root: [u8; 32]) -> bool {
}
pub fn path<const N: usize>(leaves: [[u8; 32]; N], idx: usize) -> Vec<PathNode> {
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 {

View File

@ -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]);

View File

@ -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(),
}
}

View File

@ -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] {

View File

@ -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"]

View File

@ -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" }

View File

@ -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" }

View File

@ -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<merkle::PathNode>,
) {
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<merkle::PathNode> = env::read();
eprintln!("parse input: {}", env::cycle_count());
execute(cm_root, nf, nf_sk, output, cm_path);
}