From ec5cd13d46c9cba1b1dad36ce474df214b5e0117 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Mon, 18 Nov 2024 14:07:04 +0100 Subject: [PATCH 01/10] emmarin --- emmarin/cl/.gitignore | 2 + emmarin/cl/Cargo.toml | 11 + emmarin/cl/cl/Cargo.toml | 15 ++ emmarin/cl/cl/src/balance.rs | 149 +++++++++++ emmarin/cl/cl/src/bundle.rs | 117 +++++++++ emmarin/cl/cl/src/crypto.rs | 6 + emmarin/cl/cl/src/error.rs | 4 + emmarin/cl/cl/src/input.rs | 85 +++++++ emmarin/cl/cl/src/lib.rs | 21 ++ emmarin/cl/cl/src/merkle.rs | 239 ++++++++++++++++++ emmarin/cl/cl/src/note.rs | 171 +++++++++++++ emmarin/cl/cl/src/nullifier.rs | 161 ++++++++++++ emmarin/cl/cl/src/output.rs | 45 ++++ emmarin/cl/cl/src/partial_tx.rs | 217 ++++++++++++++++ emmarin/cl/cl/src/zones.rs | 100 ++++++++ emmarin/cl/cl/tests/simple_transfer.rs | 37 +++ emmarin/cl/ledger/Cargo.toml | 16 ++ emmarin/cl/ledger/src/bundle.rs | 67 +++++ emmarin/cl/ledger/src/constraint.rs | 75 ++++++ emmarin/cl/ledger/src/error.rs | 11 + emmarin/cl/ledger/src/ledger.rs | 128 ++++++++++ emmarin/cl/ledger/src/lib.rs | 8 + emmarin/cl/ledger/src/pact.rs | 75 ++++++ emmarin/cl/ledger/src/partial_tx.rs | 77 ++++++ emmarin/cl/ledger/tests/simple_transfer.rs | 126 +++++++++ emmarin/cl/ledger_proof_statements/Cargo.toml | 8 + .../cl/ledger_proof_statements/src/bundle.rs | 12 + .../ledger_proof_statements/src/constraint.rs | 8 + .../cl/ledger_proof_statements/src/ledger.rs | 30 +++ emmarin/cl/ledger_proof_statements/src/lib.rs | 5 + .../cl/ledger_proof_statements/src/pact.rs | 15 ++ emmarin/cl/ledger_proof_statements/src/ptx.rs | 16 ++ emmarin/cl/ledger_validity_proof/Cargo.toml | 11 + emmarin/cl/ledger_validity_proof/build.rs | 3 + .../ledger_validity_proof/ledger/Cargo.toml | 19 ++ .../ledger_validity_proof/ledger/src/main.rs | 146 +++++++++++ emmarin/cl/ledger_validity_proof/src/lib.rs | 1 + emmarin/cl/risc0_proofs/Cargo.toml | 11 + emmarin/cl/risc0_proofs/build.rs | 3 + emmarin/cl/risc0_proofs/bundle/Cargo.toml | 19 ++ emmarin/cl/risc0_proofs/bundle/src/main.rs | 23 ++ .../cl/risc0_proofs/constraint_nop/Cargo.toml | 19 ++ .../risc0_proofs/constraint_nop/src/main.rs | 8 + emmarin/cl/risc0_proofs/pact/Cargo.toml | 19 ++ emmarin/cl/risc0_proofs/pact/src/main.rs | 30 +++ emmarin/cl/risc0_proofs/ptx/Cargo.toml | 19 ++ emmarin/cl/risc0_proofs/ptx/src/main.rs | 29 +++ emmarin/cl/risc0_proofs/src/lib.rs | 1 + 48 files changed, 2418 insertions(+) create mode 100644 emmarin/cl/.gitignore create mode 100644 emmarin/cl/Cargo.toml create mode 100644 emmarin/cl/cl/Cargo.toml create mode 100644 emmarin/cl/cl/src/balance.rs create mode 100644 emmarin/cl/cl/src/bundle.rs create mode 100644 emmarin/cl/cl/src/crypto.rs create mode 100644 emmarin/cl/cl/src/error.rs create mode 100644 emmarin/cl/cl/src/input.rs create mode 100644 emmarin/cl/cl/src/lib.rs create mode 100644 emmarin/cl/cl/src/merkle.rs create mode 100644 emmarin/cl/cl/src/note.rs create mode 100644 emmarin/cl/cl/src/nullifier.rs create mode 100644 emmarin/cl/cl/src/output.rs create mode 100644 emmarin/cl/cl/src/partial_tx.rs create mode 100644 emmarin/cl/cl/src/zones.rs create mode 100644 emmarin/cl/cl/tests/simple_transfer.rs create mode 100644 emmarin/cl/ledger/Cargo.toml create mode 100644 emmarin/cl/ledger/src/bundle.rs create mode 100644 emmarin/cl/ledger/src/constraint.rs create mode 100644 emmarin/cl/ledger/src/error.rs create mode 100644 emmarin/cl/ledger/src/ledger.rs create mode 100644 emmarin/cl/ledger/src/lib.rs create mode 100644 emmarin/cl/ledger/src/pact.rs create mode 100644 emmarin/cl/ledger/src/partial_tx.rs create mode 100644 emmarin/cl/ledger/tests/simple_transfer.rs create mode 100644 emmarin/cl/ledger_proof_statements/Cargo.toml create mode 100644 emmarin/cl/ledger_proof_statements/src/bundle.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/constraint.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/ledger.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/lib.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/pact.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/ptx.rs create mode 100644 emmarin/cl/ledger_validity_proof/Cargo.toml create mode 100644 emmarin/cl/ledger_validity_proof/build.rs create mode 100644 emmarin/cl/ledger_validity_proof/ledger/Cargo.toml create mode 100644 emmarin/cl/ledger_validity_proof/ledger/src/main.rs create mode 100644 emmarin/cl/ledger_validity_proof/src/lib.rs create mode 100644 emmarin/cl/risc0_proofs/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/build.rs create mode 100644 emmarin/cl/risc0_proofs/bundle/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/bundle/src/main.rs create mode 100644 emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/constraint_nop/src/main.rs create mode 100644 emmarin/cl/risc0_proofs/pact/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/pact/src/main.rs create mode 100644 emmarin/cl/risc0_proofs/ptx/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/ptx/src/main.rs create mode 100644 emmarin/cl/risc0_proofs/src/lib.rs diff --git a/emmarin/cl/.gitignore b/emmarin/cl/.gitignore new file mode 100644 index 0000000..b354aec --- /dev/null +++ b/emmarin/cl/.gitignore @@ -0,0 +1,2 @@ +Cargo.lock +target/ \ No newline at end of file diff --git a/emmarin/cl/Cargo.toml b/emmarin/cl/Cargo.toml new file mode 100644 index 0000000..836a57c --- /dev/null +++ b/emmarin/cl/Cargo.toml @@ -0,0 +1,11 @@ +[workspace] +resolver = "2" +members = [ "cl", "ledger", "ledger_proof_statements", "risc0_proofs", "ledger_validity_proof"] + +# Always optimize; building and running the risc0_proofs takes much longer without optimization. +[profile.dev] +opt-level = 3 + +[profile.release] +debug = 1 +lto = true diff --git a/emmarin/cl/cl/Cargo.toml b/emmarin/cl/cl/Cargo.toml new file mode 100644 index 0000000..b73749b --- /dev/null +++ b/emmarin/cl/cl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "cl" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = {version="1.0", features = ["derive"]} +group = "0.13.0" +rand = "0.8.5" +rand_core = "0.6.0" +hex = "0.4.3" +curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]} +sha2 = "0.10" diff --git a/emmarin/cl/cl/src/balance.rs b/emmarin/cl/cl/src/balance.rs new file mode 100644 index 0000000..10db851 --- /dev/null +++ b/emmarin/cl/cl/src/balance.rs @@ -0,0 +1,149 @@ +use rand_core::CryptoRngCore; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::PartialTxWitness; + +pub type Value = u64; +pub type Unit = [u8; 32]; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct Balance([u8; 32]); + +impl Balance { + pub fn to_bytes(&self) -> [u8; 32] { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct UnitBalance { + pub unit: Unit, + pub pos: u64, + pub neg: u64, +} + +impl UnitBalance { + pub fn is_zero(&self) -> bool { + self.pos == self.neg + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +pub struct BalanceWitness { + pub balances: Vec, + pub blinding: [u8; 16], +} + +impl BalanceWitness { + pub fn random_blinding(mut rng: impl CryptoRngCore) -> [u8; 16] { + let mut blinding = [0u8; 16]; + rng.fill_bytes(&mut blinding); + + blinding + } + + pub fn zero(blinding: [u8; 16]) -> Self { + Self { + balances: Default::default(), + blinding, + } + } + + pub fn from_ptx(ptx: &PartialTxWitness, blinding: [u8; 16]) -> Self { + let mut balance = Self::zero(blinding); + + for input in ptx.inputs.iter() { + balance.insert_negative(input.note.unit, input.note.value); + } + + for output in ptx.outputs.iter() { + balance.insert_positive(output.note.unit, output.note.value); + } + + balance.clear_zeros(); + + balance + } + + pub fn insert_positive(&mut self, unit: Unit, value: Value) { + for unit_bal in self.balances.iter_mut() { + if unit_bal.unit == unit { + unit_bal.pos += value; + return; + } + } + + // Unit was not found, so we must create one. + self.balances.push(UnitBalance { + unit, + pos: value, + neg: 0, + }); + } + + pub fn insert_negative(&mut self, unit: Unit, value: Value) { + for unit_bal in self.balances.iter_mut() { + if unit_bal.unit == unit { + unit_bal.neg += value; + return; + } + } + + self.balances.push(UnitBalance { + unit, + pos: 0, + neg: value, + }); + } + + pub fn clear_zeros(&mut self) { + let mut i = 0usize; + while i < self.balances.len() { + if self.balances[i].is_zero() { + self.balances.swap_remove(i); + // don't increment `i` since the last element has been swapped into the + // `i`'th place + } else { + i += 1; + } + } + } + + pub fn combine(balances: impl IntoIterator, blinding: [u8; 16]) -> Self { + let mut combined = BalanceWitness::zero(blinding); + + for balance in balances { + for unit_bal in balance.balances.iter() { + if unit_bal.pos > unit_bal.neg { + combined.insert_positive(unit_bal.unit, unit_bal.pos - unit_bal.neg); + } else { + combined.insert_negative(unit_bal.unit, unit_bal.neg - unit_bal.pos); + } + } + } + + combined.clear_zeros(); + + combined + } + + pub fn is_zero(&self) -> bool { + self.balances.is_empty() + } + + pub fn commit(&self) -> Balance { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_BAL_COMMIT"); + + for unit_balance in self.balances.iter() { + hasher.update(unit_balance.unit); + hasher.update(unit_balance.pos.to_le_bytes()); + hasher.update(unit_balance.neg.to_le_bytes()); + } + hasher.update(self.blinding); + + let commit_bytes: [u8; 32] = hasher.finalize().into(); + Balance(commit_bytes) + } +} diff --git a/emmarin/cl/cl/src/bundle.rs b/emmarin/cl/cl/src/bundle.rs new file mode 100644 index 0000000..c54e888 --- /dev/null +++ b/emmarin/cl/cl/src/bundle.rs @@ -0,0 +1,117 @@ +use serde::{Deserialize, Serialize}; + +use crate::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; + +/// The transaction bundle is a collection of partial transactions. +/// The goal in bundling transactions is to produce a set of partial transactions +/// that balance each other. + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Bundle { + pub partials: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundleWitness { + pub partials: Vec, +} + +impl BundleWitness { + pub fn balance(&self) -> BalanceWitness { + BalanceWitness::combine(self.partials.iter().map(|ptx| ptx.balance()), [0u8; 16]) + } + + // pub fn commit(&self) -> Bundle { + // Bundle { + // partials: Vec::from_iter(self.partials.iter().map(|ptx| ptx.commit())), + // } + // } +} + +#[cfg(test)] +mod test { + use crate::{ + balance::UnitBalance, + input::InputWitness, + note::{derive_unit, NoteWitness}, + nullifier::NullifierSecret, + output::OutputWitness, + partial_tx::PartialTxWitness, + }; + + use super::*; + + #[test] + fn test_bundle_balance() { + let mut rng = rand::thread_rng(); + let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV")); + + let nf_a = NullifierSecret::random(&mut rng); + let nf_b = NullifierSecret::random(&mut rng); + let nf_c = NullifierSecret::random(&mut rng); + + let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); + let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a); + + let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); + let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b); + + let crv_4840_out = + OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); + + let ptx_unbalanced = PartialTxWitness { + inputs: vec![nmo_10_in, eth_23_in], + outputs: vec![crv_4840_out], + balance_blinding: BalanceWitness::random_blinding(&mut rng), + }; + + let bundle_witness = BundleWitness { + partials: vec![ptx_unbalanced.clone()], + }; + + assert!(!bundle_witness.balance().is_zero()); + assert_eq!( + bundle_witness.balance().balances, + vec![ + UnitBalance { + unit: nmo, + pos: 0, + neg: 10 + }, + UnitBalance { + unit: eth, + pos: 0, + neg: 23 + }, + UnitBalance { + unit: crv, + pos: 4840, + neg: 0 + }, + ] + ); + + let crv_4840_in = InputWitness::from_output(crv_4840_out, nf_c); + let nmo_10_out = OutputWitness::new( + NoteWitness::basic(10, nmo, &mut rng), + NullifierSecret::random(&mut rng).commit(), // transferring to a random owner + ); + let eth_23_out = OutputWitness::new( + NoteWitness::basic(23, eth, &mut rng), + NullifierSecret::random(&mut rng).commit(), // transferring to a random owner + ); + + let ptx_solved = PartialTxWitness { + inputs: vec![crv_4840_in], + outputs: vec![nmo_10_out, eth_23_out], + balance_blinding: BalanceWitness::random_blinding(&mut rng), + }; + + let witness = BundleWitness { + partials: vec![ptx_unbalanced, ptx_solved], + }; + + assert!(witness.balance().is_zero()); + assert_eq!(witness.balance().balances, vec![]); + } +} diff --git a/emmarin/cl/cl/src/crypto.rs b/emmarin/cl/cl/src/crypto.rs new file mode 100644 index 0000000..de35b1b --- /dev/null +++ b/emmarin/cl/cl/src/crypto.rs @@ -0,0 +1,6 @@ +use curve25519_dalek::ristretto::RistrettoPoint; +use sha2::Sha512; + +pub fn hash_to_curve(bytes: &[u8]) -> RistrettoPoint { + RistrettoPoint::hash_from_bytes::(bytes) +} diff --git a/emmarin/cl/cl/src/error.rs b/emmarin/cl/cl/src/error.rs new file mode 100644 index 0000000..03d01d1 --- /dev/null +++ b/emmarin/cl/cl/src/error.rs @@ -0,0 +1,4 @@ +#[derive(Debug)] +pub enum Error { + ProofFailed, +} diff --git a/emmarin/cl/cl/src/input.rs b/emmarin/cl/cl/src/input.rs new file mode 100644 index 0000000..5253ce9 --- /dev/null +++ b/emmarin/cl/cl/src/input.rs @@ -0,0 +1,85 @@ +/// This module defines the partial transaction structure. +/// +/// Partial transactions, as the name suggests, are transactions +/// which on their own may not balance (i.e. \sum inputs != \sum outputs) +use crate::{ + note::{Constraint, NoteWitness}, + nullifier::{Nullifier, NullifierSecret}, + Nonce, +}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Input { + pub nullifier: Nullifier, + pub constraint: Constraint, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct InputWitness { + pub note: NoteWitness, + pub nf_sk: NullifierSecret, +} + +impl InputWitness { + pub fn new(note: NoteWitness, nf_sk: NullifierSecret) -> Self { + Self { note, nf_sk } + } + + pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { + assert_eq!(nf_sk.commit(), output.nf_pk); + Self::new(output.note, nf_sk) + } + + pub fn public(output: crate::OutputWitness) -> Self { + let nf_sk = NullifierSecret::zero(); + assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO + Self::new(output.note, nf_sk) + } + + pub fn evolved_nonce(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> Nonce { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_COIN_EVOLVE"); + hasher.update(domain); + hasher.update(self.nf_sk.0); + hasher.update(self.note.commit(tag, self.nf_sk.commit()).0); + + let nonce_bytes: [u8; 32] = hasher.finalize().into(); + Nonce::from_bytes(nonce_bytes) + } + + pub fn evolve_output(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> crate::OutputWitness { + crate::OutputWitness { + note: NoteWitness { + nonce: self.evolved_nonce(tag, domain), + ..self.note + }, + nf_pk: self.nf_sk.commit(), + } + } + + pub fn nullifier(&self, tag: &dyn AsRef<[u8]>) -> Nullifier { + Nullifier::new(tag, self.nf_sk, self.note_commitment(tag)) + } + + pub fn commit(&self, tag: &dyn AsRef<[u8]>) -> Input { + Input { + nullifier: self.nullifier(tag), + constraint: self.note.constraint, + } + } + + pub fn note_commitment(&self, tag: &dyn AsRef<[u8]>) -> crate::NoteCommitment { + self.note.commit(tag, self.nf_sk.commit()) + } +} + +impl Input { + 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.constraint.0); + bytes + } +} diff --git a/emmarin/cl/cl/src/lib.rs b/emmarin/cl/cl/src/lib.rs new file mode 100644 index 0000000..fea07f5 --- /dev/null +++ b/emmarin/cl/cl/src/lib.rs @@ -0,0 +1,21 @@ +pub mod balance; +pub mod bundle; +pub mod crypto; +pub mod error; +pub mod input; +pub mod merkle; +pub mod note; +pub mod nullifier; +pub mod output; +pub mod partial_tx; +pub mod zones; + +pub use balance::{Balance, BalanceWitness}; +pub use bundle::{Bundle, BundleWitness}; +pub use input::{Input, InputWitness}; +pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; +pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; +pub use output::{Output, OutputWitness}; +pub use partial_tx::{ + PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot, +}; diff --git a/emmarin/cl/cl/src/merkle.rs b/emmarin/cl/cl/src/merkle.rs new file mode 100644 index 0000000..bb10e3c --- /dev/null +++ b/emmarin/cl/cl/src/merkle.rs @@ -0,0 +1,239 @@ +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +pub fn padded_leaves(elements: &[Vec]) -> [[u8; 32]; N] { + let mut leaves = [[0u8; 32]; N]; + + for (i, element) in elements.iter().enumerate() { + assert!(i < N); + leaves[i] = leaf(element); + } + + leaves +} + +pub fn leaf(data: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_MERKLE_LEAF"); + hasher.update(data); + hasher.finalize().into() +} + +pub fn node(a: [u8; 32], b: [u8; 32]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_MERKLE_NODE"); + hasher.update(a); + hasher.update(b); + hasher.finalize().into() +} + +pub fn root(elements: [[u8; 32]; N]) -> [u8; 32] { + let n = elements.len(); + + assert!(n.is_power_of_two()); + + let mut nodes = elements; + + for h in (1..=n.ilog2()).rev() { + for i in 0..2usize.pow(h - 1) { + nodes[i] = node(nodes[i * 2], nodes[i * 2 + 1]); + } + } + + nodes[0] +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum PathNode { + Left([u8; 32]), + Right([u8; 32]), +} + +pub fn path_root(leaf: [u8; 32], path: &[PathNode]) -> [u8; 32] { + let mut computed_hash = leaf; + + for path_node in path { + match path_node { + PathNode::Left(sibling_hash) => { + computed_hash = node(*sibling_hash, computed_hash); + } + PathNode::Right(sibling_hash) => { + computed_hash = node(computed_hash, *sibling_hash); + } + } + } + + computed_hash +} + +pub fn path(leaves: [[u8; 32]; N], idx: usize) -> Vec { + 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() { + if idx % 2 == 0 { + path.push(PathNode::Right(nodes[idx + 1])); + } else { + path.push(PathNode::Left(nodes[idx - 1])); + } + + idx /= 2; + + for i in 0..2usize.pow(h - 1) { + nodes[i] = node(nodes[i * 2], nodes[i * 2 + 1]); + } + } + + path +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_root_height_1() { + let r = root::<1>(padded_leaves(&[b"sand".into()])); + + let expected = leaf(b"sand"); + + assert_eq!(r, expected); + } + + #[test] + fn test_root_height_2() { + let r = root::<2>(padded_leaves(&[b"desert".into(), b"sand".into()])); + + let expected = node(leaf(b"desert"), leaf(b"sand")); + + assert_eq!(r, expected); + } + + #[test] + fn test_root_height_3() { + let r = root::<4>(padded_leaves(&[ + b"desert".into(), + b"sand".into(), + b"feels".into(), + b"warm".into(), + ])); + + let expected = node( + node(leaf(b"desert"), leaf(b"sand")), + node(leaf(b"feels"), leaf(b"warm")), + ); + + assert_eq!(r, expected); + } + + #[test] + fn test_root_height_4() { + let r = root::<8>(padded_leaves(&[ + b"desert".into(), + b"sand".into(), + b"feels".into(), + b"warm".into(), + b"at".into(), + b"night".into(), + ])); + + let expected = node( + node( + node(leaf(b"desert"), leaf(b"sand")), + node(leaf(b"feels"), leaf(b"warm")), + ), + node( + node(leaf(b"at"), leaf(b"night")), + node([0u8; 32], [0u8; 32]), + ), + ); + + assert_eq!(r, expected); + } + + #[test] + fn test_path_height_1() { + let leaves = padded_leaves(&[b"desert".into()]); + let r = root::<1>(leaves); + + let p = path::<1>(leaves, 0); + let expected = vec![]; + assert_eq!(p, expected); + assert_eq!(path_root(leaf(b"desert"), &p), r); + } + + #[test] + fn test_path_height_2() { + let leaves = padded_leaves(&[b"desert".into(), b"sand".into()]); + let r = root::<2>(leaves); + + // --- proof for element at idx 0 + + let p0 = path(leaves, 0); + let expected0 = vec![PathNode::Right(leaf(b"sand"))]; + assert_eq!(p0, expected0); + assert_eq!(path_root(leaf(b"desert"), &p0), r); + + // --- proof for element at idx 1 + + let p1 = path(leaves, 1); + let expected1 = vec![PathNode::Left(leaf(b"desert"))]; + assert_eq!(p1, expected1); + assert_eq!(path_root(leaf(b"sand"), &p1), r); + } + + #[test] + fn test_path_height_3() { + let leaves = padded_leaves(&[ + b"desert".into(), + b"sand".into(), + b"feels".into(), + b"warm".into(), + ]); + let r = root::<4>(leaves); + + // --- proof for element at idx 0 + + let p0 = path(leaves, 0); + let expected0 = vec![ + PathNode::Right(leaf(b"sand")), + PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))), + ]; + assert_eq!(p0, expected0); + assert_eq!(path_root(leaf(b"desert"), &p0), r); + + // --- proof for element at idx 1 + + let p1 = path(leaves, 1); + let expected1 = vec![ + PathNode::Left(leaf(b"desert")), + PathNode::Right(node(leaf(b"feels"), leaf(b"warm"))), + ]; + assert_eq!(p1, expected1); + assert_eq!(path_root(leaf(b"sand"), &p1), r); + + // --- proof for element at idx 2 + + let p2 = path(leaves, 2); + let expected2 = vec![ + PathNode::Right(leaf(b"warm")), + PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))), + ]; + assert_eq!(p2, expected2); + assert_eq!(path_root(leaf(b"feels"), &p2), r); + + // --- proof for element at idx 3 + + let p3 = path(leaves, 3); + let expected3 = vec![ + PathNode::Left(leaf(b"feels")), + PathNode::Left(node(leaf(b"desert"), leaf(b"sand"))), + ]; + assert_eq!(p3, expected3); + assert_eq!(path_root(leaf(b"warm"), &p3), r); + } +} diff --git a/emmarin/cl/cl/src/note.rs b/emmarin/cl/cl/src/note.rs new file mode 100644 index 0000000..96b3fed --- /dev/null +++ b/emmarin/cl/cl/src/note.rs @@ -0,0 +1,171 @@ +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::{balance::Unit, nullifier::NullifierCommitment}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct Constraint(pub [u8; 32]); + +impl Constraint { + pub fn from_vk(constraint_vk: &[u8]) -> Self { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_CONSTRAINT_COMMIT"); + hasher.update(constraint_vk); + let constraint_cm: [u8; 32] = hasher.finalize().into(); + + Self(constraint_cm) + } +} + +pub fn derive_unit(unit: &str) -> Unit { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_UNIT"); + hasher.update(unit.as_bytes()); + let unit: Unit = hasher.finalize().into(); + unit +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct NoteCommitment(pub [u8; 32]); + +impl NoteCommitment { + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +pub struct NoteWitness { + pub value: u64, + pub unit: Unit, + pub constraint: Constraint, + pub state: [u8; 32], + pub nonce: Nonce, +} + +impl NoteWitness { + pub fn new( + value: u64, + unit: Unit, + constraint: Constraint, + state: [u8; 32], + nonce: Nonce, + ) -> Self { + Self { + value, + unit, + constraint, + state, + nonce, + } + } + + pub fn basic(value: u64, unit: Unit, rng: impl RngCore) -> Self { + let constraint = Constraint([0u8; 32]); + let nonce = Nonce::random(rng); + Self::new(value, unit, constraint, [0u8; 32], nonce) + } + + pub fn stateless(value: u64, unit: Unit, constraint: Constraint, rng: impl RngCore) -> Self { + Self::new(value, unit, constraint, [0u8; 32], Nonce::random(rng)) + } + + pub fn commit(&self, tag: &dyn AsRef<[u8]>, nf_pk: NullifierCommitment) -> NoteCommitment { + let mut hasher = Sha256::new(); + hasher.update(tag.as_ref()); + + // COMMIT TO BALANCE + hasher.update(self.value.to_le_bytes()); + hasher.update(self.unit); + // Important! we don't commit to the balance blinding factor as that may make the notes linkable. + + // COMMIT TO STATE + hasher.update(self.state); + + // COMMIT TO CONSTRAINT + hasher.update(self.constraint.0); + + // COMMIT TO NONCE + hasher.update(self.nonce.as_bytes()); + + // COMMIT TO NULLIFIER + hasher.update(nf_pk.as_bytes()); + + let commit_bytes: [u8; 32] = hasher.finalize().into(); + NoteCommitment(commit_bytes) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Nonce([u8; 32]); + +impl Nonce { + pub fn random(mut rng: impl RngCore) -> Self { + let mut nonce = [0u8; 32]; + rng.fill_bytes(&mut nonce); + Self(nonce) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::nullifier::NullifierSecret; + + // #[test] + // fn test_note_commit_permutations() { + // let (nmo, eth) = (derive_unit("NMO"), derive_unit("ETH")); + + // let mut rng = rand::thread_rng(); + + // let nf_pk = NullifierSecret::random(&mut rng).commit(); + + // let reference_note = NoteWitness::basic(32, nmo, &mut rng); + + // // different notes under same nullifier produce different commitments + // let mutation_tests = [ + // NoteWitness { + // value: 12, + // ..reference_note + // }, + // NoteWitness { + // unit: eth, + // ..reference_note + // }, + // NoteWitness { + // constraint: Constraint::from_vk(&[1u8; 32]), + // ..reference_note + // }, + // NoteWitness { + // state: [1u8; 32], + // ..reference_note + // }, + // NoteWitness { + // nonce: Nonce::random(&mut rng), + // ..reference_note + // }, + // ]; + + // for n in mutation_tests { + // assert_ne!(n.commit(nf_pk), reference_note.commit(nf_pk)); + // } + + // // commitment to same note with different nullifiers produce different commitments + + // let other_nf_pk = NullifierSecret::random(&mut rng).commit(); + + // assert_ne!( + // reference_note.commit(nf_pk), + // reference_note.commit(other_nf_pk) + // ); + // } +} diff --git a/emmarin/cl/cl/src/nullifier.rs b/emmarin/cl/cl/src/nullifier.rs new file mode 100644 index 0000000..c6ee27a --- /dev/null +++ b/emmarin/cl/cl/src/nullifier.rs @@ -0,0 +1,161 @@ +// The Nullifier is used to detect if a note has +// already been consumed. + +// The same nullifier secret may be used across multiple +// notes to allow users to hold fewer secrets. A note +// nonce is used to disambiguate when the same nullifier +// secret is used for multiple notes. + +use rand_core::RngCore; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::NoteCommitment; + +// Maintained privately by note holder +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct NullifierSecret(pub [u8; 16]); + +// Nullifier commitment is public information that +// can be provided to anyone wishing to transfer +// you a note +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct NullifierCommitment([u8; 32]); + +// The nullifier attached to input notes to prove an input has not +// already been spent. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)] +pub struct Nullifier([u8; 32]); + +impl NullifierSecret { + pub fn random(mut rng: impl RngCore) -> Self { + let mut sk = [0u8; 16]; + rng.fill_bytes(&mut sk); + Self(sk) + } + + pub const fn zero() -> Self { + Self([0u8; 16]) + } + + pub fn commit(&self) -> NullifierCommitment { + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_NULL_COMMIT"); + hasher.update(self.0); + + let commit_bytes: [u8; 32] = hasher.finalize().into(); + NullifierCommitment(commit_bytes) + } + + pub fn from_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } +} + +impl NullifierCommitment { + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn hex(&self) -> String { + hex::encode(self.0) + } + + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl Nullifier { + pub fn new(tag: &dyn AsRef<[u8]>, sk: NullifierSecret, note_cm: NoteCommitment) -> Self { + let mut hasher = Sha256::new(); + hasher.update(tag.as_ref()); + hasher.update(sk.0); + hasher.update(note_cm.0); + + let nf_bytes: [u8; 32] = hasher.finalize().into(); + Self(nf_bytes) + } + + pub fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } +} + +#[cfg(test)] +mod test { + use crate::{note::derive_unit, Constraint, Nonce, NoteWitness}; + + // use super::*; + + // #[ignore = "nullifier test vectors not stable yet"] + // #[test] + // fn test_nullifier_commitment_vectors() { + // assert_eq!( + // NullifierSecret([0u8; 16]).commit().hex(), + // "384318f9864fe57647bac344e2afdc500a672dedb29d2dc63b004e940e4b382a" + // ); + // assert_eq!( + // NullifierSecret([1u8; 16]).commit().hex(), + // "0fd667e6bb39fbdc35d6265726154b839638ea90bcf4e736953ccf27ca5f870b" + // ); + // assert_eq!( + // NullifierSecret([u8::MAX; 16]).commit().hex(), + // "1cb78e487eb0b3116389311fdde84cd3f619a4d7f487b29bf5a002eed3784d75" + // ); + // } + + // #[test] + // fn test_nullifier_same_sk_different_nonce() { + // let mut rng = rand::thread_rng(); + // let sk = NullifierSecret::random(&mut rng); + // let note_1 = NoteWitness { + // value: 1, + // unit: derive_unit("NMO"), + // constraint: Constraint::from_vk(&[]), + // state: [0u8; 32], + // nonce: Nonce::random(&mut rng), + // }; + // let note_2 = NoteWitness { + // nonce: Nonce::random(&mut rng), + // ..note_1 + // }; + + // let note_cm_1 = note_1.commit(sk.commit()); + // let note_cm_2 = note_2.commit(sk.commit()); + + // let nf_1 = Nullifier::new(sk, note_cm_1); + // let nf_2 = Nullifier::new(sk, note_cm_2); + + // assert_ne!(nf_1, nf_2); + // } + + // #[test] + // fn test_same_sk_same_nonce_different_note() { + // let mut rng = rand::thread_rng(); + + // let sk = NullifierSecret::random(&mut rng); + // let nonce = Nonce::random(&mut rng); + + // let note_1 = NoteWitness { + // value: 1, + // unit: derive_unit("NMO"), + // constraint: Constraint::from_vk(&[]), + // state: [0u8; 32], + // nonce, + // }; + + // let note_2 = NoteWitness { + // unit: derive_unit("ETH"), + // ..note_1 + // }; + + // let note_cm_1 = note_1.commit(sk.commit()); + // let note_cm_2 = note_2.commit(sk.commit()); + + // let nf_1 = Nullifier::new(sk, note_cm_1); + // let nf_2 = Nullifier::new(sk, note_cm_2); + + // assert_ne!(nf_1, nf_2); + // } +} diff --git a/emmarin/cl/cl/src/output.rs b/emmarin/cl/cl/src/output.rs new file mode 100644 index 0000000..e6aed88 --- /dev/null +++ b/emmarin/cl/cl/src/output.rs @@ -0,0 +1,45 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + note::{NoteCommitment, NoteWitness}, + nullifier::NullifierCommitment, + NullifierSecret, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct Output { + pub note_comm: NoteCommitment, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct OutputWitness { + pub note: NoteWitness, + pub nf_pk: NullifierCommitment, +} + +impl OutputWitness { + pub fn new(note: NoteWitness, nf_pk: NullifierCommitment) -> Self { + Self { note, nf_pk } + } + + pub fn public(note: NoteWitness) -> Self { + let nf_pk = NullifierSecret::zero().commit(); + Self { note, nf_pk } + } + + pub fn commit_note(&self, tag: &dyn AsRef<[u8]>) -> NoteCommitment { + self.note.commit(tag, self.nf_pk) + } + + pub fn commit(&self, tag: &dyn AsRef<[u8]>) -> Output { + Output { + note_comm: self.commit_note(tag), + } + } +} + +impl Output { + pub fn to_bytes(&self) -> [u8; 32] { + self.note_comm.0 + } +} diff --git a/emmarin/cl/cl/src/partial_tx.rs b/emmarin/cl/cl/src/partial_tx.rs new file mode 100644 index 0000000..3d5fc97 --- /dev/null +++ b/emmarin/cl/cl/src/partial_tx.rs @@ -0,0 +1,217 @@ +use rand_core::{CryptoRngCore, RngCore}; +use serde::{Deserialize, Serialize}; + +use crate::balance::{Balance, BalanceWitness}; +use crate::input::{Input, InputWitness}; +use crate::merkle; +use crate::output::{Output, OutputWitness}; + +pub const MAX_INPUTS: usize = 8; +pub const MAX_OUTPUTS: usize = 8; + +/// The partial transaction commitment couples an input to a partial transaction. +/// Prevents partial tx unbundling. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)] +pub struct PtxRoot(pub [u8; 32]); + +impl From<[u8; 32]> for PtxRoot { + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl PtxRoot { + pub fn random(mut rng: impl RngCore) -> Self { + let mut sk = [0u8; 32]; + rng.fill_bytes(&mut sk); + Self(sk) + } + + pub fn hex(&self) -> String { + hex::encode(self.0) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTx { + pub inputs: Vec, + pub outputs: Vec, + pub balance: Balance, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxWitness { + pub inputs: Vec, + pub outputs: Vec, + pub balance_blinding: [u8; 16], +} + +impl PartialTxWitness { + pub fn random( + inputs: Vec, + outputs: Vec, + mut rng: impl CryptoRngCore, + ) -> Self { + Self { + inputs, + outputs, + balance_blinding: BalanceWitness::random_blinding(&mut rng), + } + } + + pub fn balance(&self) -> BalanceWitness { + BalanceWitness::from_ptx(self, self.balance_blinding) + } + + pub fn commit(&self, zone: &dyn AsRef<[u8]>) -> PartialTx { + PartialTx { + inputs: Vec::from_iter(self.inputs.iter().map(|i| i.commit(zone))), + outputs: Vec::from_iter(self.outputs.iter().map(|o| o.commit(zone))), + balance: self.balance().commit(), + } + } + + pub fn input_witness(&self, tag: &dyn AsRef<[u8]>, idx: usize) -> PartialTxInputWitness { + let input_bytes = Vec::from_iter( + self.inputs + .iter() + .map(|i| i.commit(tag).to_bytes().to_vec()), + ); + let input_merkle_leaves = merkle::padded_leaves::(&input_bytes); + + let path = merkle::path(input_merkle_leaves, idx); + let input = self.inputs[idx]; + PartialTxInputWitness { input, path } + } + + pub fn output_witness(&self, tag: &dyn AsRef<[u8]>, idx: usize) -> PartialTxOutputWitness { + let output_bytes = Vec::from_iter( + self.outputs + .iter() + .map(|o| o.commit(tag).to_bytes().to_vec()), + ); + let output_merkle_leaves = merkle::padded_leaves::(&output_bytes); + + let path = merkle::path(output_merkle_leaves, idx); + let output = self.outputs[idx]; + PartialTxOutputWitness { output, path } + } +} + +impl PartialTx { + pub fn input_root(&self) -> [u8; 32] { + let input_bytes = + Vec::from_iter(self.inputs.iter().map(Input::to_bytes).map(Vec::from_iter)); + let input_merkle_leaves = merkle::padded_leaves(&input_bytes); + merkle::root::(input_merkle_leaves) + } + + pub fn output_root(&self) -> [u8; 32] { + let output_bytes = Vec::from_iter( + self.outputs + .iter() + .map(Output::to_bytes) + .map(Vec::from_iter), + ); + let output_merkle_leaves = merkle::padded_leaves(&output_bytes); + merkle::root::(output_merkle_leaves) + } + + pub fn root(&self) -> PtxRoot { + let input_root = self.input_root(); + let output_root = self.output_root(); + let root = merkle::node(input_root, output_root); + PtxRoot(root) + } +} + +/// An input to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxInputWitness { + pub input: InputWitness, + pub path: Vec, +} + +impl PartialTxInputWitness { + pub fn input_root(&self, tag: &dyn AsRef<[u8]>) -> [u8; 32] { + let leaf = merkle::leaf(&self.input.commit(tag).to_bytes()); + merkle::path_root(leaf, &self.path) + } +} + +/// An output to a partial transaction +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PartialTxOutputWitness { + pub output: OutputWitness, + pub path: Vec, +} + +impl PartialTxOutputWitness { + pub fn output_root(&self, tag: &dyn AsRef<[u8]>) -> [u8; 32] { + let leaf = merkle::leaf(&self.output.commit(tag).to_bytes()); + merkle::path_root(leaf, &self.path) + } +} + +#[cfg(test)] +mod test { + + use crate::{ + balance::UnitBalance, + note::{derive_unit, NoteWitness}, + nullifier::NullifierSecret, + }; + + use super::*; + + // #[test] + // fn test_partial_tx_balance() { + // let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV")); + // let mut rng = rand::thread_rng(); + + // let nf_a = NullifierSecret::random(&mut rng); + // let nf_b = NullifierSecret::random(&mut rng); + // let nf_c = NullifierSecret::random(&mut rng); + + // let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); + // let nmo_10 = InputWitness::from_output(nmo_10_utxo, nf_a); + + // let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); + // let eth_23 = InputWitness::from_output(eth_23_utxo, nf_b); + + // let crv_4840 = OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); + + // let ptx_witness = PartialTxWitness { + // inputs: vec![nmo_10, eth_23], + // outputs: vec![crv_4840], + // balance_blinding: BalanceWitness::random_blinding(&mut rng), + // }; + + // let ptx = ptx_witness.commit(); + + // assert_eq!( + // ptx.balance, + // BalanceWitness { + // balances: vec![ + // UnitBalance { + // unit: nmo, + // pos: 0, + // neg: 10 + // }, + // UnitBalance { + // unit: eth, + // pos: 0, + // neg: 23 + // }, + // UnitBalance { + // unit: crv, + // pos: 4840, + // neg: 0 + // }, + // ], + // blinding: ptx_witness.balance_blinding + // } + // .commit() + // ); + // } +} diff --git a/emmarin/cl/cl/src/zones.rs b/emmarin/cl/cl/src/zones.rs new file mode 100644 index 0000000..ede32b8 --- /dev/null +++ b/emmarin/cl/cl/src/zones.rs @@ -0,0 +1,100 @@ +use crate::{merkle, Constraint, NoteCommitment, Nullifier, PartialTx, PartialTxWitness}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Pact { + pub tx: PartialTx, + pub to: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PactWitness { + pub tx: PartialTxWitness, + pub from: ZoneId, + pub to: Vec, +} + +impl PactWitness { + pub fn commit(&self) -> Pact { + assert_eq!(self.tx.outputs.len(), self.to.len()); + let ptx = PartialTx { + inputs: Vec::from_iter(self.tx.inputs.iter().map(|i| i.commit(&self.from))), + outputs: Vec::from_iter( + self.tx + .outputs + .iter() + .zip(&self.to) + .map(|(o, z)| o.commit(z)), + ), + balance: self.tx.balance().commit(), + }; + Pact { + tx: ptx, + to: self.to.clone(), + } + } +} + +pub struct ZoneNote { + pub stf: Constraint, + pub state: State, + pub ledger: Ledger, + pub id: [u8; 32], +} + +pub type State = [u8; 32]; +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Ledger { + cm_root: [u8; 32], + nf_root: [u8; 32], +} + +pub type ZoneId = [u8; 32]; +pub struct StateWitness; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerWitness { + pub commitments: Vec, + pub nullifiers: Vec, +} + +const MAX_COMM: usize = 256; +const MAX_NULL: usize = 256; + +impl LedgerWitness { + pub fn commit(&self) -> Ledger { + Ledger { + cm_root: self.cm_root(), + nf_root: self.nf_root(), + } + } + + pub fn nf_root(&self) -> [u8; 32] { + let bytes = self + .nullifiers + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + merkle::root(merkle::padded_leaves::(&bytes)) + } + + pub fn cm_root(&self) -> [u8; 32] { + let bytes = self + .commitments + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + merkle::root(merkle::padded_leaves::(&bytes)) + } + + pub fn cm_path(&self, cm: &NoteCommitment) -> Option> { + let bytes = self + .commitments + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + let leaves = merkle::padded_leaves::(&bytes); + let idx = self.commitments.iter().position(|c| c == cm)?; + Some(merkle::path(leaves, idx)) + } +} diff --git a/emmarin/cl/cl/tests/simple_transfer.rs b/emmarin/cl/cl/tests/simple_transfer.rs new file mode 100644 index 0000000..f1a7a95 --- /dev/null +++ b/emmarin/cl/cl/tests/simple_transfer.rs @@ -0,0 +1,37 @@ +use cl::{note::derive_unit, BalanceWitness}; + +fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { + cl::OutputWitness::new(note, nf_pk) +} + +#[test] +fn test_simple_transfer() { + let nmo = derive_unit("NMO"); + let mut rng = rand::thread_rng(); + + let sender_nf_sk = cl::NullifierSecret::random(&mut rng); + let sender_nf_pk = sender_nf_sk.commit(); + + let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit(); + + // Assume the sender has received an unspent output from somewhere + let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); + + // and wants to send 8 NMO to some recipient and return 2 NMO to itself. + let recipient_output = + cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk); + let change_output = + cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); + + let ptx_witness = cl::PartialTxWitness { + inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], + outputs: vec![recipient_output, change_output], + balance_blinding: BalanceWitness::random_blinding(&mut rng), + }; + + let bundle = cl::BundleWitness { + partials: vec![ptx_witness], + }; + + assert!(bundle.balance().is_zero()) +} diff --git a/emmarin/cl/ledger/Cargo.toml b/emmarin/cl/ledger/Cargo.toml new file mode 100644 index 0000000..053c813 --- /dev/null +++ b/emmarin/cl/ledger/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ledger" +version = "0.1.0" +edition = "2021" + +[dependencies] +cl = { path = "../cl" } +ledger_proof_statements = { path = "../ledger_proof_statements" } +nomos_cl_risc0_proofs = { path = "../risc0_proofs" } +ledger_validity_proof = { path = "../ledger_validity_proof" } +risc0-zkvm = { version = "1.0", features = ["prove", "metal"] } +risc0-groth16 = { version = "1.0" } +rand = "0.8.5" +rand_core = "0.6.0" +thiserror = "1.0.62" +sha2 = "0.10" diff --git a/emmarin/cl/ledger/src/bundle.rs b/emmarin/cl/ledger/src/bundle.rs new file mode 100644 index 0000000..17e8432 --- /dev/null +++ b/emmarin/cl/ledger/src/bundle.rs @@ -0,0 +1,67 @@ +use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic}; + +use crate::error::{Error, Result}; + +pub struct ProvedBundle { + pub bundle: BundlePublic, + pub risc0_receipt: risc0_zkvm::Receipt, +} + +impl ProvedBundle { + pub fn prove(bundle_witness: &cl::BundleWitness) -> Result { + // need to show that bundle is balanced. + // i.e. the sum of ptx balances is 0 + + let bundle_private = BundlePrivate { + balances: bundle_witness + .partials + .iter() + .map(|ptx| ptx.balance()) + .collect(), + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&bundle_private) + .unwrap() + .build() + .unwrap(); + + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts) + .map_err(|_| Error::Risc0ProofFailed)?; + + println!( + "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + let receipt = prove_info.receipt; + + Ok(Self { + bundle: receipt.journal.decode()?, + risc0_receipt: receipt, + }) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + // let Ok(_bundle_public) = self.public() else { + // return false; + // }; + + // Vec::from_iter(self.bundle.partials.iter().map(|ptx| ptx.balance)) == bundle_public.balances + // && + self.risc0_receipt + .verify(nomos_cl_risc0_proofs::BUNDLE_ID) + .is_ok() + } +} diff --git a/emmarin/cl/ledger/src/constraint.rs b/emmarin/cl/ledger/src/constraint.rs new file mode 100644 index 0000000..4d03773 --- /dev/null +++ b/emmarin/cl/ledger/src/constraint.rs @@ -0,0 +1,75 @@ +use cl::Constraint; +use ledger_proof_statements::constraint::ConstraintPublic; + +use crate::error::Result; + +#[derive(Debug, Clone)] +pub struct ConstraintProof { + pub risc0_id: [u32; 8], + pub risc0_receipt: risc0_zkvm::Receipt, +} + +pub fn risc0_constraint(risc0_id: [u32; 8]) -> Constraint { + unsafe { Constraint(core::mem::transmute::<[u32; 8], [u8; 32]>(risc0_id)) } +} + +impl ConstraintProof { + pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self { + Self { + risc0_id, + risc0_receipt, + } + } + + pub fn constraint(&self) -> Constraint { + risc0_constraint(self.risc0_id) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self, expected_public: ConstraintPublic) -> bool { + let Ok(public) = self.public() else { + return false; + }; + + expected_public == public && self.risc0_receipt.verify(self.risc0_id).is_ok() + } + + pub fn nop_constraint() -> Constraint { + risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID) + } + + pub fn prove_nop(nf: cl::Nullifier, ptx_root: cl::PtxRoot) -> Self { + let constraint_public = ConstraintPublic { nf, ptx_root }; + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&constraint_public) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::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, nomos_cl_risc0_proofs::CONSTRAINT_NOP_ELF, &opts) + .unwrap(); + + println!( + "STARK 'constraint-nop' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + // extract the receipt. + let receipt = prove_info.receipt; + + Self::from_risc0(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID, receipt) + } +} diff --git a/emmarin/cl/ledger/src/error.rs b/emmarin/cl/ledger/src/error.rs new file mode 100644 index 0000000..4431a25 --- /dev/null +++ b/emmarin/cl/ledger/src/error.rs @@ -0,0 +1,11 @@ +use thiserror::Error; + +pub type Result = core::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("risc0 failed to serde")] + Risc0Serde(#[from] risc0_zkvm::serde::Error), + #[error("risc0 failed to prove execution of the zkvm")] + Risc0ProofFailed, +} diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs new file mode 100644 index 0000000..9288916 --- /dev/null +++ b/emmarin/cl/ledger/src/ledger.rs @@ -0,0 +1,128 @@ +use ledger_proof_statements::ledger::{LedgerProofPrivate, LedgerProofPublic, ZoneTx}; + +use crate::{ + bundle::ProvedBundle, + constraint::ConstraintProof, + error::{Error, Result}, + pact::ProvedPact, + partial_tx::ProvedPartialTx, +}; +use cl::zones::{LedgerWitness, ZoneId}; + +pub struct ProvedLedgerTransition { + pub public: LedgerProofPublic, + pub risc0_receipt: risc0_zkvm::Receipt, +} + +pub enum ProvedZoneTx { + LocalTx { + bundle: ProvedBundle, + ptxs: Vec, + }, + Pact(ProvedPact), +} + +impl ProvedZoneTx { + fn to_public(&self) -> ZoneTx { + match self { + Self::LocalTx { ptxs, bundle } => ZoneTx::LocalTx { + ptxs: ptxs.iter().map(|p| p.public().unwrap()).collect(), + bundle: bundle.public().unwrap(), + }, + Self::Pact(pact) => ZoneTx::Pact(pact.public().unwrap()), + } + } + + fn proofs(&self) -> Vec { + match self { + Self::LocalTx { ptxs, bundle } => { + let mut proofs = vec![bundle.risc0_receipt.clone()]; + proofs.extend(ptxs.iter().map(|p| p.risc0_receipt.clone())); + proofs + } + Self::Pact(pact) => vec![pact.risc0_receipt.clone()], + } + } +} + +impl ProvedLedgerTransition { + pub fn prove( + ledger: LedgerWitness, + zone_id: ZoneId, + ptxs: Vec, + constraints: Vec, + ) -> Result { + let witness = LedgerProofPrivate { + txs: ptxs.iter().map(|p| p.to_public()).collect(), + ledger, + id: zone_id, + }; + + let mut env = risc0_zkvm::ExecutorEnv::builder(); + + for ptx in ptxs { + for proof in ptx.proofs() { + env.add_assumption(proof); + } + } + for covenant in constraints { + env.add_assumption(covenant.risc0_receipt); + } + let env = env.write(&witness).unwrap().build().unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::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, ledger_validity_proof::LEDGER_ELF, &opts) + .map_err(|e| { + eprintln!("{e}"); + Error::Risc0ProofFailed + })?; + + println!( + "STARK 'ledger' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + public: prove_info + .receipt + .journal + .decode::() + .unwrap(), + risc0_receipt: prove_info.receipt, + }) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + // let Ok(proved_ptx_inputs) = self.public() else { + // return false; + // }; + // let expected_ptx_inputs = PtxPublic { + // ptx: self.ptx.clone(), + // cm_root: self.cm_root, + // from: self.from, + // to: self.to, + // }; + // if expected_ptx_inputs != proved_ptx_inputs { + // return false; + // } + + // let ptx_root = self.ptx.root(); + + self.risc0_receipt + .verify(ledger_validity_proof::LEDGER_ID) + .is_ok() + } +} diff --git a/emmarin/cl/ledger/src/lib.rs b/emmarin/cl/ledger/src/lib.rs new file mode 100644 index 0000000..e95c59c --- /dev/null +++ b/emmarin/cl/ledger/src/lib.rs @@ -0,0 +1,8 @@ +pub mod bundle; +pub mod constraint; +pub mod error; +pub mod ledger; +pub mod pact; +pub mod partial_tx; + +pub use constraint::ConstraintProof; diff --git a/emmarin/cl/ledger/src/pact.rs b/emmarin/cl/ledger/src/pact.rs new file mode 100644 index 0000000..50ee756 --- /dev/null +++ b/emmarin/cl/ledger/src/pact.rs @@ -0,0 +1,75 @@ +use crate::error::{Error, Result}; +use cl::zones::*; +use ledger_proof_statements::pact::{PactPrivate, PactPublic}; + +#[derive(Debug, Clone)] +pub struct ProvedPact { + pub pact: Pact, + pub cm_root: [u8; 32], + pub risc0_receipt: risc0_zkvm::Receipt, +} + +impl ProvedPact { + pub fn prove( + pact: cl::zones::PactWitness, + input_cm_paths: Vec>, + cm_root: [u8; 32], + ) -> Result { + let pact_private = PactPrivate { + pact, + input_cm_paths, + cm_root, + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&pact_private) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::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, nomos_cl_risc0_proofs::PACT_ELF, &opts) + .map_err(|_| Error::Risc0ProofFailed)?; + + println!( + "STARK 'pact' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + pact: pact_private.pact.commit(), + cm_root, + risc0_receipt: prove_info.receipt, + }) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + let Ok(proved_ptx_inputs) = self.public() else { + return false; + }; + let expected_ptx_inputs = PactPublic { + pact: self.pact.clone(), + cm_root: self.cm_root, + }; + if expected_ptx_inputs != proved_ptx_inputs { + return false; + } + + self.risc0_receipt + .verify(nomos_cl_risc0_proofs::PACT_ID) + .is_ok() + } +} diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs new file mode 100644 index 0000000..0820891 --- /dev/null +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -0,0 +1,77 @@ +use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; + +use crate::error::{Error, Result}; +use cl::zones::*; + +pub struct ProvedPartialTx { + pub ptx: cl::PartialTx, + pub cm_root: [u8; 32], + pub risc0_receipt: risc0_zkvm::Receipt, +} + +impl ProvedPartialTx { + pub fn prove( + ptx: &cl::PartialTxWitness, + input_cm_paths: Vec>, + cm_root: [u8; 32], + id: ZoneId, + ) -> Result { + let ptx_private = PtxPrivate { + ptx: ptx.clone(), + input_cm_paths, + cm_root, + from: id, + }; + + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&ptx_private) + .unwrap() + .build() + .unwrap(); + + // Obtain the default prover. + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::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, nomos_cl_risc0_proofs::PTX_ELF, &opts) + .map_err(|_| Error::Risc0ProofFailed)?; + + println!( + "STARK 'ptx' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + Ok(Self { + ptx: ptx.commit(&id), + cm_root, + risc0_receipt: prove_info.receipt, + }) + } + + pub fn public(&self) -> Result { + Ok(self.risc0_receipt.journal.decode()?) + } + + pub fn verify(&self) -> bool { + let Ok(proved_ptx_inputs) = self.public() else { + return false; + }; + let expected_ptx_inputs = PtxPublic { + ptx: self.ptx.clone(), + cm_root: self.cm_root, + }; + if expected_ptx_inputs != proved_ptx_inputs { + return false; + } + + self.risc0_receipt + .verify(nomos_cl_risc0_proofs::PTX_ID) + .is_ok() + } +} diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs new file mode 100644 index 0000000..879ca19 --- /dev/null +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -0,0 +1,126 @@ +use cl::{note::derive_unit, BalanceWitness}; +use ledger::{ + constraint::ConstraintProof, + ledger::{ProvedLedgerTransition, ProvedZoneTx}, + pact::ProvedPact, +}; +use rand_core::CryptoRngCore; + +struct User(cl::NullifierSecret); + +impl User { + fn random(mut rng: impl CryptoRngCore) -> Self { + Self(cl::NullifierSecret::random(&mut rng)) + } + + fn pk(&self) -> cl::NullifierCommitment { + self.0.commit() + } + + fn sk(&self) -> cl::NullifierSecret { + self.0 + } +} + +fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { + cl::OutputWitness::new(note, nf_pk) +} + +#[test] +fn ledger_transition() { + let nmo = derive_unit("NMO"); + + let mut rng = rand::thread_rng(); + + // alice is sending 8 NMO to bob. + + let alice = User::random(&mut rng); + let bob = User::random(&mut rng); + + // Alice has an unspent note worth 10 NMO + let utxo = receive_utxo( + cl::NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng), + alice.pk(), + ); + + // Alice wants to send 8 NMO to bob + let bobs_output = cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), bob.pk()); + + let alice_change = cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), alice.pk()); + + let alice_input = cl::InputWitness::from_output(utxo, alice.sk()); + + let zone_a = [0; 32]; + let zone_b = [1; 32]; + let ledger_a = cl::zones::LedgerWitness { + commitments: vec![utxo.commit_note(&zone_a)], + nullifiers: vec![], + }; + + let ledger_b = cl::zones::LedgerWitness { + commitments: vec![], + nullifiers: vec![], + }; + + let expected_ledger_a = cl::zones::LedgerWitness { + commitments: vec![utxo.commit_note(&zone_a), alice_change.commit_note(&zone_a)], + nullifiers: vec![alice_input.nullifier(&zone_a)], + }; + + let expected_ledger_b = cl::zones::LedgerWitness { + commitments: vec![bobs_output.commit_note(&zone_b)], + nullifiers: vec![], + }; + + // Construct the ptx consuming Alices inputs and producing the two outputs. + let alice_pact_witness = cl::zones::PactWitness { + tx: cl::PartialTxWitness { + inputs: vec![alice_input], + outputs: vec![bobs_output, alice_change], + + balance_blinding: BalanceWitness::random_blinding(&mut rng), + }, + from: zone_a, + to: vec![zone_b, zone_a], + }; + + let proved_pact = ProvedPact::prove( + alice_pact_witness, + vec![ledger_a + .cm_path(&alice_input.note_commitment(&zone_a)) + .unwrap()], + ledger_a.cm_root(), + ) + .unwrap(); + + assert_eq!(proved_pact.cm_root, ledger_a.cm_root()); + + // Prove the constraints for alices input (she uses the no-op constraint) + let constraint_proof = + ConstraintProof::prove_nop(alice_input.nullifier(&zone_a), proved_pact.pact.tx.root()); + + let ledger_a_transition = ProvedLedgerTransition::prove( + ledger_a, + zone_a, + vec![ProvedZoneTx::Pact(proved_pact.clone())], + vec![constraint_proof], + ) + .unwrap(); + + let ledger_b_transition = ProvedLedgerTransition::prove( + ledger_b, + zone_b, + vec![ProvedZoneTx::Pact(proved_pact)], + vec![], + ) + .unwrap(); + + assert_eq!( + ledger_a_transition.public.ledger, + expected_ledger_a.commit() + ); + assert_eq!( + ledger_b_transition.public.ledger, + expected_ledger_b.commit() + ); +} diff --git a/emmarin/cl/ledger_proof_statements/Cargo.toml b/emmarin/cl/ledger_proof_statements/Cargo.toml new file mode 100644 index 0000000..65ea695 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ledger_proof_statements" +version = "0.1.0" +edition = "2021" + +[dependencies] +cl = { path = "../cl" } +serde = { version = "1.0", features = ["derive"] } diff --git a/emmarin/cl/ledger_proof_statements/src/bundle.rs b/emmarin/cl/ledger_proof_statements/src/bundle.rs new file mode 100644 index 0000000..f45feb9 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/bundle.rs @@ -0,0 +1,12 @@ +use cl::{Balance, BalanceWitness}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundlePublic { + pub balances: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundlePrivate { + pub balances: Vec, +} diff --git a/emmarin/cl/ledger_proof_statements/src/constraint.rs b/emmarin/cl/ledger_proof_statements/src/constraint.rs new file mode 100644 index 0000000..91e7aa4 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/constraint.rs @@ -0,0 +1,8 @@ +use cl::{Nullifier, PtxRoot}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub struct ConstraintPublic { + pub nf: Nullifier, + pub ptx_root: PtxRoot, +} diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs new file mode 100644 index 0000000..2d5e1c6 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -0,0 +1,30 @@ +use crate::bundle::BundlePublic; +use crate::pact::PactPublic; +use crate::ptx::PtxPublic; +use cl::zones::*; +use cl::Output; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerProofPublic { + pub ledger: Ledger, + pub id: ZoneId, + pub cross_in: Vec, + pub cross_out: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerProofPrivate { + pub ledger: LedgerWitness, + pub id: ZoneId, + pub txs: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum ZoneTx { + LocalTx { + ptxs: Vec, + bundle: BundlePublic, + }, + Pact(PactPublic), +} diff --git a/emmarin/cl/ledger_proof_statements/src/lib.rs b/emmarin/cl/ledger_proof_statements/src/lib.rs new file mode 100644 index 0000000..12c64c5 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/lib.rs @@ -0,0 +1,5 @@ +pub mod bundle; +pub mod constraint; +pub mod ledger; +pub mod pact; +pub mod ptx; diff --git a/emmarin/cl/ledger_proof_statements/src/pact.rs b/emmarin/cl/ledger_proof_statements/src/pact.rs new file mode 100644 index 0000000..1229c7f --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/pact.rs @@ -0,0 +1,15 @@ +use cl::zones::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PactPublic { + pub pact: Pact, + pub cm_root: [u8; 32], +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PactPrivate { + pub pact: PactWitness, + pub input_cm_paths: Vec>, + pub cm_root: [u8; 32], +} diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs new file mode 100644 index 0000000..731408b --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -0,0 +1,16 @@ +use cl::{PartialTx, PartialTxWitness}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PtxPublic { + pub ptx: PartialTx, + pub cm_root: [u8; 32], +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PtxPrivate { + pub ptx: PartialTxWitness, + pub input_cm_paths: Vec>, + pub cm_root: [u8; 32], + pub from: [u8; 32], +} diff --git a/emmarin/cl/ledger_validity_proof/Cargo.toml b/emmarin/cl/ledger_validity_proof/Cargo.toml new file mode 100644 index 0000000..b3a8d5f --- /dev/null +++ b/emmarin/cl/ledger_validity_proof/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ledger_validity_proof" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "1.0" } + +[package.metadata.risc0] +methods = [ "ledger"] + diff --git a/emmarin/cl/ledger_validity_proof/build.rs b/emmarin/cl/ledger_validity_proof/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/emmarin/cl/ledger_validity_proof/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml b/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml new file mode 100644 index 0000000..a05b1fa --- /dev/null +++ b/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ledger" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } +nomos_cl_risc0_proofs = { path = "../../risc0_proofs" } + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs new file mode 100644 index 0000000..06cedcb --- /dev/null +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -0,0 +1,146 @@ +use cl::{zones::*, Output}; +use ledger_proof_statements::{bundle::*, constraint::*, ledger::*, pact::PactPublic, ptx::*}; +use risc0_zkvm::{guest::env, serde}; + +fn main() { + let LedgerProofPrivate { + mut ledger, + id, + txs, + } = env::read(); + + let cm_root = ledger.cm_root(); + + let mut cross_in = vec![]; + let mut cross_out = vec![]; + + for tx in txs { + match tx { + ZoneTx::LocalTx { bundle, ptxs } => { + ledger = process_bundle(ledger, ptxs, bundle, cm_root); + } + ZoneTx::Pact(pact) => { + let (new_ledger, consumed_commits, produced_commits) = + process_pact(ledger, pact, id, cm_root); + ledger = new_ledger; + cross_in.extend(consumed_commits); + cross_out.extend(produced_commits); + } + } + } + + env::commit(&LedgerProofPublic { + ledger: ledger.commit(), + id, + cross_in, + cross_out, + }); +} + +fn process_bundle( + mut ledger: LedgerWitness, + ptxs: Vec, + bundle_proof: BundlePublic, + cm_root: [u8; 32], +) -> LedgerWitness { + assert_eq!( + ptxs.iter().map(|ptx| ptx.ptx.balance).collect::>(), + bundle_proof.balances + ); + // verify bundle is balanced + env::verify( + nomos_cl_risc0_proofs::BUNDLE_ID, + &serde::to_vec(&bundle_proof).unwrap(), + ) + .unwrap(); + + for ptx in ptxs { + ledger = process_ptx(ledger, ptx, cm_root); + } + + ledger +} + +fn process_ptx(mut ledger: LedgerWitness, ptx: PtxPublic, cm_root: [u8; 32]) -> LedgerWitness { + env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap(); + assert_eq!(ptx.cm_root, cm_root); + + let ptx = ptx.ptx; + + for input in &ptx.inputs { + assert!(!ledger.nullifiers.contains(&input.nullifier)); + ledger.nullifiers.push(input.nullifier); + + env::verify( + input.constraint.0, + &serde::to_vec(&ConstraintPublic { + ptx_root: ptx.root(), + nf: input.nullifier, + }) + .unwrap(), + ) + .unwrap(); + } + + for output in &ptx.outputs { + ledger.commitments.push(output.note_comm); + } + + ledger +} + +fn process_pact( + mut ledger: LedgerWitness, + pact: PactPublic, + id: ZoneId, + cm_root: [u8; 32], +) -> (LedgerWitness, Vec, Vec) { + let mut cross_in = vec![]; + let mut cross_out = vec![]; + + env::verify( + nomos_cl_risc0_proofs::PACT_ID, + &serde::to_vec(&pact).unwrap(), + ) + .unwrap(); + + let pact_cm_root = pact.cm_root; + let pact = pact.pact; + + if cm_root != pact_cm_root { + // zone is the receiver of the transfer + for (comm, zone) in pact.tx.outputs.iter().zip(&pact.to) { + if *zone == id { + cross_in.push(*comm); + ledger.commitments.push(comm.note_comm); + } + } + } else { + // zone is the sender of the transfer + // proof of non-membership + for input in &pact.tx.inputs { + assert!(!ledger.nullifiers.contains(&input.nullifier)); + ledger.nullifiers.push(input.nullifier); + + env::verify( + input.constraint.0, + &serde::to_vec(&ConstraintPublic { + ptx_root: pact.tx.root(), + nf: input.nullifier, + }) + .unwrap(), + ) + .unwrap(); + } + + for (output, to) in pact.tx.outputs.iter().zip(&pact.to) { + if *to == id { + ledger.commitments.push(output.note_comm); + } else { + cross_out.push(*output); + } + } + } + + (ledger, cross_in, cross_out) +} diff --git a/emmarin/cl/ledger_validity_proof/src/lib.rs b/emmarin/cl/ledger_validity_proof/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/emmarin/cl/ledger_validity_proof/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/emmarin/cl/risc0_proofs/Cargo.toml b/emmarin/cl/risc0_proofs/Cargo.toml new file mode 100644 index 0000000..b34bd02 --- /dev/null +++ b/emmarin/cl/risc0_proofs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nomos_cl_risc0_proofs" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +risc0-build = { version = "1.0" } + +[package.metadata.risc0] +methods = ["bundle", "constraint_nop", "ptx", "pact"] + diff --git a/emmarin/cl/risc0_proofs/build.rs b/emmarin/cl/risc0_proofs/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/emmarin/cl/risc0_proofs/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/emmarin/cl/risc0_proofs/bundle/Cargo.toml b/emmarin/cl/risc0_proofs/bundle/Cargo.toml new file mode 100644 index 0000000..f207737 --- /dev/null +++ b/emmarin/cl/risc0_proofs/bundle/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "bundle" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } + + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/risc0_proofs/bundle/src/main.rs b/emmarin/cl/risc0_proofs/bundle/src/main.rs new file mode 100644 index 0000000..9beec67 --- /dev/null +++ b/emmarin/cl/risc0_proofs/bundle/src/main.rs @@ -0,0 +1,23 @@ +/// Bundle Proof +/// +/// The bundle proof demonstrates that the set of partial transactions +/// balance to zero. i.e. \sum inputs = \sum outputs. +/// +/// This is done by proving knowledge of some blinding factor `r` s.t. +/// \sum outputs - \sum input = 0*G + r*H +/// +/// To avoid doing costly ECC in stark, we compute only the RHS in stark. +/// The sums and equality is checked outside of stark during proof verification. +use risc0_zkvm::guest::env; + +fn main() { + let bundle_private: ledger_proof_statements::bundle::BundlePrivate = env::read(); + + let bundle_public = ledger_proof_statements::bundle::BundlePublic { + balances: Vec::from_iter(bundle_private.balances.iter().map(|b| b.commit())), + }; + + assert!(cl::BalanceWitness::combine(bundle_private.balances, [0u8; 16]).is_zero()); + + env::commit(&bundle_public); +} diff --git a/emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml b/emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml new file mode 100644 index 0000000..1aac242 --- /dev/null +++ b/emmarin/cl/risc0_proofs/constraint_nop/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "constraint_nop" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } + + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/risc0_proofs/constraint_nop/src/main.rs b/emmarin/cl/risc0_proofs/constraint_nop/src/main.rs new file mode 100644 index 0000000..25d9c0c --- /dev/null +++ b/emmarin/cl/risc0_proofs/constraint_nop/src/main.rs @@ -0,0 +1,8 @@ +/// Constraint No-op Proof +use ledger_proof_statements::constraint::ConstraintPublic; +use risc0_zkvm::guest::env; + +fn main() { + let public: ConstraintPublic = env::read(); + env::commit(&public); +} diff --git a/emmarin/cl/risc0_proofs/pact/Cargo.toml b/emmarin/cl/risc0_proofs/pact/Cargo.toml new file mode 100644 index 0000000..9fdfdc0 --- /dev/null +++ b/emmarin/cl/risc0_proofs/pact/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "pact" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } + + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/risc0_proofs/pact/src/main.rs b/emmarin/cl/risc0_proofs/pact/src/main.rs new file mode 100644 index 0000000..04928f0 --- /dev/null +++ b/emmarin/cl/risc0_proofs/pact/src/main.rs @@ -0,0 +1,30 @@ +/// Input Proof +use cl::merkle; +use ledger_proof_statements::pact::{PactPrivate, PactPublic}; +use risc0_zkvm::guest::env; + +fn main() { + let PactPrivate { + pact, + input_cm_paths, + cm_root, + } = env::read(); + + assert_eq!(pact.tx.inputs.len(), input_cm_paths.len()); + for (input, cm_path) in pact.tx.inputs.iter().zip(input_cm_paths) { + let note_cm = input.note_commitment(&pact.from); + let cm_leaf = merkle::leaf(note_cm.as_bytes()); + assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); + } + + for output in pact.tx.outputs.iter() { + assert!(output.note.value > 0); + } + + assert!(pact.tx.balance().is_zero()); + + env::commit(&PactPublic { + pact: pact.commit(), + cm_root, + }); +} diff --git a/emmarin/cl/risc0_proofs/ptx/Cargo.toml b/emmarin/cl/risc0_proofs/ptx/Cargo.toml new file mode 100644 index 0000000..181aef7 --- /dev/null +++ b/emmarin/cl/risc0_proofs/ptx/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "ptx" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } + + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs new file mode 100644 index 0000000..9af2f5d --- /dev/null +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -0,0 +1,29 @@ +/// Input Proof +use cl::merkle; +use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; +use risc0_zkvm::guest::env; + +fn main() { + let PtxPrivate { + ptx, + input_cm_paths, + cm_root, + from, + } = env::read(); + + assert_eq!(ptx.inputs.len(), input_cm_paths.len()); + for (input, cm_path) in ptx.inputs.iter().zip(input_cm_paths) { + let note_cm = input.note_commitment(&from); + let cm_leaf = merkle::leaf(note_cm.as_bytes()); + assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); + } + + for output in ptx.outputs.iter() { + assert!(output.note.value > 0); + } + + env::commit(&PtxPublic { + ptx: ptx.commit(&from), + cm_root, + }); +} diff --git a/emmarin/cl/risc0_proofs/src/lib.rs b/emmarin/cl/risc0_proofs/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/emmarin/cl/risc0_proofs/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); From afd9bafb79d286d8c366406c58fb919447826706 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 20 Nov 2024 15:46:35 +0100 Subject: [PATCH 02/10] move to zone_layer --- emmarin/cl/cl/src/{ => cl}/balance.rs | 2 +- emmarin/cl/cl/src/{ => cl}/bundle.rs | 2 +- emmarin/cl/cl/src/{ => cl}/crypto.rs | 0 emmarin/cl/cl/src/{ => cl}/error.rs | 0 emmarin/cl/cl/src/{ => cl}/input.rs | 14 +++---- emmarin/cl/cl/src/{ => cl}/merkle.rs | 0 emmarin/cl/cl/src/cl/mod.rs | 21 +++++++++++ emmarin/cl/cl/src/{ => cl}/note.rs | 2 +- emmarin/cl/cl/src/{ => cl}/nullifier.rs | 2 +- emmarin/cl/cl/src/{ => cl}/output.rs | 2 +- emmarin/cl/cl/src/cl/pact.rs | 37 +++++++++++++++++++ emmarin/cl/cl/src/{ => cl}/partial_tx.rs | 10 +++-- emmarin/cl/cl/src/lib.rs | 23 +----------- .../cl/cl/src/{zones.rs => zone_layer/mod.rs} | 36 +----------------- emmarin/cl/ledger/src/bundle.rs | 6 +-- emmarin/cl/ledger/src/constraint.rs | 4 +- emmarin/cl/ledger/src/ledger.rs | 2 +- emmarin/cl/ledger/src/pact.rs | 9 +++-- emmarin/cl/ledger/src/partial_tx.rs | 9 +++-- .../cl/ledger_proof_statements/src/bundle.rs | 2 +- .../ledger_proof_statements/src/constraint.rs | 2 +- .../cl/ledger_proof_statements/src/ledger.rs | 4 +- .../cl/ledger_proof_statements/src/pact.rs | 7 +++- emmarin/cl/ledger_proof_statements/src/ptx.rs | 4 +- emmarin/cl/risc0_proofs/bundle/src/main.rs | 3 +- emmarin/cl/risc0_proofs/pact/src/main.rs | 2 +- emmarin/cl/risc0_proofs/ptx/src/main.rs | 2 +- 27 files changed, 111 insertions(+), 96 deletions(-) rename emmarin/cl/cl/src/{ => cl}/balance.rs (99%) rename emmarin/cl/cl/src/{ => cl}/bundle.rs (98%) rename emmarin/cl/cl/src/{ => cl}/crypto.rs (100%) rename emmarin/cl/cl/src/{ => cl}/error.rs (100%) rename emmarin/cl/cl/src/{ => cl}/input.rs (88%) rename emmarin/cl/cl/src/{ => cl}/merkle.rs (100%) create mode 100644 emmarin/cl/cl/src/cl/mod.rs rename emmarin/cl/cl/src/{ => cl}/note.rs (98%) rename emmarin/cl/cl/src/{ => cl}/nullifier.rs (99%) rename emmarin/cl/cl/src/{ => cl}/output.rs (98%) create mode 100644 emmarin/cl/cl/src/cl/pact.rs rename emmarin/cl/cl/src/{ => cl}/partial_tx.rs (97%) rename emmarin/cl/cl/src/{zones.rs => zone_layer/mod.rs} (63%) diff --git a/emmarin/cl/cl/src/balance.rs b/emmarin/cl/cl/src/cl/balance.rs similarity index 99% rename from emmarin/cl/cl/src/balance.rs rename to emmarin/cl/cl/src/cl/balance.rs index 10db851..1f2cc99 100644 --- a/emmarin/cl/cl/src/balance.rs +++ b/emmarin/cl/cl/src/cl/balance.rs @@ -2,7 +2,7 @@ use rand_core::CryptoRngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::PartialTxWitness; +use crate::cl::PartialTxWitness; pub type Value = u64; pub type Unit = [u8; 32]; diff --git a/emmarin/cl/cl/src/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs similarity index 98% rename from emmarin/cl/cl/src/bundle.rs rename to emmarin/cl/cl/src/cl/bundle.rs index c54e888..aa6beef 100644 --- a/emmarin/cl/cl/src/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; +use crate::cl::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions diff --git a/emmarin/cl/cl/src/crypto.rs b/emmarin/cl/cl/src/cl/crypto.rs similarity index 100% rename from emmarin/cl/cl/src/crypto.rs rename to emmarin/cl/cl/src/cl/crypto.rs diff --git a/emmarin/cl/cl/src/error.rs b/emmarin/cl/cl/src/cl/error.rs similarity index 100% rename from emmarin/cl/cl/src/error.rs rename to emmarin/cl/cl/src/cl/error.rs diff --git a/emmarin/cl/cl/src/input.rs b/emmarin/cl/cl/src/cl/input.rs similarity index 88% rename from emmarin/cl/cl/src/input.rs rename to emmarin/cl/cl/src/cl/input.rs index 5253ce9..a0f2db0 100644 --- a/emmarin/cl/cl/src/input.rs +++ b/emmarin/cl/cl/src/cl/input.rs @@ -2,10 +2,10 @@ /// /// Partial transactions, as the name suggests, are transactions /// which on their own may not balance (i.e. \sum inputs != \sum outputs) -use crate::{ +use crate::cl::{ note::{Constraint, NoteWitness}, nullifier::{Nullifier, NullifierSecret}, - Nonce, + Nonce, NoteCommitment, OutputWitness, }; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -27,12 +27,12 @@ impl InputWitness { Self { note, nf_sk } } - pub fn from_output(output: crate::OutputWitness, nf_sk: NullifierSecret) -> Self { + pub fn from_output(output: OutputWitness, nf_sk: NullifierSecret) -> Self { assert_eq!(nf_sk.commit(), output.nf_pk); Self::new(output.note, nf_sk) } - pub fn public(output: crate::OutputWitness) -> Self { + pub fn public(output: OutputWitness) -> Self { let nf_sk = NullifierSecret::zero(); assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO Self::new(output.note, nf_sk) @@ -49,8 +49,8 @@ impl InputWitness { Nonce::from_bytes(nonce_bytes) } - pub fn evolve_output(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> crate::OutputWitness { - crate::OutputWitness { + pub fn evolve_output(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> OutputWitness { + OutputWitness { note: NoteWitness { nonce: self.evolved_nonce(tag, domain), ..self.note @@ -70,7 +70,7 @@ impl InputWitness { } } - pub fn note_commitment(&self, tag: &dyn AsRef<[u8]>) -> crate::NoteCommitment { + pub fn note_commitment(&self, tag: &dyn AsRef<[u8]>) -> NoteCommitment { self.note.commit(tag, self.nf_sk.commit()) } } diff --git a/emmarin/cl/cl/src/merkle.rs b/emmarin/cl/cl/src/cl/merkle.rs similarity index 100% rename from emmarin/cl/cl/src/merkle.rs rename to emmarin/cl/cl/src/cl/merkle.rs diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs new file mode 100644 index 0000000..31b8cc8 --- /dev/null +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -0,0 +1,21 @@ +pub mod balance; +pub mod bundle; +pub mod crypto; +pub mod error; +pub mod input; +pub mod merkle; +pub mod note; +pub mod nullifier; +pub mod output; +pub mod pact; +pub mod partial_tx; + +pub use balance::{Balance, BalanceWitness}; +pub use bundle::{Bundle, BundleWitness}; +pub use input::{Input, InputWitness}; +pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; +pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; +pub use output::{Output, OutputWitness}; +pub use partial_tx::{ + PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot, +}; diff --git a/emmarin/cl/cl/src/note.rs b/emmarin/cl/cl/src/cl/note.rs similarity index 98% rename from emmarin/cl/cl/src/note.rs rename to emmarin/cl/cl/src/cl/note.rs index 96b3fed..d198d47 100644 --- a/emmarin/cl/cl/src/note.rs +++ b/emmarin/cl/cl/src/cl/note.rs @@ -2,7 +2,7 @@ use rand::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::{balance::Unit, nullifier::NullifierCommitment}; +use crate::cl::{balance::Unit, nullifier::NullifierCommitment}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Constraint(pub [u8; 32]); diff --git a/emmarin/cl/cl/src/nullifier.rs b/emmarin/cl/cl/src/cl/nullifier.rs similarity index 99% rename from emmarin/cl/cl/src/nullifier.rs rename to emmarin/cl/cl/src/cl/nullifier.rs index c6ee27a..3a522f6 100644 --- a/emmarin/cl/cl/src/nullifier.rs +++ b/emmarin/cl/cl/src/cl/nullifier.rs @@ -10,7 +10,7 @@ use rand_core::RngCore; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use crate::NoteCommitment; +use crate::cl::NoteCommitment; // Maintained privately by note holder #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/emmarin/cl/cl/src/output.rs b/emmarin/cl/cl/src/cl/output.rs similarity index 98% rename from emmarin/cl/cl/src/output.rs rename to emmarin/cl/cl/src/cl/output.rs index e6aed88..a1fc8e4 100644 --- a/emmarin/cl/cl/src/output.rs +++ b/emmarin/cl/cl/src/cl/output.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{ +use crate::cl::{ note::{NoteCommitment, NoteWitness}, nullifier::NullifierCommitment, NullifierSecret, diff --git a/emmarin/cl/cl/src/cl/pact.rs b/emmarin/cl/cl/src/cl/pact.rs new file mode 100644 index 0000000..3f5f3e1 --- /dev/null +++ b/emmarin/cl/cl/src/cl/pact.rs @@ -0,0 +1,37 @@ +use crate::zone_layer::ZoneId; +use crate::cl::{PartialTx, PartialTxWitness}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Pact { + pub tx: PartialTx, + pub to: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct PactWitness { + pub tx: PartialTxWitness, + pub from: ZoneId, + pub to: Vec, +} + +impl PactWitness { + pub fn commit(&self) -> Pact { + assert_eq!(self.tx.outputs.len(), self.to.len()); + let ptx = PartialTx { + inputs: Vec::from_iter(self.tx.inputs.iter().map(|i| i.commit(&self.from))), + outputs: Vec::from_iter( + self.tx + .outputs + .iter() + .zip(&self.to) + .map(|(o, z)| o.commit(z)), + ), + balance: self.tx.balance().commit(), + }; + Pact { + tx: ptx, + to: self.to.clone(), + } + } +} diff --git a/emmarin/cl/cl/src/partial_tx.rs b/emmarin/cl/cl/src/cl/partial_tx.rs similarity index 97% rename from emmarin/cl/cl/src/partial_tx.rs rename to emmarin/cl/cl/src/cl/partial_tx.rs index 3d5fc97..5245ea1 100644 --- a/emmarin/cl/cl/src/partial_tx.rs +++ b/emmarin/cl/cl/src/cl/partial_tx.rs @@ -1,10 +1,12 @@ use rand_core::{CryptoRngCore, RngCore}; use serde::{Deserialize, Serialize}; -use crate::balance::{Balance, BalanceWitness}; -use crate::input::{Input, InputWitness}; -use crate::merkle; -use crate::output::{Output, OutputWitness}; +use crate::cl::{ + balance::{Balance, BalanceWitness}, + input::{Input, InputWitness}, + merkle, + output::{Output, OutputWitness}, +}; pub const MAX_INPUTS: usize = 8; pub const MAX_OUTPUTS: usize = 8; diff --git a/emmarin/cl/cl/src/lib.rs b/emmarin/cl/cl/src/lib.rs index fea07f5..9bd83c5 100644 --- a/emmarin/cl/cl/src/lib.rs +++ b/emmarin/cl/cl/src/lib.rs @@ -1,21 +1,2 @@ -pub mod balance; -pub mod bundle; -pub mod crypto; -pub mod error; -pub mod input; -pub mod merkle; -pub mod note; -pub mod nullifier; -pub mod output; -pub mod partial_tx; -pub mod zones; - -pub use balance::{Balance, BalanceWitness}; -pub use bundle::{Bundle, BundleWitness}; -pub use input::{Input, InputWitness}; -pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; -pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; -pub use output::{Output, OutputWitness}; -pub use partial_tx::{ - PartialTx, PartialTxInputWitness, PartialTxOutputWitness, PartialTxWitness, PtxRoot, -}; +pub mod cl; +pub mod zone_layer; diff --git a/emmarin/cl/cl/src/zones.rs b/emmarin/cl/cl/src/zone_layer/mod.rs similarity index 63% rename from emmarin/cl/cl/src/zones.rs rename to emmarin/cl/cl/src/zone_layer/mod.rs index ede32b8..1487ef7 100644 --- a/emmarin/cl/cl/src/zones.rs +++ b/emmarin/cl/cl/src/zone_layer/mod.rs @@ -1,40 +1,6 @@ -use crate::{merkle, Constraint, NoteCommitment, Nullifier, PartialTx, PartialTxWitness}; +use crate::cl::{merkle, Constraint, NoteCommitment, Nullifier}; use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Pact { - pub tx: PartialTx, - pub to: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PactWitness { - pub tx: PartialTxWitness, - pub from: ZoneId, - pub to: Vec, -} - -impl PactWitness { - pub fn commit(&self) -> Pact { - assert_eq!(self.tx.outputs.len(), self.to.len()); - let ptx = PartialTx { - inputs: Vec::from_iter(self.tx.inputs.iter().map(|i| i.commit(&self.from))), - outputs: Vec::from_iter( - self.tx - .outputs - .iter() - .zip(&self.to) - .map(|(o, z)| o.commit(z)), - ), - balance: self.tx.balance().commit(), - }; - Pact { - tx: ptx, - to: self.to.clone(), - } - } -} - pub struct ZoneNote { pub stf: Constraint, pub state: State, diff --git a/emmarin/cl/ledger/src/bundle.rs b/emmarin/cl/ledger/src/bundle.rs index 17e8432..25f0765 100644 --- a/emmarin/cl/ledger/src/bundle.rs +++ b/emmarin/cl/ledger/src/bundle.rs @@ -1,6 +1,6 @@ -use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic}; - use crate::error::{Error, Result}; +use cl::cl::BundleWitness; +use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic}; pub struct ProvedBundle { pub bundle: BundlePublic, @@ -8,7 +8,7 @@ pub struct ProvedBundle { } impl ProvedBundle { - pub fn prove(bundle_witness: &cl::BundleWitness) -> Result { + pub fn prove(bundle_witness: &BundleWitness) -> Result { // need to show that bundle is balanced. // i.e. the sum of ptx balances is 0 diff --git a/emmarin/cl/ledger/src/constraint.rs b/emmarin/cl/ledger/src/constraint.rs index 4d03773..f6d4e5f 100644 --- a/emmarin/cl/ledger/src/constraint.rs +++ b/emmarin/cl/ledger/src/constraint.rs @@ -1,4 +1,4 @@ -use cl::Constraint; +use cl::cl::{Constraint, Nullifier, PtxRoot}; use ledger_proof_statements::constraint::ConstraintPublic; use crate::error::Result; @@ -41,7 +41,7 @@ impl ConstraintProof { risc0_constraint(nomos_cl_risc0_proofs::CONSTRAINT_NOP_ID) } - pub fn prove_nop(nf: cl::Nullifier, ptx_root: cl::PtxRoot) -> Self { + pub fn prove_nop(nf: Nullifier, ptx_root: PtxRoot) -> Self { let constraint_public = ConstraintPublic { nf, ptx_root }; let env = risc0_zkvm::ExecutorEnv::builder() .write(&constraint_public) diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index 9288916..b955cc7 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -7,7 +7,7 @@ use crate::{ pact::ProvedPact, partial_tx::ProvedPartialTx, }; -use cl::zones::{LedgerWitness, ZoneId}; +use cl::zone_layer::{LedgerWitness, ZoneId}; pub struct ProvedLedgerTransition { pub public: LedgerProofPublic, diff --git a/emmarin/cl/ledger/src/pact.rs b/emmarin/cl/ledger/src/pact.rs index 50ee756..44d52eb 100644 --- a/emmarin/cl/ledger/src/pact.rs +++ b/emmarin/cl/ledger/src/pact.rs @@ -1,5 +1,8 @@ use crate::error::{Error, Result}; -use cl::zones::*; +use cl::cl::{ + merkle, + pact::{Pact, PactWitness}, +}; use ledger_proof_statements::pact::{PactPrivate, PactPublic}; #[derive(Debug, Clone)] @@ -11,8 +14,8 @@ pub struct ProvedPact { impl ProvedPact { pub fn prove( - pact: cl::zones::PactWitness, - input_cm_paths: Vec>, + pact: PactWitness, + input_cm_paths: Vec>, cm_root: [u8; 32], ) -> Result { let pact_private = PactPrivate { diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index 0820891..29a333a 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -1,18 +1,19 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use crate::error::{Error, Result}; -use cl::zones::*; +use cl::cl::{merkle, PartialTx, PartialTxWitness}; +use cl::zone_layer::ZoneId; pub struct ProvedPartialTx { - pub ptx: cl::PartialTx, + pub ptx: PartialTx, pub cm_root: [u8; 32], pub risc0_receipt: risc0_zkvm::Receipt, } impl ProvedPartialTx { pub fn prove( - ptx: &cl::PartialTxWitness, - input_cm_paths: Vec>, + ptx: &PartialTxWitness, + input_cm_paths: Vec>, cm_root: [u8; 32], id: ZoneId, ) -> Result { diff --git a/emmarin/cl/ledger_proof_statements/src/bundle.rs b/emmarin/cl/ledger_proof_statements/src/bundle.rs index f45feb9..f952a4c 100644 --- a/emmarin/cl/ledger_proof_statements/src/bundle.rs +++ b/emmarin/cl/ledger_proof_statements/src/bundle.rs @@ -1,4 +1,4 @@ -use cl::{Balance, BalanceWitness}; +use cl::cl::{Balance, BalanceWitness}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/emmarin/cl/ledger_proof_statements/src/constraint.rs b/emmarin/cl/ledger_proof_statements/src/constraint.rs index 91e7aa4..0a29aa6 100644 --- a/emmarin/cl/ledger_proof_statements/src/constraint.rs +++ b/emmarin/cl/ledger_proof_statements/src/constraint.rs @@ -1,4 +1,4 @@ -use cl::{Nullifier, PtxRoot}; +use cl::cl::{Nullifier, PtxRoot}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index 2d5e1c6..04700fd 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -1,8 +1,8 @@ use crate::bundle::BundlePublic; use crate::pact::PactPublic; use crate::ptx::PtxPublic; -use cl::zones::*; -use cl::Output; +use cl::cl::Output; +use cl::zone_layer::*; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/emmarin/cl/ledger_proof_statements/src/pact.rs b/emmarin/cl/ledger_proof_statements/src/pact.rs index 1229c7f..f9a6c8c 100644 --- a/emmarin/cl/ledger_proof_statements/src/pact.rs +++ b/emmarin/cl/ledger_proof_statements/src/pact.rs @@ -1,4 +1,7 @@ -use cl::zones::*; +use cl::cl::{ + merkle, + pact::{Pact, PactWitness}, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -10,6 +13,6 @@ pub struct PactPublic { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PactPrivate { pub pact: PactWitness, - pub input_cm_paths: Vec>, + pub input_cm_paths: Vec>, pub cm_root: [u8; 32], } diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 731408b..a11070c 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -1,4 +1,4 @@ -use cl::{PartialTx, PartialTxWitness}; +use cl::cl::{merkle, PartialTx, PartialTxWitness}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -10,7 +10,7 @@ pub struct PtxPublic { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, - pub input_cm_paths: Vec>, + pub input_cm_paths: Vec>, pub cm_root: [u8; 32], pub from: [u8; 32], } diff --git a/emmarin/cl/risc0_proofs/bundle/src/main.rs b/emmarin/cl/risc0_proofs/bundle/src/main.rs index 9beec67..30d2f59 100644 --- a/emmarin/cl/risc0_proofs/bundle/src/main.rs +++ b/emmarin/cl/risc0_proofs/bundle/src/main.rs @@ -1,3 +1,4 @@ +use cl::cl::BalanceWitness; /// Bundle Proof /// /// The bundle proof demonstrates that the set of partial transactions @@ -17,7 +18,7 @@ fn main() { balances: Vec::from_iter(bundle_private.balances.iter().map(|b| b.commit())), }; - assert!(cl::BalanceWitness::combine(bundle_private.balances, [0u8; 16]).is_zero()); + assert!(BalanceWitness::combine(bundle_private.balances, [0u8; 16]).is_zero()); env::commit(&bundle_public); } diff --git a/emmarin/cl/risc0_proofs/pact/src/main.rs b/emmarin/cl/risc0_proofs/pact/src/main.rs index 04928f0..0dd6e79 100644 --- a/emmarin/cl/risc0_proofs/pact/src/main.rs +++ b/emmarin/cl/risc0_proofs/pact/src/main.rs @@ -1,5 +1,5 @@ /// Input Proof -use cl::merkle; +use cl::cl::merkle; use ledger_proof_statements::pact::{PactPrivate, PactPublic}; use risc0_zkvm::guest::env; diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index 9af2f5d..49012cc 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -1,5 +1,5 @@ /// Input Proof -use cl::merkle; +use cl::cl::merkle; use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use risc0_zkvm::guest::env; From a940705b0155f577b1462587e3aef1676c070fce Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 20 Nov 2024 18:19:36 +0100 Subject: [PATCH 03/10] add zone layer txs --- emmarin/cl/cl/src/cl/output.rs | 2 +- emmarin/cl/cl/src/cl/pact.rs | 2 +- emmarin/cl/cl/src/zone_layer/ledger.rs | 55 ++++++++++ emmarin/cl/cl/src/zone_layer/mod.rs | 69 +----------- emmarin/cl/cl/src/zone_layer/notes.rs | 14 +++ emmarin/cl/cl/src/zone_layer/tx.rs | 24 ++++ emmarin/cl/ledger/src/ledger.rs | 21 +--- emmarin/cl/ledger/src/lib.rs | 2 + emmarin/cl/ledger/src/partial_tx.rs | 2 +- emmarin/cl/ledger/src/stf.rs | 33 ++++++ emmarin/cl/ledger/src/zone_update.rs | 103 ++++++++++++++++++ .../cl/ledger_proof_statements/src/ledger.rs | 6 +- emmarin/cl/ledger_proof_statements/src/lib.rs | 1 + emmarin/cl/ledger_proof_statements/src/stf.rs | 8 ++ .../ledger_validity_proof/ledger/src/main.rs | 8 +- 15 files changed, 259 insertions(+), 91 deletions(-) create mode 100644 emmarin/cl/cl/src/zone_layer/ledger.rs create mode 100644 emmarin/cl/cl/src/zone_layer/notes.rs create mode 100644 emmarin/cl/cl/src/zone_layer/tx.rs create mode 100644 emmarin/cl/ledger/src/stf.rs create mode 100644 emmarin/cl/ledger/src/zone_update.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/stf.rs diff --git a/emmarin/cl/cl/src/cl/output.rs b/emmarin/cl/cl/src/cl/output.rs index a1fc8e4..604ef66 100644 --- a/emmarin/cl/cl/src/cl/output.rs +++ b/emmarin/cl/cl/src/cl/output.rs @@ -6,7 +6,7 @@ use crate::cl::{ NullifierSecret, }; -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct Output { pub note_comm: NoteCommitment, } diff --git a/emmarin/cl/cl/src/cl/pact.rs b/emmarin/cl/cl/src/cl/pact.rs index 3f5f3e1..b231bc9 100644 --- a/emmarin/cl/cl/src/cl/pact.rs +++ b/emmarin/cl/cl/src/cl/pact.rs @@ -1,5 +1,5 @@ -use crate::zone_layer::ZoneId; use crate::cl::{PartialTx, PartialTxWitness}; +use crate::zone_layer::notes::ZoneId; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs new file mode 100644 index 0000000..11acbe6 --- /dev/null +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -0,0 +1,55 @@ +use crate::cl::{merkle, NoteCommitment, Nullifier}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct Ledger { + cm_root: [u8; 32], + nf_root: [u8; 32], +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerWitness { + pub commitments: Vec, + pub nullifiers: Vec, +} + +const MAX_COMM: usize = 256; +const MAX_NULL: usize = 256; + +impl LedgerWitness { + pub fn commit(&self) -> Ledger { + Ledger { + cm_root: self.cm_root(), + nf_root: self.nf_root(), + } + } + + pub fn nf_root(&self) -> [u8; 32] { + let bytes = self + .nullifiers + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + merkle::root(merkle::padded_leaves::(&bytes)) + } + + pub fn cm_root(&self) -> [u8; 32] { + let bytes = self + .commitments + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + merkle::root(merkle::padded_leaves::(&bytes)) + } + + pub fn cm_path(&self, cm: &NoteCommitment) -> Option> { + let bytes = self + .commitments + .iter() + .map(|i| i.as_bytes().to_vec()) + .collect::>(); + let leaves = merkle::padded_leaves::(&bytes); + let idx = self.commitments.iter().position(|c| c == cm)?; + Some(merkle::path(leaves, idx)) + } +} diff --git a/emmarin/cl/cl/src/zone_layer/mod.rs b/emmarin/cl/cl/src/zone_layer/mod.rs index 1487ef7..20710b3 100644 --- a/emmarin/cl/cl/src/zone_layer/mod.rs +++ b/emmarin/cl/cl/src/zone_layer/mod.rs @@ -1,66 +1,3 @@ -use crate::cl::{merkle, Constraint, NoteCommitment, Nullifier}; -use serde::{Deserialize, Serialize}; - -pub struct ZoneNote { - pub stf: Constraint, - pub state: State, - pub ledger: Ledger, - pub id: [u8; 32], -} - -pub type State = [u8; 32]; -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Ledger { - cm_root: [u8; 32], - nf_root: [u8; 32], -} - -pub type ZoneId = [u8; 32]; -pub struct StateWitness; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct LedgerWitness { - pub commitments: Vec, - pub nullifiers: Vec, -} - -const MAX_COMM: usize = 256; -const MAX_NULL: usize = 256; - -impl LedgerWitness { - pub fn commit(&self) -> Ledger { - Ledger { - cm_root: self.cm_root(), - nf_root: self.nf_root(), - } - } - - pub fn nf_root(&self) -> [u8; 32] { - let bytes = self - .nullifiers - .iter() - .map(|i| i.as_bytes().to_vec()) - .collect::>(); - merkle::root(merkle::padded_leaves::(&bytes)) - } - - pub fn cm_root(&self) -> [u8; 32] { - let bytes = self - .commitments - .iter() - .map(|i| i.as_bytes().to_vec()) - .collect::>(); - merkle::root(merkle::padded_leaves::(&bytes)) - } - - pub fn cm_path(&self, cm: &NoteCommitment) -> Option> { - let bytes = self - .commitments - .iter() - .map(|i| i.as_bytes().to_vec()) - .collect::>(); - let leaves = merkle::padded_leaves::(&bytes); - let idx = self.commitments.iter().position(|c| c == cm)?; - Some(merkle::path(leaves, idx)) - } -} +pub mod ledger; +pub mod notes; +pub mod tx; diff --git a/emmarin/cl/cl/src/zone_layer/notes.rs b/emmarin/cl/cl/src/zone_layer/notes.rs new file mode 100644 index 0000000..8adf0e4 --- /dev/null +++ b/emmarin/cl/cl/src/zone_layer/notes.rs @@ -0,0 +1,14 @@ +use super::ledger::Ledger; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct ZoneNote { + pub stf: Stf, + pub state: State, + pub ledger: Ledger, + pub id: [u8; 32], +} + +pub type Stf = [u8; 32]; +pub type ZoneId = [u8; 32]; +pub type State = [u8; 32]; diff --git a/emmarin/cl/cl/src/zone_layer/tx.rs b/emmarin/cl/cl/src/zone_layer/tx.rs new file mode 100644 index 0000000..02bb511 --- /dev/null +++ b/emmarin/cl/cl/src/zone_layer/tx.rs @@ -0,0 +1,24 @@ +use super::notes::ZoneNote; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct UpdateBundle { + pub updates: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ZoneUpdate { + pub old: ZoneNote, + pub new: ZoneNote, +} + +impl ZoneUpdate { + pub fn new(old: ZoneNote, new: ZoneNote) -> Self { + assert_eq!(old.id, new.id); + Self { old, new } + } + + pub fn well_formed(&self) -> bool { + self.old.id == self.new.id + } +} diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index b955cc7..6fab328 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -7,7 +7,7 @@ use crate::{ pact::ProvedPact, partial_tx::ProvedPartialTx, }; -use cl::zone_layer::{LedgerWitness, ZoneId}; +use cl::zone_layer::{ledger::LedgerWitness, notes::ZoneId}; pub struct ProvedLedgerTransition { pub public: LedgerProofPublic, @@ -101,26 +101,7 @@ impl ProvedLedgerTransition { }) } - pub fn public(&self) -> Result { - Ok(self.risc0_receipt.journal.decode()?) - } - pub fn verify(&self) -> bool { - // let Ok(proved_ptx_inputs) = self.public() else { - // return false; - // }; - // let expected_ptx_inputs = PtxPublic { - // ptx: self.ptx.clone(), - // cm_root: self.cm_root, - // from: self.from, - // to: self.to, - // }; - // if expected_ptx_inputs != proved_ptx_inputs { - // return false; - // } - - // let ptx_root = self.ptx.root(); - self.risc0_receipt .verify(ledger_validity_proof::LEDGER_ID) .is_ok() diff --git a/emmarin/cl/ledger/src/lib.rs b/emmarin/cl/ledger/src/lib.rs index e95c59c..93be05a 100644 --- a/emmarin/cl/ledger/src/lib.rs +++ b/emmarin/cl/ledger/src/lib.rs @@ -4,5 +4,7 @@ pub mod error; pub mod ledger; pub mod pact; pub mod partial_tx; +pub mod stf; +pub mod zone_update; pub use constraint::ConstraintProof; diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index 29a333a..6a1c614 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -2,7 +2,7 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use crate::error::{Error, Result}; use cl::cl::{merkle, PartialTx, PartialTxWitness}; -use cl::zone_layer::ZoneId; +use cl::zone_layer::notes::ZoneId; pub struct ProvedPartialTx { pub ptx: PartialTx, diff --git a/emmarin/cl/ledger/src/stf.rs b/emmarin/cl/ledger/src/stf.rs new file mode 100644 index 0000000..f416008 --- /dev/null +++ b/emmarin/cl/ledger/src/stf.rs @@ -0,0 +1,33 @@ +use cl::zone_layer::notes::Stf; +use ledger_proof_statements::stf::StfPublic; + +#[derive(Debug, Clone)] +pub struct StfProof { + pub risc0_id: [u32; 8], + pub public: StfPublic, + pub risc0_receipt: risc0_zkvm::Receipt, +} + +pub fn risc0_constraint(risc0_id: [u32; 8]) -> Stf { + // TODO: hash + + unsafe { core::mem::transmute::<[u32; 8], [u8; 32]>(risc0_id) } +} + +impl StfProof { + pub fn from_risc0(risc0_id: [u32; 8], risc0_receipt: risc0_zkvm::Receipt) -> Self { + Self { + risc0_id, + public: risc0_receipt.journal.decode().unwrap(), + risc0_receipt, + } + } + + pub fn stf(&self) -> Stf { + risc0_constraint(self.risc0_id) + } + + pub fn verify(&self) -> bool { + self.risc0_receipt.verify(self.risc0_id).is_ok() + } +} diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs new file mode 100644 index 0000000..efcb251 --- /dev/null +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -0,0 +1,103 @@ +pub use crate::error::{Error, Result}; +use crate::{ledger::ProvedLedgerTransition, stf::StfProof}; +use cl::zone_layer::tx::UpdateBundle; +use std::collections::HashSet; + +pub struct ProvedUpdateBundle { + pub bundle: UpdateBundle, + pub ledger_proofs: Vec, + pub stf_proofs: Vec, +} + +impl ProvedUpdateBundle { + // pub fn prove(bundle_witness: &BundleWitness) -> Result { + // // need to show that bundle is balanced. + // // i.e. the sum of ptx balances is 0 + + // let bundle_private = BundlePrivate { + // balances: bundle_witness + // .partials + // .iter() + // .map(|ptx| ptx.balance()) + // .collect(), + // }; + + // let env = risc0_zkvm::ExecutorEnv::builder() + // .write(&bundle_private) + // .unwrap() + // .build() + // .unwrap(); + + // let prover = risc0_zkvm::default_prover(); + + // let start_t = std::time::Instant::now(); + + // let opts = risc0_zkvm::ProverOpts::succinct(); + // let prove_info = prover + // .prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts) + // .map_err(|_| Error::Risc0ProofFailed)?; + + // println!( + // "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", + // start_t.elapsed(), + // prove_info.stats.total_cycles + // ); + + // let receipt = prove_info.receipt; + + // Ok(Self { + // bundle: receipt.journal.decode()?, + // risc0_receipt: receipt, + // }) + // } + + pub fn verify(&self) -> bool { + let mut consumed_commitments = HashSet::new(); + let mut produced_commitments = HashSet::new(); + for proof in &self.ledger_proofs { + if !proof.verify() { + return false; + } + + for comm in &proof.public.cross_out { + if produced_commitments.insert(comm) { + // already in? + } + } + for comm in &proof.public.cross_in { + if consumed_commitments.insert(comm) { + // already in? + } + } + } + + // check that cross zone transactions match + if consumed_commitments != produced_commitments { + return false; + } + + for ((update, stf_proof), ledger_proof) in self + .bundle + .updates + .iter() + .zip(self.stf_proofs.iter()) + .zip(self.ledger_proofs.iter()) + { + if !update.well_formed() { + return false; + } + + if ledger_proof.public.old_ledger != update.old.ledger + || ledger_proof.public.ledger != update.new.ledger + { + return false; + } + + if stf_proof.public.old != update.old || stf_proof.public.new != update.new { + return false; + } + } + + true + } +} diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index 04700fd..ed3d728 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -2,11 +2,15 @@ use crate::bundle::BundlePublic; use crate::pact::PactPublic; use crate::ptx::PtxPublic; use cl::cl::Output; -use cl::zone_layer::*; +use cl::zone_layer::{ + ledger::{Ledger, LedgerWitness}, + notes::ZoneId, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct LedgerProofPublic { + pub old_ledger: Ledger, pub ledger: Ledger, pub id: ZoneId, pub cross_in: Vec, diff --git a/emmarin/cl/ledger_proof_statements/src/lib.rs b/emmarin/cl/ledger_proof_statements/src/lib.rs index 12c64c5..fa0941c 100644 --- a/emmarin/cl/ledger_proof_statements/src/lib.rs +++ b/emmarin/cl/ledger_proof_statements/src/lib.rs @@ -3,3 +3,4 @@ pub mod constraint; pub mod ledger; pub mod pact; pub mod ptx; +pub mod stf; diff --git a/emmarin/cl/ledger_proof_statements/src/stf.rs b/emmarin/cl/ledger_proof_statements/src/stf.rs new file mode 100644 index 0000000..22eb9cc --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/stf.rs @@ -0,0 +1,8 @@ +use cl::zone_layer::notes::ZoneNote; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct StfPublic { + pub old: ZoneNote, + pub new: ZoneNote, +} diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 06cedcb..55bf280 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -1,4 +1,7 @@ -use cl::{zones::*, Output}; +use cl::{ + cl::Output, + zone_layer::{ledger::LedgerWitness, notes::ZoneId}, +}; use ledger_proof_statements::{bundle::*, constraint::*, ledger::*, pact::PactPublic, ptx::*}; use risc0_zkvm::{guest::env, serde}; @@ -9,6 +12,8 @@ fn main() { txs, } = env::read(); + let old_ledger = ledger.commit(); + let cm_root = ledger.cm_root(); let mut cross_in = vec![]; @@ -30,6 +35,7 @@ fn main() { } env::commit(&LedgerProofPublic { + old_ledger, ledger: ledger.commit(), id, cross_in, From a76bb268a1220575304524c10e40e4aeeb3c635d Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Fri, 22 Nov 2024 18:15:56 +0100 Subject: [PATCH 04/10] uniform ptx and pacts --- emmarin/cl/cl/src/cl/bundle.rs | 4 +- emmarin/cl/cl/src/cl/input.rs | 22 ++- emmarin/cl/cl/src/cl/mod.rs | 1 - emmarin/cl/cl/src/cl/note.rs | 4 +- emmarin/cl/cl/src/cl/nullifier.rs | 2 +- emmarin/cl/cl/src/cl/output.rs | 24 ++- emmarin/cl/cl/src/cl/pact.rs | 37 ----- emmarin/cl/cl/src/cl/partial_tx.rs | 60 +++++--- emmarin/cl/cl/tests/simple_transfer.rs | 26 ++-- emmarin/cl/ledger/src/bundle.rs | 1 + emmarin/cl/ledger/src/ledger.rs | 41 ++--- emmarin/cl/ledger/src/lib.rs | 1 - emmarin/cl/ledger/src/pact.rs | 78 ---------- emmarin/cl/ledger/src/partial_tx.rs | 14 +- emmarin/cl/ledger/tests/simple_transfer.rs | 84 ++++++----- .../cl/ledger_proof_statements/src/ledger.rs | 13 +- emmarin/cl/ledger_proof_statements/src/lib.rs | 1 - .../cl/ledger_proof_statements/src/pact.rs | 18 --- emmarin/cl/ledger_proof_statements/src/ptx.rs | 8 +- .../ledger_validity_proof/ledger/src/main.rs | 141 ++++++------------ emmarin/cl/risc0_proofs/Cargo.toml | 2 +- emmarin/cl/risc0_proofs/pact/Cargo.toml | 19 --- emmarin/cl/risc0_proofs/pact/src/main.rs | 30 ---- emmarin/cl/risc0_proofs/ptx/src/main.rs | 7 +- 24 files changed, 219 insertions(+), 419 deletions(-) delete mode 100644 emmarin/cl/cl/src/cl/pact.rs delete mode 100644 emmarin/cl/ledger/src/pact.rs delete mode 100644 emmarin/cl/ledger_proof_statements/src/pact.rs delete mode 100644 emmarin/cl/risc0_proofs/pact/Cargo.toml delete mode 100644 emmarin/cl/risc0_proofs/pact/src/main.rs diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs index aa6beef..c9166cf 100644 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -6,7 +6,7 @@ use crate::cl::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; /// The goal in bundling transactions is to produce a set of partial transactions /// that balance each other. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Bundle { pub partials: Vec, } @@ -30,7 +30,7 @@ impl BundleWitness { #[cfg(test)] mod test { - use crate::{ + use crate::cl::{ balance::UnitBalance, input::InputWitness, note::{derive_unit, NoteWitness}, diff --git a/emmarin/cl/cl/src/cl/input.rs b/emmarin/cl/cl/src/cl/input.rs index a0f2db0..6503b7b 100644 --- a/emmarin/cl/cl/src/cl/input.rs +++ b/emmarin/cl/cl/src/cl/input.rs @@ -2,10 +2,13 @@ /// /// Partial transactions, as the name suggests, are transactions /// which on their own may not balance (i.e. \sum inputs != \sum outputs) -use crate::cl::{ - note::{Constraint, NoteWitness}, - nullifier::{Nullifier, NullifierSecret}, - Nonce, NoteCommitment, OutputWitness, +use crate::{ + cl::{ + note::{Constraint, NoteWitness}, + nullifier::{Nullifier, NullifierSecret}, + Nonce, NoteCommitment, OutputWitness, + }, + zone_layer::notes::ZoneId, }; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -14,6 +17,7 @@ use sha2::{Digest, Sha256}; pub struct Input { pub nullifier: Nullifier, pub constraint: Constraint, + pub zone_id: ZoneId, } #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -63,10 +67,11 @@ impl InputWitness { Nullifier::new(tag, self.nf_sk, self.note_commitment(tag)) } - pub fn commit(&self, tag: &dyn AsRef<[u8]>) -> Input { + pub fn commit(&self, zone_id: ZoneId) -> Input { Input { - nullifier: self.nullifier(tag), + nullifier: self.nullifier(&zone_id), constraint: self.note.constraint, + zone_id, } } @@ -76,10 +81,11 @@ impl InputWitness { } impl Input { - pub fn to_bytes(&self) -> [u8; 64] { - let mut bytes = [0u8; 64]; + pub fn to_bytes(&self) -> [u8; 96] { + let mut bytes = [0u8; 96]; bytes[..32].copy_from_slice(self.nullifier.as_bytes()); bytes[32..64].copy_from_slice(&self.constraint.0); + bytes[64..96].copy_from_slice(&self.zone_id); bytes } } diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs index 31b8cc8..e1d559e 100644 --- a/emmarin/cl/cl/src/cl/mod.rs +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -7,7 +7,6 @@ pub mod merkle; pub mod note; pub mod nullifier; pub mod output; -pub mod pact; pub mod partial_tx; pub use balance::{Balance, BalanceWitness}; diff --git a/emmarin/cl/cl/src/cl/note.rs b/emmarin/cl/cl/src/cl/note.rs index d198d47..ca9c313 100644 --- a/emmarin/cl/cl/src/cl/note.rs +++ b/emmarin/cl/cl/src/cl/note.rs @@ -118,8 +118,8 @@ impl Nonce { #[cfg(test)] mod test { - use super::*; - use crate::nullifier::NullifierSecret; + // use super::*; + // use crate::cl::nullifier::NullifierSecret; // #[test] // fn test_note_commit_permutations() { diff --git a/emmarin/cl/cl/src/cl/nullifier.rs b/emmarin/cl/cl/src/cl/nullifier.rs index 3a522f6..c2340f4 100644 --- a/emmarin/cl/cl/src/cl/nullifier.rs +++ b/emmarin/cl/cl/src/cl/nullifier.rs @@ -84,7 +84,7 @@ impl Nullifier { #[cfg(test)] mod test { - use crate::{note::derive_unit, Constraint, Nonce, NoteWitness}; + // use crate::cl::{note::derive_unit, Constraint, Nonce, NoteWitness}; // use super::*; diff --git a/emmarin/cl/cl/src/cl/output.rs b/emmarin/cl/cl/src/cl/output.rs index 604ef66..40e7ccd 100644 --- a/emmarin/cl/cl/src/cl/output.rs +++ b/emmarin/cl/cl/src/cl/output.rs @@ -1,13 +1,17 @@ use serde::{Deserialize, Serialize}; -use crate::cl::{ - note::{NoteCommitment, NoteWitness}, - nullifier::NullifierCommitment, - NullifierSecret, +use crate::{ + cl::{ + note::{NoteCommitment, NoteWitness}, + nullifier::NullifierCommitment, + NullifierSecret, + }, + zone_layer::notes::ZoneId, }; #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] pub struct Output { + pub zone_id: ZoneId, pub note_comm: NoteCommitment, } @@ -31,15 +35,19 @@ impl OutputWitness { self.note.commit(tag, self.nf_pk) } - pub fn commit(&self, tag: &dyn AsRef<[u8]>) -> Output { + pub fn commit(&self, zone_id: ZoneId) -> Output { Output { - note_comm: self.commit_note(tag), + zone_id, + note_comm: self.commit_note(&zone_id), } } } impl Output { - pub fn to_bytes(&self) -> [u8; 32] { - self.note_comm.0 + pub fn to_bytes(&self) -> [u8; 64] { + let mut bytes = [0u8; 64]; + bytes[..32].copy_from_slice(&self.zone_id); + bytes[32..].copy_from_slice(&self.note_comm.0); + bytes } } diff --git a/emmarin/cl/cl/src/cl/pact.rs b/emmarin/cl/cl/src/cl/pact.rs deleted file mode 100644 index b231bc9..0000000 --- a/emmarin/cl/cl/src/cl/pact.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::cl::{PartialTx, PartialTxWitness}; -use crate::zone_layer::notes::ZoneId; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Pact { - pub tx: PartialTx, - pub to: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PactWitness { - pub tx: PartialTxWitness, - pub from: ZoneId, - pub to: Vec, -} - -impl PactWitness { - pub fn commit(&self) -> Pact { - assert_eq!(self.tx.outputs.len(), self.to.len()); - let ptx = PartialTx { - inputs: Vec::from_iter(self.tx.inputs.iter().map(|i| i.commit(&self.from))), - outputs: Vec::from_iter( - self.tx - .outputs - .iter() - .zip(&self.to) - .map(|(o, z)| o.commit(z)), - ), - balance: self.tx.balance().commit(), - }; - Pact { - tx: ptx, - to: self.to.clone(), - } - } -} diff --git a/emmarin/cl/cl/src/cl/partial_tx.rs b/emmarin/cl/cl/src/cl/partial_tx.rs index 5245ea1..2710a09 100644 --- a/emmarin/cl/cl/src/cl/partial_tx.rs +++ b/emmarin/cl/cl/src/cl/partial_tx.rs @@ -1,11 +1,14 @@ use rand_core::{CryptoRngCore, RngCore}; use serde::{Deserialize, Serialize}; -use crate::cl::{ - balance::{Balance, BalanceWitness}, - input::{Input, InputWitness}, - merkle, - output::{Output, OutputWitness}, +use crate::{ + cl::{ + balance::{Balance, BalanceWitness}, + input::{Input, InputWitness}, + merkle, + output::{Output, OutputWitness}, + }, + zone_layer::notes::ZoneId, }; pub const MAX_INPUTS: usize = 8; @@ -65,19 +68,32 @@ impl PartialTxWitness { BalanceWitness::from_ptx(self, self.balance_blinding) } - pub fn commit(&self, zone: &dyn AsRef<[u8]>) -> PartialTx { + pub fn commit(&self, input_zones: &[ZoneId], output_zones: &[ZoneId]) -> PartialTx { + assert_eq!(self.inputs.len(), input_zones.len()); + assert_eq!(self.outputs.len(), output_zones.len()); PartialTx { - inputs: Vec::from_iter(self.inputs.iter().map(|i| i.commit(zone))), - outputs: Vec::from_iter(self.outputs.iter().map(|o| o.commit(zone))), + inputs: self + .inputs + .iter() + .zip(input_zones.iter()) + .map(|(i, z)| i.commit(*z)) + .collect(), + + outputs: self + .outputs + .iter() + .zip(output_zones.iter()) + .map(|(o, z)| o.commit(*z)) + .collect(), balance: self.balance().commit(), } } - pub fn input_witness(&self, tag: &dyn AsRef<[u8]>, idx: usize) -> PartialTxInputWitness { + pub fn input_witness(&self, zone_id: ZoneId, idx: usize) -> PartialTxInputWitness { let input_bytes = Vec::from_iter( self.inputs .iter() - .map(|i| i.commit(tag).to_bytes().to_vec()), + .map(|i| i.commit(zone_id).to_bytes().to_vec()), ); let input_merkle_leaves = merkle::padded_leaves::(&input_bytes); @@ -86,11 +102,11 @@ impl PartialTxWitness { PartialTxInputWitness { input, path } } - pub fn output_witness(&self, tag: &dyn AsRef<[u8]>, idx: usize) -> PartialTxOutputWitness { + pub fn output_witness(&self, zone_id: ZoneId, idx: usize) -> PartialTxOutputWitness { let output_bytes = Vec::from_iter( self.outputs .iter() - .map(|o| o.commit(tag).to_bytes().to_vec()), + .map(|o| o.commit(zone_id).to_bytes().to_vec()), ); let output_merkle_leaves = merkle::padded_leaves::(&output_bytes); @@ -135,8 +151,8 @@ pub struct PartialTxInputWitness { } impl PartialTxInputWitness { - pub fn input_root(&self, tag: &dyn AsRef<[u8]>) -> [u8; 32] { - let leaf = merkle::leaf(&self.input.commit(tag).to_bytes()); + pub fn input_root(&self, zone_id: ZoneId) -> [u8; 32] { + let leaf = merkle::leaf(&self.input.commit(zone_id).to_bytes()); merkle::path_root(leaf, &self.path) } } @@ -149,8 +165,8 @@ pub struct PartialTxOutputWitness { } impl PartialTxOutputWitness { - pub fn output_root(&self, tag: &dyn AsRef<[u8]>) -> [u8; 32] { - let leaf = merkle::leaf(&self.output.commit(tag).to_bytes()); + pub fn output_root(&self, zone_id: ZoneId) -> [u8; 32] { + let leaf = merkle::leaf(&self.output.commit(zone_id).to_bytes()); merkle::path_root(leaf, &self.path) } } @@ -158,13 +174,13 @@ impl PartialTxOutputWitness { #[cfg(test)] mod test { - use crate::{ - balance::UnitBalance, - note::{derive_unit, NoteWitness}, - nullifier::NullifierSecret, - }; + // use crate::cl::{ + // balance::UnitBalance, + // note::{derive_unit, NoteWitness}, + // nullifier::NullifierSecret, + // }; - use super::*; + // use super::*; // #[test] // fn test_partial_tx_balance() { diff --git a/emmarin/cl/cl/tests/simple_transfer.rs b/emmarin/cl/cl/tests/simple_transfer.rs index f1a7a95..01a2cc8 100644 --- a/emmarin/cl/cl/tests/simple_transfer.rs +++ b/emmarin/cl/cl/tests/simple_transfer.rs @@ -1,7 +1,10 @@ -use cl::{note::derive_unit, BalanceWitness}; +use cl::cl::{ + note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, + NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, +}; -fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { - cl::OutputWitness::new(note, nf_pk) +fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness { + OutputWitness::new(note, nf_pk) } #[test] @@ -9,27 +12,26 @@ fn test_simple_transfer() { let nmo = derive_unit("NMO"); let mut rng = rand::thread_rng(); - let sender_nf_sk = cl::NullifierSecret::random(&mut rng); + let sender_nf_sk = NullifierSecret::random(&mut rng); let sender_nf_pk = sender_nf_sk.commit(); - let recipient_nf_pk = cl::NullifierSecret::random(&mut rng).commit(); + let recipient_nf_pk = NullifierSecret::random(&mut rng).commit(); // Assume the sender has received an unspent output from somewhere - let utxo = receive_utxo(cl::NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); + let utxo = receive_utxo(NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); // and wants to send 8 NMO to some recipient and return 2 NMO to itself. let recipient_output = - cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk); - let change_output = - cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); + OutputWitness::new(NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk); + let change_output = OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); - let ptx_witness = cl::PartialTxWitness { - inputs: vec![cl::InputWitness::from_output(utxo, sender_nf_sk)], + let ptx_witness = PartialTxWitness { + inputs: vec![InputWitness::from_output(utxo, sender_nf_sk)], outputs: vec![recipient_output, change_output], balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let bundle = cl::BundleWitness { + let bundle = BundleWitness { partials: vec![ptx_witness], }; diff --git a/emmarin/cl/ledger/src/bundle.rs b/emmarin/cl/ledger/src/bundle.rs index 25f0765..e8d4e8e 100644 --- a/emmarin/cl/ledger/src/bundle.rs +++ b/emmarin/cl/ledger/src/bundle.rs @@ -2,6 +2,7 @@ use crate::error::{Error, Result}; use cl::cl::BundleWitness; use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic}; +#[derive(Debug, Clone)] pub struct ProvedBundle { pub bundle: BundlePublic, pub risc0_receipt: risc0_zkvm::Receipt, diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index 6fab328..7920972 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -1,47 +1,38 @@ -use ledger_proof_statements::ledger::{LedgerProofPrivate, LedgerProofPublic, ZoneTx}; +use ledger_proof_statements::{ + ledger::{LedgerProofPrivate, LedgerProofPublic}, + ptx::PtxPublic, +}; use crate::{ bundle::ProvedBundle, constraint::ConstraintProof, error::{Error, Result}, - pact::ProvedPact, partial_tx::ProvedPartialTx, }; use cl::zone_layer::{ledger::LedgerWitness, notes::ZoneId}; +#[derive(Debug, Clone)] pub struct ProvedLedgerTransition { pub public: LedgerProofPublic, pub risc0_receipt: risc0_zkvm::Receipt, } -pub enum ProvedZoneTx { - LocalTx { - bundle: ProvedBundle, - ptxs: Vec, - }, - Pact(ProvedPact), +// TODO: find a better name +#[derive(Debug, Clone)] +pub struct ProvedZoneTx { + pub bundle: ProvedBundle, + pub ptxs: Vec, } impl ProvedZoneTx { - fn to_public(&self) -> ZoneTx { - match self { - Self::LocalTx { ptxs, bundle } => ZoneTx::LocalTx { - ptxs: ptxs.iter().map(|p| p.public().unwrap()).collect(), - bundle: bundle.public().unwrap(), - }, - Self::Pact(pact) => ZoneTx::Pact(pact.public().unwrap()), - } + fn to_public(&self) -> Vec { + self.ptxs.iter().map(|p| p.public().unwrap()).collect() } fn proofs(&self) -> Vec { - match self { - Self::LocalTx { ptxs, bundle } => { - let mut proofs = vec![bundle.risc0_receipt.clone()]; - proofs.extend(ptxs.iter().map(|p| p.risc0_receipt.clone())); - proofs - } - Self::Pact(pact) => vec![pact.risc0_receipt.clone()], - } + let mut proofs = vec![self.bundle.risc0_receipt.clone()]; + proofs.extend(self.ptxs.iter().map(|p| p.risc0_receipt.clone())); + proofs } } @@ -53,7 +44,7 @@ impl ProvedLedgerTransition { constraints: Vec, ) -> Result { let witness = LedgerProofPrivate { - txs: ptxs.iter().map(|p| p.to_public()).collect(), + bundles: ptxs.iter().map(|p| p.to_public()).collect(), ledger, id: zone_id, }; diff --git a/emmarin/cl/ledger/src/lib.rs b/emmarin/cl/ledger/src/lib.rs index 93be05a..ad327b3 100644 --- a/emmarin/cl/ledger/src/lib.rs +++ b/emmarin/cl/ledger/src/lib.rs @@ -2,7 +2,6 @@ pub mod bundle; pub mod constraint; pub mod error; pub mod ledger; -pub mod pact; pub mod partial_tx; pub mod stf; pub mod zone_update; diff --git a/emmarin/cl/ledger/src/pact.rs b/emmarin/cl/ledger/src/pact.rs deleted file mode 100644 index 44d52eb..0000000 --- a/emmarin/cl/ledger/src/pact.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::error::{Error, Result}; -use cl::cl::{ - merkle, - pact::{Pact, PactWitness}, -}; -use ledger_proof_statements::pact::{PactPrivate, PactPublic}; - -#[derive(Debug, Clone)] -pub struct ProvedPact { - pub pact: Pact, - pub cm_root: [u8; 32], - pub risc0_receipt: risc0_zkvm::Receipt, -} - -impl ProvedPact { - pub fn prove( - pact: PactWitness, - input_cm_paths: Vec>, - cm_root: [u8; 32], - ) -> Result { - let pact_private = PactPrivate { - pact, - input_cm_paths, - cm_root, - }; - - let env = risc0_zkvm::ExecutorEnv::builder() - .write(&pact_private) - .unwrap() - .build() - .unwrap(); - - // Obtain the default prover. - let prover = risc0_zkvm::default_prover(); - - let start_t = std::time::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, nomos_cl_risc0_proofs::PACT_ELF, &opts) - .map_err(|_| Error::Risc0ProofFailed)?; - - println!( - "STARK 'pact' prover time: {:.2?}, total_cycles: {}", - start_t.elapsed(), - prove_info.stats.total_cycles - ); - - Ok(Self { - pact: pact_private.pact.commit(), - cm_root, - risc0_receipt: prove_info.receipt, - }) - } - - pub fn public(&self) -> Result { - Ok(self.risc0_receipt.journal.decode()?) - } - - pub fn verify(&self) -> bool { - let Ok(proved_ptx_inputs) = self.public() else { - return false; - }; - let expected_ptx_inputs = PactPublic { - pact: self.pact.clone(), - cm_root: self.cm_root, - }; - if expected_ptx_inputs != proved_ptx_inputs { - return false; - } - - self.risc0_receipt - .verify(nomos_cl_risc0_proofs::PACT_ID) - .is_ok() - } -} diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index 6a1c614..d49891f 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -4,6 +4,7 @@ use crate::error::{Error, Result}; use cl::cl::{merkle, PartialTx, PartialTxWitness}; use cl::zone_layer::notes::ZoneId; +#[derive(Debug, Clone)] pub struct ProvedPartialTx { pub ptx: PartialTx, pub cm_root: [u8; 32], @@ -12,16 +13,19 @@ pub struct ProvedPartialTx { impl ProvedPartialTx { pub fn prove( - ptx: &PartialTxWitness, + ptx_witness: PartialTxWitness, input_cm_paths: Vec>, cm_root: [u8; 32], - id: ZoneId, + from: Vec, + to: Vec, ) -> Result { + let ptx = ptx_witness.commit(&from, &to); let ptx_private = PtxPrivate { - ptx: ptx.clone(), + ptx: ptx_witness, input_cm_paths, cm_root, - from: id, + from, + to, }; let env = risc0_zkvm::ExecutorEnv::builder() @@ -49,7 +53,7 @@ impl ProvedPartialTx { ); Ok(Self { - ptx: ptx.commit(&id), + ptx, cm_root, risc0_receipt: prove_info.receipt, }) diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index 879ca19..dbccf05 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,29 +1,36 @@ -use cl::{note::derive_unit, BalanceWitness}; +use cl::{ + cl::{ + note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, + NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, + }, + zone_layer::ledger::LedgerWitness, +}; use ledger::{ + bundle::ProvedBundle, constraint::ConstraintProof, ledger::{ProvedLedgerTransition, ProvedZoneTx}, - pact::ProvedPact, + partial_tx::ProvedPartialTx, }; use rand_core::CryptoRngCore; -struct User(cl::NullifierSecret); +struct User(NullifierSecret); impl User { fn random(mut rng: impl CryptoRngCore) -> Self { - Self(cl::NullifierSecret::random(&mut rng)) + Self(NullifierSecret::random(&mut rng)) } - fn pk(&self) -> cl::NullifierCommitment { + fn pk(&self) -> NullifierCommitment { self.0.commit() } - fn sk(&self) -> cl::NullifierSecret { + fn sk(&self) -> NullifierSecret { self.0 } } -fn receive_utxo(note: cl::NoteWitness, nf_pk: cl::NullifierCommitment) -> cl::OutputWitness { - cl::OutputWitness::new(note, nf_pk) +fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness { + OutputWitness::new(note, nf_pk) } #[test] @@ -39,81 +46,82 @@ fn ledger_transition() { // Alice has an unspent note worth 10 NMO let utxo = receive_utxo( - cl::NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng), + NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng), alice.pk(), ); // Alice wants to send 8 NMO to bob - let bobs_output = cl::OutputWitness::new(cl::NoteWitness::basic(8, nmo, &mut rng), bob.pk()); + let bobs_output = OutputWitness::new(NoteWitness::basic(8, nmo, &mut rng), bob.pk()); - let alice_change = cl::OutputWitness::new(cl::NoteWitness::basic(2, nmo, &mut rng), alice.pk()); + let alice_change = OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), alice.pk()); - let alice_input = cl::InputWitness::from_output(utxo, alice.sk()); + let alice_input = InputWitness::from_output(utxo, alice.sk()); let zone_a = [0; 32]; let zone_b = [1; 32]; - let ledger_a = cl::zones::LedgerWitness { + let ledger_a = LedgerWitness { commitments: vec![utxo.commit_note(&zone_a)], nullifiers: vec![], }; - let ledger_b = cl::zones::LedgerWitness { + let ledger_b = LedgerWitness { commitments: vec![], nullifiers: vec![], }; - let expected_ledger_a = cl::zones::LedgerWitness { + let expected_ledger_a = LedgerWitness { commitments: vec![utxo.commit_note(&zone_a), alice_change.commit_note(&zone_a)], nullifiers: vec![alice_input.nullifier(&zone_a)], }; - let expected_ledger_b = cl::zones::LedgerWitness { + let expected_ledger_b = LedgerWitness { commitments: vec![bobs_output.commit_note(&zone_b)], nullifiers: vec![], }; // Construct the ptx consuming Alices inputs and producing the two outputs. - let alice_pact_witness = cl::zones::PactWitness { - tx: cl::PartialTxWitness { - inputs: vec![alice_input], - outputs: vec![bobs_output, alice_change], - - balance_blinding: BalanceWitness::random_blinding(&mut rng), - }, - from: zone_a, - to: vec![zone_b, zone_a], + let alice_ptx_witness = PartialTxWitness { + inputs: vec![alice_input], + outputs: vec![bobs_output, alice_change], + balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - - let proved_pact = ProvedPact::prove( - alice_pact_witness, + let proved_ptx = ProvedPartialTx::prove( + alice_ptx_witness.clone(), vec![ledger_a .cm_path(&alice_input.note_commitment(&zone_a)) .unwrap()], ledger_a.cm_root(), + vec![zone_a], + vec![zone_b, zone_a], ) .unwrap(); - assert_eq!(proved_pact.cm_root, ledger_a.cm_root()); + let bundle = ProvedBundle::prove(&BundleWitness { + partials: vec![alice_ptx_witness], + }) + .unwrap(); + + assert_eq!(proved_ptx.cm_root, ledger_a.cm_root()); + + let zone_tx = ProvedZoneTx { + ptxs: vec![proved_ptx.clone()], + bundle, + }; // Prove the constraints for alices input (she uses the no-op constraint) let constraint_proof = - ConstraintProof::prove_nop(alice_input.nullifier(&zone_a), proved_pact.pact.tx.root()); + ConstraintProof::prove_nop(alice_input.nullifier(&zone_a), proved_ptx.ptx.root()); let ledger_a_transition = ProvedLedgerTransition::prove( ledger_a, zone_a, - vec![ProvedZoneTx::Pact(proved_pact.clone())], + vec![zone_tx.clone()], vec![constraint_proof], ) .unwrap(); - let ledger_b_transition = ProvedLedgerTransition::prove( - ledger_b, - zone_b, - vec![ProvedZoneTx::Pact(proved_pact)], - vec![], - ) - .unwrap(); + let ledger_b_transition = + ProvedLedgerTransition::prove(ledger_b, zone_b, vec![zone_tx], vec![]).unwrap(); assert_eq!( ledger_a_transition.public.ledger, diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index ed3d728..824246e 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -1,5 +1,3 @@ -use crate::bundle::BundlePublic; -use crate::pact::PactPublic; use crate::ptx::PtxPublic; use cl::cl::Output; use cl::zone_layer::{ @@ -21,14 +19,5 @@ pub struct LedgerProofPublic { pub struct LedgerProofPrivate { pub ledger: LedgerWitness, pub id: ZoneId, - pub txs: Vec, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum ZoneTx { - LocalTx { - ptxs: Vec, - bundle: BundlePublic, - }, - Pact(PactPublic), + pub bundles: Vec>, } diff --git a/emmarin/cl/ledger_proof_statements/src/lib.rs b/emmarin/cl/ledger_proof_statements/src/lib.rs index fa0941c..5d907dc 100644 --- a/emmarin/cl/ledger_proof_statements/src/lib.rs +++ b/emmarin/cl/ledger_proof_statements/src/lib.rs @@ -1,6 +1,5 @@ pub mod bundle; pub mod constraint; pub mod ledger; -pub mod pact; pub mod ptx; pub mod stf; diff --git a/emmarin/cl/ledger_proof_statements/src/pact.rs b/emmarin/cl/ledger_proof_statements/src/pact.rs deleted file mode 100644 index f9a6c8c..0000000 --- a/emmarin/cl/ledger_proof_statements/src/pact.rs +++ /dev/null @@ -1,18 +0,0 @@ -use cl::cl::{ - merkle, - pact::{Pact, PactWitness}, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PactPublic { - pub pact: Pact, - pub cm_root: [u8; 32], -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PactPrivate { - pub pact: PactWitness, - pub input_cm_paths: Vec>, - pub cm_root: [u8; 32], -} diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index a11070c..fe256cc 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -1,4 +1,7 @@ -use cl::cl::{merkle, PartialTx, PartialTxWitness}; +use cl::{ + cl::{merkle, PartialTx, PartialTxWitness}, + zone_layer::notes::ZoneId, +}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -12,5 +15,6 @@ pub struct PtxPrivate { pub ptx: PartialTxWitness, pub input_cm_paths: Vec>, pub cm_root: [u8; 32], - pub from: [u8; 32], + pub from: Vec, + pub to: Vec, } diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 55bf280..b71212c 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -2,14 +2,19 @@ use cl::{ cl::Output, zone_layer::{ledger::LedgerWitness, notes::ZoneId}, }; -use ledger_proof_statements::{bundle::*, constraint::*, ledger::*, pact::PactPublic, ptx::*}; +use ledger_proof_statements::{ + bundle::BundlePublic, + constraint::ConstraintPublic, + ledger::{LedgerProofPrivate, LedgerProofPublic}, + ptx::PtxPublic, +}; use risc0_zkvm::{guest::env, serde}; fn main() { let LedgerProofPrivate { mut ledger, id, - txs, + bundles, } = env::read(); let old_ledger = ledger.commit(); @@ -19,18 +24,23 @@ fn main() { let mut cross_in = vec![]; let mut cross_out = vec![]; - for tx in txs { - match tx { - ZoneTx::LocalTx { bundle, ptxs } => { - ledger = process_bundle(ledger, ptxs, bundle, cm_root); - } - ZoneTx::Pact(pact) => { - let (new_ledger, consumed_commits, produced_commits) = - process_pact(ledger, pact, id, cm_root); - ledger = new_ledger; - cross_in.extend(consumed_commits); - cross_out.extend(produced_commits); - } + for bundle in bundles { + let bundle_public = BundlePublic { + balances: bundle.iter().map(|ptx| ptx.ptx.balance).collect::>(), + }; + // verify bundle is balanced + env::verify( + nomos_cl_risc0_proofs::BUNDLE_ID, + &serde::to_vec(&bundle_public).unwrap(), + ) + .unwrap(); + + for ptx in &bundle { + let (new_ledger, consumed_commitments, produced_commitments) = + process_ptx(ledger, ptx, id, cm_root); + cross_in.extend(consumed_commitments); + cross_out.extend(produced_commitments); + ledger = new_ledger; } } @@ -43,106 +53,51 @@ fn main() { }); } -fn process_bundle( +fn process_ptx( mut ledger: LedgerWitness, - ptxs: Vec, - bundle_proof: BundlePublic, - cm_root: [u8; 32], -) -> LedgerWitness { - assert_eq!( - ptxs.iter().map(|ptx| ptx.ptx.balance).collect::>(), - bundle_proof.balances - ); - // verify bundle is balanced - env::verify( - nomos_cl_risc0_proofs::BUNDLE_ID, - &serde::to_vec(&bundle_proof).unwrap(), - ) - .unwrap(); - - for ptx in ptxs { - ledger = process_ptx(ledger, ptx, cm_root); - } - - ledger -} - -fn process_ptx(mut ledger: LedgerWitness, ptx: PtxPublic, cm_root: [u8; 32]) -> LedgerWitness { - env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap(); - assert_eq!(ptx.cm_root, cm_root); - - let ptx = ptx.ptx; - - for input in &ptx.inputs { - assert!(!ledger.nullifiers.contains(&input.nullifier)); - ledger.nullifiers.push(input.nullifier); - - env::verify( - input.constraint.0, - &serde::to_vec(&ConstraintPublic { - ptx_root: ptx.root(), - nf: input.nullifier, - }) - .unwrap(), - ) - .unwrap(); - } - - for output in &ptx.outputs { - ledger.commitments.push(output.note_comm); - } - - ledger -} - -fn process_pact( - mut ledger: LedgerWitness, - pact: PactPublic, - id: ZoneId, + ptx: &PtxPublic, + zone_id: ZoneId, cm_root: [u8; 32], ) -> (LedgerWitness, Vec, Vec) { let mut cross_in = vec![]; let mut cross_out = vec![]; - env::verify( - nomos_cl_risc0_proofs::PACT_ID, - &serde::to_vec(&pact).unwrap(), - ) - .unwrap(); + env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap(); - let pact_cm_root = pact.cm_root; - let pact = pact.pact; + let ptx_cm_root = ptx.cm_root; + let ptx = &ptx.ptx; - if cm_root != pact_cm_root { - // zone is the receiver of the transfer - for (comm, zone) in pact.tx.outputs.iter().zip(&pact.to) { - if *zone == id { - cross_in.push(*comm); - ledger.commitments.push(comm.note_comm); - } - } - } else { - // zone is the sender of the transfer - // proof of non-membership - for input in &pact.tx.inputs { + // TODO: accept inputs from multiple zones + let check_inputs = ptx.inputs.iter().all(|input| input.zone_id == zone_id); + + if check_inputs { + assert_eq!(ptx_cm_root, cm_root); + for input in &ptx.inputs { assert!(!ledger.nullifiers.contains(&input.nullifier)); ledger.nullifiers.push(input.nullifier); env::verify( input.constraint.0, &serde::to_vec(&ConstraintPublic { - ptx_root: pact.tx.root(), + ptx_root: ptx.root(), nf: input.nullifier, }) .unwrap(), ) .unwrap(); } + } - for (output, to) in pact.tx.outputs.iter().zip(&pact.to) { - if *to == id { - ledger.commitments.push(output.note_comm); - } else { + for output in &ptx.outputs { + if output.zone_id == zone_id { + ledger.commitments.push(output.note_comm); + // if this output was not originating from this zone, it is a cross zone transaction + if !check_inputs { + cross_in.push(*output); + } + } else { + // if this output is not going to this zone but originated from this zone, it is a cross zone transaction + if check_inputs { cross_out.push(*output); } } diff --git a/emmarin/cl/risc0_proofs/Cargo.toml b/emmarin/cl/risc0_proofs/Cargo.toml index b34bd02..21cc296 100644 --- a/emmarin/cl/risc0_proofs/Cargo.toml +++ b/emmarin/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["bundle", "constraint_nop", "ptx", "pact"] +methods = ["bundle", "constraint_nop", "ptx"] diff --git a/emmarin/cl/risc0_proofs/pact/Cargo.toml b/emmarin/cl/risc0_proofs/pact/Cargo.toml deleted file mode 100644 index 9fdfdc0..0000000 --- a/emmarin/cl/risc0_proofs/pact/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "pact" -version = "0.1.0" -edition = "2021" - -[workspace] - -[dependencies] -risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } -serde = { version = "1.0", features = ["derive"] } -cl = { path = "../../cl" } -ledger_proof_statements = { path = "../../ledger_proof_statements" } - - -[patch.crates-io] -# add RISC Zero 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" } -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/emmarin/cl/risc0_proofs/pact/src/main.rs b/emmarin/cl/risc0_proofs/pact/src/main.rs deleted file mode 100644 index 0dd6e79..0000000 --- a/emmarin/cl/risc0_proofs/pact/src/main.rs +++ /dev/null @@ -1,30 +0,0 @@ -/// Input Proof -use cl::cl::merkle; -use ledger_proof_statements::pact::{PactPrivate, PactPublic}; -use risc0_zkvm::guest::env; - -fn main() { - let PactPrivate { - pact, - input_cm_paths, - cm_root, - } = env::read(); - - assert_eq!(pact.tx.inputs.len(), input_cm_paths.len()); - for (input, cm_path) in pact.tx.inputs.iter().zip(input_cm_paths) { - let note_cm = input.note_commitment(&pact.from); - let cm_leaf = merkle::leaf(note_cm.as_bytes()); - assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); - } - - for output in pact.tx.outputs.iter() { - assert!(output.note.value > 0); - } - - assert!(pact.tx.balance().is_zero()); - - env::commit(&PactPublic { - pact: pact.commit(), - cm_root, - }); -} diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index 49012cc..af4c2b1 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -9,11 +9,12 @@ fn main() { input_cm_paths, cm_root, from, + to, } = env::read(); assert_eq!(ptx.inputs.len(), input_cm_paths.len()); - for (input, cm_path) in ptx.inputs.iter().zip(input_cm_paths) { - let note_cm = input.note_commitment(&from); + for ((input, cm_path), zone_id) in ptx.inputs.iter().zip(input_cm_paths).zip(&from) { + let note_cm = input.note_commitment(zone_id); let cm_leaf = merkle::leaf(note_cm.as_bytes()); assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); } @@ -23,7 +24,7 @@ fn main() { } env::commit(&PtxPublic { - ptx: ptx.commit(&from), + ptx: ptx.commit(&from, &to), cm_root, }); } From 854fab935b30ffe2be7a346aecbc75d2fabd8138 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Mon, 25 Nov 2024 13:06:53 +0100 Subject: [PATCH 05/10] check zone updates --- emmarin/cl/cl/tests/simple_transfer.rs | 1 + emmarin/cl/ledger/src/stf.rs | 32 +++- emmarin/cl/ledger/src/zone_update.rs | 41 ---- emmarin/cl/ledger/tests/simple_transfer.rs | 202 ++++++++++++++------ emmarin/cl/risc0_proofs/Cargo.toml | 2 +- emmarin/cl/risc0_proofs/stf_nop/Cargo.toml | 19 ++ emmarin/cl/risc0_proofs/stf_nop/src/main.rs | 8 + 7 files changed, 205 insertions(+), 100 deletions(-) create mode 100644 emmarin/cl/risc0_proofs/stf_nop/Cargo.toml create mode 100644 emmarin/cl/risc0_proofs/stf_nop/src/main.rs diff --git a/emmarin/cl/cl/tests/simple_transfer.rs b/emmarin/cl/cl/tests/simple_transfer.rs index 01a2cc8..4cad8a1 100644 --- a/emmarin/cl/cl/tests/simple_transfer.rs +++ b/emmarin/cl/cl/tests/simple_transfer.rs @@ -37,3 +37,4 @@ fn test_simple_transfer() { assert!(bundle.balance().is_zero()) } + diff --git a/emmarin/cl/ledger/src/stf.rs b/emmarin/cl/ledger/src/stf.rs index f416008..d826503 100644 --- a/emmarin/cl/ledger/src/stf.rs +++ b/emmarin/cl/ledger/src/stf.rs @@ -26,8 +26,38 @@ impl StfProof { pub fn stf(&self) -> Stf { risc0_constraint(self.risc0_id) } - pub fn verify(&self) -> bool { self.risc0_receipt.verify(self.risc0_id).is_ok() } + + pub fn prove_nop(public: StfPublic) -> Self { + let env = risc0_zkvm::ExecutorEnv::builder() + .write(&public) + .unwrap() + .build() + .unwrap(); + + let prover = risc0_zkvm::default_prover(); + + let start_t = std::time::Instant::now(); + + let opts = risc0_zkvm::ProverOpts::succinct(); + let prove_info = prover + .prove_with_opts(env, nomos_cl_risc0_proofs::STF_NOP_ELF, &opts) + .unwrap(); + + println!( + "STARK 'stf' prover time: {:.2?}, total_cycles: {}", + start_t.elapsed(), + prove_info.stats.total_cycles + ); + + let receipt = prove_info.receipt; + + Self { + risc0_id: nomos_cl_risc0_proofs::STF_NOP_ID, + public, + risc0_receipt: receipt, + } + } } diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs index efcb251..fb145cd 100644 --- a/emmarin/cl/ledger/src/zone_update.rs +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -10,47 +10,6 @@ pub struct ProvedUpdateBundle { } impl ProvedUpdateBundle { - // pub fn prove(bundle_witness: &BundleWitness) -> Result { - // // need to show that bundle is balanced. - // // i.e. the sum of ptx balances is 0 - - // let bundle_private = BundlePrivate { - // balances: bundle_witness - // .partials - // .iter() - // .map(|ptx| ptx.balance()) - // .collect(), - // }; - - // let env = risc0_zkvm::ExecutorEnv::builder() - // .write(&bundle_private) - // .unwrap() - // .build() - // .unwrap(); - - // let prover = risc0_zkvm::default_prover(); - - // let start_t = std::time::Instant::now(); - - // let opts = risc0_zkvm::ProverOpts::succinct(); - // let prove_info = prover - // .prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts) - // .map_err(|_| Error::Risc0ProofFailed)?; - - // println!( - // "STARK 'bundle' prover time: {:.2?}, total_cycles: {}", - // start_t.elapsed(), - // prove_info.stats.total_cycles - // ); - - // let receipt = prove_info.receipt; - - // Ok(Self { - // bundle: receipt.journal.decode()?, - // risc0_receipt: receipt, - // }) - // } - pub fn verify(&self) -> bool { let mut consumed_commitments = HashSet::new(); let mut produced_commitments = HashSet::new(); diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index dbccf05..8918fb6 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,17 +1,30 @@ use cl::{ cl::{ - note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, + balance::Unit, note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, }, - zone_layer::ledger::LedgerWitness, + zone_layer::{ + ledger::LedgerWitness, + notes::ZoneNote, + tx::{UpdateBundle, ZoneUpdate}, + }, }; use ledger::{ bundle::ProvedBundle, constraint::ConstraintProof, ledger::{ProvedLedgerTransition, ProvedZoneTx}, partial_tx::ProvedPartialTx, + stf::StfProof, + zone_update::ProvedUpdateBundle, }; +use ledger_proof_statements::stf::StfPublic; use rand_core::CryptoRngCore; +use std::sync::OnceLock; + +fn nmo() -> &'static Unit { + static NMO: OnceLock = OnceLock::new(); + NMO.get_or_init(|| derive_unit("NMO")) +} struct User(NullifierSecret); @@ -33,63 +46,33 @@ fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness OutputWitness::new(note, nf_pk) } -#[test] -fn ledger_transition() { - let nmo = derive_unit("NMO"); - +fn cross_transfer_transition( + input: InputWitness, + to: User, + amount: u64, + zone_a: [u8; 32], + zone_b: [u8; 32], + ledger_a: LedgerWitness, + ledger_b: LedgerWitness, +) -> (ProvedLedgerTransition, ProvedLedgerTransition) { let mut rng = rand::thread_rng(); - - // alice is sending 8 NMO to bob. - - let alice = User::random(&mut rng); - let bob = User::random(&mut rng); - - // Alice has an unspent note worth 10 NMO - let utxo = receive_utxo( - NoteWitness::stateless(10, nmo, ConstraintProof::nop_constraint(), &mut rng), - alice.pk(), + assert!(amount <= input.note.value); + let change = input.note.value - amount; + let transfer = OutputWitness::new(NoteWitness::basic(amount, *nmo(), &mut rng), to.pk()); + let change = OutputWitness::new( + NoteWitness::basic(change, *nmo(), &mut rng), + input.nf_sk.commit(), ); - // Alice wants to send 8 NMO to bob - let bobs_output = OutputWitness::new(NoteWitness::basic(8, nmo, &mut rng), bob.pk()); - - let alice_change = OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), alice.pk()); - - let alice_input = InputWitness::from_output(utxo, alice.sk()); - - let zone_a = [0; 32]; - let zone_b = [1; 32]; - let ledger_a = LedgerWitness { - commitments: vec![utxo.commit_note(&zone_a)], - nullifiers: vec![], - }; - - let ledger_b = LedgerWitness { - commitments: vec![], - nullifiers: vec![], - }; - - let expected_ledger_a = LedgerWitness { - commitments: vec![utxo.commit_note(&zone_a), alice_change.commit_note(&zone_a)], - nullifiers: vec![alice_input.nullifier(&zone_a)], - }; - - let expected_ledger_b = LedgerWitness { - commitments: vec![bobs_output.commit_note(&zone_b)], - nullifiers: vec![], - }; - - // Construct the ptx consuming Alices inputs and producing the two outputs. - let alice_ptx_witness = PartialTxWitness { - inputs: vec![alice_input], - outputs: vec![bobs_output, alice_change], + // Construct the ptx consuming the input and producing the two outputs. + let ptx_witness = PartialTxWitness { + inputs: vec![input], + outputs: vec![transfer, change], balance_blinding: BalanceWitness::random_blinding(&mut rng), }; let proved_ptx = ProvedPartialTx::prove( - alice_ptx_witness.clone(), - vec![ledger_a - .cm_path(&alice_input.note_commitment(&zone_a)) - .unwrap()], + ptx_witness.clone(), + vec![ledger_a.cm_path(&input.note_commitment(&zone_a)).unwrap()], ledger_a.cm_root(), vec![zone_a], vec![zone_b, zone_a], @@ -97,12 +80,10 @@ fn ledger_transition() { .unwrap(); let bundle = ProvedBundle::prove(&BundleWitness { - partials: vec![alice_ptx_witness], + partials: vec![ptx_witness], }) .unwrap(); - assert_eq!(proved_ptx.cm_root, ledger_a.cm_root()); - let zone_tx = ProvedZoneTx { ptxs: vec![proved_ptx.clone()], bundle, @@ -110,7 +91,7 @@ fn ledger_transition() { // Prove the constraints for alices input (she uses the no-op constraint) let constraint_proof = - ConstraintProof::prove_nop(alice_input.nullifier(&zone_a), proved_ptx.ptx.root()); + ConstraintProof::prove_nop(input.nullifier(&zone_a), proved_ptx.ptx.root()); let ledger_a_transition = ProvedLedgerTransition::prove( ledger_a, @@ -123,6 +104,16 @@ fn ledger_transition() { let ledger_b_transition = ProvedLedgerTransition::prove(ledger_b, zone_b, vec![zone_tx], vec![]).unwrap(); + let expected_ledger_a = LedgerWitness { + commitments: vec![input.note_commitment(&zone_a), change.commit_note(&zone_a)], + nullifiers: vec![input.nullifier(&zone_a)], + }; + + let expected_ledger_b = LedgerWitness { + commitments: vec![transfer.commit_note(&zone_b)], + nullifiers: vec![], + }; + assert_eq!( ledger_a_transition.public.ledger, expected_ledger_a.commit() @@ -131,4 +122,101 @@ fn ledger_transition() { ledger_b_transition.public.ledger, expected_ledger_b.commit() ); + + (ledger_a_transition, ledger_b_transition) +} + +#[test] +fn zone_update_cross() { + let mut rng = rand::thread_rng(); + + // alice is sending 8 NMO to bob. + + let alice = User::random(&mut rng); + let bob = User::random(&mut rng); + + // Alice has an unspent note worth 10 NMO + let utxo = receive_utxo( + NoteWitness::stateless(10, *nmo(), ConstraintProof::nop_constraint(), &mut rng), + alice.pk(), + ); + + let alice_input = InputWitness::from_output(utxo, alice.sk()); + + let zone_a_id = [0; 32]; + let zone_b_id = [1; 32]; + + let ledger_a = LedgerWitness { + commitments: vec![utxo.commit_note(&zone_a_id)], + nullifiers: vec![], + }; + + let ledger_b = LedgerWitness { + commitments: vec![], + nullifiers: vec![], + }; + + let zone_a_old = ZoneNote { + id: zone_a_id, + state: [0; 32], + ledger: ledger_a.commit(), + stf: [0; 32], + }; + let zone_b_old = ZoneNote { + id: zone_b_id, + state: [0; 32], + ledger: ledger_b.commit(), + stf: [0; 32], + }; + + let (ledger_a_transition, ledger_b_transition) = cross_transfer_transition( + alice_input, + bob, + 8, + zone_a_id, + zone_b_id, + ledger_a, + ledger_b, + ); + + let zone_a_new = ZoneNote { + ledger: ledger_a_transition.public.ledger, + ..zone_a_old + }; + + let zone_b_new = ZoneNote { + ledger: ledger_b_transition.public.ledger, + ..zone_b_old + }; + + let stf_proof_a = StfProof::prove_nop(StfPublic { + old: zone_a_old, + new: zone_a_new, + }); + + let stf_proof_b = StfProof::prove_nop(StfPublic { + old: zone_b_old, + new: zone_b_new, + }); + + let update_bundle = UpdateBundle { + updates: vec![ + ZoneUpdate { + old: zone_a_old, + new: zone_a_new, + }, + ZoneUpdate { + old: zone_b_old, + new: zone_b_new, + }, + ], + }; + + let proved_bundle = ProvedUpdateBundle { + bundle: update_bundle, + ledger_proofs: vec![ledger_a_transition, ledger_b_transition], + stf_proofs: vec![stf_proof_a, stf_proof_b], + }; + + assert!(proved_bundle.verify()); } diff --git a/emmarin/cl/risc0_proofs/Cargo.toml b/emmarin/cl/risc0_proofs/Cargo.toml index 21cc296..e544320 100644 --- a/emmarin/cl/risc0_proofs/Cargo.toml +++ b/emmarin/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["bundle", "constraint_nop", "ptx"] +methods = ["bundle", "constraint_nop", "ptx", "stf_nop"] diff --git a/emmarin/cl/risc0_proofs/stf_nop/Cargo.toml b/emmarin/cl/risc0_proofs/stf_nop/Cargo.toml new file mode 100644 index 0000000..ec1e0aa --- /dev/null +++ b/emmarin/cl/risc0_proofs/stf_nop/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "stf_nop" +version = "0.1.0" +edition = "2021" + +[workspace] + +[dependencies] +risc0-zkvm = { version = "1.0", default-features = false, features = ['std'] } +serde = { version = "1.0", features = ["derive"] } +cl = { path = "../../cl" } +ledger_proof_statements = { path = "../../ledger_proof_statements" } + + +[patch.crates-io] +# add RISC Zero 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" } +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/emmarin/cl/risc0_proofs/stf_nop/src/main.rs b/emmarin/cl/risc0_proofs/stf_nop/src/main.rs new file mode 100644 index 0000000..7c10c92 --- /dev/null +++ b/emmarin/cl/risc0_proofs/stf_nop/src/main.rs @@ -0,0 +1,8 @@ +/// Constraint No-op Proof +use ledger_proof_statements::stf::StfPublic; +use risc0_zkvm::guest::env; + +fn main() { + let public: StfPublic = env::read(); + env::commit(&public); +} From a0b9b357dafe92f25c283628514d1e333269598d Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Mon, 25 Nov 2024 18:45:00 +0100 Subject: [PATCH 06/10] remove limitation of single ptx origin --- emmarin/cl/cl/src/cl/bundle.rs | 36 +++++++++- .../cl/ledger/src/{bundle.rs => balance.rs} | 16 ++--- emmarin/cl/ledger/src/ledger.rs | 18 ++--- emmarin/cl/ledger/src/lib.rs | 2 +- emmarin/cl/ledger/src/partial_tx.rs | 28 ++------ emmarin/cl/ledger/src/zone_update.rs | 33 ++++----- emmarin/cl/ledger/tests/simple_transfer.rs | 12 ++-- .../src/{bundle.rs => balance.rs} | 4 +- .../cl/ledger_proof_statements/src/ledger.rs | 12 +++- emmarin/cl/ledger_proof_statements/src/lib.rs | 2 +- emmarin/cl/ledger_proof_statements/src/ptx.rs | 4 +- .../ledger_validity_proof/ledger/src/main.rs | 67 +++++++++---------- emmarin/cl/risc0_proofs/Cargo.toml | 2 +- .../{bundle => balance}/Cargo.toml | 2 +- .../{bundle => balance}/src/main.rs | 10 +-- emmarin/cl/risc0_proofs/ptx/src/main.rs | 14 ++-- 16 files changed, 145 insertions(+), 117 deletions(-) rename emmarin/cl/ledger/src/{bundle.rs => balance.rs} (79%) rename emmarin/cl/ledger_proof_statements/src/{bundle.rs => balance.rs} (83%) rename emmarin/cl/risc0_proofs/{bundle => balance}/Cargo.toml (97%) rename emmarin/cl/risc0_proofs/{bundle => balance}/src/main.rs (57%) diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs index c9166cf..b43e734 100644 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -1,16 +1,50 @@ use serde::{Deserialize, Serialize}; -use crate::cl::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}; +use crate::{ + cl::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}, + zone_layer::notes::ZoneId, +}; +use sha2::{Digest, Sha256}; +use std::collections::HashSet; /// The transaction bundle is a collection of partial transactions. /// The goal in bundling transactions is to produce a set of partial transactions /// that balance each other. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct BundleId(pub [u8; 32]); + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Bundle { pub partials: Vec, } +impl Bundle { + pub fn zones(&self) -> HashSet { + self.partials + .iter() + .flat_map(|ptx| { + ptx.inputs + .iter() + .map(|i| i.zone_id) + .chain(ptx.outputs.iter().map(|o| o.zone_id)) + }) + .collect() + } + + /// + pub fn id(&self) -> BundleId { + // TODO: change to merkle root + let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_BUNDLE_ID"); + for ptx in &self.partials { + hasher.update(&ptx.root().0); + } + + BundleId(hasher.finalize().into()) + } +} + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct BundleWitness { pub partials: Vec, diff --git a/emmarin/cl/ledger/src/bundle.rs b/emmarin/cl/ledger/src/balance.rs similarity index 79% rename from emmarin/cl/ledger/src/bundle.rs rename to emmarin/cl/ledger/src/balance.rs index e8d4e8e..453ae8c 100644 --- a/emmarin/cl/ledger/src/bundle.rs +++ b/emmarin/cl/ledger/src/balance.rs @@ -1,19 +1,19 @@ use crate::error::{Error, Result}; use cl::cl::BundleWitness; -use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic}; +use ledger_proof_statements::balance::{BalancePrivate, BalancePublic}; #[derive(Debug, Clone)] -pub struct ProvedBundle { - pub bundle: BundlePublic, +pub struct ProvedBalance { + pub bundle: BalancePublic, pub risc0_receipt: risc0_zkvm::Receipt, } -impl ProvedBundle { +impl ProvedBalance { pub fn prove(bundle_witness: &BundleWitness) -> Result { // need to show that bundle is balanced. // i.e. the sum of ptx balances is 0 - let bundle_private = BundlePrivate { + let bundle_private = BalancePrivate { balances: bundle_witness .partials .iter() @@ -33,7 +33,7 @@ impl ProvedBundle { let opts = risc0_zkvm::ProverOpts::succinct(); let prove_info = prover - .prove_with_opts(env, nomos_cl_risc0_proofs::BUNDLE_ELF, &opts) + .prove_with_opts(env, nomos_cl_risc0_proofs::BALANCE_ELF, &opts) .map_err(|_| Error::Risc0ProofFailed)?; println!( @@ -50,7 +50,7 @@ impl ProvedBundle { }) } - pub fn public(&self) -> Result { + pub fn public(&self) -> Result { Ok(self.risc0_receipt.journal.decode()?) } @@ -62,7 +62,7 @@ impl ProvedBundle { // Vec::from_iter(self.bundle.partials.iter().map(|ptx| ptx.balance)) == bundle_public.balances // && self.risc0_receipt - .verify(nomos_cl_risc0_proofs::BUNDLE_ID) + .verify(nomos_cl_risc0_proofs::BALANCE_ID) .is_ok() } } diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index 7920972..d7cdf68 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -4,7 +4,7 @@ use ledger_proof_statements::{ }; use crate::{ - bundle::ProvedBundle, + balance::ProvedBalance, constraint::ConstraintProof, error::{Error, Result}, partial_tx::ProvedPartialTx, @@ -19,14 +19,14 @@ pub struct ProvedLedgerTransition { // TODO: find a better name #[derive(Debug, Clone)] -pub struct ProvedZoneTx { - pub bundle: ProvedBundle, +pub struct ProvedBundle { + pub bundle: ProvedBalance, pub ptxs: Vec, } -impl ProvedZoneTx { +impl ProvedBundle { fn to_public(&self) -> Vec { - self.ptxs.iter().map(|p| p.public().unwrap()).collect() + self.ptxs.iter().map(|p| p.public.clone()).collect() } fn proofs(&self) -> Vec { @@ -40,19 +40,19 @@ impl ProvedLedgerTransition { pub fn prove( ledger: LedgerWitness, zone_id: ZoneId, - ptxs: Vec, + bundles: Vec, constraints: Vec, ) -> Result { let witness = LedgerProofPrivate { - bundles: ptxs.iter().map(|p| p.to_public()).collect(), + bundles: bundles.iter().map(|p| p.to_public()).collect(), ledger, id: zone_id, }; let mut env = risc0_zkvm::ExecutorEnv::builder(); - for ptx in ptxs { - for proof in ptx.proofs() { + for bundle in bundles { + for proof in bundle.proofs() { env.add_assumption(proof); } } diff --git a/emmarin/cl/ledger/src/lib.rs b/emmarin/cl/ledger/src/lib.rs index ad327b3..d42f46c 100644 --- a/emmarin/cl/ledger/src/lib.rs +++ b/emmarin/cl/ledger/src/lib.rs @@ -1,4 +1,4 @@ -pub mod bundle; +pub mod balance; pub mod constraint; pub mod error; pub mod ledger; diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index d49891f..d57bea5 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -1,13 +1,12 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use crate::error::{Error, Result}; -use cl::cl::{merkle, PartialTx, PartialTxWitness}; +use cl::cl::{merkle, PartialTxWitness}; use cl::zone_layer::notes::ZoneId; #[derive(Debug, Clone)] pub struct ProvedPartialTx { - pub ptx: PartialTx, - pub cm_root: [u8; 32], + pub public: PtxPublic, pub risc0_receipt: risc0_zkvm::Receipt, } @@ -15,15 +14,14 @@ impl ProvedPartialTx { pub fn prove( ptx_witness: PartialTxWitness, input_cm_paths: Vec>, - cm_root: [u8; 32], + cm_roots: Vec<[u8; 32]>, from: Vec, to: Vec, ) -> Result { - let ptx = ptx_witness.commit(&from, &to); let ptx_private = PtxPrivate { ptx: ptx_witness, input_cm_paths, - cm_root, + cm_roots: cm_roots.clone(), from, to, }; @@ -53,28 +51,12 @@ impl ProvedPartialTx { ); Ok(Self { - ptx, - cm_root, + public: prove_info.receipt.journal.decode()?, risc0_receipt: prove_info.receipt, }) } - pub fn public(&self) -> Result { - Ok(self.risc0_receipt.journal.decode()?) - } - pub fn verify(&self) -> bool { - let Ok(proved_ptx_inputs) = self.public() else { - return false; - }; - let expected_ptx_inputs = PtxPublic { - ptx: self.ptx.clone(), - cm_root: self.cm_root, - }; - if expected_ptx_inputs != proved_ptx_inputs { - return false; - } - self.risc0_receipt .verify(nomos_cl_risc0_proofs::PTX_ID) .is_ok() diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs index fb145cd..2bf7baa 100644 --- a/emmarin/cl/ledger/src/zone_update.rs +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -1,7 +1,7 @@ pub use crate::error::{Error, Result}; use crate::{ledger::ProvedLedgerTransition, stf::StfProof}; use cl::zone_layer::tx::UpdateBundle; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; pub struct ProvedUpdateBundle { pub bundle: UpdateBundle, @@ -11,28 +11,31 @@ pub struct ProvedUpdateBundle { impl ProvedUpdateBundle { pub fn verify(&self) -> bool { - let mut consumed_commitments = HashSet::new(); - let mut produced_commitments = HashSet::new(); + let mut expected_zones = HashMap::new(); + let mut actual_zones = HashMap::new(); for proof in &self.ledger_proofs { if !proof.verify() { return false; } - for comm in &proof.public.cross_out { - if produced_commitments.insert(comm) { - // already in? - } - } - for comm in &proof.public.cross_in { - if consumed_commitments.insert(comm) { - // already in? - } + for bundle in &proof.public.cross_bundles { + expected_zones.insert(bundle.id, HashSet::from_iter(bundle.zones.clone())); + actual_zones + .entry(bundle.id) + .or_insert_with(|| HashSet::new()) + .insert(proof.public.id); } } - // check that cross zone transactions match - if consumed_commitments != produced_commitments { - return false; + println!("{:?} | {:?}", expected_zones, actual_zones); + for (bundle, expected) in expected_zones.iter() { + if let Some(actual) = actual_zones.get(bundle) { + if actual != expected { + panic!("{:?} | {:?}", actual, expected); + } + } else { + panic!(); + } } for ((update, stf_proof), ledger_proof) in self diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index 8918fb6..b9858c3 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -10,9 +10,9 @@ use cl::{ }, }; use ledger::{ - bundle::ProvedBundle, + balance::ProvedBalance, constraint::ConstraintProof, - ledger::{ProvedLedgerTransition, ProvedZoneTx}, + ledger::{ProvedBundle, ProvedLedgerTransition}, partial_tx::ProvedPartialTx, stf::StfProof, zone_update::ProvedUpdateBundle, @@ -73,25 +73,25 @@ fn cross_transfer_transition( let proved_ptx = ProvedPartialTx::prove( ptx_witness.clone(), vec![ledger_a.cm_path(&input.note_commitment(&zone_a)).unwrap()], - ledger_a.cm_root(), + vec![ledger_a.cm_root()], vec![zone_a], vec![zone_b, zone_a], ) .unwrap(); - let bundle = ProvedBundle::prove(&BundleWitness { + let bundle = ProvedBalance::prove(&BundleWitness { partials: vec![ptx_witness], }) .unwrap(); - let zone_tx = ProvedZoneTx { + let zone_tx = ProvedBundle { ptxs: vec![proved_ptx.clone()], bundle, }; // Prove the constraints for alices input (she uses the no-op constraint) let constraint_proof = - ConstraintProof::prove_nop(input.nullifier(&zone_a), proved_ptx.ptx.root()); + ConstraintProof::prove_nop(input.nullifier(&zone_a), proved_ptx.public.ptx.root()); let ledger_a_transition = ProvedLedgerTransition::prove( ledger_a, diff --git a/emmarin/cl/ledger_proof_statements/src/bundle.rs b/emmarin/cl/ledger_proof_statements/src/balance.rs similarity index 83% rename from emmarin/cl/ledger_proof_statements/src/bundle.rs rename to emmarin/cl/ledger_proof_statements/src/balance.rs index f952a4c..509b1b5 100644 --- a/emmarin/cl/ledger_proof_statements/src/bundle.rs +++ b/emmarin/cl/ledger_proof_statements/src/balance.rs @@ -2,11 +2,11 @@ use cl::cl::{Balance, BalanceWitness}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BundlePublic { +pub struct BalancePublic { pub balances: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BundlePrivate { +pub struct BalancePrivate { pub balances: Vec, } diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index 824246e..a7feda6 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -1,5 +1,5 @@ use crate::ptx::PtxPublic; -use cl::cl::Output; +use cl::cl::{bundle::BundleId, Output}; use cl::zone_layer::{ ledger::{Ledger, LedgerWitness}, notes::ZoneId, @@ -11,8 +11,8 @@ pub struct LedgerProofPublic { pub old_ledger: Ledger, pub ledger: Ledger, pub id: ZoneId, - pub cross_in: Vec, - pub cross_out: Vec, + pub cross_bundles: Vec, + pub outputs: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -21,3 +21,9 @@ pub struct LedgerProofPrivate { pub id: ZoneId, pub bundles: Vec>, } + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct CrossZoneBundle { + pub id: BundleId, + pub zones: Vec, +} diff --git a/emmarin/cl/ledger_proof_statements/src/lib.rs b/emmarin/cl/ledger_proof_statements/src/lib.rs index 5d907dc..2000d4b 100644 --- a/emmarin/cl/ledger_proof_statements/src/lib.rs +++ b/emmarin/cl/ledger_proof_statements/src/lib.rs @@ -1,4 +1,4 @@ -pub mod bundle; +pub mod balance; pub mod constraint; pub mod ledger; pub mod ptx; diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index fe256cc..7f53548 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -7,14 +7,14 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPublic { pub ptx: PartialTx, - pub cm_root: [u8; 32], + pub cm_roots: Vec<[u8; 32]>, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, pub input_cm_paths: Vec>, - pub cm_root: [u8; 32], + pub cm_roots: Vec<[u8; 32]>, pub from: Vec, pub to: Vec, } diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index b71212c..d4b644a 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -1,11 +1,11 @@ use cl::{ - cl::Output, + cl::{Bundle, Output}, zone_layer::{ledger::LedgerWitness, notes::ZoneId}, }; use ledger_proof_statements::{ - bundle::BundlePublic, + balance::BalancePublic, constraint::ConstraintPublic, - ledger::{LedgerProofPrivate, LedgerProofPublic}, + ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic}, ptx::PtxPublic, }; use risc0_zkvm::{guest::env, serde}; @@ -18,29 +18,37 @@ fn main() { } = env::read(); let old_ledger = ledger.commit(); + let mut cross_bundles = vec![]; + let mut outputs = vec![]; let cm_root = ledger.cm_root(); - let mut cross_in = vec![]; - let mut cross_out = vec![]; - for bundle in bundles { - let bundle_public = BundlePublic { + let balance_public = BalancePublic { balances: bundle.iter().map(|ptx| ptx.ptx.balance).collect::>(), }; // verify bundle is balanced env::verify( - nomos_cl_risc0_proofs::BUNDLE_ID, - &serde::to_vec(&bundle_public).unwrap(), + nomos_cl_risc0_proofs::BALANCE_ID, + &serde::to_vec(&balance_public).unwrap(), ) .unwrap(); for ptx in &bundle { - let (new_ledger, consumed_commitments, produced_commitments) = - process_ptx(ledger, ptx, id, cm_root); - cross_in.extend(consumed_commitments); - cross_out.extend(produced_commitments); + let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, cm_root); ledger = new_ledger; + outputs.extend(ptx_outputs); + } + + let bundle = Bundle { + partials: bundle.into_iter().map(|ptx| ptx.ptx).collect(), + }; + let zones = bundle.zones(); + if zones.len() > 1 { + cross_bundles.push(CrossZoneBundle { + id: bundle.id(), + zones: zones.into_iter().collect(), + }); } } @@ -48,8 +56,8 @@ fn main() { old_ledger, ledger: ledger.commit(), id, - cross_in, - cross_out, + cross_bundles, + outputs, }); } @@ -58,21 +66,18 @@ fn process_ptx( ptx: &PtxPublic, zone_id: ZoneId, cm_root: [u8; 32], -) -> (LedgerWitness, Vec, Vec) { - let mut cross_in = vec![]; - let mut cross_out = vec![]; - +) -> (LedgerWitness, Vec) { + // always verify the ptx to ensure outputs were derived with the correct zone id env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap(); - let ptx_cm_root = ptx.cm_root; + let cm_roots = &ptx.cm_roots; let ptx = &ptx.ptx; - // TODO: accept inputs from multiple zones - let check_inputs = ptx.inputs.iter().all(|input| input.zone_id == zone_id); + let mut outputs = vec![]; - if check_inputs { - assert_eq!(ptx_cm_root, cm_root); - for input in &ptx.inputs { + for (input, input_cm_root) in ptx.inputs.iter().zip(cm_roots) { + if input.zone_id == zone_id { + assert_eq!(*input_cm_root, cm_root); assert!(!ledger.nullifiers.contains(&input.nullifier)); ledger.nullifiers.push(input.nullifier); @@ -91,17 +96,9 @@ fn process_ptx( for output in &ptx.outputs { if output.zone_id == zone_id { ledger.commitments.push(output.note_comm); - // if this output was not originating from this zone, it is a cross zone transaction - if !check_inputs { - cross_in.push(*output); - } - } else { - // if this output is not going to this zone but originated from this zone, it is a cross zone transaction - if check_inputs { - cross_out.push(*output); - } + outputs.push(*output); } } - (ledger, cross_in, cross_out) + (ledger, outputs) } diff --git a/emmarin/cl/risc0_proofs/Cargo.toml b/emmarin/cl/risc0_proofs/Cargo.toml index e544320..19d562a 100644 --- a/emmarin/cl/risc0_proofs/Cargo.toml +++ b/emmarin/cl/risc0_proofs/Cargo.toml @@ -7,5 +7,5 @@ edition = "2021" risc0-build = { version = "1.0" } [package.metadata.risc0] -methods = ["bundle", "constraint_nop", "ptx", "stf_nop"] +methods = ["balance", "constraint_nop", "ptx", "stf_nop"] diff --git a/emmarin/cl/risc0_proofs/bundle/Cargo.toml b/emmarin/cl/risc0_proofs/balance/Cargo.toml similarity index 97% rename from emmarin/cl/risc0_proofs/bundle/Cargo.toml rename to emmarin/cl/risc0_proofs/balance/Cargo.toml index f207737..161f1c8 100644 --- a/emmarin/cl/risc0_proofs/bundle/Cargo.toml +++ b/emmarin/cl/risc0_proofs/balance/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bundle" +name = "balance" version = "0.1.0" edition = "2021" diff --git a/emmarin/cl/risc0_proofs/bundle/src/main.rs b/emmarin/cl/risc0_proofs/balance/src/main.rs similarity index 57% rename from emmarin/cl/risc0_proofs/bundle/src/main.rs rename to emmarin/cl/risc0_proofs/balance/src/main.rs index 30d2f59..3342001 100644 --- a/emmarin/cl/risc0_proofs/bundle/src/main.rs +++ b/emmarin/cl/risc0_proofs/balance/src/main.rs @@ -12,13 +12,13 @@ use cl::cl::BalanceWitness; use risc0_zkvm::guest::env; fn main() { - let bundle_private: ledger_proof_statements::bundle::BundlePrivate = env::read(); + let balance_private: ledger_proof_statements::balance::BalancePrivate = env::read(); - let bundle_public = ledger_proof_statements::bundle::BundlePublic { - balances: Vec::from_iter(bundle_private.balances.iter().map(|b| b.commit())), + let balance_public = ledger_proof_statements::balance::BalancePublic { + balances: Vec::from_iter(balance_private.balances.iter().map(|b| b.commit())), }; - assert!(BalanceWitness::combine(bundle_private.balances, [0u8; 16]).is_zero()); + assert!(BalanceWitness::combine(balance_private.balances, [0u8; 16]).is_zero()); - env::commit(&bundle_public); + env::commit(&balance_public); } diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index af4c2b1..83a8cee 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -7,16 +7,22 @@ fn main() { let PtxPrivate { ptx, input_cm_paths, - cm_root, + cm_roots, from, to, } = env::read(); assert_eq!(ptx.inputs.len(), input_cm_paths.len()); - for ((input, cm_path), zone_id) in ptx.inputs.iter().zip(input_cm_paths).zip(&from) { + for (((input, cm_path), zone_id), cm_root) in ptx + .inputs + .iter() + .zip(input_cm_paths) + .zip(&from) + .zip(&cm_roots) + { let note_cm = input.note_commitment(zone_id); let cm_leaf = merkle::leaf(note_cm.as_bytes()); - assert_eq!(cm_root, merkle::path_root(cm_leaf, &cm_path)); + assert_eq!(*cm_root, merkle::path_root(cm_leaf, &cm_path)); } for output in ptx.outputs.iter() { @@ -25,6 +31,6 @@ fn main() { env::commit(&PtxPublic { ptx: ptx.commit(&from, &to), - cm_root, + cm_roots, }); } From 3646971fd9ef853065e415ce1b3799d0409402d1 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 27 Nov 2024 11:44:42 +0100 Subject: [PATCH 07/10] use MMR --- emmarin/cl/cl/Cargo.toml | 2 +- emmarin/cl/cl/src/cl/mmr.rs | 126 ++++++++++++++++++ emmarin/cl/cl/src/cl/mod.rs | 1 + emmarin/cl/cl/src/cl/note.rs | 1 + emmarin/cl/cl/src/zone_layer/ledger.rs | 31 +---- emmarin/cl/ledger/src/balance.rs | 17 +-- emmarin/cl/ledger/tests/simple_transfer.rs | 53 ++++---- .../ledger_validity_proof/ledger/src/main.rs | 15 ++- 8 files changed, 171 insertions(+), 75 deletions(-) create mode 100644 emmarin/cl/cl/src/cl/mmr.rs diff --git a/emmarin/cl/cl/Cargo.toml b/emmarin/cl/cl/Cargo.toml index b73749b..761fa4a 100644 --- a/emmarin/cl/cl/Cargo.toml +++ b/emmarin/cl/cl/Cargo.toml @@ -12,4 +12,4 @@ rand = "0.8.5" rand_core = "0.6.0" hex = "0.4.3" curve25519-dalek = {version = "4.1", features = ["serde", "digest", "rand_core"]} -sha2 = "0.10" +sha2 = "0.10" \ No newline at end of file diff --git a/emmarin/cl/cl/src/cl/mmr.rs b/emmarin/cl/cl/src/cl/mmr.rs new file mode 100644 index 0000000..32ef04c --- /dev/null +++ b/emmarin/cl/cl/src/cl/mmr.rs @@ -0,0 +1,126 @@ +use crate::cl::merkle; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MMR { + pub roots: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Root { + pub root: [u8; 32], + pub height: u8, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct MMRProof { + pub path: Vec, +} + +impl MMR { + pub fn new() -> Self { + Self { roots: vec![] } + } + + pub fn push(&mut self, elem: &[u8]) -> MMRProof { + let new_root = Root { + root: merkle::leaf(elem), + height: 1, + }; + self.roots.push(new_root); + + let mut path = vec![]; + + for i in (1..self.roots.len()).rev() { + if self.roots[i].height == self.roots[i - 1].height { + path.push(merkle::PathNode::Left(self.roots[i - 1].root)); + + self.roots[i - 1] = Root { + root: merkle::node(self.roots[i - 1].root, self.roots[i].root), + height: self.roots[i - 1].height + 1, + }; + + self.roots.remove(i); + } else { + break; + } + } + + MMRProof { path } + } + + pub fn verify_proof(&self, elem: &[u8], proof: &MMRProof) -> bool { + let path_len = proof.path.len(); + let leaf = merkle::leaf(elem); + let root = merkle::path_root(leaf, &proof.path); + + for mmr_root in self.roots.iter() { + if mmr_root.height == (path_len + 1) as u8 { + return mmr_root.root == root; + } + } + + false + } + + pub fn commit(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + for mrr_root in self.roots.iter() { + hasher.update(mrr_root.root); + hasher.update(mrr_root.height.to_le_bytes()); + } + hasher.finalize().into() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_mrr_push() { + let mut mmr = MMR::new(); + let proof = mmr.push(b"hello"); + + assert_eq!(mmr.roots.len(), 1); + assert_eq!(mmr.roots[0].height, 1); + assert_eq!(mmr.roots[0].root, merkle::leaf(b"hello")); + assert!(mmr.verify_proof(b"hello", &proof)); + + let proof = mmr.push(b"world"); + + assert_eq!(mmr.roots.len(), 1); + assert_eq!(mmr.roots[0].height, 2); + assert_eq!( + mmr.roots[0].root, + merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world")) + ); + assert!(mmr.verify_proof(b"world", &proof)); + + let proof = mmr.push(b"!"); + + assert_eq!(mmr.roots.len(), 2); + assert_eq!(mmr.roots[0].height, 2); + assert_eq!( + mmr.roots[0].root, + merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world")) + ); + assert_eq!(mmr.roots[1].height, 1); + assert_eq!(mmr.roots[1].root, merkle::leaf(b"!")); + assert!(mmr.verify_proof(b"!", &proof)); + + let proof = mmr.push(b"!"); + + assert_eq!(mmr.roots.len(), 1); + assert_eq!(mmr.roots[0].height, 3); + assert_eq!( + mmr.roots[0].root, + merkle::node( + merkle::node(merkle::leaf(b"hello"), merkle::leaf(b"world")), + merkle::node(merkle::leaf(b"!"), merkle::leaf(b"!")) + ) + ); + assert!(mmr.verify_proof(b"!", &proof)); + } +} diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs index e1d559e..0ba3e1c 100644 --- a/emmarin/cl/cl/src/cl/mod.rs +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -4,6 +4,7 @@ pub mod crypto; pub mod error; pub mod input; pub mod merkle; +pub mod mmr; pub mod note; pub mod nullifier; pub mod output; diff --git a/emmarin/cl/cl/src/cl/note.rs b/emmarin/cl/cl/src/cl/note.rs index ca9c313..e9b6c31 100644 --- a/emmarin/cl/cl/src/cl/note.rs +++ b/emmarin/cl/cl/src/cl/note.rs @@ -73,6 +73,7 @@ impl NoteWitness { pub fn commit(&self, tag: &dyn AsRef<[u8]>, nf_pk: NullifierCommitment) -> NoteCommitment { let mut hasher = Sha256::new(); + hasher.update(b"NOMOS_CL_NOTE_CM"); hasher.update(tag.as_ref()); // COMMIT TO BALANCE diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index 11acbe6..43d476b 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -1,6 +1,8 @@ -use crate::cl::{merkle, NoteCommitment, Nullifier}; +use crate::cl::{merkle, mmr::MMR, Nullifier}; use serde::{Deserialize, Serialize}; +const MAX_NULL: usize = 256; + #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Ledger { cm_root: [u8; 32], @@ -9,17 +11,14 @@ pub struct Ledger { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct LedgerWitness { - pub commitments: Vec, + pub commitments: MMR, pub nullifiers: Vec, } -const MAX_COMM: usize = 256; -const MAX_NULL: usize = 256; - impl LedgerWitness { pub fn commit(&self) -> Ledger { Ledger { - cm_root: self.cm_root(), + cm_root: self.commitments.commit(), nf_root: self.nf_root(), } } @@ -32,24 +31,4 @@ impl LedgerWitness { .collect::>(); merkle::root(merkle::padded_leaves::(&bytes)) } - - pub fn cm_root(&self) -> [u8; 32] { - let bytes = self - .commitments - .iter() - .map(|i| i.as_bytes().to_vec()) - .collect::>(); - merkle::root(merkle::padded_leaves::(&bytes)) - } - - pub fn cm_path(&self, cm: &NoteCommitment) -> Option> { - let bytes = self - .commitments - .iter() - .map(|i| i.as_bytes().to_vec()) - .collect::>(); - let leaves = merkle::padded_leaves::(&bytes); - let idx = self.commitments.iter().position(|c| c == cm)?; - Some(merkle::path(leaves, idx)) - } } diff --git a/emmarin/cl/ledger/src/balance.rs b/emmarin/cl/ledger/src/balance.rs index 453ae8c..d408782 100644 --- a/emmarin/cl/ledger/src/balance.rs +++ b/emmarin/cl/ledger/src/balance.rs @@ -1,5 +1,4 @@ use crate::error::{Error, Result}; -use cl::cl::BundleWitness; use ledger_proof_statements::balance::{BalancePrivate, BalancePublic}; #[derive(Debug, Clone)] @@ -9,20 +8,10 @@ pub struct ProvedBalance { } impl ProvedBalance { - pub fn prove(bundle_witness: &BundleWitness) -> Result { - // need to show that bundle is balanced. - // i.e. the sum of ptx balances is 0 - - let bundle_private = BalancePrivate { - balances: bundle_witness - .partials - .iter() - .map(|ptx| ptx.balance()) - .collect(), - }; - + pub fn prove(balance_witness: &BalancePrivate) -> Result { + //show that the sum of ptx balances is 0 let env = risc0_zkvm::ExecutorEnv::builder() - .write(&bundle_private) + .write(&balance_witness) .unwrap() .build() .unwrap(); diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index b9858c3..ef25efa 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,7 +1,8 @@ use cl::{ cl::{ - balance::Unit, note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, - NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, + balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, BundleWitness, + InputWitness, NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, + PartialTxWitness, }, zone_layer::{ ledger::LedgerWitness, @@ -17,7 +18,7 @@ use ledger::{ stf::StfProof, zone_update::ProvedUpdateBundle, }; -use ledger_proof_statements::stf::StfPublic; +use ledger_proof_statements::{balance::BalancePrivate, stf::StfPublic}; use rand_core::CryptoRngCore; use std::sync::OnceLock; @@ -48,12 +49,13 @@ fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness fn cross_transfer_transition( input: InputWitness, + input_path: Vec, to: User, amount: u64, zone_a: [u8; 32], zone_b: [u8; 32], - ledger_a: LedgerWitness, - ledger_b: LedgerWitness, + mut ledger_a: LedgerWitness, + mut ledger_b: LedgerWitness, ) -> (ProvedLedgerTransition, ProvedLedgerTransition) { let mut rng = rand::thread_rng(); assert!(amount <= input.note.value); @@ -72,15 +74,15 @@ fn cross_transfer_transition( }; let proved_ptx = ProvedPartialTx::prove( ptx_witness.clone(), - vec![ledger_a.cm_path(&input.note_commitment(&zone_a)).unwrap()], - vec![ledger_a.cm_root()], + vec![input_path], + vec![ledger_a.commitments.roots[0].root], vec![zone_a], vec![zone_b, zone_a], ) .unwrap(); - let bundle = ProvedBalance::prove(&BundleWitness { - partials: vec![ptx_witness], + let bundle = ProvedBalance::prove(&BalancePrivate { + balances: vec![ptx_witness.balance()], }) .unwrap(); @@ -94,7 +96,7 @@ fn cross_transfer_transition( ConstraintProof::prove_nop(input.nullifier(&zone_a), proved_ptx.public.ptx.root()); let ledger_a_transition = ProvedLedgerTransition::prove( - ledger_a, + ledger_a.clone(), zone_a, vec![zone_tx.clone()], vec![constraint_proof], @@ -102,26 +104,15 @@ fn cross_transfer_transition( .unwrap(); let ledger_b_transition = - ProvedLedgerTransition::prove(ledger_b, zone_b, vec![zone_tx], vec![]).unwrap(); + ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![zone_tx], vec![]).unwrap(); - let expected_ledger_a = LedgerWitness { - commitments: vec![input.note_commitment(&zone_a), change.commit_note(&zone_a)], - nullifiers: vec![input.nullifier(&zone_a)], - }; + ledger_a.commitments.push(&change.commit_note(&zone_a).0); + ledger_a.nullifiers.push(input.nullifier(&zone_a)); - let expected_ledger_b = LedgerWitness { - commitments: vec![transfer.commit_note(&zone_b)], - nullifiers: vec![], - }; + ledger_b.commitments.push(&transfer.commit_note(&zone_b).0); - assert_eq!( - ledger_a_transition.public.ledger, - expected_ledger_a.commit() - ); - assert_eq!( - ledger_b_transition.public.ledger, - expected_ledger_b.commit() - ); + assert_eq!(ledger_a_transition.public.ledger, ledger_a.commit()); + assert_eq!(ledger_b_transition.public.ledger, ledger_b.commit()); (ledger_a_transition, ledger_b_transition) } @@ -146,13 +137,16 @@ fn zone_update_cross() { let zone_a_id = [0; 32]; let zone_b_id = [1; 32]; + let mut mmr = MMR::new(); + let input_cm_path = mmr.push(&utxo.commit_note(&zone_a_id).0).path; + let ledger_a = LedgerWitness { - commitments: vec![utxo.commit_note(&zone_a_id)], + commitments: mmr, nullifiers: vec![], }; let ledger_b = LedgerWitness { - commitments: vec![], + commitments: MMR::new(), nullifiers: vec![], }; @@ -171,6 +165,7 @@ fn zone_update_cross() { let (ledger_a_transition, ledger_b_transition) = cross_transfer_transition( alice_input, + input_cm_path, bob, 8, zone_a_id, diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index d4b644a..5849b15 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -21,7 +21,12 @@ fn main() { let mut cross_bundles = vec![]; let mut outputs = vec![]; - let cm_root = ledger.cm_root(); + let roots = ledger + .commitments + .roots + .iter() + .map(|r| r.root) + .collect::>(); for bundle in bundles { let balance_public = BalancePublic { @@ -35,7 +40,7 @@ fn main() { .unwrap(); for ptx in &bundle { - let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, cm_root); + let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, &roots); ledger = new_ledger; outputs.extend(ptx_outputs); } @@ -65,7 +70,7 @@ fn process_ptx( mut ledger: LedgerWitness, ptx: &PtxPublic, zone_id: ZoneId, - cm_root: [u8; 32], + roots: &[[u8; 32]], ) -> (LedgerWitness, Vec) { // always verify the ptx to ensure outputs were derived with the correct zone id env::verify(nomos_cl_risc0_proofs::PTX_ID, &serde::to_vec(&ptx).unwrap()).unwrap(); @@ -77,7 +82,7 @@ fn process_ptx( for (input, input_cm_root) in ptx.inputs.iter().zip(cm_roots) { if input.zone_id == zone_id { - assert_eq!(*input_cm_root, cm_root); + assert!(roots.contains(input_cm_root)); assert!(!ledger.nullifiers.contains(&input.nullifier)); ledger.nullifiers.push(input.nullifier); @@ -95,7 +100,7 @@ fn process_ptx( for output in &ptx.outputs { if output.zone_id == zone_id { - ledger.commitments.push(output.note_comm); + ledger.commitments.push(&output.note_comm.0); outputs.push(*output); } } From 720836e7f213d3e17f280e138678eee5b997f377 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 27 Nov 2024 17:17:27 +0100 Subject: [PATCH 08/10] move zone_id into {input,output}witness --- emmarin/cl/cl/src/cl/bundle.rs | 65 ++++++++----------- emmarin/cl/cl/src/cl/input.rs | 36 +++++----- emmarin/cl/cl/src/cl/mmr.rs | 7 +- emmarin/cl/cl/src/cl/mod.rs | 2 +- emmarin/cl/cl/src/cl/output.rs | 41 +++++++----- emmarin/cl/cl/src/cl/partial_tx.rs | 58 +++++------------ emmarin/cl/cl/tests/simple_transfer.rs | 33 +++++----- emmarin/cl/ledger/src/ledger.rs | 4 +- emmarin/cl/ledger/src/partial_tx.rs | 5 -- emmarin/cl/ledger/tests/simple_transfer.rs | 45 +++++++------ emmarin/cl/ledger_proof_statements/src/ptx.rs | 7 +- 11 files changed, 140 insertions(+), 163 deletions(-) diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs index b43e734..aa32e08 100644 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -1,9 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{ - cl::{partial_tx::PartialTx, BalanceWitness, PartialTxWitness}, - zone_layer::notes::ZoneId, -}; +use crate::{cl::partial_tx::PartialTx, zone_layer::notes::ZoneId}; use sha2::{Digest, Sha256}; use std::collections::HashSet; @@ -45,27 +42,10 @@ impl Bundle { } } -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BundleWitness { - pub partials: Vec, -} - -impl BundleWitness { - pub fn balance(&self) -> BalanceWitness { - BalanceWitness::combine(self.partials.iter().map(|ptx| ptx.balance()), [0u8; 16]) - } - - // pub fn commit(&self) -> Bundle { - // Bundle { - // partials: Vec::from_iter(self.partials.iter().map(|ptx| ptx.commit())), - // } - // } -} - #[cfg(test)] mod test { use crate::cl::{ - balance::UnitBalance, + balance::{BalanceWitness, UnitBalance}, input::InputWitness, note::{derive_unit, NoteWitness}, nullifier::NullifierSecret, @@ -73,25 +53,35 @@ mod test { partial_tx::PartialTxWitness, }; - use super::*; - #[test] fn test_bundle_balance() { let mut rng = rand::thread_rng(); + let zone_id = [0; 32]; let (nmo, eth, crv) = (derive_unit("NMO"), derive_unit("ETH"), derive_unit("CRV")); let nf_a = NullifierSecret::random(&mut rng); let nf_b = NullifierSecret::random(&mut rng); let nf_c = NullifierSecret::random(&mut rng); - let nmo_10_utxo = OutputWitness::new(NoteWitness::basic(10, nmo, &mut rng), nf_a.commit()); + let nmo_10_utxo = OutputWitness::new( + NoteWitness::basic(10, nmo, &mut rng), + nf_a.commit(), + zone_id, + ); let nmo_10_in = InputWitness::from_output(nmo_10_utxo, nf_a); - let eth_23_utxo = OutputWitness::new(NoteWitness::basic(23, eth, &mut rng), nf_b.commit()); + let eth_23_utxo = OutputWitness::new( + NoteWitness::basic(23, eth, &mut rng), + nf_b.commit(), + zone_id, + ); let eth_23_in = InputWitness::from_output(eth_23_utxo, nf_b); - let crv_4840_out = - OutputWitness::new(NoteWitness::basic(4840, crv, &mut rng), nf_c.commit()); + let crv_4840_out = OutputWitness::new( + NoteWitness::basic(4840, crv, &mut rng), + nf_c.commit(), + zone_id, + ); let ptx_unbalanced = PartialTxWitness { inputs: vec![nmo_10_in, eth_23_in], @@ -99,13 +89,9 @@ mod test { balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let bundle_witness = BundleWitness { - partials: vec![ptx_unbalanced.clone()], - }; - - assert!(!bundle_witness.balance().is_zero()); + assert!(!ptx_unbalanced.balance().is_zero()); assert_eq!( - bundle_witness.balance().balances, + ptx_unbalanced.balance().balances, vec![ UnitBalance { unit: nmo, @@ -129,10 +115,12 @@ mod test { let nmo_10_out = OutputWitness::new( NoteWitness::basic(10, nmo, &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner + zone_id, ); let eth_23_out = OutputWitness::new( NoteWitness::basic(23, eth, &mut rng), NullifierSecret::random(&mut rng).commit(), // transferring to a random owner + zone_id, ); let ptx_solved = PartialTxWitness { @@ -141,11 +129,10 @@ mod test { balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let witness = BundleWitness { - partials: vec![ptx_unbalanced, ptx_solved], - }; + let bundle_balance = + BalanceWitness::combine([ptx_unbalanced.balance(), ptx_solved.balance()], [0; 16]); - assert!(witness.balance().is_zero()); - assert_eq!(witness.balance().balances, vec![]); + assert!(bundle_balance.is_zero()); + assert_eq!(bundle_balance.balances, vec![]); } } diff --git a/emmarin/cl/cl/src/cl/input.rs b/emmarin/cl/cl/src/cl/input.rs index 6503b7b..56a6a08 100644 --- a/emmarin/cl/cl/src/cl/input.rs +++ b/emmarin/cl/cl/src/cl/input.rs @@ -24,59 +24,65 @@ pub struct Input { pub struct InputWitness { pub note: NoteWitness, pub nf_sk: NullifierSecret, + pub zone_id: ZoneId, } impl InputWitness { - pub fn new(note: NoteWitness, nf_sk: NullifierSecret) -> Self { - Self { note, nf_sk } + pub fn new(note: NoteWitness, nf_sk: NullifierSecret, zone_id: ZoneId) -> Self { + Self { + note, + nf_sk, + zone_id, + } } pub fn from_output(output: OutputWitness, nf_sk: NullifierSecret) -> Self { assert_eq!(nf_sk.commit(), output.nf_pk); - Self::new(output.note, nf_sk) + Self::new(output.note, nf_sk, output.zone_id) } pub fn public(output: OutputWitness) -> Self { let nf_sk = NullifierSecret::zero(); assert_eq!(nf_sk.commit(), output.nf_pk); // ensure the output was a public UTXO - Self::new(output.note, nf_sk) + Self::new(output.note, nf_sk, output.zone_id) } - pub fn evolved_nonce(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> Nonce { + pub fn evolved_nonce(&self, domain: &[u8]) -> Nonce { let mut hasher = Sha256::new(); hasher.update(b"NOMOS_COIN_EVOLVE"); hasher.update(domain); hasher.update(self.nf_sk.0); - hasher.update(self.note.commit(tag, self.nf_sk.commit()).0); + hasher.update(self.note.commit(&self.zone_id, self.nf_sk.commit()).0); let nonce_bytes: [u8; 32] = hasher.finalize().into(); Nonce::from_bytes(nonce_bytes) } - pub fn evolve_output(&self, tag: &dyn AsRef<[u8]>, domain: &[u8]) -> OutputWitness { + pub fn evolve_output(&self, domain: &[u8]) -> OutputWitness { OutputWitness { note: NoteWitness { - nonce: self.evolved_nonce(tag, domain), + nonce: self.evolved_nonce(domain), ..self.note }, nf_pk: self.nf_sk.commit(), + zone_id: self.zone_id, } } - pub fn nullifier(&self, tag: &dyn AsRef<[u8]>) -> Nullifier { - Nullifier::new(tag, self.nf_sk, self.note_commitment(tag)) + pub fn nullifier(&self) -> Nullifier { + Nullifier::new(&self.zone_id, self.nf_sk, self.note_commitment()) } - pub fn commit(&self, zone_id: ZoneId) -> Input { + pub fn commit(&self) -> Input { Input { - nullifier: self.nullifier(&zone_id), + nullifier: self.nullifier(), constraint: self.note.constraint, - zone_id, + zone_id: self.zone_id, } } - pub fn note_commitment(&self, tag: &dyn AsRef<[u8]>) -> NoteCommitment { - self.note.commit(tag, self.nf_sk.commit()) + pub fn note_commitment(&self) -> NoteCommitment { + self.note.commit(&self.zone_id, self.nf_sk.commit()) } } diff --git a/emmarin/cl/cl/src/cl/mmr.rs b/emmarin/cl/cl/src/cl/mmr.rs index 32ef04c..acba97c 100644 --- a/emmarin/cl/cl/src/cl/mmr.rs +++ b/emmarin/cl/cl/src/cl/mmr.rs @@ -65,10 +65,11 @@ impl MMR { } pub fn commit(&self) -> [u8; 32] { + // todo: baggin the peaks let mut hasher = Sha256::new(); - for mrr_root in self.roots.iter() { - hasher.update(mrr_root.root); - hasher.update(mrr_root.height.to_le_bytes()); + for mmr_root in self.roots.iter() { + hasher.update(mmr_root.root); + hasher.update(mmr_root.height.to_le_bytes()); } hasher.finalize().into() } diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs index 0ba3e1c..c2c25f6 100644 --- a/emmarin/cl/cl/src/cl/mod.rs +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -11,7 +11,7 @@ pub mod output; pub mod partial_tx; pub use balance::{Balance, BalanceWitness}; -pub use bundle::{Bundle, BundleWitness}; +pub use bundle::Bundle; pub use input::{Input, InputWitness}; pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness}; pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret}; diff --git a/emmarin/cl/cl/src/cl/output.rs b/emmarin/cl/cl/src/cl/output.rs index 40e7ccd..0bcc377 100644 --- a/emmarin/cl/cl/src/cl/output.rs +++ b/emmarin/cl/cl/src/cl/output.rs @@ -19,26 +19,35 @@ pub struct Output { pub struct OutputWitness { pub note: NoteWitness, pub nf_pk: NullifierCommitment, + pub zone_id: ZoneId, } impl OutputWitness { - pub fn new(note: NoteWitness, nf_pk: NullifierCommitment) -> Self { - Self { note, nf_pk } - } - - pub fn public(note: NoteWitness) -> Self { - let nf_pk = NullifierSecret::zero().commit(); - Self { note, nf_pk } - } - - pub fn commit_note(&self, tag: &dyn AsRef<[u8]>) -> NoteCommitment { - self.note.commit(tag, self.nf_pk) - } - - pub fn commit(&self, zone_id: ZoneId) -> Output { - Output { + pub fn new(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> Self { + Self { + note, + nf_pk, zone_id, - note_comm: self.commit_note(&zone_id), + } + } + + pub fn public(note: NoteWitness, zone_id: ZoneId) -> Self { + let nf_pk = NullifierSecret::zero().commit(); + Self { + note, + nf_pk, + zone_id, + } + } + + pub fn commit_note(&self) -> NoteCommitment { + self.note.commit(&self.zone_id, self.nf_pk) + } + + pub fn commit(&self) -> Output { + Output { + zone_id: self.zone_id, + note_comm: self.commit_note(), } } } diff --git a/emmarin/cl/cl/src/cl/partial_tx.rs b/emmarin/cl/cl/src/cl/partial_tx.rs index 2710a09..f4db0e4 100644 --- a/emmarin/cl/cl/src/cl/partial_tx.rs +++ b/emmarin/cl/cl/src/cl/partial_tx.rs @@ -1,14 +1,11 @@ use rand_core::{CryptoRngCore, RngCore}; use serde::{Deserialize, Serialize}; -use crate::{ - cl::{ - balance::{Balance, BalanceWitness}, - input::{Input, InputWitness}, - merkle, - output::{Output, OutputWitness}, - }, - zone_layer::notes::ZoneId, +use crate::cl::{ + balance::{Balance, BalanceWitness}, + input::{Input, InputWitness}, + merkle, + output::{Output, OutputWitness}, }; pub const MAX_INPUTS: usize = 8; @@ -68,33 +65,17 @@ impl PartialTxWitness { BalanceWitness::from_ptx(self, self.balance_blinding) } - pub fn commit(&self, input_zones: &[ZoneId], output_zones: &[ZoneId]) -> PartialTx { - assert_eq!(self.inputs.len(), input_zones.len()); - assert_eq!(self.outputs.len(), output_zones.len()); + pub fn commit(&self) -> PartialTx { PartialTx { - inputs: self - .inputs - .iter() - .zip(input_zones.iter()) - .map(|(i, z)| i.commit(*z)) - .collect(), - - outputs: self - .outputs - .iter() - .zip(output_zones.iter()) - .map(|(o, z)| o.commit(*z)) - .collect(), + inputs: self.inputs.iter().map(InputWitness::commit).collect(), + outputs: self.outputs.iter().map(OutputWitness::commit).collect(), balance: self.balance().commit(), } } - pub fn input_witness(&self, zone_id: ZoneId, idx: usize) -> PartialTxInputWitness { - let input_bytes = Vec::from_iter( - self.inputs - .iter() - .map(|i| i.commit(zone_id).to_bytes().to_vec()), - ); + pub fn input_witness(&self, idx: usize) -> PartialTxInputWitness { + let input_bytes = + Vec::from_iter(self.inputs.iter().map(|i| i.commit().to_bytes().to_vec())); let input_merkle_leaves = merkle::padded_leaves::(&input_bytes); let path = merkle::path(input_merkle_leaves, idx); @@ -102,12 +83,9 @@ impl PartialTxWitness { PartialTxInputWitness { input, path } } - pub fn output_witness(&self, zone_id: ZoneId, idx: usize) -> PartialTxOutputWitness { - let output_bytes = Vec::from_iter( - self.outputs - .iter() - .map(|o| o.commit(zone_id).to_bytes().to_vec()), - ); + pub fn output_witness(&self, idx: usize) -> PartialTxOutputWitness { + let output_bytes = + Vec::from_iter(self.outputs.iter().map(|o| o.commit().to_bytes().to_vec())); let output_merkle_leaves = merkle::padded_leaves::(&output_bytes); let path = merkle::path(output_merkle_leaves, idx); @@ -151,8 +129,8 @@ pub struct PartialTxInputWitness { } impl PartialTxInputWitness { - pub fn input_root(&self, zone_id: ZoneId) -> [u8; 32] { - let leaf = merkle::leaf(&self.input.commit(zone_id).to_bytes()); + pub fn input_root(&self) -> [u8; 32] { + let leaf = merkle::leaf(&self.input.commit().to_bytes()); merkle::path_root(leaf, &self.path) } } @@ -165,8 +143,8 @@ pub struct PartialTxOutputWitness { } impl PartialTxOutputWitness { - pub fn output_root(&self, zone_id: ZoneId) -> [u8; 32] { - let leaf = merkle::leaf(&self.output.commit(zone_id).to_bytes()); + pub fn output_root(&self) -> [u8; 32] { + let leaf = merkle::leaf(&self.output.commit().to_bytes()); merkle::path_root(leaf, &self.path) } } diff --git a/emmarin/cl/cl/tests/simple_transfer.rs b/emmarin/cl/cl/tests/simple_transfer.rs index 4cad8a1..fca1b91 100644 --- a/emmarin/cl/cl/tests/simple_transfer.rs +++ b/emmarin/cl/cl/tests/simple_transfer.rs @@ -1,16 +1,20 @@ -use cl::cl::{ - note::derive_unit, BalanceWitness, BundleWitness, InputWitness, NoteWitness, - NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, +use cl::{ + cl::{ + note::derive_unit, BalanceWitness, InputWitness, NoteWitness, NullifierCommitment, + NullifierSecret, OutputWitness, PartialTxWitness, + }, + zone_layer::notes::ZoneId, }; -fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness { - OutputWitness::new(note, nf_pk) +fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> OutputWitness { + OutputWitness::new(note, nf_pk, zone_id) } #[test] fn test_simple_transfer() { let nmo = derive_unit("NMO"); let mut rng = rand::thread_rng(); + let zone_id = [0; 32]; let sender_nf_sk = NullifierSecret::random(&mut rng); let sender_nf_pk = sender_nf_sk.commit(); @@ -18,12 +22,16 @@ fn test_simple_transfer() { let recipient_nf_pk = NullifierSecret::random(&mut rng).commit(); // Assume the sender has received an unspent output from somewhere - let utxo = receive_utxo(NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk); + let utxo = receive_utxo(NoteWitness::basic(10, nmo, &mut rng), sender_nf_pk, zone_id); // and wants to send 8 NMO to some recipient and return 2 NMO to itself. - let recipient_output = - OutputWitness::new(NoteWitness::basic(8, nmo, &mut rng), recipient_nf_pk); - let change_output = OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk); + let recipient_output = OutputWitness::new( + NoteWitness::basic(8, nmo, &mut rng), + recipient_nf_pk, + zone_id, + ); + let change_output = + OutputWitness::new(NoteWitness::basic(2, nmo, &mut rng), sender_nf_pk, zone_id); let ptx_witness = PartialTxWitness { inputs: vec![InputWitness::from_output(utxo, sender_nf_sk)], @@ -31,10 +39,5 @@ fn test_simple_transfer() { balance_blinding: BalanceWitness::random_blinding(&mut rng), }; - let bundle = BundleWitness { - partials: vec![ptx_witness], - }; - - assert!(bundle.balance().is_zero()) + assert!(ptx_witness.balance().is_zero()) } - diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index d7cdf68..4875ba8 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -20,7 +20,7 @@ pub struct ProvedLedgerTransition { // TODO: find a better name #[derive(Debug, Clone)] pub struct ProvedBundle { - pub bundle: ProvedBalance, + pub balance: ProvedBalance, pub ptxs: Vec, } @@ -30,7 +30,7 @@ impl ProvedBundle { } fn proofs(&self) -> Vec { - let mut proofs = vec![self.bundle.risc0_receipt.clone()]; + let mut proofs = vec![self.balance.risc0_receipt.clone()]; proofs.extend(self.ptxs.iter().map(|p| p.risc0_receipt.clone())); proofs } diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index d57bea5..0566f2a 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -2,7 +2,6 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use crate::error::{Error, Result}; use cl::cl::{merkle, PartialTxWitness}; -use cl::zone_layer::notes::ZoneId; #[derive(Debug, Clone)] pub struct ProvedPartialTx { @@ -15,15 +14,11 @@ impl ProvedPartialTx { ptx_witness: PartialTxWitness, input_cm_paths: Vec>, cm_roots: Vec<[u8; 32]>, - from: Vec, - to: Vec, ) -> Result { let ptx_private = PtxPrivate { ptx: ptx_witness, input_cm_paths, cm_roots: cm_roots.clone(), - from, - to, }; let env = risc0_zkvm::ExecutorEnv::builder() diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index ef25efa..03cf9af 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,12 +1,11 @@ use cl::{ cl::{ - balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, BundleWitness, - InputWitness, NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, - PartialTxWitness, + balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, InputWitness, + NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, }, zone_layer::{ ledger::LedgerWitness, - notes::ZoneNote, + notes::{ZoneId, ZoneNote}, tx::{UpdateBundle, ZoneUpdate}, }, }; @@ -43,8 +42,8 @@ impl User { } } -fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment) -> OutputWitness { - OutputWitness::new(note, nf_pk) +fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) -> OutputWitness { + OutputWitness::new(note, nf_pk, zone_id) } fn cross_transfer_transition( @@ -52,18 +51,23 @@ fn cross_transfer_transition( input_path: Vec, to: User, amount: u64, - zone_a: [u8; 32], - zone_b: [u8; 32], + zone_a: ZoneId, + zone_b: ZoneId, mut ledger_a: LedgerWitness, mut ledger_b: LedgerWitness, ) -> (ProvedLedgerTransition, ProvedLedgerTransition) { let mut rng = rand::thread_rng(); assert!(amount <= input.note.value); let change = input.note.value - amount; - let transfer = OutputWitness::new(NoteWitness::basic(amount, *nmo(), &mut rng), to.pk()); + let transfer = OutputWitness::new( + NoteWitness::basic(amount, *nmo(), &mut rng), + to.pk(), + zone_b, + ); let change = OutputWitness::new( NoteWitness::basic(change, *nmo(), &mut rng), input.nf_sk.commit(), + zone_a, ); // Construct the ptx consuming the input and producing the two outputs. @@ -76,24 +80,22 @@ fn cross_transfer_transition( ptx_witness.clone(), vec![input_path], vec![ledger_a.commitments.roots[0].root], - vec![zone_a], - vec![zone_b, zone_a], ) .unwrap(); - let bundle = ProvedBalance::prove(&BalancePrivate { + let balance = ProvedBalance::prove(&BalancePrivate { balances: vec![ptx_witness.balance()], }) .unwrap(); let zone_tx = ProvedBundle { ptxs: vec![proved_ptx.clone()], - bundle, + balance, }; // Prove the constraints for alices input (she uses the no-op constraint) let constraint_proof = - ConstraintProof::prove_nop(input.nullifier(&zone_a), proved_ptx.public.ptx.root()); + ConstraintProof::prove_nop(input.nullifier(), proved_ptx.public.ptx.root()); let ledger_a_transition = ProvedLedgerTransition::prove( ledger_a.clone(), @@ -106,10 +108,10 @@ fn cross_transfer_transition( let ledger_b_transition = ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![zone_tx], vec![]).unwrap(); - ledger_a.commitments.push(&change.commit_note(&zone_a).0); - ledger_a.nullifiers.push(input.nullifier(&zone_a)); + ledger_a.commitments.push(&change.commit_note().0); + ledger_a.nullifiers.push(input.nullifier()); - ledger_b.commitments.push(&transfer.commit_note(&zone_b).0); + ledger_b.commitments.push(&transfer.commit_note().0); assert_eq!(ledger_a_transition.public.ledger, ledger_a.commit()); assert_eq!(ledger_b_transition.public.ledger, ledger_b.commit()); @@ -121,6 +123,9 @@ fn cross_transfer_transition( fn zone_update_cross() { let mut rng = rand::thread_rng(); + let zone_a_id = [0; 32]; + let zone_b_id = [1; 32]; + // alice is sending 8 NMO to bob. let alice = User::random(&mut rng); @@ -130,15 +135,13 @@ fn zone_update_cross() { let utxo = receive_utxo( NoteWitness::stateless(10, *nmo(), ConstraintProof::nop_constraint(), &mut rng), alice.pk(), + zone_a_id, ); let alice_input = InputWitness::from_output(utxo, alice.sk()); - let zone_a_id = [0; 32]; - let zone_b_id = [1; 32]; - let mut mmr = MMR::new(); - let input_cm_path = mmr.push(&utxo.commit_note(&zone_a_id).0).path; + let input_cm_path = mmr.push(&utxo.commit_note().0).path; let ledger_a = LedgerWitness { commitments: mmr, diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 7f53548..93d555c 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -1,7 +1,4 @@ -use cl::{ - cl::{merkle, PartialTx, PartialTxWitness}, - zone_layer::notes::ZoneId, -}; +use cl::cl::{merkle, PartialTx, PartialTxWitness}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -15,6 +12,4 @@ pub struct PtxPrivate { pub ptx: PartialTxWitness, pub input_cm_paths: Vec>, pub cm_roots: Vec<[u8; 32]>, - pub from: Vec, - pub to: Vec, } From 7e76a06c752a26c58058f5450286c6469ecc0325 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 27 Nov 2024 18:27:19 +0100 Subject: [PATCH 09/10] leftover risc0proof update --- emmarin/cl/risc0_proofs/ptx/src/main.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index 83a8cee..7b03f68 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -8,19 +8,11 @@ fn main() { ptx, input_cm_paths, cm_roots, - from, - to, } = env::read(); assert_eq!(ptx.inputs.len(), input_cm_paths.len()); - for (((input, cm_path), zone_id), cm_root) in ptx - .inputs - .iter() - .zip(input_cm_paths) - .zip(&from) - .zip(&cm_roots) - { - let note_cm = input.note_commitment(zone_id); + for ((input, cm_path), cm_root) in ptx.inputs.iter().zip(input_cm_paths).zip(&cm_roots) { + let note_cm = input.note_commitment(); let cm_leaf = merkle::leaf(note_cm.as_bytes()); assert_eq!(*cm_root, merkle::path_root(cm_leaf, &cm_path)); } @@ -30,7 +22,7 @@ fn main() { } env::commit(&PtxPublic { - ptx: ptx.commit(&from, &to), + ptx: ptx.commit(), cm_roots, }); } From 34157841657a9f2ebb4c6a315e87624cc033ea15 Mon Sep 17 00:00:00 2001 From: Giacomo Pasini Date: Wed, 27 Nov 2024 18:27:27 +0100 Subject: [PATCH 10/10] typo --- emmarin/cl/cl/src/cl/mmr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/emmarin/cl/cl/src/cl/mmr.rs b/emmarin/cl/cl/src/cl/mmr.rs index acba97c..a5a4151 100644 --- a/emmarin/cl/cl/src/cl/mmr.rs +++ b/emmarin/cl/cl/src/cl/mmr.rs @@ -80,7 +80,7 @@ mod test { use super::*; #[test] - fn test_mrr_push() { + fn test_mmr_push() { let mut mmr = MMR::new(); let proof = mmr.push(b"hello");