From deacf373e117b9eb3093846e8d676848e66a6d15 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sat, 13 Apr 2024 18:27:59 -0400 Subject: [PATCH 1/3] rvv040 - Add new function to decode a Transparent Address in HRF The function retunrs a TransparentAddress object. --- CHANGELOG.md | 7 ++++ src/ZcashHaskell/Orchard.hs | 10 +++--- src/ZcashHaskell/Transparent.hs | 60 ++++++++++++++++++++++++++------- src/ZcashHaskell/Types.hs | 14 +++++--- test/Spec.hs | 13 ++++--- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80e6446..8247aeb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ 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.0] + +- Added unction to decode Transparent Address in Human Readable Format +- TransparentAddress type refactored +- TransparentReceiver added to replace old TransparentAddress +- sha256 Functionmoved outside of encodeTransparentReceiver + ## [0.5.4.0] - Function to decode Orchard actions with a spending key diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 76bbad2..8f57ad5 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -95,9 +95,9 @@ isValidUnifiedAddress str = then Just $ SaplingReceiver (raw_s x) else Nothing) (if not (BS.null (raw_t x)) - then Just $ TransparentAddress P2PKH (fromRawBytes $ raw_t x) + then Just $ TransparentReceiver P2PKH (fromRawBytes $ raw_t x) else if not (BS.null (raw_to x)) - then Just $ TransparentAddress P2SH (fromRawBytes $ raw_to x) + then Just $ TransparentReceiver P2SH (fromRawBytes $ raw_to x) else Nothing) -- | Encode a 'UnifiedAddress' per [ZIP-316](https://zips.z.cash/zip-0316) @@ -113,9 +113,9 @@ encodeUnifiedAddress ua = encodeBech32m (E.encodeUtf8 hr) b case t_rec ua of Nothing -> BS.empty Just t -> - case ta_type t of - P2SH -> packReceiver 0x01 $ Just $ toBytes $ ta_bytes t - P2PKH -> packReceiver 0x00 $ Just $ toBytes $ ta_bytes t + case tr_type t of + P2SH -> packReceiver 0x01 $ Just $ toBytes $ tr_bytes t + P2PKH -> packReceiver 0x00 $ Just $ toBytes $ tr_bytes t sReceiver = packReceiver 0x02 $ getBytes <$> s_rec ua oReceiver = packReceiver 0x03 $ getBytes <$> o_rec ua padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 140bdbb..5e49ad8 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -20,11 +20,12 @@ import Crypto.Hash import Crypto.Secp256k1 import qualified Data.ByteArray as BA import qualified Data.ByteString as BS -import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58) +import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58, decodeBase58) import Data.HexString import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word +import Data.Char (chr) import Haskoin.Address (Address(..)) import qualified Haskoin.Crypto.Hash as H import Haskoin.Crypto.Keys.Extended @@ -34,6 +35,7 @@ import ZcashHaskell.Types , Scope(..) , Seed(..) , ToBytes(..) + , TransparentReceiver(..) , TransparentAddress(..) , TransparentSpendingKey(..) , TransparentType(..) @@ -42,21 +44,23 @@ import ZcashHaskell.Types , getValue ) --- | Encodes a `TransparentAddress` into the human-readable format per the Zcash Protocol section 5.6.1.1 -encodeTransparent :: +-- | Required for `TransparentReceiver` encoding and decoding +sha256 :: BS.ByteString -> BS.ByteString +sha256 bs = BA.convert (hash bs :: Digest SHA256) + +-- | Encodes a `TransparentReceiver` into the human-readable format per the Zcash Protocol section 5.6.1.1 +encodeTransparentReceiver :: ZcashNet -- ^ The network, `MainNet` or `TestNet` - -> TransparentAddress -- ^ The address to encode + -> TransparentReceiver -- ^ The address to encode -> T.Text -encodeTransparent zNet t = - encodeTransparent' (getTransparentPrefix zNet (ta_type t)) $ - toBytes $ ta_bytes t +encodeTransparentReceiver zNet t = + encodeTransparent' (getTransparentPrefix zNet (tr_type t)) $ + toBytes $ tr_bytes t where encodeTransparent' :: (Word8, Word8) -> BS.ByteString -> T.Text encodeTransparent' (a, b) h = E.decodeUtf8 $ encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum where - sha256 :: BS.ByteString -> BS.ByteString - sha256 bs = BA.convert (hash bs :: Digest SHA256) digest = BS.pack [a, b] <> h checksum = sha256 $ sha256 digest @@ -78,7 +82,7 @@ genTransparentReceiver :: Int -- ^ The index of the address to be created -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses -> XPrvKey -- ^ The transparent private key - -> IO TransparentAddress + -> IO TransparentReceiver genTransparentReceiver i scope xprvk = do ioCtx <- createContext let s = @@ -90,6 +94,38 @@ genTransparentReceiver i scope xprvk = do let childPubKey = deriveXPubKey ioCtx childPrvKey let x = xPubAddr ioCtx childPubKey case x of - PubKeyAddress k -> return $ TransparentAddress P2PKH $ fromBinary k - ScriptAddress j -> return $ TransparentAddress P2SH $ fromBinary j + PubKeyAddress k -> return $ TransparentReceiver P2PKH $ fromBinary k + ScriptAddress j -> return $ TransparentReceiver P2SH $ fromBinary j _anyOtherKind -> throwIO $ userError "Unsupported transparent address type" + +-- } decode a Transparent Address in HRF and return a TransparentAddress object +decodeTransparentAddress :: BS.ByteString -> Maybe TransparentAddress +decodeTransparentAddress taddress = do + if BS.length taddress < 34 + then Nothing -- Not a valid transparent address + else do + let maybeDecoded = decodeBase58 bitcoinAlphabet taddress + case maybeDecoded of + Nothing -> Nothing + Just decoded -> do + let digest = BS.take 22 decoded + let chksum = BS.drop 22 decoded + let chksumd = BS.take 4 (sha256 $ sha256 digest) + if chksum /= chksum + then Nothing -- Invalid address ( invalid checksum ) + else do + -- build the TransparentAddress Object + let addressType = BS.take 2 digest + let transparentReceiver = BS.drop 2 digest + let fb = BS.index addressType 0 + let sb = BS.index addressType 1 + case fb of + 28 -> case sb of + 189 -> Just $ TransparentAddress MainNet $ TransparentReceiver P2SH (fromRawBytes digest) + 186 -> Just $ TransparentAddress TestNet $ TransparentReceiver P2SH (fromRawBytes digest) + 184 -> Just $ TransparentAddress MainNet $ TransparentReceiver P2PKH (fromRawBytes digest) + _ -> Nothing + 29 -> if sb == 37 + then Just $ TransparentAddress TestNet $ TransparentReceiver P2PKH (fromRawBytes digest) + else Nothing + _ -> Nothing diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 0320479..8d310bc 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -94,7 +94,7 @@ data ZcashNet type AccountId = Int --- | Function to get the Base58 prefix for encoding a 'TransparentAddress' +-- | Function to get the Base58 prefix for encoding a 'TransparentReceiver' getTransparentPrefix :: ZcashNet -> TransparentType -> (Word8, Word8) getTransparentPrefix n t = case t of @@ -422,10 +422,16 @@ data TransparentType -- | Type for transparent spending key type TransparentSpendingKey = XPrvKey +-- | Type to represent a transparent Zcash addresses +data TransparentReceiver = TransparentReceiver + { tr_type :: !TransparentType + , tr_bytes :: !HexString + } deriving (Eq, Prelude.Show, Read) + -- | Type to represent a transparent Zcash addresses data TransparentAddress = TransparentAddress - { ta_type :: !TransparentType - , ta_bytes :: !HexString + { ta_network :: !ZcashNet + , ta_receiver :: !TransparentReceiver } deriving (Eq, Prelude.Show, Read) -- | Wrapper types for transparent elements @@ -541,7 +547,7 @@ data UnifiedAddress = UnifiedAddress { ua_net :: !ZcashNet , o_rec :: !(Maybe OrchardReceiver) , s_rec :: !(Maybe SaplingReceiver) - , t_rec :: !(Maybe TransparentAddress) + , t_rec :: !(Maybe TransparentReceiver) } deriving (Prelude.Show, Eq, Read) -- | Helper type for marshalling UAs diff --git a/test/Spec.hs b/test/Spec.hs index 2333a69..5abff0a 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -77,6 +77,7 @@ import ZcashHaskell.Types , Seed(..) , ShieldedOutput(..) , ToBytes(..) + , TransparentReceiver(..) , TransparentAddress(..) , TransparentBundle(..) , TransparentType(..) @@ -513,7 +514,7 @@ main = do case isValidUnifiedAddress ua of Nothing -> "Bad UA" Just u -> - maybe "No transparent" (encodeTransparent (ua_net u)) $ + maybe "No transparent" (encodeTransparentReceiver (ua_net u)) $ t_rec u msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" it "Recover UA from YWallet" $ @@ -807,7 +808,7 @@ main = do BS.drop 3 $ (\(TxOut v s) -> s) (head (tb_vout myTb')) pkHash `shouldBe` - maybe "" (hexBytes . ta_bytes) (t_rec addr) + maybe "" (hexBytes . tr_bytes) (t_rec addr) myTb `shouldNotBe` Nothing it "Sapling component is read" $ do case t of @@ -856,7 +857,11 @@ main = do let sr = getSaplingFromUA "u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" it "Try to extract sapling address from invalid UA" $ do sr `shouldBe` Nothing - + describe "Decode a Transparent Address" $ do + let ta = decodeTransparentAddress "t1dMjvesbzdG41xgKaGU3HgwYJwSgbCK54e" + it "Try to decode a valid Transparent Address" $ do + print ta + ta `shouldNotBe` Nothing -- | Properties prop_PhraseLength :: Property @@ -930,7 +935,7 @@ prop_TransparentReceiver s coinType scope (NonNegative i) (NonNegative j) = ioProperty $ do k <- genTransparentPrvKey s coinType i r <- genTransparentReceiver j scope k - return $ ta_type r == P2PKH + return $ tr_type r == P2PKH -- | Generators genOrcArgs :: Gen (CoinType, Int, Int) From e781ed6bd06a4fa463ed3b7bddc0563680380bb2 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Sun, 14 Apr 2024 06:57:36 -0500 Subject: [PATCH 2/3] Add test to for transparent decoding --- CHANGELOG.md | 11 +++++-- src/ZcashHaskell/Transparent.hs | 55 ++++++++++++++++++++------------- test/Spec.hs | 35 ++++++++++++++++----- zcash-haskell.cabal | 2 +- 4 files changed, 70 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8247aeb..fa7d323 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.5.5.0] +### Added + - Added unction to decode Transparent Address in Human Readable Format -- TransparentAddress type refactored -- TransparentReceiver added to replace old TransparentAddress -- sha256 Functionmoved outside of encodeTransparentReceiver + +### Changed + +- `TransparentAddress` type refactored +- `TransparentReceiver` added to replace old `TransparentAddress` +- `sha256` Function moved outside of `encodeTransparentReceiver` ## [0.5.4.0] diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 5e49ad8..6689c08 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -20,12 +20,12 @@ import Crypto.Hash import Crypto.Secp256k1 import qualified Data.ByteArray as BA import qualified Data.ByteString as BS -import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58, decodeBase58) +import Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58, encodeBase58) +import Data.Char (chr) import Data.HexString import qualified Data.Text as T import qualified Data.Text.Encoding as E import Data.Word -import Data.Char (chr) import Haskoin.Address (Address(..)) import qualified Haskoin.Crypto.Hash as H import Haskoin.Crypto.Keys.Extended @@ -35,8 +35,8 @@ import ZcashHaskell.Types , Scope(..) , Seed(..) , ToBytes(..) - , TransparentReceiver(..) , TransparentAddress(..) + , TransparentReceiver(..) , TransparentSpendingKey(..) , TransparentType(..) , ZcashNet(..) @@ -101,31 +101,44 @@ genTransparentReceiver i scope xprvk = do -- } decode a Transparent Address in HRF and return a TransparentAddress object decodeTransparentAddress :: BS.ByteString -> Maybe TransparentAddress decodeTransparentAddress taddress = do - if BS.length taddress < 34 + if BS.length taddress < 34 then Nothing -- Not a valid transparent address - else do - let maybeDecoded = decodeBase58 bitcoinAlphabet taddress - case maybeDecoded of - Nothing -> Nothing - Just decoded -> do + else do + let maybeDecoded = decodeBase58 bitcoinAlphabet taddress + case maybeDecoded of + Nothing -> Nothing + Just decoded -> do let digest = BS.take 22 decoded let chksum = BS.drop 22 decoded let chksumd = BS.take 4 (sha256 $ sha256 digest) - if chksum /= chksum + if chksum /= chksumd then Nothing -- Invalid address ( invalid checksum ) - else do -- build the TransparentAddress Object + else do let addressType = BS.take 2 digest let transparentReceiver = BS.drop 2 digest let fb = BS.index addressType 0 - let sb = BS.index addressType 1 + let sb = BS.index addressType 1 case fb of - 28 -> case sb of - 189 -> Just $ TransparentAddress MainNet $ TransparentReceiver P2SH (fromRawBytes digest) - 186 -> Just $ TransparentAddress TestNet $ TransparentReceiver P2SH (fromRawBytes digest) - 184 -> Just $ TransparentAddress MainNet $ TransparentReceiver P2PKH (fromRawBytes digest) - _ -> Nothing - 29 -> if sb == 37 - then Just $ TransparentAddress TestNet $ TransparentReceiver P2PKH (fromRawBytes digest) - else Nothing - _ -> Nothing + 28 -> + case sb of + 189 -> + Just $ + TransparentAddress MainNet $ + TransparentReceiver P2SH (fromRawBytes digest) + 186 -> + Just $ + TransparentAddress TestNet $ + TransparentReceiver P2SH (fromRawBytes digest) + 184 -> + Just $ + TransparentAddress MainNet $ + TransparentReceiver P2PKH (fromRawBytes digest) + _ -> Nothing + 29 -> + if sb == 37 + then Just $ + TransparentAddress TestNet $ + TransparentReceiver P2PKH (fromRawBytes digest) + else Nothing + _ -> Nothing diff --git a/test/Spec.hs b/test/Spec.hs index 5abff0a..60625e0 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -77,9 +77,9 @@ import ZcashHaskell.Types , Seed(..) , ShieldedOutput(..) , ToBytes(..) - , TransparentReceiver(..) , TransparentAddress(..) , TransparentBundle(..) + , TransparentReceiver(..) , TransparentType(..) , UnifiedAddress(..) , UnifiedFullViewingKey(..) @@ -846,22 +846,41 @@ main = do print tb show tb `shouldNotBe` "" describe "Extract Sapling Address - UA Valid" $ do - let sr = getSaplingFromUA "u14a5c4ufn9feqvxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" + let sr = + getSaplingFromUA + "u14a5c4ufn9feqvxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" it "Extract sapling address" $ do - case sr of - Nothing -> assertFailure "UA invalid or does not contain a Sapling receiver" + case sr of + Nothing -> + assertFailure "UA invalid or does not contain a Sapling receiver" Just t -> do - print t - t `shouldBe` "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" + print t + t `shouldBe` + "zs1waxrpde36rlrjdwfhnvw030sn29lzwmvmeupd8x2uuqgypaafx7mqcy0ep8yf2xtg30n5424t60" describe "Extract Sapling Address - UA Invalid" $ do - let sr = getSaplingFromUA "u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" + let sr = + getSaplingFromUA + "u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" it "Try to extract sapling address from invalid UA" $ do - sr `shouldBe` Nothing + sr `shouldBe` Nothing describe "Decode a Transparent Address" $ do let ta = decodeTransparentAddress "t1dMjvesbzdG41xgKaGU3HgwYJwSgbCK54e" it "Try to decode a valid Transparent Address" $ do print ta ta `shouldNotBe` Nothing + it "Encode and decode should be the same" $ do + let ua = + "u17n7hpwaujyq7ux8f9jpyymtnk5urw7pyrf60smp5mawy7jgz325hfvz3jn3zsfya8yxryf9q7ldk8nu8df0emra5wne28zq9d9nm2pu4x6qwjha565av9aze0xgujgslz74ufkj0c0cylqwjyrh9msjfh7jzal6d3qzrnhkkqy3pqm8j63y07jxj7txqeac982778rmt64f32aum94x" + case isValidUnifiedAddress ua of + Nothing -> assertFailure "Bad UA" + Just u -> do + let tAdd = + maybe + "No transparent" + (encodeTransparentReceiver (ua_net u)) $ + t_rec u + (ta_receiver <$> decodeTransparentAddress (E.encodeUtf8 tAdd)) `shouldBe` + t_rec u -- | Properties prop_PhraseLength :: Property diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index 2e0cae2..8d16f53 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.5.4.0 +version: 0.5.5.0 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain From 15b2f98f1dc9dddbfb056a92fc5f5189cb7f72ce Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sun, 14 Apr 2024 08:37:19 -0400 Subject: [PATCH 3/3] rvv040 - Fix transparent address decoding - Function was returning the "digest" instead the "transparentReceiver" --- src/ZcashHaskell/Transparent.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 6689c08..366dfe4 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -125,20 +125,20 @@ decodeTransparentAddress taddress = do 189 -> Just $ TransparentAddress MainNet $ - TransparentReceiver P2SH (fromRawBytes digest) + TransparentReceiver P2SH (fromRawBytes transparentReceiver) 186 -> Just $ TransparentAddress TestNet $ - TransparentReceiver P2SH (fromRawBytes digest) + TransparentReceiver P2SH (fromRawBytes transparentReceiver) 184 -> Just $ TransparentAddress MainNet $ - TransparentReceiver P2PKH (fromRawBytes digest) + TransparentReceiver P2PKH (fromRawBytes transparentReceiver) _ -> Nothing 29 -> if sb == 37 then Just $ TransparentAddress TestNet $ - TransparentReceiver P2PKH (fromRawBytes digest) + TransparentReceiver P2PKH (fromRawBytes transparentReceiver) else Nothing _ -> Nothing