From d75d5cb3ee7e11b869e98d519828bb3b8f6169ab Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Tue, 22 Dec 2020 11:37:11 -0300 Subject: [PATCH] [WIP] shield funds PoC --- Cargo.lock | 89 +++++--- Cargo.toml | 12 +- ZcashLightClientKit/zcashlc/zcashlc.h | 25 +++ rust/build.rs | 9 +- rust/src/lib.rs | 312 +++++++++++++++++++++++--- 5 files changed, 373 insertions(+), 74 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 79c43eab..10c21101 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,9 +49,9 @@ dependencies = [ [[package]] name = "ahash" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6789e291be47ace86a60303502173d84af8327e3627ecf334356ee0f87a164c" +checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" [[package]] name = "ansi_term" @@ -340,9 +340,9 @@ dependencies = [ [[package]] name = "const_fn" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" +checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826" [[package]] name = "constant_time_eq" @@ -429,7 +429,7 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" [[package]] name = "equihash" version = "0.1.0" -source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387" +source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9" dependencies = [ "blake2b_simd", "byteorder", @@ -597,14 +597,14 @@ dependencies = [ "lazy_static", "rand", "ring", - "secp256k1", + "secp256k1 0.17.2", ] [[package]] name = "heck" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" dependencies = [ "unicode-segmentation", ] @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.80" +version = "0.2.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" +checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb" [[package]] name = "libsqlite3-sys" @@ -701,8 +701,9 @@ dependencies = [ "hdwallet", "hex", "ripemd160", - "secp256k1", + "secp256k1 0.17.2", "sha2 0.9.2", + "time", "zcash_client_backend", "zcash_client_sqlite", "zcash_primitives", @@ -863,9 +864,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] @@ -971,9 +972,9 @@ dependencies = [ [[package]] name = "rusqlite" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3d4791ab5517217f51216a84a688b53c1ebf7988736469c538d02f46ddba68" +checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112" dependencies = [ "bitflags", "fallible-iterator", @@ -1024,7 +1025,16 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2932dc07acd2066ff2e3921a4419606b220ba6cd03a9935123856cc534877056" dependencies = [ - "secp256k1-sys", + "secp256k1-sys 0.1.2", +] + +[[package]] +name = "secp256k1" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6179428c22c73ac0fbb7b5579a56353ce78ba29759b3b8575183336ea74cdfb" +dependencies = [ + "secp256k1-sys 0.3.0", ] [[package]] @@ -1036,6 +1046,15 @@ dependencies = [ "cc", ] +[[package]] +name = "secp256k1-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11553d210db090930f4432bea123b31f70bbf693ace14504ea2a35e796c28dd2" +dependencies = [ + "cc", +] + [[package]] name = "semver" version = "0.9.0" @@ -1053,18 +1072,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" +checksum = "06c64263859d87aa2eb554587e2d23183398d617427327cf2b3d0ed8c69e4800" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.117" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" +checksum = "c84d3526699cd55261af4b941e4e725444df67aa4f9e6a3564f18030d12672df" dependencies = [ "proc-macro2", "quote", @@ -1115,9 +1134,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acad6f34eb9e8a259d3283d1e8c1d34d7415943d4895f65cc73813c7396fc85" +checksum = "ae524f056d7d770e174287294f562e95044c68e88dec909a00d2094805db9d75" [[package]] name = "spin" @@ -1197,15 +1216,15 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" [[package]] name = "subtle" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343f3f510c2915908f155e94f17220b19ccfacf2a64a2a5d8004f2c3e311e7fd" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.53" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8833e20724c24de12bbaba5ad230ea61c3eafb05b881c7c9d3cfe8638b187e68" +checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a" dependencies = [ "proc-macro2", "quote", @@ -1287,9 +1306,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] @@ -1326,9 +1345,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "vcpkg" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" +checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" [[package]] name = "vec_map" @@ -1443,7 +1462,7 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" [[package]] name = "zcash_client_backend" version = "0.4.0" -source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387" +source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9" dependencies = [ "base64 0.12.3", "bech32", @@ -1466,7 +1485,7 @@ dependencies = [ [[package]] name = "zcash_client_sqlite" version = "0.2.1" -source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387" +source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9" dependencies = [ "bech32", "bs58", @@ -1484,7 +1503,7 @@ dependencies = [ [[package]] name = "zcash_primitives" version = "0.4.0" -source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387" +source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9" dependencies = [ "aes", "bitvec", @@ -1503,6 +1522,8 @@ dependencies = [ "log", "rand", "rand_core", + "ripemd160", + "secp256k1 0.19.0", "sha2 0.9.2", "subtle", ] @@ -1510,7 +1531,7 @@ dependencies = [ [[package]] name = "zcash_proofs" version = "0.4.0" -source = "git+https://github.com/nuttycom/librustzcash?branch=data_access_api#e96578195adfe0a80bc4be897e72d89813ea7387" +source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9" dependencies = [ "bellman", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index a40d376f..19b062c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ ffi_helpers = "0.2" hex = "0.4" zcash_client_backend = "0.4" zcash_client_sqlite = "0.2.1" -zcash_primitives = "0.4" +zcash_primitives = { version = "0.4", features = ["transparent-inputs"] } #### Temporary additions: #################################### base58 = "0.1.0" @@ -22,8 +22,10 @@ bs58 = { version = "0.3", features = ["check"] } hdwallet = "0.2.2" ripemd160 = "0.9" secp256k1 = "0.17.2" +time = "0.2" ############################################################## + [dependencies.zcash_proofs] version = "0.4" default-features = false @@ -41,10 +43,10 @@ crate-type = ["staticlib"] lto = true [patch.crates-io] -zcash_client_backend = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"} -zcash_client_sqlite = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"} -zcash_primitives = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"} -zcash_proofs = {git = "https://github.com/nuttycom/librustzcash", branch = "data_access_api"} +zcash_client_backend = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" } +zcash_client_sqlite = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" } +zcash_primitives = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" } +zcash_proofs = {git = "https://github.com/pacu/librustzcash", branch = "autoshield-poc" } [features] mainnet = ["zcash_client_sqlite/mainnet"] diff --git a/ZcashLightClientKit/zcashlc/zcashlc.h b/ZcashLightClientKit/zcashlc/zcashlc.h index f502a38f..8a0afa6f 100644 --- a/ZcashLightClientKit/zcashlc/zcashlc.h +++ b/ZcashLightClientKit/zcashlc/zcashlc.h @@ -3,6 +3,31 @@ #include #include +/** + * + * psst, hey I have a Major in Social Sciences. Consider using something else + * Creates a transaction paying the specified address from the given account. + * + * Returns the row index of the newly-created transaction in the `transactions` table + * within the data database. The caller can read the raw transaction bytes from the `raw` + * column in order to broadcast the transaction to the network. + * + * Do not call this multiple times in parallel, or you will generate transactions that + * double-spend the same notes. + */ +int64_t zcashlc_autoshield_funds(const uint8_t *db_data, + uintptr_t db_data_len, + const uint8_t *db_cache, + uintptr_t db_cache_len, + int32_t account, + const char *tsk, + const char *extsk, + const char *memo, + const uint8_t *spend_params, + uintptr_t spend_params_len, + const uint8_t *output_params, + uintptr_t output_params_len); + int32_t zcashlc_branch_id_for_height(int32_t height); /** diff --git a/rust/build.rs b/rust/build.rs index ad6146ec..7e155e65 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -5,10 +5,11 @@ use std::env; fn main() { let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - cbindgen::Builder::new() + if let Ok(b) = cbindgen::Builder::new() .with_crate(crate_dir) .with_language(cbindgen::Language::C) - .generate() - .expect("Unable to generate bindings") - .write_to_file("ZcashLightClientKit/zcashlc/zcashlc.h"); + .generate() { + b.write_to_file("ZcashLightClientKit/zcashlc/zcashlc.h"); + } + } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 573733c1..c27c6756 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -25,13 +25,22 @@ use zcash_client_backend::{ use zcash_client_sqlite::{ wallet::init::{init_accounts_table, init_blocks_table, init_data_database}, BlockDB, NoteId, WalletDB, + wallet::{UnspentTransactionOutput, get_utxos}, }; use zcash_primitives::{ block::BlockHash, consensus::{BlockHeight, BranchId, Parameters}, note_encryption::Memo, - transaction::{components::Amount, Transaction}, - zip32::ExtendedFullViewingKey, + transaction::components::{ + amount::DEFAULT_FEE, + Amount, + OutPoint, + TxOut + }, + legacy::Script, + transaction::builder::Builder, + transaction::Transaction, + zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}, }; #[cfg(feature = "mainnet")] @@ -49,7 +58,10 @@ use base58::ToBase58; use sha2::{Digest, Sha256}; // use zcash_primitives::legacy::TransparentAddress; use hdwallet::{ExtendedPrivKey, KeyIndex}; -use secp256k1::{PublicKey, Secp256k1}; +use secp256k1::{ + Secp256k1, + key::{PublicKey, SecretKey}, +}; // use crate::extended_key::{key_index::KeyIndex, ExtendedPrivKey, ExtendedPubKey, KeySeed}; @@ -314,15 +326,25 @@ pub unsafe extern "C" fn zcashlc_derive_shielded_address_from_seed( } else { return Err(format_err!("accounts argument must be greater than zero")); }; - let address = spending_key(&seed, NETWORK.coin_type(), account_index) - .default_address() - .unwrap() - .1; - let address_str = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &address); + let address_str = deriveShieldedAddressFromSeed(&NETWORK, &seed, account_index); Ok(CString::new(address_str).unwrap().into_raw()) }); unwrap_exc_or_null(res) } + +fn deriveShieldedAddressFromSeed(params: &P, seed: &[u8], account_index: u32) -> String { + let address = spending_key(&seed, NETWORK.coin_type(), account_index) + .default_address() + .unwrap() + .1; + encode_payment_address(params.hrp_sapling_payment_address(), &address) +} + +fn deriveShieldedAddressFromSpendingKey(params: &P, extsk: &ExtendedSpendingKey) -> String { + let address = extsk.default_address().unwrap().1; + encode_payment_address(params.hrp_sapling_payment_address(), &address) +} + /// derives a shielded address from the given viewing key. /// call zcashlc_string_free with the returned pointer when done using it #[no_mangle] @@ -366,17 +388,16 @@ pub unsafe extern "C" fn zcashlc_derive_extended_full_viewing_key( Ok(Some(extsk)) => ExtendedFullViewingKey::from(&extsk), Ok(None) => { return Err(format_err!("Deriving viewing key from spending key returned no results. Encoding was valid but type was incorrect.")); - } + }, Err(e) => { return Err(format_err!( "Error while deriving viewing key from spending key: {}", e )); - } + }, }; - let encoded = - encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &extfvk); + let encoded = encode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &extfvk); Ok(CString::new(encoded).unwrap().into_raw()) }); @@ -456,32 +477,28 @@ pub extern "C" fn zcashlc_get_address( pub unsafe extern "C" fn zcashlc_is_valid_shielded_address(address: *const c_char) -> bool { let res = catch_panic(|| { let addr = CStr::from_ptr(address).to_str()?; - - match RecipientAddress::decode(&NETWORK, &addr) { - Some(addr) => match addr { - RecipientAddress::Shielded(_) => Ok(true), - RecipientAddress::Transparent(_) => Ok(false), - }, - None => Err(format_err!("Address is for the wrong network")), - } + Ok(is_valid_shielded_address(&addr)) }); unwrap_exc_or(res, false) } +fn is_valid_shielded_address(address: &str) -> bool { + match RecipientAddress::decode(&NETWORK, &address) { + Some(addr) => match addr { + RecipientAddress::Shielded(_) => true, + RecipientAddress::Transparent(_) => false, + }, + None => false, + } +} + /// Returns true when the address is valid and transparent. /// Returns false in any other case #[no_mangle] pub unsafe extern "C" fn zcashlc_is_valid_transparent_address(address: *const c_char) -> bool { let res = catch_panic(|| { let addr = CStr::from_ptr(address).to_str()?; - - match RecipientAddress::decode(&NETWORK, &addr) { - Some(addr) => match addr { - RecipientAddress::Shielded(_) => Ok(false), - RecipientAddress::Transparent(_) => Ok(true), - }, - None => Err(format_err!("Address is for the wrong network")), - } + Ok(is_valid_transparent_address(&addr)) }); unwrap_exc_or(res, false) } @@ -501,6 +518,16 @@ pub unsafe extern "C" fn zcashlc_is_valid_viewing_key(key: *const c_char) -> boo }); unwrap_exc_or(res, false) } +fn is_valid_transparent_address(address: &str) -> bool { + match RecipientAddress::decode(&NETWORK, &address) { + Some(addr) => match addr { + RecipientAddress::Shielded(_) => false, + RecipientAddress::Transparent(_) => true, + }, + None => false, + } +} + /// Returns the balance for the account, including all unspent notes that we know about. #[no_mangle] pub extern "C" fn zcashlc_get_balance(db_data: *const u8, db_data_len: usize, account: i32) -> i64 { @@ -785,17 +812,17 @@ pub extern "C" fn zcashlc_create_to_address( Ok(Some(extsk)) => extsk, Ok(None) => { return Err(format_err!("ExtendedSpendingKey is for the wrong network")); - } + }, Err(e) => { return Err(format_err!("Invalid ExtendedSpendingKey: {}", e)); - } + }, }; let to = match RecipientAddress::decode(&NETWORK, &to) { Some(to) => to, None => { return Err(format_err!("PaymentAddress is for the wrong network")); - } + }, }; let memo = Memo::from_str(&memo).map_err(|_| format_err!("Invalid memo"))?; @@ -931,6 +958,17 @@ pub unsafe extern "C" fn zcashlc_derive_transparent_address_from_seed( unwrap_exc_or_null(res) } + +fn derive_transparent_address_from_secret_key(secret_key: SecretKey) -> String { + let secp = Secp256k1::new(); + let pk = PublicKey::from_secret_key(&secp, &secret_key); + let mut hash160 = ripemd160::Ripemd160::new(); + hash160.update(Sha256::digest(&pk.serialize()[..].to_vec())); + hash160 + .finalize() + .to_base58check(&NETWORK.b58_pubkey_address_prefix(), &[]) +} + // // Helper code from: https://github.com/adityapk00/zecwallet-light-cli/blob/master/lib/src/lightwallet.rs // @@ -959,4 +997,216 @@ pub fn double_sha256(payload: &[u8]) -> Vec { let h1 = Sha256::digest(&payload); let h2 = Sha256::digest(&h1); h2.to_vec() +} + + + +//// Extremely Experimental: I'm not even a rust developer +/// +/// psst, hey I have a Major in Social Sciences. Consider using something else + +/// Creates a transaction paying the specified address from the given account. +/// +/// Returns the row index of the newly-created transaction in the `transactions` table +/// within the data database. The caller can read the raw transaction bytes from the `raw` +/// column in order to broadcast the transaction to the network. +/// +/// Do not call this multiple times in parallel, or you will generate transactions that +/// double-spend the same notes. +#[no_mangle] +pub extern "C" fn zcashlc_autoshield_funds( + db_data: *const u8, + db_data_len: usize, + db_cache: *const u8, + db_cache_len: usize, + account: i32, + tsk: *const c_char, + extsk: *const c_char, + memo: *const c_char, + spend_params: *const u8, + spend_params_len: usize, + output_params: *const u8, + output_params_len: usize, +) -> i64 { + let res = catch_panic(|| { + + let db_data = wallet_db(db_data, db_data_len)?; + let db_cache = block_db(db_cache, db_cache_len)?; + let account = if account >= 0 { + account as u32 + } else { + return Err(format_err!("account argument must be positive")); + }; + let tsk = unsafe { CStr::from_ptr(tsk) }.to_str()?; + let extsk = unsafe { CStr::from_ptr(extsk) }.to_str()?; + let memo = unsafe { CStr::from_ptr(memo) }.to_str()?; + let spend_params = Path::new(OsStr::from_bytes(unsafe { + slice::from_raw_parts(spend_params, spend_params_len) + })); + let output_params = Path::new(OsStr::from_bytes(unsafe { + slice::from_raw_parts(output_params, output_params_len) + })); + + let anchor_and_height = match (&db_data).get_target_and_anchor_heights() { + Ok(Some(h)) => h, + Err(e) => { + return Err(format_err!("Error fetching anchor and target heights: {}", e)); + }, + }; + + + // grab secret private key for t-funds + let sk = match secp256k1::key::SecretKey::from_str(&tsk) { + Ok(sk) => sk, + Err(e) => { + return Err(format_err!("Invalid Transparent Secret key: {}", e)); + }, + }; + + // derive the corresponding t-address + let t_addr_str = derive_transparent_address_from_secret_key(sk); + + let t_addr = match RecipientAddress::decode(&NETWORK, &t_addr_str) { + Some(to) => to, + None => { + return Err(format_err!("PaymentAddress is for the wrong network")); + }, + }; + + + let extsk = + match decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &extsk) + { + Ok(Some(extsk)) => extsk, + Ok(None) => { + return Err(format_err!("ExtendedSpendingKey is for the wrong network")); + }, + Err(e) => { + return Err(format_err!("Invalid ExtendedSpendingKey: {}", e)); + }, + }; + + // derive own shielded address from the provided extended spending key + let z_address = extsk.default_address().unwrap().1; + + let exfvk = ExtendedFullViewingKey::from(&extsk); + + let ovk = exfvk.fvk.ovk; + + let memo = match Memo::from_str(&memo) { + Ok(memo) => memo, + }; + + // get UTXOs from DB + let utxos = match get_utxos(&NETWORK, &db_cache) { + Ok(u) => u + }; + + // verify that the addresses of the UTXOs are correspond to the given t-address + let distinctAddresses: Vec = utxos.into_iter().filter(|utxo| utxo.address != t_addr).collect(); + + if distinctAddresses.len() > 0 { + return Err(format_err!("one or more UTXOs correspond to other addresses that don't match the provided SecretKey")); + } + + // check that the utxos are confirmed + + let latest_scanned_height = anchor_and_height.1; + let latest_anchor = anchor_and_height.0; + let unconfirmed_funds: Vec = utxos.iter().map(|u| u.height).filter(|h| h > &latest_anchor).collect(); + + if unconfirmed_funds.len() > 0 { + return Err(format_err!("one or more UTXOs are unconfirmed ")); + } + + let total_amount = utxos.iter().map(|u| u.value).sum(); + let fee = DEFAULT_FEE; + let target_value = fee + total_amount; + if fee >= total_amount { + return Err(format_err!("Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.", + u64::from(total_amount), target_value, anchor_and_height.0 + 1)); + } + let amount_to_shield = total_amount - fee; + + let prover = LocalTxProver::new(spend_params, output_params); + + let mut builder = Builder::new(NETWORK, latest_scanned_height); + + utxos.iter().map(|utxo| { + let outpoint = OutPoint::new( + utxo.txid.0, + utxo.index as u32 + ); + + let coin = TxOut { + value: utxo.value.clone(), + script_pubkey: utxo.script.clone(), + }; + builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone()) + }) + .collect() + .map_err(|e| format_err!("Error adding transparent output {}",e)); + + // there are no sapling notes so we set the change manually + builder.send_change_to(ovk, z_address); + + let recipient = RecipientAddress::Shielded(z_address); + + // add the sapling output to shield the funds + builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo)); + let consensus_branch_id = BranchId::for_height(&NETWORK, anchor_and_height.1); + + + let (tx, tx_metadata) = match builder + .build(consensus_branch_id, &prover) { + Ok(t) => t, + Err(e) => { + return Err(format_err!("Error building transaction {}",e)); + }, + }; + + + // We only called add_sapling_output() once. + let output_index = match tx_metadata.output_index(0) { + Some(idx) => idx as i64, + None => { + return Err(format_err!("Output 0 should exist in the transaction")); + }, + }; + + // Update the database atomically, to ensure the result is internally consistent. + let mut db_update = (&db_data).get_update_ops().map_err(|e| e.into())?; + db_update + .transactionally(|up| { + let created = time::OffsetDateTime::now_utc(); + let tx_ref = up.put_tx_data(&tx, Some(created))?; + + // Mark notes as spent. + // + // This locks the notes so they aren't selected again by a subsequent call to + // create_spend_to_address() before this transaction has been mined (at which point the notes + // get re-marked as spent). + // + // Assumes that create_spend_to_address() will never be called in parallel, which is a + // reasonable assumption for a light client such as a mobile phone. + for spend in &tx.shielded_spends { + up.mark_spent(tx_ref, &spend.nullifier)?; + } + + up.insert_sent_note( + &NETWORK, + tx_ref, + output_index as usize, + AccountId(account), + &RecipientAddress::from(z_address), + amount_to_shield, + Some(memo), + )?; + + // Return the row number of the transaction, so the caller can fetch it for sending. + Ok(tx_ref) + }) + .map_err(|e| format_err!("error building updating data_db {}",e)) + }); + unwrap_exc_or(res, -1) } \ No newline at end of file