Implement Frontier for Sapling #97

Merged
pitmutt merged 59 commits from rav001 into milestone2 2024-10-18 19:40:35 +00:00
13 changed files with 1280 additions and 572 deletions

View file

@ -5,6 +5,46 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.8.0]
## Added
- New `UnifiedIncomingViewingKey` type
- Functions to derive Orchard full viewing key
- Functions to derive Sapling full viewing key
- Functions to derive transparent "full viewing key"
- Functions to encode Unified Full Viewing Keys
- Functions to encode Unified Incoming Viewing Keys
## [0.7.7.0]
### Changed
- Updated Rust crates
## [0.7.6.0]
### Changed
- Removed workaround for missing `time` field in Zebra's `getblock` response.
## [0.7.5.0]
### Added
- Sapling commitment node functions
- Sapling Merkle path test
### Changed
- Upgraded Rust dependencies to latest versions:
- `zcash_primitives` 0.19.0
- `zcash_client_backend` 0.14.0
- `orchard` 0.10.0
- `sapling-crypto` 0.3.0
- `incrementalmerkletree` 0.7.0
- `zip32` 0.1.2
## [0.7.4.0]
### Added

File diff suppressed because it is too large Load diff

View file

@ -9,22 +9,23 @@ haskell-ffi.git = "https://github.com/BeFunctional/haskell-rust-ffi.git"
haskell-ffi.rev = "2bf292e2e56eac8e9fb0fb2e1450cf4a4bd01274"
f4jumble = "0.1"
zcash_address = "0.2.0"
borsh = "0.10"
borsh = "0.9"
bech32 = "0.11"
orchard = "0.9.0"
orchard = "0.10.0"
zcash_note_encryption = "0.4.0"
zcash_primitives = { version = "0.16.0", features = ["transparent-inputs"]}
zcash_client_backend = "0.13.0"
sapling-crypto = "0.2"
zip32 = "0.1.0"
zcash_primitives = { version = "0.21.0", features = ["transparent-inputs"]}
zcash_client_backend = "0.16.0"
sapling-crypto = "0.4"
zip32 = "0.1.2"
proc-macro2 = "1.0.66"
nonempty = "0.7.0"
incrementalmerkletree = "0.6.0"
incrementalmerkletree = "0.7.0"
secp256k1 = "0.27.0"
jubjub = "0.10.0"
rand_core = { version = "0.6.4", features = ["getrandom"]}
wagyu-zcash-parameters = "0.2.0"
bip0039 = "0.12.0"
ahash = "0.7.8"
[features]

View file

@ -1,4 +1,4 @@
[toolchain]
channel = "nightly-2024-02-04"
channel = "nightly"
components = [ "rustfmt", "rustc-dev"]
profile = "minimal"

View file

@ -116,6 +116,7 @@ use zcash_primitives::{
},
transparent::{
Bundle as TransparentBundle,
builder::TransparentSigningSet,
TxIn,
TxOut,
OutPoint,
@ -134,7 +135,7 @@ use zcash_primitives::{
use zcash_address::{
Network,
unified::{Address, Encoding, Ufvk, Container, Fvk, Receiver},
unified::{Address, Encoding, Ufvk, Uivk, Ivk, Container, Fvk, Receiver},
ZcashAddress
};
@ -156,7 +157,7 @@ use orchard::{
Flags
},
Action,
keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment},
note_encryption::OrchardDomain,
primitives::redpallas::{VerificationKey, SpendAuth, Signature},
@ -570,11 +571,11 @@ impl Hspend {
}
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct HsaplingInput {
sk: Vec<u8>,
note: Hnote,
iw: Vec<u8>
iw: Hpath
}
impl<RW> FromHaskell<RW> for HsaplingInput {
@ -585,11 +586,11 @@ impl<RW> FromHaskell<RW> for HsaplingInput {
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
#[derive(BorshSerialize, BorshDeserialize)]
pub struct HorchardInput {
sk: Vec<u8>,
note: Hnote,
iw: Vec<u8>
iw: Hpath
}
impl<RW> FromHaskell<RW> for HorchardInput {
@ -689,6 +690,35 @@ impl Hufvk {
}
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct Huivk {
net: u8,
orchard: Vec<u8>,
sapling: Vec<u8>,
transparent: Vec<u8>
}
impl<RW> ToHaskell<RW> for Huivk {
fn to_haskell<W: Write>(&self, writer: &mut W, _tag: PhantomData<RW>) -> Result<()> {
self.serialize(writer)?;
Ok(())
}
}
impl Huivk {
fn add_key_section(&mut self, ivk: &Ivk) {
if let Ivk::Orchard(v) = ivk {
self.orchard = v.to_vec();
}
if let Ivk::Sapling(w) = ivk {
self.sapling = w.to_vec();
}
if let Ivk::P2pkh(x) = ivk {
self.transparent = x.to_vec();
}
}
}
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct Hsvk {
vk: Vec<u8>,
@ -967,6 +997,34 @@ pub extern "C" fn rust_wrapper_ufvk_decode(
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_uivk_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 = Uivk::decode(&input);
match dec_key {
Ok((n, uivk)) => {
let x = match n {
Network::Main => 1,
Network::Test => 2,
Network::Regtest => 3
};
let mut hk = Huivk { net: x, orchard: vec![0], sapling: vec![0], transparent: vec![0] };
let ivks = uivk.items();
ivks.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_esk_decrypt(
key: *const u8,
@ -1542,6 +1600,183 @@ pub extern "C" fn rust_wrapper_update_sapling_witness(
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_node(
cmu: *const u8,
cmu_len: usize,
out: *mut u8,
out_len: &mut usize
){
let node_in: Vec<u8> = marshall_from_haskell_var(cmu, cmu_len, RW);
let sapling_note_comm = SaplingNoteCommitment::from_bytes(&to_array(node_in));
if sapling_note_comm.is_some().into() {
let n = Node::from_cmu(&sapling_note_comm.unwrap());
let h = Hhex { bytes: n.to_bytes().to_vec()};
marshall_to_haskell_var(&h, out, out_len, RW);
} else {
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_combine_sapling_nodes(
level: u8,
left: *const u8,
left_len: usize,
right: *const u8,
right_len: usize,
out: *mut u8,
out_len: &mut usize
){
let left_in: Vec<u8> = marshall_from_haskell_var(left, left_len, RW);
let right_in: Vec<u8> = marshall_from_haskell_var(right, right_len, RW);
if left_in.len() == 1 {
let n = Node::combine(Level::new(level), &Node::empty_leaf(), &Node::empty_leaf());
let h = Hhex { bytes: n.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
} else {
let left_node = Node::from_bytes(to_array(left_in));
if left_node.is_some().into() {
if right_in.len() > 1 {
let right_node = Node::from_bytes(to_array(right_in));
if right_node.is_some().into() {
let n = Node::combine(Level::new(level), &left_node.unwrap(), &right_node.unwrap());
let h = Hhex { bytes: n.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
} else {
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
} else {
let n = Node::combine(Level::new(level), &left_node.unwrap(), &Node::empty_leaf());
let h = Hhex { bytes: n.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
}
} else {
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_get_sapling_root(
level: u8,
out: *mut u8,
out_len: &mut usize
){
let tree: CommitmentTree<Node, 32> = CommitmentTree::empty();
let root = tree.root();
let h = Hhex { bytes: root.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_commitment_tree_parts(
tree: *const u8,
tree_len: usize,
out: *mut u8,
out_len: &mut usize
){
let tree_in: Vec<u8> = marshall_from_haskell_var(tree, tree_len, RW);
let tree_reader = Cursor::new(tree_in);
let comm_tree = read_commitment_tree(tree_reader);
match comm_tree {
Ok::<CommitmentTree<Node, 32>, _>(c1) => {
let left = match c1.left() {
Some(x) => {
Hhex { bytes: x.to_bytes().to_vec() }
},
None => {
Hhex { bytes: vec![0] }
}
};
let right = match c1.right() {
Some(x) => {
Hhex { bytes: x.to_bytes().to_vec() }
},
None => {
Hhex { bytes: vec![0] }
}
};
let parents = c1.parents().iter().map(|x| match x {
Some(y) => {
Hhex { bytes: y.to_bytes().to_vec() }
},
None => {
Hhex { bytes: vec![0] }
}
}).collect();
let ht = Htree { left, right, parents};
marshall_to_haskell_var(&ht, out, out_len, RW);
},
Err(_e) => {
let ht0 = Htree { left: Hhex { bytes: vec![0] } , right: Hhex { bytes: vec![0] }, parents: vec![Hhex { bytes: vec![0] }]};
marshall_to_haskell_var(&ht0, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_tree_anchor(
tree: *const u8,
tree_len: usize,
out: *mut u8,
out_len: &mut usize
){
let tree_in: Vec<u8> = marshall_from_haskell_var(tree, tree_len, RW);
let tree_reader = Cursor::new(tree_in);
let comm_tree = read_commitment_tree(tree_reader);
match comm_tree {
Ok::<CommitmentTree<Node, 32>, _>(c1) => {
let root = c1.root();
let h = Hhex { bytes: root.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
},
Err(_) => {
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_sapling_path_anchor(
path: *const u8,
path_len: usize,
cmx: *const u8,
cmx_len: usize,
out: *mut u8,
out_len: &mut usize
){
let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW);
let cmx_in: Vec<u8> = marshall_from_haskell_var(cmx, cmx_len, RW);
let mk_path = sapling_crypto::MerklePath::from_parts(path_in.path.iter().map(|x|
if x.bytes.len() > 1 {
Node::from_bytes(to_array(x.bytes.clone())).unwrap()
} else {
Node::empty_leaf()
}).collect(), Position::from(u64::from(path_in.position)));
match mk_path {
Ok(mp1) => {
let nc = SaplingNoteCommitment::from_bytes(&to_array(cmx_in));
if nc.is_some().into() {
let anchor = mp1.root(Node::from_cmu(&nc.unwrap()));
let h = Hhex { bytes: anchor.to_bytes().to_vec() };
marshall_to_haskell_var(&h, out, out_len, RW);
} else {
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
},
Err(_e) =>{
let h0 = Hhex { bytes: vec![0] };
marshall_to_haskell_var(&h0, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_read_orchard_frontier(
tree: *const u8,
@ -1696,7 +1931,13 @@ pub extern "C" fn rust_wrapper_read_orchard_path_anchor(
){
let path_in: Hpath = marshall_from_haskell_var(path, path_len, RW);
let cmx_in: Vec<u8> = marshall_from_haskell_var(cmx, cmx_len, RW);
let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x| MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()).collect()));
let mk_path = orchard::tree::MerklePath::from_parts(path_in.position, to_array(path_in.path.iter().map(|x|
if x.bytes.len() > 1 {
MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()
} else {
MerkleHashOrchard::empty_leaf()
}
).collect()));
let nc = ExtractedNoteCommitment::from_bytes(&to_array(cmx_in));
if nc.is_some().into() {
let anchor = mk_path.root(nc.unwrap());
@ -1967,56 +2208,36 @@ pub extern "C" fn rust_wrapper_create_transaction(
out: *mut u8,
out_len: &mut usize){
let sap_input: Vec<HsaplingInput> = marshall_from_haskell_var(s_input, s_input_len, RW);
let sap_anchor =
if sap_input.is_empty() {
let sap_wit_in: Vec<u8> = marshall_from_haskell_var(sap_wit, sap_wit_len, RW);
let sap_wit_reader = Cursor::new(sap_wit_in);
let sap_iw = read_commitment_tree::<Node, Cursor<Vec<u8>>, SAPLING_DEPTH>(sap_wit_reader);
match sap_iw {
Ok(s_iw) => {
Some(SaplingAnchor::from(s_iw.root()))
},
Err(_e) => {
None
}
}
let sap_anchor_in: Vec<u8> = marshall_from_haskell_var(sap_wit, sap_wit_len, RW);
let sap_anchor = Node::from_bytes(to_array(sap_anchor_in));
let sapling_anchor =
if sap_anchor.is_some().into() {
Some(SaplingAnchor::from(sap_anchor.unwrap()))
} else {
let si = &sap_input[0];
let swit_reader = Cursor::new(&si.iw);
let iw: IncrementalWitness<Node, SAPLING_DEPTH> = read_incremental_witness(swit_reader).unwrap();
Some(SaplingAnchor::from(iw.root()))
None
};
//println!("{:?}", sap_anchor);
//println!("{:?}", sapling_anchor);
let orch_input: Vec<HorchardInput> = marshall_from_haskell_var(o_input, o_input_len, RW);
let orch_anchor =
if orch_input.is_empty() {
let orch_wit_in: Vec<u8> = marshall_from_haskell_var(orch_wit, orch_wit_len, RW);
let orch_wit_reader = Cursor::new(orch_wit_in);
let orch_iw = read_commitment_tree::<MerkleHashOrchard, Cursor<Vec<u8>>, 32>(orch_wit_reader);
match orch_iw {
Ok(o_iw) => {
Some(OrchardAnchor::from(o_iw.root()))
},
Err(_e) => {
None
}
}
let orch_anchor_in : Vec<u8> = marshall_from_haskell_var(orch_wit, orch_wit_len, RW);
let orch_anchor = MerkleHashOrchard::from_bytes(&to_array(orch_anchor_in));
let orchard_anchor =
if orch_anchor.is_some().into() {
Some(OrchardAnchor::from(orch_anchor.unwrap()))
} else {
let oi = &orch_input[0];
let wit_reader = Cursor::new(&oi.iw);
let iw: IncrementalWitness<MerkleHashOrchard, 32> = read_incremental_witness(wit_reader).unwrap();
Some(OrchardAnchor::from(iw.root()))
None
};
let build_config = BuildConfig::Standard {sapling_anchor: sap_anchor, orchard_anchor: orch_anchor};
//println!("{:?}", orchard_anchor);
let build_config = BuildConfig::Standard {sapling_anchor, orchard_anchor};
let mut main_builder = Builder::new(MainNetwork, BlockHeight::from(bl_height), build_config);
let mut test_builder = Builder::new(TestNetwork, BlockHeight::from(bl_height), build_config);
let trans_input: Vec<HtransparentInput> = marshall_from_haskell_var(t_input, t_input_len, RW);
let mut tss = TransparentSigningSet::new();
for t_in in trans_input {
if t_in.sk.len() > 1 {
//println!("t inp: {:?}", t_in);
let k = SecretKey::from_slice(&t_in.sk).unwrap();
if net {
match main_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) {
match main_builder.add_transparent_input(tss.add_key(k), t_in.utxo.unpack(), t_in.coin.unpack()) {
Ok(()) => {
//println!("added t-input in main");
continue;
@ -2024,7 +2245,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
Err(_e) => { println!("Error reading transparent input"); }
}
} else {
match test_builder.add_transparent_input(k, t_in.utxo.unpack(), t_in.coin.unpack()) {
match test_builder.add_transparent_input(tss.add_key(k), t_in.utxo.unpack(), t_in.coin.unpack()) {
Ok(()) => {
//println!("added t-input in test");
continue;
@ -2034,12 +2255,14 @@ pub extern "C" fn rust_wrapper_create_transaction(
}
}
}
let mut sap_key_array = vec![];
for s_in in sap_input {
if s_in.sk.len() > 1 {
//println!("s inp: {:?}", s_in);
let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk);
match sp_key {
Ok(sk) => {
sap_key_array.push(sk.clone());
let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap();
let rseed = if s_in.note.rseed.kind == 1 {
Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap())
@ -2047,11 +2270,16 @@ pub extern "C" fn rust_wrapper_create_transaction(
Rseed::AfterZip212(to_array(s_in.note.rseed.bytes))
};
let note = SaplingNote::from_parts(pay_addr, SaplingNoteValue::from_raw(s_in.note.note), rseed);
let wit_reader = Cursor::new(s_in.iw);
let iw: IncrementalWitness<Node, SAPLING_DEPTH> = read_incremental_witness(wit_reader).unwrap();
let merkle_path = iw.path().unwrap();
let mk_path = sapling_crypto::MerklePath::from_parts(s_in.iw.path.iter().map(|x|
if x.bytes.len() > 1 {
Node::from_bytes(to_array(x.bytes.clone())).unwrap()
} else {
Node::empty_leaf()
}).collect(), Position::from(u64::from(s_in.iw.position)));
let merkle_path = mk_path.unwrap();
let fvk = sk.to_diversifiable_full_viewing_key().fvk().clone();
if net {
let mb = main_builder.add_sapling_spend::<String>(&sk, note, merkle_path);
let mb = main_builder.add_sapling_spend::<String>(fvk, note, merkle_path);
match mb {
Ok(()) => {
continue;
@ -2062,7 +2290,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
}
}
} else {
let tb = test_builder.add_sapling_spend::<String>(&sk, note, merkle_path);
let tb = test_builder.add_sapling_spend::<String>(fvk, note, merkle_path);
match tb {
Ok(()) => {
continue;
@ -2081,36 +2309,46 @@ pub extern "C" fn rust_wrapper_create_transaction(
}
}
}
let mut orch_keys = vec![];
for o_in in orch_input {
if o_in.sk.len() > 1 {
let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap();
orch_keys.push(SpendAuthorizingKey::from(&sp_key));
let pay_addr = OrchardAddress::from_raw_address_bytes(&to_array(o_in.note.recipient)).unwrap();
let rho = Rho::from_bytes(&to_array(o_in.note.rho)).unwrap();
let rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap();
let val = NoteValue::from_raw(o_in.note.note);
//println!("o inp: {:?}", val);
let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap();
let wit_reader = Cursor::new(o_in.iw);
let iw: IncrementalWitness<MerkleHashOrchard, 32> = read_incremental_witness(wit_reader).unwrap();
let merkle_path = OrchardMerklePath::from(iw.path().unwrap());
let merkle_path = orchard::tree::MerklePath::from_parts(o_in.iw.position, to_array(o_in.iw.path.iter().map(|x|
if x.bytes.len() > 1 {
MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()
} else {
MerkleHashOrchard::empty_leaf()
}
).collect()));
if net {
let mb = main_builder.add_orchard_spend::<String>(&sp_key, note, merkle_path);
let mb = main_builder.add_orchard_spend::<String>(FullViewingKey::from(&sp_key), note, merkle_path);
match mb {
Ok(()) => {
//println!("added orchard inp: {:?}", val);
continue;
},
Err(_e) => {
Err(e) => {
//println!("failed orchard inp: {:?}", e);
let x = Hhex {bytes: vec![7]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
} else {
let tb = test_builder.add_orchard_spend::<String>(&sp_key, note, merkle_path);
let tb = test_builder.add_orchard_spend::<String>(FullViewingKey::from(&sp_key), note, merkle_path);
match tb {
Ok(()) => {
//println!("added orchard inp: {:?}", val);
continue;
},
Err(_e) => {
Err(e) => {
//println!("failed orchard inp: {:?}", e);
let x = Hhex {bytes: vec![7]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
@ -2247,9 +2485,9 @@ pub extern "C" fn rust_wrapper_create_transaction(
let output_params_reader = Cursor::new(output_params_in);
let output_prover = OutputParameters::read(output_params_reader, false).unwrap();
let result = if net {
main_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard())
main_builder.build(&tss, &sap_key_array, &orch_keys, OsRng, &spend_prover, &output_prover, &FeeRule::standard())
} else {
test_builder.build(OsRng, &spend_prover, &output_prover, &FeeRule::standard())
test_builder.build(&tss, &sap_key_array, &orch_keys,OsRng, &spend_prover, &output_prover, &FeeRule::standard())
};
match result {
Ok(r) => {
@ -2261,6 +2499,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
Err(e) => {
match e {
Error::InsufficientFunds(y) => {
//println!("ins funds: {:?}", y);
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
},
@ -2326,3 +2565,87 @@ pub extern "C" fn rust_wrapper_create_transaction(
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_orchard_fvk(
orch_in: *const u8,
orch_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(orch_in, orch_in_len, RW);
let sk = SpendingKey::from_bytes(to_array(input));
if sk.is_some().into() {
let fvk = FullViewingKey::from(&sk.unwrap());
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
} else {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_orchard_ivk(
orch_in: *const u8,
orch_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(orch_in, orch_in_len, RW);
let sk = SpendingKey::from_bytes(to_array(input));
if sk.is_some().into() {
let fvk = FullViewingKey::from(&sk.unwrap()).to_ivk(Scope::External);
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
} else {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_sapling_fvk(
sap_in: *const u8,
sap_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(sap_in, sap_in_len, RW);
let in_bytes: [u8; 169] = to_array(input);
let sk = ExtendedSpendingKey::from_bytes(&in_bytes);
match sk {
Ok(k) => {
let fvk = k.to_diversifiable_full_viewing_key();
let x = Hhex {bytes: fvk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
},
Err(_e) => {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
}
#[no_mangle]
pub extern "C" fn rust_wrapper_create_sapling_ivk(
sap_in: *const u8,
sap_in_len: usize,
out: *mut u8,
out_len: &mut usize
){
let input: Vec<u8> = marshall_from_haskell_var(sap_in, sap_in_len, RW);
let in_bytes: [u8; 169] = to_array(input);
let sk = ExtendedSpendingKey::from_bytes(&in_bytes);
match sk {
Ok(k) => {
let ivk = k.to_diversifiable_full_viewing_key().to_external_ivk();
let x = Hhex {bytes: ivk.to_bytes().to_vec()};
marshall_to_haskell_var(&x, out, out_len, RW);
},
Err(_e) => {
let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW);
}
}
}

View file

@ -120,6 +120,13 @@ import ZcashHaskell.Types
-> `()'
#}
{# fun unsafe rust_wrapper_uivk_decode as rustWrapperUivkDecode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer UnifiedIncomingViewingKey'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode
{ toBorshVar* `BS.ByteString'&
, toBorshVar* `OrchardAction'&
@ -246,6 +253,51 @@ import ZcashHaskell.Types
-> `()'
#}
{# fun unsafe rust_wrapper_read_sapling_node as rustWrapperReadSaplingNode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_combine_sapling_nodes as rustWrapperCombineSaplingNodes
{ `Int8'
, toBorshVar* `BS.ByteString'&
, toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_get_sapling_root as rustWrapperGetSaplingRootTest
{ `Int8'
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_read_sapling_commitment_tree_parts as rustWrapperReadSaplingTreeParts
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer SaplingRawTree'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_read_sapling_tree_anchor as rustWrapperReadSaplingTreeAnchor
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_read_sapling_path_anchor as rustWrapperReadSaplingPathAnchor
{ toBorshVar* `MerklePath'&
, toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_read_orchard_node as rustWrapperReadOrchardNode
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
@ -365,3 +417,31 @@ import ZcashHaskell.Types
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_orchard_fvk as rustWrapperCreateOrchardFvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_orchard_ivk as rustWrapperCreateOrchardIvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_sapling_fvk as rustWrapperCreateSaplingFvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}
{# fun unsafe rust_wrapper_create_sapling_ivk as rustWrapperCreateSaplingIvk
{ toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'&
}
-> `()'
#}

View file

@ -15,13 +15,42 @@
module ZcashHaskell.Keys where
import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed)
import Crypto.Secp256k1 (createContext)
import qualified Data.ByteString as BS
import Data.HexString (hexBytes)
import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Word (Word8(..))
import Foreign.Rust.Marshall.Variable
( withBorshVarBuffer
, withPureBorshVarBuffer
)
import ZcashHaskell.Types (Phrase, Seed(..), ToBytes(..))
import Haskoin.Address.Base58 (decodeBase58)
import Haskoin.Crypto.Keys.Extended
( DerivPath(..)
, DerivPathI(..)
, XPubKey(..)
, derivePath
, deriveXPubKey
, xPubExport
)
import Haskoin.Network.Constants (btc)
import ZcashHaskell.Orchard (deriveOrchardFvk, deriveOrchardIvk)
import ZcashHaskell.Sapling (deriveSaplingFvk, deriveSaplingIvk)
import ZcashHaskell.Types
( OrchardSpendingKey(..)
, Phrase
, SaplingSpendingKey(..)
, Seed(..)
, ToBytes(..)
, TransparentSpendingKey(..)
, ZcashNet(..)
, uniFullViewingKeyHrp
, uniIncomingViewingKeyHrp
, uniTestFullViewingKeyHrp
, uniTestIncomingViewingKeyHrp
)
import ZcashHaskell.Utils (encodeBech32m, f4Jumble)
-- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses.
generateWalletSeedPhrase :: IO Phrase
@ -36,3 +65,97 @@ getWalletSeed p =
where
result :: Seed
result = (withPureBorshVarBuffer . rustWrapperGetSeed) p
-- | Derive a transparent root node for unified viewing keys
deriveFullTransparentNode :: TransparentSpendingKey -> IO BS.ByteString
deriveFullTransparentNode sk = do
ioCtx <- createContext
let tPubKey = deriveXPubKey ioCtx sk
let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey
case tPubKeyBytes of
Nothing -> fail "Unable to get transparent key bytes"
Just pb -> return $ BS.takeEnd 65 pb
-- | Derive a transparent incoming root node for unified incoming viewing keys
deriveIncomingTransparentNode :: TransparentSpendingKey -> IO BS.ByteString
deriveIncomingTransparentNode sk = do
ioCtx <- createContext
let path = Deriv :/ 0 :: DerivPath
let childPrvKey = derivePath ioCtx path sk
let tPubKey = deriveXPubKey ioCtx childPrvKey
let tPubKeyBytes = decodeBase58 $ xPubExport btc ioCtx tPubKey
case tPubKeyBytes of
Nothing -> fail "Unable to get transparent key bytes"
Just pb -> return $ BS.takeEnd 65 pb
-- | Derive a Unified Full Viewing Key
deriveUfvk ::
ZcashNet
-> OrchardSpendingKey
-> SaplingSpendingKey
-> TransparentSpendingKey
-> IO T.Text
deriveUfvk net okey skey tkey = do
tSec <- deriveFullTransparentNode tkey
let oSec = deriveOrchardFvk okey
let sSec = deriveSaplingFvk skey
case oSec of
Nothing -> fail "Unable to derive Orchard viewing key"
Just oSec' -> do
case sSec of
Nothing -> fail "Unable to derive Sapling viewing key"
Just sSec' ->
return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net True
-- | Derive a Unified Incoming Viewing Key
deriveUivk ::
ZcashNet
-> OrchardSpendingKey
-> SaplingSpendingKey
-> TransparentSpendingKey
-> IO T.Text
deriveUivk net okey skey tkey = do
tSec <- deriveIncomingTransparentNode tkey
let oSec = deriveOrchardIvk okey
let sSec = deriveSaplingIvk skey
case oSec of
Nothing -> fail "Unable to derive Orchard viewing key"
Just oSec' -> do
case sSec of
Nothing -> fail "Unable to derive Sapling viewing key"
Just sSec' ->
return $ encodeVK (hexBytes oSec') (hexBytes sSec') tSec net False
-- | Encode a Unified Viewing Key per [ZIP-316](https://zips.z.cash/zip-0316)
encodeVK ::
BS.ByteString -- ^ Orchard FVK
-> BS.ByteString -- ^ Sapling FVK
-> BS.ByteString -- ^ Transparent root node
-> ZcashNet -- ^ Network
-> Bool -- ^ Full?
-> T.Text
encodeVK ovk svk tvk net full = encodeBech32m (E.encodeUtf8 hr) b
where
tReceiver = packReceiver 0x00 $ Just tvk
b = f4Jumble $ tReceiver <> sReceiver <> oReceiver <> padding
hr =
if full
then case net of
MainNet -> uniFullViewingKeyHrp
TestNet -> uniTestFullViewingKeyHrp
else case net of
MainNet -> uniIncomingViewingKeyHrp
TestNet -> uniTestIncomingViewingKeyHrp
sReceiver = packReceiver 0x02 $ Just svk
oReceiver = packReceiver 0x03 $ Just ovk
padding = E.encodeUtf8 $ T.justifyLeft 16 '\NUL' hr
packReceiver :: Word8 -> Maybe BS.ByteString -> BS.ByteString
packReceiver typeCode receiver' =
case receiver' of
Just receiver ->
if BS.length receiver > 1
then BS.singleton typeCode `BS.append`
(BS.singleton . toEnum . BS.length) receiver `BS.append`
receiver
else BS.empty
Nothing -> BS.empty

View file

@ -19,6 +19,8 @@ module ZcashHaskell.Orchard where
import C.Zcash
( rustWrapperCombineOrchardNodes
, rustWrapperCreateOrchardFvk
, rustWrapperCreateOrchardIvk
, rustWrapperGenOrchardReceiver
, rustWrapperGenOrchardSpendKey
, rustWrapperGetOrchardRootTest
@ -37,6 +39,7 @@ import C.Zcash
, rustWrapperReadOrchardWitnessAnchor
, rustWrapperUADecode
, rustWrapperUfvkDecode
, rustWrapperUivkDecode
, rustWrapperUpdateOrchardWitness
)
import qualified Data.ByteString as BS
@ -157,6 +160,15 @@ decodeUfvk str =
where
decodedKey = (withPureBorshVarBuffer . rustWrapperUfvkDecode) str
-- | Attempts to decode the given bytestring into a Unified Full Viewing Key
decodeUivk :: BS.ByteString -> Maybe UnifiedIncomingViewingKey
decodeUivk str =
case i_net decodedKey of
0 -> Nothing
_ -> Just decodedKey
where
decodedKey = (withPureBorshVarBuffer . rustWrapperUivkDecode) str
-- | Check if the given UVK matches the UA given
matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool
matchOrchardAddress = rustWrapperOrchardCheck
@ -337,3 +349,25 @@ compareAddress a u =
Sapling s -> s_rec u == Just (sa_receiver s) && ua_net u == net_type s
Transparent t -> t_rec u == Just (ta_receiver t) && ua_net u == ta_network t
Exchange x -> False
-- | Derive an Orchard Full Viewing Key
deriveOrchardFvk ::
OrchardSpendingKey -- ^ The Orchard spending key
-> Maybe HexString
deriveOrchardFvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateOrchardFvk $ getBytes sk
-- | Derive an Orchard Incoming Viewing Key
deriveOrchardIvk ::
OrchardSpendingKey -- ^ The Orchard spending key
-> Maybe HexString
deriveOrchardIvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateOrchardIvk $ getBytes sk

View file

@ -18,11 +18,19 @@
module ZcashHaskell.Sapling where
import C.Zcash
( rustWrapperDecodeSaplingAddress
( rustWrapperCombineSaplingNodes
, rustWrapperCreateSaplingFvk
, rustWrapperCreateSaplingIvk
, rustWrapperDecodeSaplingAddress
, rustWrapperGetSaplingRootTest
, rustWrapperIsShielded
, rustWrapperReadSaplingCommitmentTree
, rustWrapperReadSaplingFrontier
, rustWrapperReadSaplingNode
, rustWrapperReadSaplingPathAnchor
, rustWrapperReadSaplingPosition
, rustWrapperReadSaplingTreeAnchor
, rustWrapperReadSaplingTreeParts
, rustWrapperReadSaplingWitness
, rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress
@ -38,6 +46,7 @@ import Data.Aeson
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import Data.HexString (HexString(..), fromText, hexString, toBytes, toText)
import Data.Int (Int8)
import qualified Data.Text as T
import Data.Word
import Foreign.Rust.Marshall.Variable
@ -185,6 +194,56 @@ genSaplingInternalAddress sk =
res =
withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk)
getSaplingNodeValue :: BS.ByteString -> Maybe HexString
getSaplingNodeValue cmu =
if BS.length (hexBytes n) > 1
then Just n
else Nothing
where
n = withPureBorshVarBuffer $ rustWrapperReadSaplingNode cmu
combineSaplingNodes :: Int8 -> HexString -> HexString -> Maybe HexString
combineSaplingNodes level n1 n2 =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r =
withPureBorshVarBuffer $
rustWrapperCombineSaplingNodes level (toBytes n1) (toBytes n2)
getSaplingRootTest :: Int8 -> HexString
getSaplingRootTest level =
withPureBorshVarBuffer $ rustWrapperGetSaplingRootTest level
getSaplingTreeParts :: SaplingCommitmentTree -> Maybe SaplingTree
getSaplingTreeParts h =
if isBlank (srt_left tree) && isBlank (srt_right tree)
then Nothing
else Just $
SaplingTree
(parseHex $ srt_left tree)
(parseHex $ srt_right tree)
(map parseHex (srt_parents tree))
where
isBlank h = (BS.length $ hexBytes $ h) == 1
parseHex h =
if (BS.length $ hexBytes $ h) > 1
then Just h
else Nothing
tree =
withPureBorshVarBuffer $
rustWrapperReadSaplingTreeParts $ toBytes $ sapTree h
getSaplingTreeAnchor :: SaplingCommitmentTree -> HexString
getSaplingTreeAnchor tree =
withPureBorshVarBuffer $
rustWrapperReadSaplingTreeAnchor $ toBytes $ sapTree tree
getSaplingPathAnchor :: HexString -> MerklePath -> HexString
getSaplingPathAnchor hex p =
withPureBorshVarBuffer $ rustWrapperReadSaplingPathAnchor p (hexBytes hex)
getSaplingFrontier :: SaplingCommitmentTree -> Maybe SaplingFrontier
getSaplingFrontier tree =
if sf_pos updatedTree > 1
@ -261,3 +320,25 @@ decodeSaplingAddress sapling_address = do
where
sa =
withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address
-- | Derive a Sapling Full Viewing Key
deriveSaplingFvk ::
SaplingSpendingKey -- ^ The Sapling spending key
-> Maybe HexString
deriveSaplingFvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateSaplingFvk $ getBytes sk
-- | Derive a Sapling Incoming Viewing Key
deriveSaplingIvk ::
SaplingSpendingKey -- ^ The Sapling spending key
-> Maybe HexString
deriveSaplingIvk sk =
if BS.length (hexBytes r) > 1
then Just r
else Nothing
where
r = withPureBorshVarBuffer $ rustWrapperCreateSaplingIvk $ getBytes sk

View file

@ -268,10 +268,10 @@ instance FromJSON BlockResponse where
withObject "BlockResponse" $ \obj -> do
c <- obj .: "confirmations"
h <- obj .: "height"
t <- obj .:? "time"
t <- obj .: "time"
txs <- obj .: "tx"
hash <- obj .: "hash"
pure $ BlockResponse hash c h (fromMaybe 0 t) txs
pure $ BlockResponse hash c h t txs
instance ToJSON BlockResponse where
toJSON (BlockResponse h c ht t txs) =
@ -611,6 +611,21 @@ newtype SaplingCommitmentTree = SaplingCommitmentTree
{ sapTree :: HexString
} deriving (Eq, Prelude.Show, Read)
data SaplingRawTree = SaplingRawTree
{ srt_left :: !HexString
, srt_right :: !HexString
, srt_parents :: ![HexString]
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct SaplingRawTree
data SaplingTree = SaplingTree
{ st_left :: !(Maybe HexString)
, st_right :: !(Maybe HexString)
, st_parents :: ![Maybe HexString]
} deriving (Eq, Prelude.Show, Read)
data SaplingFrontier = SaplingFrontier
{ sf_pos :: !Int64
, sf_leaf :: !HexString
@ -681,6 +696,18 @@ data UnifiedFullViewingKey = UnifiedFullViewingKey
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey
-- | Type to represent a Unified Incoming Viewing Key
data UnifiedIncomingViewingKey = UnifiedIncomingViewingKey
{ i_net :: !Word8 -- ^ Number representing the network the key belongs to. @1@ for @mainnet@, @2@ for @testnet@ and @3@ for @regtestnet@.
, i_o_key :: !BS.ByteString -- ^ Raw bytes of the Orchard Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
, i_s_key :: !BS.ByteString -- ^ Raw bytes of the Sapling Incoming Viewing Key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
, i_t_key :: !BS.ByteString -- ^ Raw bytes of the P2PKH chain code and public key as specified in [ZIP-316](https://zips.z.cash/zip-0316)
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct
UnifiedIncomingViewingKey
-- | Type to represent an Orchard Action as provided by the @getrawtransaction@ RPC method of @zcashd@, and defined in the [Zcash Protocol](https://zips.z.cash/protocol/protocol.pdf)
data OrchardAction = OrchardAction
{ nf :: !HexString -- ^ The nullifier of the input note
@ -797,7 +824,7 @@ data TransparentTxSpend = TransparentTxSpend
data SaplingTxSpend = SaplingTxSpend
{ ss_sk :: !BS.ByteString
, ss_note :: !DecodedNote
, ss_iw :: !BS.ByteString
, ss_iw :: !MerklePath
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)
@ -806,7 +833,7 @@ data SaplingTxSpend = SaplingTxSpend
data OrchardTxSpend = OrchardTxSpend
{ ss_sk :: !BS.ByteString
, ss_note :: !DecodedNote
, ss_iw :: !BS.ByteString
, ss_iw :: !MerklePath
} deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show)

View file

@ -123,8 +123,8 @@ readZebraTransaction hex =
rawTx = (withPureBorshVarBuffer . rustWrapperTxRead) $ hexBytes hex
createTransaction ::
Maybe SaplingCommitmentTree -- ^ to obtain the Sapling anchor
-> Maybe OrchardCommitmentTree -- ^ to obtain the Orchard anchor
HexString -- ^ to obtain the Sapling anchor
-> HexString -- ^ to obtain the Orchard anchor
-> [TransparentTxSpend] -- ^ the list of transparent notes to spend
-> [SaplingTxSpend] -- ^ the list of Sapling notes to spend
-> [OrchardTxSpend] -- ^ the list of Orchard notes to spend
@ -137,12 +137,8 @@ createTransaction sapAnchor orchAnchor tSpend sSpend oSpend outgoing znet bh bui
txResult <-
withBorshBufferOfInitSize 51200 $
rustWrapperCreateTx
(case sapAnchor of
Nothing -> "0"
Just sA -> toBytes $ sapTree sA)
(case orchAnchor of
Nothing -> "0"
Just oA -> toBytes $ orchTree oA)
(hexBytes sapAnchor)
(hexBytes orchAnchor)
tSpend
sSpend
oSpend

View file

@ -45,7 +45,12 @@ import Test.HUnit
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed)
import ZcashHaskell.Keys
( deriveUfvk
, deriveUivk
, generateWalletSeedPhrase
, getWalletSeed
)
import ZcashHaskell.Orchard
import ZcashHaskell.Sapling
( decodeSaplingAddress
@ -1162,77 +1167,33 @@ main = do
Just addr -> do
let eadr = decodeExchangeAddress (E.encodeUtf8 addr)
eadr `shouldNotBe` Nothing
describe "Tree updates" $ do
it "Orchard" $ do
let tree =
OrchardCommitmentTree $
hexString
"0136a7886d7d73bc1845223165fd9cb0cef02046c707e8f88a7f61564720bd0f3501dca1fbdd7b5ba92a0809af5e85874626ce0db14d0532a48e41dde6f0f81b46011f0001fb48c27bd07e68f27aba47cd6e93fa961e0ef8c63f993963a614e56855d2013c0001ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
let cmx1 =
hexString
"1712ead46028d4349e234abf59e94e0640fe7a0829e2e2e17e1a931631810400"
let cmx2 =
hexString
"39f5ad39817fb432fa07c5feb3a957189fbe7662a4b5555ca95093b6d853cf07"
let cmx3 =
hexString
"84f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f"
let cmx4 =
hexString
"e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d429"
let finalTree =
getOrchardFrontier $
OrchardCommitmentTree $
hexString
"0184f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f01e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d4291f0000014b1a76d3820087b26cd087ca84e17f3067a25ebed82ad23a93fa485affb5530b01ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000"
case getOrchardFrontier tree of
Nothing -> assertFailure "Failed to get frontier"
Just t1 ->
case updateOrchardCommitmentTree t1 cmx1 of
Nothing -> assertFailure "Failed to update frontier with cmx"
Just t2 -> do
case getOrchardWitness t2 of
Nothing -> assertFailure "Failed to get witness"
Just wit -> do
let uWit = updateOrchardWitness wit [cmx2, cmx3, cmx4]
Just (getOrchardWitnessAnchor uWit) `shouldBe`
getOrchardTreeAnchor <$>
finalTree
describe "Witness updates" $ do
it "Sapling" $ do
let wit =
SaplingWitness $
hexString
"01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e57ec972a9b9383dc9cb228980d2d7752bb2abebc4a604ca48c5457039d2e05b000301392bed8592185dde5ab7fc81aed75e98fcf041f1a3fda55ad0b0b139ba9380130001808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15"
let cmus =
[ hexString
"958ccdc752f2f593f6c1c8e2d7201348cd896e54c6d3c92200bdbe8b859eac44"
, hexString
"e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447"
, hexString
"0c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e18"
]
updateSaplingWitness wit cmus `shouldBe`
SaplingWitness
(hexString
"01bd8a3f3cfc964332a2ada8c09a0da9dfc24174befb938abb086b9be5ca049e49013607f5e51826c8e5f660571ddfae14cd6fb1dc026bcd6855459b4e9339b20521100000019f0d7efb00169bb2202152d3266059d208ab17d14642c3339f9075e997160657000000012f4f72c03f8c937a94919a01a07f21165cc8394295291cb888ca91ed003810390107114fe4bb4cd08b47f6ae47477c182d5da9fe5c189061808c1091e9bf3b4524000001447d6b9100cddd5f80c8cf4ddee2b87eba053bd987465aec2293bd0514e68b0d015f6c95e75f4601a0a31670a7deb970fc8988c611685161d2e1629d0a1a0ebd07015f8b9205e0514fa235d75c150b87e23866b882b39786852d1ab42aab11d31a4a0117ddeb3a5f8d2f6b2d0a07f28f01ab25e03a05a9319275bb86d72fcaef6fc01501f08f39275112dd8905b854170b7f247cf2df18454d4fa94e6e4f9320cca05f24011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39038cd7f6e2238d16ef49420963348dd4e4c7d23d5e5dac69507fba8937f63eb626f6856115bea2fa8db3a65a0ab294db41c51435d3b7ea27c7b2835aca28e82a2c1d9634efe07449a47c251518ac6f92c49f3a1ef119948f6a824d1e7ff7d0443e0101e49992fdd071d90bf56242d1aa625bbe267a34e0debd4307818a686d05b45447010c4b26766d89bf6cdb4fd3b0317b4e9a2fb3850f6a24869f32fe7cb0fd512e1803000121c06ee1f1584f79d50785797a694c742be2ded600367ab7d54f3ed49e3adf7201808304b4d7c4fc407f5ce28247a7119013aeaaf1481902419c42bc8b21575c15")
it "Orchard" $ do
let wit =
OrchardWitness $
hexString
"016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e000200019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301"
let cmxs =
[ hexString
"712ba86615ff4447e8d7c7b59f3873f03c03a173438b8e4c8d416756ed4fae10"
, hexString
"c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e"
, hexString
"ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f79823"
]
updateOrchardWitness wit cmxs `shouldBe`
OrchardWitness
(hexString
"016225b41339a00dd764b452fca190a0245e7118224965942e3a6d798365c34631001f0000011d6f5da3f619bfaab957fc643c17eb144db0101c90f422da2fcbe0e80d74412e000000000001746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000040e02c864db8b574f165f616d48e2f12eb25099b5c90186af26d9e50f5058863e0504bfbc12edc35e05042c16bbfb8fed591f01f18fe128eeb57f2c456c9eb222d6d261c549e95d9007bce4c6ae0b86bc865711cdd9f0fa92e2d5b5e149b51f3be127df3b1d2372adf6c811b2e456c1d64d0e9eb167a995f9c6b66a03c9cbda250101c094201bae3b4ef582a3e8654f65a72fbd41e20e1ec9a43d3f4101afc868731e01ac20b8170b008888c19fc6e16f5e30a5ef1653e5219d0cd0c9353c3aa8f7982302010cfb50d8c877eb39e9c07082a032dd99d34be7c19fa7f30e9fecf5f14736240f019df5b9366d0f21caa678d1567390b5bfd3cfa0438271bcfe301b5558a2863301")
describe "Generate Viewing Keys" $ do
let p =
Phrase
"cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest"
let seed = getWalletSeed p
let oK = genOrchardSpendingKey (fromJust seed) MainNetCoin 0
let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0
it "Generate FVK" $ do
tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
case oK of
Nothing -> assertFailure "Failed to generate Orchard SK"
Just o ->
case sK of
Nothing -> assertFailure "Failed to generate Sapling SK"
Just s -> do
fvk <- deriveUfvk MainNet o s tK
decodeUfvk (E.encodeUtf8 fvk) `shouldNotBe` Nothing
it "Generate IVK" $ do
tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
case oK of
Nothing -> assertFailure "Failed to generate Orchard SK"
Just o ->
case sK of
Nothing -> assertFailure "Failed to generate Sapling SK"
Just s -> do
ivk <- deriveUivk MainNet o s tK
decodeUivk (E.encodeUtf8 ivk) `shouldNotBe` Nothing
-- | Properties
prop_PhraseLength :: Property

View file

@ -5,7 +5,7 @@ cabal-version: 3.0
-- see: https://github.com/sol/hpack
name: zcash-haskell
version: 0.7.4.0
version: 0.7.8.0
synopsis: Utilities to interact with the Zcash blockchain
description: Please see the README on the repo at <https://git.vergara.tech/Vergara_Tech/zcash-haskell#readme>
category: Blockchain