From 4d8a3dfb9c814b838089dc938daab4dad06adcdd Mon Sep 17 00:00:00 2001 From: davidrusu Date: Tue, 16 Jul 2024 18:34:26 +0400 Subject: [PATCH] CL: expand scope of nullifier proof to an "input proof" (#106) * cl: nullifier proof -> input proof; add death_cm to input proof * cl: add death_cm to cl::Input commitment * cl/ledger: prove_input_nullifier -> prove_input --- cl/cl/src/balance.rs | 8 +- cl/cl/src/input.rs | 21 +++-- cl/cl/src/lib.rs | 2 +- cl/cl/src/note.rs | 24 ++++- cl/ledger/src/input.rs | 90 +++++++++++-------- cl/proof_statements/src/input.rs | 21 +++++ cl/proof_statements/src/lib.rs | 2 +- cl/proof_statements/src/nullifier.rs | 22 ----- cl/risc0_proofs/Cargo.toml | 2 +- .../{nullifier => input}/Cargo.toml | 2 +- cl/risc0_proofs/input/src/main.rs | 17 ++++ cl/risc0_proofs/nullifier/src/main.rs | 28 ------ 12 files changed, 128 insertions(+), 111 deletions(-) create mode 100644 cl/proof_statements/src/input.rs delete mode 100644 cl/proof_statements/src/nullifier.rs rename cl/risc0_proofs/{nullifier => input}/Cargo.toml (97%) create mode 100644 cl/risc0_proofs/input/src/main.rs delete mode 100644 cl/risc0_proofs/nullifier/src/main.rs diff --git a/cl/cl/src/balance.rs b/cl/cl/src/balance.rs index 01fb26a..b09202d 100644 --- a/cl/cl/src/balance.rs +++ b/cl/cl/src/balance.rs @@ -7,10 +7,10 @@ lazy_static! { static ref PEDERSON_COMMITMENT_BLINDING_POINT: RistrettoPoint = crate::crypto::hash_to_curve(b"NOMOS_CL_PEDERSON_COMMITMENT_BLINDING"); } -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct Balance(pub RistrettoPoint); -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct BalanceWitness { pub value: u64, pub unit: RistrettoPoint, @@ -27,7 +27,7 @@ impl BalanceWitness { pub fn new(value: u64, unit: impl Into, blinding: Scalar) -> Self { Self { value, - unit: unit_point(&unit.into()).into(), + unit: unit_point(&unit.into()), blinding, } } @@ -37,7 +37,7 @@ impl BalanceWitness { } pub fn commit(&self) -> Balance { - Balance(balance(self.value, self.unit.into(), self.blinding).into()) + Balance(balance(self.value, self.unit, self.blinding)) } } diff --git a/cl/cl/src/input.rs b/cl/cl/src/input.rs index 7feba93..445d8c8 100644 --- a/cl/cl/src/input.rs +++ b/cl/cl/src/input.rs @@ -4,21 +4,20 @@ /// which on their own may not balance (i.e. \sum inputs != \sum outputs) use crate::{ balance::Balance, - note::{NoteCommitment, NoteWitness}, + note::{DeathCommitment, NoteWitness}, nullifier::{Nullifier, NullifierNonce, NullifierSecret}, }; use rand_core::RngCore; -// use risc0_groth16::{PublicInputsJson, Verifier}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Input { - pub note_comm: NoteCommitment, pub nullifier: Nullifier, pub balance: Balance, + pub death_cm: DeathCommitment, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct InputWitness { pub note: NoteWitness, pub nf_sk: NullifierSecret, @@ -36,9 +35,9 @@ impl InputWitness { pub fn commit(&self) -> Input { Input { - note_comm: self.note.commit(self.nf_sk.commit(), self.nonce), nullifier: Nullifier::new(self.nf_sk, self.nonce), balance: self.note.balance(), + death_cm: self.note.death_commitment(), } } @@ -52,11 +51,11 @@ impl InputWitness { } impl Input { - pub fn to_bytes(&self) -> [u8; 96] { - let mut bytes = [0u8; 96]; - bytes[..32].copy_from_slice(self.note_comm.as_bytes()); - bytes[32..64].copy_from_slice(self.nullifier.as_bytes()); - bytes[64..96].copy_from_slice(&self.balance.to_bytes()); + pub fn to_bytes(&self) -> [u8; 64] { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(self.nullifier.as_bytes()); + bytes[32..64].copy_from_slice(&self.balance.to_bytes()); + bytes[64..96].copy_from_slice(&self.death_cm.0); bytes } } diff --git a/cl/cl/src/lib.rs b/cl/cl/src/lib.rs index 0ab85c9..74d8dcf 100644 --- a/cl/cl/src/lib.rs +++ b/cl/cl/src/lib.rs @@ -12,7 +12,7 @@ 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 note::{DeathCommitment, NoteCommitment, NoteWitness}; pub use nullifier::{Nullifier, NullifierCommitment, NullifierNonce, NullifierSecret}; pub use output::{Output, OutputWitness}; pub use partial_tx::{PartialTx, PartialTxWitness, PtxRoot}; diff --git a/cl/cl/src/note.rs b/cl/cl/src/note.rs index a819f7f..0beed54 100644 --- a/cl/cl/src/note.rs +++ b/cl/cl/src/note.rs @@ -7,6 +7,18 @@ use crate::{ nullifier::{NullifierCommitment, NullifierNonce}, }; +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct DeathCommitment(pub [u8; 32]); + +pub fn death_commitment(death_constraint: &[u8]) -> DeathCommitment { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_DEATH_COMMIT"); + hasher.update(death_constraint); + let death_cm: [u8; 32] = hasher.finalize().into(); + + DeathCommitment(death_cm) +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct NoteCommitment([u8; 32]); @@ -18,10 +30,10 @@ impl NoteCommitment { // TODO: Rename Note to NoteWitness and NoteCommitment to Note -#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] pub struct NoteWitness { pub balance: BalanceWitness, - pub death_constraint: Vec, // serialized verification key of death constraint + pub death_constraint: [u8; 32], // death constraint verification key pub state: [u8; 32], } @@ -34,7 +46,7 @@ impl NoteWitness { ) -> Self { Self { balance: BalanceWitness::random(value, unit, rng), - death_constraint: vec![], + death_constraint: [0u8; 32], state, } } @@ -52,7 +64,7 @@ impl NoteWitness { hasher.update(self.state); // COMMIT TO DEATH CONSTRAINT - hasher.update(&self.death_constraint); + hasher.update(self.death_constraint); // COMMIT TO NULLIFIER hasher.update(nf_pk.as_bytes()); @@ -65,6 +77,10 @@ impl NoteWitness { pub fn balance(&self) -> Balance { self.balance.commit() } + + pub fn death_commitment(&self) -> DeathCommitment { + death_commitment(&self.death_constraint) + } } #[cfg(test)] diff --git a/cl/ledger/src/input.rs b/cl/ledger/src/input.rs index 2f3ba55..3e8cf66 100644 --- a/cl/ledger/src/input.rs +++ b/cl/ledger/src/input.rs @@ -1,50 +1,40 @@ -use proof_statements::nullifier::{NullifierPrivate, NullifierPublic}; +use proof_statements::input::{InputPrivate, InputPublic}; use crate::error::Result; const MAX_NOTE_COMMS: usize = 2usize.pow(8); #[derive(Debug, Clone)] -pub struct InputNullifierProof { +pub struct InputProof { receipt: risc0_zkvm::Receipt, } -impl InputNullifierProof { - pub fn public(&self) -> Result { +impl InputProof { + pub fn public(&self) -> Result { Ok(self.receipt.journal.decode()?) } - pub fn verify(&self, expected_public_inputs: NullifierPublic) -> bool { + pub fn verify(&self, expected_public_inputs: &InputPublic) -> bool { let Ok(public_inputs) = self.public() else { return false; }; - public_inputs == expected_public_inputs - && self - .receipt - .verify(nomos_cl_risc0_proofs::NULLIFIER_ID) - .is_ok() + &public_inputs == expected_public_inputs + && self.receipt.verify(nomos_cl_risc0_proofs::INPUT_ID).is_ok() } } -pub fn prove_input_nullifier( - input: &cl::InputWitness, - note_commitments: &[cl::NoteCommitment], -) -> InputNullifierProof { - let output = input.to_output_witness(); +pub fn prove_input(input: cl::InputWitness, note_commitments: &[cl::NoteCommitment]) -> InputProof { + let output_cm = input.to_output_witness().commit_note(); + let cm_leaves = note_commitment_leaves(note_commitments); - let output_cm = output.commit_note(); let cm_idx = note_commitments .iter() .position(|c| c == &output_cm) .unwrap(); let cm_path = cl::merkle::path(cm_leaves, cm_idx); - let secrets = NullifierPrivate { - nf_sk: input.nf_sk, - output, - cm_path, - }; + let secrets = InputPrivate { input, cm_path }; let env = risc0_zkvm::ExecutorEnv::builder() .write(&secrets) @@ -62,7 +52,7 @@ pub fn prove_input_nullifier( // 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, nomos_cl_risc0_proofs::NULLIFIER_ELF, &opts) + .prove_with_opts(env, nomos_cl_risc0_proofs::INPUT_ELF, &opts) .unwrap(); println!( @@ -72,7 +62,7 @@ pub fn prove_input_nullifier( ); // extract the receipt. let receipt = prove_info.receipt; - InputNullifierProof { receipt } + InputProof { receipt } } fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; MAX_NOTE_COMMS] { @@ -83,10 +73,9 @@ fn note_commitment_leaves(note_commitments: &[cl::NoteCommitment]) -> [[u8; 32]; #[cfg(test)] mod test { - use proof_statements::nullifier::NullifierPublic; use rand::thread_rng; - use super::{note_commitment_leaves, prove_input_nullifier}; + use super::*; #[test] fn test_input_nullifier_prover() { @@ -94,7 +83,7 @@ mod test { let input = cl::InputWitness { note: cl::NoteWitness { balance: cl::BalanceWitness::random(32, "NMO", &mut rng), - death_constraint: vec![], + death_constraint: [0u8; 32], state: [0u8; 32], }, nf_sk: cl::NullifierSecret::random(&mut rng), @@ -103,24 +92,49 @@ mod test { let notes = vec![input.to_output_witness().commit_note()]; - let proof = prove_input_nullifier(&input, ¬es); + let proof = prove_input(input, ¬es); - let expected_public_inputs = NullifierPublic { + let expected_public_inputs = InputPublic { cm_root: cl::merkle::root(note_commitment_leaves(¬es)), - nf: input.commit().nullifier, + input: input.commit(), }; - assert!(proof.verify(expected_public_inputs)); + assert!(proof.verify(&expected_public_inputs)); - let wrong_public_inputs = NullifierPublic { - cm_root: cl::merkle::root(note_commitment_leaves(¬es)), - nf: cl::Nullifier::new( - cl::NullifierSecret::random(&mut rng), - cl::NullifierNonce::random(&mut rng), - ), - }; + let wrong_public_inputs = [ + InputPublic { + cm_root: cl::merkle::root([cl::merkle::leaf(b"bad_root")]), + ..expected_public_inputs + }, + InputPublic { + input: cl::Input { + nullifier: cl::Nullifier::new( + cl::NullifierSecret::random(&mut rng), + cl::NullifierNonce::random(&mut rng), + ), + ..expected_public_inputs.input + }, + ..expected_public_inputs + }, + InputPublic { + input: cl::Input { + death_cm: cl::note::death_commitment(b"wrong death vk"), + ..expected_public_inputs.input + }, + ..expected_public_inputs + }, + InputPublic { + input: cl::Input { + balance: cl::BalanceWitness::random(32, "NMO", &mut rng).commit(), + ..expected_public_inputs.input + }, + ..expected_public_inputs + }, + ]; - assert!(!proof.verify(wrong_public_inputs)); + for wrong_input in wrong_public_inputs { + assert!(!proof.verify(&wrong_input)); + } } // ----- The following tests still need to be built. ----- diff --git a/cl/proof_statements/src/input.rs b/cl/proof_statements/src/input.rs new file mode 100644 index 0000000..20dab89 --- /dev/null +++ b/cl/proof_statements/src/input.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +/// for public inputs `nf` (nullifier), `root_cm` (root of merkle tree over commitment set) and `death_cm` (commitment to death constraint). +/// 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) +/// 4. death_cm = death_commitment(note.death_constraint) + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct InputPublic { + pub cm_root: [u8; 32], + pub input: cl::Input, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct InputPrivate { + pub input: cl::InputWitness, + pub cm_path: Vec, +} diff --git a/cl/proof_statements/src/lib.rs b/cl/proof_statements/src/lib.rs index 1d68566..7839bc5 100644 --- a/cl/proof_statements/src/lib.rs +++ b/cl/proof_statements/src/lib.rs @@ -1 +1 @@ -pub mod nullifier; +pub mod input; diff --git a/cl/proof_statements/src/nullifier.rs b/cl/proof_statements/src/nullifier.rs deleted file mode 100644 index 709aa9d..0000000 --- a/cl/proof_statements/src/nullifier.rs +++ /dev/null @@ -1,22 +0,0 @@ -use serde::{Deserialize, Serialize}; - -/// 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) - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct NullifierPublic { - pub cm_root: [u8; 32], - pub nf: cl::Nullifier, - // TODO: we need a way to link this statement to a particular input. i.e. prove that the nullifier is actually derived from the input note. -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct NullifierPrivate { - pub nf_sk: cl::NullifierSecret, - pub output: cl::OutputWitness, - pub cm_path: Vec, -} diff --git a/cl/risc0_proofs/Cargo.toml b/cl/risc0_proofs/Cargo.toml index 18a6183..198a3c0 100644 --- a/cl/risc0_proofs/Cargo.toml +++ b/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["nullifier"] +methods = ["input"] diff --git a/cl/risc0_proofs/nullifier/Cargo.toml b/cl/risc0_proofs/input/Cargo.toml similarity index 97% rename from cl/risc0_proofs/nullifier/Cargo.toml rename to cl/risc0_proofs/input/Cargo.toml index 7a93f84..f39def3 100644 --- a/cl/risc0_proofs/nullifier/Cargo.toml +++ b/cl/risc0_proofs/input/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "nullifier" +name = "input" version = "0.1.0" edition = "2021" diff --git a/cl/risc0_proofs/input/src/main.rs b/cl/risc0_proofs/input/src/main.rs new file mode 100644 index 0000000..b84517f --- /dev/null +++ b/cl/risc0_proofs/input/src/main.rs @@ -0,0 +1,17 @@ +/// Input Proof +use cl::merkle; +use proof_statements::input::{InputPrivate, InputPublic}; +use risc0_zkvm::guest::env; + +fn main() { + let secret: InputPrivate = env::read(); + + let out_cm = secret.input.to_output_witness().commit_note(); + let cm_leaf = merkle::leaf(out_cm.as_bytes()); + let cm_root = merkle::path_root(cm_leaf, &secret.cm_path); + + env::commit(&InputPublic { + input: secret.input.commit(), + cm_root, + }); +} diff --git a/cl/risc0_proofs/nullifier/src/main.rs b/cl/risc0_proofs/nullifier/src/main.rs deleted file mode 100644 index 9c5c15e..0000000 --- a/cl/risc0_proofs/nullifier/src/main.rs +++ /dev/null @@ -1,28 +0,0 @@ -/// 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; -use proof_statements::nullifier::{NullifierPrivate, NullifierPublic}; -use risc0_zkvm::guest::env; - -fn main() { - let secret: NullifierPrivate = env::read(); - assert_eq!(secret.output.nf_pk, secret.nf_sk.commit()); - - let cm_out = secret.output.commit_note(); - let cm_leaf = merkle::leaf(cm_out.as_bytes()); - let cm_root = merkle::path_root(cm_leaf, &secret.cm_path); - - let nf = Nullifier::new(secret.nf_sk, secret.output.nonce); - - env::commit(&NullifierPublic { cm_root, nf }); -}