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/), 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).
## [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] ## [0.7.4.0]
### Added ### 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" 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.9"
bech32 = "0.11" bech32 = "0.11"
orchard = "0.9.0" orchard = "0.10.0"
zcash_note_encryption = "0.4.0" zcash_note_encryption = "0.4.0"
zcash_primitives = { version = "0.16.0", features = ["transparent-inputs"]} zcash_primitives = { version = "0.21.0", features = ["transparent-inputs"]}
zcash_client_backend = "0.13.0" zcash_client_backend = "0.16.0"
sapling-crypto = "0.2" sapling-crypto = "0.4"
zip32 = "0.1.0" zip32 = "0.1.2"
proc-macro2 = "1.0.66" proc-macro2 = "1.0.66"
nonempty = "0.7.0" nonempty = "0.7.0"
incrementalmerkletree = "0.6.0" incrementalmerkletree = "0.7.0"
secp256k1 = "0.27.0" secp256k1 = "0.27.0"
jubjub = "0.10.0" jubjub = "0.10.0"
rand_core = { version = "0.6.4", features = ["getrandom"]} rand_core = { version = "0.6.4", features = ["getrandom"]}
wagyu-zcash-parameters = "0.2.0" wagyu-zcash-parameters = "0.2.0"
bip0039 = "0.12.0" bip0039 = "0.12.0"
ahash = "0.7.8"
[features] [features]

View file

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

View file

@ -116,6 +116,7 @@ use zcash_primitives::{
}, },
transparent::{ transparent::{
Bundle as TransparentBundle, Bundle as TransparentBundle,
builder::TransparentSigningSet,
TxIn, TxIn,
TxOut, TxOut,
OutPoint, OutPoint,
@ -134,7 +135,7 @@ use zcash_primitives::{
use zcash_address::{ use zcash_address::{
Network, Network,
unified::{Address, Encoding, Ufvk, Container, Fvk, Receiver}, unified::{Address, Encoding, Ufvk, Uivk, Ivk, Container, Fvk, Receiver},
ZcashAddress ZcashAddress
}; };
@ -156,7 +157,7 @@ use orchard::{
Flags Flags
}, },
Action, Action,
keys::{SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope}, keys::{SpendAuthorizingKey, SpendingKey, FullViewingKey, PreparedIncomingViewingKey, Scope},
note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment}, note::{Rho, RandomSeed, Note, Nullifier, TransmittedNoteCiphertext, ExtractedNoteCommitment},
note_encryption::OrchardDomain, note_encryption::OrchardDomain,
primitives::redpallas::{VerificationKey, SpendAuth, Signature}, primitives::redpallas::{VerificationKey, SpendAuth, Signature},
@ -570,11 +571,11 @@ impl Hspend {
} }
} }
#[derive(Debug, BorshSerialize, BorshDeserialize)] #[derive(BorshSerialize, BorshDeserialize)]
pub struct HsaplingInput { pub struct HsaplingInput {
sk: Vec<u8>, sk: Vec<u8>,
note: Hnote, note: Hnote,
iw: Vec<u8> iw: Hpath
} }
impl<RW> FromHaskell<RW> for HsaplingInput { 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 { pub struct HorchardInput {
sk: Vec<u8>, sk: Vec<u8>,
note: Hnote, note: Hnote,
iw: Vec<u8> iw: Hpath
} }
impl<RW> FromHaskell<RW> for HorchardInput { 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)] #[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct Hsvk { pub struct Hsvk {
vk: Vec<u8>, 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] #[no_mangle]
pub extern "C" fn rust_wrapper_sapling_esk_decrypt( pub extern "C" fn rust_wrapper_sapling_esk_decrypt(
key: *const u8, 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] #[no_mangle]
pub extern "C" fn rust_wrapper_read_orchard_frontier( pub extern "C" fn rust_wrapper_read_orchard_frontier(
tree: *const u8, 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 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 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)); let nc = ExtractedNoteCommitment::from_bytes(&to_array(cmx_in));
if nc.is_some().into() { if nc.is_some().into() {
let anchor = mk_path.root(nc.unwrap()); let anchor = mk_path.root(nc.unwrap());
@ -1967,56 +2208,36 @@ pub extern "C" fn rust_wrapper_create_transaction(
out: *mut u8, out: *mut u8,
out_len: &mut usize){ out_len: &mut usize){
let sap_input: Vec<HsaplingInput> = marshall_from_haskell_var(s_input, s_input_len, RW); let sap_input: Vec<HsaplingInput> = marshall_from_haskell_var(s_input, s_input_len, RW);
let sap_anchor = let sap_anchor_in: Vec<u8> = marshall_from_haskell_var(sap_wit, sap_wit_len, RW);
if sap_input.is_empty() { let sap_anchor = Node::from_bytes(to_array(sap_anchor_in));
let sap_wit_in: Vec<u8> = marshall_from_haskell_var(sap_wit, sap_wit_len, RW); let sapling_anchor =
let sap_wit_reader = Cursor::new(sap_wit_in); if sap_anchor.is_some().into() {
let sap_iw = read_commitment_tree::<Node, Cursor<Vec<u8>>, SAPLING_DEPTH>(sap_wit_reader); Some(SaplingAnchor::from(sap_anchor.unwrap()))
match sap_iw {
Ok(s_iw) => {
Some(SaplingAnchor::from(s_iw.root()))
},
Err(_e) => {
None
}
}
} else { } else {
let si = &sap_input[0]; None
let swit_reader = Cursor::new(&si.iw);
let iw: IncrementalWitness<Node, SAPLING_DEPTH> = read_incremental_witness(swit_reader).unwrap();
Some(SaplingAnchor::from(iw.root()))
}; };
//println!("{:?}", sap_anchor); //println!("{:?}", sapling_anchor);
let orch_input: Vec<HorchardInput> = marshall_from_haskell_var(o_input, o_input_len, RW); let orch_input: Vec<HorchardInput> = marshall_from_haskell_var(o_input, o_input_len, RW);
let orch_anchor = let orch_anchor_in : Vec<u8> = marshall_from_haskell_var(orch_wit, orch_wit_len, RW);
if orch_input.is_empty() { let orch_anchor = MerkleHashOrchard::from_bytes(&to_array(orch_anchor_in));
let orch_wit_in: Vec<u8> = marshall_from_haskell_var(orch_wit, orch_wit_len, RW); let orchard_anchor =
let orch_wit_reader = Cursor::new(orch_wit_in); if orch_anchor.is_some().into() {
let orch_iw = read_commitment_tree::<MerkleHashOrchard, Cursor<Vec<u8>>, 32>(orch_wit_reader); Some(OrchardAnchor::from(orch_anchor.unwrap()))
match orch_iw {
Ok(o_iw) => {
Some(OrchardAnchor::from(o_iw.root()))
},
Err(_e) => {
None
}
}
} else { } else {
let oi = &orch_input[0]; None
let wit_reader = Cursor::new(&oi.iw);
let iw: IncrementalWitness<MerkleHashOrchard, 32> = read_incremental_witness(wit_reader).unwrap();
Some(OrchardAnchor::from(iw.root()))
}; };
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 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 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 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 { for t_in in trans_input {
if t_in.sk.len() > 1 { if t_in.sk.len() > 1 {
//println!("t inp: {:?}", t_in); //println!("t inp: {:?}", t_in);
let k = SecretKey::from_slice(&t_in.sk).unwrap(); let k = SecretKey::from_slice(&t_in.sk).unwrap();
if net { 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(()) => { Ok(()) => {
//println!("added t-input in main"); //println!("added t-input in main");
continue; continue;
@ -2024,7 +2245,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
Err(_e) => { println!("Error reading transparent input"); } Err(_e) => { println!("Error reading transparent input"); }
} }
} else { } 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(()) => { Ok(()) => {
//println!("added t-input in test"); //println!("added t-input in test");
continue; 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 { for s_in in sap_input {
if s_in.sk.len() > 1 { if s_in.sk.len() > 1 {
//println!("s inp: {:?}", s_in); //println!("s inp: {:?}", s_in);
let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk); let sp_key = ExtendedSpendingKey::from_bytes(&s_in.sk);
match sp_key { match sp_key {
Ok(sk) => { Ok(sk) => {
sap_key_array.push(sk.clone());
let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap(); let pay_addr = PaymentAddress::from_bytes(&to_array(s_in.note.recipient)).unwrap();
let rseed = if s_in.note.rseed.kind == 1 { let rseed = if s_in.note.rseed.kind == 1 {
Rseed::BeforeZip212(Fr::from_bytes(&to_array(s_in.note.rseed.bytes)).unwrap()) 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)) 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 note = SaplingNote::from_parts(pay_addr, SaplingNoteValue::from_raw(s_in.note.note), rseed);
let wit_reader = Cursor::new(s_in.iw); let mk_path = sapling_crypto::MerklePath::from_parts(s_in.iw.path.iter().map(|x|
let iw: IncrementalWitness<Node, SAPLING_DEPTH> = read_incremental_witness(wit_reader).unwrap(); if x.bytes.len() > 1 {
let merkle_path = iw.path().unwrap(); 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 { 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 { match mb {
Ok(()) => { Ok(()) => {
continue; continue;
@ -2062,7 +2290,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} else { } 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 { match tb {
Ok(()) => { Ok(()) => {
continue; continue;
@ -2081,36 +2309,46 @@ pub extern "C" fn rust_wrapper_create_transaction(
} }
} }
} }
let mut orch_keys = vec![];
for o_in in orch_input { for o_in in orch_input {
if o_in.sk.len() > 1 { if o_in.sk.len() > 1 {
let sp_key = SpendingKey::from_bytes(o_in.sk[0..32].try_into().unwrap()).unwrap(); 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 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 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 rseed = RandomSeed::from_bytes(to_array(o_in.note.rseed.bytes), &rho).unwrap();
let val = NoteValue::from_raw(o_in.note.note); let val = NoteValue::from_raw(o_in.note.note);
//println!("o inp: {:?}", val); //println!("o inp: {:?}", val);
let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap(); let note = Note::from_parts(pay_addr, val, rho, rseed).unwrap();
let wit_reader = Cursor::new(o_in.iw); let merkle_path = orchard::tree::MerklePath::from_parts(o_in.iw.position, to_array(o_in.iw.path.iter().map(|x|
let iw: IncrementalWitness<MerkleHashOrchard, 32> = read_incremental_witness(wit_reader).unwrap(); if x.bytes.len() > 1 {
let merkle_path = OrchardMerklePath::from(iw.path().unwrap()); MerkleHashOrchard::from_bytes(&to_array(x.bytes.clone())).unwrap()
} else {
MerkleHashOrchard::empty_leaf()
}
).collect()));
if net { 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 { match mb {
Ok(()) => { Ok(()) => {
//println!("added orchard inp: {:?}", val);
continue; continue;
}, },
Err(_e) => { Err(e) => {
//println!("failed orchard inp: {:?}", e);
let x = Hhex {bytes: vec![7]}; let x = Hhex {bytes: vec![7]};
marshall_to_haskell_var(&x, out, out_len, RW); marshall_to_haskell_var(&x, out, out_len, RW);
} }
} }
} else { } 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 { match tb {
Ok(()) => { Ok(()) => {
//println!("added orchard inp: {:?}", val);
continue; continue;
}, },
Err(_e) => { Err(e) => {
//println!("failed orchard inp: {:?}", e);
let x = Hhex {bytes: vec![7]}; let x = Hhex {bytes: vec![7]};
marshall_to_haskell_var(&x, out, out_len, RW); 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_params_reader = Cursor::new(output_params_in);
let output_prover = OutputParameters::read(output_params_reader, false).unwrap(); let output_prover = OutputParameters::read(output_params_reader, false).unwrap();
let result = if net { 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 { } 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 { match result {
Ok(r) => { Ok(r) => {
@ -2261,6 +2499,7 @@ pub extern "C" fn rust_wrapper_create_transaction(
Err(e) => { Err(e) => {
match e { match e {
Error::InsufficientFunds(y) => { Error::InsufficientFunds(y) => {
//println!("ins funds: {:?}", y);
let x = Hhex {bytes: vec![0]}; let x = Hhex {bytes: vec![0]};
marshall_to_haskell_var(&x, out, out_len, RW); 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 {# fun unsafe rust_wrapper_orchard_note_decrypt as rustWrapperOrchardNoteDecode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, toBorshVar* `OrchardAction'& , 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 {# fun unsafe rust_wrapper_read_orchard_node as rustWrapperReadOrchardNode
{ toBorshVar* `BS.ByteString'& { toBorshVar* `BS.ByteString'&
, getVarBuffer `Buffer HexString'& , 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 module ZcashHaskell.Keys where
import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed) import C.Zcash (rustWrapperGenSeedPhrase, rustWrapperGetSeed)
import Crypto.Secp256k1 (createContext)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.HexString (hexBytes)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Encoding as E
import Data.Word (Word8(..))
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
( withBorshVarBuffer ( withBorshVarBuffer
, withPureBorshVarBuffer , 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. -- | Generate a random seed that can be used to generate private keys for shielded addresses and transparent addresses.
generateWalletSeedPhrase :: IO Phrase generateWalletSeedPhrase :: IO Phrase
@ -36,3 +65,97 @@ getWalletSeed p =
where where
result :: Seed result :: Seed
result = (withPureBorshVarBuffer . rustWrapperGetSeed) p 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 import C.Zcash
( rustWrapperCombineOrchardNodes ( rustWrapperCombineOrchardNodes
, rustWrapperCreateOrchardFvk
, rustWrapperCreateOrchardIvk
, rustWrapperGenOrchardReceiver , rustWrapperGenOrchardReceiver
, rustWrapperGenOrchardSpendKey , rustWrapperGenOrchardSpendKey
, rustWrapperGetOrchardRootTest , rustWrapperGetOrchardRootTest
@ -37,6 +39,7 @@ import C.Zcash
, rustWrapperReadOrchardWitnessAnchor , rustWrapperReadOrchardWitnessAnchor
, rustWrapperUADecode , rustWrapperUADecode
, rustWrapperUfvkDecode , rustWrapperUfvkDecode
, rustWrapperUivkDecode
, rustWrapperUpdateOrchardWitness , rustWrapperUpdateOrchardWitness
) )
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
@ -157,6 +160,15 @@ decodeUfvk str =
where where
decodedKey = (withPureBorshVarBuffer . rustWrapperUfvkDecode) str 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 -- | Check if the given UVK matches the UA given
matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool matchOrchardAddress :: BS.ByteString -> BS.ByteString -> Bool
matchOrchardAddress = rustWrapperOrchardCheck matchOrchardAddress = rustWrapperOrchardCheck
@ -337,3 +349,25 @@ compareAddress a u =
Sapling s -> s_rec u == Just (sa_receiver s) && ua_net u == net_type s 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 Transparent t -> t_rec u == Just (ta_receiver t) && ua_net u == ta_network t
Exchange x -> False 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 module ZcashHaskell.Sapling where
import C.Zcash import C.Zcash
( rustWrapperDecodeSaplingAddress ( rustWrapperCombineSaplingNodes
, rustWrapperCreateSaplingFvk
, rustWrapperCreateSaplingIvk
, rustWrapperDecodeSaplingAddress
, rustWrapperGetSaplingRootTest
, rustWrapperIsShielded , rustWrapperIsShielded
, rustWrapperReadSaplingCommitmentTree , rustWrapperReadSaplingCommitmentTree
, rustWrapperReadSaplingFrontier , rustWrapperReadSaplingFrontier
, rustWrapperReadSaplingNode
, rustWrapperReadSaplingPathAnchor
, rustWrapperReadSaplingPosition , rustWrapperReadSaplingPosition
, rustWrapperReadSaplingTreeAnchor
, rustWrapperReadSaplingTreeParts
, rustWrapperReadSaplingWitness , rustWrapperReadSaplingWitness
, rustWrapperSaplingCheck , rustWrapperSaplingCheck
, rustWrapperSaplingChgPaymentAddress , rustWrapperSaplingChgPaymentAddress
@ -38,6 +46,7 @@ import Data.Aeson
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C import qualified Data.ByteString.Char8 as C
import Data.HexString (HexString(..), fromText, hexString, toBytes, toText) import Data.HexString (HexString(..), fromText, hexString, toBytes, toText)
import Data.Int (Int8)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Word import Data.Word
import Foreign.Rust.Marshall.Variable import Foreign.Rust.Marshall.Variable
@ -185,6 +194,56 @@ genSaplingInternalAddress sk =
res = res =
withPureBorshVarBuffer (rustWrapperSaplingChgPaymentAddress $ getBytes sk) 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 :: SaplingCommitmentTree -> Maybe SaplingFrontier
getSaplingFrontier tree = getSaplingFrontier tree =
if sf_pos updatedTree > 1 if sf_pos updatedTree > 1
@ -261,3 +320,25 @@ decodeSaplingAddress sapling_address = do
where where
sa = sa =
withPureBorshVarBuffer $ rustWrapperDecodeSaplingAddress sapling_address 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 withObject "BlockResponse" $ \obj -> do
c <- obj .: "confirmations" c <- obj .: "confirmations"
h <- obj .: "height" h <- obj .: "height"
t <- obj .:? "time" t <- obj .: "time"
txs <- obj .: "tx" txs <- obj .: "tx"
hash <- obj .: "hash" hash <- obj .: "hash"
pure $ BlockResponse hash c h (fromMaybe 0 t) txs pure $ BlockResponse hash c h t txs
instance ToJSON BlockResponse where instance ToJSON BlockResponse where
toJSON (BlockResponse h c ht t txs) = toJSON (BlockResponse h c ht t txs) =
@ -611,6 +611,21 @@ newtype SaplingCommitmentTree = SaplingCommitmentTree
{ sapTree :: HexString { sapTree :: HexString
} deriving (Eq, Prelude.Show, Read) } 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 data SaplingFrontier = SaplingFrontier
{ sf_pos :: !Int64 { sf_pos :: !Int64
, sf_leaf :: !HexString , sf_leaf :: !HexString
@ -681,6 +696,18 @@ data UnifiedFullViewingKey = UnifiedFullViewingKey
deriving anyclass (Data.Structured.Show) deriving anyclass (Data.Structured.Show)
deriving (BorshSize, ToBorsh, FromBorsh) via AsStruct UnifiedFullViewingKey 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) -- | 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 data OrchardAction = OrchardAction
{ nf :: !HexString -- ^ The nullifier of the input note { nf :: !HexString -- ^ The nullifier of the input note
@ -797,7 +824,7 @@ data TransparentTxSpend = TransparentTxSpend
data SaplingTxSpend = SaplingTxSpend data SaplingTxSpend = SaplingTxSpend
{ ss_sk :: !BS.ByteString { ss_sk :: !BS.ByteString
, ss_note :: !DecodedNote , ss_note :: !DecodedNote
, ss_iw :: !BS.ByteString , ss_iw :: !MerklePath
} deriving stock (Eq, Prelude.Show, GHC.Generic) } deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show) deriving anyclass (Data.Structured.Show)
@ -806,7 +833,7 @@ data SaplingTxSpend = SaplingTxSpend
data OrchardTxSpend = OrchardTxSpend data OrchardTxSpend = OrchardTxSpend
{ ss_sk :: !BS.ByteString { ss_sk :: !BS.ByteString
, ss_note :: !DecodedNote , ss_note :: !DecodedNote
, ss_iw :: !BS.ByteString , ss_iw :: !MerklePath
} deriving stock (Eq, Prelude.Show, GHC.Generic) } deriving stock (Eq, Prelude.Show, GHC.Generic)
deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo) deriving anyclass (SOP.Generic, SOP.HasDatatypeInfo)
deriving anyclass (Data.Structured.Show) deriving anyclass (Data.Structured.Show)

View file

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

View file

@ -45,7 +45,12 @@ import Test.HUnit
import Test.Hspec import Test.Hspec
import Test.Hspec.QuickCheck import Test.Hspec.QuickCheck
import Test.QuickCheck import Test.QuickCheck
import ZcashHaskell.Keys (generateWalletSeedPhrase, getWalletSeed) import ZcashHaskell.Keys
( deriveUfvk
, deriveUivk
, generateWalletSeedPhrase
, getWalletSeed
)
import ZcashHaskell.Orchard import ZcashHaskell.Orchard
import ZcashHaskell.Sapling import ZcashHaskell.Sapling
( decodeSaplingAddress ( decodeSaplingAddress
@ -1162,77 +1167,33 @@ main = do
Just addr -> do Just addr -> do
let eadr = decodeExchangeAddress (E.encodeUtf8 addr) let eadr = decodeExchangeAddress (E.encodeUtf8 addr)
eadr `shouldNotBe` Nothing eadr `shouldNotBe` Nothing
describe "Tree updates" $ do describe "Generate Viewing Keys" $ do
it "Orchard" $ do let p =
let tree = Phrase
OrchardCommitmentTree $ "cloth swing left trap random tornado have great onion element until make shy dad success art tuition canvas thunder apple decade elegant struggle invest"
hexString let seed = getWalletSeed p
"0136a7886d7d73bc1845223165fd9cb0cef02046c707e8f88a7f61564720bd0f3501dca1fbdd7b5ba92a0809af5e85874626ce0db14d0532a48e41dde6f0f81b46011f0001fb48c27bd07e68f27aba47cd6e93fa961e0ef8c63f993963a614e56855d2013c0001ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" let oK = genOrchardSpendingKey (fromJust seed) MainNetCoin 0
let cmx1 = let sK = genSaplingSpendingKey (fromJust seed) MainNetCoin 0
hexString it "Generate FVK" $ do
"1712ead46028d4349e234abf59e94e0640fe7a0829e2e2e17e1a931631810400" tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
let cmx2 = case oK of
hexString Nothing -> assertFailure "Failed to generate Orchard SK"
"39f5ad39817fb432fa07c5feb3a957189fbe7662a4b5555ca95093b6d853cf07" Just o ->
let cmx3 = case sK of
hexString Nothing -> assertFailure "Failed to generate Sapling SK"
"84f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f" Just s -> do
let cmx4 = fvk <- deriveUfvk MainNet o s tK
hexString decodeUfvk (E.encodeUtf8 fvk) `shouldNotBe` Nothing
"e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d429" it "Generate IVK" $ do
let finalTree = tK <- genTransparentPrvKey (fromJust seed) MainNetCoin 0
getOrchardFrontier $ case oK of
OrchardCommitmentTree $ Nothing -> assertFailure "Failed to generate Orchard SK"
hexString Just o ->
"0184f7fbc4b9f87215c653078d7fdd90756c3ba370c745065167da9eb73a65a83f01e55ad64e1ea2b261893fdea6ad0509b66e5f62d3142f351298c7135c4498d4291f0000014b1a76d3820087b26cd087ca84e17f3067a25ebed82ad23a93fa485affb5530b01ea572db9c5c2d24c7ad9132ae32b27179466bf67a580d59901d13b281d3f530b01c160348f10b9ad893d9731317ebe36ac8665e01c52cbe15a56aa9b72e4e6c41e000001cd7695156de2debdc5b13ea84d32e4e3ac020fb0aa7cd372c57ce765103bd70401746e6bc066a10e7f80a9ff8993dcb25c819edd64f2ca10ac248ef7848d41450500011e6191f91b3fceb62dc881a156e1b9d2e88e09dca25093cf9c4936c8869fb41a013bf8b923e4187754e85175748d9cce4824a6787e4258977b5bfe1ba59012c032000001f3bbdc62260c4fca5c84bf3487246d4542da48eeeec8ec40c1029b6908eef83c00000000000000000000000000000000" case sK of
case getOrchardFrontier tree of Nothing -> assertFailure "Failed to generate Sapling SK"
Nothing -> assertFailure "Failed to get frontier" Just s -> do
Just t1 -> ivk <- deriveUivk MainNet o s tK
case updateOrchardCommitmentTree t1 cmx1 of decodeUivk (E.encodeUtf8 ivk) `shouldNotBe` Nothing
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")
-- | Properties -- | Properties
prop_PhraseLength :: Property prop_PhraseLength :: Property

View file

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