From 382284a8ca3a67eca8f029091f336e82e0d0a373 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Tue, 16 Apr 2024 14:39:56 -0400 Subject: [PATCH 1/3] rvv040 - decodeSaplingAddress Function added SaplingAddress data type added --- CHANGELOG.md | 16 ++++++++++- librustzcash-wrapper/src/lib.rs | 48 +++++++++++++++++++++++++++++++++ src/C/Zcash.chs | 7 +++++ src/ZcashHaskell/Sapling.hs | 25 +++++++++++++++++ src/ZcashHaskell/Types.hs | 6 +++++ test/Spec.hs | 13 +++++++++ 6 files changed, 114 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8ae53c..9f35ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ 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.5.5.1] + +### Added + +- Added unction to decode a Sappling Address in Human Readable Format + `decodeSaplingAddress` returns 43 byte array containing +- Added a new Datatype `SaplingAddress` +- Added a new FFI function `rust_wrapper_decode_sapling_address` to haskell-rust interface + +### Changed + +- `TransparentAddress` type refactored +- `TransparentReceiver` added to replace old `TransparentAddress` +- `sha256` Function moved outside of `encodeTransparentReceiver` + ## [0.5.5.0] ### Added @@ -16,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `TransparentAddress` type refactored - `TransparentReceiver` added to replace old `TransparentAddress` - `sha256` Function moved outside of `encodeTransparentReceiver` - ## [0.5.4.1] ### Added diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 81f7235..fb89971 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -90,6 +90,7 @@ use zcash_address::{ ZcashAddress }; +use zcash_client_backend::encoding::decode_payment_address; use zcash_client_backend::keys::sapling::{ spending_key, ExtendedFullViewingKey, @@ -1242,3 +1243,50 @@ pub extern "C" fn rust_wrapper_read_sapling_position( } } } + +#[no_mangle] +pub extern "C" fn rust_wrapper_decode_sapling_address( + sapling: *const u8, + sapling_len: usize, + out: *mut u8, + out_len: &mut usize){ + let sapling_address_from_haskell : Vec = marshall_from_haskell_var(sapling, sapling_len, RW); + let sapling_address = std::str::from_utf8(&sapling_address_from_haskell).unwrap(); + + let mut netid = 0; + match sapling_address.find("1") { + Some(ix) => { + let netstr = &sapling_address[0..ix]; + if netstr == "zs" { + netid = 1 + } else { + if netstr == "ztestsapling" { + netid = 2 + } + } + match decode_payment_address(netstr, sapling_address) { + Ok( t )=> { + let address_to_bytes = t.to_bytes(); + let mut out_bytes_temp : [u8;44] = [0;44]; + out_bytes_temp[0] = netid; + let mut iy = 1; + for ix in 0..43 { + out_bytes_temp[iy] = address_to_bytes[ix]; + iy += 1; + } + let out_bytes: Vec = out_bytes_temp.to_vec(); + marshall_to_haskell_var(&out_bytes, out, out_len, RW); + } + Err(e) => { + let h = vec![0]; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } + } + None => { + let h = vec![0]; + marshall_to_haskell_var(&h, out, out_len, RW); + } + } +} + diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 4b08f78..239bc3e 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -232,3 +232,10 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_decode_sapling_address as rustWrapperDecodeSaplingAddress + { toBorshVar* `BS.ByteString'& + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index a108eaa..d80abf8 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -22,6 +22,7 @@ import C.Zcash , rustWrapperReadSaplingCommitmentTree , rustWrapperReadSaplingPosition , rustWrapperReadSaplingWitness + , rustWrapperDecodeSaplingAddress , rustWrapperSaplingCheck , rustWrapperSaplingChgPaymentAddress , rustWrapperSaplingDecodeEsk @@ -45,6 +46,7 @@ import ZcashHaskell.Types , DecodedNote(..) , RawData(..) , RawTxResponse(..) + , SaplingAddress(..) , SaplingCommitmentTree(..) , SaplingReceiver(..) , SaplingSpendingKey(..) @@ -205,3 +207,26 @@ getSaplingWitness tree = getSaplingNotePosition :: SaplingWitness -> Integer getSaplingNotePosition = fromIntegral . rustWrapperReadSaplingPosition . hexBytes . sapWit + +getNetId:: [Word8] -> ZcashNet +getNetId [x] = do + case x of + 1 -> MainNet + 2 -> TestNet + + +-- | decode a Sapling address +decodeSaplingAddress :: BS.ByteString -> Maybe SaplingAddress +decodeSaplingAddress sapling_address = do + if BS.length sa > 1 + then do + let sa0 = BS.unpack sa + Just $ SaplingAddress (getNetId (take 1 sa0)) (BS.pack (drop 1 sa0)) + else Nothing + where + sa = + withPureBorshVarBuffer $ + rustWrapperDecodeSaplingAddress sapling_address + + + diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index c6cff22..5bc66f4 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -477,6 +477,12 @@ newtype SaplingReceiver = instance ToBytes SaplingReceiver where getBytes (SaplingReceiver s) = s +data SaplingAddress = SaplingAddress + { + net_type :: !ZcashNet + , address :: !BS.ByteString + } deriving (Eq, Prelude.Show, Read) + -- | Type to represent a Sapling Shielded Spend as provided by the @getrawtransaction@ RPC method data ShieldedSpend = ShieldedSpend { sp_cv :: !HexString diff --git a/test/Spec.hs b/test/Spec.hs index 3f66d1d..0f10226 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -57,6 +57,7 @@ import ZcashHaskell.Sapling , isValidShieldedAddress , matchSaplingAddress , updateSaplingCommitmentTree + , decodeSaplingAddress ) import ZcashHaskell.Transparent import ZcashHaskell.Types @@ -94,6 +95,7 @@ import ZcashHaskell.Types , fromRawSBundle , fromRawTBundle , getValue + , SaplingAddress(..) ) import ZcashHaskell.Utils @@ -911,6 +913,17 @@ main = do t_rec u (ta_receiver <$> decodeTransparentAddress (E.encodeUtf8 tAdd)) `shouldBe` t_rec u + describe "Decode a Sapling Address (MainNet)" $ do + let sa = decodeSaplingAddress "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" + it "Try to decode a valid MainNet Sapling Address" $ do + print sa + sa `shouldNotBe` Nothing + describe "Decode a Sapling Address (TestNet)" $ do + let sa = decodeSaplingAddress "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" + it "Try to decode a valid TestNet Sapling Address " $ do + print sa + sa `shouldBe` Nothing + -- | Properties prop_PhraseLength :: Property From 058bbfe3f29f154b237d55bf0e1572cc63b83b49 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Tue, 16 Apr 2024 14:48:36 -0400 Subject: [PATCH 2/3] rvv040 - Added a decode Testnet Sapling Address test --- test/Spec.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Spec.hs b/test/Spec.hs index 0f10226..37a45c5 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -922,7 +922,7 @@ main = do let sa = decodeSaplingAddress "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" it "Try to decode a valid TestNet Sapling Address " $ do print sa - sa `shouldBe` Nothing + sa `shouldNotBe` Nothing -- | Properties From 06aff8c7879bceed47393b4ca3a23354b3937d7d Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Tue, 16 Apr 2024 18:51:14 -0400 Subject: [PATCH 3/3] rvv040 - Add encodeSaplingAddress function Added new tests for encodeSaplingAddress and decodeSaplingAddress --- CHANGELOG.md | 10 +++++++ src/ZcashHaskell/Sapling.hs | 60 +++++++++++++++++++++---------------- src/ZcashHaskell/Types.hs | 4 +-- test/Spec.hs | 24 +++++++++++---- 4 files changed, 66 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f35ce1..ecf9c65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ 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.5.5.2] + +### Added + +- Added unction to encode a Sappling Address in Human Readable Format Using a SaplingReceiver + `encodeSaplingAddress` a zcash sapling address is returned or Nothing if the function fails +- Added decoding and encoding test + + + ## [0.5.5.1] ### Added diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index d80abf8..2b971a2 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -34,32 +34,35 @@ import C.Zcash ) import Data.Aeson import qualified Data.ByteString as BS +import qualified Data.ByteString.Char8 as C +import qualified Data.Text as T import Data.HexString (HexString(..), fromText, hexString, toBytes, toText) import Data.Word import Foreign.Rust.Marshall.Variable ( withPureBorshVarBuffer , withPureBorshVarBuffer ) -import ZcashHaskell.Types - ( AccountId - , CoinType - , DecodedNote(..) - , RawData(..) - , RawTxResponse(..) - , SaplingAddress(..) - , SaplingCommitmentTree(..) - , SaplingReceiver(..) - , SaplingSpendingKey(..) - , SaplingWitness(..) - , Scope(..) - , Seed(..) - , ShieldedOutput(..) - , ToBytes(..) - , ZcashNet(..) - , decodeHexText - , getValue - ) -import ZcashHaskell.Utils (decodeBech32) +import ZcashHaskell.Types +--import ZcashHaskell.Types +-- ( AccountId +-- , CoinType +-- , DecodedNote(..) +-- , RawData(..) +-- , RawTxResponse(..) +-- , SaplingAddress(..) +-- , SaplingCommitmentTree(..) +-- , SaplingReceiver(..) +-- , SaplingSpendingKey(..) +-- , SaplingWitness(..) +-- , Scope(..) +-- , Seed(..) +-- , ShieldedOutput(..) +-- , ToBytes(..) +-- , ZcashNet(..) +-- , decodeHexText +-- , getValue +-- ) +import ZcashHaskell.Utils (decodeBech32, encodeBech32, encodeBech32m) -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool @@ -208,25 +211,32 @@ getSaplingNotePosition :: SaplingWitness -> Integer getSaplingNotePosition = fromIntegral . rustWrapperReadSaplingPosition . hexBytes . sapWit +-- | Encode a SaplingReceiver into HRF text +encodeSaplingAddress :: ZcashNet -> SaplingReceiver -> Maybe T.Text +encodeSaplingAddress net sr = do + case net of + MainNet -> + Just $ encodeBech32 (C.pack sapPaymentAddressHrp) (getBytes sr) + TestNet -> + Just $ encodeBech32 (C.pack sapTestPaymentAddressHrp) (getBytes sr) + +-- | Helper to get de Nework Id from FFI response getNetId:: [Word8] -> ZcashNet getNetId [x] = do case x of 1 -> MainNet 2 -> TestNet - -- | decode a Sapling address decodeSaplingAddress :: BS.ByteString -> Maybe SaplingAddress decodeSaplingAddress sapling_address = do if BS.length sa > 1 then do let sa0 = BS.unpack sa - Just $ SaplingAddress (getNetId (take 1 sa0)) (BS.pack (drop 1 sa0)) + Just $ SaplingAddress (getNetId (take 1 sa0)) + $ SaplingReceiver (BS.pack (drop 1 sa0)) else Nothing where sa = withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address - - - diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 5bc66f4..08da042 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -480,9 +480,9 @@ instance ToBytes SaplingReceiver where data SaplingAddress = SaplingAddress { net_type :: !ZcashNet - , address :: !BS.ByteString + , sa_receiver :: !SaplingReceiver } deriving (Eq, Prelude.Show, Read) - + -- | Type to represent a Sapling Shielded Spend as provided by the @getrawtransaction@ RPC method data ShieldedSpend = ShieldedSpend { sp_cv :: !HexString diff --git a/test/Spec.hs b/test/Spec.hs index 37a45c5..4f5fd1e 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -58,6 +58,7 @@ import ZcashHaskell.Sapling , matchSaplingAddress , updateSaplingCommitmentTree , decodeSaplingAddress + , encodeSaplingAddress ) import ZcashHaskell.Transparent import ZcashHaskell.Types @@ -916,14 +917,27 @@ main = do describe "Decode a Sapling Address (MainNet)" $ do let sa = decodeSaplingAddress "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" it "Try to decode a valid MainNet Sapling Address" $ do - print sa - sa `shouldNotBe` Nothing + case sa of + Nothing -> assertFailure "Failed to decode MainNet SaplingAddress" + Just s -> do -- Sapling address decoded succesfully + let sh = encodeSaplingAddress (net_type s) (sa_receiver s) + case sh of + Nothing -> assertFailure "Failed to encode MainNet SaplingAddress" + Just zsh -> do + print zsh + zsh `shouldBe` "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" describe "Decode a Sapling Address (TestNet)" $ do let sa = decodeSaplingAddress "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" it "Try to decode a valid TestNet Sapling Address " $ do - print sa - sa `shouldNotBe` Nothing - + case sa of + Nothing -> assertFailure "Failed to decode TestNet SaplingAddress" + Just s -> do -- Sapling address decoded succesfully + let sh = encodeSaplingAddress (net_type s) (sa_receiver s) + case sh of + Nothing -> assertFailure "Failed to encode TestNet SaplingAddress" + Just zsh -> do + print zsh + zsh `shouldBe` "ztestsapling188csdsvhdny25am8ume03qr2026hdy03zpg5pq7jmmfxtxtct0e93p0rg80yfxvynqd4gwlwft5" -- | Properties prop_PhraseLength :: Property