From e00cf2170994245d2a664364bd2e59e74ad3a28c Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Mon, 8 Apr 2024 12:54:05 -0500 Subject: [PATCH] Add Sapling output decrypting with spending key --- CHANGELOG.md | 4 ++ librustzcash-wrapper/Cargo.lock | 1 + librustzcash-wrapper/Cargo.toml | 1 + librustzcash-wrapper/src/lib.rs | 78 ++++++++++++++++++++++++++++++--- src/C/Zcash.chs | 10 +++++ src/ZcashHaskell/Sapling.hs | 34 +++++++++++++- 6 files changed, 120 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39c8d2a..ea99a3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.3.0] +### Added + +- Function to decode Sapling outputs with a spending key + ### Fixed - Parsing of `TxIn` for FFI diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index 812bccd..6a172d0 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -1284,6 +1284,7 @@ dependencies = [ "borsh 0.10.3", "f4jumble", "haskell-ffi", + "incrementalmerkletree", "nonempty", "orchard", "proc-macro2", diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 7abadcc..73ee84c 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -18,6 +18,7 @@ zcash_client_backend = "0.10.0" zip32 = "0.1.0" proc-macro2 = "1.0.66" nonempty = "0.7.0" +incrementalmerkletree = "0.5.0" [features] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 15c242f..623f001 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -23,6 +23,8 @@ use haskell_ffi::{ FromHaskell, HaskellSize, ToHaskell }; +use incrementalmerkletree::frontier::CommitmentTree; + use zip32; use zcash_primitives::{ @@ -51,6 +53,9 @@ use zcash_primitives::{ } }, sapling::{ + Node, + MerklePath, + NOTE_COMMITMENT_TREE_DEPTH as SAPLING_DEPTH, PaymentAddress, keys::{ PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, @@ -63,6 +68,7 @@ use zcash_primitives::{ consensus::{ BranchId::Nu5, MainNetwork, + TestNetwork, BlockHeight } }; @@ -602,7 +608,7 @@ pub extern "C" fn rust_wrapper_svk_decode( let input: Vec = marshall_from_haskell_var(input, input_len, RW); let svk = ExtendedFullViewingKey::read(&*input); match svk { - Ok(k) => { + Ok(_k) => { true } Err(e) => { @@ -698,6 +704,64 @@ pub extern "C" fn rust_wrapper_ufvk_decode( } } +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_esk_decrypt( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + external: bool, + net: bool, + out: *mut u8, + out_len: &mut usize + ){ + let sk: Vec = marshall_from_haskell_var(key, key_len, RW); + let note_input: Vec = marshall_from_haskell_var(note,note_len,RW); + let mut note_reader = Cursor::new(note_input); + let esk = ExtendedSpendingKey::from_bytes(&sk); + let main_domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(419200)); + let test_domain = SaplingDomain::for_height(TestNetwork, BlockHeight::from_u32(419200)); + let scope = if external { + SaplingScope::External + } else { + SaplingScope::Internal + }; + match esk { + Ok(k) => { + let action = OutputDescription::read(&mut note_reader); + match action { + Ok(action2) => { + let dfvk = k.to_diversifiable_full_viewing_key(); + let ivk = dfvk.to_ivk(scope); + let nk = dfvk.to_nk(scope); + let pivk = SaplingPreparedIncomingViewingKey::new(&ivk); + let result = if net { zcash_note_encryption::try_note_decryption(&main_domain, &pivk, &action2)} + else {zcash_note_encryption::try_note_decryption(&test_domain, &pivk, &action2)}; + match result { + Some((n, r, m)) => { + //let nullifier = n.nf(&nk, MerklePath.position()); + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec() }; + marshall_to_haskell_var(&hn, out, out_len, RW); + }, + None => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]}; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e1) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } + }, + Err(_e) => { + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + marshall_to_haskell_var(&hn0, out, out_len, RW); + } + } +} + #[no_mangle] pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( key: *const u8, @@ -722,11 +786,11 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action3); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec() }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_bytes().to_vec(), memo: m.as_slice().to_vec()}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -738,7 +802,7 @@ pub extern "C" fn rust_wrapper_sapling_note_decrypt_v2( } } Err(_e) => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } @@ -772,17 +836,17 @@ pub extern "C" fn rust_wrapper_orchard_note_decrypt( let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action); match result { Some((n, r, m)) => { - let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec() }; + let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec()}; marshall_to_haskell_var(&hn, out, out_len, RW); } None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } }, None => { - let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] }; + let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0]}; marshall_to_haskell_var(&hn0, out, out_len, RW); } } diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 7bf023f..c236b03 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -101,6 +101,16 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_sapling_esk_decrypt as rustWrapperSaplingDecodeEsk + { toBorshVar* `BS.ByteString'& + , toBorshVar* `BS.ByteString'& + , `Bool' + , `Bool' + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer UnifiedFullViewingKey'& diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 2dc7f49..ef287d6 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -21,6 +21,7 @@ import C.Zcash ( rustWrapperIsShielded , rustWrapperSaplingCheck , rustWrapperSaplingChgPaymentAddress + , rustWrapperSaplingDecodeEsk , rustWrapperSaplingNoteDecode , rustWrapperSaplingPaymentAddress , rustWrapperSaplingSpendingkey @@ -29,7 +30,7 @@ import C.Zcash ) import Data.Aeson import qualified Data.ByteString as BS -import Data.HexString (HexString(..), toBytes) +import Data.HexString (HexString(..), fromText, toBytes, toText) import Data.Word import Foreign.Rust.Marshall.Variable ( withPureBorshVarBuffer @@ -43,9 +44,11 @@ import ZcashHaskell.Types , RawTxResponse(..) , SaplingReceiver(..) , SaplingSpendingKey(..) + , Scope(..) , Seed(..) , ShieldedOutput(..) , ToBytes(..) + , ZcashNet(..) , decodeHexText , getValue ) @@ -58,6 +61,15 @@ isValidShieldedAddress = rustWrapperIsShielded getShieldedOutputs :: HexString -> [BS.ByteString] getShieldedOutputs t = withPureBorshVarBuffer $ rustWrapperTxParse $ toBytes t +serializeShieldedOutput :: ShieldedOutput -> BS.ByteString +serializeShieldedOutput so = + hexBytes . fromText $ + toText (s_cv so) <> + toText (s_cmu so) <> + toText (s_ephKey so) <> + toText (s_encCipherText so) <> + toText (s_outCipherText so) <> toText (s_proof so) + -- | Check if given bytestring is a valid Sapling viewing key isValidSaplingViewingKey :: BS.ByteString -> Bool isValidSaplingViewingKey k = @@ -98,6 +110,26 @@ instance FromJSON RawTxResponse where a <- o' .: "actions" pure $ RawTxResponse i h sSpend (getShieldedOutputs h) a ht c b +-- | Attempt to decode the given raw tx with the given Sapling spending key +decodeSaplingOutputEsk :: + SaplingSpendingKey + -> ShieldedOutput + -> ZcashNet + -> Scope + -> Maybe DecodedNote +decodeSaplingOutputEsk key out znet scope = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ + rustWrapperSaplingDecodeEsk + (getBytes key) + (serializeShieldedOutput out) + (znet == MainNet) + (scope == External) + -- | Attempts to obtain a sapling SpendingKey using a HDSeed genSaplingSpendingKey :: Seed -> CoinType -> Int -> Maybe SaplingSpendingKey genSaplingSpendingKey seed c i = do