From 97b338bddcdb242a6cddd142dfb5565b7a155e71 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sun, 3 Mar 2024 16:19:06 -0500 Subject: [PATCH 1/8] Commit for revision... --- librustzcash-wrapper/src/lib.rs | 23 ++++++++++-- src/C/Zcash.chs | 6 ++++ src/ZcashHaskell/Sapling.hs | 24 +++++++++++-- src/ZcashHaskell/Transparent.hs | 20 +++++++++++ src/ZcashHaskell/Types.hs | 10 ++++++ src/ZcashHaskell/Utils.hs | 17 +++++++++ test/Spec.hs | 63 +++++++++++++++++++++++++++++++-- zcash-haskell.cabal | 5 +++ 8 files changed, 162 insertions(+), 6 deletions(-) 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 From a6c358cd5d87d1c462bd8e612554f0ea047df3df Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Mon, 4 Mar 2024 18:46:39 -0500 Subject: [PATCH 2/8] Commit for debuggin process Erros with Parameters for rustWrapperSaplingSpendingkey function x in Sapling.hs module --- src/C/Zcash.chs | 7 +++++-- src/ZcashHaskell/Sapling.hs | 4 +--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index bfaab7b..0526a45 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -127,7 +127,10 @@ import ZcashHaskell.Types #} {# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey - { toBorshVar* `SaplingSKeyParams'& + { toBorshVar* `BS.ByteString'& + , toBorshFixed Int + , toBorshFixed Int + , getVarBuffer `Buffer (BS.ByteString)'& } -> `()' -#} +#} \ No newline at end of file diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index a0ca034..1f564f9 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -42,7 +42,6 @@ import ZcashHaskell.Types , SaplingSKeyParams(..) ) import ZcashHaskell.Utils - import Data.Word -- | Check if given bytesting is a valid encoded shielded address @@ -98,6 +97,5 @@ genSaplingSpendingKey seed coin_type account_id = do then Just res else Nothing where - let params = SaplingSKeyParams seed coin_type account_id - res = (withPureBorshVarBuffer . rustWrapperSaplingSpendingkey) params + res = (withPureBorshVarBuffer . rustWrapperSaplingSpendingkey) seed coin_type account_id From 9acbe5b98c48c64868f3d5db34fdbd28eaf5e9e0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Tue, 5 Mar 2024 07:36:45 -0600 Subject: [PATCH 3/8] Fix compilation for Spending Key --- librustzcash-wrapper/src/lib.rs | 5 +++++ src/C/Zcash.chs | 6 +++--- src/ZcashHaskell/Sapling.hs | 20 ++++++++++---------- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 045a7b9..702879b 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -612,9 +612,14 @@ pub extern "C" fn rust_wrapper_recover_seed( pub extern "C" fn rust_wrapper_sapling_spendingkey( input: *const u8, input_len: usize, + coin: u32, + acc_id: u32, out: *mut u8, out_len: &mut usize ){ + let seed: Vec = marshall_from_haskell_var(input, input_len, RW); + let fake_response = "It's working"; + marshall_to_haskell_var(&fake_response.as_bytes().to_vec(), out, out_len, RW); // - Retrieve parameters // let extsk = sapling::spending_key(&seed[0..32], diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 0526a45..f0ad1b8 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -128,9 +128,9 @@ import ZcashHaskell.Types {# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey { toBorshVar* `BS.ByteString'& - , toBorshFixed Int - , toBorshFixed Int + , `Int' + , `Int' , getVarBuffer `Buffer (BS.ByteString)'& } -> `()' -#} \ No newline at end of file +#} diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 1f564f9..81d6507 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -21,15 +21,16 @@ import C.Zcash ( rustWrapperIsShielded , rustWrapperSaplingCheck , rustWrapperSaplingNoteDecode + , rustWrapperSaplingSpendingkey , rustWrapperSaplingVkDecode , rustWrapperTxParse - , rustWrapperSaplingSpendingkey ) import Data.Aeson import qualified Data.ByteString as BS -import Data.ByteString.Lazy as BL +import Data.ByteString.Lazy as BL import Data.HexString (HexString(..), toBytes) -import Foreign.Rust.Marshall.Variable +import Data.Word +import Foreign.Rust.Marshall.Variable ( withPureBorshVarBuffer , withPureBorshVarBuffer ) @@ -37,12 +38,11 @@ import ZcashHaskell.Types ( DecodedNote(..) , RawData(..) , RawTxResponse(..) + , SaplingSKeyParams(..) , ShieldedOutput(..) , decodeHexText - , SaplingSKeyParams(..) ) -import ZcashHaskell.Utils -import Data.Word +import ZcashHaskell.Utils -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool @@ -90,12 +90,12 @@ instance FromJSON RawTxResponse where 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 :: BS.ByteString -> Int -> Int -> Maybe BS.ByteString genSaplingSpendingKey seed coin_type account_id = do if BS.length res > 0 then Just res else Nothing where - res = (withPureBorshVarBuffer . rustWrapperSaplingSpendingkey) seed coin_type account_id - + res = + withPureBorshVarBuffer + (rustWrapperSaplingSpendingkey seed coin_type account_id) From 7b65d322e68ebee907b8da6ad5237490206fc705 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Tue, 5 Mar 2024 17:01:17 -0500 Subject: [PATCH 4/8] Commit 003 - before merging with dev040 - with orchard spending key --- src/C/Zcash.chs | 4 ++-- src/ZcashHaskell/Sapling.hs | 2 +- test/Spec.hs | 11 +++++++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index f0ad1b8..fcabf7c 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -128,8 +128,8 @@ import ZcashHaskell.Types {# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey { toBorshVar* `BS.ByteString'& - , `Int' - , `Int' + , `Word32' + , `Word32' , getVarBuffer `Buffer (BS.ByteString)'& } -> `()' diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 81d6507..d32668e 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -90,7 +90,7 @@ instance FromJSON RawTxResponse where 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 -> Int -> Int -> Maybe BS.ByteString +genSaplingSpendingKey :: BS.ByteString -> Word32 -> Word32 -> Maybe BS.ByteString genSaplingSpendingKey seed coin_type account_id = do if BS.length res > 0 then Just res diff --git a/test/Spec.hs b/test/Spec.hs index c9ceab8..8032b6d 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -62,6 +62,10 @@ import Foreign.C.Types import Data.Word import Haskoin.Crypto.Keys.Extended +m2bs :: Maybe BS.ByteString -> BS.ByteString +m2bs x = fromMaybe "" x + + main :: IO () main = do hspec $ do @@ -512,5 +516,8 @@ main = do ] :: [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 + let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) coin account + case msg of + Nothing -> "Bad response from genSapingSpendingKey" + Just msg -> fromMaybe "BadResponse" msg + "mm " `shouldBe` "genSaplingSpendingKey Function called." \ No newline at end of file From 0e6f7503d4947e57b657e93210a2cec9737f28d9 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Tue, 5 Mar 2024 22:10:05 -0500 Subject: [PATCH 5/8] Sapling Extended Spemding Key generation from HDSedd (64 byte array) --- librustzcash-wrapper/Cargo.lock | 1 + librustzcash-wrapper/Cargo.toml | 1 + librustzcash-wrapper/src/lib.rs | 35 ++++++++++++++++++++------------- src/ZcashHaskell/Sapling.hs | 15 ++++++-------- test/Spec.hs | 18 ++++++++++------- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index 7f59103..bc4b733 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -1314,6 +1314,7 @@ dependencies = [ "borsh 0.10.3", "f4jumble", "haskell-ffi", + "nom", "orchard 0.7.1", "proc-macro2", "zcash_address 0.2.0", diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index 9c8f966..d7b2495 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -17,6 +17,7 @@ zcash_primitives = "0.13.0" zcash_client_backend = "0.10.0" zip32 = "0.1.0" proc-macro2 = "1.0.66" +nom = "7.1.3" [features] capi = [] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index bfb2794..8963325 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -49,7 +49,11 @@ use zcash_address::{ ZcashAddress }; -use zcash_client_backend::keys::{sapling, sapling::ExtendedFullViewingKey}; +use zcash_client_backend::keys::{ + sapling, + sapling::ExtendedFullViewingKey, + sapling::ExtendedSpendingKey}; + use zcash_primitives::zip32::AccountId; use std::slice; @@ -615,24 +619,27 @@ pub extern "C" fn rust_wrapper_recover_seed( #[no_mangle] pub extern "C" fn rust_wrapper_sapling_spendingkey( - in_seed: *const u8, - seed_len: usize, + iseed: *const u8, + iseed_len: usize, coin_type: u32, acc_id: u32, out: *mut u8, out_len: &mut usize ){ - let seed: Vec = marshall_from_haskell_var(in_seed, input_len, RW); - let fake_response = "It's working"; - marshall_to_haskell_var(&fake_response.as_bytes().to_vec(), out, out_len, RW); - -// 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); + println!("From Rust\n========="); + let seed: Vec = marshall_from_haskell_var(iseed, iseed_len, RW); + if ( seed.len() <= 0 ) { + println!("Seed error, returning a null vector..."); + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } else { + println!("Seed in rust : {:?}\n", seed); + println!("Coin Type -> {}\nAccount Id -> {}",coin_type,acc_id); + let su8 = &seed; + let seedu8 : &[u8] = &su8; + let extsk: ExtendedSpendingKey = sapling::ExtendedSpendingKey::master(&seedu8); + let extsk_bytes = extsk.to_bytes().to_vec(); + marshall_to_haskell_var(&extsk_bytes, out, out_len, RW); + } } #[no_mangle] diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index d32668e..c34314c 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -41,6 +41,8 @@ import ZcashHaskell.Types , SaplingSKeyParams(..) , ShieldedOutput(..) , decodeHexText + , AccountId + , CoinType ) import ZcashHaskell.Utils @@ -88,14 +90,9 @@ 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 :: BS.ByteString -> Word32-> AccountId -> BS.ByteString genSaplingSpendingKey seed coin_type account_id = do - if BS.length res > 0 - then Just res - else Nothing - where - res = - withPureBorshVarBuffer - (rustWrapperSaplingSpendingkey seed coin_type account_id) + let res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed (fromIntegral coin_type) (fromIntegral account_id) ) + res \ No newline at end of file diff --git a/test/Spec.hs b/test/Spec.hs index 9f85b03..abfd17c 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -57,6 +57,9 @@ import ZcashHaskell.Types , UnifiedAddress(..) , UnifiedFullViewingKey(..) , decodeHexText + , AccountId + , CoinType + , getValue ) import ZcashHaskell.Utils @@ -528,10 +531,11 @@ main = do 241, 243, 172, 178, 104, 81, 159, 144 ] :: [Word8] - let coin = 1 :: Word32 - let account = 0 :: Word32 - let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) coin account - case msg of - Nothing -> "Bad response from genSapingSpendingKey" - Just msg -> fromMaybe "BadResponse" msg - "mm " `shouldBe` "genSaplingSpendingKey Function called." \ No newline at end of file + let cointype = getValue TestNetCoin + let account = 0 :: AccountId + let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) cointype account + let msgArr = BS.unpack msg + if (length msgArr) == 169 + then True + else False +-- msgArr `shouldBe` "It's working." \ No newline at end of file From 54b15670117c1c3bc7adec91a3f838ffdb2651c2 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Fri, 8 Mar 2024 14:46:41 -0500 Subject: [PATCH 6/8] Default Paymebt address and DiversifierIndex created x --- librustzcash-wrapper/src/lib.rs | 57 ++++++++++++++++++++++++++++----- src/C/Zcash.chs | 9 ++++-- src/ZcashHaskell/Sapling.hs | 16 ++++++--- test/Spec.hs | 31 +++++++++++++++--- 4 files changed, 95 insertions(+), 18 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 8963325..ce35b57 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -54,7 +54,7 @@ use zcash_client_backend::keys::{ sapling::ExtendedFullViewingKey, sapling::ExtendedSpendingKey}; -use zcash_primitives::zip32::AccountId; +use zcash_primitives::zip32::{ AccountId, DiversifierIndex }; use std::slice; use orchard::{ @@ -621,27 +621,68 @@ pub extern "C" fn rust_wrapper_recover_seed( pub extern "C" fn rust_wrapper_sapling_spendingkey( iseed: *const u8, iseed_len: usize, - coin_type: u32, - acc_id: u32, out: *mut u8, out_len: &mut usize ){ - println!("From Rust\n========="); + println!("Starting extended spending key generation...."); let seed: Vec = marshall_from_haskell_var(iseed, iseed_len, RW); - if ( seed.len() <= 0 ) { - println!("Seed error, returning a null vector..."); + if ( seed.len() != 64 ) { + // invalid seed length marshall_to_haskell_var(&vec![0], out, out_len, RW); } else { - println!("Seed in rust : {:?}\n", seed); - println!("Coin Type -> {}\nAccount Id -> {}",coin_type,acc_id); + // Obtain the ExtendedSpendingKey using the seed + // Returns a byte array (169 bytes) let su8 = &seed; let seedu8 : &[u8] = &su8; + println!("Seed : {:?}\n", &seedu8); let extsk: ExtendedSpendingKey = sapling::ExtendedSpendingKey::master(&seedu8); let extsk_bytes = extsk.to_bytes().to_vec(); marshall_to_haskell_var(&extsk_bytes, out, out_len, RW); } } +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_paymentaddress( + extspk: *const u8, + extspk_len: usize, +// divIx: u32, + out: *mut u8, + out_len: &mut usize + ){ + let divIx : u32 = 2; + println!("Starting paymentAddress generation...."); + let extspkb: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + if ( extspkb.len() != 169 ) { + // invalid ExtendedSpenndingKey Array length + println!("Invalid ExtendedSpendingKey...."); + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } else { + // Process + println!("Extended Spending Key validated, continue ...."); + let extspkbu8 = &extspkb; + let xsku8 : &[u8] = &extspkbu8; + let xsk = match sapling::ExtendedSpendingKey::from_bytes(&xsku8) { + Ok ( x ) => x, + Err ( err ) => { + // Error recovering ExtendedSpendingKey from bytes + marshall_to_haskell_var(&vec![0], out, out_len, RW); + return + } + }; + // Obtain the DiversifiableFullViewingKey from ExtendedSpendingKey + let dfvk = xsk.to_diversifiable_full_viewing_key(); + // Obtain the Address from the DiversifiableFullViewingKey +// println!("dfvk -> \n{:?}", dfvk); +// let divIndex : DiversifierIndex = divIx.into(); +// println!("divIndex -> {:?}", divIndex); + let (divIx, paddress) = dfvk.default_address(); + println!("Rust pmtAddress - \n{:?}\n\nRust Diversifier - \n{:?}\n", paddress, divIx); + let pmtAddress = paddress.to_bytes(); + println!("\nRust pntAddress as byte array -\n{:?}\n", pmtAddress); + marshall_to_haskell_var(&pmtAddress.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 ab3ef8d..14bfc45 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -136,8 +136,13 @@ import ZcashHaskell.Types {# fun unsafe rust_wrapper_sapling_spendingkey as rustWrapperSaplingSpendingkey { toBorshVar* `BS.ByteString'& - , `Word32' - , `Word32' + , getVarBuffer `Buffer (BS.ByteString)'& + } + -> `()' +#} + +{# fun unsafe rust_wrapper_sapling_paymentaddress as rustWrapperPaymentAddress + { toBorshVar* `BS.ByteString'& , getVarBuffer `Buffer (BS.ByteString)'& } -> `()' diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index c34314c..62f513f 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -22,6 +22,7 @@ import C.Zcash , rustWrapperSaplingCheck , rustWrapperSaplingNoteDecode , rustWrapperSaplingSpendingkey + , rustWrapperPaymentAddress , rustWrapperSaplingVkDecode , rustWrapperTxParse ) @@ -92,7 +93,14 @@ instance FromJSON RawTxResponse where 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-> AccountId -> BS.ByteString -genSaplingSpendingKey seed coin_type account_id = do - let res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed (fromIntegral coin_type) (fromIntegral account_id) ) - res \ No newline at end of file +genSaplingSpendingKey :: BS.ByteString -> BS.ByteString +genSaplingSpendingKey seed = do + let res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed ) + res +-- +-- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey +-- | and a Diversifier Index +genSaplingPaymentAddress :: BS.ByteString -> BS.ByteString +genSaplingPaymentAddress extspk = do + let pmtaddress = withPureBorshVarBuffer (rustWrapperPaymentAddress extspk ) + pmtaddress diff --git a/test/Spec.hs b/test/Spec.hs index abfd17c..c6d5086 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -43,6 +43,7 @@ import ZcashHaskell.Sapling , isValidShieldedAddress , matchSaplingAddress , genSaplingSpendingKey + , genSaplingPaymentAddress ) import ZcashHaskell.Transparent --(encodeTransparent) @@ -531,11 +532,33 @@ main = do 241, 243, 172, 178, 104, 81, 159, 144 ] :: [Word8] - let cointype = getValue TestNetCoin - let account = 0 :: AccountId - let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) cointype account + let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) let msgArr = BS.unpack msg if (length msgArr) == 169 then True else False --- msgArr `shouldBe` "It's working." \ No newline at end of file + describe "Sapling Payment Address generation test" $ do + it "Call genSaplingPaymentAddress" $ do + let hdseed1 = [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 msg1 = genSaplingSpendingKey (word8ArrayToByteString hdseed1) + let pmtaddress = genSaplingPaymentAddress msg1 --(word8ArrayToByteString hdseed1) + let msgArr = BS.unpack pmtaddress + if (length msgArr) == 43 + then True + else False From 477817f37f414a459d70b1f89e950f85438ca267 Mon Sep 17 00:00:00 2001 From: "Rene V. Vergara" Date: Sat, 9 Mar 2024 10:28:36 -0500 Subject: [PATCH 7/8] Find_Address method implemented --- librustzcash-wrapper/src/lib.rs | 66 +++++++++++++++++++++++++++++---- src/C/Zcash.chs | 8 ++++ src/ZcashHaskell/Sapling.hs | 9 ++++- test/Spec.hs | 34 +++++++++++++---- 4 files changed, 101 insertions(+), 16 deletions(-) diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 649b699..05c7853 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -649,8 +649,48 @@ pub extern "C" fn rust_wrapper_sapling_paymentaddress( out: *mut u8, out_len: &mut usize ){ - let divIx : u32 = 2; - println!("Starting paymentAddress generation...."); +// println!("Starting paymentAddress generation...."); + let extspkb: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + if ( extspkb.len() != 169 ) { + // invalid ExtendedSpenndingKey Array length + println!("Invalid ExtendedSpendingKey...."); + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } else { + // Process +// println!("Extended Spending Key validated, continue ...."); + let extspkbu8 = &extspkb; + let xsku8 : &[u8] = &extspkbu8; + let xsk = match sapling::ExtendedSpendingKey::from_bytes(&xsku8) { + Ok ( x ) => x, + Err ( err ) => { + // Error recovering ExtendedSpendingKey from bytes + marshall_to_haskell_var(&vec![0], out, out_len, RW); + return + } + }; + // Obtain the DiversifiableFullViewingKey from ExtendedSpendingKey + let dfvk = xsk.to_diversifiable_full_viewing_key(); + // Obtain the Address from the DiversifiableFullViewingKey +// println!("dfvk -> \n{:?}", dfvk); +// let divIndex : DiversifierIndex = divIx.into(); +// println!("divIndex -> {:?}", divIndex); + let (divIx, paddress) = dfvk.default_address(); +// println!("Rust pmtAddress - \n{:?}\n\nRust Diversifier - \n{:?}\n", paddress, divIx); + let pmtAddress = paddress.to_bytes(); +// println!("\nRust pntAddress as byte array -\n{:?}\n", pmtAddress); + marshall_to_haskell_var(&pmtAddress.to_vec(), out, out_len, RW); + } +} + +#[no_mangle] +pub extern "C" fn rust_wrapper_sapling_find_paymentaddress( + extspk: *const u8, + extspk_len: usize, + divIx: u32, + out: *mut u8, + out_len: &mut usize + ){ + println!("Starting paymentAddress generation using Find_Address()...."); let extspkb: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); if ( extspkb.len() != 169 ) { // invalid ExtendedSpenndingKey Array length @@ -669,20 +709,30 @@ pub extern "C" fn rust_wrapper_sapling_paymentaddress( return } }; + println!("Obtain a DiversifierIndex from u32 parameter {:?}....",divIx); + let diversifierIndex : DiversifierIndex = divIx.into(); // Obtain the DiversifiableFullViewingKey from ExtendedSpendingKey let dfvk = xsk.to_diversifiable_full_viewing_key(); // Obtain the Address from the DiversifiableFullViewingKey // println!("dfvk -> \n{:?}", dfvk); // let divIndex : DiversifierIndex = divIx.into(); // println!("divIndex -> {:?}", divIndex); - let (divIx, paddress) = dfvk.default_address(); - println!("Rust pmtAddress - \n{:?}\n\nRust Diversifier - \n{:?}\n", paddress, divIx); - let pmtAddress = paddress.to_bytes(); - println!("\nRust pntAddress as byte array -\n{:?}\n", pmtAddress); - marshall_to_haskell_var(&pmtAddress.to_vec(), out, out_len, RW); + let result = dfvk.find_address(diversifierIndex) + .map (|(divIx,paddress) | { + println!("Rust pmtAddress - \n{:?}\n\nRust Diversifier - \n{:?}\n", paddress, divIx); + let pmtAddress = paddress.to_bytes(); + println!("\nRust pntAddress as byte array -\n{:?}\n", pmtAddress); + marshall_to_haskell_var(&pmtAddress.to_vec(), out, out_len, RW); +// + }) + .unwrap_or_else(|| { + // Handle the case where the function returns None + println!("Rust - Error finding payment address.... "); + marshall_to_haskell_var(&vec![0], out, out_len, RW); + return + }); } } - #[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 97ad03b..d0f100f 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -148,6 +148,14 @@ import ZcashHaskell.Types -> `()' #} +{# fun unsafe rust_wrapper_sapling_find_paymentaddress as rustWrapperFindPaymentAddress + { toBorshVar* `BS.ByteString'& + , `Word32' + , 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 62f513f..c470417 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -23,6 +23,7 @@ import C.Zcash , rustWrapperSaplingNoteDecode , rustWrapperSaplingSpendingkey , rustWrapperPaymentAddress + , rustWrapperFindPaymentAddress , rustWrapperSaplingVkDecode , rustWrapperTxParse ) @@ -99,8 +100,14 @@ genSaplingSpendingKey seed = do res -- -- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey --- | and a Diversifier Index genSaplingPaymentAddress :: BS.ByteString -> BS.ByteString genSaplingPaymentAddress extspk = do let pmtaddress = withPureBorshVarBuffer (rustWrapperPaymentAddress extspk ) pmtaddress + +-- +-- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey +genSaplingFindPaymentAddress :: BS.ByteString -> Word32 -> BS.ByteString +genSaplingFindPaymentAddress extspk divIx = do + let pmtaddress = withPureBorshVarBuffer (rustWrapperFindPaymentAddress extspk divIx) + pmtaddress diff --git a/test/Spec.hs b/test/Spec.hs index 164329d..63cf1be 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -46,7 +46,8 @@ import ZcashHaskell.Sapling , isValidShieldedAddress , matchSaplingAddress , genSaplingSpendingKey - , genSaplingPaymentAddress + , genSaplingPaymentAddress + , genSaplingFindPaymentAddress ) import ZcashHaskell.Transparent --(encodeTransparent) @@ -532,9 +533,7 @@ main = do ] :: [Word8] let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) let msgArr = BS.unpack msg - if (length msgArr) == 169 - then True - else False + length msgArr `shouldBe` 169 describe "Sapling Payment Address generation test" $ do it "Call genSaplingPaymentAddress" $ do let hdseed1 = [206, 61, 120, 38, @@ -557,9 +556,30 @@ main = do let msg1 = genSaplingSpendingKey (word8ArrayToByteString hdseed1) let pmtaddress = genSaplingPaymentAddress msg1 --(word8ArrayToByteString hdseed1) let msgArr = BS.unpack pmtaddress - if (length msgArr) == 43 - then True - else False + length msgArr `shouldBe` 43 + describe "Sapling Payment Find Address generation test" $ do + it "Call genSaplingFindPaymentAddress" $ do + let hdseed1 = [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 msg1 = genSaplingSpendingKey (word8ArrayToByteString hdseed1) + let pmtaddress = genSaplingFindPaymentAddress msg1 0 --(word8ArrayToByteString hdseed1) + let msgArr = BS.unpack pmtaddress + length msgArr `shouldBe` 42 -- | Properties prop_PhraseLength :: Int -> Property From 7538bbfa190c45ccb76b99d0cfcdf124e391a0f0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Sun, 10 Mar 2024 07:47:26 -0500 Subject: [PATCH 8/8] Revision of Sapling receiver logic --- CHANGELOG.md | 1 + librustzcash-wrapper/Cargo.lock | 1 - librustzcash-wrapper/Cargo.toml | 1 - librustzcash-wrapper/src/lib.rs | 72 +++---- src/C/Zcash.chs | 3 +- src/ZcashHaskell/Sapling.hs | 41 ++-- src/ZcashHaskell/Types.hs | 10 - src/ZcashHaskell/Utils.hs | 12 +- test/Spec.hs | 347 ++++++++++++++++++++++++-------- zcash-haskell.cabal | 2 +- 10 files changed, 323 insertions(+), 167 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fff58dc..fe73f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Constants for Zcash protocol - Types for Spending Keys and Receivers for Sapling and Orchard - Function to generate an Orchard receiver +- Function to generate a Sapling receiver ### Changed diff --git a/librustzcash-wrapper/Cargo.lock b/librustzcash-wrapper/Cargo.lock index bc4b733..7f59103 100644 --- a/librustzcash-wrapper/Cargo.lock +++ b/librustzcash-wrapper/Cargo.lock @@ -1314,7 +1314,6 @@ dependencies = [ "borsh 0.10.3", "f4jumble", "haskell-ffi", - "nom", "orchard 0.7.1", "proc-macro2", "zcash_address 0.2.0", diff --git a/librustzcash-wrapper/Cargo.toml b/librustzcash-wrapper/Cargo.toml index d7b2495..9c8f966 100644 --- a/librustzcash-wrapper/Cargo.toml +++ b/librustzcash-wrapper/Cargo.toml @@ -17,7 +17,6 @@ zcash_primitives = "0.13.0" zcash_client_backend = "0.10.0" zip32 = "0.1.0" proc-macro2 = "1.0.66" -nom = "7.1.3" [features] capi = [] diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs index 649b699..5eea232 100644 --- a/librustzcash-wrapper/src/lib.rs +++ b/librustzcash-wrapper/src/lib.rs @@ -24,15 +24,23 @@ use haskell_ffi::{ use zip32; use zcash_primitives::{ - zip32::Scope as SaplingScope, + zip32::{ + Scope as SaplingScope, + sapling_find_address, + sapling::DiversifierKey + }, zip339::{Count, Mnemonic}, transaction::components::sapling::{ GrothProofBytes, - OutputDescription, + OutputDescription }, sapling::{ PaymentAddress, - keys::PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, + keys::{ + PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, + ExpandedSpendingKey, + FullViewingKey as SaplingFullViewingKey + }, note_encryption::SaplingDomain }, transaction::Transaction, @@ -49,13 +57,12 @@ use zcash_address::{ ZcashAddress }; -use zcash_client_backend::keys::{ - sapling, - sapling::ExtendedFullViewingKey, - sapling::ExtendedSpendingKey}; +use zcash_client_backend::keys::sapling::{ + ExtendedFullViewingKey, + ExtendedSpendingKey +}; use zcash_primitives::zip32::{ AccountId, DiversifierIndex }; -use std::slice; use orchard::{ Action, @@ -624,7 +631,6 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey( out: *mut u8, out_len: &mut usize ){ - println!("Starting extended spending key generation...."); let seed: Vec = marshall_from_haskell_var(iseed, iseed_len, RW); if ( seed.len() != 64 ) { // invalid seed length @@ -634,8 +640,7 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey( // Returns a byte array (169 bytes) let su8 = &seed; let seedu8 : &[u8] = &su8; - println!("Seed : {:?}\n", &seedu8); - let extsk: ExtendedSpendingKey = sapling::ExtendedSpendingKey::master(&seedu8); + let extsk: ExtendedSpendingKey = ExtendedSpendingKey::master(&seedu8); let extsk_bytes = extsk.to_bytes().to_vec(); marshall_to_haskell_var(&extsk_bytes, out, out_len, RW); } @@ -645,41 +650,22 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey( pub extern "C" fn rust_wrapper_sapling_paymentaddress( extspk: *const u8, extspk_len: usize, -// divIx: u32, + div_ix: u32, out: *mut u8, out_len: &mut usize ){ - let divIx : u32 = 2; - println!("Starting paymentAddress generation...."); - let extspkb: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); - if ( extspkb.len() != 169 ) { - // invalid ExtendedSpenndingKey Array length - println!("Invalid ExtendedSpendingKey...."); - marshall_to_haskell_var(&vec![0], out, out_len, RW); - } else { - // Process - println!("Extended Spending Key validated, continue ...."); - let extspkbu8 = &extspkb; - let xsku8 : &[u8] = &extspkbu8; - let xsk = match sapling::ExtendedSpendingKey::from_bytes(&xsku8) { - Ok ( x ) => x, - Err ( err ) => { - // Error recovering ExtendedSpendingKey from bytes - marshall_to_haskell_var(&vec![0], out, out_len, RW); - return - } - }; - // Obtain the DiversifiableFullViewingKey from ExtendedSpendingKey - let dfvk = xsk.to_diversifiable_full_viewing_key(); - // Obtain the Address from the DiversifiableFullViewingKey -// println!("dfvk -> \n{:?}", dfvk); -// let divIndex : DiversifierIndex = divIx.into(); -// println!("divIndex -> {:?}", divIndex); - let (divIx, paddress) = dfvk.default_address(); - println!("Rust pmtAddress - \n{:?}\n\nRust Diversifier - \n{:?}\n", paddress, divIx); - let pmtAddress = paddress.to_bytes(); - println!("\nRust pntAddress as byte array -\n{:?}\n", pmtAddress); - marshall_to_haskell_var(&pmtAddress.to_vec(), out, out_len, RW); + let extspk: Vec = marshall_from_haskell_var(extspk, extspk_len, RW); + let expsk = ExpandedSpendingKey::from_spending_key(&extspk); + let fvk = SaplingFullViewingKey::from_expanded_spending_key(&expsk); + let dk = DiversifierKey::master(&extspk); + let result = sapling_find_address(&fvk, &dk, DiversifierIndex::from(div_ix)); + match result { + Some((_d, p_address)) => { + marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW); + }, + None => { + marshall_to_haskell_var(&vec![0], out, out_len, RW); + } } } diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs index 97ad03b..1e32f93 100644 --- a/src/C/Zcash.chs +++ b/src/C/Zcash.chs @@ -141,8 +141,9 @@ import ZcashHaskell.Types -> `()' #} -{# fun unsafe rust_wrapper_sapling_paymentaddress as rustWrapperPaymentAddress +{# fun unsafe rust_wrapper_sapling_paymentaddress as rustWrapperSaplingPaymentAddress { toBorshVar* `BS.ByteString'& + , `Word32' , getVarBuffer `Buffer (BS.ByteString)'& } -> `()' diff --git a/src/ZcashHaskell/Sapling.hs b/src/ZcashHaskell/Sapling.hs index 62f513f..7ca8ec5 100644 --- a/src/ZcashHaskell/Sapling.hs +++ b/src/ZcashHaskell/Sapling.hs @@ -21,14 +21,13 @@ import C.Zcash ( rustWrapperIsShielded , rustWrapperSaplingCheck , rustWrapperSaplingNoteDecode + , rustWrapperSaplingPaymentAddress , rustWrapperSaplingSpendingkey - , rustWrapperPaymentAddress , rustWrapperSaplingVkDecode , rustWrapperTxParse ) import Data.Aeson import qualified Data.ByteString as BS -import Data.ByteString.Lazy as BL import Data.HexString (HexString(..), toBytes) import Data.Word import Foreign.Rust.Marshall.Variable @@ -36,16 +35,18 @@ import Foreign.Rust.Marshall.Variable , withPureBorshVarBuffer ) import ZcashHaskell.Types - ( DecodedNote(..) + ( AccountId + , CoinType + , DecodedNote(..) , RawData(..) , RawTxResponse(..) - , SaplingSKeyParams(..) + , SaplingReceiver + , SaplingSpendingKey(..) + , Seed(..) , ShieldedOutput(..) , decodeHexText - , AccountId - , CoinType ) -import ZcashHaskell.Utils +import ZcashHaskell.Utils (decodeBech32) -- | Check if given bytesting is a valid encoded shielded address isValidShieldedAddress :: BS.ByteString -> Bool @@ -91,16 +92,26 @@ 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 -> BS.ByteString +-- | Attempts to obtain a sapling SpendingKey using a HDSeed +genSaplingSpendingKey :: Seed -> Maybe SaplingSpendingKey genSaplingSpendingKey seed = do - let res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed ) - res + if BS.length res == 196 + then Just res + else Nothing + where + res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed) + -- -- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey -- | and a Diversifier Index -genSaplingPaymentAddress :: BS.ByteString -> BS.ByteString -genSaplingPaymentAddress extspk = do - let pmtaddress = withPureBorshVarBuffer (rustWrapperPaymentAddress extspk ) - pmtaddress +genSaplingPaymentAddress :: SaplingSpendingKey -> Int -> Maybe SaplingReceiver +genSaplingPaymentAddress extspk i = + if BS.length res == 43 + then Just res + else Nothing + where + res = + withPureBorshVarBuffer + (rustWrapperSaplingPaymentAddress extspk (fromIntegral i)) diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs index f86f368..41e41e8 100644 --- a/src/ZcashHaskell/Types.hs +++ b/src/ZcashHaskell/Types.hs @@ -346,16 +346,6 @@ 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 3e392e2..f6f1ceb 100644 --- a/src/ZcashHaskell/Utils.hs +++ b/src/ZcashHaskell/Utils.hs @@ -37,8 +37,8 @@ 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 @@ -88,13 +88,3 @@ 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 164329d..b7a38ff 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -41,18 +41,21 @@ import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) import ZcashHaskell.Orchard import ZcashHaskell.Sapling ( decodeSaplingOutput + , genSaplingPaymentAddress + , genSaplingSpendingKey , getShieldedOutputs , isValidSaplingViewingKey , isValidShieldedAddress , matchSaplingAddress - , genSaplingSpendingKey - , genSaplingPaymentAddress ) -import ZcashHaskell.Transparent +import ZcashHaskell.Transparent --(encodeTransparent) + import ZcashHaskell.Types - ( BlockResponse(..) + ( AccountId + , BlockResponse(..) , CoinType(..) + , CoinType , DecodedNote(..) , OrchardAction(..) , Phrase(..) @@ -62,19 +65,16 @@ import ZcashHaskell.Types , UnifiedAddress(..) , UnifiedFullViewingKey(..) , decodeHexText - , AccountId - , CoinType , getValue ) import ZcashHaskell.Utils -import Foreign.C.Types import Data.Word +import Foreign.C.Types import Haskoin.Crypto.Keys.Extended -m2bs :: Maybe BS.ByteString -> BS.ByteString -m2bs x = fromMaybe "" x - +m2bs :: Maybe BS.ByteString -> BS.ByteString +m2bs x = fromMaybe "" x main :: IO () main = do @@ -483,83 +483,251 @@ main = do 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 + 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 (BS.pack hdseed) + let testpvk = + XPrvKey + 0 + "0000000000" + 0 + "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" + "46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943" + xtpvk `shouldBe` testpvk + 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 + 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 msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) - let msgArr = BS.unpack msg - if (length msgArr) == 169 - then True - else False + let hdseed = + BS.pack $ + [ 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 + ] + --xit "Call function with parameters" $ do + --let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) + --let msgArr = BS.unpack msg + --if (length msgArr) == 169 + --then True + --else False + it "Generate Sapling spending key" $ do + p <- generateWalletSeedPhrase + let s = getWalletSeed p + genSaplingSpendingKey <$> s `shouldNotBe` Nothing describe "Sapling Payment Address generation test" $ do - it "Call genSaplingPaymentAddress" $ do - let hdseed1 = [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 msg1 = genSaplingSpendingKey (word8ArrayToByteString hdseed1) - let pmtaddress = genSaplingPaymentAddress msg1 --(word8ArrayToByteString hdseed1) - let msgArr = BS.unpack pmtaddress - if (length msgArr) == 43 - then True - else False + it "Call genSaplingPaymentAddress" $ do + let hdseed1 = + [ 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] + p <- generateWalletSeedPhrase + let s = getWalletSeed p + genSaplingPaymentAddress (fromMaybe "" s) 0 `shouldNotBe` Nothing + prop "Sapling receivers are valid" $ + forAll genSapArgs $ \(i) -> prop_SaplingReceiver i -- | Properties prop_PhraseLength :: Int -> Property @@ -590,6 +758,14 @@ prop_OrchardReceiver c i j = let sk = genOrchardSpendingKey (fromMaybe "" s) c i return $ genOrchardReceiver j (fromMaybe "" sk) =/= Nothing +prop_SaplingReceiver :: Int -> Property +prop_SaplingReceiver i = + ioProperty $ do + p <- generateWalletSeedPhrase + let s = getWalletSeed p + let sk = genSaplingSpendingKey (fromMaybe "" s) + return $ genSaplingPaymentAddress (fromMaybe "" sk) i =/= Nothing + -- | Generators genOrcArgs :: Gen (CoinType, Int, Int) genOrcArgs = do @@ -597,4 +773,7 @@ genOrcArgs = do j <- arbitrarySizedNatural c <- elements [MainNetCoin, TestNetCoin, RegTestNetCoin] return (c, i, j) + +genSapArgs :: Gen Int +genSapArgs = choose (1, 50) -- | Arbitrary instances diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal index ec49d03..0bee561 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.4.4.0 +version: 0.4.4.1 synopsis: Utilities to interact with the Zcash blockchain description: Please see the README on the repo at category: Blockchain