From 1d8e3729a87e49b78f73e23ae81d4e3fdcb5f693 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 22 Aug 2023 15:05:40 -0500 Subject: [PATCH] Implement Sapling tx decoding --- librustzcash-wrapper/src/lib.rs | 78 ++++++++++++++++++++------------- src/C/Zcash.chs | 12 ++++- src/ZcashHaskell/Orchard.hs | 2 +- src/ZcashHaskell/Sapling.hs | 13 ++++++ src/ZcashHaskell/Types.hs | 6 +-- test/Spec.hs | 27 ++++++++++-- 6 files changed, 98 insertions(+), 40 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 6be1288..620a206 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -19,13 +19,17 @@ use haskell_ffi::{ use zcash_primitives::{ zip32::Scope as SaplingScope, transaction::components::sapling::{ + read_zkproof, GrothProofBytes, OutputDescription, CompactOutputDescription }, sapling::{ value::ValueCommitment as SaplingValueCommitment, - keys::FullViewingKey as SaplingViewingKey, + keys::{ + FullViewingKey as SaplingViewingKey, + PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey + }, note_encryption::SaplingDomain, PaymentAddress, note::ExtractedNoteCommitment as SaplingExtractedNoteCommitment @@ -311,35 +315,49 @@ pub extern "C" fn rust_wrapper_ufvk_decode( } } -//#[no_mangle] -//pub extern "C" fn rust_wrapper_sapling_note_decrypt( - //key: *const u8, - //key_len: usize, - //note: *const u8, - //note_len: usize, - //out: *mut u8, - //out_len: &mut usize - //){ - //let evk: Vec = marshall_from_haskell_var(key, key_len, RW); - //let note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); - //let svk = ExtendedFullViewingKey::read(&*evk); - //match svk { - //Ok(k) => { - //let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000)); - //let action: CompactOutputDescription = CompactOutputDescription { - //ephemeral_key: EphemeralKeyBytes(to_array(note_input.eph_key)), - //cmu: SaplingExtractedNoteCommitment::from_bytes(&to_array(note_input.cmu)).unwrap(), - //enc_ciphertext: to_array(note_input.enc_txt) - //}; - //let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External); - //let result = zcash_note_encryption::try_note_decryption(&domain, &ivk, &action); - //} - //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( + key: *const u8, + key_len: usize, + note: *const u8, + note_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + let evk: Vec = marshall_from_haskell_var(key, key_len, RW); + let note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW); + let svk = ExtendedFullViewingKey::read(&*evk); + match svk { + Ok(k) => { + let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000)); + let mut action_bytes = vec![0]; + action_bytes.extend(¬e_input.cv); + action_bytes.extend(¬e_input.cmu); + action_bytes.extend(¬e_input.eph_key); + action_bytes.extend(¬e_input.enc_txt); + action_bytes.extend(¬e_input.out_txt); + action_bytes.extend(¬e_input.proof); + let action2 = OutputDescription::read(&mut action_bytes.as_slice()).unwrap(); + let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External); + let pivk = SaplingPreparedIncomingViewingKey::new(&fvk); + let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action2); + 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() }; + 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(_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_orchard_note_decrypt( diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 1a85cf9..3f195fe 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -72,6 +72,14 @@ import ZcashHaskell.Types -> `Bool' #} +{# fun unsafe rust_wrapper_sapling_note_decrypt as rustWrapperSaplingNoteDecode + { toBorshVar* `BS.ByteString'& + , toBorshVar* `ShieldedOutput'& + , getVarBuffer `Buffer DecodedNote'& + } + -> `()' +#} + {# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer UnifiedFullViewingKey'& @@ -82,7 +90,7 @@ import ZcashHaskell.Types {# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode { toBorshVar* `BS.ByteString'& , toBorshVar* `OrchardAction'& - , getVarBuffer `Buffer OrchardDecodedAction'& + , getVarBuffer `Buffer DecodedNote'& } -> `()' - #} +#} diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index d6bbb56..2cc3b95 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -35,7 +35,7 @@ decodeUfvk str = -- | Attempts to decode the given @OrchardAction@ using the given @UnifiedFullViewingKey@. decryptOrchardAction :: - OrchardAction -> UnifiedFullViewingKey -> Maybe OrchardDecodedAction + OrchardAction -> UnifiedFullViewingKey -> Maybe DecodedNote decryptOrchardAction encAction key = case a_value decodedAction of 0 -> Nothing diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index ed6a81d..e0016bd 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -3,9 +3,12 @@ module ZcashHaskell.Sapling where import C.Zcash ( rustWrapperIsShielded , rustWrapperSaplingCheck + , rustWrapperSaplingNoteDecode , rustWrapperSaplingVkDecode ) import qualified Data.ByteString as BS +import Foreign.Rust.Marshall.Variable (withPureBorshVarBuffer) +import ZcashHaskell.Types (DecodedNote(..), ShieldedOutput) -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool @@ -18,3 +21,13 @@ isValidSaplingViewingKey = rustWrapperSaplingVkDecode -- | Check if the given bytestring for the Sapling viewing key matches the second bytestring for the address matchSaplingAddress :: BS.ByteString -> BS.ByteString -> Bool matchSaplingAddress = rustWrapperSaplingCheck + +-- | Attempt to decode the given Sapling raw output with the given Sapling viewing key +decodeSaplingOutput :: BS.ByteString -> ShieldedOutput -> Maybe DecodedNote +decodeSaplingOutput key out = + case a_value decodedAction of + 0 -> Nothing + _ -> Just decodedAction + where + decodedAction = + withPureBorshVarBuffer $ rustWrapperSaplingNoteDecode key out diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index f03eaab..233a991 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -153,15 +153,15 @@ instance FromJSON OrchardAction where (decodeHexText cval) (decodeHexText a) --- | Type to represent a decoded Orchard Action -data OrchardDecodedAction = OrchardDecodedAction +-- | Type to represent a decoded note +data DecodedNote = DecodedNote { a_value :: Int64 -- ^ The amount of the transaction in _zatoshis_. , a_recipient :: BS.ByteString -- ^ The recipient Orchard receiver. , a_memo :: BS.ByteString -- ^ The decoded shielded memo field. } deriving stock (Eq, Prelude.Show, GHC.Generic) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (Data.Structured.Show) - deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardDecodedAction + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote -- * Helpers -- | Helper function to turn a hex-encoded string to bytestring diff --git a/test/Spec.hs b/test/Spec.hs index a9999b9..f9f7ecc 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -11,17 +11,18 @@ import Data.Word import Test.Hspec import ZcashHaskell.Orchard import ZcashHaskell.Sapling - ( isValidSaplingViewingKey + ( decodeSaplingOutput + , isValidSaplingViewingKey , isValidShieldedAddress , matchSaplingAddress ) import ZcashHaskell.Types ( BlockResponse(..) + , DecodedNote(..) , OrchardAction(..) - , OrchardDecodedAction(..) , RawData(..) , RawTxResponse(..) - , ShieldedOutput(s_cmu) + , ShieldedOutput(ShieldedOutput, s_cmu) , UnifiedFullViewingKey(..) , decodeHexText ) @@ -313,7 +314,25 @@ main = do describe "Decode Sapling tx" $ do let svk = "zxviews1qvapd723qqqqpqq09ldgykvyusthmkky2w062esx5xg3nz4m29qxcvndyx6grrhrdepu4ns88sjr3u6mfp2hhwj5hfd6y24r0f64uwq65vjrmsh9mr568kenk33fcumag6djcjywkm5v295egjuk3qdd47atprs0j33nhaaqep3uqspzp5kg4mthugvug0sc3gc83atkrgmguw9g7gkvh82tugrntf66lnvyeh6ufh4j2xt0xr2r4zujtm3qvrmd3vvnulycuwqtetg2jk384" - it "succeeds with correct key" pending + it "succeeds with correct key" $ do + let rawKey = decodeBech32 svk + let rawNote = + ShieldedOutput + (decodeHexText + "a2853a37316dfc32301dfcae2583797a6dab0aaba8b08e9bd052d2d65bc66c14") + (decodeHexText + "65dc83588d7fa63510391505b2b57455d8740a29bce1ad20f798a647b4163a5a") + (decodeHexText + "3c9748817d696cf1e540d0ffa759092740e23c2b07415d326f9d007ba1a43bea") + (decodeHexText + "62ffe037fc83aded21e4c91722b52520a2395c23e9c1a896f4b0f12d32ae8e31833d9d95adae40f6ecf7aff52af184efd390a4c1aa76b5fb1cab6003b1a8a004016f385926661f56d38273ec2c3df7775210310a65fff5fa9ac5509f0784eefea28bdcc67b0ff69eef930335f3b9768529e2bfe733024492101f642f989de8cbf04dd66638e9317780bce47085079675b772664c8007e96597dba83ea9af22ddf07ff1c212983d4a902914431245357527294e69ea5616e720ef1e9215bbfa33ba108b8d07efff2bad1850525d7725c681761c9b8c844a5548afabf176863de7b4cde3901defc3e83d31086d3c6e6af9a5fcc3cfb38b52ac7de84f91df5e0587f7603773401a62eeef10cd3ccf4d927ef42402c32f32280abbeaac33e73ceda52089820a186e9a1adfea81453998c6bbaa0deb41bc4f94586bfee80bad25fc71abe7c6dd44bcb1a6929a0112c7e4f8fcadb9745bde9422b954f72954c4d22db48719de61f383d620935b647337f73d119d79fd208e1d5a92f0855447df5782cd4764ba91efa65d9e4ebaa34e2eccb7aac93a5b0efe0c7664f3cd9384b3ff706ad3019c907cdcfa084351c9f6a0bfa8c78c91272ca66ac86dd6e1d0d6ba9704ea7dc54f71a053dce91f844c1ca62b5ddfe6b53834f4a816b1b01460810d9b87517659f4915adf4b84783a60ecf3bd71569259f1ff90a91a0b314bd4c77976d7893bf42e5d6ad0f8df95eb6d6c69d41490be8e39b2452df3bebfc297d5b0fc97f081890390fb0727a96898585f0120a7da9a798f2032590553f724d8756c67c5b0d1c0d233") + (decodeHexText + "01c4ed60fa283994fd712aab17ca6360256fd5aef0ebc48f0256e3eda5894b53981d0d46768aefdc85b48c1525b7f134dce5d4ec2d76c03c821513f1652d9671219d744bdce5e69b9a74ca0c7c837668") + (decodeHexText + "9534b3d594e1609b3bace18608750b35a066c57f85e291d194400cb351430bbbe212abba32be071e747b7310863bd5fd989855a6567a351b288144b6e9f838c6a517db94673246ef0010b65f9c0be8aca654f6f57b83d893663cfd389ab96ce50e8077fe588c16b1b5989c6cc262e6658efb9b88ac800e49e9e5999e2651b8fff28fa77071d63790df155ed8344e2581ac5205b31d4f17bd748fcf60e35a9d6048d23c94c7aca8d4e541fda497aa268df9c173af5877a5da56d8fa2a42166900") + print rawNote + let a = decodeSaplingOutput (bytes rawKey) rawNote + maybe 0 a_value a `shouldNotBe` 0 describe "Decode Orchard tx" $ do let uvk = "uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"