Merge pull request 'Complete Unified Address generation' (#37) from rav001 into dev040

Reviewed-on: #37
This commit is contained in:
pitmutt 2024-03-15 16:31:50 +00:00 committed by Vergara Technologies LLC
commit 6e86f2caf0
Signed by: Vergara Technologies LLC
GPG key ID: 99DB473BB4715618
9 changed files with 325 additions and 38 deletions

View file

@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Types for Spending Keys and Receivers for Sapling and Orchard - Types for Spending Keys and Receivers for Sapling and Orchard
- Function to generate an Orchard receiver - Function to generate an Orchard receiver
- Function to generate a Sapling receiver - Function to generate a Sapling receiver
- Function to generate a Transparent receiver
### Changed ### Changed

View file

@ -61,7 +61,8 @@ use zcash_address::{
use zcash_client_backend::keys::sapling::{ use zcash_client_backend::keys::sapling::{
spending_key, spending_key,
ExtendedFullViewingKey, ExtendedFullViewingKey,
ExtendedSpendingKey ExtendedSpendingKey,
DiversifiableFullViewingKey
}; };
use zcash_primitives::zip32::DiversifierIndex; use zcash_primitives::zip32::DiversifierIndex;
@ -676,6 +677,29 @@ pub extern "C" fn rust_wrapper_sapling_paymentaddress(
} }
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_sapling_chgpaymentaddress(
extspk: *const u8,
extspk_len: usize,
out: *mut u8,
out_len: &mut usize
){
let vexspk: Vec<u8> = marshall_from_haskell_var(extspk, extspk_len, RW);
let vexspkp = &vexspk;
let extspku8 : &[u8] = &vexspkp;
let extspk = match ExtendedSpendingKey::from_bytes(&extspku8) {
Ok( k ) => k,
Err( e ) => {
// error recovering ExtendedSpendingKey
marshall_to_haskell_var(&vec![0], out, out_len, RW);
return
}
};
let dfvk = extspk.to_diversifiable_full_viewing_key();
let ( divIx, cPmtAddress ) = dfvk.change_address();
marshall_to_haskell_var(&cPmtAddress.to_bytes().to_vec(), out, out_len, RW);
}
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_derive_orchard_spending_key( pub extern "C" fn rust_wrapper_derive_orchard_spending_key(
seed: *const u8, seed: *const u8,

View file

@ -151,6 +151,13 @@ import ZcashHaskell.Types
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_sapling_chgpaymentaddress as rustWrapperSaplingChgPaymentAddress
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (BS.ByteString)'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_derive_orchard_spending_key as rustWrapperGenOrchardSpendKey {# fun unsafe rust_wrapper_derive_orchard_spending_key as rustWrapperGenOrchardSpendKey
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, `Word32' , `Word32'

View file

@ -37,7 +37,10 @@ import ZcashHaskell.Utils (encodeBech32m, f4Jumble)
-- | Derives an Orchard spending key for the given seed and account ID -- | Derives an Orchard spending key for the given seed and account ID
genOrchardSpendingKey :: genOrchardSpendingKey ::
Seed -> CoinType -> AccountId -> Maybe OrchardSpendingKey Seed -- ^ The cryptographic seed for the wallet
-> CoinType -- ^ The coin type constant
-> AccountId -- ^ The index of the account to be used
-> Maybe OrchardSpendingKey
genOrchardSpendingKey s coinType accountId = genOrchardSpendingKey s coinType accountId =
if BS.length k /= 32 if BS.length k /= 32
then Nothing then Nothing
@ -52,7 +55,10 @@ genOrchardSpendingKey s coinType accountId =
-- | Derives an Orchard receiver for the given spending key and index -- | Derives an Orchard receiver for the given spending key and index
genOrchardReceiver :: genOrchardReceiver ::
Int -> Scope -> OrchardSpendingKey -> Maybe OrchardReceiver Int -- ^ The index of the address to be created
-> Scope -- ^ `External` for wallet addresses, `Internal` for change addresses
-> OrchardSpendingKey -- ^ The spending key
-> Maybe OrchardReceiver
genOrchardReceiver i scope osk = genOrchardReceiver i scope osk =
if BS.length k /= 43 if BS.length k /= 43
then Nothing then Nothing

View file

@ -20,6 +20,7 @@ module ZcashHaskell.Sapling where
import C.Zcash import C.Zcash
( rustWrapperIsShielded ( rustWrapperIsShielded
, rustWrapperSaplingCheck , rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress
, rustWrapperSaplingNoteDecode , rustWrapperSaplingNoteDecode
, rustWrapperSaplingPaymentAddress , rustWrapperSaplingPaymentAddress
, rustWrapperSaplingSpendingkey , rustWrapperSaplingSpendingkey
@ -124,4 +125,10 @@ genSaplingPaymentAddress i extspk =
-- | Generate an internal Sapling address -- | Generate an internal Sapling address
genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingReceiver
genSaplingInternalAddress sk = undefined genSaplingInternalAddress sk =
if BS.length res > 0
then Just $ SaplingReceiver res
else Nothing
where
res =
withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk)

View file

@ -17,29 +17,35 @@ module ZcashHaskell.Transparent where
import Control.Exception (throwIO) import Control.Exception (throwIO)
import Crypto.Hash import Crypto.Hash
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, encodeBase58)
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
import Data.Word
import Haskoin.Address (Address(..))
import qualified Haskoin.Crypto.Hash as H
import Haskoin.Crypto.Keys.Extended
import ZcashHaskell.Types import ZcashHaskell.Types
( AccountId ( AccountId
, CoinType(..)
, Scope(..)
, Seed(..) , Seed(..)
, ToBytes(..) , ToBytes(..)
, TransparentAddress(..) , TransparentAddress(..)
, TransparentType(..) , TransparentType(..)
, ZcashNet(..) , ZcashNet(..)
, getTransparentPrefix , getTransparentPrefix
, getValue
) )
import Crypto.Secp256k1 -- | Encodes a `TransparentAddress` into the human-readable format per the Zcash Protocol section 5.6.1.1
import Data.HexString encodeTransparent ::
import Data.Word ZcashNet -- ^ The network, `MainNet` or `TestNet`
import Haskoin.Address (Address(..)) -> TransparentAddress -- ^ The address to encode
import qualified Haskoin.Crypto.Hash as H -> T.Text
import Haskoin.Crypto.Keys.Extended
encodeTransparent :: ZcashNet -> TransparentAddress -> T.Text
encodeTransparent zNet t = encodeTransparent zNet t =
encodeTransparent' (getTransparentPrefix zNet (ta_type t)) $ encodeTransparent' (getTransparentPrefix zNet (ta_type t)) $
toBytes $ ta_bytes t toBytes $ ta_bytes t
@ -53,19 +59,34 @@ encodeTransparent zNet t =
digest = BS.pack [a, b] <> h digest = BS.pack [a, b] <> h
checksum = sha256 $ sha256 digest checksum = sha256 $ sha256 digest
-- | Attempts to generate an Extended Private Key from a known HDSeed. -- | Generate an Extended Private Key from a known HDSeed.
genTransparentPrvKey :: Seed -> AccountId -> IO XPrvKey genTransparentPrvKey ::
genTransparentPrvKey hdseed i = do Seed -- ^ The cryptographic seed of the wallet
let prvKey = makeXPrvKey $ getBytes hdseed -> CoinType -- ^ The coin type constant to be used
-> AccountId -- ^ The index of the account to be used
-> IO XPrvKey
genTransparentPrvKey hdseed ctype accid = do
let coin = getValue ctype
ioCtx <- createContext ioCtx <- createContext
return $ hardSubKey ioCtx prvKey (fromIntegral i) let path = Deriv :| 44 :| coin :| fromIntegral accid :: DerivPath
let prvKey = makeXPrvKey $ getBytes hdseed
return $ derivePath ioCtx path prvKey
-- | Generate a transparent receiver -- | Generate a transparent receiver
genTransparentReceiver :: Int -> XPrvKey -> IO TransparentAddress genTransparentReceiver ::
genTransparentReceiver i xprvk = do 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
genTransparentReceiver i scope xprvk = do
ioCtx <- createContext ioCtx <- createContext
let rootPubKey = deriveXPubKey ioCtx xprvk let s =
let childPubKey = pubSubKey ioCtx rootPubKey (fromIntegral i) case scope of
External -> 0
Internal -> 1
let path = Deriv :/ s :/ fromIntegral i :: DerivPath
let childPrvKey = derivePath ioCtx path xprvk
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 $ TransparentAddress P2PKH $ fromBinary k

View file

@ -65,8 +65,8 @@ instance ToBytes Phrase where
-- | Scope for addresses/receivers -- | Scope for addresses/receivers
data Scope data Scope
= External = External -- ^ Addresses used publically to receive payments
| Internal | Internal -- ^ Addresses used internally by wallets for change and shielding
deriving (Eq, Prelude.Show, Read) deriving (Eq, Prelude.Show, Read)
-- | Type to represent data after Bech32 decoding -- | Type to represent data after Bech32 decoding

View file

@ -33,7 +33,6 @@ import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
import qualified Data.Text.Lazy.Encoding as LE import qualified Data.Text.Lazy.Encoding as LE
import qualified Data.Text.Lazy.IO as LTIO import qualified Data.Text.Lazy.IO as LTIO
import GHC.Float.RealFracMethods (properFractionDoubleInteger) import GHC.Float.RealFracMethods (properFractionDoubleInteger)
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
@ -42,6 +41,7 @@ import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Orchard import ZcashHaskell.Orchard
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingOutput ( decodeSaplingOutput
, genSaplingInternalAddress
, genSaplingPaymentAddress , genSaplingPaymentAddress
, genSaplingSpendingKey , genSaplingSpendingKey
, getShieldedOutputs , getShieldedOutputs
@ -54,13 +54,13 @@ import ZcashHaskell.Types
( AccountId ( AccountId
, BlockResponse(..) , BlockResponse(..)
, CoinType(..) , CoinType(..)
, CoinType
, DecodedNote(..) , DecodedNote(..)
, OrchardAction(..) , OrchardAction(..)
, OrchardSpendingKey(..) , OrchardSpendingKey(..)
, Phrase(..) , Phrase(..)
, RawData(..) , RawData(..)
, RawTxResponse(..) , RawTxResponse(..)
, SaplingReceiver(..)
, SaplingSpendingKey(..) , SaplingSpendingKey(..)
, Scope(..) , Scope(..)
, Seed(..) , Seed(..)
@ -504,7 +504,7 @@ main = do
maybe "No transparent" (encodeTransparent (ua_net u)) $ maybe "No transparent" (encodeTransparent (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" $
ioProperty $ do ioProperty $ do
let p = let p =
Phrase Phrase
@ -518,10 +518,10 @@ main = do
Just s' -> do Just s' -> do
let oK = genOrchardSpendingKey s' MainNetCoin 0 let oK = genOrchardSpendingKey s' MainNetCoin 0
let sK = genSaplingSpendingKey s' MainNetCoin 0 let sK = genSaplingSpendingKey s' MainNetCoin 0
let tK = genTransparentPrvKey s' 0 let tK = genTransparentPrvKey s' MainNetCoin 0
let oR = genOrchardReceiver 0 External =<< oK let oR = genOrchardReceiver 0 External =<< oK
let sR = genSaplingPaymentAddress 0 =<< sK let sR = genSaplingPaymentAddress 0 =<< sK
tR <- genTransparentReceiver 0 =<< tK tR <- genTransparentReceiver 0 External =<< tK
let newUA = UnifiedAddress MainNet oR sR $ Just tR let newUA = UnifiedAddress MainNet oR sR $ Just tR
return $ Just newUA `shouldBe` targetUA return $ Just newUA `shouldBe` targetUA
it "Recover UA from Zingo:" $ it "Recover UA from Zingo:" $
@ -538,12 +538,233 @@ main = do
Just s' -> do Just s' -> do
let oK = genOrchardSpendingKey s' MainNetCoin 0 let oK = genOrchardSpendingKey s' MainNetCoin 0
let sK = genSaplingSpendingKey s' MainNetCoin 0 let sK = genSaplingSpendingKey s' MainNetCoin 0
let tK = genTransparentPrvKey s' 0 let tK = genTransparentPrvKey s' MainNetCoin 0
let oR = genOrchardReceiver 0 External =<< oK let oR = genOrchardReceiver 0 External =<< oK
let sR = genSaplingPaymentAddress 0 =<< sK let sR = genSaplingPaymentAddress 0 =<< sK
tR <- genTransparentReceiver 0 =<< tK tR <- genTransparentReceiver 0 External =<< tK
let newUA = UnifiedAddress MainNet oR sR $ Just tR let newUA = UnifiedAddress MainNet oR sR $ Just tR
return $ Just newUA `shouldBe` targetUA return $ Just newUA `shouldBe` targetUA
describe "Sapling Change Payment Address generation test" $ do
it "Call genSaplingInternalAddress" $ do
let sk =
[ 3
, 183
, 26
, 151
, 89
, 0
, 0
, 0
, 128
, 199
, 189
, 33
, 169
, 114
, 194
, 50
, 0
, 139
, 140
, 162
, 100
, 100
, 35
, 58
, 226
, 6
, 47
, 232
, 34
, 214
, 11
, 173
, 142
, 40
, 45
, 163
, 190
, 207
, 49
, 130
, 158
, 113
, 232
, 251
, 79
, 98
, 77
, 195
, 196
, 40
, 42
, 113
, 133
, 35
, 211
, 68
, 146
, 104
, 5
, 56
, 244
, 162
, 55
, 239
, 55
, 112
, 37
, 38
, 189
, 183
, 121
, 201
, 1
, 60
, 158
, 151
, 141
, 123
, 250
, 95
, 169
, 123
, 208
, 56
, 103
, 74
, 85
, 49
, 152
, 207
, 245
, 216
, 58
, 37
, 0
, 127
, 186
, 245
, 234
, 47
, 68
, 11
, 78
, 76
, 12
, 171
, 37
, 63
, 172
, 90
, 111
, 94
, 88
, 152
, 211
, 53
, 243
, 142
, 16
, 195
, 142
, 50
, 14
, 13
, 32
, 91
, 5
, 82
, 182
, 121
, 136
, 109
, 125
, 165
, 125
, 123
, 226
, 54
, 60
, 34
, 62
, 111
, 167
, 88
, 254
, 113
, 204
, 47
, 181
, 97
, 18
, 220
, 46
, 51
, 160
, 62
, 16
, 199
, 143
, 184
, 200
, 209
, 124
, 154
, 175
, 29
, 216
, 48
, 201
] :: [Word8]
let cAdr =
[ 31
, 232
, 31
, 17
, 196
, 178
, 208
, 227
, 206
, 199
, 105
, 55
, 147
, 23
, 151
, 206
, 117
, 59
, 249
, 162
, 218
, 140
, 189
, 17
, 60
, 116
, 106
, 56
, 64
, 203
, 152
, 52
, 155
, 133
, 179
, 118
, 47
, 161
, 70
, 155
, 21
, 22
, 41
] :: [Word8]
let bscAdr = SaplingReceiver $ BS.pack cAdr
let ca = genSaplingInternalAddress (SaplingSpendingKey $ BS.pack sk)
fromMaybe (SaplingReceiver "") ca `shouldBe` bscAdr
-- | Properties -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property
@ -605,18 +826,18 @@ prop_OrchardRecRepeated s c (NonNegative i) (NonNegative j) scope =
scope scope
(fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i) (fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i)
prop_TransparentSpendingKey :: Seed -> NonNegative Int -> Property prop_TransparentSpendingKey :: Seed -> CoinType -> NonNegative Int -> Property
prop_TransparentSpendingKey s (NonNegative i) = prop_TransparentSpendingKey s coinType (NonNegative i) =
ioProperty $ do ioProperty $ do
k <- genTransparentPrvKey s i k <- genTransparentPrvKey s coinType i
return $ xPrvChild k == fromIntegral i return $ xPrvChild k == fromIntegral i
prop_TransparentReceiver :: prop_TransparentReceiver ::
Seed -> NonNegative Int -> NonNegative Int -> Property Seed -> CoinType -> Scope -> NonNegative Int -> NonNegative Int -> Property
prop_TransparentReceiver s (NonNegative i) (NonNegative j) = prop_TransparentReceiver s coinType scope (NonNegative i) (NonNegative j) =
ioProperty $ do ioProperty $ do
k <- genTransparentPrvKey s i k <- genTransparentPrvKey s coinType i
r <- genTransparentReceiver j k r <- genTransparentReceiver j scope k
return $ ta_type r == P2PKH return $ ta_type r == P2PKH
-- | Generators -- | Generators

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.0.0 version: 0.5.0.1
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