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