diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 6f3cb18..5b71bc6 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -112,6 +112,7 @@ use orchard::{ note::{Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note_encryption::OrchardDomain, primitives::redpallas::{VerificationKey, SpendAuth, Signature}, + tree::MerkleHashOrchard, value::ValueCommitment }; @@ -1259,6 +1260,94 @@ pub extern "C" fn rust_wrapper_read_sapling_position( } #[no_mangle] +pub extern "C" fn rust_wrapper_read_orchard_commitment_tree( + tree: *const u8, + tree_len: usize, + node: *const u8, + node_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 ct = read_commitment_tree::>, 32>(tree_reader); + match ct { + Ok(mut comm_tree) => { + 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()); + comm_tree.append(n); + let mut out_bytes: Vec = Vec::new(); + let result = write_commitment_tree(&comm_tree, &mut out_bytes ); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + 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); + } + }, + Err(_e) => { + 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_witness( + 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 ct: CommitmentTree = read_commitment_tree(tree_reader).unwrap(); + let inc_wit = IncrementalWitness::from_tree(ct); + let mut out_bytes: Vec = Vec::new(); + let result = write_incremental_witness(&inc_wit, &mut out_bytes); + match result { + Ok(()) => { + let h = Hhex { bytes: out_bytes}; + marshall_to_haskell_var(&h, out, out_len, RW); + }, + Err(_e) => { + 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_position( + wit: *const u8, + wit_len: usize, + ) -> u64 { + let wit_in: Vec = marshall_from_haskell_var(wit, wit_len, RW); + let wit_reader = Cursor::new(wit_in); + let iw: IncrementalWitness = read_incremental_witness(wit_reader).unwrap(); + let path = iw.path(); + match path { + Some(p) => { + let pos = p.position(); + return u64::from(pos); + }, + None => { + return 0; + } + } +} + pub extern "C" fn rust_wrapper_decode_sapling_address( sapling: *const u8, sapling_len: usize, @@ -1303,4 +1392,3 @@ pub extern "C" fn rust_wrapper_decode_sapling_address( } } } - diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 239bc3e..a4bfb9d 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -239,3 +239,23 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_read_orchard_commitment_tree as rustWrapperReadOrchardCommitmentTree + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_read_orchard_witness as rustWrapperReadOrchardWitness + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer HexString'& + } + -> `()' +#} + +{# fun pure unsafe rust_wrapper_read_orchard_position as rustWrapperReadOrchardPosition + { toBorshVar* `BS.ByteString'& + } + -> `Word64' +#} diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 8f57ad5..7e6fc6c 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -23,12 +23,15 @@ import C.Zcash , rustWrapperOrchardCheck , rustWrapperOrchardNoteDecode , rustWrapperOrchardNoteDecodeSK + , rustWrapperReadOrchardCommitmentTree + , rustWrapperReadOrchardPosition + , rustWrapperReadOrchardWitness , rustWrapperUADecode , rustWrapperUfvkDecode ) import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C -import Data.HexString (fromRawBytes, toBytes) +import Data.HexString (HexString(..), fromRawBytes, toBytes) import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word @@ -184,3 +187,35 @@ decryptOrchardActionSK sk scope oa = decodedAction = withPureBorshVarBuffer $ rustWrapperOrchardNoteDecodeSK (getBytes sk) oa (scope == External) + +-- | Update a Orchard commitment tree +updateOrchardCommitmentTree :: + OrchardCommitmentTree -- ^ the base tree + -> HexString -- ^ the new note commitment + -> Maybe OrchardCommitmentTree +updateOrchardCommitmentTree tree cmx = + if BS.length (hexBytes updatedTree) > 1 + then Just $ OrchardCommitmentTree updatedTree + else Nothing + where + updatedTree = + withPureBorshVarBuffer $ + rustWrapperReadOrchardCommitmentTree + (hexBytes $ orchTree tree) + (hexBytes cmx) + +-- | Get the Orchard incremental witness from a commitment tree +getOrchardWitness :: OrchardCommitmentTree -> Maybe OrchardWitness +getOrchardWitness tree = + if BS.length (hexBytes wit) > 1 + then Just $ OrchardWitness wit + else Nothing + where + wit = + withPureBorshVarBuffer $ + rustWrapperReadOrchardWitness (hexBytes $ orchTree tree) + +-- | Get the Sapling note position from a witness +getOrchardNotePosition :: OrchardWitness -> Integer +getOrchardNotePosition = + fromIntegral . rustWrapperReadOrchardPosition . hexBytes . orchWit diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index c6d9c34..37fa1b6 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -617,6 +617,16 @@ instance FromJSON OrchardAction where a <- obj .: "spendAuthSig" pure $ OrchardAction n r c ephKey encText outText cval a +-- | Type for a Orchard note commitment tree +newtype OrchardCommitmentTree = OrchardCommitmentTree + { orchTree :: HexString + } deriving (Eq, Prelude.Show, Read) + +-- | Type for a Sapling incremental witness +newtype OrchardWitness = OrchardWitness + { orchWit :: HexString + } deriving (Eq, Prelude.Show, Read) + -- | Type to represent a decoded note data DecodedNote = DecodedNote { a_value :: !Int64 -- ^ The amount of the transaction in _zatoshis_. diff --git a/test/Spec.hs b/test/Spec.hs index 5931df8..49e985c 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -68,7 +68,9 @@ import ZcashHaskell.Types , CoinType(..) , DecodedNote(..) , OrchardAction(..) + , OrchardCommitmentTree(..) , OrchardSpendingKey(..) + , OrchardWitness(..) , Phrase(..) , RawData(..) , RawOutPoint(..) @@ -880,6 +882,27 @@ main = do getSaplingNotePosition <$> (getSaplingWitness =<< updateSaplingCommitmentTree tree cmu1) p `shouldBe` Just 129405 + describe "Orchard commitment trees" $ do + let tree = + OrchardCommitmentTree $ + hexString + "01d5c803729654208f33d33dc68ef539ea098abc5aec215ae67c4d8aa10a14e11d01bb83047c72eb4f71813d00dee37082169546df2d7097bf7fd187ef6a93063b281f015e710ed46b53b48b12733652e150f9dcbc7e7b571cf64f294cf903864c78882f01cac32bc901f501f714a028f7ebe44c1dd8b42661be1c96730066a6fa6ede653600000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" + let cmx = + hexString + "d5c803729654208f33d33dc68ef539ea098abc5aec215ae67c4d8aa10a14e11d" + it "Commitment tree is updated correctly" $ do + let t1 = updateOrchardCommitmentTree tree cmx + t1 `shouldNotBe` Nothing + it "Incremental witness is generated" $ do + let t1 = updateOrchardCommitmentTree tree cmx + case t1 of + Nothing -> assertFailure "Failed to append node to tree" + Just t -> getOrchardWitness t `shouldNotBe` Nothing + it "Position of note is obtained" $ do + let p = + getOrchardNotePosition <$> + (getOrchardWitness =<< updateOrchardCommitmentTree tree cmx) + p `shouldBe` Just 39432 describe "Extract Sapling Address - UA Valid" $ do let sr = getSaplingFromUA