diff --git a/CHANGELOG.md b/CHANGELOG.md index fe73f0a..31c81c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 9d09942..2451d27 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -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 = 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, diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 0f93729..19df3c3 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -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' diff --git a/src/ZcashHaskell/Orchard.hs b/src/ZcashHaskell/Orchard.hs index 3021234..a810114 100644 --- a/src/ZcashHaskell/Orchard.hs +++ b/src/ZcashHaskell/Orchard.hs @@ -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 diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index d1904ad..c744516 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -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) diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 7cc1dce..690fa4c 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -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 diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index 2e6cd5c..12a27a2 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -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 diff --git a/test/Spec.hs b/test/Spec.hs index 23e67a8..97c4a0c 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -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 diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index d7d33d9..646d810 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.0.0 +version: 0.5.0.1 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain