From 46efe9cd2195376b9d68fd9181411f5b93c70e75 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sun, 10 Mar 2024 22:19:51 -0400 Subject: [PATCH 1/5] Commit first version of genSaplingInternalAddress function --- librustzcash-wrapper/src/lib.rs | 35 +++++- src/C/Zcash.chs | 7 ++ src/ZcashHaskell/Sapling.hs | 6 +- test/Spec.hs | 188 +++++++++++++++++++++++++++++++- 4 files changed, 231 insertions(+), 5 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 5eea232..3d2754a 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -59,7 +59,8 @@ use zcash_address::{ use zcash_client_backend::keys::sapling::{ ExtendedFullViewingKey, - ExtendedSpendingKey + ExtendedSpendingKey, + DiversifiableFullViewingKey }; use zcash_primitives::zip32::{ AccountId, DiversifierIndex }; @@ -632,7 +633,7 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey( out_len: &mut usize ){ let seed: Vec = marshall_from_haskell_var(iseed, iseed_len, RW); - if ( seed.len() != 64 ) { + if seed.len() != 64 { // invalid seed length marshall_to_haskell_var(&vec![0], out, out_len, RW); } else { @@ -669,6 +670,36 @@ 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 + ){ + println!("Entering ChangeAddress generation...."); + let vexspk: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + let vexspkp = &vexspk; + let extspku8 : &[u8] = &vexspkp; + println!("Received ExtendedSpendingKey in Bytes...\n{:?}\n",extspku8); + let extspk = match ExtendedSpendingKey::from_bytes(&extspku8) { + Ok( k ) => k, + Err( e ) => { + // error recovering ExtendedSpendingKey + println!("\n>>>> Error generating ExtendedSpendingKey"); + marshall_to_haskell_var(&vec![0], out, out_len, RW); + return + } + }; + println!("ExtendedSpendingKey -> {:?}",extspk); + let dfvk = extspk.to_diversifiable_full_viewing_key(); + let ( divIx, cPmtAddress ) = dfvk.change_address(); + println!("\nDiversifierIndex > {:?}\n\nChange Payment Address -> {:?}\n ", + divIx, cPmtAddress); + println!("Change Payment Address in bytes : \n{:?}",cPmtAddress.to_bytes()); + 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 1e32f93..e68404b 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -149,6 +149,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/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 0a25661..64aa7a6 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -22,6 +22,7 @@ import C.Zcash , rustWrapperSaplingCheck , rustWrapperSaplingNoteDecode , rustWrapperSaplingPaymentAddress + , rustWrapperSaplingChgPaymentAddress , rustWrapperSaplingSpendingkey , rustWrapperSaplingVkDecode , rustWrapperTxParse @@ -115,5 +116,6 @@ genSaplingPaymentAddress extspk i = (rustWrapperSaplingPaymentAddress extspk (fromIntegral i)) -- | Generate an internal Sapling address -genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingInternalReceiver -genSaplingInternalAddress sk = undefined +genSaplingInternalAddress :: SaplingSpendingKey -> BS.ByteString -- SaplingInternalReceiver +genSaplingInternalAddress sk = withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress sk) + \ No newline at end of file diff --git a/test/Spec.hs b/test/Spec.hs index e6d6bc2..e00893b 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -42,6 +42,7 @@ import ZcashHaskell.Orchard import ZcashHaskell.Sapling ( decodeSaplingOutput , genSaplingPaymentAddress + , genSaplingInternalAddress , genSaplingSpendingKey , getShieldedOutputs , isValidSaplingViewingKey @@ -481,7 +482,7 @@ main = do Nothing -> "Bad UA" Just u -> maybe "No transparent" encodeTransparent $ t_rec u msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" - describe "Transparent Private and Publicc Key Generation" $ do + describe "Transparent Private and Public Key Generation" $ do it "Obtain a transparent extended private key from HDSeed" $ do let hdseed = [ 206 @@ -589,6 +590,191 @@ main = do genSaplingPaymentAddress (fromMaybe "" s) 0 `shouldNotBe` Nothing prop "Sapling receivers are valid" $ forAll genSapArgs $ \i -> prop_SaplingReceiver i + 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, 195, -- 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 = BS.pack cAdr + let ca = genSaplingInternalAddress (BS.pack sk) + ca `shouldBe` bscAdr -- | Properties prop_PhraseLength :: Int -> Property From 2c58d1260a98a2cb1624c4af36bfd41766a34376 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Thu, 14 Mar 2024 17:44:18 -0400 Subject: [PATCH 2/5] Transparent address fix --- src/ZcashHaskell/Sapling.hs | 2 +- src/ZcashHaskell/Transparent.hs | 24 +++++++++++++++++++----- test/Spec.hs | 20 ++++++++------------ 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index f93c635..3029b5b 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -124,7 +124,7 @@ genSaplingPaymentAddress i extspk = -- | Generate an internal Sapling address genSaplingInternalAddress :: SaplingSpendingKey -> Maybe SaplingInternalReceiver genSaplingInternalAddress sk = - if BS.length res <> 0 + if (BS.length res) > 0 then Just res else Nothing where diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 944948e..fed2ae7 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -28,15 +28,17 @@ import ZcashHaskell.Types , TransparentAddress(..) , TransparentType(..) , ZcashNet(..) + , CoinType(..) , getTransparentPrefix + , getValue ) import Crypto.Secp256k1 +import Haskoin.Crypto.Keys.Extended 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 encodeTransparent zNet t = @@ -53,11 +55,23 @@ encodeTransparent zNet t = 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 hdseed +genTransparentPrvKey :: Seed -> CoinType -> AccountId -> IO XPrvKey +genTransparentPrvKey hdseed ctype accid = do + let coin = getValue ctype ioCtx <- createContext - return $ hardSubKey ioCtx prvKey (fromIntegral i) + let path = Deriv :| 44 :| 133 :| coin :/ 0 :/ 0 :: DerivPath + let prvKey = makeXPrvKey hdseed + return $ derivePath ioCtx path prvKey + +genTransparentPubKey :: XPrvKey -> IO XPubKey +genTransparentPubKey xPrvKey = do + ioCtx <- createContext + return $ deriveXPubKey ioCtx xPrvKey + +genTransparentPubAddress :: XPubKey -> IO Address +genTransparentPubAddress xPubKey = do + ioCtx <- createContext + return $ xPubAddr ioCtx xPubKey -- | Generate a transparent receiver genTransparentReceiver :: Int -> XPrvKey -> IO TransparentAddress diff --git a/test/Spec.hs b/test/Spec.hs index 24990da..06684a2 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -45,7 +45,6 @@ import ZcashHaskell.Sapling , genSaplingPaymentAddress , genSaplingInternalAddress , genSaplingSpendingKey - , genSaplingMasterSpendingKey , getShieldedOutputs , isValidSaplingViewingKey , isValidShieldedAddress @@ -502,7 +501,6 @@ main = do maybe "No transparent" (encodeTransparent (ua_net u)) $ t_rec u msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" -<<<<<<< HEAD describe "Transparent Private and Public Key Generation" $ do it "Obtain a transparent extended private key from HDSeed" $ do let hdseed = @@ -571,7 +569,7 @@ main = do , 159 , 144 ] :: [Word8] - let xtpvk = genTransparentPrvKey (BS.pack hdseed) + xtpvk <- genTransparentPrvKey (BS.pack hdseed) MainNetCoin 0 let testpvk = XPrvKey 0 @@ -608,9 +606,9 @@ main = do it "Call genSaplingPaymentAddress" $ do p <- generateWalletSeedPhrase let s = getWalletSeed p - genSaplingPaymentAddress (fromMaybe "" s) 0 `shouldNotBe` Nothing - prop "Sapling receivers are valid" $ - forAll genSapArgs $ \i -> prop_SaplingReceiver i + genSaplingPaymentAddress 0 (fromMaybe "" s) `shouldNotBe` Nothing +-- prop "Sapling receivers are valid" $ +-- forAll genSapArgs $ \s -> prop_SaplingReceiver s describe "Sapling Change Payment Address generation test" $ do it "Call genSaplingInternalAddress" $ do let sk = [ 3 @@ -796,7 +794,6 @@ main = do let bscAdr = BS.pack cAdr let ca = genSaplingInternalAddress (BS.pack sk) (fromMaybe "" ca) `shouldBe` bscAdr -======= it "Recover UA from YWallet" $ ioProperty $ do let p = @@ -810,7 +807,7 @@ 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 =<< oK let sR = genSaplingPaymentAddress 0 =<< sK tR <- genTransparentReceiver 0 =<< tK @@ -829,14 +826,13 @@ 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 =<< oK let sR = genSaplingPaymentAddress 0 =<< sK tR <- genTransparentReceiver 0 =<< tK let newUA = UnifiedAddress MainNet oR sR $ Just tR return $ Just newUA `shouldBe` targetUA ->>>>>>> origin/dev040 -- | Properties prop_PhraseLength :: Property prop_PhraseLength = @@ -884,14 +880,14 @@ prop_OrchardRecRepeated s c (NonNegative i) (NonNegative j) = prop_TransparentSpendingKey :: Seed -> NonNegative Int -> Property prop_TransparentSpendingKey s (NonNegative i) = ioProperty $ do - k <- genTransparentPrvKey s i + k <- genTransparentPrvKey s MainNetCoin 0 return $ xPrvChild k == fromIntegral i prop_TransparentReceiver :: Seed -> NonNegative Int -> NonNegative Int -> Property prop_TransparentReceiver s (NonNegative i) (NonNegative j) = ioProperty $ do - k <- genTransparentPrvKey s i + k <- genTransparentPrvKey s MainCoinNet i r <- genTransparentReceiver j k return $ ta_type r == P2PKH From 0a98246855c13ba13605453a9c21283aecb56e64 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Thu, 14 Mar 2024 17:47:09 -0400 Subject: [PATCH 3/5] Transparent fix 2 --- src/ZcashHaskell/Transparent.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index fed2ae7..6c59edd 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -59,7 +59,7 @@ genTransparentPrvKey :: Seed -> CoinType -> AccountId -> IO XPrvKey genTransparentPrvKey hdseed ctype accid = do let coin = getValue ctype ioCtx <- createContext - let path = Deriv :| 44 :| 133 :| coin :/ 0 :/ 0 :: DerivPath + let path = Deriv :| 44 :| coin :| AccounId :/ 0 :/ 0 :: DerivPath let prvKey = makeXPrvKey hdseed return $ derivePath ioCtx path prvKey From c7c4d664dc2a9180805c35e5d08a61fc75d2c1ff Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 15 Mar 2024 08:15:08 -0500 Subject: [PATCH 4/5] Remove duplicate import --- test/Spec.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Spec.hs b/test/Spec.hs index 23e67a8..47fdda0 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -54,7 +54,6 @@ import ZcashHaskell.Types ( AccountId , BlockResponse(..) , CoinType(..) - , CoinType , DecodedNote(..) , OrchardAction(..) , OrchardSpendingKey(..) From d1b0436af2509d772d5127bd720df6072c023724 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Fri, 15 Mar 2024 10:26:06 -0500 Subject: [PATCH 5/5] Add documentation for new functions --- CHANGELOG.md | 1 + src/ZcashHaskell/Orchard.hs | 10 ++++++++-- src/ZcashHaskell/Transparent.hs | 28 +++++++++++++++------------- src/ZcashHaskell/Types.hs | 4 ++-- zcash-haskell.cabal | 2 +- 5 files changed, 27 insertions(+), 18 deletions(-) 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/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/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 52bc469..690fa4c 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -41,7 +41,11 @@ import ZcashHaskell.Types , getValue ) -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 @@ -56,7 +60,11 @@ encodeTransparent zNet t = checksum = sha256 $ sha256 digest -- | Generate an Extended Private Key from a known HDSeed. -genTransparentPrvKey :: Seed -> CoinType -> AccountId -> IO XPrvKey +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 @@ -64,18 +72,12 @@ genTransparentPrvKey hdseed ctype accid = do let prvKey = makeXPrvKey $ getBytes hdseed return $ derivePath ioCtx path prvKey -genTransparentPubKey :: XPrvKey -> IO XPubKey -genTransparentPubKey xPrvKey = do - ioCtx <- createContext - return $ deriveXPubKey ioCtx xPrvKey - -genTransparentPubAddress :: XPubKey -> IO Address -genTransparentPubAddress xPubKey = do - ioCtx <- createContext - return $ xPubAddr ioCtx xPubKey - -- | Generate a transparent receiver -genTransparentReceiver :: Int -> Scope -> XPrvKey -> IO TransparentAddress +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 s = 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/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