From 7538bbfa190c45ccb76b99d0cfcdf124e391a0f0 Mon Sep 17 00:00:00 2001 From: Rene Vergara Date: Sun, 10 Mar 2024 07:47:26 -0500 Subject: [PATCH] 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