diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7b07525..820998f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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/),
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]
### Added
diff --git a/librustzcash-wrapper/src/lib.rs b/librustzcash-wrapper/src/lib.rs
index 8973af8..5a6f046 100644
--- a/librustzcash-wrapper/src/lib.rs
+++ b/librustzcash-wrapper/src/lib.rs
@@ -37,6 +37,7 @@ use haskell_ffi::{
use zcash_primitives::{
zip32::Scope as SaplingScope,
+ zip339::{Count, Mnemonic},
transaction::components::sapling::{
read_zkproof,
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);
+ }
+ }
+}
diff --git a/package.yaml b/package.yaml
index 4856888..35f34e7 100644
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
name: zcash-haskell
-version: 0.3.0
+version: 0.4.0
git: "https://git.vergara.tech/Vergara_Tech/zcash-haskell"
license: LGPL-3
author: "Rene Vergara"
@@ -53,3 +53,4 @@ tests:
- bytestring
- text
- aeson
+ - haskoin-core
diff --git a/src/C/Zcash.chs b/src/C/Zcash.chs
index ebda582..3d83a26 100644
--- a/src/C/Zcash.chs
+++ b/src/C/Zcash.chs
@@ -29,7 +29,7 @@ module C.Zcash where
import qualified Data.ByteString as BS
import Codec.Borsh
-import Data.Text (Text)
+import qualified Data.Text as T
import Data.Word
import Data.Int
import Data.Structured
@@ -43,6 +43,12 @@ import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import ZcashHaskell.Types
+{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode
+ { toBorshVar* `BS.ByteString'&
+ , getVarBuffer `Buffer RawData'&
+ }
+ -> `()'
+#}
{# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble
{ toBorshVar* `BS.ByteString'&
@@ -71,13 +77,6 @@ import ZcashHaskell.Types
-> `Bool'
#}
-{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode
- { toBorshVar* `BS.ByteString'&
- , getVarBuffer `Buffer RawData'&
- }
- -> `()'
-#}
-
{# fun pure unsafe rust_wrapper_svk_decode as rustWrapperSaplingVkDecode
{ 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'&
+ }
+ -> `()'
+#}
diff --git a/src/ZcashHaskell/Keys.hs b/src/ZcashHaskell/Keys.hs
new file mode 100644
index 0000000..7bedf5f
--- /dev/null
+++ b/src/ZcashHaskell/Keys.hs
@@ -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 .
+-}
+-- |
+-- 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
diff --git a/src/ZcashHaskell/Transparent.hs b/src/ZcashHaskell/Transparent.hs
index bbc10d6..0d605e0 100644
--- a/src/ZcashHaskell/Transparent.hs
+++ b/src/ZcashHaskell/Transparent.hs
@@ -31,4 +31,36 @@
--
module ZcashHaskell.Transparent where
+import Crypto.Hash
+import qualified Data.ByteArray as BA
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
diff --git a/src/ZcashHaskell/Types.hs b/src/ZcashHaskell/Types.hs
index 47df822..759e148 100644
--- a/src/ZcashHaskell/Types.hs
+++ b/src/ZcashHaskell/Types.hs
@@ -25,10 +25,10 @@
-- |
-- Module : ZcashHaskell.Types
--- Copyright : Vergara Technologies 2023
--- License : BOSL
+-- Copyright : 2022-2024 Vergara Technologies
+-- License : LGPL-3
--
--- Maintainer : rene@vergara.network
+-- Maintainer : pitmut@vergara.tech
-- Stability : experimental
-- Portability : unknown
--
@@ -37,12 +37,10 @@
module ZcashHaskell.Types where
import Codec.Borsh
-import Control.Exception (MaskingState(Unmasked))
import Crypto.Hash
import Data.Aeson
import qualified Data.ByteArray as BA
import qualified Data.ByteString as BS
-import Data.ByteString.Base58 (bitcoinAlphabet, encodeBase58)
import qualified Data.ByteString.Char8 as C
import Data.Int
import Data.Structured
@@ -53,6 +51,14 @@ import qualified GHC.Generics as GHC
import qualified Generics.SOP as SOP
-- * 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
data RawData = RawData
{ hrp :: BS.ByteString -- ^ Human-readable part of the Bech32 encoding
@@ -144,35 +150,14 @@ data ZcashNet
data TransparentType
= P2SH
| P2PKH
- deriving (Eq)
+ deriving (Eq, Prelude.Show)
-- | Type to represent a transparent Zcash addresses
data TransparentAddress = TransparentAddress
- { ta_type :: TransparentType
- , ta_net :: ZcashNet
- , ta_bytes :: BS.ByteString
- } deriving (Eq)
-
-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
+ { ta_type :: !TransparentType
+ , ta_net :: !ZcashNet
+ , ta_bytes :: !BS.ByteString
+ } deriving (Eq, Prelude.Show)
-- * Sapling
-- | Type to represent a Sapling Shielded Output as provided by the @getrawtransaction@ RPC method of @zcashd@.
diff --git a/stack.yaml b/stack.yaml
index 33603d4..45d827d 100644
--- a/stack.yaml
+++ b/stack.yaml
@@ -17,7 +17,7 @@
#
# resolver: ./custom-snapshot.yaml
# resolver: https://example.com/snapshots/2018-01-01.yaml
-resolver: lts-21.21
+resolver: lts-21.22
# User packages to be built.
# Various formats can be used as shown in the example below.
diff --git a/test/Spec.hs b/test/Spec.hs
index 8007b19..a360e77 100644
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -22,6 +22,8 @@ import C.Zcash (rustWrapperUADecode)
import Data.Aeson
import Data.Bool (Bool(True))
import qualified Data.ByteString as BS
+import Data.Either (isRight)
+import Data.Foldable (sequenceA_)
import Data.Maybe
import qualified Data.Text as T
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 Data.Word
import GHC.Float.RealFracMethods (properFractionDoubleInteger)
+import Haskoin.Keys.Mnemonic
import Test.Hspec
+import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( decodeSaplingOutput
@@ -38,6 +42,7 @@ import ZcashHaskell.Sapling
, isValidShieldedAddress
, matchSaplingAddress
)
+import ZcashHaskell.Transparent (encodeTransparent)
import ZcashHaskell.Types
( BlockResponse(..)
, DecodedNote(..)
@@ -280,6 +285,18 @@ main = do
Right x ->
rt_id x `shouldBe`
"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
it "succeeds with valid address" $ do
let sa =
@@ -427,5 +444,5 @@ main = do
let msg =
case isValidUnifiedAddress ua of
Nothing -> "Bad UA"
- Just u -> maybe "No transparent" show $ t_rec u
- msg `shouldBe` "Got it"
+ Just u -> maybe "No transparent" encodeTransparent $ t_rec u
+ msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD"
diff --git a/zcash-haskell.cabal b/zcash-haskell.cabal
index 30803ce..8bb6a08 100644
--- a/zcash-haskell.cabal
+++ b/zcash-haskell.cabal
@@ -1,11 +1,11 @@
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
name: zcash-haskell
-version: 0.3.0
+version: 0.4.0
synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at
category: Blockchain
@@ -26,6 +26,8 @@ source-repository head
library
exposed-modules:
C.Zcash
+ ZcashHaskell.DB
+ ZcashHaskell.Keys
ZcashHaskell.Orchard
ZcashHaskell.Sapling
ZcashHaskell.Transparent
@@ -63,6 +65,7 @@ test-suite zcash-haskell-test
aeson
, base >=4.7 && <5
, bytestring
+ , haskoin-core
, hspec
, text
, zcash-haskell