From 51233f62fbf1656059dd6c620b00c3451517b6fa Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 5 Dec 2024 18:46:36 +0400 Subject: [PATCH 01/13] emmarin: sparse merkle tree impl --- emmarin/cl/cl/src/zone_layer/mod.rs | 1 + .../cl/src/zone_layer/sparse_merkle_tree.rs | 279 ++++++++++++++++++ 2 files changed, 280 insertions(+) create mode 100644 emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs diff --git a/emmarin/cl/cl/src/zone_layer/mod.rs b/emmarin/cl/cl/src/zone_layer/mod.rs index 20710b3..06da01c 100644 --- a/emmarin/cl/cl/src/zone_layer/mod.rs +++ b/emmarin/cl/cl/src/zone_layer/mod.rs @@ -1,3 +1,4 @@ pub mod ledger; pub mod notes; +pub mod sparse_merkle_tree; pub mod tx; diff --git a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs new file mode 100644 index 0000000..1fef49a --- /dev/null +++ b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs @@ -0,0 +1,279 @@ +use std::collections::BTreeSet; + +use crate::cl::merkle; + +fn smt_root(elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { + smt_root_rec(0, elems) +} + +fn smt_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { + if elems.is_empty() { + return empty_tree_root(255 - prefix); + } + if prefix == 255 { + assert_eq!(elems.len(), 1); + return merkle::leaf(&[255u8; 32]); // presence of element is marked with all 1's + } + // partition the elements + let (left, right): (BTreeSet<_>, BTreeSet<_>) = elems.iter().partition(|e| !bit(prefix, **e)); + + merkle::node( + smt_root_rec(prefix + 1, &left), + smt_root_rec(prefix + 1, &right), + ) +} + +fn smt_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec { + fn stm_path_rec( + prefix: u8, + elem: [u8; 32], + elems: &BTreeSet<[u8; 32]>, + ) -> Vec { + if prefix == 255 { + return Vec::new(); + } + // partition the elements + let (left, right): (BTreeSet<_>, BTreeSet<_>) = + elems.iter().partition(|e| !bit(prefix, **e)); + + match bit(prefix, elem) { + true => { + let left_root = smt_root_rec(prefix + 1, &left); + let mut path = stm_path_rec(prefix + 1, elem, &right); + + path.push(merkle::PathNode::Left(left_root)); + path + } + false => { + let right_root = smt_root_rec(prefix + 1, &right); + let mut path = stm_path_rec(prefix + 1, elem, &left); + + path.push(merkle::PathNode::Right(right_root)); + path + } + } + } + + stm_path_rec(0, elem, elems) +} + +fn empty_tree_root(height: u8) -> [u8; 32] { + let mut root = merkle::leaf(&[0u8; 32]); + for _ in 0..height { + root = merkle::node(root, root); + } + root +} + +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)) >> bit_in_byte == 1 +} + +#[cfg(test)] +mod tests { + use monotree::utils::random_hash; + + use super::*; + + #[test] + fn test_smt_path() { + let elems = BTreeSet::from_iter(std::iter::repeat_with(random_hash).take(10)); + + let one = merkle::leaf(&[255u8; 32]); + let zero = merkle::leaf(&[0u8; 32]); + + let root = smt_root(&elems); + + // membership proofs + for e in elems.iter() { + let path = smt_path(*e, &elems); + assert_eq!(path.len(), 255); + assert_eq!(merkle::path_root(one, &path), root); + } + + // non-membership proofs + for _ in 0..10 { + let elem = random_hash(); + let path = smt_path(elem, &elems); + assert!(!elems.contains(&elem)); + assert_eq!(path.len(), 255); + assert_eq!(merkle::path_root(zero, &path), root); + } + } + + #[test] + fn test_smt_non_membership_in_empty_tree() { + let root = smt_root(&BTreeSet::new()); + + let path = smt_path([0u8; 32], &BTreeSet::new()); + + let zero = merkle::leaf(&[0u8; 32]); + assert_eq!(merkle::path_root(zero, &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 u8)) + } + } + } + } + + #[test] + fn test_smt_root_left_most_occupied() { + let root = smt_root(&BTreeSet::from_iter([[0u8; 32]])); + + // We are constructing the tree: + // + // / \ + // / \ 0 subtree + // / \ 0 subtree + // 1 0 + let mut expected_root = merkle::leaf(&[255u8; 32]); + for h in 0..=254 { + expected_root = merkle::node(expected_root, empty_tree_root(h)) + } + + assert_eq!(root, expected_root) + } + + #[test] + fn test_smt_root_right_most_occupied() { + let root = smt_root(&BTreeSet::from_iter([[255u8; 32]])); + + // We are constructing the tree: + // + // /\ + // 0 /\ + // 0 /\ + // 0 1 + let mut expected_root = merkle::leaf(&[255u8; 32]); + for h in 0..=254 { + expected_root = merkle::node(empty_tree_root(h), expected_root) + } + + assert_eq!(root, expected_root) + } + + #[test] + fn test_smt_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 = smt_root(&BTreeSet::from_iter([elem])); + + // We are constructing the tree: + // root + // / \ + // /\ 0 + // 0 /\ + // 0 /\ + // 0 ... + // \ + // 1 + let mut expected_root = merkle::leaf(&[255u8; 32]); + for h in 0..=253 { + expected_root = merkle::node(empty_tree_root(h), expected_root) + } + expected_root = merkle::node(expected_root, empty_tree_root(254)); + + assert_eq!(root, expected_root) + } + + #[test] + fn test_smt_root_middle_weave_elem() { + let elem = [85u8; 32]; + for i in 0..=255 { + assert_eq!(bit(i, elem), i % 2 == 0); + } + + let root = smt_root(&BTreeSet::from_iter([elem])); + + // We are constructing the tree: + // /\ + // 0 /\ + // /\0 + // /\ + // 0 /\ + // /\0 + // 0 1 + + let mut expected_root = merkle::leaf(&[255u8; 32]); + for h in 0..=254 { + if h % 2 == 0 { + expected_root = merkle::node(empty_tree_root(h), expected_root) + } else { + expected_root = merkle::node(expected_root, empty_tree_root(h)) + } + } + assert_eq!(root, expected_root) + } + + #[test] + fn test_smt_multiple_elems() { + let root = smt_root(&BTreeSet::from_iter([[0u8; 32], [255u8; 32]])); + + // We are constructing the tree: + // root + // / \ + // /\ /\ + // /\0 0 /\ + // 1 0 0 1 + + let mut left_root = merkle::leaf(&[255u8; 32]); + for h in 0..=253 { + left_root = merkle::node(left_root, empty_tree_root(h)) + } + + let mut right_root = merkle::leaf(&[255u8; 32]); + for h in 0..=253 { + 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_eq!(bit(i, [0u8; 32]), false, "{}", i) + } + + for i in 0..=255 { + assert_eq!(bit(i, [255u8; 32]), true, "{}", i) + } + + for i in 0..=255 { + assert_eq!(bit(i, [85u8; 32]), i % 2 == 0, "{}", i) + } + } + #[test] + fn test_empty_tree_root() { + let zero = merkle::leaf(&[0u8; 32]); + assert_eq!(empty_tree_root(0), zero); + + assert_eq!(empty_tree_root(1), merkle::node(zero, zero)); + assert_eq!( + empty_tree_root(2), + merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), + ); + assert_eq!( + empty_tree_root(3), + merkle::node( + merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), + merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), + ) + ); + } +} From 11556f07e88b6e128f5414f01c1f2acf949ea6c9 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 5 Dec 2024 19:32:56 +0400 Subject: [PATCH 02/13] rename smt_ to sparse_ --- .../cl/src/zone_layer/sparse_merkle_tree.rs | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs index 1fef49a..d43ebd0 100644 --- a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs +++ b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs @@ -2,11 +2,11 @@ use std::collections::BTreeSet; use crate::cl::merkle; -fn smt_root(elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { - smt_root_rec(0, elems) +pub fn sparse_root(elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { + sparse_root_rec(0, elems) } -fn smt_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { +fn sparse_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { if elems.is_empty() { return empty_tree_root(255 - prefix); } @@ -18,13 +18,13 @@ fn smt_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { let (left, right): (BTreeSet<_>, BTreeSet<_>) = elems.iter().partition(|e| !bit(prefix, **e)); merkle::node( - smt_root_rec(prefix + 1, &left), - smt_root_rec(prefix + 1, &right), + sparse_root_rec(prefix + 1, &left), + sparse_root_rec(prefix + 1, &right), ) } -fn smt_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec { - fn stm_path_rec( +pub fn sparse_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec { + fn sparse_path_rec( prefix: u8, elem: [u8; 32], elems: &BTreeSet<[u8; 32]>, @@ -38,15 +38,15 @@ fn smt_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec match bit(prefix, elem) { true => { - let left_root = smt_root_rec(prefix + 1, &left); - let mut path = stm_path_rec(prefix + 1, elem, &right); + 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 = smt_root_rec(prefix + 1, &right); - let mut path = stm_path_rec(prefix + 1, elem, &left); + 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 @@ -54,7 +54,7 @@ fn smt_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec } } - stm_path_rec(0, elem, elems) + sparse_path_rec(0, elem, elems) } fn empty_tree_root(height: u8) -> [u8; 32] { @@ -74,22 +74,24 @@ fn bit(idx: u8, elem: [u8; 32]) -> bool { #[cfg(test)] mod tests { - use monotree::utils::random_hash; - use super::*; + fn random_hash() -> [u8; 32] { + rand::random() + } + #[test] - fn test_smt_path() { + fn test_sparse_path() { let elems = BTreeSet::from_iter(std::iter::repeat_with(random_hash).take(10)); let one = merkle::leaf(&[255u8; 32]); let zero = merkle::leaf(&[0u8; 32]); - let root = smt_root(&elems); + let root = sparse_root(&elems); // membership proofs for e in elems.iter() { - let path = smt_path(*e, &elems); + let path = sparse_path(*e, &elems); assert_eq!(path.len(), 255); assert_eq!(merkle::path_root(one, &path), root); } @@ -97,7 +99,7 @@ mod tests { // non-membership proofs for _ in 0..10 { let elem = random_hash(); - let path = smt_path(elem, &elems); + let path = sparse_path(elem, &elems); assert!(!elems.contains(&elem)); assert_eq!(path.len(), 255); assert_eq!(merkle::path_root(zero, &path), root); @@ -105,10 +107,10 @@ mod tests { } #[test] - fn test_smt_non_membership_in_empty_tree() { - let root = smt_root(&BTreeSet::new()); + fn test_sparse_non_membership_in_empty_tree() { + let root = sparse_root(&BTreeSet::new()); - let path = smt_path([0u8; 32], &BTreeSet::new()); + let path = sparse_path([0u8; 32], &BTreeSet::new()); let zero = merkle::leaf(&[0u8; 32]); assert_eq!(merkle::path_root(zero, &path), root); @@ -123,8 +125,8 @@ mod tests { } #[test] - fn test_smt_root_left_most_occupied() { - let root = smt_root(&BTreeSet::from_iter([[0u8; 32]])); + fn test_sparse_root_left_most_occupied() { + let root = sparse_root(&BTreeSet::from_iter([[0u8; 32]])); // We are constructing the tree: // @@ -141,8 +143,8 @@ mod tests { } #[test] - fn test_smt_root_right_most_occupied() { - let root = smt_root(&BTreeSet::from_iter([[255u8; 32]])); + fn test_sparse_root_right_most_occupied() { + let root = sparse_root(&BTreeSet::from_iter([[255u8; 32]])); // We are constructing the tree: // @@ -159,7 +161,7 @@ mod tests { } #[test] - fn test_smt_root_middle_elem() { + fn test_sparse_root_middle_elem() { let elem = { let mut x = [255u8; 32]; x[0] = 254; @@ -170,7 +172,7 @@ mod tests { assert!(bit(i, elem)); } - let root = smt_root(&BTreeSet::from_iter([elem])); + let root = sparse_root(&BTreeSet::from_iter([elem])); // We are constructing the tree: // root @@ -191,13 +193,13 @@ mod tests { } #[test] - fn test_smt_root_middle_weave_elem() { + 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 = smt_root(&BTreeSet::from_iter([elem])); + let root = sparse_root(&BTreeSet::from_iter([elem])); // We are constructing the tree: // /\ @@ -220,8 +222,8 @@ mod tests { } #[test] - fn test_smt_multiple_elems() { - let root = smt_root(&BTreeSet::from_iter([[0u8; 32], [255u8; 32]])); + fn test_sparse_multiple_elems() { + let root = sparse_root(&BTreeSet::from_iter([[0u8; 32], [255u8; 32]])); // We are constructing the tree: // root From bd1f928e00d1714567f0bdb14ec32c92528146fc Mon Sep 17 00:00:00 2001 From: David Rusu Date: Thu, 5 Dec 2024 23:47:52 +0400 Subject: [PATCH 03/13] pre-compute empty trees --- emmarin/cl/cl/Cargo.toml | 3 +- .../cl/src/zone_layer/sparse_merkle_tree.rs | 67 +++++++++++-------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/emmarin/cl/cl/Cargo.toml b/emmarin/cl/cl/Cargo.toml index 761fa4a..ead4861 100644 --- a/emmarin/cl/cl/Cargo.toml +++ b/emmarin/cl/cl/Cargo.toml @@ -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" \ No newline at end of file +sha2 = "0.10" +lazy_static = "1.5.0" \ No newline at end of file diff --git a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs index d43ebd0..3d490de 100644 --- a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs +++ b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs @@ -1,6 +1,26 @@ use std::collections::BTreeSet; use crate::cl::merkle; +use lazy_static::lazy_static; + +/// absence of element is marked with all 0's +static ABSENT: [u8; 32] = [0u8; 32]; + +/// presence of element is marked with all 1's +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]; 256] = { + let mut roots = [ABSENT; 256]; + for h in 1..256 { + 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) @@ -12,7 +32,7 @@ fn sparse_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { } if prefix == 255 { assert_eq!(elems.len(), 1); - return merkle::leaf(&[255u8; 32]); // presence of element is marked with all 1's + return PRESENT; } // partition the elements let (left, right): (BTreeSet<_>, BTreeSet<_>) = elems.iter().partition(|e| !bit(prefix, **e)); @@ -58,11 +78,7 @@ pub fn sparse_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec [u8; 32] { - let mut root = merkle::leaf(&[0u8; 32]); - for _ in 0..height { - root = merkle::node(root, root); - } - root + EMPTY_ROOTS[height as usize] } fn bit(idx: u8, elem: [u8; 32]) -> bool { @@ -84,16 +100,13 @@ mod tests { fn test_sparse_path() { let elems = BTreeSet::from_iter(std::iter::repeat_with(random_hash).take(10)); - let one = merkle::leaf(&[255u8; 32]); - let zero = merkle::leaf(&[0u8; 32]); - let root = sparse_root(&elems); // membership proofs for e in elems.iter() { let path = sparse_path(*e, &elems); assert_eq!(path.len(), 255); - assert_eq!(merkle::path_root(one, &path), root); + assert_eq!(merkle::path_root(PRESENT, &path), root); } // non-membership proofs @@ -102,7 +115,7 @@ mod tests { let path = sparse_path(elem, &elems); assert!(!elems.contains(&elem)); assert_eq!(path.len(), 255); - assert_eq!(merkle::path_root(zero, &path), root); + assert_eq!(merkle::path_root(ABSENT, &path), root); } } @@ -112,8 +125,7 @@ mod tests { let path = sparse_path([0u8; 32], &BTreeSet::new()); - let zero = merkle::leaf(&[0u8; 32]); - assert_eq!(merkle::path_root(zero, &path), root); + assert_eq!(merkle::path_root(ABSENT, &path), root); for (h, node) in path.into_iter().enumerate() { match node { @@ -134,7 +146,7 @@ mod tests { // / \ 0 subtree // / \ 0 subtree // 1 0 - let mut expected_root = merkle::leaf(&[255u8; 32]); + let mut expected_root = PRESENT; for h in 0..=254 { expected_root = merkle::node(expected_root, empty_tree_root(h)) } @@ -152,7 +164,7 @@ mod tests { // 0 /\ // 0 /\ // 0 1 - let mut expected_root = merkle::leaf(&[255u8; 32]); + let mut expected_root = PRESENT; for h in 0..=254 { expected_root = merkle::node(empty_tree_root(h), expected_root) } @@ -183,7 +195,7 @@ mod tests { // 0 ... // \ // 1 - let mut expected_root = merkle::leaf(&[255u8; 32]); + let mut expected_root = PRESENT; for h in 0..=253 { expected_root = merkle::node(empty_tree_root(h), expected_root) } @@ -210,7 +222,7 @@ mod tests { // /\0 // 0 1 - let mut expected_root = merkle::leaf(&[255u8; 32]); + let mut expected_root = PRESENT; for h in 0..=254 { if h % 2 == 0 { expected_root = merkle::node(empty_tree_root(h), expected_root) @@ -232,12 +244,12 @@ mod tests { // /\0 0 /\ // 1 0 0 1 - let mut left_root = merkle::leaf(&[255u8; 32]); + let mut left_root = PRESENT; for h in 0..=253 { left_root = merkle::node(left_root, empty_tree_root(h)) } - let mut right_root = merkle::leaf(&[255u8; 32]); + let mut right_root = PRESENT; for h in 0..=253 { right_root = merkle::node(empty_tree_root(h), right_root) } @@ -249,32 +261,31 @@ mod tests { #[test] fn test_bit() { for i in 0..=255 { - assert_eq!(bit(i, [0u8; 32]), false, "{}", i) + assert!(!bit(i, [0u8; 32])) } for i in 0..=255 { - assert_eq!(bit(i, [255u8; 32]), true, "{}", i) + assert!(bit(i, [255u8; 32])) } for i in 0..=255 { - assert_eq!(bit(i, [85u8; 32]), i % 2 == 0, "{}", i) + assert_eq!(bit(i, [85u8; 32]), i % 2 == 0) } } #[test] fn test_empty_tree_root() { - let zero = merkle::leaf(&[0u8; 32]); - assert_eq!(empty_tree_root(0), zero); + assert_eq!(empty_tree_root(0), ABSENT); - assert_eq!(empty_tree_root(1), merkle::node(zero, zero)); + assert_eq!(empty_tree_root(1), merkle::node(ABSENT, ABSENT)); assert_eq!( empty_tree_root(2), - merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), + merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)), ); assert_eq!( empty_tree_root(3), merkle::node( - merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), - merkle::node(merkle::node(zero, zero), merkle::node(zero, zero)), + merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)), + merkle::node(merkle::node(ABSENT, ABSENT), merkle::node(ABSENT, ABSENT)), ) ); } From 0056486a6cb7d3724b0171d703ee665245e987da Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 11:36:06 +0400 Subject: [PATCH 04/13] fix off-by-one in sparse merkle tree --- emmarin/cl/cl/src/cl/nullifier.rs | 2 +- emmarin/cl/cl/src/zone_layer/ledger.rs | 60 +++++++-- .../cl/src/zone_layer/sparse_merkle_tree.rs | 122 ++++++++++++++---- 3 files changed, 147 insertions(+), 37 deletions(-) diff --git a/emmarin/cl/cl/src/cl/nullifier.rs b/emmarin/cl/cl/src/cl/nullifier.rs index c2340f4..9af8265 100644 --- a/emmarin/cl/cl/src/cl/nullifier.rs +++ b/emmarin/cl/cl/src/cl/nullifier.rs @@ -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 { diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index 43d476b..1273115 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -1,7 +1,13 @@ -use crate::cl::{merkle, mmr::MMR, Nullifier}; +use std::collections::BTreeSet; + +use crate::cl::{ + merkle, + mmr::{MMRProof, MMR}, + NoteCommitment, Nullifier, +}; use serde::{Deserialize, Serialize}; -const MAX_NULL: usize = 256; +use super::sparse_merkle_tree; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Ledger { @@ -12,23 +18,59 @@ pub struct Ledger { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct LedgerWitness { pub commitments: MMR, - pub nullifiers: Vec, + 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 assert_nf_update(&mut self, nf: Nullifier, path: &[merkle::PathNode]) { + // verify that the path corresponds to the nullifier + assert_eq!(sparse_merkle_tree::path_key(path), nf.0); + + // verify that the nullifier was not already present + assert_eq!( + merkle::path_root(sparse_merkle_tree::ABSENT, path), + self.nf_root + ); + + // update the nullifer root with the nullifier inserted into the tree + self.nf_root = merkle::path_root(sparse_merkle_tree::PRESENT, path); + } +} + +pub struct LedgerState { + commitments: MMR, + 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::>(); - merkle::root(merkle::padded_leaves::(&bytes)) + sparse_merkle_tree::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 { + let path = sparse_merkle_tree::sparse_path(nf.0, &self.nullifiers); + + assert!(!self.nullifiers.contains(&nf.0)); + self.nullifiers.insert(nf.0); + + path } } diff --git a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs index 3d490de..bc1d035 100644 --- a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs +++ b/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs @@ -4,17 +4,17 @@ use crate::cl::merkle; use lazy_static::lazy_static; /// absence of element is marked with all 0's -static ABSENT: [u8; 32] = [0u8; 32]; +pub static ABSENT: [u8; 32] = [0u8; 32]; /// presence of element is marked with all 1's -static PRESENT: [u8; 32] = [255u8; 32]; +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]; 256] = { - let mut roots = [ABSENT; 256]; - for h in 1..256 { + 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]); } @@ -26,16 +26,17 @@ pub fn sparse_root(elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { sparse_root_rec(0, elems) } -fn sparse_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { +fn sparse_root_rec(prefix: u64, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { if elems.is_empty() { - return empty_tree_root(255 - prefix); + return empty_tree_root(256 - prefix); } - if prefix == 255 { + if prefix == 256 { assert_eq!(elems.len(), 1); return PRESENT; } // partition the elements - let (left, right): (BTreeSet<_>, BTreeSet<_>) = elems.iter().partition(|e| !bit(prefix, **e)); + let (left, right): (BTreeSet<_>, BTreeSet<_>) = + elems.iter().partition(|e| !bit(prefix as u8, **e)); merkle::node( sparse_root_rec(prefix + 1, &left), @@ -45,18 +46,18 @@ fn sparse_root_rec(prefix: u8, elems: &BTreeSet<[u8; 32]>) -> [u8; 32] { pub fn sparse_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec { fn sparse_path_rec( - prefix: u8, + prefix: u64, elem: [u8; 32], elems: &BTreeSet<[u8; 32]>, ) -> Vec { - if prefix == 255 { + if prefix == 256 { return Vec::new(); } // partition the elements let (left, right): (BTreeSet<_>, BTreeSet<_>) = - elems.iter().partition(|e| !bit(prefix, **e)); + elems.iter().partition(|e| !bit(prefix as u8, **e)); - match bit(prefix, elem) { + 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); @@ -77,7 +78,27 @@ pub fn sparse_path(elem: [u8; 32], elems: &BTreeSet<[u8; 32]>) -> Vec [u8; 32] { +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] } @@ -85,7 +106,7 @@ 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)) >> bit_in_byte == 1 + (elem[byte as usize] & (1 << bit_in_byte)) != 0 } #[cfg(test)] @@ -96,6 +117,55 @@ mod tests { 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)); @@ -105,7 +175,6 @@ mod tests { // membership proofs for e in elems.iter() { let path = sparse_path(*e, &elems); - assert_eq!(path.len(), 255); assert_eq!(merkle::path_root(PRESENT, &path), root); } @@ -114,7 +183,6 @@ mod tests { let elem = random_hash(); let path = sparse_path(elem, &elems); assert!(!elems.contains(&elem)); - assert_eq!(path.len(), 255); assert_eq!(merkle::path_root(ABSENT, &path), root); } } @@ -130,7 +198,7 @@ mod tests { 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 u8)) + assert_eq!(hash, empty_tree_root(h as u64)) } } } @@ -147,7 +215,7 @@ mod tests { // / \ 0 subtree // 1 0 let mut expected_root = PRESENT; - for h in 0..=254 { + for h in 0..=255 { expected_root = merkle::node(expected_root, empty_tree_root(h)) } @@ -165,7 +233,7 @@ mod tests { // 0 /\ // 0 1 let mut expected_root = PRESENT; - for h in 0..=254 { + for h in 0..=255 { expected_root = merkle::node(empty_tree_root(h), expected_root) } @@ -196,10 +264,10 @@ mod tests { // \ // 1 let mut expected_root = PRESENT; - for h in 0..=253 { + for h in 0..=254 { expected_root = merkle::node(empty_tree_root(h), expected_root) } - expected_root = merkle::node(expected_root, empty_tree_root(254)); + expected_root = merkle::node(expected_root, empty_tree_root(255)); assert_eq!(root, expected_root) } @@ -223,11 +291,11 @@ mod tests { // 0 1 let mut expected_root = PRESENT; - for h in 0..=254 { + for h in 0..=255 { if h % 2 == 0 { - expected_root = merkle::node(empty_tree_root(h), expected_root) - } else { 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) @@ -245,12 +313,12 @@ mod tests { // 1 0 0 1 let mut left_root = PRESENT; - for h in 0..=253 { + for h in 0..=254 { left_root = merkle::node(left_root, empty_tree_root(h)) } let mut right_root = PRESENT; - for h in 0..=253 { + for h in 0..=254 { right_root = merkle::node(empty_tree_root(h), right_root) } let expected_root = merkle::node(left_root, right_root); From 53aa3b75c0c0507e6e3ef16078296e1d3a1fc3f3 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 12:34:28 +0400 Subject: [PATCH 05/13] mv sparse merkle tree to cl --- .../src/{zone_layer/sparse_merkle_tree.rs => cl/sparse_merkle.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename emmarin/cl/cl/src/{zone_layer/sparse_merkle_tree.rs => cl/sparse_merkle.rs} (100%) diff --git a/emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs b/emmarin/cl/cl/src/cl/sparse_merkle.rs similarity index 100% rename from emmarin/cl/cl/src/zone_layer/sparse_merkle_tree.rs rename to emmarin/cl/cl/src/cl/sparse_merkle.rs From aa57295f4b0a7c3e29060595c268dc43d8fc12c7 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 12:58:00 +0400 Subject: [PATCH 06/13] wip: integrating new ledger into proofs --- emmarin/cl/cl/src/cl/merkle.rs | 4 +- emmarin/cl/cl/src/cl/mod.rs | 1 + emmarin/cl/cl/src/zone_layer/ledger.rs | 17 +++---- emmarin/cl/cl/src/zone_layer/mod.rs | 1 - .../cl/ledger_proof_statements/src/ledger.rs | 14 +++++- emmarin/cl/ledger_proof_statements/src/ptx.rs | 12 +++-- .../ledger_validity_proof/ledger/src/main.rs | 48 ++++++++++--------- 7 files changed, 56 insertions(+), 41 deletions(-) diff --git a/emmarin/cl/cl/src/cl/merkle.rs b/emmarin/cl/cl/src/cl/merkle.rs index bb10e3c..8aecb78 100644 --- a/emmarin/cl/cl/src/cl/merkle.rs +++ b/emmarin/cl/cl/src/cl/merkle.rs @@ -43,6 +43,8 @@ pub fn root(elements: [[u8; 32]; N]) -> [u8; 32] { nodes[0] } +pub type Path = Vec; + #[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(leaves: [[u8; 32]; N], idx: usize) -> Vec { +pub fn path(leaves: [[u8; 32]; N], idx: usize) -> Path { assert!(N.is_power_of_two()); assert!(idx < N); diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs index c2c25f6..714e83a 100644 --- a/emmarin/cl/cl/src/cl/mod.rs +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -9,6 +9,7 @@ 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; diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index 1273115..240304b 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -3,12 +3,10 @@ use std::collections::BTreeSet; use crate::cl::{ merkle, mmr::{MMRProof, MMR}, - NoteCommitment, Nullifier, + sparse_merkle, NoteCommitment, Nullifier, }; use serde::{Deserialize, Serialize}; -use super::sparse_merkle_tree; - #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct Ledger { cm_root: [u8; 32], @@ -31,16 +29,13 @@ impl LedgerWitness { pub fn assert_nf_update(&mut self, nf: Nullifier, path: &[merkle::PathNode]) { // verify that the path corresponds to the nullifier - assert_eq!(sparse_merkle_tree::path_key(path), nf.0); + assert_eq!(sparse_merkle::path_key(path), nf.0); // verify that the nullifier was not already present - assert_eq!( - merkle::path_root(sparse_merkle_tree::ABSENT, path), - self.nf_root - ); + 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_tree::PRESENT, path); + self.nf_root = merkle::path_root(sparse_merkle::PRESENT, path); } } @@ -58,7 +53,7 @@ impl LedgerState { } pub fn nf_root(&self) -> [u8; 32] { - sparse_merkle_tree::sparse_root(&self.nullifiers) + sparse_merkle::sparse_root(&self.nullifiers) } pub fn add_commitment(&mut self, cm: NoteCommitment) -> MMRProof { @@ -66,7 +61,7 @@ impl LedgerState { } pub fn add_nullifier(&mut self, nf: Nullifier) -> Vec { - let path = sparse_merkle_tree::sparse_path(nf.0, &self.nullifiers); + let path = sparse_merkle::sparse_path(nf.0, &self.nullifiers); assert!(!self.nullifiers.contains(&nf.0)); self.nullifiers.insert(nf.0); diff --git a/emmarin/cl/cl/src/zone_layer/mod.rs b/emmarin/cl/cl/src/zone_layer/mod.rs index 06da01c..20710b3 100644 --- a/emmarin/cl/cl/src/zone_layer/mod.rs +++ b/emmarin/cl/cl/src/zone_layer/mod.rs @@ -1,4 +1,3 @@ pub mod ledger; pub mod notes; -pub mod sparse_merkle_tree; pub mod tx; diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index a7feda6..0355703 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -1,4 +1,5 @@ use crate::ptx::PtxPublic; +use cl::cl::merkle; use cl::cl::{bundle::BundleId, Output}; use cl::zone_layer::{ ledger::{Ledger, LedgerWitness}, @@ -19,7 +20,18 @@ pub struct LedgerProofPublic { pub struct LedgerProofPrivate { pub ledger: LedgerWitness, pub id: ZoneId, - pub bundles: Vec>, + pub bundles: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerBundleWitness { + pub partials: Vec, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct LedgerPtxWitness { + pub ptx: PtxPublic, + pub nf_proofs: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 93d555c..6f2b189 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -1,15 +1,19 @@ -use cl::cl::{merkle, PartialTx, PartialTxWitness}; +use cl::cl::{ + merkle, + 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_mmr: MMR, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, - pub input_cm_paths: Vec>, - pub cm_roots: Vec<[u8; 32]>, + pub input_cm_paths: Vec, + pub cm_mmr: MMR, } diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 5849b15..9f9ab02 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -30,8 +30,9 @@ fn main() { for bundle in bundles { let balance_public = BalancePublic { - balances: bundle.iter().map(|ptx| ptx.ptx.balance).collect::>(), + balances: bundle.partials.iter().map(|bundle_ptx| bundle_ptx.ptx.ptx.balance).collect::>(), }; + // verify bundle is balanced env::verify( nomos_cl_risc0_proofs::BALANCE_ID, @@ -68,41 +69,42 @@ fn main() { fn process_ptx( mut ledger: LedgerWitness, - ptx: &PtxPublic, + ptx_witness: &LedgerPtxWitness, zone_id: ZoneId, 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(); - let cm_roots = &ptx.cm_roots; - let ptx = &ptx.ptx; + PtxWitness { ref ptx, ref nf_proofs } = ptx_witness; - let mut outputs = vec![]; + assert_eq!(ptx.cm_mmr, roots); // we force commitment proofs w.r.t. latest MMR + assert_eq!(ptx.ptx.inputs.len(), nf_proofs.len()); - 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 (input, nf_proof) in ptx.ptx.inputs.iter().zip(nf_proofs) { + if input.zone_id != zone_id { + continue; } + + ledger.assert_nf_update(input.nullifier, nf_proof); + + env::verify( + input.constraint.0, + &serde::to_vec(&ConstraintPublic { + ptx_root: ptx.root(), + nf: input.nullifier, + }).unwrap(), + ).unwrap(); } + let mut outputs = vec![]; for output in &ptx.outputs { - if output.zone_id == zone_id { - ledger.commitments.push(&output.note_comm.0); - outputs.push(*output); + if output.zone_id != zone_id { + continue; } + + ledger.commitments.push(&output.note_comm.0); + outputs.push(*output); } (ledger, outputs) From 84cb37240b17d2895e8f8e7b09650e357b636f31 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 13:44:53 +0400 Subject: [PATCH 07/13] clippy/get everything building --- emmarin/cl/cl/src/cl/bundle.rs | 3 +- emmarin/cl/cl/src/cl/mmr.rs | 4 +- emmarin/cl/cl/src/zone_layer/ledger.rs | 5 ++ emmarin/cl/ledger/src/ledger.rs | 40 +++++++---- emmarin/cl/ledger/src/partial_tx.rs | 11 +-- emmarin/cl/ledger/src/zone_update.rs | 2 +- emmarin/cl/ledger/tests/simple_transfer.rs | 72 +++++++++---------- emmarin/cl/ledger_proof_statements/src/ptx.rs | 1 - 8 files changed, 76 insertions(+), 62 deletions(-) diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs index aa32e08..4407ba9 100644 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -29,13 +29,12 @@ impl Bundle { .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); + hasher.update(ptx.root().0); } BundleId(hasher.finalize().into()) diff --git a/emmarin/cl/cl/src/cl/mmr.rs b/emmarin/cl/cl/src/cl/mmr.rs index a5a4151..153e930 100644 --- a/emmarin/cl/cl/src/cl/mmr.rs +++ b/emmarin/cl/cl/src/cl/mmr.rs @@ -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, } @@ -20,7 +20,7 @@ pub struct MMRProof { impl MMR { pub fn new() -> Self { - Self { roots: vec![] } + Self::default() } pub fn push(&mut self, elem: &[u8]) -> MMRProof { diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index 240304b..eca60e6 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -39,6 +39,7 @@ impl LedgerWitness { } } +#[derive(Default, Clone)] pub struct LedgerState { commitments: MMR, nullifiers: BTreeSet<[u8; 32]>, @@ -52,6 +53,10 @@ impl LedgerState { } } + pub fn cm_mmr(&self) -> MMR { + self.commitments.clone() + } + pub fn nf_root(&self) -> [u8; 32] { sparse_merkle::sparse_root(&self.nullifiers) } diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index 4875ba8..cc126d3 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -1,6 +1,5 @@ -use ledger_proof_statements::{ - ledger::{LedgerProofPrivate, LedgerProofPublic}, - ptx::PtxPublic, +use ledger_proof_statements::ledger::{ + LedgerBundleWitness, LedgerProofPrivate, LedgerProofPublic, LedgerPtxWitness, }; use crate::{ @@ -9,7 +8,7 @@ use crate::{ error::{Error, Result}, partial_tx::ProvedPartialTx, }; -use cl::zone_layer::{ledger::LedgerWitness, notes::ZoneId}; +use cl::zone_layer::{ledger::LedgerState, notes::ZoneId}; #[derive(Debug, Clone)] pub struct ProvedLedgerTransition { @@ -25,10 +24,6 @@ pub struct ProvedBundle { } impl ProvedBundle { - fn to_public(&self) -> Vec { - self.ptxs.iter().map(|p| p.public.clone()).collect() - } - fn proofs(&self) -> Vec { let mut proofs = vec![self.balance.risc0_receipt.clone()]; proofs.extend(self.ptxs.iter().map(|p| p.risc0_receipt.clone())); @@ -38,17 +33,38 @@ impl ProvedBundle { impl ProvedLedgerTransition { pub fn prove( - ledger: LedgerWitness, + mut ledger: LedgerState, zone_id: ZoneId, bundles: Vec, constraints: Vec, ) -> Result { - let witness = LedgerProofPrivate { - bundles: bundles.iter().map(|p| p.to_public()).collect(), - ledger, + let mut witness = LedgerProofPrivate { + bundles: Vec::new(), + ledger: ledger.to_witness(), id: zone_id, }; + // prepare the sparse merkle tree nullifier proofs + for bundle in &bundles { + let mut partials = Vec::new(); + + for ptx in &bundle.ptxs { + let mut nf_proofs = Vec::new(); + + for input in &ptx.public.ptx.inputs { + let nf_proof = ledger.add_nullifier(input.nullifier); + nf_proofs.push(nf_proof); + } + + partials.push(LedgerPtxWitness { + ptx: ptx.public.clone(), + nf_proofs, + }); + } + + witness.bundles.push(LedgerBundleWitness { partials }) + } + let mut env = risc0_zkvm::ExecutorEnv::builder(); for bundle in bundles { diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index 0566f2a..2cb007d 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -1,7 +1,10 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; use crate::error::{Error, Result}; -use cl::cl::{merkle, PartialTxWitness}; +use cl::cl::{ + mmr::{MMRProof, MMR}, + PartialTxWitness, +}; #[derive(Debug, Clone)] pub struct ProvedPartialTx { @@ -12,13 +15,13 @@ pub struct ProvedPartialTx { impl ProvedPartialTx { pub fn prove( ptx_witness: PartialTxWitness, - input_cm_paths: Vec>, - cm_roots: Vec<[u8; 32]>, + input_cm_paths: Vec, + cm_mmr: MMR, ) -> Result { let ptx_private = PtxPrivate { ptx: ptx_witness, input_cm_paths, - cm_roots: cm_roots.clone(), + cm_mmr: cm_mmr.clone(), }; let env = risc0_zkvm::ExecutorEnv::builder() diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs index 2bf7baa..7aa1341 100644 --- a/emmarin/cl/ledger/src/zone_update.rs +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -22,7 +22,7 @@ impl ProvedUpdateBundle { expected_zones.insert(bundle.id, HashSet::from_iter(bundle.zones.clone())); actual_zones .entry(bundle.id) - .or_insert_with(|| HashSet::new()) + .or_insert_with(HashSet::new) .insert(proof.public.id); } } diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index 03cf9af..cf6f967 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,10 +1,10 @@ use cl::{ cl::{ - balance::Unit, merkle, mmr::MMR, note::derive_unit, BalanceWitness, InputWitness, - NoteWitness, NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, + balance::Unit, mmr::MMRProof, note::derive_unit, BalanceWitness, InputWitness, NoteWitness, + NullifierCommitment, NullifierSecret, OutputWitness, PartialTxWitness, }, zone_layer::{ - ledger::LedgerWitness, + ledger::LedgerState, notes::{ZoneId, ZoneNote}, tx::{UpdateBundle, ZoneUpdate}, }, @@ -21,9 +21,9 @@ use ledger_proof_statements::{balance::BalancePrivate, stf::StfPublic}; use rand_core::CryptoRngCore; use std::sync::OnceLock; -fn nmo() -> &'static Unit { +fn nmo() -> Unit { static NMO: OnceLock = OnceLock::new(); - NMO.get_or_init(|| derive_unit("NMO")) + *NMO.get_or_init(|| derive_unit("NMO")) } struct User(NullifierSecret); @@ -48,24 +48,22 @@ fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) fn cross_transfer_transition( input: InputWitness, - input_path: Vec, + input_path: 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,12 +74,8 @@ 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 proved_ptx = + ProvedPartialTx::prove(ptx_witness.clone(), vec![input_path], ledger_a.cm_mmr()).unwrap(); let balance = ProvedBalance::prove(&BalancePrivate { balances: vec![ptx_witness.balance()], @@ -108,13 +102,19 @@ 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().0); - ledger_a.nullifiers.push(input.nullifier()); + ledger_a.add_commitment(change.commit_note()); + ledger_a.add_nullifier(input.nullifier()); - ledger_b.commitments.push(&transfer.commit_note().0); + ledger_b.add_commitment(transfer.commit_note()); - assert_eq!(ledger_a_transition.public.ledger, ledger_a.commit()); - assert_eq!(ledger_b_transition.public.ledger, ledger_b.commit()); + 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,36 +133,28 @@ 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 input_cm_path = ledger_a.add_commitment(utxo.commit_note()); - 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], }; diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 6f2b189..5f63f46 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -1,5 +1,4 @@ use cl::cl::{ - merkle, mmr::{MMRProof, MMR}, PartialTx, PartialTxWitness, }; From f8a62fe7c9f0711244d0148b090807d52f392148 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 15:12:36 +0400 Subject: [PATCH 08/13] update ptx proof to use MMR's --- emmarin/cl/ledger_proof_statements/src/ptx.rs | 2 +- emmarin/cl/risc0_proofs/ptx/src/main.rs | 14 ++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 5f63f46..6f25761 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -13,6 +13,6 @@ pub struct PtxPublic { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, - pub input_cm_paths: Vec, + pub input_cm_proofs: Vec, pub cm_mmr: MMR, } diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index 7b03f68..2ab181a 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -1,20 +1,18 @@ /// 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, + input_cm_proofs, + cm_mmr, } = 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) { + assert_eq!(ptx.inputs.len(), input_cm_proofs.len()); + for (input, cm_mmr_proof) in ptx.inputs.iter().zip(input_cm_proofs) { 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)); + assert!(cm_mmr.verify_proof(¬e_cm.0, &cm_mmr_proof)); } for output in ptx.outputs.iter() { @@ -23,6 +21,6 @@ fn main() { env::commit(&PtxPublic { ptx: ptx.commit(), - cm_roots, + cm_mmr, }); } From fdab50a0e4de040b259dde1cb3d56ec41632e9ee Mon Sep 17 00:00:00 2001 From: David Rusu Date: Fri, 6 Dec 2024 23:47:54 +0400 Subject: [PATCH 09/13] all working now --- emmarin/cl/cl/src/cl/mmr.rs | 10 ++++- emmarin/cl/cl/src/zone_layer/ledger.rs | 10 ++--- emmarin/cl/ledger/src/partial_tx.rs | 6 +-- emmarin/cl/ledger/src/zone_update.rs | 1 - emmarin/cl/ledger/tests/simple_transfer.rs | 17 ++++---- emmarin/cl/ledger_proof_statements/src/ptx.rs | 5 +-- .../ledger_validity_proof/ledger/src/main.rs | 40 ++++++++----------- emmarin/cl/risc0_proofs/ptx/src/main.rs | 7 ++-- 8 files changed, 46 insertions(+), 50 deletions(-) diff --git a/emmarin/cl/cl/src/cl/mmr.rs b/emmarin/cl/cl/src/cl/mmr.rs index 153e930..0a11847 100644 --- a/emmarin/cl/cl/src/cl/mmr.rs +++ b/emmarin/cl/cl/src/cl/mmr.rs @@ -18,6 +18,13 @@ pub struct MMRProof { pub path: Vec, } +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::default() @@ -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 { diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index eca60e6..e01d49d 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -39,10 +39,10 @@ impl LedgerWitness { } } -#[derive(Default, Clone)] +#[derive(Debug, Default, Clone)] pub struct LedgerState { - commitments: MMR, - nullifiers: BTreeSet<[u8; 32]>, + pub commitments: MMR, + pub nullifiers: BTreeSet<[u8; 32]>, } impl LedgerState { @@ -53,10 +53,6 @@ impl LedgerState { } } - pub fn cm_mmr(&self) -> MMR { - self.commitments.clone() - } - pub fn nf_root(&self) -> [u8; 32] { sparse_merkle::sparse_root(&self.nullifiers) } diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index 2cb007d..d4e8b91 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -15,13 +15,11 @@ pub struct ProvedPartialTx { impl ProvedPartialTx { pub fn prove( ptx_witness: PartialTxWitness, - input_cm_paths: Vec, - cm_mmr: MMR, + input_cm_proofs: Vec<(MMR, MMRProof)>, ) -> Result { let ptx_private = PtxPrivate { ptx: ptx_witness, - input_cm_paths, - cm_mmr: cm_mmr.clone(), + input_cm_proofs, }; let env = risc0_zkvm::ExecutorEnv::builder() diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs index 7aa1341..988945b 100644 --- a/emmarin/cl/ledger/src/zone_update.rs +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -27,7 +27,6 @@ impl ProvedUpdateBundle { } } - println!("{:?} | {:?}", expected_zones, actual_zones); for (bundle, expected) in expected_zones.iter() { if let Some(actual) = actual_zones.get(bundle) { if actual != expected { diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index cf6f967..3134274 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -1,7 +1,10 @@ use cl::{ cl::{ - balance::Unit, mmr::MMRProof, 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::LedgerState, @@ -48,7 +51,7 @@ fn receive_utxo(note: NoteWitness, nf_pk: NullifierCommitment, zone_id: ZoneId) fn cross_transfer_transition( input: InputWitness, - input_path: MMRProof, + input_proof: (MMR, MMRProof), to: User, amount: u64, zone_a: ZoneId, @@ -74,8 +77,7 @@ 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], ledger_a.cm_mmr()).unwrap(); + let proved_ptx = ProvedPartialTx::prove(ptx_witness.clone(), vec![input_proof]).unwrap(); let balance = ProvedBalance::prove(&BalancePrivate { balances: vec![ptx_witness.balance()], @@ -141,7 +143,8 @@ fn zone_update_cross() { let alice_input = InputWitness::from_output(utxo, alice.sk()); let mut ledger_a = LedgerState::default(); - let input_cm_path = ledger_a.add_commitment(utxo.commit_note()); + let alice_cm_path = ledger_a.add_commitment(utxo.commit_note()); + let alice_cm_proof = (ledger_a.commitments.clone(), alice_cm_path); let ledger_b = LedgerState::default(); @@ -160,7 +163,7 @@ fn zone_update_cross() { let (ledger_a_transition, ledger_b_transition) = cross_transfer_transition( alice_input, - input_cm_path, + alice_cm_proof, bob, 8, zone_a_id, diff --git a/emmarin/cl/ledger_proof_statements/src/ptx.rs b/emmarin/cl/ledger_proof_statements/src/ptx.rs index 6f25761..425675f 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -7,12 +7,11 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPublic { pub ptx: PartialTx, - pub cm_mmr: MMR, + pub cm_mmr: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPrivate { pub ptx: PartialTxWitness, - pub input_cm_proofs: Vec, - pub cm_mmr: MMR, + pub input_cm_proofs: Vec<(MMR, MMRProof)>, } diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 9f9ab02..215f243 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -5,8 +5,7 @@ use cl::{ use ledger_proof_statements::{ balance::BalancePublic, constraint::ConstraintPublic, - ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic}, - ptx::PtxPublic, + ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic, LedgerPtxWitness}, }; use risc0_zkvm::{guest::env, serde}; @@ -21,13 +20,6 @@ fn main() { let mut cross_bundles = vec![]; let mut outputs = vec![]; - let roots = ledger - .commitments - .roots - .iter() - .map(|r| r.root) - .collect::>(); - for bundle in bundles { let balance_public = BalancePublic { balances: bundle.partials.iter().map(|bundle_ptx| bundle_ptx.ptx.ptx.balance).collect::>(), @@ -40,14 +32,13 @@ fn main() { ) .unwrap(); - for ptx in &bundle { - let (new_ledger, ptx_outputs) = process_ptx(ledger, ptx, id, &roots); - ledger = new_ledger; + for ptx in &bundle.partials { + let ptx_outputs = process_ptx(&mut ledger, ptx, id); outputs.extend(ptx_outputs); } let bundle = Bundle { - partials: bundle.into_iter().map(|ptx| ptx.ptx).collect(), + partials: bundle.partials.into_iter().map(|ptx_witness| ptx_witness.ptx.ptx).collect(), }; let zones = bundle.zones(); if zones.len() > 1 { @@ -68,37 +59,40 @@ fn main() { } fn process_ptx( - mut ledger: LedgerWitness, + ledger: &mut LedgerWitness, ptx_witness: &LedgerPtxWitness, zone_id: ZoneId, - roots: &[[u8; 32]], -) -> (LedgerWitness, Vec) { +) -> Vec { + let ptx = &ptx_witness.ptx; + let nf_proofs = &ptx_witness.nf_proofs; + // 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(); - PtxWitness { ref ptx, ref nf_proofs } = ptx_witness; - - assert_eq!(ptx.cm_mmr, roots); // we force commitment proofs w.r.t. latest MMR + assert_eq!(ptx.ptx.inputs.len(), nf_proofs.len()); + assert_eq!(ptx.ptx.inputs.len(), ptx.cm_mmr.len()); - for (input, nf_proof) in ptx.ptx.inputs.iter().zip(nf_proofs) { + for ((input, nf_proof), cm_mmr) in ptx.ptx.inputs.iter().zip(nf_proofs).zip(ptx.cm_mmr.iter()) { if input.zone_id != zone_id { continue; } + + assert_eq!(cm_mmr, &ledger.commitments); // we force commitment proofs w.r.t. latest MMR ledger.assert_nf_update(input.nullifier, nf_proof); env::verify( input.constraint.0, &serde::to_vec(&ConstraintPublic { - ptx_root: ptx.root(), + ptx_root: ptx.ptx.root(), nf: input.nullifier, }).unwrap(), ).unwrap(); } let mut outputs = vec![]; - for output in &ptx.outputs { + for output in &ptx.ptx.outputs { if output.zone_id != zone_id { continue; } @@ -107,5 +101,5 @@ fn process_ptx( outputs.push(*output); } - (ledger, outputs) + outputs } diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index 2ab181a..b03995b 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -6,13 +6,14 @@ fn main() { let PtxPrivate { ptx, input_cm_proofs, - cm_mmr, } = env::read(); assert_eq!(ptx.inputs.len(), input_cm_proofs.len()); - for (input, cm_mmr_proof) in ptx.inputs.iter().zip(input_cm_proofs) { + let mut cm_mmr = Vec::new(); + for (input, (mmr, mmr_proof)) in ptx.inputs.iter().zip(input_cm_proofs) { let note_cm = input.note_commitment(); - assert!(cm_mmr.verify_proof(¬e_cm.0, &cm_mmr_proof)); + assert!(mmr.verify_proof(¬e_cm.0, &mmr_proof)); + cm_mmr.push(mmr); } for output in ptx.outputs.iter() { From baaf10a4297e3677be784ed441ef32f64c952766 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Sat, 7 Dec 2024 00:32:25 +0400 Subject: [PATCH 10/13] mv covenant verification into ptx proof --- emmarin/cl/ledger/src/partial_tx.rs | 17 +++++++++++------ emmarin/cl/ledger/tests/simple_transfer.rs | 16 +++++++++++----- .../ledger_validity_proof/ledger/src/main.rs | 9 --------- emmarin/cl/risc0_proofs/ptx/src/main.rs | 18 +++++++++++++++--- 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/emmarin/cl/ledger/src/partial_tx.rs b/emmarin/cl/ledger/src/partial_tx.rs index d4e8b91..2d79956 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -1,6 +1,9 @@ use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; -use crate::error::{Error, Result}; +use crate::{ + error::{Error, Result}, + ConstraintProof, +}; use cl::cl::{ mmr::{MMRProof, MMR}, PartialTxWitness, @@ -16,17 +19,19 @@ impl ProvedPartialTx { pub fn prove( ptx_witness: PartialTxWitness, input_cm_proofs: Vec<(MMR, MMRProof)>, + covenant_proofs: Vec, ) -> Result { let ptx_private = PtxPrivate { ptx: ptx_witness, 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(); diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index 3134274..e8700b6 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -77,7 +77,17 @@ 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_proof]).unwrap(); + + // Prove the constraints for alices input (she uses the no-op constraint) + let constraint_proof = + ConstraintProof::prove_nop(input.nullifier(), ptx_witness.commit().root()); + + let proved_ptx = ProvedPartialTx::prove( + ptx_witness.clone(), + vec![input_proof], + vec![constraint_proof.clone()], + ) + .unwrap(); let balance = ProvedBalance::prove(&BalancePrivate { balances: vec![ptx_witness.balance()], @@ -89,10 +99,6 @@ fn cross_transfer_transition( 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()); - let ledger_a_transition = ProvedLedgerTransition::prove( ledger_a.clone(), zone_a, diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 215f243..d803906 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -4,7 +4,6 @@ use cl::{ }; use ledger_proof_statements::{ balance::BalancePublic, - constraint::ConstraintPublic, ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic, LedgerPtxWitness}, }; use risc0_zkvm::{guest::env, serde}; @@ -81,14 +80,6 @@ fn process_ptx( assert_eq!(cm_mmr, &ledger.commitments); // we force commitment proofs w.r.t. latest MMR ledger.assert_nf_update(input.nullifier, nf_proof); - - env::verify( - input.constraint.0, - &serde::to_vec(&ConstraintPublic { - ptx_root: ptx.ptx.root(), - nf: input.nullifier, - }).unwrap(), - ).unwrap(); } let mut outputs = vec![]; diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/risc0_proofs/ptx/src/main.rs index b03995b..3115eab 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/risc0_proofs/ptx/src/main.rs @@ -1,6 +1,6 @@ /// Input Proof -use ledger_proof_statements::ptx::{PtxPrivate, PtxPublic}; -use risc0_zkvm::guest::env; +use ledger_proof_statements::{constraint::ConstraintPublic, ptx::{PtxPrivate, PtxPublic}}; +use risc0_zkvm::{serde, guest::env}; fn main() { let PtxPrivate { @@ -8,12 +8,24 @@ fn main() { 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_mmr = 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(¬e_cm.0, &mmr_proof)); cm_mmr.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() { @@ -21,7 +33,7 @@ fn main() { } env::commit(&PtxPublic { - ptx: ptx.commit(), + ptx: ptx_commit, cm_mmr, }); } From 98f0a3b7528cb35125e7a5dcfb207f9b6038f52f Mon Sep 17 00:00:00 2001 From: David Rusu Date: Mon, 9 Dec 2024 15:23:43 +0400 Subject: [PATCH 11/13] rework PACT recursion --- emmarin/.gitignore | 1 + emmarin/cl/Cargo.toml | 10 +- emmarin/cl/bundle_risc0_proof/Cargo.toml | 11 ++ emmarin/cl/bundle_risc0_proof/build.rs | 3 + .../bundle}/Cargo.toml | 3 +- .../cl/bundle_risc0_proof/bundle/src/main.rs | 49 +++++++ emmarin/cl/bundle_risc0_proof/src/lib.rs | 1 + emmarin/cl/cl/src/cl/bundle.rs | 54 ++++---- emmarin/cl/cl/src/zone_layer/ledger.rs | 12 +- emmarin/cl/ledger/Cargo.toml | 2 + emmarin/cl/ledger/src/balance.rs | 57 -------- emmarin/cl/ledger/src/bundle.rs | 52 ++++++++ emmarin/cl/ledger/src/ledger.rs | 123 ++++++++---------- emmarin/cl/ledger/src/lib.rs | 2 +- emmarin/cl/ledger/src/partial_tx.rs | 10 +- emmarin/cl/ledger/src/zone_update.rs | 8 +- emmarin/cl/ledger/tests/simple_transfer.rs | 54 ++++---- emmarin/cl/ledger_proof_statements/Cargo.toml | 1 + .../cl/ledger_proof_statements/src/balance.rs | 12 -- .../cl/ledger_proof_statements/src/bundle.rs | 48 +++++++ .../cl/ledger_proof_statements/src/ledger.rs | 18 ++- emmarin/cl/ledger_proof_statements/src/lib.rs | 2 +- emmarin/cl/ledger_proof_statements/src/ptx.rs | 2 +- .../ledger_validity_proof/ledger/Cargo.toml | 2 +- .../ledger_validity_proof/ledger/src/main.rs | 96 ++++---------- emmarin/cl/ptx_risc0_proof/Cargo.toml | 11 ++ emmarin/cl/ptx_risc0_proof/build.rs | 3 + .../ptx/Cargo.toml | 0 .../ptx/src/main.rs | 20 +-- emmarin/cl/ptx_risc0_proof/src/lib.rs | 1 + emmarin/cl/risc0_proofs/Cargo.toml | 2 +- emmarin/cl/risc0_proofs/balance/src/main.rs | 24 ---- 32 files changed, 367 insertions(+), 327 deletions(-) create mode 100644 emmarin/.gitignore create mode 100644 emmarin/cl/bundle_risc0_proof/Cargo.toml create mode 100644 emmarin/cl/bundle_risc0_proof/build.rs rename emmarin/cl/{risc0_proofs/balance => bundle_risc0_proof/bundle}/Cargo.toml (90%) create mode 100644 emmarin/cl/bundle_risc0_proof/bundle/src/main.rs create mode 100644 emmarin/cl/bundle_risc0_proof/src/lib.rs delete mode 100644 emmarin/cl/ledger/src/balance.rs create mode 100644 emmarin/cl/ledger/src/bundle.rs delete mode 100644 emmarin/cl/ledger_proof_statements/src/balance.rs create mode 100644 emmarin/cl/ledger_proof_statements/src/bundle.rs create mode 100644 emmarin/cl/ptx_risc0_proof/Cargo.toml create mode 100644 emmarin/cl/ptx_risc0_proof/build.rs rename emmarin/cl/{risc0_proofs => ptx_risc0_proof}/ptx/Cargo.toml (100%) rename emmarin/cl/{risc0_proofs => ptx_risc0_proof}/ptx/src/main.rs (72%) create mode 100644 emmarin/cl/ptx_risc0_proof/src/lib.rs delete mode 100644 emmarin/cl/risc0_proofs/balance/src/main.rs diff --git a/emmarin/.gitignore b/emmarin/.gitignore new file mode 100644 index 0000000..6125671 --- /dev/null +++ b/emmarin/.gitignore @@ -0,0 +1 @@ +*profile.pb \ No newline at end of file diff --git a/emmarin/cl/Cargo.toml b/emmarin/cl/Cargo.toml index 836a57c..86795ca 100644 --- a/emmarin/cl/Cargo.toml +++ b/emmarin/cl/Cargo.toml @@ -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] diff --git a/emmarin/cl/bundle_risc0_proof/Cargo.toml b/emmarin/cl/bundle_risc0_proof/Cargo.toml new file mode 100644 index 0000000..63e3d70 --- /dev/null +++ b/emmarin/cl/bundle_risc0_proof/Cargo.toml @@ -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"] + diff --git a/emmarin/cl/bundle_risc0_proof/build.rs b/emmarin/cl/bundle_risc0_proof/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/emmarin/cl/bundle_risc0_proof/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/emmarin/cl/risc0_proofs/balance/Cargo.toml b/emmarin/cl/bundle_risc0_proof/bundle/Cargo.toml similarity index 90% rename from emmarin/cl/risc0_proofs/balance/Cargo.toml rename to emmarin/cl/bundle_risc0_proof/bundle/Cargo.toml index 161f1c8..0903802 100644 --- a/emmarin/cl/risc0_proofs/balance/Cargo.toml +++ b/emmarin/cl/bundle_risc0_proof/bundle/Cargo.toml @@ -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] diff --git a/emmarin/cl/bundle_risc0_proof/bundle/src/main.rs b/emmarin/cl/bundle_risc0_proof/bundle/src/main.rs new file mode 100644 index 0000000..0473977 --- /dev/null +++ b/emmarin/cl/bundle_risc0_proof/bundle/src/main.rs @@ -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 = 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, + }); +} diff --git a/emmarin/cl/bundle_risc0_proof/src/lib.rs b/emmarin/cl/bundle_risc0_proof/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/emmarin/cl/bundle_risc0_proof/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/methods.rs")); diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs index 4407ba9..5609c0b 100644 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ b/emmarin/cl/cl/src/cl/bundle.rs @@ -1,45 +1,41 @@ use serde::{Deserialize, Serialize}; -use crate::{cl::partial_tx::PartialTx, zone_layer::notes::ZoneId}; -use sha2::{Digest, Sha256}; -use std::collections::HashSet; +use crate::cl::partial_tx::PartialTx; /// 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)] +#[derive(Debug, Clone, PartialEq, Eq, 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() - } +// impl Bundle { +// pub fn zones(&self) -> BTreeSet { +// 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); - } +// // TODO: remove this +// 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()) - } -} +// BundleId(hasher.finalize().into()) +// } +// } #[cfg(test)] mod test { diff --git a/emmarin/cl/cl/src/zone_layer/ledger.rs b/emmarin/cl/cl/src/zone_layer/ledger.rs index e01d49d..6ac6c72 100644 --- a/emmarin/cl/cl/src/zone_layer/ledger.rs +++ b/emmarin/cl/cl/src/zone_layer/ledger.rs @@ -27,7 +27,15 @@ impl LedgerWitness { } } - pub fn assert_nf_update(&mut self, nf: Nullifier, path: &[merkle::PathNode]) { + 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); @@ -57,7 +65,7 @@ impl LedgerState { sparse_merkle::sparse_root(&self.nullifiers) } - pub fn add_commitment(&mut self, cm: NoteCommitment) -> MMRProof { + pub fn add_commitment(&mut self, cm: &NoteCommitment) -> MMRProof { self.commitments.push(&cm.0) } diff --git a/emmarin/cl/ledger/Cargo.toml b/emmarin/cl/ledger/Cargo.toml index 053c813..6bde197 100644 --- a/emmarin/cl/ledger/Cargo.toml +++ b/emmarin/cl/ledger/Cargo.toml @@ -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" } diff --git a/emmarin/cl/ledger/src/balance.rs b/emmarin/cl/ledger/src/balance.rs deleted file mode 100644 index d408782..0000000 --- a/emmarin/cl/ledger/src/balance.rs +++ /dev/null @@ -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 { - //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 { - 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() - } -} diff --git a/emmarin/cl/ledger/src/bundle.rs b/emmarin/cl/ledger/src/bundle.rs new file mode 100644 index 0000000..213f53b --- /dev/null +++ b/emmarin/cl/ledger/src/bundle.rs @@ -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) -> 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() + } +} diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index cc126d3..1660d9d 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -1,80 +1,66 @@ -use ledger_proof_statements::ledger::{ - LedgerBundleWitness, LedgerProofPrivate, LedgerProofPublic, LedgerPtxWitness, -}; +use std::collections::BTreeMap; -use crate::{ - balance::ProvedBalance, - constraint::ConstraintProof, - error::{Error, Result}, - partial_tx::ProvedPartialTx, -}; +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, -} - -impl ProvedBundle { - fn proofs(&self) -> Vec { - 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( - mut ledger: LedgerState, - zone_id: ZoneId, - bundles: Vec, - constraints: Vec, - ) -> Result { + pub fn prove(mut ledger: LedgerState, zone_id: ZoneId, bundles: Vec) -> Self { let mut witness = LedgerProofPrivate { bundles: Vec::new(), ledger: ledger.to_witness(), id: zone_id, }; - // prepare the sparse merkle tree nullifier proofs - for bundle in &bundles { - let mut partials = Vec::new(); - - for ptx in &bundle.ptxs { - let mut nf_proofs = Vec::new(); - - for input in &ptx.public.ptx.inputs { - let nf_proof = ledger.add_nullifier(input.nullifier); - nf_proofs.push(nf_proof); - } - - partials.push(LedgerPtxWitness { - ptx: ptx.public.clone(), - nf_proofs, - }); - } - - witness.bundles.push(LedgerBundleWitness { partials }) - } - 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(); + println!("OUTSIDE LEDGER_PROOF {:?}", bundle); + println!("BUNDLE_ID {:?}", nomos_cl_bundle_risc0_proof::BUNDLE_ID); + + 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); } + + for cm in &zone_ledger_update.commitments { + ledger.add_commitment(cm); + } + + 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. @@ -87,10 +73,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: {}", @@ -98,14 +81,16 @@ impl ProvedLedgerTransition { prove_info.stats.total_cycles ); - Ok(Self { - public: prove_info - .receipt - .journal - .decode::() - .unwrap(), + Self { risc0_receipt: prove_info.receipt, - }) + } + } + + pub fn public(&self) -> LedgerProofPublic { + self.risc0_receipt + .journal + .decode::() + .unwrap() } pub fn verify(&self) -> bool { diff --git a/emmarin/cl/ledger/src/lib.rs b/emmarin/cl/ledger/src/lib.rs index d42f46c..ad327b3 100644 --- a/emmarin/cl/ledger/src/lib.rs +++ b/emmarin/cl/ledger/src/lib.rs @@ -1,4 +1,4 @@ -pub mod balance; +pub mod bundle; 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 2d79956..4f73d95 100644 --- a/emmarin/cl/ledger/src/partial_tx.rs +++ b/emmarin/cl/ledger/src/partial_tx.rs @@ -11,7 +11,6 @@ use cl::cl::{ #[derive(Debug, Clone)] pub struct ProvedPartialTx { - pub public: PtxPublic, pub risc0_receipt: risc0_zkvm::Receipt, } @@ -42,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!( @@ -52,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() } } diff --git a/emmarin/cl/ledger/src/zone_update.rs b/emmarin/cl/ledger/src/zone_update.rs index 988945b..1ffff33 100644 --- a/emmarin/cl/ledger/src/zone_update.rs +++ b/emmarin/cl/ledger/src/zone_update.rs @@ -18,12 +18,12 @@ 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); + .insert(proof.public().id); } } @@ -48,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; } diff --git a/emmarin/cl/ledger/tests/simple_transfer.rs b/emmarin/cl/ledger/tests/simple_transfer.rs index e8700b6..b451b22 100644 --- a/emmarin/cl/ledger/tests/simple_transfer.rs +++ b/emmarin/cl/ledger/tests/simple_transfer.rs @@ -13,14 +13,10 @@ use cl::{ }, }; 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; @@ -89,38 +85,32 @@ fn cross_transfer_transition( ) .unwrap(); - let balance = ProvedBalance::prove(&BalancePrivate { - balances: vec![ptx_witness.balance()], - }) - .unwrap(); + let bundle = ProvedBundle::prove( + &BundlePrivate { + bundle: vec![proved_ptx.public()], + balances: vec![ptx_witness.balance()], + }, + vec![proved_ptx], + ); - let zone_tx = ProvedBundle { - ptxs: vec![proved_ptx.clone()], - balance, - }; + println!("proving ledger A transition"); + let ledger_a_transition = + ProvedLedgerTransition::prove(ledger_a.clone(), zone_a, vec![bundle.clone()]); - let ledger_a_transition = ProvedLedgerTransition::prove( - ledger_a.clone(), - zone_a, - vec![zone_tx.clone()], - vec![constraint_proof], - ) - .unwrap(); + println!("proving ledger B transition"); + let ledger_b_transition = ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![bundle]); - let ledger_b_transition = - ProvedLedgerTransition::prove(ledger_b.clone(), zone_b, vec![zone_tx], vec![]).unwrap(); - - ledger_a.add_commitment(change.commit_note()); + ledger_a.add_commitment(&change.commit_note()); ledger_a.add_nullifier(input.nullifier()); - ledger_b.add_commitment(transfer.commit_note()); + ledger_b.add_commitment(&transfer.commit_note()); assert_eq!( - ledger_a_transition.public.ledger, + ledger_a_transition.public().ledger, ledger_a.to_witness().commit() ); assert_eq!( - ledger_b_transition.public.ledger, + ledger_b_transition.public().ledger, ledger_b.to_witness().commit() ); @@ -149,7 +139,7 @@ fn zone_update_cross() { let alice_input = InputWitness::from_output(utxo, alice.sk()); let mut ledger_a = LedgerState::default(); - let alice_cm_path = ledger_a.add_commitment(utxo.commit_note()); + let alice_cm_path = ledger_a.add_commitment(&utxo.commit_note()); let alice_cm_proof = (ledger_a.commitments.clone(), alice_cm_path); let ledger_b = LedgerState::default(); @@ -179,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 }; diff --git a/emmarin/cl/ledger_proof_statements/Cargo.toml b/emmarin/cl/ledger_proof_statements/Cargo.toml index 65ea695..50bd1ca 100644 --- a/emmarin/cl/ledger_proof_statements/Cargo.toml +++ b/emmarin/cl/ledger_proof_statements/Cargo.toml @@ -6,3 +6,4 @@ edition = "2021" [dependencies] cl = { path = "../cl" } serde = { version = "1.0", features = ["derive"] } +sha2 = "0.10" diff --git a/emmarin/cl/ledger_proof_statements/src/balance.rs b/emmarin/cl/ledger_proof_statements/src/balance.rs deleted file mode 100644 index 509b1b5..0000000 --- a/emmarin/cl/ledger_proof_statements/src/balance.rs +++ /dev/null @@ -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, -} - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BalancePrivate { - pub balances: Vec, -} 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..abefbb3 --- /dev/null +++ b/emmarin/cl/ledger_proof_statements/src/bundle.rs @@ -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, +} + +#[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, + // these are commitments to created notes in this bundle + pub commitments: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BundlePrivate { + pub bundle: Vec, + pub balances: Vec, +} + +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()) + } +} diff --git a/emmarin/cl/ledger_proof_statements/src/ledger.rs b/emmarin/cl/ledger_proof_statements/src/ledger.rs index 0355703..4766c89 100644 --- a/emmarin/cl/ledger_proof_statements/src/ledger.rs +++ b/emmarin/cl/ledger_proof_statements/src/ledger.rs @@ -1,6 +1,8 @@ -use crate::ptx::PtxPublic; -use cl::cl::merkle; -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, @@ -13,7 +15,7 @@ pub struct LedgerProofPublic { pub ledger: Ledger, pub id: ZoneId, pub cross_bundles: Vec, - pub outputs: Vec, + pub outputs: Vec, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -25,12 +27,8 @@ pub struct LedgerProofPrivate { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct LedgerBundleWitness { - pub partials: Vec, -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct LedgerPtxWitness { - pub ptx: PtxPublic, + pub bundle: BundlePublic, + pub cm_root_proofs: BTreeMap<[u8; 32], merkle::Path>, pub nf_proofs: Vec, } diff --git a/emmarin/cl/ledger_proof_statements/src/lib.rs b/emmarin/cl/ledger_proof_statements/src/lib.rs index 2000d4b..5d907dc 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 balance; +pub mod bundle; 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 425675f..c058c35 100644 --- a/emmarin/cl/ledger_proof_statements/src/ptx.rs +++ b/emmarin/cl/ledger_proof_statements/src/ptx.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PtxPublic { pub ptx: PartialTx, - pub cm_mmr: Vec, + pub cm_mmrs: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml b/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml index a05b1fa..8b6ec52 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml +++ b/emmarin/cl/ledger_validity_proof/ledger/Cargo.toml @@ -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. diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index d803906..4c3d7b2 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -1,10 +1,6 @@ -use cl::{ - cl::{Bundle, Output}, - zone_layer::{ledger::LedgerWitness, notes::ZoneId}, -}; +use cl::cl::merkle; use ledger_proof_statements::{ - balance::BalancePublic, - ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic, LedgerPtxWitness}, + ledger::{CrossZoneBundle, LedgerProofPrivate, LedgerProofPublic, LedgerBundleWitness}, }; use risc0_zkvm::{guest::env, serde}; @@ -15,82 +11,44 @@ fn main() { bundles, } = env::read(); - let old_ledger = ledger.commit(); + let old_ledger = ledger.clone(); let mut cross_bundles = vec![]; let mut outputs = vec![]; - for bundle in bundles { - let balance_public = BalancePublic { - balances: bundle.partials.iter().map(|bundle_ptx| bundle_ptx.ptx.ptx.balance).collect::>(), - }; + for LedgerBundleWitness { bundle, cm_root_proofs, nf_proofs } in bundles { + println!("IN LEDGER PROOF {:?}", bundle); + println!("BUNDLE_ID {:?}", nomos_cl_bundle_risc0_proof::BUNDLE_ID); + env::verify(nomos_cl_bundle_risc0_proof::BUNDLE_ID, &serde::to_vec(&bundle).unwrap()).unwrap(); - // 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.partials { - let ptx_outputs = process_ptx(&mut ledger, ptx, id); - 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.partials.into_iter().map(|ptx_witness| ptx_witness.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( - ledger: &mut LedgerWitness, - ptx_witness: &LedgerPtxWitness, - zone_id: ZoneId, -) -> Vec { - let ptx = &ptx_witness.ptx; - let nf_proofs = &ptx_witness.nf_proofs; - - // 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(); - - - assert_eq!(ptx.ptx.inputs.len(), nf_proofs.len()); - assert_eq!(ptx.ptx.inputs.len(), ptx.cm_mmr.len()); - - for ((input, nf_proof), cm_mmr) in ptx.ptx.inputs.iter().zip(nf_proofs).zip(ptx.cm_mmr.iter()) { - if input.zone_id != zone_id { - continue; - } - - assert_eq!(cm_mmr, &ledger.commitments); // we force commitment proofs w.r.t. latest MMR - - ledger.assert_nf_update(input.nullifier, nf_proof); - } - - let mut outputs = vec![]; - for output in &ptx.ptx.outputs { - if output.zone_id != zone_id { - continue; - } - - ledger.commitments.push(&output.note_comm.0); - outputs.push(*output); - } - - outputs -} diff --git a/emmarin/cl/ptx_risc0_proof/Cargo.toml b/emmarin/cl/ptx_risc0_proof/Cargo.toml new file mode 100644 index 0000000..96515d9 --- /dev/null +++ b/emmarin/cl/ptx_risc0_proof/Cargo.toml @@ -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"] + diff --git a/emmarin/cl/ptx_risc0_proof/build.rs b/emmarin/cl/ptx_risc0_proof/build.rs new file mode 100644 index 0000000..08a8a4e --- /dev/null +++ b/emmarin/cl/ptx_risc0_proof/build.rs @@ -0,0 +1,3 @@ +fn main() { + risc0_build::embed_methods(); +} diff --git a/emmarin/cl/risc0_proofs/ptx/Cargo.toml b/emmarin/cl/ptx_risc0_proof/ptx/Cargo.toml similarity index 100% rename from emmarin/cl/risc0_proofs/ptx/Cargo.toml rename to emmarin/cl/ptx_risc0_proof/ptx/Cargo.toml diff --git a/emmarin/cl/risc0_proofs/ptx/src/main.rs b/emmarin/cl/ptx_risc0_proof/ptx/src/main.rs similarity index 72% rename from emmarin/cl/risc0_proofs/ptx/src/main.rs rename to emmarin/cl/ptx_risc0_proof/ptx/src/main.rs index 3115eab..bbf862c 100644 --- a/emmarin/cl/risc0_proofs/ptx/src/main.rs +++ b/emmarin/cl/ptx_risc0_proof/ptx/src/main.rs @@ -1,6 +1,9 @@ /// Input Proof -use ledger_proof_statements::{constraint::ConstraintPublic, ptx::{PtxPrivate, PtxPublic}}; -use risc0_zkvm::{serde, guest::env}; +use ledger_proof_statements::{ + constraint::ConstraintPublic, + ptx::{PtxPrivate, PtxPublic}, +}; +use risc0_zkvm::{guest::env, serde}; fn main() { let PtxPrivate { @@ -12,20 +15,21 @@ fn main() { let ptx_root = ptx_commit.root(); assert_eq!(ptx.inputs.len(), input_cm_proofs.len()); - let mut cm_mmr = Vec::new(); + 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(¬e_cm.0, &mmr_proof)); - cm_mmr.push(mmr); + cm_mmrs.push(mmr); env::verify( input.note.constraint.0, &serde::to_vec(&ConstraintPublic { ptx_root, nf: input.nullifier(), - }).unwrap(), - ).unwrap(); - + }) + .unwrap(), + ) + .unwrap(); } for output in ptx.outputs.iter() { @@ -34,6 +38,6 @@ fn main() { env::commit(&PtxPublic { ptx: ptx_commit, - cm_mmr, + cm_mmrs, }); } diff --git a/emmarin/cl/ptx_risc0_proof/src/lib.rs b/emmarin/cl/ptx_risc0_proof/src/lib.rs new file mode 100644 index 0000000..1bdb308 --- /dev/null +++ b/emmarin/cl/ptx_risc0_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 index 19d562a..0c5b64d 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 = ["balance", "constraint_nop", "ptx", "stf_nop"] +methods = ["constraint_nop", "stf_nop"] diff --git a/emmarin/cl/risc0_proofs/balance/src/main.rs b/emmarin/cl/risc0_proofs/balance/src/main.rs deleted file mode 100644 index 3342001..0000000 --- a/emmarin/cl/risc0_proofs/balance/src/main.rs +++ /dev/null @@ -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); -} From 29cd911654f5b7df31cbda3969b1c8422e6f2252 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Tue, 10 Dec 2024 20:13:58 +0400 Subject: [PATCH 12/13] cr comments --- emmarin/cl/ledger/src/ledger.rs | 6 ------ emmarin/cl/ledger_validity_proof/ledger/src/main.rs | 2 -- 2 files changed, 8 deletions(-) diff --git a/emmarin/cl/ledger/src/ledger.rs b/emmarin/cl/ledger/src/ledger.rs index 1660d9d..3b06621 100644 --- a/emmarin/cl/ledger/src/ledger.rs +++ b/emmarin/cl/ledger/src/ledger.rs @@ -25,8 +25,6 @@ impl ProvedLedgerTransition { env.add_assumption(proved_bundle.risc0_receipt.clone()); let bundle = proved_bundle.public(); - println!("OUTSIDE LEDGER_PROOF {:?}", bundle); - println!("BUNDLE_ID {:?}", nomos_cl_bundle_risc0_proof::BUNDLE_ID); let zone_ledger_update = bundle .zone_ledger_updates @@ -48,10 +46,6 @@ impl ProvedLedgerTransition { nf_proofs.push(nf_proof); } - for cm in &zone_ledger_update.commitments { - ledger.add_commitment(cm); - } - let ledger_bundle = LedgerBundleWitness { bundle, cm_root_proofs, diff --git a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs index 4c3d7b2..7e2dab8 100644 --- a/emmarin/cl/ledger_validity_proof/ledger/src/main.rs +++ b/emmarin/cl/ledger_validity_proof/ledger/src/main.rs @@ -16,8 +16,6 @@ fn main() { let mut outputs = vec![]; for LedgerBundleWitness { bundle, cm_root_proofs, nf_proofs } in bundles { - println!("IN LEDGER PROOF {:?}", bundle); - println!("BUNDLE_ID {:?}", nomos_cl_bundle_risc0_proof::BUNDLE_ID); env::verify(nomos_cl_bundle_risc0_proof::BUNDLE_ID, &serde::to_vec(&bundle).unwrap()).unwrap(); if let Some(ledger_update) = bundle.zone_ledger_updates.get(&id) { From 0c312bdd35a0888a3be65eedc28c33ec57ac5985 Mon Sep 17 00:00:00 2001 From: David Rusu Date: Tue, 10 Dec 2024 21:48:18 +0400 Subject: [PATCH 13/13] rm cl::bundle --- emmarin/cl/cl/src/cl/bundle.rs | 133 --------------------------------- emmarin/cl/cl/src/cl/mod.rs | 2 - 2 files changed, 135 deletions(-) delete mode 100644 emmarin/cl/cl/src/cl/bundle.rs diff --git a/emmarin/cl/cl/src/cl/bundle.rs b/emmarin/cl/cl/src/cl/bundle.rs deleted file mode 100644 index 5609c0b..0000000 --- a/emmarin/cl/cl/src/cl/bundle.rs +++ /dev/null @@ -1,133 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::cl::partial_tx::PartialTx; - -/// 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, PartialEq, Eq, Serialize, Deserialize)] -pub struct Bundle { - pub partials: Vec, -} - -// impl Bundle { -// pub fn zones(&self) -> BTreeSet { -// self.partials -// .iter() -// .flat_map(|ptx| { -// ptx.inputs -// .iter() -// .map(|i| i.zone_id) -// .chain(ptx.outputs.iter().map(|o| o.zone_id)) -// }) -// .collect() -// } - -// // TODO: remove this -// 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![]); - } -} diff --git a/emmarin/cl/cl/src/cl/mod.rs b/emmarin/cl/cl/src/cl/mod.rs index 714e83a..31e7981 100644 --- a/emmarin/cl/cl/src/cl/mod.rs +++ b/emmarin/cl/cl/src/cl/mod.rs @@ -1,5 +1,4 @@ pub mod balance; -pub mod bundle; pub mod crypto; pub mod error; pub mod input; @@ -12,7 +11,6 @@ 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};