Compare commits

...

16 commits

17 changed files with 2390 additions and 48 deletions

View file

@ -5,9 +5,23 @@ 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).
## [Unreleased] ## [0.1.0] - 2023-06-14
### Added ### Added
- Function `decodeHexText`
- Function `decodeBech32`
- Function `f4Jumble` - Function `f4Jumble`
- Function `f4UnJumble`
- Function `isValidShieldedAddress`
- Function `isValidSaplingViewingKey`
- Function `matchSaplingAddress`
- Function `isValidUnifiedAddress` - Function `isValidUnifiedAddress`
- Function `decodeUfvk`
- Function `decryptOrchardAction`
- Type `RawData`
- Type `ShieldedOutput`
- Type `OrchardAction`
- Type `OrchardDecodedAction`
- Type `UnifiedFullViewingKey`

14
Makefile Normal file
View file

@ -0,0 +1,14 @@
rustlib := librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug
.PHONY: all
all: haskell
test: test/Spec.hs haskell
stack test
haskell: src/ZcashHaskell/Orchard.hs src/ZcashHaskell/Sapling.hs src/ZcashHaskell/Types.hs src/ZcashHaskell/Utils.hs src/C/Zcash.chs package.yaml stack.yaml $(rustlib)/rustzcash_wrapper.h $(rustlib)/librustzcash_wrapper.a $(rustlib)/librustzcash_wrapper.so $(rustlib)/rustzcash_wrapper-uninstalled.pc
stack build
$(rustlib)/rustzcash_wrapper.h: librustzcash-wrapper/src/lib.rs librustzcash-wrapper/Cargo.toml
cd librustzcash-wrapper && cargo +nightly cbuild

View file

@ -1 +1,7 @@
# haskell-wrapper # zcash-haskell
A Haskell library to interact with the Zcash blockchain.
## Installation

View file

@ -1,6 +0,0 @@
{-# LANGUAGE OverloadedStrings #-}
module Main where
main :: IO ()
main = putStrLn "Zcash Utilities for Haskell"

5
configure vendored
View file

@ -1,4 +1,5 @@
#!/bin/bash #!/bin/bash
export PKG_CONFIG_PATH=$(pwd)/librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug echo "export PKG_CONFIG_PATH=$(pwd)/librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug:\$PKG_CONFIG_PATH" | tee -a ~/.bashrc
export LD_LIBRARY_PATH=$(pwd)/librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug echo "export LD_LIBRARY_PATH=$(pwd)/librustzcash-wrapper/target/x86_64-unknown-linux-gnu/debug:\$LD_LIBRARY_PATH" | tee -a ~/.bashrc
source ~/.bashrc

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,11 @@ haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274"
f4jumble = "0.1" f4jumble = "0.1"
zcash_address = "0.2.0" zcash_address = "0.2.0"
borsh = "0.10" borsh = "0.10"
bech32 = "0.9.1"
orchard = "0.4.0"
zcash_note_encryption = "0.3.0"
zcash_primitives = "0.11.0"
zcash_client_backend = "0.9.0"
[features] [features]
capi = [] capi = []

View file

@ -8,21 +8,180 @@ use std::{
use f4jumble; use f4jumble;
use borsh::{BorshDeserialize, BorshSerialize}; use borsh::{BorshDeserialize, BorshSerialize};
use haskell_ffi::{ use haskell_ffi::{
error::Result, error::Result,
from_haskell::{marshall_from_haskell_var}, from_haskell::{marshall_from_haskell_var, marshall_from_haskell_fixed},
to_haskell::{marshall_to_haskell_var, marshall_to_haskell_fixed}, to_haskell::{marshall_to_haskell_var, marshall_to_haskell_fixed},
FromHaskell, HaskellSize, ToHaskell FromHaskell, HaskellSize, ToHaskell
}; };
use zcash_primitives::{
zip32::Scope as SaplingScope,
transaction::components::sapling::{
GrothProofBytes,
OutputDescription,
CompactOutputDescription
},
sapling::{
value::ValueCommitment as SaplingValueCommitment,
keys::FullViewingKey as SaplingViewingKey,
note_encryption::SaplingDomain,
PaymentAddress,
note::ExtractedNoteCommitment as SaplingExtractedNoteCommitment
},
consensus::{
MainNetwork,
BlockHeight
}
};
use zcash_address::{ use zcash_address::{
Network, Network,
unified::{Address, Encoding} unified::{Address, Encoding, Ufvk, Container, Fvk},
ZcashAddress
};
use zcash_client_backend::keys::sapling::ExtendedFullViewingKey;
use orchard::{
Action,
keys::{FullViewingKey, PreparedIncomingViewingKey, Scope},
note::{Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment},
note_encryption::OrchardDomain,
primitives::redpallas::{VerificationKey, SpendAuth, Signature},
value::ValueCommitment
};
use zcash_note_encryption::EphemeralKeyBytes;
use bech32::{
decode,
u5,
FromBase32,
ToBase32,
Variant
}; };
pub enum RW {} pub enum RW {}
pub const RW: PhantomData<RW> = PhantomData; pub const RW: PhantomData<RW> = PhantomData;
#[derive(BorshSerialize, BorshDeserialize)]
pub struct RawData {
hrp: Vec<u8>,
bytes: Vec<u8>
}
impl<RW> ToHaskell<RW> for RawData {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
//impl<RW> FromHaskell<RW> for RawData {
//fn from_haskell(buf: &mut &[u8], _tag: PhantomData<RW>) -> Result<Self> {
//let x = RawData::deserialize(buf)?;
//Ok(x)
//}
//}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct HshieldedOutput {
cv: Vec<u8>,
cmu: Vec<u8>,
eph_key: Vec<u8>,
enc_txt: Vec<u8>,
out_txt: Vec<u8>,
proof: Vec<u8>
}
impl<RW> FromHaskell<RW> for HshieldedOutput {
fn from_haskell(buf: &mut &[u8], _tag: PhantomData<RW>) -> Result<Self> {
let x = HshieldedOutput::deserialize(buf)?;
Ok(x)
}
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Haction {
nf: Vec<u8>,
rk: Vec<u8>,
cmx: Vec<u8>,
eph_key: Vec<u8>,
enc_txt: Vec<u8>,
out_txt: Vec<u8>,
cv: Vec<u8>,
auth: Vec<u8>
}
impl<RW> FromHaskell<RW> for Haction {
fn from_haskell(buf: &mut &[u8], _tag: PhantomData<RW>) -> Result<Self> {
let x = Haction::deserialize(buf)?;
Ok(x)
}
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Hnote {
note: u64,
recipient: Vec<u8>,
memo: Vec<u8>
}
impl<RW> ToHaskell<RW> for Hnote {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Hufvk {
net: u8,
orchard: Vec<u8>,
sapling: Vec<u8>,
transparent: Vec<u8>
}
impl<RW> ToHaskell<RW> for Hufvk {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
impl Hufvk {
fn add_key_section(&mut self, fvk: &Fvk) {
if let Fvk::Orchard(v) = fvk {
self.orchard = v.to_vec();
}
if let Fvk::Sapling(w) = fvk {
self.sapling = w.to_vec();
}
if let Fvk::P2pkh(x) = fvk {
self.transparent = x.to_vec();
}
}
}
#[derive(BorshSerialize, BorshDeserialize)]
pub struct Hsvk {
vk: Vec<u8>,
ovk: Vec<u8>
}
impl<RW> ToHaskell<RW> for Hsvk {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
fn to_array<T, const N: usize>(v: Vec<T>) -> [T; N] {
v.try_into().unwrap_or_else(|v: Vec<T>| panic!("Expected a Vec of length {} but it was {}", N, v.len()))
}
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_f4jumble( pub extern "C" fn rust_wrapper_f4jumble(
@ -35,6 +194,17 @@ pub extern "C" fn rust_wrapper_f4jumble(
marshall_to_haskell_var(&result, out, out_len, RW); marshall_to_haskell_var(&result, out, out_len, RW);
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_f4unjumble(
input: *const u8,
input_len: usize,
out: *mut u8,
out_len: &mut usize) {
let input: Vec<u8> = marshall_from_haskell_var(input, input_len, RW);
let result = f4jumble::f4jumble_inv(&input).unwrap();
marshall_to_haskell_var(&result, out, out_len, RW);
}
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_ua_decode( pub extern "C" fn rust_wrapper_ua_decode(
input: *const u8, input: *const u8,
@ -43,3 +213,174 @@ pub extern "C" fn rust_wrapper_ua_decode(
Address::decode(&input).is_ok() Address::decode(&input).is_ok()
//marshall_to_haskell_var(&result, out, out_len, RW); //marshall_to_haskell_var(&result, out, out_len, RW);
} }
#[no_mangle]
pub extern "C" fn rust_wrapper_shielded_decode(
input: *const u8,
input_len: usize) -> bool {
let input: String = marshall_from_haskell_var(input, input_len, RW);
ZcashAddress::try_from_encoded(&input).is_ok()
}
#[no_mangle]
pub extern "C" fn rust_wrapper_bech32decode(
input: *const u8,
input_len: usize,
out: *mut u8,
out_len: &mut usize
) {
let input: String = marshall_from_haskell_var(input, input_len, RW);
let decodedBytes = bech32::decode(&input);
match decodedBytes {
Ok((hrp, bytes, variant)) => {
let rd = RawData {hrp: hrp.into(), bytes: Vec::<u8>::from_base32(&bytes).unwrap()};
marshall_to_haskell_var(&rd, out, out_len, RW);
}
Err(_e) => {
let rd1 = RawData {hrp: "fail".into(), bytes: vec![0]};
marshall_to_haskell_var(&rd1, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_svk_decode(
input: *const u8,
input_len: usize
) -> bool {
let input: Vec<u8> = marshall_from_haskell_var(input, input_len, RW);
let svk = ExtendedFullViewingKey::read(&*input);
match svk {
Ok(k) => {
true
}
Err(e) => {
print!("{}", e);
false
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_svk_check_address(
key_input: *const u8,
key_input_len: usize,
address_input: *const u8,
address_input_len: usize
) -> bool {
let key_input: Vec<u8> = marshall_from_haskell_var(key_input, key_input_len, RW);
let address_input: Vec<u8> = marshall_from_haskell_var(address_input, address_input_len, RW);
let svk = ExtendedFullViewingKey::read(&*key_input);
let sa = PaymentAddress::from_bytes(&to_array(address_input)).unwrap();
match svk {
Ok(k) => {
let (div_index, def_address) = k.default_address();
sa == def_address
}
Err(e) => {
false
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_ufvk_decode(
input: *const u8,
input_len: usize,
out: *mut u8,
out_len: &mut usize
) {
let input: String = marshall_from_haskell_var(input, input_len, RW);
let dec_key = Ufvk::decode(&input);
match dec_key {
Ok((n, ufvk)) => {
let x = match n {
Network::Main => 1,
Network::Test => 2,
Network::Regtest => 3
};
let mut hk = Hufvk { net: x, orchard: vec![0], sapling: vec![0], transparent: vec![0] };
let fvks = ufvk.items();
fvks.iter().for_each(|k| hk.add_key_section(k));
marshall_to_haskell_var(&hk, out, out_len, RW);
}
Err(_e) => {
let hk0 = Hufvk { net: 0, orchard: vec![0], sapling: vec![0], transparent: vec![0] };
marshall_to_haskell_var(&hk0, out, out_len, RW);
}
}
}
//#[no_mangle]
//pub extern "C" fn rust_wrapper_sapling_note_decrypt(
//key: *const u8,
//key_len: usize,
//note: *const u8,
//note_len: usize,
//out: *mut u8,
//out_len: &mut usize
//){
//let evk: Vec<u8> = marshall_from_haskell_var(key, key_len, RW);
//let note_input: HshieldedOutput = marshall_from_haskell_var(note,note_len,RW);
//let svk = ExtendedFullViewingKey::read(&*evk);
//match svk {
//Ok(k) => {
//let domain = SaplingDomain::for_height(MainNetwork, BlockHeight::from_u32(2000000));
//let action: CompactOutputDescription = CompactOutputDescription {
//ephemeral_key: EphemeralKeyBytes(to_array(note_input.eph_key)),
//cmu: SaplingExtractedNoteCommitment::from_bytes(&to_array(note_input.cmu)).unwrap(),
//enc_ciphertext: to_array(note_input.enc_txt)
//};
//let fvk = k.to_diversifiable_full_viewing_key().to_ivk(SaplingScope::External);
//let result = zcash_note_encryption::try_note_decryption(&domain, &ivk, &action);
//}
//Err(_e) => {
//let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
//marshall_to_haskell_var(&hn0, out, out_len, RW);
//}
//}
//}
#[no_mangle]
pub extern "C" fn rust_wrapper_orchard_note_decrypt(
key: *const u8,
key_len: usize,
note: *const u8,
note_len: usize,
out: *mut u8,
out_len: &mut usize
){
let fvk_input: Vec<u8> = marshall_from_haskell_var(key, key_len, RW);
let note_input: Haction = marshall_from_haskell_var(note, note_len, RW);
let action: Action<Signature<SpendAuth>> = Action::from_parts(
Nullifier::from_bytes(&to_array(note_input.nf)).unwrap(),
VerificationKey::try_from(to_array(note_input.rk)).unwrap(),
ExtractedNoteCommitment::from_bytes(&to_array(note_input.cmx)).unwrap(),
TransmittedNoteCiphertext {epk_bytes: to_array(note_input.eph_key), enc_ciphertext: to_array(note_input.enc_txt), out_ciphertext: to_array(note_input.out_txt)},
ValueCommitment::from_bytes(&to_array(note_input.cv)).unwrap(),
Signature::from(to_array(note_input.auth)));
let fvk_array = to_array(fvk_input);
let domain = OrchardDomain::for_nullifier(*action.nullifier());
let dec_fvk = FullViewingKey::from_bytes(&fvk_array);
match dec_fvk {
Some(fvk) => {
let ivk = fvk.to_ivk(Scope::External);
let pivk = PreparedIncomingViewingKey::new(&ivk);
let result = zcash_note_encryption::try_note_decryption(&domain, &pivk, &action);
match result {
Some((n, r, m)) => {
let hn = Hnote {note: n.value().inner(), recipient: r.to_raw_address_bytes().to_vec(), memo: m.to_vec() };
marshall_to_haskell_var(&hn, out, out_len, RW);
}
None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
marshall_to_haskell_var(&hn0, out, out_len, RW);
}
}
},
None => {
let hn0 = Hnote { note: 0, recipient: vec![0], memo: vec![0] };
marshall_to_haskell_var(&hn0, out, out_len, RW);
}
}
}

View file

@ -9,6 +9,7 @@ copyright: "(c)2023 Vergara Technologies LLC"
extra-source-files: extra-source-files:
- README.md - README.md
- CHANGELOG.md - CHANGELOG.md
- configure
# Metadata used when publishing your package # Metadata used when publishing your package
synopsis: Utilities to interact with the Zcash blockchain synopsis: Utilities to interact with the Zcash blockchain
@ -26,9 +27,10 @@ library:
source-dirs: src source-dirs: src
dependencies: dependencies:
- bytestring - bytestring
- borsh - borsh >= 0.2
- text - text
- foreign-rust - foreign-rust
- generics-sop
pkg-config-dependencies: pkg-config-dependencies:
- rustzcash_wrapper-uninstalled - rustzcash_wrapper-uninstalled
@ -44,3 +46,4 @@ tests:
- zcash-haskell - zcash-haskell
- hspec - hspec
- bytestring - bytestring
- text

View file

@ -1,4 +1,6 @@
{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
@ -11,6 +13,7 @@ import qualified Data.ByteString as BS
import Codec.Borsh import Codec.Borsh
import Data.Text (Text) import Data.Text (Text)
import Data.Word import Data.Word
import Data.Int
import Data.Structured import Data.Structured
import Foreign.C.Types import Foreign.C.Types
import Foreign.Rust.Marshall.External import Foreign.Rust.Marshall.External
@ -18,16 +21,21 @@ import Foreign.Rust.Marshall.Fixed
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
import Foreign.Rust.Serialisation.Raw import Foreign.Rust.Serialisation.Raw
import Foreign.Rust.Serialisation.Raw.Base16 import Foreign.Rust.Serialisation.Raw.Base16
import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
import ZcashHaskell.Types
newtype CodedString = CodedString BS.ByteString
deriving (Eq)
deriving newtype (BorshSize, ToBorsh, FromBorsh)
deriving newtype (IsRaw)
deriving (Prelude.Show, Data.Structured.Show) via AsBase16 CodedString
{# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble {# fun unsafe rust_wrapper_f4jumble as rustWrapperF4Jumble
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (CodedString)'& , getVarBuffer `Buffer (BS.ByteString)'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_f4unjumble as rustWrapperF4UnJumble
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer (BS.ByteString)'&
} }
-> `()' -> `()'
#} #}
@ -37,3 +45,44 @@ newtype CodedString = CodedString BS.ByteString
} }
-> `Bool' -> `Bool'
#} #}
{# fun pure unsafe rust_wrapper_shielded_decode as rustWrapperIsShielded
{ toBorshVar* `BS.ByteString'&
}
-> `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'&
}
-> `Bool'
#}
{# fun pure unsafe rust_wrapper_svk_check_address as rustWrapperSaplingCheck
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'&
}
-> `Bool'
#}
{# fun unsafe rust_wrapper_ufvk_decode as rustWrapperUfvkDecode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer UnifiedFullViewingKey'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `OrchardAction'&
, getVarBuffer `Buffer OrchardDecodedAction'&
}
-> `()'
#}

View file

@ -1,15 +0,0 @@
module Zcash
( f4Jumble
, isValidUnifiedAddress
) where
import C.Zcash (CodedString, rustWrapperF4Jumble, rustWrapperIsUA)
import qualified Data.ByteString as BS
import Foreign.Rust.Marshall.Fixed
import Foreign.Rust.Marshall.Variable
f4Jumble :: BS.ByteString -> CodedString
f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble
isValidUnifiedAddress :: BS.ByteString -> Bool
isValidUnifiedAddress = rustWrapperIsUA

View file

@ -0,0 +1,34 @@
module ZcashHaskell.Orchard where
import C.Zcash
( rustWrapperIsUA
, rustWrapperOrchardNoteDecode
, rustWrapperUfvkDecode
)
import qualified Data.ByteString as BS
import Foreign.Rust.Marshall.Variable
import ZcashHaskell.Types
-- | Check if given bytestring is a valid encoded unified address
isValidUnifiedAddress :: BS.ByteString -> Bool
isValidUnifiedAddress = rustWrapperIsUA
-- | Attempt to decode the given bytestring into a Unified Full Viewing Key
decodeUfvk :: BS.ByteString -> Maybe UnifiedFullViewingKey
decodeUfvk str =
case net decodedKey of
0 -> Nothing
_ -> Just decodedKey
where
decodedKey = (withPureBorshVarBuffer . rustWrapperUfvkDecode) str
decryptOrchardAction ::
OrchardAction -> UnifiedFullViewingKey -> Maybe OrchardDecodedAction
decryptOrchardAction encAction key =
case a_value decodedAction of
0 -> Nothing
_ -> Just decodedAction
where
decodedAction =
withPureBorshVarBuffer $
rustWrapperOrchardNoteDecode (o_key key) encAction

View file

@ -0,0 +1,20 @@
module ZcashHaskell.Sapling where
import C.Zcash
( rustWrapperIsShielded
, rustWrapperSaplingCheck
, rustWrapperSaplingVkDecode
)
import qualified Data.ByteString as BS
-- | Check if given bytesting is a valid encoded shielded address
isValidShieldedAddress :: BS.ByteString -> Bool
isValidShieldedAddress = rustWrapperIsShielded
-- | Check if given bytestring is a valid Sapling viewing key
isValidSaplingViewingKey :: BS.ByteString -> Bool
isValidSaplingViewingKey = rustWrapperSaplingVkDecode
-- | Check if the given bytestring for the Sapling viewing key matches the second bytestring for the address
matchSaplingAddress :: BS.ByteString -> BS.ByteString -> Bool
matchSaplingAddress = rustWrapperSaplingCheck

75
src/ZcashHaskell/Types.hs Normal file
View file

@ -0,0 +1,75 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE UndecidableInstances #-}
module ZcashHaskell.Types where
import qualified Data.ByteString as BS
import Codec.Borsh
import Data.Word
import Data.Int
import Data.Structured
import qualified Generics.SOP as SOP
import qualified GHC.Generics as GHC
data RawData = RawData { hrp :: BS.ByteString, bytes :: BS.ByteString}
deriving stock (Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawData
data UnifiedFullViewingKey =
UnifiedFullViewingKey
{ net :: Word8
, o_key :: BS.ByteString
, s_key :: BS.ByteString
, t_key :: BS.ByteString
}
deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey
data ShieldedOutput =
ShieldedOutput
{ s_cv :: BS.ByteString
, s_cmu :: BS.ByteString
, s_ephKey :: BS.ByteString
, s_encCipherText :: BS.ByteString
, s_outCipherText :: BS.ByteString
, s_proof :: BS.ByteString
}
deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct ShieldedOutput
data OrchardAction =
OrchardAction
{ nf :: BS.ByteString
, rk :: BS.ByteString
, cmx :: BS.ByteString
, eph_key :: BS.ByteString
, enc_ciphertext :: BS.ByteString
, out_ciphertext :: BS.ByteString
, cv :: BS.ByteString
, auth :: BS.ByteString
}
deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardAction
data OrchardDecodedAction =
OrchardDecodedAction
{ a_value :: Int64
, a_recipient :: BS.ByteString
, a_memo :: BS.ByteString
}
deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct OrchardDecodedAction

34
src/ZcashHaskell/Utils.hs Normal file
View file

@ -0,0 +1,34 @@
module ZcashHaskell.Utils where
import C.Zcash
( rustWrapperBech32Decode
, rustWrapperF4Jumble
, rustWrapperF4UnJumble
)
import qualified Data.ByteString as BS
import Foreign.Rust.Marshall.Variable
import ZcashHaskell.Types
-- | Helper function to turn a hex-encoded strings to bytestring
decodeHexText :: String -> BS.ByteString
decodeHexText h = BS.pack $ hexRead h
where
hexRead hexText
| null chunk = []
| otherwise =
fromIntegral (read ("0x" <> chunk)) : hexRead (drop 2 hexText)
where
chunk = take 2 hexText
-- | Decode the given bytestring using Bech32
decodeBech32 :: BS.ByteString -> RawData
decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode
-- | Apply the F4Jumble transformation to the given bytestring
f4Jumble :: BS.ByteString -> BS.ByteString
f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble
-- | Apply the inverse F4Jumble transformation to the given bytestring
f4UnJumble :: BS.ByteString -> BS.ByteString
f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble

View file

@ -1,14 +1,33 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
import C.Zcash (CodedString(CodedString), rustWrapperIsUA) import C.Zcash (rustWrapperIsUA)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.Text.Encoding as E
import Data.Word import Data.Word
import Test.Hspec import Test.Hspec
import Zcash import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( isValidSaplingViewingKey
, isValidShieldedAddress
, matchSaplingAddress
)
import ZcashHaskell.Types
( OrchardAction(..)
, OrchardDecodedAction(..)
, RawData(..)
, UnifiedFullViewingKey(..)
)
import ZcashHaskell.Utils
main :: IO () main :: IO ()
main = do main = do
hspec $ do hspec $ do
describe "Bech32" $ do
let s = "bech321qqqsyrhqy2a"
let decodedString = decodeBech32 s
it "hrp matches" $ do hrp decodedString `shouldBe` "bech32"
it "data matches" $ do
bytes decodedString `shouldBe` BS.pack ([0x00, 0x01, 0x02] :: [Word8])
describe "F4Jumble" $ do describe "F4Jumble" $ do
it "jumble a string" $ do it "jumble a string" $ do
let input = let input =
@ -111,7 +130,140 @@ main = do
, 0x8d , 0x8d
, 0x22 , 0x22
] :: [Word8] ] :: [Word8]
CodedString (BS.pack out) `shouldBe` f4Jumble (BS.pack input) BS.pack out `shouldBe` f4Jumble (BS.pack input)
it "unjumble a string" $ do
let input =
[ 0x5d
, 0x7a
, 0x8f
, 0x73
, 0x9a
, 0x2d
, 0x9e
, 0x94
, 0x5b
, 0x0c
, 0xe1
, 0x52
, 0xa8
, 0x04
, 0x9e
, 0x29
, 0x4c
, 0x4d
, 0x6e
, 0x66
, 0xb1
, 0x64
, 0x93
, 0x9d
, 0xaf
, 0xfa
, 0x2e
, 0xf6
, 0xee
, 0x69
, 0x21
, 0x48
, 0x1c
, 0xdd
, 0x86
, 0xb3
, 0xcc
, 0x43
, 0x18
, 0xd9
, 0x61
, 0x4f
, 0xc8
, 0x20
, 0x90
, 0x5d
, 0x04
, 0x2b
] :: [Word8]
let out =
[ 0x03
, 0x04
, 0xd0
, 0x29
, 0x14
, 0x1b
, 0x99
, 0x5d
, 0xa5
, 0x38
, 0x7c
, 0x12
, 0x59
, 0x70
, 0x67
, 0x35
, 0x04
, 0xd6
, 0xc7
, 0x64
, 0xd9
, 0x1e
, 0xa6
, 0xc0
, 0x82
, 0x12
, 0x37
, 0x70
, 0xc7
, 0x13
, 0x9c
, 0xcd
, 0x88
, 0xee
, 0x27
, 0x36
, 0x8c
, 0xd0
, 0xc0
, 0x92
, 0x1a
, 0x04
, 0x44
, 0xc8
, 0xe5
, 0x85
, 0x8d
, 0x22
] :: [Word8]
f4UnJumble (BS.pack out) `shouldBe` BS.pack input
describe "Sapling address" $ do
it "succeeds with valid address" $ do
let sa =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8"
isValidShieldedAddress sa `shouldBe` True
it "fails with invalid address" $ do
let sa =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvffake"
isValidShieldedAddress sa `shouldBe` False
describe "Decode Sapling VK" $ do
let vk =
"zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwcpjs"
let sa =
"zs1g2ne5w2r8kvalwzngsk3kfzppx3qcx5560pnfmw9rj5xfd3zfg9dkm7hyxnfyhc423fev5wuue4"
let sa' =
"zs17faa6l5ma55s55exq9rnr32tu0wl8nmqg7xp3e6tz0m5ajn2a6yxlc09t03mqdmvyphavvf3sl8"
let rawKey = decodeBech32 vk
let rawSa = decodeBech32 sa
let rawSa' = decodeBech32 sa'
it "is mainnet" $ do hrp rawKey `shouldBe` "zxviews"
it "is valid Sapling extended full viewing key" $ do
isValidSaplingViewingKey (bytes rawKey) `shouldBe` True
it "matches the right Sapling address" $ do
matchSaplingAddress (bytes rawKey) (bytes rawSa) `shouldBe` True
it "doesn't match the wrong Sapling address" $ do
matchSaplingAddress (bytes rawKey) (bytes rawSa') `shouldBe` False
describe "Decode invalid Sapling VK" $ do
let vk =
"zxviews1qdjagrrpqqqqpq8es75mlu6rref0qyrstchf8dxzeygtsejwfqu8ckhwl2qj5m8am7lmupxk3vkvdjm8pawjpmesjfapvsqw96pa46c2z0kk7letrxf7mkltwz54fwpxc7kc79mm5kce3rwn5ssl009zwsra2spppwgrx25s9k5hq65f69l4jz2tjmqgy0pl49qmtaj3nudk6wglwe2hpa327hydlchtyq9av6wjd6hu68e04ahwk9a9n2kt0kj3nj99nue65awtu5cwwfake"
let rawKey = decodeBech32 vk
it "is not mainnet" $ do hrp rawKey `shouldBe` "fail"
describe "Unified address" $ do describe "Unified address" $ do
it "succeeds with correct UA" $ do it "succeeds with correct UA" $ do
let ua = let ua =
@ -121,3 +273,69 @@ main = do
let ua = let ua =
"u1salpdyefbreakingtheaddressh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur" "u1salpdyefbreakingtheaddressh0h9v6qjr478k80amtkqkws5pr408lxt2953dpprvu06mahxt99cv65fgsm7sw8hlchplfg5pl89ur"
isValidUnifiedAddress ua `shouldBe` False isValidUnifiedAddress ua `shouldBe` False
describe "Decode UVK from YWallet" $ do
let uvk =
"uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
let res = decodeUfvk uvk
it "is mainnet" $ do maybe 0 net res `shouldBe` 1
it "has Orchard key" $ do BS.length (maybe "" o_key res) `shouldBe` 96
it "has Sapling key" $ do BS.length (maybe "" s_key res) `shouldBe` 128
it "does not have Transparent key" $ do
BS.length (maybe "" t_key res) `shouldBe` 1
describe "Decode bad UVK" $ do
it "should fail" $ do
let fakeUvk =
"uview1u83changinga987bundchofch4ract3r5x8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
decodeUfvk fakeUvk `shouldBe` Nothing
describe "Decode Orchard tx" $ do
let uvk =
"uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
let res = decodeUfvk uvk
let a =
OrchardAction
(decodeHexText
"248b16d98dfa33f7ba69a0610a63b606699da76c288840b81d7691ee42764416")
(decodeHexText
"17fcc27cce560733edaf91439a8020c4a029a4e7d5893ce024d5ff4b40bbd0a9")
(decodeHexText
"34796d541864832acca43f083892e98a46c912802d5643672d3f25bea177c61c")
(decodeHexText
"a6d2ca10e3fc7446e372266ef45ee3dc0ba373bd378e6bf3092519a7f272bd8c")
(decodeHexText
"08beafdf59110b5d045e4acc13731ef1a27bfa3a9cabe1d575640c18f18ee6697fbb132d36e982ae3eadf5f37fd35f42c2bb07def14759deab1fbe2f98dc1d5913e4a6ef388b714e2cfd6d89ba2302800e02ab5f45e0e02e3895448518cd8afd2c37bb48a66d8b988a37de9d0838d92876894a311bb9f314ba842e5c18ff7a3d8c7f0ff1a7209e2d661595db8f4a4aa267b9593258914bf63c09286eeda7c9b27ddbb4646208c0d03a8fbdc5d96633335a5a65316f5b25189bdce735bdea7e900de56d3b475ae51b7c35eb7ae79ba104baeb0a5a09d1cd8bb347ab34fb26d62ddbf024f5394710626ec0a665b9c917e65b00256db635145164a0329db7bc5358f435d573b2662adf8a6128801825ec8fb7d8aeef567d35c875ddd784fceb7620355e3f056a648b39b4b2d29a1f5e7b7c4ec5fd2b1874ff1e832b308f8644e83878d90582b9a6fd6c293e19dd3e24dbe1b4c15c96608169843d46551900a8cb787b15f0f1696b736dd4c8ebacf1e3288b14e469bdc004fa8557d6b1395700eaba59334906bb012f876e4cd7acd2157719ebd2e28bd0cd4ab4ac458f8848e1c30e729803dd47102200fe703932a15c3618862ec83b40d3aa0ec2343641bcb9afbf931ab21aa4afdbe7e51deca24283c2ccab0eef6e07aac5a4bf3a775bf7d2ddfc8d8766c3bf8e35df1435cf515d93c3b9549477bd9f53d133f05dd256fbcc0b13a63e3e7f8cce6301ab4f19c114f5af079f8c581537458e861b553218a890ea3e77fb99781c7088cd43c67c155ec611c1148721cab5fd0168e4a5ec390b506ec44145474c")
(decodeHexText
"1e40d33446d9f0f0fad40f8829c1ffe860c11c3439e2c15d37c6c40282f9e933dc01798c800e6c92edb4d20478b92559510eda67f3855f68f5ab22ca31e1885c7fa9d4c9ebfb62ceb5e73267bcad0ba7")
(decodeHexText
"63d0d6e8e92691f700bf8af246dcd4ae1041b13e3969f7a9d819a06e0f9429bc")
(decodeHexText
"fe362be160accf2794841c244e8d80bbeb80b9bc95bb653d297a98d32bddf5a05dd5f874891d55924a83f722f75f576f63796770c31074067694cffb2cce7a2a")
let b =
OrchardAction
(decodeHexText
"8921446787f1bd28fa0e4cc5c945ea7fc71165a25f40cd2a325dae0c4467d12c")
(decodeHexText
"240b08b7861aa78989c68cbedd0038af9b3e3456bdc7ff582d597df571d54da2")
(decodeHexText
"e1bc8ccba69ab9f429bf735417aa005cf439d27500b0d3086dbf1be764b42a36")
(decodeHexText
"c89c58ef8553e7d09ba4090654edd1a8c98763c44d3dfb9dad18286c7ef363ae")
(decodeHexText
"0eee1ca5a3a4959cd4b8bc277e6e633f950680c4acb978c14ad8d944a784f46771c9d666a203ca3ac693943d79dd23f8b76a734a62e81932cbe98e8c851f47a11aaef50249e53151f38f88262a4bae8cf26f5f8b2db1d165aff9b57b64713a677c167608585c038e34ca7bbe468e5f86475ccec0a4a8b9a43b56e342e77a6bd09415787c9f4a1c6f20599f57545f1ac32c3a338d7a5bb2d35456adb880cb65c1455969e10df5d94b8c74b244e7093b1a88cc10697a7c2f4d34b6eae3296e64b820573b4d52e06b4427af5b8f5d6722d3a93fd85da615fceac732976ad2c1be4150b4821c149521f5419ea0746fb132d47f593cfc8a3aab6b2b4480c12fadf21280ccd3142e7188d9e5aef3fcd8c5dc0c066dc975bead023ef7f89a486b615b146110ae68b703a8349a5fc225b26a08b2adaf36fb44c9ad1be59d7ced134eb84e3f0b4aec19b71b2d26e910628a11446b97c5e6bbf97e1befa4e04b5947f83c65161b92f58088d28e57adc2a2873e27008e29772c5803502842045cb355d1ea5a9d27c2683dcb38cb49d26af39625ba99b1342f700387b939e7ff6c129417ca8836fe1e96331e35c8bc0763879e8c17cd4535fbcb27a2785c0a47294e07cb54837bb997df34882ce0bececc6adca365c76fc7533cf0503458937dcfb6058b016dbbd399b9f0cca44cbc881016f4957b5e10daada3393d5b2a4cb15ed983506d4d264f9855ce2ef87a7d4a1fc03293a22c28a53c4455447d546813fa33008e5d2d81848825fae2f437ab9575ba99c230e78f4b23e575e7647beff0e4c4e2b0a1f7320e9460")
(decodeHexText
"d727aeec27bb0f7463c6ed4f5b3f4085cfd3e7218478db0dcebfca875e025320fb64bc4062251823859e963446cadd9924c559e5f981480df5a4f036daf5a8033d4c8241e128902aa1aeaf6adc149730")
(decodeHexText
"98e72813aeb6ea05347798e35379bc881d9cf2b37d38850496ee956fbecd8eab")
(decodeHexText
"cb9926f519041343c957a74f2f67900ed3d250c4dbcd26b9e2addd5247b841a9fde2219d2ef8c9ae8145fecc7792ca6770830c58c95648087f3c8a0a69369402")
let decryptedNote = decryptOrchardAction a =<< res
let decryptedNote2 = decryptOrchardAction b =<< res
describe "First action (sender)" $ do
it "Decryption fails " $ do decryptedNote `shouldBe` Nothing
describe "Second action (recipient)" $ do
it "Decryption succeeds" $ do decryptedNote2 `shouldNotBe` Nothing
it "Tx amount is validated" $ do
(a_value <$> decryptedNote2) `shouldBe` Just 3000
it "Memo is validated" $ do
let msg = maybe "" a_memo decryptedNote2
msg `shouldBe`
"Hello World!\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL"

View file

@ -18,6 +18,7 @@ build-type: Simple
extra-source-files: extra-source-files:
README.md README.md
CHANGELOG.md CHANGELOG.md
configure
source-repository head source-repository head
type: git type: git
@ -26,7 +27,10 @@ source-repository head
library library
exposed-modules: exposed-modules:
C.Zcash C.Zcash
Zcash ZcashHaskell.Orchard
ZcashHaskell.Sapling
ZcashHaskell.Types
ZcashHaskell.Utils
other-modules: other-modules:
Paths_zcash_haskell Paths_zcash_haskell
hs-source-dirs: hs-source-dirs:
@ -35,9 +39,10 @@ library
rustzcash_wrapper-uninstalled rustzcash_wrapper-uninstalled
build-depends: build-depends:
base >=4.7 && <5 base >=4.7 && <5
, borsh , borsh >=0.2
, bytestring , bytestring
, foreign-rust , foreign-rust
, generics-sop
, text , text
default-language: Haskell2010 default-language: Haskell2010
@ -53,5 +58,6 @@ test-suite zcash-haskell-test
base >=4.7 && <5 base >=4.7 && <5
, bytestring , bytestring
, hspec , hspec
, text
, zcash-haskell , zcash-haskell
default-language: Haskell2010 default-language: Haskell2010