Add new function to decode a Transparent Address in HRF #59
6 changed files with 111 additions and 25 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
25
test/Spec.hs
25
test/Spec.hs
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue