Add functions to generate seed phrases and seeds

This commit is contained in:
Rene Vergara 2024-01-16 16:15:05 -06:00
parent 72e3700aa6
commit ca8010145b
Signed by: pitmutt
GPG key ID: 65122AD495A7F5B2
10 changed files with 183 additions and 45 deletions

View file

@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.0]
### Added
- Function to encode a human-readable transparent address
- Function to generate a seed phrase
## [0.3.0] ## [0.3.0]
### Added ### Added

View file

@ -37,6 +37,7 @@ use haskell_ffi::{
use zcash_primitives::{ use zcash_primitives::{
zip32::Scope as SaplingScope, zip32::Scope as SaplingScope,
zip339::{Count, Mnemonic},
transaction::components::sapling::{ transaction::components::sapling::{
read_zkproof, read_zkproof,
GrothProofBytes, GrothProofBytes,
@ -582,3 +583,33 @@ pub extern "C" fn rust_wrapper_tx_parse(
} }
} }
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_gen_seed_phrase(
out: *mut u8,
out_len: &mut usize
){
let mnemonic = Mnemonic::generate(Count::Words24);
let seed = mnemonic.phrase().as_bytes().to_vec();
marshall_to_haskell_var(&seed, out, out_len, RW);
}
#[no_mangle]
pub extern "C" fn rust_wrapper_recover_seed(
input: *const u8,
input_len: usize,
out: *mut u8,
out_len: &mut usize
){
let phrase: String = marshall_from_haskell_var(input, input_len, RW);
let mnemonic = Mnemonic::from_phrase(phrase);
match mnemonic {
Ok(m) => {
let s = m.to_seed("").to_vec();
marshall_to_haskell_var(&s, out, out_len, RW);
},
Err(_e) => {
marshall_to_haskell_var(&vec![0], out, out_len, RW);
}
}
}

View file

@ -1,5 +1,5 @@
name: zcash-haskell name: zcash-haskell
version: 0.3.0 version: 0.4.0
git: "https://git.vergara.tech/Vergara_Tech/zcash-haskell" git: "https://git.vergara.tech/Vergara_Tech/zcash-haskell"
license: LGPL-3 license: LGPL-3
author: "Rene Vergara" author: "Rene Vergara"
@ -53,3 +53,4 @@ tests:
- bytestring - bytestring
- text - text
- aeson - aeson
- haskoin-core

View file

@ -29,7 +29,7 @@ module C.Zcash where
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Codec.Borsh import Codec.Borsh
import Data.Text (Text) import qualified Data.Text as T
import Data.Word import Data.Word
import Data.Int import Data.Int
import Data.Structured import Data.Structured
@ -43,6 +43,12 @@ import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC import qualified GHC.Generics as GHC
import ZcashHaskell.Types import ZcashHaskell.Types
{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer RawData'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble {# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
@ -71,13 +77,6 @@ import ZcashHaskell.Types
-> `Bool' -> `Bool'
#} #}
{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer RawData'&
}
-> `()'
#}
{# fun pure unsafe rust_wrapper_svk_decode as rustWrapperSaplingVkDecode {# fun pure unsafe rust_wrapper_svk_decode as rustWrapperSaplingVkDecode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
} }
@ -127,3 +126,14 @@ import ZcashHaskell.Types
} }
-> `()' -> `()'
#} #}
{# fun unsafe rust_wrapper_gen_seed_phrase as rustWrapperGenSeedPhrase
{ getVarBuffer `Buffer Phrase'& } -> `()'
#}
{# fun unsafe rust_wrapper_recover_seed as rustWrapperGetSeed
{ toBorshVar* `Phrase'&
, getVarBuffer `Buffer Seed'&
}
-> `()'
#}

52
src/ZcashHaskell/Keys.hs Normal file
View file

@ -0,0 +1,52 @@
{- Copyright 2022-2024 Vergara Technologies LLC
This file is part of Zcash-Haskell.
Zcash-Haskell is free software: you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
Zcash-Haskell is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details.
You should have received a copy of the GNU Lesser General Public License along with
Zcash-Haskell. If not, see <https://www.gnu.org/licenses/>.
-}
-- |
-- Module : ZcashHaskell.Keys
-- Copyright : 2022-2024 Vergara Technologies
-- License : LGPL-3
--
-- Maintainer : pitmutt@vergara.tech
-- Stability : experimental
-- Portability : unknown
--
-- Functions to generate keys for the Zcash blockchain
--
module ZcashHaskell.Keys where
import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed)
import qualified Data.ByteString as BS
import qualified Data.Text as T
import Foreign.Rust.Marshall.Variable
( withBorshVarBuffer
, withPureBorshVarBuffer
)
import ZcashHaskell.Types (Phrase, Seed)
-- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses.
generateWalletSeedPhrase :: IO Phrase
generateWalletSeedPhrase = withBorshVarBuffer rustWrapperGenSeedPhrase
-- | Get
getWalletSeed :: Phrase -> Maybe Seed
getWalletSeed p =
if BS.length result > 0
then Just result
else Nothing
where
result :: Seed
result = (withPureBorshVarBuffer . rustWrapperGetSeed) p

View file

@ -31,4 +31,36 @@
-- --
module ZcashHaskell.Transparent where module ZcashHaskell.Transparent where
import Crypto.Hash
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58)
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Word
import ZcashHaskell.Types
( TransparentAddress(..)
, TransparentType(..)
, ZcashNet(..)
)
encodeTransparent :: TransparentAddress -> T.Text
encodeTransparent t =
case ta_type t of
P2SH ->
case ta_net t of
MainNet -> encodeTransparent' (0x1c, 0xbd) $ ta_bytes t
_ -> encodeTransparent' (0x1c, 0xba) $ ta_bytes t
P2PKH ->
case ta_net t of
MainNet -> encodeTransparent' (0x1c, 0xb8) $ ta_bytes t
_ -> encodeTransparent' (0x1d, 0x25) $ ta_bytes t
where
encodeTransparent' :: (Word8, Word8) -> BS.ByteString -> T.Text
encodeTransparent' (a, b) h =
E.decodeUtf8 $ encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum
where
sha256 :: BS.ByteString -> BS.ByteString
sha256 bs = BA.convert (hash bs :: Digest SHA256)
digest = BS.pack [a, b] <> h
checksum = sha256 $ sha256 digest

View file

@ -25,10 +25,10 @@
-- | -- |
-- Module : ZcashHaskell.Types -- Module : ZcashHaskell.Types
-- Copyright : Vergara Technologies 2023 -- Copyright : 2022-2024 Vergara Technologies
-- License : BOSL -- License : LGPL-3
-- --
-- Maintainer : rene@vergara.network -- Maintainer : pitmut@vergara.tech
-- Stability : experimental -- Stability : experimental
-- Portability : unknown -- Portability : unknown
-- --
@ -37,12 +37,10 @@
module ZcashHaskell.Types where module ZcashHaskell.Types where
import Codec.Borsh import Codec.Borsh
import Control.Exception (MaskingState(Unmasked))
import Crypto.Hash import Crypto.Hash
import Data.Aeson import Data.Aeson
import qualified Data.ByteArray as BA import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58)
import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Char8 as C
import Data.Int import Data.Int
import Data.Structured import Data.Structured
@ -53,6 +51,14 @@ import qualified GHC.Generics as GHC
import qualified Generics.SOP as SOP import qualified Generics.SOP as SOP
-- * General -- * General
--
-- | A seed for generating private keys
type Seed = C.ByteString
-- | A mnemonic phrase used to derive seeds
type Phrase = BS.ByteString
--
-- | Type to represent data after Bech32 decoding -- | Type to represent data after Bech32 decoding
data RawData = RawData data RawData = RawData
{ hrp :: BS.ByteString -- ^ Human-readable part of the Bech32 encoding { hrp :: BS.ByteString -- ^ Human-readable part of the Bech32 encoding
@ -144,35 +150,14 @@ data ZcashNet
data TransparentType data TransparentType
= P2SH = P2SH
| P2PKH | P2PKH
deriving (Eq) deriving (Eq, Prelude.Show)
-- | Type to represent a transparent Zcash addresses -- | Type to represent a transparent Zcash addresses
data TransparentAddress = TransparentAddress data TransparentAddress = TransparentAddress
{ ta_type :: TransparentType { ta_type :: !TransparentType
, ta_net :: ZcashNet , ta_net :: !ZcashNet
, ta_bytes :: BS.ByteString , ta_bytes :: !BS.ByteString
} deriving (Eq) } deriving (Eq, Prelude.Show)
instance Prelude.Show TransparentAddress where
show t =
case ta_type t of
P2SH ->
case ta_net t of
MainNet -> Prelude.show $ encodeTransparent (0x1c, 0xbd) $ ta_bytes t
_ -> Prelude.show $ encodeTransparent (0x1c, 0xba) $ ta_bytes t
P2PKH ->
case ta_net t of
MainNet -> Prelude.show $ encodeTransparent (0x1c, 0xb8) $ ta_bytes t
_ -> Prelude.show $ encodeTransparent (0x1d, 0x25) $ ta_bytes t
where
encodeTransparent :: (Word8, Word8) -> BS.ByteString -> BS.ByteString
encodeTransparent (a, b) h =
encodeBase58 bitcoinAlphabet $ digest <> BS.take 4 checksum
where
sha256 :: BS.ByteString -> BS.ByteString
sha256 bs = BA.convert (hash bs :: Digest SHA256)
digest = BS.pack [a, b] <> h
checksum = sha256 $ sha256 digest
-- * Sapling -- * Sapling
-- | Type to represent a Sapling Shielded Output as provided by the @getrawtransaction@ RPC method of @zcashd@. -- | Type to represent a Sapling Shielded Output as provided by the @getrawtransaction@ RPC method of @zcashd@.

View file

@ -17,7 +17,7 @@
# #
# resolver: ./custom-snapshot.yaml # resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml
resolver: lts-21.21 resolver: lts-21.22
# User packages to be built. # User packages to be built.
# Various formats can be used as shown in the example below. # Various formats can be used as shown in the example below.

View file

@ -22,6 +22,8 @@ import C.Zcash (rustWrapperUADecode)
import Data.Aeson import Data.Aeson
import Data.Bool (Bool(True)) import Data.Bool (Bool(True))
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.Either (isRight)
import Data.Foldable (sequenceA_)
import Data.Maybe import Data.Maybe
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E import qualified Data.Text.Encoding as E
@ -29,7 +31,9 @@ import qualified Data.Text.Lazy.Encoding as LE
import qualified Data.Text.Lazy.IO as LTIO import qualified Data.Text.Lazy.IO as LTIO
import Data.Word import Data.Word
import GHC.Float.RealFracMethods (properFractionDoubleInteger) import GHC.Float.RealFracMethods (properFractionDoubleInteger)
import Haskoin.Keys.Mnemonic
import Test.Hspec import Test.Hspec
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Orchard import ZcashHaskell.Orchard
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingOutput ( decodeSaplingOutput
@ -38,6 +42,7 @@ import ZcashHaskell.Sapling
, isValidShieldedAddress , isValidShieldedAddress
, matchSaplingAddress , matchSaplingAddress
) )
import ZcashHaskell.Transparent (encodeTransparent)
import ZcashHaskell.Types import ZcashHaskell.Types
( BlockResponse(..) ( BlockResponse(..)
, DecodedNote(..) , DecodedNote(..)
@ -280,6 +285,18 @@ main = do
Right x -> Right x ->
rt_id x `shouldBe` rt_id x `shouldBe`
"5242b51f22a7d6fe9dee237137271cde704d306a5fff6a862bffaebb6f0e7e56" "5242b51f22a7d6fe9dee237137271cde704d306a5fff6a862bffaebb6f0e7e56"
describe "Seeds" $ do
it "generate seed phrase" $ do
s <- generateWalletSeedPhrase
BS.length s `shouldNotBe` 0
it "get seed from phrase" $ do
s <- generateWalletSeedPhrase
let x = getWalletSeed s
let result =
case x of
Nothing -> False
Just s' -> True
result `shouldBe` True
describe "Sapling address" $ do describe "Sapling address" $ do
it "succeeds with valid address" $ do it "succeeds with valid address" $ do
let sa = let sa =
@ -427,5 +444,5 @@ main = do
let msg = let msg =
case isValidUnifiedAddress ua of case isValidUnifiedAddress ua of
Nothing -> "Bad UA" Nothing -> "Bad UA"
Just u -> maybe "No transparent" show $ t_rec u Just u -> maybe "No transparent" encodeTransparent $ t_rec u
msg `shouldBe` "Got it" msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD"

View file

@ -1,11 +1,11 @@
cabal-version: 1.12 cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.35.2. -- This file has been generated from package.yaml by hpack version 0.36.0.
-- --
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.3.0 version: 0.4.0
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme> description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain category: Blockchain
@ -26,6 +26,8 @@ source-repository head
library library
exposed-modules: exposed-modules:
C.Zcash C.Zcash
ZcashHaskell.DB
ZcashHaskell.Keys
ZcashHaskell.Orchard ZcashHaskell.Orchard
ZcashHaskell.Sapling ZcashHaskell.Sapling
ZcashHaskell.Transparent ZcashHaskell.Transparent
@ -63,6 +65,7 @@ test-suite zcash-haskell-test
aeson aeson
, base >=4.7 && <5 , base >=4.7 && <5
, bytestring , bytestring
, haskoin-core
, hspec , hspec
, text , text
, zcash-haskell , zcash-haskell