Compare commits
3 commits
1261a585e6
...
831403744d
Author | SHA1 | Date | |
---|---|---|---|
831403744d | |||
ccd4ede950 | |||
6c8a67f2d7 |
10 changed files with 271 additions and 15 deletions
|
@ -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`
|
||||||
|
|
|
@ -1 +1,7 @@
|
||||||
# haskell-wrapper
|
# zcash-haskell
|
||||||
|
|
||||||
|
A Haskell library to interact with the Zcash blockchain.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
|
||||||
|
|
5
configure
vendored
5
configure
vendored
|
@ -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
|
||||||
|
|
|
@ -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 = []
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'&
|
||||||
|
}
|
||||||
|
-> `()'
|
||||||
|
#}
|
||||||
|
|
14
src/Zcash.hs
14
src/Zcash.hs
|
@ -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
|
||||||
|
|
118
test/Spec.hs
118
test/Spec.hs
|
@ -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 =
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue