Merge pull request #45 from logos-co/drusu/sparse_merkle_tree_nullifiers

PACT: Sparse Merkle Tree & Rework proof recusion
This commit is contained in:
davidrusu 2024-12-11 21:17:04 +04:00 committed by GitHub
commit edec3632f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 858 additions and 520 deletions

1
emmarin/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*profile.pb

View File

@ -1,6 +1,14 @@
[workspace]
resolver = "2"
members = [ "cl", "ledger", "ledger_proof_statements", "risc0_proofs", "ledger_validity_proof"]
members = [
"cl",
"ledger",
"ledger_proof_statements",
"risc0_proofs",
"bundle_risc0_proof",
"ptx_risc0_proof",
"ledger_validity_proof"
]
# Always optimize; building and running the risc0_proofs takes much longer without optimization.
[profile.dev]

View File

@ -0,0 +1,11 @@
[package]
name = "nomos_cl_bundle_risc0_proof"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[package.metadata.risc0]
methods = ["bundle"]

View File

@ -0,0 +1,3 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -1,5 +1,5 @@
[package]
name = "balance"
name = "bundle"
version = "0.1.0"
edition = "2021"
@ -10,6 +10,7 @@ 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_ptx_risc0_proof = { path = "../../ptx_risc0_proof" }
[patch.crates-io]

View File

@ -0,0 +1,49 @@
use cl::cl::BalanceWitness;
use cl::zone_layer::notes::ZoneId;
use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic, LedgerUpdate};
use risc0_zkvm::{guest::env, serde};
use std::collections::BTreeMap;
fn main() {
let bundle_private: BundlePrivate = env::read();
let bundle_id = bundle_private.id();
let BundlePrivate { bundle, balances } = bundle_private;
assert_eq!(bundle.len(), balances.len());
let mut zone_ledger_updates: BTreeMap<ZoneId, LedgerUpdate> = BTreeMap::new();
for (ptx_public, balance) in bundle.into_iter().zip(balances.iter()) {
assert_eq!(ptx_public.ptx.balance, balance.commit());
env::verify(
nomos_cl_ptx_risc0_proof::PTX_ID,
&serde::to_vec(&ptx_public).unwrap(),
)
.unwrap();
for (input, cm_mmr) in ptx_public.ptx.inputs.iter().zip(ptx_public.cm_mmrs) {
let zone_ledger_update = zone_ledger_updates.entry(input.zone_id).or_default();
zone_ledger_update.nullifiers.push(input.nullifier);
zone_ledger_update
.cm_roots
.extend(cm_mmr.roots.iter().map(|r| r.root));
}
for output in &ptx_public.ptx.outputs {
zone_ledger_updates
.entry(output.zone_id)
.or_default()
.commitments
.push(output.note_comm);
}
}
assert!(BalanceWitness::combine(balances, [0u8; 16]).is_zero());
env::commit(&BundlePublic {
bundle_id,
zone_ledger_updates,
});
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -12,4 +12,5 @@ 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"
lazy_static = "1.5.0"

View File

@ -1,138 +0,0 @@
use serde::{Deserialize, Serialize};
use crate::{cl::partial_tx::PartialTx, 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<PartialTx>,
}
impl Bundle {
pub fn zones(&self) -> HashSet<ZoneId> {
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())
}
}
#[cfg(test)]
mod test {
use crate::cl::{
balance::{BalanceWitness, UnitBalance},
input::InputWitness,
note::{derive_unit, NoteWitness},
nullifier::NullifierSecret,
output::OutputWitness,
partial_tx::PartialTxWitness,
};
#[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(),
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(),
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(),
zone_id,
);
let ptx_unbalanced = PartialTxWitness {
inputs: vec![nmo_10_in, eth_23_in],
outputs: vec![crv_4840_out],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
assert!(!ptx_unbalanced.balance().is_zero());
assert_eq!(
ptx_unbalanced.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
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 {
inputs: vec![crv_4840_in],
outputs: vec![nmo_10_out, eth_23_out],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
let bundle_balance =
BalanceWitness::combine([ptx_unbalanced.balance(), ptx_solved.balance()], [0; 16]);
assert!(bundle_balance.is_zero());
assert_eq!(bundle_balance.balances, vec![]);
}
}

View File

@ -43,6 +43,8 @@ pub fn root<const N: usize>(elements: [[u8; 32]; N]) -> [u8; 32] {
nodes[0]
}
pub type Path = Vec<PathNode>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum PathNode {
Left([u8; 32]),
@ -66,7 +68,7 @@ pub fn path_root(leaf: [u8; 32], path: &[PathNode]) -> [u8; 32] {
computed_hash
}
pub fn path<const N: usize>(leaves: [[u8; 32]; N], idx: usize) -> Vec<PathNode> {
pub fn path<const N: usize>(leaves: [[u8; 32]; N], idx: usize) -> Path {
assert!(N.is_power_of_two());
assert!(idx < N);

View File

@ -2,7 +2,7 @@ use crate::cl::merkle;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct MMR {
pub roots: Vec<Root>,
}
@ -18,9 +18,16 @@ pub struct MMRProof {
pub path: Vec<merkle::PathNode>,
}
impl MMRProof {
pub fn root(&self, elem: &[u8]) -> [u8; 32] {
let leaf = merkle::leaf(elem);
merkle::path_root(leaf, &self.path)
}
}
impl MMR {
pub fn new() -> Self {
Self { roots: vec![] }
Self::default()
}
pub fn push(&mut self, elem: &[u8]) -> MMRProof {
@ -52,8 +59,7 @@ impl MMR {
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);
let root = proof.root(elem);
for mmr_root in self.roots.iter() {
if mmr_root.height == (path_len + 1) as u8 {

View File

@ -1,5 +1,4 @@
pub mod balance;
pub mod bundle;
pub mod crypto;
pub mod error;
pub mod input;
@ -9,9 +8,9 @@ pub mod note;
pub mod nullifier;
pub mod output;
pub mod partial_tx;
pub mod sparse_merkle;
pub use balance::{Balance, BalanceWitness};
pub use bundle::Bundle;
pub use input::{Input, InputWitness};
pub use note::{Constraint, Nonce, NoteCommitment, NoteWitness};
pub use nullifier::{Nullifier, NullifierCommitment, NullifierSecret};

View File

@ -25,7 +25,7 @@ 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]);
pub struct Nullifier(pub [u8; 32]);
impl NullifierSecret {
pub fn random(mut rng: impl RngCore) -> Self {

View File

@ -0,0 +1,360 @@
use std::collections::BTreeSet;
use crate::cl::merkle;
use lazy_static::lazy_static;
/// absence of element is marked with all 0's
pub static ABSENT: [u8; 32] = [0u8; 32];
/// presence of element is marked with all 1's
pub static PRESENT: [u8; 32] = [255u8; 32];
lazy_static! {
// the roots of empty merkle trees of diffent heights
// i.e. all leafs are ABSENT
static ref EMPTY_ROOTS: [[u8; 32]; 257] = {
let mut roots = [ABSENT; 257];
for h in 1..257 {
roots[h] = merkle::node(roots[h - 1], roots[h - 1]);
}
roots
};
}
pub fn sparse_root(elems: &BTreeSet<[u8; 32]>) -> [u8; 32] {
sparse_root_rec(0, elems)
}
fn sparse_root_rec(prefix: u64, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] {
if elems.is_empty() {
return empty_tree_root(256 - prefix);
}
if prefix == 256 {
assert_eq!(elems.len(), 1);
return PRESENT;
}
// partition the elements
let (left, right): (BTreeSet<_>, BTreeSet<_>) =
elems.iter().partition(|e| !bit(prefix as u8, **e));
merkle::node(
sparse_root_rec(prefix + 1, &left),
sparse_root_rec(prefix + 1, &right),
)
}
pub fn sparse_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec<merkle::PathNode> {
fn sparse_path_rec(
prefix: u64,
elem: [u8; 32],
elems: &BTreeSet<[u8; 32]>,
) -> Vec<merkle::PathNode> {
if prefix == 256 {
return Vec::new();
}
// partition the elements
let (left, right): (BTreeSet<_>, BTreeSet<_>) =
elems.iter().partition(|e| !bit(prefix as u8, **e));
match bit(prefix as u8, elem) {
true => {
let left_root = sparse_root_rec(prefix + 1, &left);
let mut path = sparse_path_rec(prefix + 1, elem, &right);
path.push(merkle::PathNode::Left(left_root));
path
}
false => {
let right_root = sparse_root_rec(prefix + 1, &right);
let mut path = sparse_path_rec(prefix + 1, elem, &left);
path.push(merkle::PathNode::Right(right_root));
path
}
}
}
sparse_path_rec(0, elem, elems)
}
pub fn path_key(path: &[merkle::PathNode]) -> [u8; 32] {
assert_eq!(path.len(), 256);
let mut key = [0u8; 32];
for byte_i in (0..32).rev() {
let mut byte = 0u8;
for bit_i in 0..8 {
byte <<= 1;
match path[byte_i * 8 + bit_i] {
merkle::PathNode::Left(_) => byte += 1,
merkle::PathNode::Right(_) => byte += 0,
};
}
key[31 - byte_i] = byte;
}
key
}
fn empty_tree_root(height: u64) -> [u8; 32] {
assert!(height <= 256);
EMPTY_ROOTS[height as usize]
}
fn bit(idx: u8, elem: [u8; 32]) -> bool {
let byte = idx / 8;
let bit_in_byte = idx - byte * 8;
(elem[byte as usize] & (1 << bit_in_byte)) != 0
}
#[cfg(test)]
mod tests {
use super::*;
fn random_hash() -> [u8; 32] {
rand::random()
}
#[test]
fn test_neighbour_paths() {
let elems = BTreeSet::from_iter([[0u8; 32]]);
let path_0 = sparse_path([0u8; 32], &elems);
let mut key_1 = [0u8; 32];
key_1[31] = 128;
let path_1 = sparse_path(key_1, &elems);
assert_ne!(path_0, path_1);
}
#[test]
fn test_path_bit_agreement() {
fn path_bit(idx: u8, path: &[merkle::PathNode]) -> bool {
match path[255 - idx as usize] {
merkle::PathNode::Left(_) => true,
merkle::PathNode::Right(_) => false,
}
}
let key = random_hash();
let path = sparse_path(key, &BTreeSet::new());
for i in 0..=255 {
let b = bit(i, key);
let pb = path_bit(i, &path);
assert_eq!(b, pb, "{}!={}@{}", b, pb, i);
}
}
#[test]
fn test_path_key() {
let elems = BTreeSet::from_iter(std::iter::repeat_with(random_hash).take(10));
// membership proofs
for e in elems.iter() {
let path = sparse_path(*e, &elems);
assert_eq!(path_key(&path), *e);
}
// non-membership proofs
for _ in 0..10 {
let elem = random_hash();
let path = sparse_path(elem, &elems);
assert_eq!(path_key(&path), elem);
}
}
#[test]
fn test_sparse_path() {
let elems = BTreeSet::from_iter(std::iter::repeat_with(random_hash).take(10));
let root = sparse_root(&elems);
// membership proofs
for e in elems.iter() {
let path = sparse_path(*e, &elems);
assert_eq!(merkle::path_root(PRESENT, &path), root);
}
// non-membership proofs
for _ in 0..10 {
let elem = random_hash();
let path = sparse_path(elem, &elems);
assert!(!elems.contains(&elem));
assert_eq!(merkle::path_root(ABSENT, &path), root);
}
}
#[test]
fn test_sparse_non_membership_in_empty_tree() {
let root = sparse_root(&BTreeSet::new());
let path = sparse_path([0u8; 32], &BTreeSet::new());
assert_eq!(merkle::path_root(ABSENT, &path), root);
for (h, node) in path.into_iter().enumerate() {
match node {
merkle::PathNode::Left(hash) | merkle::PathNode::Right(hash) => {
assert_eq!(hash, empty_tree_root(h as u64))
}
}
}
}
#[test]
fn test_sparse_root_left_most_occupied() {
let root = sparse_root(&BTreeSet::from_iter([[0u8; 32]]));
// We are constructing the tree:
//
// / \
// / \ 0 subtree
// / \ 0 subtree
// 1 0
let mut expected_root = PRESENT;
for h in 0..=255 {
expected_root = merkle::node(expected_root, empty_tree_root(h))
}
assert_eq!(root, expected_root)
}
#[test]
fn test_sparse_root_right_most_occupied() {
let root = sparse_root(&BTreeSet::from_iter([[255u8; 32]]));
// We are constructing the tree:
//
// /\
// 0 /\
// 0 /\
// 0 1
let mut expected_root = PRESENT;
for h in 0..=255 {
expected_root = merkle::node(empty_tree_root(h), expected_root)
}
assert_eq!(root, expected_root)
}
#[test]
fn test_sparse_root_middle_elem() {
let elem = {
let mut x = [255u8; 32];
x[0] = 254;
x
};
assert!(!bit(0, elem));
for i in 1..=255 {
assert!(bit(i, elem));
}
let root = sparse_root(&BTreeSet::from_iter([elem]));
// We are constructing the tree:
// root
// / \
// /\ 0
// 0 /\
// 0 /\
// 0 ...
// \
// 1
let mut expected_root = PRESENT;
for h in 0..=254 {
expected_root = merkle::node(empty_tree_root(h), expected_root)
}
expected_root = merkle::node(expected_root, empty_tree_root(255));
assert_eq!(root, expected_root)
}
#[test]
fn test_sparse_root_middle_weave_elem() {
let elem = [85u8; 32];
for i in 0..=255 {
assert_eq!(bit(i, elem), i % 2 == 0);
}
let root = sparse_root(&BTreeSet::from_iter([elem]));
// We are constructing the tree:
// /\
// 0 /\
// /\0
// /\
// 0 /\
// /\0
// 0 1
let mut expected_root = PRESENT;
for h in 0..=255 {
if h % 2 == 0 {
expected_root = merkle::node(expected_root, empty_tree_root(h))
} else {
expected_root = merkle::node(empty_tree_root(h), expected_root)
}
}
assert_eq!(root, expected_root)
}
#[test]
fn test_sparse_multiple_elems() {
let root = sparse_root(&BTreeSet::from_iter([[0u8; 32], [255u8; 32]]));
// We are constructing the tree:
// root
// / \
// /\ /\
// /\0 0 /\
// 1 0 0 1
let mut left_root = PRESENT;
for h in 0..=254 {
left_root = merkle::node(left_root, empty_tree_root(h))
}
let mut right_root = PRESENT;
for h in 0..=254 {
right_root = merkle::node(empty_tree_root(h), right_root)
}
let expected_root = merkle::node(left_root, right_root);
assert_eq!(root, expected_root)
}
#[test]
fn test_bit() {
for i in 0..=255 {
assert!(!bit(i, [0u8; 32]))
}
for i in 0..=255 {
assert!(bit(i, [255u8; 32]))
}
for i in 0..=255 {
assert_eq!(bit(i, [85u8; 32]), i % 2 == 0)
}
}
#[test]
fn test_empty_tree_root() {
assert_eq!(empty_tree_root(0), ABSENT);
assert_eq!(empty_tree_root(1), merkle::node(ABSENT, ABSENT));
assert_eq!(
empty_tree_root(2),
merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)),
);
assert_eq!(
empty_tree_root(3),
merkle::node(
merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)),
merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)),
)
);
}
}

View File

@ -1,7 +1,11 @@
use crate::cl::{merkle, mmr::MMR, Nullifier};
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
const MAX_NULL: usize = 256;
use crate::cl::{
merkle,
mmr::{MMRProof, MMR},
sparse_merkle, NoteCommitment, Nullifier,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct Ledger {
@ -12,23 +16,65 @@ pub struct Ledger {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LedgerWitness {
pub commitments: MMR,
pub nullifiers: Vec<Nullifier>,
pub nf_root: [u8; 32],
}
impl LedgerWitness {
pub fn commit(&self) -> Ledger {
Ledger {
cm_root: self.commitments.commit(),
nf_root: self.nf_root,
}
}
pub fn valid_cm_root(&self, root: [u8; 32]) -> bool {
self.commitments.roots.iter().any(|r| r.root == root)
}
pub fn add_commitment(&mut self, cm: &NoteCommitment) {
self.commitments.push(&cm.0);
}
pub fn assert_nf_update(&mut self, nf: &Nullifier, path: &[merkle::PathNode]) {
// verify that the path corresponds to the nullifier
assert_eq!(sparse_merkle::path_key(path), nf.0);
// verify that the nullifier was not already present
assert_eq!(merkle::path_root(sparse_merkle::ABSENT, path), self.nf_root);
// update the nullifer root with the nullifier inserted into the tree
self.nf_root = merkle::path_root(sparse_merkle::PRESENT, path);
}
}
#[derive(Debug, Default, Clone)]
pub struct LedgerState {
pub commitments: MMR,
pub nullifiers: BTreeSet<[u8; 32]>,
}
impl LedgerState {
pub fn to_witness(&self) -> LedgerWitness {
LedgerWitness {
commitments: self.commitments.clone(),
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::<Vec<_>>();
merkle::root(merkle::padded_leaves::<MAX_NULL>(&bytes))
sparse_merkle::sparse_root(&self.nullifiers)
}
pub fn add_commitment(&mut self, cm: &NoteCommitment) -> MMRProof {
self.commitments.push(&cm.0)
}
pub fn add_nullifier(&mut self, nf: Nullifier) -> Vec<merkle::PathNode> {
let path = sparse_merkle::sparse_path(nf.0, &self.nullifiers);
assert!(!self.nullifiers.contains(&nf.0));
self.nullifiers.insert(nf.0);
path
}
}

View File

@ -7,6 +7,8 @@ edition = "2021"
cl = { path = "../cl" }
ledger_proof_statements = { path = "../ledger_proof_statements" }
nomos_cl_risc0_proofs = { path = "../risc0_proofs" }
nomos_cl_bundle_risc0_proof = { path = "../bundle_risc0_proof" }
nomos_cl_ptx_risc0_proof = { path = "../ptx_risc0_proof" }
ledger_validity_proof = { path = "../ledger_validity_proof" }
risc0-zkvm = { version = "1.0", features = ["prove", "metal"] }
risc0-groth16 = { version = "1.0" }

View File

@ -1,57 +0,0 @@
use crate::error::{Error, Result};
use ledger_proof_statements::balance::{BalancePrivate, BalancePublic};
#[derive(Debug, Clone)]
pub struct ProvedBalance {
pub bundle: BalancePublic,
pub risc0_receipt: risc0_zkvm::Receipt,
}
impl ProvedBalance {
pub fn prove(balance_witness: &BalancePrivate) -> Result<Self> {
//show that the sum of ptx balances is 0
let env = risc0_zkvm::ExecutorEnv::builder()
.write(&balance_witness)
.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::BALANCE_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<ledger_proof_statements::balance::BalancePublic> {
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::BALANCE_ID)
.is_ok()
}
}

View File

@ -0,0 +1,52 @@
use ledger_proof_statements::bundle::{BundlePrivate, BundlePublic};
use crate::partial_tx::ProvedPartialTx;
#[derive(Debug, Clone)]
pub struct ProvedBundle {
pub risc0_receipt: risc0_zkvm::Receipt,
}
impl ProvedBundle {
pub fn prove(bundle: &BundlePrivate, partials: Vec<ProvedPartialTx>) -> Self {
//show that all ptx's are individually valid, and balance to 0
let mut env = risc0_zkvm::ExecutorEnv::builder();
for proved_ptx in partials {
env.add_assumption(proved_ptx.risc0_receipt);
}
let env = env.write(&bundle).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_bundle_risc0_proof::BUNDLE_ELF, &opts)
.unwrap();
println!(
"STARK 'bundle' prover time: {:.2?}, total_cycles: {}",
start_t.elapsed(),
prove_info.stats.total_cycles
);
let receipt = prove_info.receipt;
Self {
risc0_receipt: receipt,
}
}
pub fn public(&self) -> BundlePublic {
self.risc0_receipt.journal.decode().unwrap()
}
pub fn verify(&self) -> bool {
self.risc0_receipt
.verify(nomos_cl_bundle_risc0_proof::BUNDLE_ID)
.is_ok()
}
}

View File

@ -1,64 +1,60 @@
use ledger_proof_statements::{
ledger::{LedgerProofPrivate, LedgerProofPublic},
ptx::PtxPublic,
};
use std::collections::BTreeMap;
use crate::{
balance::ProvedBalance,
constraint::ConstraintProof,
error::{Error, Result},
partial_tx::ProvedPartialTx,
};
use cl::zone_layer::{ledger::LedgerWitness, notes::ZoneId};
use ledger_proof_statements::ledger::{LedgerBundleWitness, LedgerProofPrivate, LedgerProofPublic};
use crate::bundle::ProvedBundle;
use cl::zone_layer::{ledger::LedgerState, notes::ZoneId};
#[derive(Debug, Clone)]
pub struct ProvedLedgerTransition {
pub public: LedgerProofPublic,
pub risc0_receipt: risc0_zkvm::Receipt,
}
// TODO: find a better name
#[derive(Debug, Clone)]
pub struct ProvedBundle {
pub balance: ProvedBalance,
pub ptxs: Vec<ProvedPartialTx>,
}
impl ProvedBundle {
fn to_public(&self) -> Vec<PtxPublic> {
self.ptxs.iter().map(|p| p.public.clone()).collect()
}
fn proofs(&self) -> Vec<risc0_zkvm::Receipt> {
let mut proofs = vec![self.balance.risc0_receipt.clone()];
proofs.extend(self.ptxs.iter().map(|p| p.risc0_receipt.clone()));
proofs
}
}
impl ProvedLedgerTransition {
pub fn prove(
ledger: LedgerWitness,
zone_id: ZoneId,
bundles: Vec<ProvedBundle>,
constraints: Vec<ConstraintProof>,
) -> Result<Self> {
let witness = LedgerProofPrivate {
bundles: bundles.iter().map(|p| p.to_public()).collect(),
ledger,
pub fn prove(mut ledger: LedgerState, zone_id: ZoneId, bundles: Vec<ProvedBundle>) -> Self {
let mut witness = LedgerProofPrivate {
bundles: Vec::new(),
ledger: ledger.to_witness(),
id: zone_id,
};
let mut env = risc0_zkvm::ExecutorEnv::builder();
for bundle in bundles {
for proof in bundle.proofs() {
env.add_assumption(proof);
// prepare the sparse merkle tree nullifier proofs
for proved_bundle in &bundles {
env.add_assumption(proved_bundle.risc0_receipt.clone());
let bundle = proved_bundle.public();
let zone_ledger_update = bundle
.zone_ledger_updates
.get(&zone_id)
.expect("why are we proving this bundle for this zone if it's not involved?");
let cm_root_proofs =
BTreeMap::from_iter(zone_ledger_update.cm_roots.iter().map(|root| {
// We make the simplifying assumption that bundle proofs
// are done w.r.t. the latest MMR (hence, empty merkle proofs)
//
// We can remove this assumption by tracking old MMR roots in the LedgerState
(*root, vec![])
}));
let mut nf_proofs = Vec::new();
for nf in &zone_ledger_update.nullifiers {
let nf_proof = ledger.add_nullifier(*nf);
nf_proofs.push(nf_proof);
}
let ledger_bundle = LedgerBundleWitness {
bundle,
cm_root_proofs,
nf_proofs,
};
witness.bundles.push(ledger_bundle)
}
for covenant in constraints {
env.add_assumption(covenant.risc0_receipt);
}
let env = env.write(&witness).unwrap().build().unwrap();
// Obtain the default prover.
@ -71,10 +67,7 @@ impl ProvedLedgerTransition {
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
})?;
.unwrap();
println!(
"STARK 'ledger' prover time: {:.2?}, total_cycles: {}",
@ -82,14 +75,16 @@ impl ProvedLedgerTransition {
prove_info.stats.total_cycles
);
Ok(Self {
public: prove_info
.receipt
.journal
.decode::<LedgerProofPublic>()
.unwrap(),
Self {
risc0_receipt: prove_info.receipt,
})
}
}
pub fn public(&self) -> LedgerProofPublic {
self.risc0_receipt
.journal
.decode::<LedgerProofPublic>()
.unwrap()
}
pub fn verify(&self) -> bool {

View File

@ -1,4 +1,4 @@
pub mod balance;
pub mod bundle;
pub mod constraint;
pub mod error;
pub mod ledger;

View File

@ -1,31 +1,36 @@
use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic};
use crate::error::{Error, Result};
use cl::cl::{merkle, PartialTxWitness};
use crate::{
error::{Error, Result},
ConstraintProof,
};
use cl::cl::{
mmr::{MMRProof, MMR},
PartialTxWitness,
};
#[derive(Debug, Clone)]
pub struct ProvedPartialTx {
pub public: PtxPublic,
pub risc0_receipt: risc0_zkvm::Receipt,
}
impl ProvedPartialTx {
pub fn prove(
ptx_witness: PartialTxWitness,
input_cm_paths: Vec<Vec<merkle::PathNode>>,
cm_roots: Vec<[u8; 32]>,
input_cm_proofs: Vec<(MMR, MMRProof)>,
covenant_proofs: Vec<ConstraintProof>,
) -> Result<ProvedPartialTx> {
let ptx_private = PtxPrivate {
ptx: ptx_witness,
input_cm_paths,
cm_roots: cm_roots.clone(),
input_cm_proofs,
};
let env = risc0_zkvm::ExecutorEnv::builder()
.write(&ptx_private)
.unwrap()
.build()
.unwrap();
let mut env = risc0_zkvm::ExecutorEnv::builder();
for covenant_proof in covenant_proofs {
env.add_assumption(covenant_proof.risc0_receipt);
}
let env = env.write(&ptx_private).unwrap().build().unwrap();
// Obtain the default prover.
let prover = risc0_zkvm::default_prover();
@ -36,7 +41,7 @@ impl ProvedPartialTx {
// 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)
.prove_with_opts(env, nomos_cl_ptx_risc0_proof::PTX_ELF, &opts)
.map_err(|_| Error::Risc0ProofFailed)?;
println!(
@ -46,14 +51,17 @@ impl ProvedPartialTx {
);
Ok(Self {
public: prove_info.receipt.journal.decode()?,
risc0_receipt: prove_info.receipt,
})
}
pub fn public(&self) -> PtxPublic {
self.risc0_receipt.journal.decode().unwrap()
}
pub fn verify(&self) -> bool {
self.risc0_receipt
.verify(nomos_cl_risc0_proofs::PTX_ID)
.verify(nomos_cl_ptx_risc0_proof::PTX_ID)
.is_ok()
}
}

View File

@ -18,16 +18,15 @@ impl ProvedUpdateBundle {
return false;
}
for bundle in &proof.public.cross_bundles {
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);
.or_insert_with(HashSet::new)
.insert(proof.public().id);
}
}
println!("{:?} | {:?}", expected_zones, actual_zones);
for (bundle, expected) in expected_zones.iter() {
if let Some(actual) = actual_zones.get(bundle) {
if actual != expected {
@ -49,8 +48,8 @@ impl ProvedUpdateBundle {
return false;
}
if ledger_proof.public.old_ledger != update.old.ledger
|| ledger_proof.public.ledger != update.new.ledger
if ledger_proof.public().old_ledger != update.old.ledger
|| ledger_proof.public().ledger != update.new.ledger
{
return false;
}

View File

@ -1,29 +1,28 @@
use cl::{
cl::{
balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, InputWitness,
NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness,
balance::Unit,
mmr::{MMRProof, MMR},
note::derive_unit,
BalanceWitness, InputWitness, NoteWitness, NullifierCommitment, NullifierSecret,
OutputWitness, PartialTxWitness,
},
zone_layer::{
ledger::LedgerWitness,
ledger::LedgerState,
notes::{ZoneId, ZoneNote},
tx::{UpdateBundle, ZoneUpdate},
},
};
use ledger::{
balance::ProvedBalance,
constraint::ConstraintProof,
ledger::{ProvedBundle, ProvedLedgerTransition},
partial_tx::ProvedPartialTx,
stf::StfProof,
zone_update::ProvedUpdateBundle,
bundle::ProvedBundle, constraint::ConstraintProof, ledger::ProvedLedgerTransition,
partial_tx::ProvedPartialTx, stf::StfProof, zone_update::ProvedUpdateBundle,
};
use ledger_proof_statements::{balance::BalancePrivate, stf::StfPublic};
use ledger_proof_statements::{bundle::BundlePrivate, stf::StfPublic};
use rand_core::CryptoRngCore;
use std::sync::OnceLock;
fn nmo() -> &'static Unit {
fn nmo() -> Unit {
static NMO: OnceLock<Unit> = OnceLock::new();
NMO.get_or_init(|| derive_unit("NMO"))
*NMO.get_or_init(|| derive_unit("NMO"))
}
struct User(NullifierSecret);
@ -48,24 +47,22 @@ fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId)
fn cross_transfer_transition(
input: InputWitness,
input_path: Vec<merkle::PathNode>,
input_proof: (MMR, MMRProof),
to: User,
amount: u64,
zone_a: ZoneId,
zone_b: ZoneId,
mut ledger_a: LedgerWitness,
mut ledger_b: LedgerWitness,
mut ledger_a: LedgerState,
mut ledger_b: LedgerState,
) -> (ProvedLedgerTransition, ProvedLedgerTransition) {
let mut rng = rand::thread_rng();
assert!(amount <= input.note.value);
let mut rng = rand::thread_rng();
let change = input.note.value - amount;
let transfer = OutputWitness::new(
NoteWitness::basic(amount, *nmo(), &mut rng),
to.pk(),
zone_b,
);
let transfer = OutputWitness::new(NoteWitness::basic(amount, nmo(), &mut rng), to.pk(), zone_b);
let change = OutputWitness::new(
NoteWitness::basic(change, *nmo(), &mut rng),
NoteWitness::basic(change, nmo(), &mut rng),
input.nf_sk.commit(),
zone_a,
);
@ -76,45 +73,46 @@ fn cross_transfer_transition(
outputs: vec![transfer, change],
balance_blinding: BalanceWitness::random_blinding(&mut rng),
};
let proved_ptx = ProvedPartialTx::prove(
ptx_witness.clone(),
vec![input_path],
vec![ledger_a.commitments.roots[0].root],
)
.unwrap();
let balance = ProvedBalance::prove(&BalancePrivate {
balances: vec![ptx_witness.balance()],
})
.unwrap();
let zone_tx = ProvedBundle {
ptxs: vec![proved_ptx.clone()],
balance,
};
// Prove the constraints for alices input (she uses the no-op constraint)
let constraint_proof =
ConstraintProof::prove_nop(input.nullifier(), proved_ptx.public.ptx.root());
ConstraintProof::prove_nop(input.nullifier(), ptx_witness.commit().root());
let ledger_a_transition = ProvedLedgerTransition::prove(
ledger_a.clone(),
zone_a,
vec![zone_tx.clone()],
vec![constraint_proof],
let proved_ptx = ProvedPartialTx::prove(
ptx_witness.clone(),
vec![input_proof],
vec![constraint_proof.clone()],
)
.unwrap();
let ledger_b_transition =
ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![zone_tx], vec![]).unwrap();
let bundle = ProvedBundle::prove(
&BundlePrivate {
bundle: vec![proved_ptx.public()],
balances: vec![ptx_witness.balance()],
},
vec![proved_ptx],
);
ledger_a.commitments.push(&change.commit_note().0);
ledger_a.nullifiers.push(input.nullifier());
println!("proving ledger A transition");
let ledger_a_transition =
ProvedLedgerTransition::prove(ledger_a.clone(), zone_a, vec![bundle.clone()]);
ledger_b.commitments.push(&transfer.commit_note().0);
println!("proving ledger B transition");
let ledger_b_transition = ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![bundle]);
assert_eq!(ledger_a_transition.public.ledger, ledger_a.commit());
assert_eq!(ledger_b_transition.public.ledger, ledger_b.commit());
ledger_a.add_commitment(&change.commit_note());
ledger_a.add_nullifier(input.nullifier());
ledger_b.add_commitment(&transfer.commit_note());
assert_eq!(
ledger_a_transition.public().ledger,
ledger_a.to_witness().commit()
);
assert_eq!(
ledger_b_transition.public().ledger,
ledger_b.to_witness().commit()
);
(ledger_a_transition, ledger_b_transition)
}
@ -133,42 +131,35 @@ fn zone_update_cross() {
// Alice has an unspent note worth 10 NMO
let utxo = receive_utxo(
NoteWitness::stateless(10, *nmo(), ConstraintProof::nop_constraint(), &mut rng),
NoteWitness::stateless(10, nmo(), ConstraintProof::nop_constraint(), &mut rng),
alice.pk(),
zone_a_id,
);
let alice_input = InputWitness::from_output(utxo, alice.sk());
let mut mmr = MMR::new();
let input_cm_path = mmr.push(&utxo.commit_note().0).path;
let mut ledger_a = LedgerState::default();
let alice_cm_path = ledger_a.add_commitment(&utxo.commit_note());
let alice_cm_proof = (ledger_a.commitments.clone(), alice_cm_path);
let ledger_a = LedgerWitness {
commitments: mmr,
nullifiers: vec![],
};
let ledger_b = LedgerWitness {
commitments: MMR::new(),
nullifiers: vec![],
};
let ledger_b = LedgerState::default();
let zone_a_old = ZoneNote {
id: zone_a_id,
state: [0; 32],
ledger: ledger_a.commit(),
ledger: ledger_a.to_witness().commit(),
stf: [0; 32],
};
let zone_b_old = ZoneNote {
id: zone_b_id,
state: [0; 32],
ledger: ledger_b.commit(),
ledger: ledger_b.to_witness().commit(),
stf: [0; 32],
};
let (ledger_a_transition, ledger_b_transition) = cross_transfer_transition(
alice_input,
input_cm_path,
alice_cm_proof,
bob,
8,
zone_a_id,
@ -178,12 +169,12 @@ fn zone_update_cross() {
);
let zone_a_new = ZoneNote {
ledger: ledger_a_transition.public.ledger,
ledger: ledger_a_transition.public().ledger,
..zone_a_old
};
let zone_b_new = ZoneNote {
ledger: ledger_b_transition.public.ledger,
ledger: ledger_b_transition.public().ledger,
..zone_b_old
};

View File

@ -6,3 +6,4 @@ edition = "2021"
[dependencies]
cl = { path = "../cl" }
serde = { version = "1.0", features = ["derive"] }
sha2 = "0.10"

View File

@ -1,12 +0,0 @@
use cl::cl::{Balance, BalanceWitness};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BalancePublic {
pub balances: Vec<Balance>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BalancePrivate {
pub balances: Vec<BalanceWitness>,
}

View File

@ -0,0 +1,48 @@
use std::collections::{BTreeMap, BTreeSet};
use cl::{
cl::{BalanceWitness, NoteCommitment, Nullifier},
zone_layer::notes::ZoneId,
};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::ptx::PtxPublic;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BundleId(pub [u8; 32]);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundlePublic {
pub bundle_id: BundleId,
pub zone_ledger_updates: BTreeMap<ZoneId, LedgerUpdate>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct LedgerUpdate {
// inputs in this bundle used the following roots in their cm membership proof.
pub cm_roots: BTreeSet<[u8; 32]>,
// these are the nullifiers of inputs used in this bundle.
pub nullifiers: Vec<Nullifier>,
// these are commitments to created notes in this bundle
pub commitments: Vec<NoteCommitment>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BundlePrivate {
pub bundle: Vec<PtxPublic>,
pub balances: Vec<BalanceWitness>,
}
impl BundlePrivate {
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.bundle {
hasher.update(ptx.ptx.root().0);
}
BundleId(hasher.finalize().into())
}
}

View File

@ -1,5 +1,8 @@
use crate::ptx::PtxPublic;
use cl::cl::{bundle::BundleId, Output};
use std::collections::BTreeMap;
use crate::bundle::BundleId;
use crate::bundle::BundlePublic;
use cl::cl::{merkle, NoteCommitment};
use cl::zone_layer::{
ledger::{Ledger, LedgerWitness},
notes::ZoneId,
@ -12,14 +15,21 @@ pub struct LedgerProofPublic {
pub ledger: Ledger,
pub id: ZoneId,
pub cross_bundles: Vec<CrossZoneBundle>,
pub outputs: Vec<Output>,
pub outputs: Vec<NoteCommitment>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LedgerProofPrivate {
pub ledger: LedgerWitness,
pub id: ZoneId,
pub bundles: Vec<Vec<PtxPublic>>,
pub bundles: Vec<LedgerBundleWitness>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LedgerBundleWitness {
pub bundle: BundlePublic,
pub cm_root_proofs: BTreeMap<[u8; 32], merkle::Path>,
pub nf_proofs: Vec<merkle::Path>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]

View File

@ -1,4 +1,4 @@
pub mod balance;
pub mod bundle;
pub mod constraint;
pub mod ledger;
pub mod ptx;

View File

@ -1,15 +1,17 @@
use cl::cl::{merkle, PartialTx, PartialTxWitness};
use cl::cl::{
mmr::{MMRProof, MMR},
PartialTx, PartialTxWitness,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PtxPublic {
pub ptx: PartialTx,
pub cm_roots: Vec<[u8; 32]>,
pub cm_mmrs: Vec<MMR>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PtxPrivate {
pub ptx: PartialTxWitness,
pub input_cm_paths: Vec<Vec<merkle::PathNode>>,
pub cm_roots: Vec<[u8; 32]>,
pub input_cm_proofs: Vec<(MMR, MMRProof)>,
}

View File

@ -10,7 +10,7 @@ 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" }
nomos_cl_bundle_risc0_proof = { path = "../../bundle_risc0_proof" }
[patch.crates-io]
# add RISC Zero accelerator support for all downstream usages of the following crates.

View File

@ -1,12 +1,6 @@
use cl::{
cl::{Bundle, Output},
zone_layer::{ledger::LedgerWitness, notes::ZoneId},
};
use cl::cl::merkle;
use ledger_proof_statements::{
balance::BalancePublic,
constraint::ConstraintPublic,
ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic},
ptx::PtxPublic,
ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic, LedgerBundleWitness},
};
use risc0_zkvm::{guest::env, serde};
@ -17,93 +11,42 @@ fn main() {
bundles,
} = env::read();
let old_ledger = ledger.commit();
let old_ledger = ledger.clone();
let mut cross_bundles = vec![];
let mut outputs = vec![];
let roots = ledger
.commitments
.roots
.iter()
.map(|r| r.root)
.collect::<Vec<_>>();
for LedgerBundleWitness { bundle, cm_root_proofs, nf_proofs } in bundles {
env::verify(nomos_cl_bundle_risc0_proof::BUNDLE_ID, &serde::to_vec(&bundle).unwrap()).unwrap();
for bundle in bundles {
let balance_public = BalancePublic {
balances: bundle.iter().map(|ptx| ptx.ptx.balance).collect::<Vec<_>>(),
};
// verify bundle is balanced
env::verify(
nomos_cl_risc0_proofs::BALANCE_ID,
&serde::to_vec(&balance_public).unwrap(),
)
.unwrap();
if let Some(ledger_update) = bundle.zone_ledger_updates.get(&id) {
for past_cm_root in &ledger_update.cm_roots {
let past_cm_root_proof = cm_root_proofs.get(past_cm_root).expect("missing cm root proof");
let expected_current_cm_root = merkle::path_root(*past_cm_root, past_cm_root_proof);
assert!(old_ledger.valid_cm_root(expected_current_cm_root))
}
for ptx in &bundle {
let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, &roots);
ledger = new_ledger;
outputs.extend(ptx_outputs);
assert_eq!(ledger_update.nullifiers.len(), nf_proofs.len());
for (nf, nf_proof) in ledger_update.nullifiers.iter().zip(nf_proofs) {
ledger.assert_nf_update(nf, &nf_proof);
}
for cm in &ledger_update.commitments {
ledger.add_commitment(cm);
outputs.push(*cm)
}
}
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(),
});
}
cross_bundles.push(CrossZoneBundle {
id: bundle.bundle_id,
zones: bundle.zone_ledger_updates.into_keys().collect(),
});
}
env::commit(&LedgerProofPublic {
old_ledger,
old_ledger: old_ledger.commit(),
ledger: ledger.commit(),
id,
cross_bundles,
outputs,
});
}
fn process_ptx(
mut ledger: LedgerWitness,
ptx: &PtxPublic,
zone_id: ZoneId,
roots: &[[u8; 32]],
) -> (LedgerWitness, Vec<Output>) {
// 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 cm_roots = &ptx.cm_roots;
let ptx = &ptx.ptx;
let mut outputs = vec![];
for (input, input_cm_root) in ptx.inputs.iter().zip(cm_roots) {
if input.zone_id == zone_id {
assert!(roots.contains(input_cm_root));
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 {
if output.zone_id == zone_id {
ledger.commitments.push(&output.note_comm.0);
outputs.push(*output);
}
}
(ledger, outputs)
}

View File

@ -0,0 +1,11 @@
[package]
name = "nomos_cl_ptx_risc0_proof"
version = "0.1.0"
edition = "2021"
[build-dependencies]
risc0-build = { version = "1.0" }
[package.metadata.risc0]
methods = ["ptx"]

View File

@ -0,0 +1,3 @@
fn main() {
risc0_build::embed_methods();
}

View File

@ -0,0 +1,43 @@
/// Input Proof
use ledger_proof_statements::{
constraint::ConstraintPublic,
ptx::{PtxPrivate, PtxPublic},
};
use risc0_zkvm::{guest::env, serde};
fn main() {
let PtxPrivate {
ptx,
input_cm_proofs,
} = env::read();
let ptx_commit = ptx.commit();
let ptx_root = ptx_commit.root();
assert_eq!(ptx.inputs.len(), input_cm_proofs.len());
let mut cm_mmrs = Vec::new();
for (input, (mmr, mmr_proof)) in ptx.inputs.iter().zip(input_cm_proofs) {
let note_cm = input.note_commitment();
assert!(mmr.verify_proof(&note_cm.0, &mmr_proof));
cm_mmrs.push(mmr);
env::verify(
input.note.constraint.0,
&serde::to_vec(&ConstraintPublic {
ptx_root,
nf: input.nullifier(),
})
.unwrap(),
)
.unwrap();
}
for output in ptx.outputs.iter() {
assert!(output.note.value > 0);
}
env::commit(&PtxPublic {
ptx: ptx_commit,
cm_mmrs,
});
}

View File

@ -0,0 +1 @@
include!(concat!(env!("OUT_DIR"), "/methods.rs"));

View File

@ -7,5 +7,5 @@ edition = "2021"
risc0-build = { version = "1.0" }
[package.metadata.risc0]
methods = ["balance", "constraint_nop", "ptx", "stf_nop"]
methods = ["constraint_nop", "stf_nop"]

View File

@ -1,24 +0,0 @@
use cl::cl::BalanceWitness;
/// 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 balance_private: ledger_proof_statements::balance::BalancePrivate = env::read();
let balance_public = ledger_proof_statements::balance::BalancePublic {
balances: Vec::from_iter(balance_private.balances.iter().map(|b| b.commit())),
};
assert!(BalanceWitness::combine(balance_private.balances, [0u8; 16]).is_zero());
env::commit(&balance_public);
}

View File

@ -1,28 +0,0 @@
/// Input Proof
use cl::cl::merkle;
use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic};
use risc0_zkvm::guest::env;
fn main() {
let PtxPrivate {
ptx,
input_cm_paths,
cm_roots,
} = env::read();
assert_eq!(ptx.inputs.len(), input_cm_paths.len());
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));
}
for output in ptx.outputs.iter() {
assert!(output.note.value > 0);
}
env::commit(&PtxPublic {
ptx: ptx.commit(),
cm_roots,
});
}