Compare commits
16 commits
22b28b9d4b
...
fef3d3af35
Author | SHA1 | Date | |
---|---|---|---|
fef3d3af35 | |||
76d42c46d4 | |||
0dd9680158 | |||
0acff0d965 | |||
659361085c | |||
19c9d8c466 | |||
80478c9b6f | |||
b35b89fbb4 | |||
7f181ccac3 | |||
831403744d | |||
ccd4ede950 | |||
6c8a67f2d7 | |||
1261a585e6 | |||
d3fe83d4a8 | |||
504969834a | |||
69b1192e0e |
17 changed files with 2390 additions and 48 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -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
14
Makefile
Normal 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
|
|
@ -1 +1,7 @@
|
||||||
# haskell-wrapper
|
# zcash-haskell
|
||||||
|
|
||||||
|
A Haskell library to interact with the Zcash blockchain.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
|
||||||
|
|
||||||
module Main where
|
|
||||||
|
|
||||||
main :: IO ()
|
|
||||||
main = putStrLn "Zcash Utilities for Haskell"
|
|
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
|
||||||
|
|
1561
librustzcash-wrapper/Cargo.lock
generated
1561
librustzcash-wrapper/Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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 = []
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'&
|
||||||
|
}
|
||||||
|
-> `()'
|
||||||
|
#}
|
||||||
|
|
15
src/Zcash.hs
15
src/Zcash.hs
|
@ -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
|
|
34
src/ZcashHaskell/Orchard.hs
Normal file
34
src/ZcashHaskell/Orchard.hs
Normal 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
|
20
src/ZcashHaskell/Sapling.hs
Normal file
20
src/ZcashHaskell/Sapling.hs
Normal 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
75
src/ZcashHaskell/Types.hs
Normal 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
34
src/ZcashHaskell/Utils.hs
Normal 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
|
224
test/Spec.hs
224
test/Spec.hs
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue