diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index ff83321..045a7b9 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -2,7 +2,6 @@ // // This file is part of Zcash-Haskell. // - use std::{ marker::PhantomData, io::{ @@ -56,7 +55,9 @@ use zcash_address::{ ZcashAddress }; -use zcash_client_backend::keys::sapling::ExtendedFullViewingKey; +use zcash_client_backend::keys::{sapling, sapling::ExtendedFullViewingKey}; +use zcash_primitives::zip32::AccountId; +use std::slice; use orchard::{ Action, @@ -606,3 +607,21 @@ pub extern "C" fn rust_wrapper_recover_seed( } } } + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_spendingkey( + input: *const u8, + input_len: usize, + out: *mut u8, + out_len: &mut usize + ){ + // - Retrieve parameters + +// let extsk = sapling::spending_key(&seed[0..32], +// cointype, +// accountid); +// println!("SpendingKey -> {:?}", extsk); +// let s = extsk.to_bytes(); +// let xsk : Vec = s.iter().cloned().collect(); +// marshall_to_haskell_var(&xsk, out, out_len, RW); +} diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index f93c6c9..bfaab7b 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -125,3 +125,9 @@ import ZcashHaskell.Types } -> `()' #} + +{# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey + { toBorshVar* `SaplingSKeyParams'& + } + -> `()' +#} diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index ea9d37e..a0ca034 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -23,19 +23,27 @@ import C.Zcash , rustWrapperSaplingNoteDecode , rustWrapperSaplingVkDecode , rustWrapperTxParse + , rustWrapperSaplingSpendingkey ) import Data.Aeson import qualified Data.ByteString as BS +import Data.ByteString.Lazy as BL import Data.HexString (HexString(..), toBytes) -import Foreign.Rust.Marshall.Variable (withPureBorshVarBuffer) +import Foreign.Rust.Marshall.Variable + ( withPureBorshVarBuffer + , withPureBorshVarBuffer + ) import ZcashHaskell.Types ( DecodedNote(..) , RawData(..) , RawTxResponse(..) , ShieldedOutput(..) , decodeHexText + , SaplingSKeyParams(..) ) -import ZcashHaskell.Utils (decodeBech32) +import ZcashHaskell.Utils + +import Data.Word -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool @@ -81,3 +89,15 @@ instance FromJSON RawTxResponse where Just o' -> do a <- o' .: "actions" pure $ RawTxResponse i h (getShieldedOutputs h) a ht c b + +-- | Attempts to obtain a sapling SpendinKey using a HDSeed, a Coin Type and an Account ID +genSaplingSpendingKey :: + BS.ByteString -> Word32 -> Word32 -> Maybe BS.ByteString +genSaplingSpendingKey seed coin_type account_id = do + if BS.length res > 0 + then Just res + else Nothing + where + let params = SaplingSKeyParams seed coin_type account_id + res = (withPureBorshVarBuffer . rustWrapperSaplingSpendingkey) params + diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs index 6de9200..f7f2a48 100644 --- a/src/ZcashHaskell/Transparent.hs +++ b/src/ZcashHaskell/Transparent.hs @@ -28,6 +28,10 @@ import ZcashHaskell.Types , ZcashNet(..) ) +import Haskoin.Crypto.Keys.Extended +import Data.Word +import Crypto.Secp256k1 + encodeTransparent :: TransparentAddress -> T.Text encodeTransparent t = case ta_type t of @@ -48,3 +52,19 @@ encodeTransparent t = sha256 bs = BA.convert (hash bs :: Digest SHA256) digest = BS.pack [a, b] <> h checksum = sha256 $ sha256 digest + +-- | Attempts to generate an Extended Private Key from a known HDSeed. +genTransparentPrvKey :: + BS.ByteString -> XPrvKey +genTransparentPrvKey hdseed = do + makeXPrvKey hdseed + +-- | Attempts to obtain an Extended Public Key from a known Extended Private Key +genTransparentPubKey :: + XPrvKey -> IO XPubKey +genTransparentPubKey xpvk = do + ioCtx <- createContext + let xpubk = deriveXPubKey ioCtx xpvk + return xpubk + + diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index f0cbcdb..131a7cb 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -278,6 +278,16 @@ data DecodedNote = DecodedNote deriving anyclass (Data.Structured.Show) deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote +-- } Type to represent parameters to call rust zcash library +data SaplingSKeyParams = SaplingSKeyParams + { hdseed :: BS.ByteString -- ^ seed required for sappling spending key generation + , coin_type :: Word32 -- ^ coin_type + , account_id :: Word32 -- ^ account id + } deriving stock (Eq, Prelude.Show, GHC.Generic) + deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) + deriving anyclass (Data.Structured.Show) + deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingSKeyParams + -- * Helpers -- | Helper function to turn a hex-encoded string to bytestring decodeHexText :: String -> BS.ByteString diff --git a/src/ZcashHaskell/Utils.hs b/src/ZcashHaskell/Utils.hs index 853857c..503ccc5 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -31,6 +31,13 @@ import Foreign.Rust.Marshall.Variable import Network.HTTP.Simple import ZcashHaskell.Types +import Foreign.C.Types +import Foreign.Marshal.Array (allocaArray, peekArray) +import Foreign.Ptr (Ptr) + +import Data.Word +-- | + -- | Decode the given bytestring using Bech32 decodeBech32 :: BS.ByteString -> RawData decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode @@ -76,3 +83,13 @@ makeZebraCall host port m params = do setRequestHost (E.encodeUtf8 host) $ setRequestMethod "POST" defaultRequest httpJSON myRequest + + +-- + Misc functions +-- Convert an array of Word8 to a ByteString +word8ArrayToByteString :: [Word8] -> BS.ByteString +word8ArrayToByteString = BS.pack + +-- Convert [Word8] to String +word8ToString :: [Word8] -> String +word8ToString = map (toEnum . fromEnum) diff --git a/test/Spec.hs b/test/Spec.hs index c387f83..c9ceab8 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -30,7 +30,7 @@ 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 Data.Word + import GHC.Float.RealFracMethods (properFractionDoubleInteger) import Test.Hspec import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) @@ -41,8 +41,10 @@ import ZcashHaskell.Sapling , isValidSaplingViewingKey , isValidShieldedAddress , matchSaplingAddress + , genSaplingSpendingKey ) -import ZcashHaskell.Transparent (encodeTransparent) +import ZcashHaskell.Transparent + --(encodeTransparent) import ZcashHaskell.Types ( BlockResponse(..) , DecodedNote(..) @@ -56,6 +58,10 @@ import ZcashHaskell.Types ) import ZcashHaskell.Utils +import Foreign.C.Types +import Data.Word +import Haskoin.Crypto.Keys.Extended + main :: IO () main = do hspec $ do @@ -455,3 +461,56 @@ 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 + it "Obtain a transparent extended private key from HDSeed" $ do + let hdseed = [206, 61, 120, 38, + 206, 40, 201, 62, + 83, 175, 151, 131, + 218, 141, 206, 254, + 28, 244, 172, 213, + 128, 248, 156, 45, + 204, 44, 169, 3, + 162, 188, 16, 173, + 192, 164, 96, 148, + 91, 52, 244, 83, + 149, 169, 82, 196, + 199, 53, 177, 170, + 1, 6, 0, 120, + 170, 2, 238, 219, + 241, 243, 172, 178, + 104, 81, 159, 144 + ] :: [Word8] + let xtpvk = genTransparentPrvKey (word8ArrayToByteString hdseed) + let testpvk = XPrvKey 0 "0000000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943" + xtpvk `shouldBe` testpvk +-- describe "Obtain transparent public key from private key" $ do + it "Obtain a transparent extended public key from private key" $ do + let testpvk = XPrvKey 0 "0000000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943" + let testpbk = XPubKey 0 "00000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "279bda9c704f6da479cedb12c7cf773b3a348569dc1cfa6002526bad67674fd737b84a2bdb1199ecab1c9fed1b9a38aba5ba19259c1510d733a2376118515cd8" + let xtpubkIO = genTransparentPubKey testpvk + xtpubk <- xtpubkIO + ---print $ show xtpubk + xtpubk `shouldBe` testpbk + describe "Sapling SpendingKey test" $ do + it "Call function with parameters" $ do + let hdseed = [206, 61, 120, 38, + 206, 40, 201, 62, + 83, 175, 151, 131, + 218, 141, 206, 254, + 28, 244, 172, 213, + 128, 248, 156, 45, + 204, 44, 169, 3, + 162, 188, 16, 173, + 192, 164, 96, 148, + 91, 52, 244, 83, + 149, 169, 82, 196, + 199, 53, 177, 170, + 1, 6, 0, 120, + 170, 2, 238, 219, + 241, 243, 172, 178, + 104, 81, 159, 144 + ] :: [Word8] + let coin = 1 :: Word32 + let account = 0 :: Word32 + res <- genSaplingSpendingKey (word8ArrayToByteString hdseed) coin account + res `shouldBe` "genSaplingSpendingKey Function called." \ No newline at end of file diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index ad2e0d3..1eab3f4 100644 --- a/zcash-haskell.cabal +++ b/zcash-haskell.cabal @@ -55,6 +55,8 @@ library , http-conduit , memory , text + , haskoin-core + , secp256k1-haskell build-tool-depends: c2hs:c2hs default-language: Haskell2010 @@ -74,5 +76,8 @@ test-suite zcash-haskell-test , hspec , text , zcash-haskell + , binary + , cryptonite + , secp256k1-haskell pkgconfig-depends: rustzcash_wrapper default-language: Haskell2010