Sapling Spending keys and receivers #27

Merged
pitmutt merged 12 commits from rvv040 into dev040 2024-03-10 15:07:10 +00:00
10 changed files with 323 additions and 167 deletions
Showing only changes of commit 1f58f111a6 - Show all commits

View file

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Constants for Zcash protocol - Constants for Zcash protocol
- Types for Spending Keys and Receivers for Sapling and Orchard - Types for Spending Keys and Receivers for Sapling and Orchard
- Function to generate an Orchard receiver - Function to generate an Orchard receiver
- Function to generate a Sapling receiver
### Changed ### Changed

View file

@ -1314,7 +1314,6 @@ dependencies = [
"borsh 0.10.3", "borsh 0.10.3",
"f4jumble", "f4jumble",
"haskell-ffi", "haskell-ffi",
"nom",
"orchard 0.7.1", "orchard 0.7.1",
"proc-macro2", "proc-macro2",
"zcash_address 0.2.0", "zcash_address 0.2.0",

View file

@ -17,7 +17,6 @@ zcash_primitives = "0.13.0"
zcash_client_backend = "0.10.0" zcash_client_backend = "0.10.0"
zip32 = "0.1.0" zip32 = "0.1.0"
proc-macro2 = "1.0.66" proc-macro2 = "1.0.66"
nom = "7.1.3"
[features] [features]
capi = [] capi = []

View file

@ -24,15 +24,23 @@ use haskell_ffi::{
use zip32; use zip32;
use zcash_primitives::{ use zcash_primitives::{
zip32::Scope as SaplingScope, zip32::{
Scope as SaplingScope,
sapling_find_address,
sapling::DiversifierKey
},
zip339::{Count, Mnemonic}, zip339::{Count, Mnemonic},
transaction::components::sapling::{ transaction::components::sapling::{
GrothProofBytes, GrothProofBytes,
OutputDescription, OutputDescription
}, },
sapling::{ sapling::{
PaymentAddress, PaymentAddress,
keys::PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey, keys::{
PreparedIncomingViewingKey as SaplingPreparedIncomingViewingKey,
ExpandedSpendingKey,
FullViewingKey as SaplingFullViewingKey
},
note_encryption::SaplingDomain note_encryption::SaplingDomain
}, },
transaction::Transaction, transaction::Transaction,
@ -49,13 +57,12 @@ use zcash_address::{
ZcashAddress ZcashAddress
}; };
use zcash_client_backend::keys::{ use zcash_client_backend::keys::sapling::{
sapling, ExtendedFullViewingKey,
sapling::ExtendedFullViewingKey, ExtendedSpendingKey
sapling::ExtendedSpendingKey}; };
use zcash_primitives::zip32::{ AccountId, DiversifierIndex }; use zcash_primitives::zip32::{ AccountId, DiversifierIndex };
use std::slice;
use orchard::{ use orchard::{
Action, Action,
@ -624,7 +631,6 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey(
out: *mut u8, out: *mut u8,
out_len: &mut usize out_len: &mut usize
){ ){
println!("Starting extended spending key generation....");
let seed: Vec<u8> = marshall_from_haskell_var(iseed, iseed_len, RW); let seed: Vec<u8> = marshall_from_haskell_var(iseed, iseed_len, RW);
if ( seed.len() != 64 ) { if ( seed.len() != 64 ) {
// invalid seed length // invalid seed length
@ -634,8 +640,7 @@ pub extern "C" fn rust_wrapper_sapling_spendingkey(
// Returns a byte array (169 bytes) // Returns a byte array (169 bytes)
let su8 = &seed; let su8 = &seed;
let seedu8 : &[u8] = &su8; let seedu8 : &[u8] = &su8;
println!("Seed : {:?}\n", &seedu8); let extsk: ExtendedSpendingKey = ExtendedSpendingKey::master(&seedu8);
let extsk: ExtendedSpendingKey = sapling::ExtendedSpendingKey::master(&seedu8);
let extsk_bytes = extsk.to_bytes().to_vec(); let extsk_bytes = extsk.to_bytes().to_vec();
marshall_to_haskell_var(&extsk_bytes, out, out_len, RW); 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( pub extern "C" fn rust_wrapper_sapling_paymentaddress(
extspk: *const u8, extspk: *const u8,
extspk_len: usize, extspk_len: usize,
// divIx: u32, div_ix: u32,
out: *mut u8, out: *mut u8,
out_len: &mut usize out_len: &mut usize
){ ){
let divIx : u32 = 2; let extspk: Vec<u8> = marshall_from_haskell_var(extspk, extspk_len, RW);
println!("Starting paymentAddress generation...."); let expsk = ExpandedSpendingKey::from_spending_key(&extspk);
let extspkb: Vec<u8> = marshall_from_haskell_var(extspk, extspk_len, RW); let fvk = SaplingFullViewingKey::from_expanded_spending_key(&expsk);
if ( extspkb.len() != 169 ) { let dk = DiversifierKey::master(&extspk);
// invalid ExtendedSpenndingKey Array length let result = sapling_find_address(&fvk, &dk, DiversifierIndex::from(div_ix));
println!("Invalid ExtendedSpendingKey...."); match result {
marshall_to_haskell_var(&vec![0], out, out_len, RW); Some((_d, p_address)) => {
} else { marshall_to_haskell_var(&p_address.to_bytes().to_vec(), out, out_len, RW);
// Process },
println!("Extended Spending Key validated, continue ...."); None => {
let extspkbu8 = &extspkb; marshall_to_haskell_var(&vec![0], out, out_len, RW);
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);
} }
} }

View file

@ -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'& { toBorshVar* `BS.ByteString'&
, `Word32'
, getVarBuffer `Buffer (BS.ByteString)'& , getVarBuffer `Buffer (BS.ByteString)'&
} }
-> `()' -> `()'

View file

@ -21,14 +21,13 @@ import C.Zcash
( rustWrapperIsShielded ( rustWrapperIsShielded
, rustWrapperSaplingCheck , rustWrapperSaplingCheck
, rustWrapperSaplingNoteDecode , rustWrapperSaplingNoteDecode
, rustWrapperSaplingPaymentAddress
, rustWrapperSaplingSpendingkey , rustWrapperSaplingSpendingkey
, rustWrapperPaymentAddress
, rustWrapperSaplingVkDecode , rustWrapperSaplingVkDecode
, rustWrapperTxParse , rustWrapperTxParse
) )
import Data.Aeson import Data.Aeson
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.ByteString.Lazy as BL
import Data.HexString (HexString(..), toBytes) import Data.HexString (HexString(..), toBytes)
import Data.Word import Data.Word
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
@ -36,16 +35,18 @@ import Foreign.Rust.Marshall.Variable
, withPureBorshVarBuffer , withPureBorshVarBuffer
) )
import ZcashHaskell.Types import ZcashHaskell.Types
( DecodedNote(..) ( AccountId
, CoinType
, DecodedNote(..)
, RawData(..) , RawData(..)
, RawTxResponse(..) , RawTxResponse(..)
, SaplingSKeyParams(..) , SaplingReceiver
, SaplingSpendingKey(..)
, Seed(..)
, ShieldedOutput(..) , ShieldedOutput(..)
, decodeHexText , decodeHexText
, AccountId
, CoinType
) )
import ZcashHaskell.Utils import ZcashHaskell.Utils (decodeBech32)
-- | Check if given bytesting is a valid encoded shielded address -- | Check if given bytesting is a valid encoded shielded address
isValidShieldedAddress :: BS.ByteString -> Bool isValidShieldedAddress :: BS.ByteString -> Bool
@ -91,16 +92,26 @@ instance FromJSON RawTxResponse where
Just o' -> do Just o' -> do
a <- o' .: "actions" a <- o' .: "actions"
pure $ RawTxResponse i h (getShieldedOutputs h) a ht c b 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 -- | Attempts to obtain a sapling SpendingKey using a HDSeed
genSaplingSpendingKey :: BS.ByteString -> BS.ByteString genSaplingSpendingKey :: Seed -> Maybe SaplingSpendingKey
genSaplingSpendingKey seed = do genSaplingSpendingKey seed = do
let res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed ) if BS.length res == 196
res then Just res
else Nothing
where
res = withPureBorshVarBuffer (rustWrapperSaplingSpendingkey seed)
-- --
-- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey -- | Attempts to generate a sapling Payment Address using an ExtendedSpendingKey
-- | and a Diversifier Index -- | and a Diversifier Index
genSaplingPaymentAddress :: BS.ByteString -> BS.ByteString genSaplingPaymentAddress :: SaplingSpendingKey -> Int -> Maybe SaplingReceiver
genSaplingPaymentAddress extspk = do genSaplingPaymentAddress extspk i =
let pmtaddress = withPureBorshVarBuffer (rustWrapperPaymentAddress extspk ) if BS.length res == 43
pmtaddress then Just res
else Nothing
where
res =
withPureBorshVarBuffer
(rustWrapperSaplingPaymentAddress extspk (fromIntegral i))

View file

@ -346,16 +346,6 @@ data DecodedNote = DecodedNote
deriving anyclass (Data.Structured.Show) deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct DecodedNote 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 -- * Helpers
-- | Helper function to turn a hex-encoded string to bytestring -- | Helper function to turn a hex-encoded string to bytestring
decodeHexText :: String -> BS.ByteString decodeHexText :: String -> BS.ByteString

View file

@ -37,8 +37,8 @@ import Foreign.Marshal.Array (allocaArray, peekArray)
import Foreign.Ptr (Ptr) import Foreign.Ptr (Ptr)
import Data.Word import Data.Word
-- |
-- |
-- | Decode the given bytestring using Bech32 -- | Decode the given bytestring using Bech32
decodeBech32 :: BS.ByteString -> RawData decodeBech32 :: BS.ByteString -> RawData
decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode
@ -88,13 +88,3 @@ makeZebraCall host port m params = do
setRequestHost (E.encodeUtf8 host) $ setRequestHost (E.encodeUtf8 host) $
setRequestMethod "POST" defaultRequest setRequestMethod "POST" defaultRequest
httpJSON myRequest 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)

View file

@ -41,18 +41,21 @@ import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Orchard import ZcashHaskell.Orchard
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingOutput ( decodeSaplingOutput
, genSaplingPaymentAddress
, genSaplingSpendingKey
, getShieldedOutputs , getShieldedOutputs
, isValidSaplingViewingKey , isValidSaplingViewingKey
, isValidShieldedAddress , isValidShieldedAddress
, matchSaplingAddress , matchSaplingAddress
, genSaplingSpendingKey
, genSaplingPaymentAddress
) )
import ZcashHaskell.Transparent import ZcashHaskell.Transparent
--(encodeTransparent) --(encodeTransparent)
import ZcashHaskell.Types import ZcashHaskell.Types
( BlockResponse(..) ( AccountId
, BlockResponse(..)
, CoinType(..) , CoinType(..)
, CoinType
, DecodedNote(..) , DecodedNote(..)
, OrchardAction(..) , OrchardAction(..)
, Phrase(..) , Phrase(..)
@ -62,20 +65,17 @@ import ZcashHaskell.Types
, UnifiedAddress(..) , UnifiedAddress(..)
, UnifiedFullViewingKey(..) , UnifiedFullViewingKey(..)
, decodeHexText , decodeHexText
, AccountId
, CoinType
, getValue , getValue
) )
import ZcashHaskell.Utils import ZcashHaskell.Utils
import Foreign.C.Types
import Data.Word import Data.Word
import Foreign.C.Types
import Haskoin.Crypto.Keys.Extended import Haskoin.Crypto.Keys.Extended
m2bs :: Maybe BS.ByteString -> BS.ByteString m2bs :: Maybe BS.ByteString -> BS.ByteString
m2bs x = fromMaybe "" x m2bs x = fromMaybe "" x
main :: IO () main :: IO ()
main = do main = do
hspec $ do hspec $ do
@ -483,83 +483,251 @@ main = do
msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD" msg `shouldBe` "t1LPWuQnjCRH7JAeEErSXKixcUteLJRJjKD"
describe "Transparent Private and Publicc Key Generation" $ do describe "Transparent Private and Publicc Key Generation" $ do
it "Obtain a transparent extended private key from HDSeed" $ do it "Obtain a transparent extended private key from HDSeed" $ do
let hdseed = [206, 61, 120, 38, let hdseed =
206, 40, 201, 62, [ 206
83, 175, 151, 131, , 61
218, 141, 206, 254, , 120
28, 244, 172, 213, , 38
128, 248, 156, 45, , 206
204, 44, 169, 3, , 40
162, 188, 16, 173, , 201
192, 164, 96, 148, , 62
91, 52, 244, 83, , 83
149, 169, 82, 196, , 175
199, 53, 177, 170, , 151
1, 6, 0, 120, , 131
170, 2, 238, 219, , 218
241, 243, 172, 178, , 141
104, 81, 159, 144 , 206
] :: [Word8] , 254
let xtpvk = genTransparentPrvKey (word8ArrayToByteString hdseed) , 28
let testpvk = XPrvKey 0 "0000000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943" , 244
xtpvk `shouldBe` testpvk , 172
-- describe "Obtain transparent public key from private key" $ do , 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 it "Obtain a transparent extended public key from private key" $ do
let testpvk = XPrvKey 0 "0000000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943" let testpvk =
let testpbk = XPubKey 0 "00000000" 0 "fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb" "279bda9c704f6da479cedb12c7cf773b3a348569dc1cfa6002526bad67674fd737b84a2bdb1199ecab1c9fed1b9a38aba5ba19259c1510d733a2376118515cd8" XPrvKey
let xtpubkIO = genTransparentPubKey testpvk 0
xtpubk <- xtpubkIO "0000000000"
0
"fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb"
"46aa0cd24a6e05709591426a4e682dd5406de4e75a39c0f410ee790403880943"
let testpbk =
XPubKey
0
"00000000"
0
"fb5b9b89d3e9dfdebeaabd15de8fbc7e9a140b7f2de2b4034c2573425d39aceb"
"279bda9c704f6da479cedb12c7cf773b3a348569dc1cfa6002526bad67674fd737b84a2bdb1199ecab1c9fed1b9a38aba5ba19259c1510d733a2376118515cd8"
let xtpubkIO = genTransparentPubKey testpvk
xtpubk <- xtpubkIO
---print $ show xtpubk ---print $ show xtpubk
xtpubk `shouldBe` testpbk xtpubk `shouldBe` testpbk
describe "Sapling SpendingKey test" $ do describe "Sapling SpendingKey test" $ do
it "Call function with parameters" $ do let hdseed =
let hdseed = [206, 61, 120, 38, BS.pack $
206, 40, 201, 62, [ 206
83, 175, 151, 131, , 61
218, 141, 206, 254, , 120
28, 244, 172, 213, , 38
128, 248, 156, 45, , 206
204, 44, 169, 3, , 40
162, 188, 16, 173, , 201
192, 164, 96, 148, , 62
91, 52, 244, 83, , 83
149, 169, 82, 196, , 175
199, 53, 177, 170, , 151
1, 6, 0, 120, , 131
170, 2, 238, 219, , 218
241, 243, 172, 178, , 141
104, 81, 159, 144 , 206
] :: [Word8] , 254
let msg = genSaplingSpendingKey (word8ArrayToByteString hdseed) , 28
let msgArr = BS.unpack msg , 244
if (length msgArr) == 169 , 172
then True , 213
else False , 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 describe "Sapling Payment Address generation test" $ do
it "Call genSaplingPaymentAddress" $ do it "Call genSaplingPaymentAddress" $ do
let hdseed1 = [206, 61, 120, 38, let hdseed1 =
206, 40, 201, 62, [ 206
83, 175, 151, 131, , 61
218, 141, 206, 254, , 120
28, 244, 172, 213, , 38
128, 248, 156, 45, , 206
204, 44, 169, 3, , 40
162, 188, 16, 173, , 201
192, 164, 96, 148, , 62
91, 52, 244, 83, , 83
149, 169, 82, 196, , 175
199, 53, 177, 170, , 151
1, 6, 0, 120, , 131
170, 2, 238, 219, , 218
241, 243, 172, 178, , 141
104, 81, 159, 144 , 206
] :: [Word8] , 254
let msg1 = genSaplingSpendingKey (word8ArrayToByteString hdseed1) , 28
let pmtaddress = genSaplingPaymentAddress msg1 --(word8ArrayToByteString hdseed1) , 244
let msgArr = BS.unpack pmtaddress , 172
if (length msgArr) == 43 , 213
then True , 128
else False , 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 -- | Properties
prop_PhraseLength :: Int -> Property prop_PhraseLength :: Int -> Property
@ -590,6 +758,14 @@ prop_OrchardReceiver c i j =
let sk = genOrchardSpendingKey (fromMaybe "" s) c i let sk = genOrchardSpendingKey (fromMaybe "" s) c i
return $ genOrchardReceiver j (fromMaybe "" sk) =/= Nothing 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 -- | Generators
genOrcArgs :: Gen (CoinType, Int, Int) genOrcArgs :: Gen (CoinType, Int, Int)
genOrcArgs = do genOrcArgs = do
@ -597,4 +773,7 @@ genOrcArgs = do
j <- arbitrarySizedNatural j <- arbitrarySizedNatural
c <- elements [MainNetCoin, TestNetCoin, RegTestNetCoin] c <- elements [MainNetCoin, TestNetCoin, RegTestNetCoin]
return (c, i, j) return (c, i, j)
genSapArgs :: Gen Int
genSapArgs = choose (1, 50)
-- | Arbitrary instances -- | Arbitrary instances

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: zcash-haskell name: zcash-haskell
version: 0.4.4.0 version: 0.4.4.1
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