Compare commits

..

3 commits

10 changed files with 271 additions and 15 deletions

View file

@ -11,3 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Function `f4Jumble` - Function `f4Jumble`
- Function `isValidUnifiedAddress` - Function `isValidUnifiedAddress`
- Function `f4UnJumble`
- Function `isValidShieldedAddress`
- Function `decodeBech32`

View file

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

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

View file

@ -11,6 +11,7 @@ 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"
[features] [features]
capi = [] capi = []

View file

@ -8,22 +8,77 @@ 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,
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_address::{ use zcash_address::{
Network, Network,
unified::{Address, Encoding}, unified::{Address, Encoding, Ufvk, Container, Fvk},
ZcashAddress ZcashAddress
}; };
use bech32::{
decode,
u5
};
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 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();
}
}
}
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_wrapper_f4jumble( pub extern "C" fn rust_wrapper_f4jumble(
input: *const u8, input: *const u8,
@ -35,6 +90,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,
@ -51,3 +117,36 @@ pub extern "C" fn rust_wrapper_shielded_decode(
let input: String = marshall_from_haskell_var(input, input_len, RW); let input: String = marshall_from_haskell_var(input, input_len, RW);
ZcashAddress::try_from_encoded(&input).is_ok() 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 (hrp, bytes) = bech32::decode_without_checksum(&input).unwrap();
let rd = RawData {hrp: hrp.into(), bytes: bytes.iter().map(|&x| bech32::u5::to_u8(x)).collect()};
marshall_to_haskell_var(&rd, out, out_len, RW);
}
#[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 (n, ufvk) = Ufvk::decode(&input).unwrap();
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);
}

View file

@ -30,6 +30,7 @@ library:
- borsh >= 0.2 - borsh >= 0.2
- text - text
- foreign-rust - foreign-rust
- generics-sop
pkg-config-dependencies: pkg-config-dependencies:
- rustzcash_wrapper-uninstalled - rustzcash_wrapper-uninstalled
@ -45,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 #-}
@ -18,16 +20,25 @@ 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
newtype CodedString = CodedString BS.ByteString data RawData = RawData { hrp :: BS.ByteString, bytes :: BS.ByteString}
deriving (Eq) deriving stock (Prelude.Show, GHC.Generic)
deriving newtype (BorshSize, ToBorsh, FromBorsh) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving newtype (IsRaw) deriving anyclass (Data.Structured.Show)
deriving (Prelude.Show, Data.Structured.Show) via AsBase16 CodedString deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct RawData
{# 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)'&
} }
-> `()' -> `()'
#} #}
@ -43,3 +54,10 @@ newtype CodedString = CodedString BS.ByteString
} }
-> `Bool' -> `Bool'
#} #}
{# fun unsafe rust_wrapper_bech32decode as rustWrapperBech32Decode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer RawData'&
}
-> `()'
#}

View file

@ -1,12 +1,16 @@
module Zcash module Zcash
( f4Jumble ( f4Jumble
, f4UnJumble
, isValidUnifiedAddress , isValidUnifiedAddress
, isValidShieldedAddress , isValidShieldedAddress
, decodeBech32
) where ) where
import C.Zcash import C.Zcash
( CodedString ( RawData
, rustWrapperBech32Decode
, rustWrapperF4Jumble , rustWrapperF4Jumble
, rustWrapperF4UnJumble
, rustWrapperIsShielded , rustWrapperIsShielded
, rustWrapperIsUA , rustWrapperIsUA
) )
@ -14,11 +18,17 @@ import qualified Data.ByteString as BS
import Foreign.Rust.Marshall.Fixed import Foreign.Rust.Marshall.Fixed
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
f4Jumble :: BS.ByteString -> CodedString f4Jumble :: BS.ByteString -> BS.ByteString
f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble f4Jumble = withPureBorshVarBuffer . rustWrapperF4Jumble
f4UnJumble :: BS.ByteString -> BS.ByteString
f4UnJumble = withPureBorshVarBuffer . rustWrapperF4UnJumble
isValidUnifiedAddress :: BS.ByteString -> Bool isValidUnifiedAddress :: BS.ByteString -> Bool
isValidUnifiedAddress = rustWrapperIsUA isValidUnifiedAddress = rustWrapperIsUA
isValidShieldedAddress :: BS.ByteString -> Bool isValidShieldedAddress :: BS.ByteString -> Bool
isValidShieldedAddress = rustWrapperIsShielded isValidShieldedAddress = rustWrapperIsShielded
decodeBech32 :: BS.ByteString -> RawData
decodeBech32 = withPureBorshVarBuffer . rustWrapperBech32Decode

View file

@ -1,7 +1,8 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
import C.Zcash (CodedString(CodedString), rustWrapperIsUA) import C.Zcash (RawData(..), 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 Zcash
@ -111,7 +112,120 @@ 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 "Decode UVK from YWallet" $ do
let uvk =
"uview1u833rp8yykd7h4druwht6xp6k8krle45fx8hqsw6vzw63n24atxpcatws82z092kryazuu6d7rayyut8m36wm4wpjy2z8r9hj48fx5pf49gw4sjrq8503qpz3vqj5hg0vg9vsqeasg5qjuyh94uyfm7v76udqcm2m0wfc25hcyqswcn56xxduq3xkgxkr0l73cjy88fdvf90eq5fda9g6x7yv7d0uckpevxg6540wc76xrc4axxvlt03ptaa2a0rektglmdy68656f3uzcdgqqyu0t7wk5cvwghyyvgqc0rp3vgu5ye4nd236ml57rjh083a2755qemf6dk6pw0qrnfm7246s8eg2hhzkzpf9h73chhng7xhmyem2sjh8rs2m9nhfcslsgenm"
let res = decodeBech32 uvk
let rawBytes = f4UnJumble $ bytes res
it "decodes Bech32" $ do
print $ hrp res
print $ BS.length $ bytes res
hrp res `shouldBe` "uview"
it "unjumble result" $ do
BS.takeEnd 16 rawBytes `shouldBe` "uview00000000000"
describe "Unified address" $ do describe "Unified address" $ do
it "succeeds with correct UA" $ do it "succeeds with correct UA" $ do
let ua = let ua =

View file

@ -39,6 +39,7 @@ library
, borsh >=0.2 , borsh >=0.2
, bytestring , bytestring
, foreign-rust , foreign-rust
, generics-sop
, text , text
default-language: Haskell2010 default-language: Haskell2010
@ -54,5 +55,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