Add new function to decode a Transparent Address in HRF #59

Merged
pitmutt merged 4 commits from rvv040 into dev040 2024-04-14 14:27:19 +00:00
6 changed files with 111 additions and 25 deletions

View file

@ -5,6 +5,18 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.5.0]
### Added
- Added unction to decode Transparent Address in Human Readable Format
### Changed
- `TransparentAddress` type refactored
- `TransparentReceiver` added to replace old `TransparentAddress`
- `sha256` Function moved outside of `encodeTransparentReceiver`
## [0.5.4.1] ## [0.5.4.1]
### Added ### Added

View file

@ -95,9 +95,9 @@ isValidUnifiedAddress str =
then Just $ SaplingReceiver (raw_s x) then Just $ SaplingReceiver (raw_s x)
else Nothing) else Nothing)
(if not (BS.null (raw_t x)) (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)) 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) else Nothing)
-- | Encode a 'UnifiedAddress' per [ZIP-316](https://zips.z.cash/zip-0316) -- | 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 case t_rec ua of
Nothing -> BS.empty Nothing -> BS.empty
Just t -> Just t ->
case ta_type t of case tr_type t of
P2SH -> packReceiver 0x01 $ Just $ toBytes $ ta_bytes t P2SH -> packReceiver 0x01 $ Just $ toBytes $ tr_bytes t
P2PKH -> packReceiver 0x00 $ Just $ toBytes $ ta_bytes t P2PKH -> packReceiver 0x00 $ Just $ toBytes $ tr_bytes t
sReceiver = packReceiver 0x02 $ getBytes <$> s_rec ua sReceiver = packReceiver 0x02 $ getBytes <$> s_rec ua
oReceiver = packReceiver 0x03 $ getBytes <$> o_rec ua oReceiver = packReceiver 0x03 $ getBytes <$> o_rec ua
padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr

View file

@ -20,7 +20,8 @@ import Crypto.Hash
import Crypto.Secp256k1 import Crypto.Secp256k1
import qualified Data.ByteArray as BA import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58) import Data.ByteString.Base58 (bitcoinAlphabet, decodeBase58, encodeBase58)
import Data.Char (chr)
import Data.HexString import Data.HexString
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
@ -35,6 +36,7 @@ import ZcashHaskell.Types
, Seed(..) , Seed(..)
, ToBytes(..) , ToBytes(..)
, TransparentAddress(..) , TransparentAddress(..)
, TransparentReceiver(..)
, TransparentSpendingKey(..) , TransparentSpendingKey(..)
, TransparentType(..) , TransparentType(..)
, ZcashNet(..) , ZcashNet(..)
@ -42,21 +44,23 @@ import ZcashHaskell.Types
, getValue , getValue
) )
-- | Encodes a `TransparentAddress` into the human-readable format per the Zcash Protocol section 5.6.1.1 -- | Required for `TransparentReceiver` encoding and decoding
encodeTransparent :: 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` ZcashNet -- ^ The network, `MainNet` or `TestNet`
-> TransparentAddress -- ^ The address to encode -> TransparentReceiver -- ^ The address to encode
-> T.Text -> T.Text
encodeTransparent zNet t = encodeTransparentReceiver zNet t =
encodeTransparent' (getTransparentPrefix zNet (ta_type t)) $ encodeTransparent' (getTransparentPrefix zNet (tr_type t)) $
toBytes $ ta_bytes t toBytes $ tr_bytes t
where where
encodeTransparent' :: (Word8, Word8) -> BS.ByteString -> T.Text encodeTransparent' :: (Word8, Word8) -> BS.ByteString -> T.Text
encodeTransparent' (a, b) h = encodeTransparent' (a, b) h =
E.decodeUtf8 $ encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum E.decodeUtf8 $ encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum
where where
sha256 :: BS.ByteString -> BS.ByteString
sha256 bs = BA.convert (hash bs :: Digest SHA256)
digest = BS.pack [a, b] <> h digest = BS.pack [a, b] <> h
checksum = sha256 $ sha256 digest checksum = sha256 $ sha256 digest
@ -78,7 +82,7 @@ genTransparentReceiver ::
Int -- ^ The index of the address to be created Int -- ^ The index of the address to be created
-> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses -> Scope -- ^ `External` for wallet addresses or `Internal` for change addresses
-> XPrvKey -- ^ The transparent private key -> XPrvKey -- ^ The transparent private key
-> IO TransparentAddress -> IO TransparentReceiver
genTransparentReceiver i scope xprvk = do genTransparentReceiver i scope xprvk = do
ioCtx <- createContext ioCtx <- createContext
let s = let s =
@ -90,6 +94,51 @@ genTransparentReceiver i scope xprvk = do
let childPubKey = deriveXPubKey ioCtx childPrvKey let childPubKey = deriveXPubKey ioCtx childPrvKey
let x = xPubAddr ioCtx childPubKey let x = xPubAddr ioCtx childPubKey
case x of case x of
PubKeyAddress k -> return $ TransparentAddress P2PKH $ fromBinary k PubKeyAddress k -> return $ TransparentReceiver P2PKH $ fromBinary k
ScriptAddress j -> return $ TransparentAddress P2SH $ fromBinary j ScriptAddress j -> return $ TransparentReceiver P2SH $ fromBinary j
_anyOtherKind -> throwIO $ userError "Unsupported transparent address type" _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 /= chksumd
then Nothing -- Invalid address ( invalid checksum )
-- 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
case fb of
28 ->
case sb of
189 ->
Just $
TransparentAddress MainNet $
TransparentReceiver P2SH (fromRawBytes transparentReceiver)
186 ->
Just $
TransparentAddress TestNet $
TransparentReceiver P2SH (fromRawBytes transparentReceiver)
184 ->
Just $
TransparentAddress MainNet $
TransparentReceiver P2PKH (fromRawBytes transparentReceiver)
_ -> Nothing
29 ->
if sb == 37
then Just $
TransparentAddress TestNet $
TransparentReceiver P2PKH (fromRawBytes transparentReceiver)
else Nothing
_ -> Nothing

View file

@ -94,7 +94,7 @@ data ZcashNet
type AccountId = Int 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 :: ZcashNet -> TransparentType -> (Word8, Word8)
getTransparentPrefix n t = getTransparentPrefix n t =
case t of case t of
@ -422,10 +422,16 @@ data TransparentType
-- | Type for transparent spending key -- | Type for transparent spending key
type TransparentSpendingKey = XPrvKey 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 -- | Type to represent a transparent Zcash addresses
data TransparentAddress = TransparentAddress data TransparentAddress = TransparentAddress
{ ta_type :: !TransparentType { ta_network :: !ZcashNet
, ta_bytes :: !HexString , ta_receiver :: !TransparentReceiver
} deriving (Eq, Prelude.Show, Read) } deriving (Eq, Prelude.Show, Read)
-- | Wrapper types for transparent elements -- | Wrapper types for transparent elements
@ -551,7 +557,7 @@ data UnifiedAddress = UnifiedAddress
{ ua_net :: !ZcashNet { ua_net :: !ZcashNet
, o_rec :: !(Maybe OrchardReceiver) , o_rec :: !(Maybe OrchardReceiver)
, s_rec :: !(Maybe SaplingReceiver) , s_rec :: !(Maybe SaplingReceiver)
, t_rec :: !(Maybe TransparentAddress) , t_rec :: !(Maybe TransparentReceiver)
} deriving (Prelude.Show, Eq, Read) } deriving (Prelude.Show, Eq, Read)
-- | Helper type for marshalling UAs -- | Helper type for marshalling UAs

View file

@ -83,6 +83,7 @@ import ZcashHaskell.Types
, ToBytes(..) , ToBytes(..)
, TransparentAddress(..) , TransparentAddress(..)
, TransparentBundle(..) , TransparentBundle(..)
, TransparentReceiver(..)
, TransparentType(..) , TransparentType(..)
, UnifiedAddress(..) , UnifiedAddress(..)
, UnifiedFullViewingKey(..) , UnifiedFullViewingKey(..)
@ -517,7 +518,7 @@ main = do
case isValidUnifiedAddress ua of case isValidUnifiedAddress ua of
Nothing -> "Bad UA" Nothing -> "Bad UA"
Just u -> Just u ->
maybe "No transparent" (encodeTransparent (ua_net u)) $ maybe "No transparent" (encodeTransparentReceiver (ua_net u)) $
t_rec u t_rec u
msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD"
it "Recover UA from YWallet" $ it "Recover UA from YWallet" $
@ -811,7 +812,7 @@ main = do
BS.drop 3 $ BS.drop 3 $
(\(TxOut v s) -> s) (head (tb_vout myTb')) (\(TxOut v s) -> s) (head (tb_vout myTb'))
pkHash `shouldBe` pkHash `shouldBe`
maybe "" (hexBytes . ta_bytes) (t_rec addr) maybe "" (hexBytes . tr_bytes) (t_rec addr)
myTb `shouldNotBe` Nothing myTb `shouldNotBe` Nothing
it "Sapling component is read" $ do it "Sapling component is read" $ do
case t of case t of
@ -892,6 +893,24 @@ main = do
"u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l" "u14a5c4ufn9qfevxssnvscep29j5cse4gjpg0w3w5vjhafn74hg9k73xgnxqv6m255n23weggr6j97c8kdwvn4pkz7rz6my52z8248gjmr7knlw536tcurs5km7knqnzez4cywudt3q6shr553hurduvljfeqvfzgegenfjashslkz3y4ykhxel6mrjp9gsm9xk7k6kdxn9y84kccmv8l"
it "Try to extract sapling address from invalid UA" $ do 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 -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property
@ -965,7 +984,7 @@ prop_TransparentReceiver s coinType scope (NonNegative i) (NonNegative j) =
ioProperty $ do ioProperty $ do
k <- genTransparentPrvKey s coinType i k <- genTransparentPrvKey s coinType i
r <- genTransparentReceiver j scope k r <- genTransparentReceiver j scope k
return $ ta_type r == P2PKH return $ tr_type r == P2PKH
-- | Generators -- | Generators
genOrcArgs :: Gen (CoinType, Int, Int) genOrcArgs :: Gen (CoinType, Int, Int)

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.5.4.1 version: 0.5.5.0
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme> description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain category: Blockchain