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
- Function to generate an Orchard receiver
- Function to generate a Sapling receiver
- Function to generate a Transparent receiver
### Changed

View file

@ -61,7 +61,8 @@ use zcash_address::{
use zcash_client_backend::keys::sapling::{
spending_key,
ExtendedFullViewingKey,
ExtendedSpendingKey
ExtendedSpendingKey,
DiversifiableFullViewingKey
};
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]
pub extern "C" fn rust_wrapper_derive_orchard_spending_key(
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
{ toBorshVar* `BS.ByteString'&
, `Word32'

View file

@ -37,7 +37,10 @@ import ZcashHaskell.Utils (encodeBech32m, f4Jumble)
-- | Derives an Orchard spending key for the given seed and account ID
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 =
if BS.length k /= 32
then Nothing
@ -52,7 +55,10 @@ genOrchardSpendingKey s coinType accountId =
-- | Derives an Orchard receiver for the given spending key and index
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 =
if BS.length k /= 43
then Nothing

View file

@ -20,6 +20,7 @@ module ZcashHaskell.Sapling where
import C.Zcash
( rustWrapperIsShielded
, rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress
, rustWrapperSaplingNoteDecode
, rustWrapperSaplingPaymentAddress
, rustWrapperSaplingSpendingkey
@ -124,4 +125,10 @@ genSaplingPaymentAddress i extspk =
-- | Generate an internal Sapling address
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 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.HexString
import qualified Data.Text as T
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
( AccountId
, CoinType(..)
, Scope(..)
, Seed(..)
, ToBytes(..)
, TransparentAddress(..)
, TransparentType(..)
, ZcashNet(..)
, getTransparentPrefix
, getValue
)
import Crypto.Secp256k1
import Data.HexString
import Data.Word
import Haskoin.Address (Address(..))
import qualified Haskoin.Crypto.Hash as H
import Haskoin.Crypto.Keys.Extended
encodeTransparent :: ZcashNet -> TransparentAddress -> T.Text
-- | Encodes a `TransparentAddress` into the human-readable format per the Zcash Protocol section 5.6.1.1
encodeTransparent ::
ZcashNet -- ^ The network, `MainNet` or `TestNet`
-> TransparentAddress -- ^ The address to encode
-> T.Text
encodeTransparent zNet t =
encodeTransparent' (getTransparentPrefix zNet (ta_type t)) $
toBytes $ ta_bytes t
@ -53,19 +59,34 @@ encodeTransparent zNet t =
digest = BS.pack [a, b] <> h
checksum = sha256 $ sha256 digest
-- | Attempts to generate an Extended Private Key from a known HDSeed.
genTransparentPrvKey :: Seed -> AccountId -> IO XPrvKey
genTransparentPrvKey hdseed i = do
let prvKey = makeXPrvKey $ getBytes hdseed
-- | Generate an Extended Private Key from a known HDSeed.
genTransparentPrvKey ::
Seed -- ^ The cryptographic seed of the wallet
-> 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
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
genTransparentReceiver :: Int -> XPrvKey -> IO TransparentAddress
genTransparentReceiver i xprvk = do
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
genTransparentReceiver i scope xprvk = do
ioCtx <- createContext
let rootPubKey = deriveXPubKey ioCtx xprvk
let childPubKey = pubSubKey ioCtx rootPubKey (fromIntegral i)
let s =
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
case x of
PubKeyAddress k -> return $ TransparentAddress P2PKH $ fromBinary k

View file

@ -65,8 +65,8 @@ instance ToBytes Phrase where
-- | Scope for addresses/receivers
data Scope
= External
| Internal
= External -- ^ Addresses used publically to receive payments
| Internal -- ^ Addresses used internally by wallets for change and shielding
deriving (Eq, Prelude.Show, Read)
-- | 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.Lazy.Encoding as LE
import qualified Data.Text.Lazy.IO as LTIO
import GHC.Float.RealFracMethods (properFractionDoubleInteger)
import Test.Hspec
import Test.Hspec.QuickCheck
@ -42,6 +41,7 @@ import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( decodeSaplingOutput
, genSaplingInternalAddress
, genSaplingPaymentAddress
, genSaplingSpendingKey
, getShieldedOutputs
@ -54,13 +54,13 @@ import ZcashHaskell.Types
( AccountId
, BlockResponse(..)
, CoinType(..)
, CoinType
, DecodedNote(..)
, OrchardAction(..)
, OrchardSpendingKey(..)
, Phrase(..)
, RawData(..)
, RawTxResponse(..)
, SaplingReceiver(..)
, SaplingSpendingKey(..)
, Scope(..)
, Seed(..)
@ -504,7 +504,7 @@ main = do
maybe "No transparent" (encodeTransparent (ua_net u)) $
t_rec u
msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD"
it "Recover UA from YWallet:" $
it "Recover UA from YWallet" $
ioProperty $ do
let p =
Phrase
@ -518,10 +518,10 @@ main = do
Just s' -> do
let oK = genOrchardSpendingKey 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 sR = genSaplingPaymentAddress 0 =<< sK
tR <- genTransparentReceiver 0 =<< tK
tR <- genTransparentReceiver 0 External =<< tK
let newUA = UnifiedAddress MainNet oR sR $ Just tR
return $ Just newUA `shouldBe` targetUA
it "Recover UA from Zingo:" $
@ -538,12 +538,233 @@ main = do
Just s' -> do
let oK = genOrchardSpendingKey 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 sR = genSaplingPaymentAddress 0 =<< sK
tR <- genTransparentReceiver 0 =<< tK
tR <- genTransparentReceiver 0 External =<< tK
let newUA = UnifiedAddress MainNet oR sR $ Just tR
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
prop_PhraseLength :: Property
@ -605,18 +826,18 @@ prop_OrchardRecRepeated s c (NonNegative i) (NonNegative j) scope =
scope
(fromMaybe (OrchardSpendingKey "") $ genOrchardSpendingKey s c i)
prop_TransparentSpendingKey :: Seed -> NonNegative Int -> Property
prop_TransparentSpendingKey s (NonNegative i) =
prop_TransparentSpendingKey :: Seed -> CoinType -> NonNegative Int -> Property
prop_TransparentSpendingKey s coinType (NonNegative i) =
ioProperty $ do
k <- genTransparentPrvKey s i
k <- genTransparentPrvKey s coinType i
return $ xPrvChild k == fromIntegral i
prop_TransparentReceiver ::
Seed -> NonNegative Int -> NonNegative Int -> Property
prop_TransparentReceiver s (NonNegative i) (NonNegative j) =
Seed -> CoinType -> Scope -> NonNegative Int -> NonNegative Int -> Property
prop_TransparentReceiver s coinType scope (NonNegative i) (NonNegative j) =
ioProperty $ do
k <- genTransparentPrvKey s i
r <- genTransparentReceiver j k
k <- genTransparentPrvKey s coinType i
r <- genTransparentReceiver j scope k
return $ ta_type r == P2PKH
-- | Generators

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack
name: zcash-haskell
version: 0.5.0.0
version: 0.5.0.1
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>
category: Blockchain