From 5ce149c54f8e7e53e455536f30dd27a35d05c036 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 5 Nov 2024 18:28:45 +0000 Subject: [PATCH 1/2] Functions to create and manage Orchard commitment trees (#99) This PR contains the functions to create, update and validate Orchard commitment trees. Reviewed-on: https://git.vergara.tech///Vergara_Tech/zcash-haskell/pulls/99 Co-authored-by: Rene Vergara Co-committed-by: Rene Vergara --- librustzcash-wrapper/src/lib.rs | 188 ++++++++++++++++++++++++++++++-- src/C/Zcash.chs | 33 +++++- src/ZcashHaskell/Orchard.hs | 40 ++++++- src/ZcashHaskell/Types.hs | 23 ++++ 4 files changed, 273 insertions(+), 11 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index a469cb8..7657829 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -36,7 +36,8 @@ use incrementalmerkletree::{ frontier::{ CommitmentTree, Frontier, - NonEmptyFrontier + NonEmptyFrontier, + PathFiller }, witness::IncrementalWitness }; @@ -271,6 +272,12 @@ impl ToHaskell for Hhex { } } +impl FromHaskell for Hhex { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hhex::deserialize(buf)?; + Ok(x) + } +} #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct Haction { @@ -716,6 +723,47 @@ impl FromHaskell for Hfrontier { } } +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Htree { + left: Hhex, + right: Hhex, + parents: Vec +} + +impl ToHaskell for Htree { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl FromHaskell for Htree { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Htree::deserialize(buf)?; + Ok(x) + } +} + +#[derive(BorshSerialize, BorshDeserialize)] +pub struct Hpath { + position: u32, + path: Vec +} + +impl ToHaskell for Hpath { + fn to_haskell(&self, writer: &mut W, _tag: PhantomData) -> Result<()> { + self.serialize(writer)?; + Ok(()) + } +} + +impl FromHaskell for Hpath { + fn from_haskell(buf: &mut &[u8], _tag: PhantomData) -> Result { + let x = Hpath::deserialize(buf)?; + Ok(x) + } +} + fn to_array(v: Vec) -> [T; N] { v.try_into().unwrap_or_else(|v: Vec| panic!("Expected a Vec of length {} but it was {}", N, v.len())) } @@ -1513,7 +1561,13 @@ pub extern "C" fn rust_wrapper_read_orchard_frontier( Some(f2) => { let (pos, leaf, omm) = f2.clone().into_parts(); let f = Hfrontier { position: ::from(pos), leaf: Hhex { bytes: leaf.to_bytes().to_vec()}, ommers: omm.iter().map(|&x| Hhex { bytes: x.to_bytes().to_vec()}).collect()}; - marshall_to_haskell_var(&f, out, out_len, RW); + let comm_tree2: Frontier = Frontier::from_parts(Position::from(f.position), MerkleHashOrchard::from_bytes(&to_array(f.leaf.bytes.clone())).unwrap(), f.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); + if f1.root() == comm_tree2.root() { + marshall_to_haskell_var(&f, out, out_len, RW); + } else { + let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; + marshall_to_haskell_var(&f0, out, out_len, RW); + } }, None => { let f0 = Hfrontier { position: 0, leaf: Hhex { bytes: vec![0]}, ommers: vec![Hhex { bytes: vec![0]}]}; @@ -1535,12 +1589,66 @@ pub extern "C" fn rust_wrapper_read_orchard_tree_anchor( out: *mut u8, out_len: &mut usize ){ - let tree_in: Hfrontier = marshall_from_haskell_var(tree, tree_len, RW); - let leaf = MerkleHashOrchard::from_bytes(&to_array(tree_in.leaf.bytes)).unwrap(); - let comm_tree: NonEmptyFrontier = NonEmptyFrontier::from_parts(Position::from(tree_in.position), leaf, tree_in.ommers.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap() ).collect()).unwrap(); - let root = comm_tree.root(None); - let h = Hhex { bytes: root.to_bytes().to_vec() }; - marshall_to_haskell_var(&h, out, out_len, RW); + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let root = c1.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_) => { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_commitment_tree_parts( + tree: *const u8, + tree_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let tree_in: Vec = marshall_from_haskell_var(tree, tree_len, RW); + let tree_reader = Cursor::new(tree_in); + let comm_tree = read_commitment_tree(tree_reader); + match comm_tree { + Ok::, _>(c1) => { + let left = match c1.left() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let right = match c1.right() { + Some(x) => { + Hhex { bytes: x.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }; + let parents = c1.parents().iter().map(|x| match x { + Some(y) => { + Hhex { bytes: y.to_bytes().to_vec() } + }, + None => { + Hhex { bytes: vec![0] } + } + }).collect(); + let ht = Htree { left, right, parents}; + marshall_to_haskell_var(&ht, out, out_len, RW); + }, + Err(_e) => { + let ht0 = Htree { left: Hhex { bytes: vec![0] } , right: Hhex { bytes: vec![0] }, parents: vec![Hhex { bytes: vec![0] }]}; + marshall_to_haskell_var(&ht0, out, out_len, RW); + } + } } #[no_mangle] @@ -1577,6 +1685,29 @@ pub extern "C" fn rust_wrapper_read_orchard_node( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_path_anchor( + path: *const u8, + path_len: usize, + cmx: *const u8, + cmx_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW); + let cmx_in: Vec = marshall_from_haskell_var(cmx, cmx_len, RW); + let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()).collect())); + let nc = ExtractedNoteCommitment::from_bytes(&to_array(cmx_in)); + if nc.is_some().into() { + let anchor = mk_path.root(nc.unwrap()); + let h = Hhex { bytes: anchor.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_combine_orchard_nodes( level: u8, @@ -1618,6 +1749,47 @@ pub extern "C" fn rust_wrapper_combine_orchard_nodes( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_get_orchard_root( + level: u8, + out: *mut u8, + out_len: &mut usize + ){ + let tree: CommitmentTree = CommitmentTree::empty(); + let root = tree.root(); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_orchard_add_node( + level: u8, + node: *const u8, + node_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let mut tree: CommitmentTree = CommitmentTree::empty(); + let node_in: Vec = marshall_from_haskell_var(node, node_len, RW); + let orchard_note_comm = ExtractedNoteCommitment::from_bytes(&to_array(node_in)); + if orchard_note_comm.is_some().into() { + let n = MerkleHashOrchard::from_cmx(&orchard_note_comm.unwrap()); + match tree.append(n) { + Ok(()) => { + let root = tree.root_at_depth(level, PathFiller::empty()); + let h = Hhex { bytes: root.to_bytes().to_vec() }; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_) => { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } + } + } else { + let h0 = Hhex { bytes: vec![0] }; + marshall_to_haskell_var(&h0, out, out_len, RW); + } +} #[no_mangle] pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index da2f86b..a896a10 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -263,7 +263,7 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_read_orchard_tree_anchor as rustWrapperReadOrchardTreeAnchor - { toBorshVar* `OrchardFrontier'& + { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer HexString'& } -> `()' @@ -276,6 +276,21 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_path_anchor as rustWrapperReadOrchardPathAnchor + { toBorshVar* `MerklePath'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_get_orchard_root as rustWrapperGetOrchardRootTest + { `Int8' + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree { toBorshVar* `OrchardFrontier'& , toBorshVar* `BS.ByteString'& @@ -284,6 +299,13 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_commitment_tree_parts as rustWrapperReadOrchardTreeParts + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer OrchardRawTree'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_read_orchard_frontier as rustWrapperReadOrchardFrontier { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer OrchardFrontier'& @@ -304,6 +326,15 @@ import ZcashHaskell.Types -> `Word64' #} +{# fun unsafe rust_wrapper_orchard_add_node as rustWrapperOrchardAddNodeTest + { `Int8' + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' + +#} + {# fun unsafe rust_wrapper_update_sapling_witness as rustWrapperUpdateSaplingWitness { toBorshVar* `BS.ByteString'& , toBorshVar* `[BS.ByteString]'& diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 79043ea..7991138 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -21,14 +21,18 @@ import C.Zcash ( rustWrapperCombineOrchardNodes , rustWrapperGenOrchardReceiver , rustWrapperGenOrchardSpendKey + , rustWrapperGetOrchardRootTest + , rustWrapperOrchardAddNodeTest , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode , rustWrapperOrchardNoteDecodeSK , rustWrapperReadOrchardCommitmentTree , rustWrapperReadOrchardFrontier , rustWrapperReadOrchardNode + , rustWrapperReadOrchardPathAnchor , rustWrapperReadOrchardPosition , rustWrapperReadOrchardTreeAnchor + , rustWrapperReadOrchardTreeParts , rustWrapperReadOrchardWitness , rustWrapperReadOrchardWitnessAnchor , rustWrapperUADecode @@ -209,15 +213,47 @@ getOrchardFrontier tree = withPureBorshVarBuffer $ rustWrapperReadOrchardFrontier $ toBytes $ orchTree tree -getOrchardTreeAnchor :: OrchardFrontier -> HexString +getOrchardTreeAnchor :: OrchardCommitmentTree -> HexString getOrchardTreeAnchor tree = - withPureBorshVarBuffer $ rustWrapperReadOrchardTreeAnchor tree + withPureBorshVarBuffer $ + rustWrapperReadOrchardTreeAnchor $ toBytes $ orchTree tree getOrchardWitnessAnchor :: OrchardWitness -> HexString getOrchardWitnessAnchor wit = withPureBorshVarBuffer $ rustWrapperReadOrchardWitnessAnchor $ toBytes $ orchWit wit +getOrchardRootTest :: Int -> HexString +getOrchardRootTest level = + withPureBorshVarBuffer $ rustWrapperGetOrchardRootTest $ fromIntegral level + +addOrchardNodeGetRoot :: Int -> BS.ByteString -> HexString +addOrchardNodeGetRoot l n = + withPureBorshVarBuffer $ rustWrapperOrchardAddNodeTest (fromIntegral l) n + +getOrchardTreeParts :: OrchardCommitmentTree -> Maybe OrchardTree +getOrchardTreeParts h = + if isBlank (ort_left tree) && isBlank (ort_right tree) + then Nothing + else Just $ + OrchardTree + (parseHex $ ort_left tree) + (parseHex $ ort_right tree) + (map parseHex (ort_parents tree)) + where + isBlank h = (BS.length $ hexBytes $ h) == 1 + parseHex h = + if (BS.length $ hexBytes $ h) > 1 + then Just h + else Nothing + tree = + withPureBorshVarBuffer $ + rustWrapperReadOrchardTreeParts $ toBytes $ orchTree h + +getOrchardPathAnchor :: HexString -> MerklePath -> HexString +getOrchardPathAnchor hex p = + withPureBorshVarBuffer $ rustWrapperReadOrchardPathAnchor p (hexBytes hex) + -- | Update a Orchard commitment tree updateOrchardCommitmentTree :: OrchardFrontier -- ^ the base tree diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 324216f..8b8a90b 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -722,11 +722,34 @@ instance FromJSON OrchardAction where a <- obj .: "spendAuthSig" pure $ OrchardAction n r c ephKey encText outText cval a +data MerklePath = MerklePath + { mp_position :: !Int32 + , mp_path :: ![HexString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct MerklePath + -- | Type for a Orchard note commitment tree newtype OrchardCommitmentTree = OrchardCommitmentTree { orchTree :: HexString } deriving (Eq, Prelude.Show, Read) +data OrchardRawTree = OrchardRawTree + { ort_left :: !HexString + , ort_right :: !HexString + , ort_parents :: ![HexString] + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardRawTree + +data OrchardTree = OrchardTree + { ot_left :: !(Maybe HexString) + , ot_right :: !(Maybe HexString) + , ot_parents :: ![Maybe HexString] + } deriving (Eq, Prelude.Show, Read) + data OrchardFrontier = OrchardFrontier { of_pos :: !Int64 , of_leaf :: !HexString From 62cda9cc15621dead6fbfd7a4944840408d69da4 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 5 Nov 2024 12:42:03 -0600 Subject: [PATCH 2/2] docs: version bump --- CHANGELOG.md | 6 ++++++ zcash-haskell.cabal | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f3a7e4..3cbfed3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.7.4.0] + +### Added + +- `MerklePath` + ## [0.7.3.0] ### Added diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 36ce2ae..3216af8 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -5,7 +5,7 @@ cabal-version: 3.0 -- see: https://github.com/sol/hpack name: zcash-haskell -version: 0.7.3.0 +version: 0.7.4.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain