diff --git a/Cargo.toml b/Cargo.toml index 421bea5..1fa4d60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,17 +10,21 @@ edition = "2018" name = "scan_all" harness = false -[[bin]] -name = "warp-cli" -path = "src/main/warp_cli.rs" +#[[bin]] +#name = "warp-cli" +#path = "src/main/warp_cli.rs" #[[bin]] #name = "ledger" #path = "src/main/ledger.rs" -[[bin]] -name = "sign" -path = "src/main/sign.rs" +#[[bin]] +#name = "sign" +#path = "src/main/sign.rs" + +[lib] +name = "warp_api_ffi" +crate-type = ["rlib"] [dependencies] dotenv = "0.15.0" @@ -70,9 +74,14 @@ hmac = { version = "0.12.1", optional = true } ed25519-bip32 = { version = "0.4.1", optional = true } ledger-transport-hid = { version = "0.9", optional = true } +allo-isolate = { version = "0.1" } +once_cell = { version = "1.8.0" } +android_logger = { version = "0.10.0" } + [features] ledger = ["ledger-apdu", "hmac", "ed25519-bip32", "ledger-transport-hid"] ledger_sapling = ["ledger"] +# dart_ffi = ["allo-isolate", "once_cell", "android_logger"] # librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd @@ -106,6 +115,7 @@ rev = "466806932d21597eb4f89a449347fa1983dffb22" [build-dependencies] tonic-build = "0.7.2" +cbindgen = "0.19.0" [dev-dependencies] criterion = "0.3.4" diff --git a/binding.h b/binding.h new file mode 100644 index 0000000..f7d9473 --- /dev/null +++ b/binding.h @@ -0,0 +1,87 @@ +#include +#include +#include +#include + +void dart_post_cobject(DartPostCObjectFnType ptr); + +void init_wallet(char *db_path); + +void set_active(uint8_t active); + +void set_active_account(uint8_t coin, uint32_t id); + +void set_coin_lwd_url(uint8_t coin, char *lwd_url); + +void reset_app(void); + +uint32_t new_account(uint8_t coin, char *name, char *data, int32_t index); + +uint32_t new_sub_account(char *name, int32_t index); + +uint8_t warp(uint8_t coin, bool get_tx, uint32_t anchor_offset, int64_t port); + +int8_t is_valid_key(uint8_t coin, char *key); + +bool valid_address(uint8_t coin, char *address); + +char *new_diversified_address(void); + +uint32_t get_latest_height(void); + +char *send_multi_payment(char *recipients_json, + bool use_transparent, + uint32_t anchor_offset, + int64_t port); + +void skip_to_last_height(uint8_t coin); + +void rewind_to_height(uint32_t height); + +int64_t mempool_sync(void); + +void mempool_reset(void); + +int64_t get_mempool_balance(void); + +uint64_t get_taddr_balance(uint8_t coin, uint32_t id_account); + +char *shield_taddr(void); + +void scan_transparent_accounts(uint32_t gap_limit); + +char *prepare_multi_payment(char *recipients_json, bool use_transparent, uint32_t anchor_offset); + +char *sign(char *tx_filename, int64_t port); + +char *broadcast(char *tx_filename); + +char *broadcast_txhex(char *txhex); + +uint32_t get_activation_date(void); + +uint32_t get_block_by_time(uint32_t time); + +uint32_t sync_historical_prices(int64_t now, uint32_t days, char *currency); + +void store_contact(uint32_t id, char *name, char *address, bool dirty); + +char *commit_unsaved_contacts(uint32_t anchor_offset); + +void mark_message_read(uint32_t message, bool read); + +void mark_all_messages_read(bool read); + +void truncate_data(void); + +void delete_account(uint8_t coin, uint32_t account); + +char *make_payment_uri(char *address, uint64_t amount, char *memo); + +char *parse_payment_uri(char *uri); + +char *generate_random_enc_key(void); + +char *get_full_backup(char *key); + +char *restore_full_backup(char *key, char *backup); diff --git a/build.rs b/build.rs index 04de3e1..b7e2ca8 100644 --- a/build.rs +++ b/build.rs @@ -6,4 +6,22 @@ fn main() { &["proto"], ) .unwrap(); + + create_c_bindings(); +} + +#[allow(dead_code)] +fn create_c_bindings() { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let config = cbindgen::Config { + language: cbindgen::Language::C, + ..Default::default() + }; + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(config) + .generate() + .expect("Unable to generate bindings") + .write_to_file("binding.h"); } diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..d997f6e --- /dev/null +++ b/src/api.rs @@ -0,0 +1,11 @@ +pub mod account; +pub mod contact; +pub mod fullbackup; +pub mod historical_prices; +pub mod mempool; +pub mod message; +pub mod payment; +pub mod payment_uri; +pub mod sync; + +pub mod dart_ffi; diff --git a/src/api/account.rs b/src/api/account.rs new file mode 100644 index 0000000..c880bd0 --- /dev/null +++ b/src/api/account.rs @@ -0,0 +1,133 @@ +// Account creation + +use crate::coinconfig::{CoinConfig, ACTIVE_COIN}; +use crate::key2::decode_key; +use anyhow::anyhow; +use bip39::{Language, Mnemonic}; +use rand::rngs::OsRng; +use rand::RngCore; +use zcash_client_backend::encoding::{decode_extended_full_viewing_key, encode_payment_address}; +use zcash_primitives::consensus::Parameters; + +pub fn new_account( + coin: u8, + name: &str, + key: Option, + index: Option, +) -> anyhow::Result { + let key = match key { + Some(key) => key, + None => { + let mut entropy = [0u8; 32]; + OsRng.fill_bytes(&mut entropy); + let mnemonic = Mnemonic::from_entropy(&entropy, Language::English)?; + mnemonic.phrase().to_string() + } + }; + let id_account = new_account_with_key(coin, name, &key, index.unwrap_or(0))?; + Ok(id_account) +} + +pub fn new_sub_account(name: &str, index: Option) -> anyhow::Result { + let c = CoinConfig::get_active(); + let db = c.db()?; + let (seed, _) = db.get_seed(c.id_account)?; + let seed = seed.ok_or_else(|| anyhow!("Account has no seed"))?; + let index = index.unwrap_or_else(|| db.next_account_id(&seed).unwrap()); + drop(db); + let id_account = new_account_with_key(c.coin, name, &seed, index)?; + Ok(id_account) +} + +fn new_account_with_key(coin: u8, name: &str, key: &str, index: u32) -> anyhow::Result { + let c = CoinConfig::get(coin); + let (seed, sk, ivk, pa) = decode_key(coin, key, index)?; + let db = c.db()?; + let (account, exists) = + db.store_account(name, seed.as_deref(), index, sk.as_deref(), &ivk, &pa)?; + if !exists { + db.create_taddr(account)?; + } + Ok(account) +} + +pub fn new_diversified_address() -> anyhow::Result { + let c = CoinConfig::get_active(); + let db = c.db()?; + let ivk = db.get_ivk(c.id_account)?; + let fvk = decode_extended_full_viewing_key( + c.chain.network().hrp_sapling_extended_full_viewing_key(), + &ivk, + )? + .unwrap(); + let mut diversifier_index = db.get_diversifier(c.id_account)?; + diversifier_index.increment().unwrap(); + let (new_diversifier_index, pa) = fvk + .find_address(diversifier_index) + .ok_or_else(|| anyhow::anyhow!("Cannot generate new address"))?; + db.store_diversifier(c.id_account, &new_diversifier_index)?; + let pa = encode_payment_address(c.chain.network().hrp_sapling_payment_address(), &pa); + Ok(pa) +} + +pub async fn get_taddr_balance_default() -> anyhow::Result { + let c = CoinConfig::get_active(); + get_taddr_balance(c.coin, c.id_account).await +} + +pub async fn get_taddr_balance(coin: u8, id_account: u32) -> anyhow::Result { + let c = CoinConfig::get(coin); + let mut client = c.connect_lwd().await?; + let address = c.db()?.get_taddr(id_account)?; + let balance = match address { + None => 0u64, + Some(address) => crate::taddr::get_taddr_balance(&mut client, &address).await?, + }; + Ok(balance) +} + +pub async fn scan_transparent_accounts(gap_limit: usize) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; + crate::taddr::scan_transparent_accounts(c.chain.network(), &mut client, gap_limit).await?; + Ok(()) +} + +// Account backup + +pub fn get_backup(account: u32) -> anyhow::Result { + let c = CoinConfig::get_active(); + let (seed, sk, ivk) = c.db()?.get_backup(account)?; + if let Some(seed) = seed { + return Ok(seed); + } + if let Some(sk) = sk { + return Ok(sk); + } + Ok(ivk) +} + +pub fn get_sk(account: u32) -> anyhow::Result { + let c = CoinConfig::get_active(); + let sk = c.db()?.get_sk(account)?; + Ok(sk) +} + +pub fn reset_db(coin: u8) -> anyhow::Result<()> { + let c = CoinConfig::get(coin); + let db = c.db()?; + db.reset_db() +} + +pub fn truncate_data() -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + let db = c.db()?; + db.truncate_data() +} + +pub fn delete_account(coin: u8, account: u32) -> anyhow::Result<()> { + let c = CoinConfig::get(coin); + let db = c.db()?; + db.delete_account(account)?; + Ok(()) +} diff --git a/src/api/contact.rs b/src/api/contact.rs new file mode 100644 index 0000000..1f1f34c --- /dev/null +++ b/src/api/contact.rs @@ -0,0 +1,49 @@ +use crate::api::payment::{build_sign_send_multi_payment, RecipientMemo}; +use crate::api::sync::get_latest_height; +use crate::coinconfig::CoinConfig; +use crate::contact::{serialize_contacts, Contact}; +use zcash_primitives::memo::Memo; + +pub fn store_contact(id: u32, name: &str, address: &str, dirty: bool) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + let contact = Contact { + id, + name: name.to_string(), + address: address.to_string(), + }; + c.db()?.store_contact(&contact, dirty)?; + Ok(()) +} + +pub async fn commit_unsaved_contacts(anchor_offset: u32) -> anyhow::Result { + let c = CoinConfig::get_active(); + let contacts = c.db()?.get_unsaved_contacts()?; + let memos = serialize_contacts(&contacts)?; + let tx_id = save_contacts_tx(&memos, anchor_offset).await?; + Ok(tx_id) +} + +pub async fn save_contacts_tx(memos: &[Memo], anchor_offset: u32) -> anyhow::Result { + let c = CoinConfig::get_active(); + let last_height = get_latest_height().await?; + let address = c.db()?.get_address(c.id_account)?; + let recipients: Vec<_> = memos + .iter() + .map(|m| RecipientMemo { + address: address.clone(), + amount: 0, + memo: m.clone(), + max_amount_per_note: 0, + }) + .collect(); + + let tx_id = build_sign_send_multi_payment( + last_height, + &recipients, + false, + anchor_offset, + Box::new(|_| {}), + ) + .await?; + Ok(tx_id) +} diff --git a/src/api/dart_ffi.rs b/src/api/dart_ffi.rs new file mode 100644 index 0000000..67709f8 --- /dev/null +++ b/src/api/dart_ffi.rs @@ -0,0 +1,506 @@ +use crate::coinconfig::{init_coin, CoinConfig}; +use crate::{broadcast_tx, ChainError}; +use allo_isolate::{ffi, IntoDart}; +use android_logger::Config; +use lazy_static::lazy_static; +use log::Level; +use std::ffi::{CStr, CString}; +use std::io::Read; +use std::os::raw::c_char; +use std::path::Path; +use std::sync::Mutex; +use zcash_primitives::transaction::builder::Progress; + +static mut POST_COBJ: Option = None; + +#[no_mangle] +pub unsafe extern "C" fn dart_post_cobject(ptr: ffi::DartPostCObjectFnType) { + POST_COBJ = Some(ptr); +} + +macro_rules! from_c_str { + ($v: ident) => { + let $v = CStr::from_ptr($v).to_string_lossy(); + }; +} + +fn to_c_str(s: String) -> *mut c_char { + CString::new(s).unwrap().into_raw() +} + +fn try_init_logger() { + android_logger::init_once( + Config::default() + // .format(|buf, record| { + // writeln!( + // buf, + // "{:?}-{:?}: {}", + // record.file(), + // record.line(), + // record.args() + // ) + // }) + .with_min_level(Level::Info), + ); +} + +fn log_result(result: anyhow::Result) -> T { + match result { + Err(err) => { + log::error!("{}", err); + T::default() + } + Ok(v) => v, + } +} + +fn log_string(result: anyhow::Result) -> String { + match result { + Err(err) => { + log::error!("{}", err); + format!("{}", err) + } + Ok(v) => v, + } +} + +fn encode_tx_result(res: anyhow::Result>) -> Vec { + let mut v = vec![]; + match res { + Ok(raw_tx) => { + v.push(0x00); + v.extend(raw_tx); + } + Err(e) => { + v.push(0x01); + v.extend(e.to_string().as_bytes()); + } + } + v +} + +#[no_mangle] +pub unsafe extern "C" fn init_wallet(db_path: *mut c_char) { + try_init_logger(); + from_c_str!(db_path); + let _ = init_coin(0, &format!("{}/zec.db", &db_path)); + let _ = init_coin(1, &format!("{}/yec.db", &db_path)); +} + +#[no_mangle] +pub unsafe extern "C" fn set_active(active: u8) { + crate::coinconfig::set_active(active); +} + +#[no_mangle] +pub unsafe extern "C" fn set_active_account(coin: u8, id: u32) { + crate::coinconfig::set_active_account(coin, id); +} + +#[no_mangle] +pub unsafe extern "C" fn set_coin_lwd_url(coin: u8, lwd_url: *mut c_char) { + from_c_str!(lwd_url); + crate::coinconfig::set_coin_lwd_url(coin, &lwd_url); +} + +#[no_mangle] +pub unsafe extern "C" fn reset_app() { + let res = || { + crate::api::account::reset_db(0)?; + crate::api::account::reset_db(1)?; + Ok(()) + }; + log_result(res()) +} + +#[no_mangle] +pub unsafe extern "C" fn new_account( + coin: u8, + name: *mut c_char, + data: *mut c_char, + index: i32, +) -> u32 { + from_c_str!(name); + from_c_str!(data); + let data = if !data.is_empty() { + Some(data.to_string()) + } else { + None + }; + let index = if index >= 0 { Some(index as u32) } else { None }; + let res = crate::api::account::new_account(coin, &name, data, index); + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn new_sub_account(name: *mut c_char, index: i32) -> u32 { + from_c_str!(name); + let index = if index >= 0 { Some(index as u32) } else { None }; + let res = crate::api::account::new_sub_account(&name, index); + log_result(res) +} + +lazy_static! { + static ref SYNC_LOCK: Mutex<()> = Mutex::new(()); +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn warp(coin: u8, get_tx: bool, anchor_offset: u32, port: i64) -> u8 { + let lock = SYNC_LOCK.try_lock(); + if let Ok(_) = lock { + let res = async { + log::info!("Sync started"); + let result = crate::api::sync::coin_sync(coin, get_tx, anchor_offset, move |height| { + let mut height = height.into_dart(); + if port != 0 { + if let Some(p) = POST_COBJ { + p(port, &mut height); + } + } + }) + .await; + log::info!("Sync finished"); + + crate::api::mempool::scan().await?; + + match result { + Ok(_) => Ok(0), + Err(err) => { + if let Some(e) = err.downcast_ref::() { + match e { + ChainError::Reorg => Ok(1), + ChainError::Busy => Ok(2), + } + } else { + log::error!("{}", err); + Ok(0xFF) + } + } + } + }; + let r = res.await; + log_result(r) + } else { + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn is_valid_key(coin: u8, key: *mut c_char) -> i8 { + from_c_str!(key); + crate::key2::is_valid_key(coin, &key) +} + +#[no_mangle] +pub unsafe extern "C" fn valid_address(coin: u8, address: *mut c_char) -> bool { + from_c_str!(address); + crate::key2::is_valid_address(coin, &address) +} + +#[no_mangle] +pub unsafe extern "C" fn new_diversified_address() -> *mut c_char { + let res = || crate::api::account::new_diversified_address(); + to_c_str(log_string(res())) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn get_latest_height() -> u32 { + let height = crate::api::sync::get_latest_height().await; + log_result(height) +} + +fn report_progress(progress: Progress, port: i64) { + if port != 0 { + let progress = match progress.end() { + Some(end) => (progress.cur() * 100 / end) as i32, + None => -(progress.cur() as i32), + }; + let mut progress = progress.into_dart(); + unsafe { + if let Some(p) = POST_COBJ { + p(port, &mut progress); + } + } + } +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn send_multi_payment( + recipients_json: *mut c_char, + use_transparent: bool, + anchor_offset: u32, + port: i64, +) -> *mut c_char { + from_c_str!(recipients_json); + let res = async move { + let height = crate::api::sync::get_latest_height().await?; + let recipients = crate::api::payment::parse_recipients(&recipients_json)?; + let res = crate::api::payment::build_sign_send_multi_payment( + height, + &recipients, + use_transparent, + anchor_offset, + Box::new(move |progress| { + report_progress(progress, port); + }), + ) + .await?; + Ok(res) + }; + to_c_str(log_string(res.await)) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn skip_to_last_height(coin: u8) { + let res = crate::api::sync::skip_to_last_height(coin).await; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn rewind_to_height(height: u32) { + let res = crate::api::sync::rewind_to_height(height).await; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn mempool_sync() -> i64 { + let res = crate::api::mempool::scan().await; + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn mempool_reset() { + let c = CoinConfig::get_active(); + let mut mempool = c.mempool.lock().unwrap(); + log_result(mempool.clear()); +} + +#[no_mangle] +pub unsafe extern "C" fn get_mempool_balance() -> i64 { + let c = CoinConfig::get_active(); + let mempool = c.mempool.lock().unwrap(); + mempool.get_unconfirmed_balance() +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn get_taddr_balance(coin: u8, id_account: u32) -> u64 { + let res = if coin == 0xFF { + crate::api::account::get_taddr_balance_default().await + } else { + crate::api::account::get_taddr_balance(coin, id_account).await + }; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn shield_taddr() -> *mut c_char { + let res = crate::api::payment::shield_taddr().await; + to_c_str(log_string(res)) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn scan_transparent_accounts(gap_limit: u32) { + let res = crate::api::account::scan_transparent_accounts(gap_limit as usize).await; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn prepare_multi_payment( + recipients_json: *mut c_char, + use_transparent: bool, + anchor_offset: u32, +) -> *mut c_char { + from_c_str!(recipients_json); + let res = async { + let last_height = crate::api::sync::get_latest_height().await?; + let recipients = crate::api::payment::parse_recipients(&recipients_json)?; + let tx = crate::api::payment::build_only_multi_payment( + last_height, + &recipients, + use_transparent, + anchor_offset, + ) + .await?; + Ok(tx) + }; + to_c_str(log_string(res.await)) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn sign(tx_filename: *mut c_char, port: i64) -> *mut c_char { + from_c_str!(tx_filename); + let res = async { + let mut file = std::fs::File::open(&tx_filename.to_string())?; + let mut s = String::new(); + file.read_to_string(&mut s)?; + let raw_tx = crate::api::payment::sign_only_multi_payment( + &s, + Box::new(move |progress| { + report_progress(progress, port); + }), + ) + .await?; + Ok(raw_tx) + }; + let tx_hex = hex::encode(encode_tx_result(res.await)); + to_c_str(tx_hex) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn broadcast(tx_filename: *mut c_char) -> *mut c_char { + from_c_str!(tx_filename); + let res = async { + let mut file = std::fs::File::open(&tx_filename.to_string())?; + let mut s = String::new(); + file.read_to_string(&mut s)?; + let tx = hex::decode(s.trim_end())?; + broadcast_tx(&tx).await + }; + to_c_str(log_string(res.await)) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn broadcast_txhex(txhex: *mut c_char) -> *mut c_char { + from_c_str!(txhex); + let res = async { + let tx = hex::decode(&txhex.to_string())?; + broadcast_tx(&tx).await + }; + to_c_str(log_string(res.await)) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn get_activation_date() -> u32 { + let res = crate::api::sync::get_activation_date().await; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn get_block_by_time(time: u32) -> u32 { + let res = crate::api::sync::get_block_by_time(time).await; + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn sync_historical_prices( + now: i64, + days: u32, + currency: *mut c_char, +) -> u32 { + from_c_str!(currency); + let res = crate::api::historical_prices::sync_historical_prices(now, days, ¤cy).await; + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn store_contact( + id: u32, + name: *mut c_char, + address: *mut c_char, + dirty: bool, +) { + from_c_str!(name); + from_c_str!(address); + let res = crate::api::contact::store_contact(id, &name, &address, dirty); + log_result(res) +} + +#[tokio::main] +#[no_mangle] +pub async unsafe extern "C" fn commit_unsaved_contacts(anchor_offset: u32) -> *mut c_char { + let res = crate::api::contact::commit_unsaved_contacts(anchor_offset).await; + to_c_str(log_string(res)) +} + +#[no_mangle] +pub unsafe extern "C" fn mark_message_read(message: u32, read: bool) { + let res = crate::api::message::mark_message_read(message, read); + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn mark_all_messages_read(read: bool) { + let res = crate::api::message::mark_all_messages_read(read); + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn truncate_data() { + let res = crate::api::account::truncate_data(); + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn delete_account(coin: u8, account: u32) { + let res = crate::api::account::delete_account(coin, account); + log_result(res) +} + +#[no_mangle] +pub unsafe extern "C" fn make_payment_uri( + address: *mut c_char, + amount: u64, + memo: *mut c_char, +) -> *mut c_char { + from_c_str!(memo); + from_c_str!(address); + let res = crate::api::payment_uri::make_payment_uri(&address, amount, &memo); + to_c_str(log_string(res)) +} + +#[no_mangle] +pub unsafe extern "C" fn parse_payment_uri(uri: *mut c_char) -> *mut c_char { + from_c_str!(uri); + let payment_json = crate::api::payment_uri::parse_payment_uri(&uri); + to_c_str(log_string(payment_json)) +} + +#[no_mangle] +pub unsafe extern "C" fn generate_random_enc_key() -> *mut c_char { + to_c_str(log_string(crate::key2::generate_random_enc_key())) +} + +#[no_mangle] +pub unsafe extern "C" fn get_full_backup(key: *mut c_char) -> *mut c_char { + from_c_str!(key); + let res = || { + let mut accounts = vec![]; + for coin in [0, 1] { + accounts.extend(crate::api::fullbackup::get_full_backup(coin)?); + } + + let backup = crate::api::fullbackup::encrypt_backup(&accounts, &key)?; + Ok(backup) + }; + to_c_str(log_string(res())) +} + +#[no_mangle] +pub unsafe extern "C" fn restore_full_backup(key: *mut c_char, backup: *mut c_char) -> *mut c_char { + from_c_str!(key); + from_c_str!(backup); + let res = || { + let accounts = crate::api::fullbackup::decrypt_backup(&key, &backup)?; + for coin in [0, 1] { + crate::api::fullbackup::restore_full_backup(coin, &accounts)?; + } + Ok(String::new()) + }; + to_c_str(log_string(res())) +} diff --git a/src/api/fullbackup.rs b/src/api/fullbackup.rs new file mode 100644 index 0000000..daf7abc --- /dev/null +++ b/src/api/fullbackup.rs @@ -0,0 +1,63 @@ +use crate::coinconfig::CoinConfig; +use crate::db::AccountBackup; +use bech32::FromBase32; +use chacha20poly1305::aead::{Aead, NewAead}; +use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; + +const NONCE: &[u8; 12] = b"unique nonce"; + +pub fn get_full_backup(coin: u8) -> anyhow::Result> { + let c = CoinConfig::get(coin); + let db = c.db()?; + db.get_full_backup() +} + +pub fn restore_full_backup(coin: u8, accounts: &[AccountBackup]) -> anyhow::Result<()> { + let c = CoinConfig::get(coin); + let db = c.db()?; + db.restore_full_backup(accounts) +} + +pub fn encrypt_backup(accounts: &[AccountBackup], key: &str) -> anyhow::Result { + let accounts_bin = bincode::serialize(&accounts)?; + let backup = if !key.is_empty() { + let (hrp, key, _) = bech32::decode(key)?; + if hrp != "zwk" { + anyhow::bail!("Invalid backup key") + } + let key = Vec::::from_base32(&key)?; + let key = Key::from_slice(&key); + + let cipher = ChaCha20Poly1305::new(key); + // nonce is constant because we always use a different key! + let cipher_text = cipher + .encrypt(Nonce::from_slice(NONCE), &*accounts_bin) + .map_err(|_e| anyhow::anyhow!("Failed to encrypt backup"))?; + base64::encode(cipher_text) + } else { + base64::encode(accounts_bin) + }; + Ok(backup) +} + +pub fn decrypt_backup(key: &str, backup: &str) -> anyhow::Result> { + let backup = if !key.is_empty() { + let (hrp, key, _) = bech32::decode(key)?; + if hrp != "zwk" { + anyhow::bail!("Not a valid decryption key"); + } + let key = Vec::::from_base32(&key)?; + let key = Key::from_slice(&key); + + let cipher = ChaCha20Poly1305::new(key); + let backup = base64::decode(backup)?; + cipher + .decrypt(Nonce::from_slice(NONCE), &*backup) + .map_err(|_e| anyhow::anyhow!("Failed to decrypt backup"))? + } else { + base64::decode(backup)? + }; + + let accounts: Vec = bincode::deserialize(&backup)?; + Ok(accounts) +} diff --git a/src/api/historical_prices.rs b/src/api/historical_prices.rs new file mode 100644 index 0000000..5d0b05d --- /dev/null +++ b/src/api/historical_prices.rs @@ -0,0 +1,9 @@ +use crate::coinconfig::CoinConfig; + +pub async fn sync_historical_prices(now: i64, days: u32, currency: &str) -> anyhow::Result { + let c = CoinConfig::get_active(); + let mut db = c.db()?; + let quotes = crate::prices::fetch_historical_prices(now, days, currency, &db).await?; + db.store_historical_prices("es, currency)?; + Ok(quotes.len() as u32) +} diff --git a/src/api/mempool.rs b/src/api/mempool.rs new file mode 100644 index 0000000..f053eab --- /dev/null +++ b/src/api/mempool.rs @@ -0,0 +1,28 @@ +use zcash_client_backend::encoding::decode_extended_full_viewing_key; +use zcash_primitives::consensus::Parameters; + +use crate::coinconfig::CoinConfig; +use crate::get_latest_height; + +pub async fn scan() -> anyhow::Result { + let c = CoinConfig::get_active(); + let ivk = c.db()?.get_ivk(c.id_account)?; + let mut client = c.connect_lwd().await?; + let height = get_latest_height(&mut client).await?; + let mut mempool = c.mempool.lock().unwrap(); + let current_height = c.height; + if height != current_height { + CoinConfig::set_height(height); + mempool.clear()?; + } + let fvk = decode_extended_full_viewing_key( + c.chain.network().hrp_sapling_extended_full_viewing_key(), + &ivk, + )? + .unwrap(); + mempool + .update(&mut client, height, &fvk.fvk.vk.ivk()) + .await?; + + Ok(mempool.get_unconfirmed_balance()) +} diff --git a/src/api/message.rs b/src/api/message.rs new file mode 100644 index 0000000..1055503 --- /dev/null +++ b/src/api/message.rs @@ -0,0 +1,13 @@ +use crate::coinconfig::CoinConfig; + +pub fn mark_message_read(message: u32, read: bool) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + c.db()?.mark_message_read(message, read)?; + Ok(()) +} + +pub fn mark_all_messages_read(read: bool) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + c.db()?.mark_all_messages_read(c.id_account, read)?; + Ok(()) +} diff --git a/src/api/payment.rs b/src/api/payment.rs new file mode 100644 index 0000000..deca86f --- /dev/null +++ b/src/api/payment.rs @@ -0,0 +1,199 @@ +use std::str::FromStr; + +use secp256k1::SecretKey; + +use crate::api::sync::get_latest_height; +use crate::coinconfig::{get_prover, CoinConfig}; +use crate::pay::TxBuilder; +use crate::{broadcast_tx, Tx}; +use zcash_client_backend::encoding::{ + decode_extended_full_viewing_key, decode_extended_spending_key, +}; +use zcash_primitives::consensus::Parameters; +use zcash_primitives::transaction::builder::Progress; + +use crate::db::ZMessage; +use crate::taddr::get_utxos; +use serde::Deserialize; +use zcash_primitives::memo::Memo; +// use crate::wallet::Recipient; + +type PaymentProgressCallback = Box; + +async fn prepare_multi_payment( + last_height: u32, + recipients: &[RecipientMemo], + use_transparent: bool, + anchor_offset: u32, +) -> anyhow::Result<(Tx, Vec)> { + let c = CoinConfig::get_active(); + let mut tx_builder = TxBuilder::new(c.coin_type, last_height); + + let fvk = c.db()?.get_ivk(c.id_account)?; + let fvk = decode_extended_full_viewing_key( + c.chain.network().hrp_sapling_extended_full_viewing_key(), + &fvk, + ) + .unwrap() + .unwrap(); + let utxos = if use_transparent { + let mut client = c.connect_lwd().await?; + let t_address = c.db()?.get_taddr(c.id_account)?; + if let Some(t_address) = t_address { + get_utxos(&mut client, &t_address, c.id_account).await? + } else { + vec![] + } + } else { + vec![] + }; + + let target_amount: u64 = recipients.iter().map(|r| r.amount).sum(); + let anchor_height = last_height.saturating_sub(anchor_offset); + let spendable_notes = c + .db()? + .get_spendable_notes(c.id_account, anchor_height, &fvk)?; + let note_ids = tx_builder.select_inputs(&fvk, &spendable_notes, &utxos, target_amount)?; + tx_builder.select_outputs(&fvk, recipients)?; + Ok((tx_builder.tx, note_ids)) +} + +fn sign(tx: &Tx, progress_callback: PaymentProgressCallback) -> anyhow::Result> { + let c = CoinConfig::get_active(); + let prover = get_prover(); + let db = c.db()?; + let zsk = db.get_sk(c.id_account)?; + let tsk = db + .get_tsk(c.id_account)? + .map(|tsk| SecretKey::from_str(&tsk).unwrap()); + let extsk = + decode_extended_spending_key(c.chain.network().hrp_sapling_extended_spending_key(), &zsk) + .unwrap() + .unwrap(); + let raw_tx = tx.sign(tsk, &extsk, prover, progress_callback)?; + Ok(raw_tx) +} + +/// Build a multi payment for offline signing +pub async fn build_only_multi_payment( + last_height: u32, + recipients: &[RecipientMemo], + use_transparent: bool, + anchor_offset: u32, +) -> anyhow::Result { + let (tx, _) = + prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?; + let tx_str = serde_json::to_string(&tx)?; + Ok(tx_str) +} + +pub async fn sign_only_multi_payment( + tx_string: &str, + progress_callback: PaymentProgressCallback, +) -> anyhow::Result> { + let tx = serde_json::from_str::(tx_string)?; + let raw_tx = sign(&tx, progress_callback)?; + Ok(raw_tx) +} + +/// Build, sign and broadcast a multi payment +pub async fn build_sign_send_multi_payment( + last_height: u32, + recipients: &[RecipientMemo], + use_transparent: bool, + anchor_offset: u32, + progress_callback: PaymentProgressCallback, +) -> anyhow::Result { + let c = CoinConfig::get_active(); + let (tx, note_ids) = + prepare_multi_payment(last_height, recipients, use_transparent, anchor_offset).await?; + let raw_tx = sign(&tx, progress_callback)?; + let tx_id = broadcast_tx(&raw_tx).await?; + + c.db()?.tx_mark_spend(¬e_ids)?; + Ok(tx_id) +} + +pub async fn shield_taddr() -> anyhow::Result { + let last_height = get_latest_height().await?; + let tx_id = build_sign_send_multi_payment(last_height, &[], true, 0, Box::new(|_| {})).await?; + Ok(tx_id) +} + +pub fn parse_recipients(recipients: &str) -> anyhow::Result> { + let c = CoinConfig::get_active(); + let address = c.db()?.get_address(c.id_account)?; + let recipients: Vec = serde_json::from_str(recipients)?; + let recipient_memos: Vec<_> = recipients + .iter() + .map(|r| RecipientMemo::from_recipient(&address, r)) + .collect(); + Ok(recipient_memos) +} + +pub fn encode_memo(from: &str, include_from: bool, subject: &str, body: &str) -> String { + let from = if include_from { from } else { "" }; + let msg = format!("\u{1F6E1}MSG\n{}\n{}\n{}", from, subject, body); + msg +} + +pub fn decode_memo(memo: &str, recipient: &str, timestamp: u32, height: u32) -> ZMessage { + let memo_lines: Vec<_> = memo.splitn(4, '\n').collect(); + let msg = if memo_lines[0] == "\u{1F6E1}MSG" { + ZMessage { + sender: if memo_lines[1].is_empty() { + None + } else { + Some(memo_lines[1].to_string()) + }, + recipient: recipient.to_string(), + subject: memo_lines[2].to_string(), + body: memo_lines[3].to_string(), + timestamp, + height, + } + } else { + ZMessage { + sender: None, + recipient: recipient.to_string(), + subject: memo_lines[0].chars().take(20).collect(), + body: memo.to_string(), + timestamp, + height, + } + }; + msg +} + +#[derive(Deserialize)] +pub struct Recipient { + pub address: String, + pub amount: u64, + pub reply_to: bool, + pub subject: String, + pub memo: String, + pub max_amount_per_note: u64, +} + +pub struct RecipientMemo { + pub address: String, + pub amount: u64, + pub memo: Memo, + pub max_amount_per_note: u64, +} + +impl RecipientMemo { + pub fn from_recipient(from: &str, r: &Recipient) -> Self { + let memo = if !r.reply_to && r.subject.is_empty() { + r.memo.clone() + } else { + encode_memo(from, r.reply_to, &r.subject, &r.memo) + }; + RecipientMemo { + address: r.address.clone(), + amount: r.amount, + memo: Memo::from_str(&memo).unwrap(), + max_amount_per_note: r.max_amount_per_note, + } + } +} diff --git a/src/api/payment_uri.rs b/src/api/payment_uri.rs new file mode 100644 index 0000000..3b91c84 --- /dev/null +++ b/src/api/payment_uri.rs @@ -0,0 +1,71 @@ +use crate::coinconfig::CoinConfig; +use serde::Serialize; +use std::convert::TryFrom; +use std::str::FromStr; +use zcash_client_backend::address::RecipientAddress; +use zcash_client_backend::zip321::{Payment, TransactionRequest}; +use zcash_primitives::memo::Memo; +use zcash_primitives::transaction::components::Amount; + +pub fn make_payment_uri(address: &str, amount: u64, memo: &str) -> anyhow::Result { + let c = CoinConfig::get_active(); + let addr = RecipientAddress::decode(c.chain.network(), address) + .ok_or_else(|| anyhow::anyhow!("Invalid address"))?; + let payment = Payment { + recipient_address: addr, + amount: Amount::from_u64(amount).map_err(|_| anyhow::anyhow!("Invalid amount"))?, + memo: Some(Memo::from_str(memo)?.into()), + label: None, + message: None, + other_params: vec![], + }; + let treq = TransactionRequest { + payments: vec![payment], + }; + let uri = treq + .to_uri(c.chain.network()) + .ok_or_else(|| anyhow::anyhow!("Cannot build Payment URI"))?; + let uri = format!("{}{}", c.chain.ticker(), &uri[5..]); // hack to replace the URI scheme + Ok(uri) +} + +pub fn parse_payment_uri(uri: &str) -> anyhow::Result { + let c = CoinConfig::get_active(); + if uri[..5].ne(c.chain.ticker()) { + anyhow::bail!("Invalid Payment URI"); + } + let uri = format!("zcash{}", &uri[5..]); // hack to replace the URI scheme + let treq = TransactionRequest::from_uri(c.chain.network(), &uri) + .map_err(|_| anyhow::anyhow!("Invalid Payment URI"))?; + if treq.payments.len() != 1 { + anyhow::bail!("Invalid Payment URI") + } + let payment = &treq.payments[0]; + let memo = match payment.memo { + Some(ref memo) => { + let memo = Memo::try_from(memo.clone())?; + match memo { + Memo::Text(text) => Ok(text.to_string()), + Memo::Empty => Ok(String::new()), + _ => Err(anyhow::anyhow!("Invalid Memo")), + } + } + None => Ok(String::new()), + }?; + let payment = MyPayment { + address: payment.recipient_address.encode(c.chain.network()), + amount: u64::from(payment.amount), + memo, + }; + + let payment_json = serde_json::to_string(&payment)?; + + Ok(payment_json) +} + +#[derive(Serialize)] +struct MyPayment { + address: String, + amount: u64, + memo: String, +} diff --git a/src/api/sync.rs b/src/api/sync.rs new file mode 100644 index 0000000..93865ea --- /dev/null +++ b/src/api/sync.rs @@ -0,0 +1,105 @@ +// Sync + +use crate::coinconfig::CoinConfig; +use crate::scan::AMProgressCallback; +use crate::{BlockId, CTree, CompactTxStreamerClient}; +use std::sync::Arc; +use tokio::sync::Mutex; +use tonic::transport::Channel; +use tonic::Request; + +const DEFAULT_CHUNK_SIZE: u32 = 100_000; + +pub async fn coin_sync( + coin: u8, + get_tx: bool, + anchor_offset: u32, + progress_callback: impl Fn(u32) + Send + 'static, +) -> anyhow::Result<()> { + let cb = Arc::new(Mutex::new(progress_callback)); + coin_sync_impl(coin, get_tx, DEFAULT_CHUNK_SIZE, anchor_offset, cb.clone()).await?; + coin_sync_impl(coin, get_tx, DEFAULT_CHUNK_SIZE, 0, cb.clone()).await?; + Ok(()) +} + +async fn coin_sync_impl( + coin: u8, + get_tx: bool, + chunk_size: u32, + target_height_offset: u32, + progress_callback: AMProgressCallback, +) -> anyhow::Result<()> { + let c = CoinConfig::get(coin); + crate::scan::sync_async( + c.coin_type, + chunk_size, + get_tx, + &c.db_path, + target_height_offset, + progress_callback, + &c.lwd_url, + ) + .await?; + Ok(()) +} + +pub async fn get_latest_height() -> anyhow::Result { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; + let last_height = crate::chain::get_latest_height(&mut client).await?; + Ok(last_height) +} + +pub async fn skip_to_last_height(coin: u8) -> anyhow::Result<()> { + let c = if coin == 0xFF { + CoinConfig::get_active() + } else { + CoinConfig::get(coin) + }; + let mut client = c.connect_lwd().await?; + let last_height = crate::chain::get_latest_height(&mut client).await?; + fetch_and_store_tree_state(&mut client, last_height).await?; + Ok(()) +} + +pub async fn rewind_to_height(height: u32) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; + c.db()?.trim_to_height(height)?; + fetch_and_store_tree_state(&mut client, height).await?; + Ok(()) +} + +async fn fetch_and_store_tree_state( + client: &mut CompactTxStreamerClient, + height: u32, +) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); + let block_id = BlockId { + height: height as u64, + hash: vec![], + }; + let block = client.get_block(block_id.clone()).await?.into_inner(); + let tree_state = client + .get_tree_state(Request::new(block_id)) + .await? + .into_inner(); + let tree = CTree::read(&*hex::decode(&tree_state.tree)?)?; + c.db()? + .store_block(height, &block.hash, block.time, &tree)?; + Ok(()) +} + +pub async fn get_activation_date() -> anyhow::Result { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; + let date_time = crate::chain::get_activation_date(c.chain.network(), &mut client).await?; + Ok(date_time) +} + +pub async fn get_block_by_time(time: u32) -> anyhow::Result { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; + let date_time = crate::chain::get_block_by_time(c.chain.network(), &mut client, time).await?; + Ok(date_time) +} diff --git a/src/builder.rs b/src/builder.rs index b516d34..18bfc91 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -10,7 +10,7 @@ use zcash_primitives::sapling::Node; #[inline(always)] fn batch_node_combine1(depth: usize, left: &Node, right: &Node) -> ExtendedPoint { // Node::new(pedersen_hash(depth as u8, &left.repr, &right.repr)) - ExtendedPoint::from(pedersen_hash_inner(depth as u8, &left.repr, &right.repr)) + pedersen_hash_inner(depth as u8, &left.repr, &right.repr) } #[inline(always)] @@ -63,15 +63,13 @@ impl Builder for CTreeBuilder { self.next_tree.right = None; m - 1 } + } else if m % 2 == 0 { + self.next_tree.parents.push(None); + m } else { - if m % 2 == 0 { - self.next_tree.parents.push(None); - m - } else { - let last_node = Self::get(commitments, m - 1, &offset); - self.next_tree.parents.push(Some(*last_node)); - m - 1 - } + let last_node = Self::get(commitments, m - 1, &offset); + self.next_tree.parents.push(Some(*last_node)); + m - 1 } } else { 0 @@ -240,13 +238,11 @@ impl Builder for WitnessBuilder { tree.left = Some(*CTreeBuilder::get(commitments, rp, &offset)); tree.right = None; } - } else { - if self.p % 2 == 1 { - tree.parents - .push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset))); - } else if self.p != 0 { - tree.parents.push(None); - } + } else if self.p % 2 == 1 { + tree.parents + .push(Some(*CTreeBuilder::get(commitments, rp - 1, &offset))); + } else if self.p != 0 { + tree.parents.push(None); } } @@ -270,10 +266,8 @@ impl Builder for WitnessBuilder { if tree.right.is_none() { self.witness.filled.push(*p1); } - } else { - if depth - 1 >= tree.parents.len() || tree.parents[depth - 1].is_none() { - self.witness.filled.push(*p1); - } + } else if depth > tree.parents.len() || tree.parents[depth - 1].is_none() { + self.witness.filled.push(*p1); } } 0 @@ -294,7 +288,7 @@ impl Builder for WitnessBuilder { let mut final_position = context.prev_tree.get_position() as u32; let mut witness_position = self.witness.tree.get_position() as u32; assert_ne!(witness_position, 0); - witness_position = witness_position - 1; + witness_position -= 1; // look for first not equal bit in MSB order final_position = final_position.reverse_bits(); @@ -434,11 +428,11 @@ mod tests { nodes: &[Node], ) { for n in nodes.iter() { - tree.append(n.clone()).unwrap(); + tree.append(*n).unwrap(); for w in ws.iter_mut() { - w.append(n.clone()).unwrap(); + w.append(*n).unwrap(); } - let w = IncrementalWitness::::from_tree(&tree); + let w = IncrementalWitness::::from_tree(tree); ws.push(w); } } @@ -450,8 +444,8 @@ mod tests { w2.write(&mut bb2).unwrap(); if bb1 != bb2 { - print_witness(&w1); - print_witness2(&w2); + print_witness(w1); + print_witness2(w2); assert!(false); } @@ -656,7 +650,7 @@ mod tests { failed_index = Some(i); println!("FAILED AT {}", i); println!("GOOD"); - print_witness(&w1); + print_witness(w1); if let Some(ref c) = w1.cursor { print_tree(c); } else { @@ -664,7 +658,7 @@ mod tests { } println!("BAD"); - print_witness2(&w2); + print_witness2(w2); } assert!(equal && failed_index.is_none()); } diff --git a/src/chain.rs b/src/chain.rs index 1aed8ef..9df1db2 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -176,12 +176,12 @@ pub fn to_output_description(co: &CompactOutput) -> CompactOutputDescription { // let epk = jubjub::ExtendedPoint::from_bytes(&epk).unwrap(); let mut enc_ciphertext = [0u8; 52]; enc_ciphertext.copy_from_slice(&co.ciphertext); - let od = CompactOutputDescription { + + CompactOutputDescription { ephemeral_key: EphemeralKeyBytes::from(epk), cmu, enc_ciphertext, - }; - od + } } struct AccountOutput<'a, N: Parameters> { @@ -208,7 +208,7 @@ impl<'a, N: Parameters> AccountOutput<'a, N> { let epk = EphemeralKeyBytes::from(epk_bytes); let mut cmu_bytes = [0u8; 32]; cmu_bytes.copy_from_slice(&co.cmu); - let cmu = as Domain>::ExtractedCommitmentBytes::from(cmu_bytes); + let cmu = cmu_bytes; let mut ciphertext_bytes = [0u8; COMPACT_NOTE_SIZE]; ciphertext_bytes.copy_from_slice(&co.ciphertext); @@ -508,7 +508,7 @@ mod tests { use crate::db::AccountViewKey; use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient; use crate::LWD_URL; - use dotenv; + use std::collections::HashMap; use std::time::Instant; use zcash_client_backend::encoding::decode_extended_full_viewing_key; diff --git a/src/coinconfig.rs b/src/coinconfig.rs new file mode 100644 index 0000000..082021d --- /dev/null +++ b/src/coinconfig.rs @@ -0,0 +1,120 @@ +use crate::{connect_lightwalletd, CompactTxStreamerClient, DbAdapter, MemPool}; +use lazy_static::lazy_static; +use lazycell::AtomicLazyCell; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard}; +use tonic::transport::Channel; +use zcash_params::coin::{get_coin_chain, CoinChain, CoinType}; +use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS}; +use zcash_proofs::prover::LocalTxProver; + +lazy_static! { + pub static ref COIN_CONFIG: [Mutex; 2] = [ + Mutex::new(CoinConfig::new(0, CoinType::Zcash)), + Mutex::new(CoinConfig::new(1, CoinType::Ycash)), + ]; + pub static ref PROVER: AtomicLazyCell = AtomicLazyCell::new(); +} + +pub static ACTIVE_COIN: AtomicU8 = AtomicU8::new(0); + +pub fn set_active(active: u8) { + ACTIVE_COIN.store(active, Ordering::Release); +} + +pub fn set_active_account(coin: u8, id: u32) { + let mempool = { + let mut c = COIN_CONFIG[coin as usize].lock().unwrap(); + c.id_account = id; + c.mempool.clone() + }; + let mut mempool = mempool.lock().unwrap(); + let _ = mempool.clear(); +} + +pub fn set_coin_lwd_url(coin: u8, lwd_url: &str) { + let mut c = COIN_CONFIG[coin as usize].lock().unwrap(); + c.lwd_url = lwd_url.to_string(); +} + +pub fn init_coin(coin: u8, db_path: &str) -> anyhow::Result<()> { + let mut c = COIN_CONFIG[coin as usize].lock().unwrap(); + c.set_db_path(db_path)?; + Ok(()) +} + +#[derive(Clone)] +pub struct CoinConfig { + pub coin: u8, + pub coin_type: CoinType, + pub id_account: u32, + pub height: u32, + pub lwd_url: String, + pub db_path: String, + pub mempool: Arc>, + pub db: Option>>, + pub chain: &'static (dyn CoinChain + Send), +} + +impl CoinConfig { + pub fn new(coin: u8, coin_type: CoinType) -> Self { + let chain = get_coin_chain(coin_type); + CoinConfig { + coin, + coin_type, + id_account: 0, + height: 0, + lwd_url: String::new(), + db_path: String::new(), + db: None, + mempool: Arc::new(Mutex::new(MemPool::new(coin))), + chain, + } + } + + pub fn set_db_path(&mut self, db_path: &str) -> anyhow::Result<()> { + self.db_path = db_path.to_string(); + let db = DbAdapter::new(self.coin_type, &self.db_path)?; + db.init_db()?; + self.db = Some(Arc::new(Mutex::new(db))); + Ok(()) + } + + pub fn get(coin: u8) -> CoinConfig { + let c = COIN_CONFIG[coin as usize].lock().unwrap(); + c.clone() + } + + pub fn get_active() -> CoinConfig { + let coin = ACTIVE_COIN.load(Ordering::Acquire) as usize; + let c = COIN_CONFIG[coin].lock().unwrap(); + c.clone() + } + + pub fn set_height(height: u32) { + let coin = ACTIVE_COIN.load(Ordering::Acquire) as usize; + let mut c = COIN_CONFIG[coin].lock().unwrap(); + c.height = height; + } + + pub fn mempool(&self) -> MutexGuard { + self.mempool.lock().unwrap() + } + + pub fn db(&self) -> anyhow::Result> { + let db = self.db.as_ref().unwrap(); + let db = db.lock().unwrap(); + Ok(db) + } + + pub async fn connect_lwd(&self) -> anyhow::Result> { + connect_lightwalletd(&self.lwd_url).await + } +} + +pub fn get_prover() -> &'static LocalTxProver { + if !PROVER.filled() { + let _ = PROVER.fill(LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS)); + } + PROVER.borrow().unwrap() +} diff --git a/src/commitment.rs b/src/commitment.rs index 9ccc7a5..5d52ebb 100644 --- a/src/commitment.rs +++ b/src/commitment.rs @@ -101,7 +101,7 @@ impl Witness { pub fn read(id_note: u32, mut reader: R) -> std::io::Result { let tree = CTree::read(&mut reader)?; let filled = Vector::read(&mut reader, |r| Node::read(r))?; - let cursor = Optional::read(&mut reader, |r| CTree::read(r))?; + let cursor = Optional::read(&mut reader, CTree::read)?; let mut witness = Witness { position: 0, @@ -147,9 +147,9 @@ impl CTree { } pub fn read(mut reader: R) -> std::io::Result { - let left = Optional::read(&mut reader, |r| Node::read(r))?; - let right = Optional::read(&mut reader, |r| Node::read(r))?; - let parents = Vector::read(&mut reader, |r| Optional::read(r, |r| Node::read(r)))?; + let left = Optional::read(&mut reader, Node::read)?; + let right = Optional::read(&mut reader, Node::read)?; + let parents = Vector::read(&mut reader, |r| Optional::read(r, Node::read))?; Ok(CTree { left, diff --git a/src/contact.rs b/src/contact.rs index 78c0a35..19c7d8e 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -63,7 +63,7 @@ impl ContactDecoder { if !self.has_contacts { return Ok(Vec::new()); } - let data: Vec<_> = self.chunks.iter().cloned().flatten().collect(); + let data: Vec<_> = self.chunks.iter().flatten().cloned().collect(); let contacts = bincode::deserialize::>(&data)?; Ok(contacts) } @@ -84,39 +84,3 @@ impl ContactDecoder { Ok((n, data.to_vec())) } } - -#[cfg(test)] -mod tests { - use crate::contact::{serialize_contacts, Contact}; - use crate::db::DEFAULT_DB_PATH; - use crate::{DbAdapter, Wallet, LWD_URL}; - use zcash_params::coin::CoinType; - - #[test] - fn test_contacts() { - let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap(); - let contact = Contact { - id: 0, - name: "hanh".to_string(), - address: - "zs1lvzgfzzwl9n85446j292zg0valw2p47hmxnw42wnqsehsmyuvjk0mhxktcs0pqrplacm2vchh35" - .to_string(), - }; - db.store_contact(&contact, true).unwrap(); - } - - #[tokio::test] - async fn test_serialize() { - let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap(); - let contacts = db.get_unsaved_contacts().unwrap(); - let memos = serialize_contacts(&contacts).unwrap(); - for m in memos.iter() { - println!("{:?}", m); - } - - let mut wallet = Wallet::new(CoinType::Zcash, "zec.db"); - wallet.set_lwd_url(LWD_URL).unwrap(); - let tx_id = wallet.save_contacts_tx(&memos, 1, 3).await.unwrap(); - println!("{}", tx_id); - } -} diff --git a/src/db.rs b/src/db.rs index c458386..469bf4a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -111,27 +111,25 @@ impl DbAdapter { sk: Option<&str>, ivk: &str, address: &str, - ) -> anyhow::Result { + ) -> anyhow::Result<(u32, bool)> { let mut statement = self .connection .prepare("SELECT id_account FROM accounts WHERE ivk = ?1")?; - if statement.exists(params![ivk])? { - return Ok(-1); - } + let exists = statement.exists(params![ivk])?; self.connection.execute( "INSERT INTO accounts(name, seed, aindex, sk, ivk, address) VALUES (?1, ?2, ?3, ?4, ?5, ?6) ON CONFLICT DO NOTHING", params![name, seed, index, sk, ivk, address], )?; - let id_tx: i32 = self.connection.query_row( + let id_account: u32 = self.connection.query_row( "SELECT id_account FROM accounts WHERE ivk = ?1", params![ivk], |row| row.get(0), )?; - Ok(id_tx) + Ok((id_account, exists)) } - pub fn next_account_id(&self, seed: &str) -> anyhow::Result { + pub fn next_account_id(&self, seed: &str) -> anyhow::Result { let index = self.connection.query_row( "SELECT MAX(aindex) FROM accounts WHERE seed = ?1", [seed], @@ -140,7 +138,7 @@ impl DbAdapter { Ok(aindex.unwrap_or(-1)) }, )? + 1; - Ok(index) + Ok(index as u32) } pub fn get_fvks(&self) -> anyhow::Result> { @@ -504,6 +502,15 @@ impl DbAdapter { Ok(spendable_notes) } + pub fn tx_mark_spend(&mut self, selected_notes: &[u32]) -> anyhow::Result<()> { + let db_tx = self.begin_transaction()?; + for id_note in selected_notes.iter() { + DbAdapter::mark_spent(*id_note, 0, &db_tx)?; + } + db_tx.commit()?; + Ok(()) + } + pub fn mark_spent(id: u32, height: u32, tx: &Transaction) -> anyhow::Result<()> { log::debug!("+mark_spent"); tx.execute( @@ -663,7 +670,7 @@ impl DbAdapter { }, ) .optional()? - .unwrap_or_else(|| [0u8; 11]); + .unwrap_or([0u8; 11]); Ok(DiversifierIndex(diversifier_index)) } diff --git a/src/db/migration.rs b/src/db/migration.rs index f9fe28c..b2ee7ba 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -28,7 +28,7 @@ pub fn reset_db(connection: &Connection) -> anyhow::Result<()> { connection.execute("DROP TABLE sapling_witnesses", [])?; connection.execute("DROP TABLE diversifiers", [])?; connection.execute("DROP TABLE historical_prices", [])?; - update_schema_version(&connection, 0)?; + update_schema_version(connection, 0)?; Ok(()) } @@ -40,7 +40,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> { [], )?; - let version = get_schema_version(&connection)?; + let version = get_schema_version(connection)?; if version < 1 { connection.execute( @@ -174,7 +174,7 @@ pub fn init_db(connection: &Connection) -> anyhow::Result<()> { } if version != 3 { - update_schema_version(&connection, 3)?; + update_schema_version(connection, 3)?; log::info!("Database migrated"); } diff --git a/src/hash.rs b/src/hash.rs index f13f308..a7733cf 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -19,7 +19,7 @@ fn read_generators_bin() -> Vec { for _j in 0..32 { for _k in 0..256 { let mut bb = [0u8; 32]; - generators_bin.read(&mut bb).unwrap(); + generators_bin.read_exact(&mut bb).unwrap(); let p = ExtendedPoint::from(SubgroupPoint::from_bytes_unchecked(&bb).unwrap()) .to_niels(); gens.push(p); @@ -52,8 +52,7 @@ type Hash = [u8; 32]; pub fn pedersen_hash(depth: u8, left: &Hash, right: &Hash) -> Hash { let p = pedersen_hash_inner(depth, left, right); - let h = jubjub::ExtendedPoint::from(p).to_affine().get_u().to_repr(); - h + p.to_affine().get_u().to_repr() } pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoint { @@ -99,7 +98,7 @@ pub fn pedersen_hash_inner(depth: u8, left: &Hash, right: &Hash) -> ExtendedPoin r = (r >> 8) | (right[r_byteoffset + 1] as u16) << 8; r_byteoffset += 1; } else if byteoffset == 63 { - r = r >> 8; + r >>= 8; } } diff --git a/src/key.rs b/src/key.rs index ecbc164..e4d5541 100644 --- a/src/key.rs +++ b/src/key.rs @@ -30,16 +30,16 @@ impl KeyHelpers { index: u32, ) -> anyhow::Result<(Option, Option, String, String)> { let network = self.chain().network(); - let res = if let Ok(mnemonic) = Mnemonic::from_phrase(&key, Language::English) { + let res = if let Ok(mnemonic) = Mnemonic::from_phrase(key, Language::English) { let (sk, ivk, pa) = self.derive_secret_key(&mnemonic, index)?; Ok((Some(key.to_string()), Some(sk), ivk, pa)) } else if let Ok(Some(sk)) = - decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key) + decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key) { let (ivk, pa) = self.derive_viewing_key(&sk)?; Ok((None, Some(key.to_string()), ivk, pa)) } else if let Ok(Some(fvk)) = - decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key) + decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key) { let pa = self.derive_address(&fvk)?; Ok((None, None, key.to_string(), pa)) @@ -51,16 +51,16 @@ impl KeyHelpers { pub fn is_valid_key(&self, key: &str) -> i8 { let network = self.chain().network(); - if Mnemonic::from_phrase(&key, Language::English).is_ok() { + if Mnemonic::from_phrase(key, Language::English).is_ok() { return 0; } if let Ok(Some(_)) = - decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &key) + decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key) { return 1; } if let Ok(Some(_)) = - decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &key) + decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key) { return 2; } @@ -73,7 +73,7 @@ impl KeyHelpers { index: u32, ) -> anyhow::Result<(String, String, String)> { let network = self.chain().network(); - let seed = Seed::new(&mnemonic, ""); + let seed = Seed::new(mnemonic, ""); let master = ExtendedSpendingKey::master(seed.as_bytes()); let path = [ ChildIndex::Hardened(32), diff --git a/src/key2.rs b/src/key2.rs new file mode 100644 index 0000000..6da54ad --- /dev/null +++ b/src/key2.rs @@ -0,0 +1,107 @@ +use crate::coinconfig::CoinConfig; +use bech32::{ToBase32, Variant}; +use bip39::{Language, Mnemonic, Seed}; +use rand::rngs::OsRng; +use rand::RngCore; +use zcash_client_backend::address::RecipientAddress; +use zcash_client_backend::encoding::{ + decode_extended_full_viewing_key, decode_extended_spending_key, + encode_extended_full_viewing_key, encode_extended_spending_key, encode_payment_address, +}; +use zcash_primitives::consensus::{Network, Parameters}; +use zcash_primitives::zip32::{ChildIndex, ExtendedFullViewingKey, ExtendedSpendingKey}; + +pub fn decode_key( + coin: u8, + key: &str, + index: u32, +) -> anyhow::Result<(Option, Option, String, String)> { + let c = CoinConfig::get(coin); + let network = c.chain.network(); + let res = if let Ok(mnemonic) = Mnemonic::from_phrase(key, Language::English) { + let (sk, ivk, pa) = derive_secret_key(network, &mnemonic, index)?; + Ok((Some(key.to_string()), Some(sk), ivk, pa)) + } else if let Ok(Some(sk)) = + decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key) + { + let (ivk, pa) = derive_viewing_key(network, &sk)?; + Ok((None, Some(key.to_string()), ivk, pa)) + } else if let Ok(Some(fvk)) = + decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key) + { + let pa = derive_address(network, &fvk)?; + Ok((None, None, key.to_string(), pa)) + } else { + Err(anyhow::anyhow!("Not a valid key")) + }; + res +} + +pub fn is_valid_key(coin: u8, key: &str) -> i8 { + let c = CoinConfig::get(coin); + let network = c.chain.network(); + if Mnemonic::from_phrase(key, Language::English).is_ok() { + return 0; + } + if let Ok(Some(_)) = + decode_extended_spending_key(network.hrp_sapling_extended_spending_key(), key) + { + return 1; + } + if let Ok(Some(_)) = + decode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), key) + { + return 2; + } + -1 +} + +pub fn is_valid_address(coin: u8, address: &str) -> bool { + let c = CoinConfig::get(coin); + let network = c.chain.network(); + let recipient = RecipientAddress::decode(network, address); + recipient.is_some() +} + +fn derive_secret_key( + network: &Network, + mnemonic: &Mnemonic, + index: u32, +) -> anyhow::Result<(String, String, String)> { + let seed = Seed::new(mnemonic, ""); + let master = ExtendedSpendingKey::master(seed.as_bytes()); + let path = [ + ChildIndex::Hardened(32), + ChildIndex::Hardened(network.coin_type()), + ChildIndex::Hardened(index), + ]; + let extsk = ExtendedSpendingKey::from_path(&master, &path); + let sk = encode_extended_spending_key(network.hrp_sapling_extended_spending_key(), &extsk); + + let (fvk, pa) = derive_viewing_key(network, &extsk)?; + Ok((sk, fvk, pa)) +} + +fn derive_viewing_key( + network: &Network, + extsk: &ExtendedSpendingKey, +) -> anyhow::Result<(String, String)> { + let fvk = ExtendedFullViewingKey::from(extsk); + let pa = derive_address(network, &fvk)?; + let fvk = + encode_extended_full_viewing_key(network.hrp_sapling_extended_full_viewing_key(), &fvk); + Ok((fvk, pa)) +} + +fn derive_address(network: &Network, fvk: &ExtendedFullViewingKey) -> anyhow::Result { + let (_, payment_address) = fvk.default_address(); + let address = encode_payment_address(network.hrp_sapling_payment_address(), &payment_address); + Ok(address) +} + +pub fn generate_random_enc_key() -> anyhow::Result { + let mut key = [0u8; 32]; + OsRng.fill_bytes(&mut key); + let key = bech32::encode("zwk", key.to_base32(), Variant::Bech32)?; + Ok(key) +} diff --git a/src/lib.rs b/src/lib.rs index aeff599..5a41b5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,11 +21,13 @@ pub const LWD_URL: &str = "https://mainnet.lightwalletd.com:9067"; mod builder; mod chain; +mod coinconfig; mod commitment; mod contact; mod db; mod hash; mod key; +mod key2; mod mempool; mod pay; mod prices; @@ -34,7 +36,8 @@ mod scan; mod taddr; mod transaction; mod ua; -mod wallet; +// mod wallet; +mod api; #[cfg(feature = "ledger")] mod ledger; @@ -72,7 +75,7 @@ pub use crate::pay::{broadcast_tx, Tx, TxIn, TxOut}; pub use crate::print::*; pub use crate::scan::{latest_height, scan_all, sync_async}; pub use crate::ua::{get_sapling, get_ua}; -pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance}; +// pub use crate::wallet::{decrypt_backup, encrypt_backup, RecipientMemo, Wallet, WalletBalance}; #[cfg(feature = "ledger_sapling")] pub use crate::ledger::sapling::build_tx_ledger; diff --git a/src/main.rs b/src/main/main.rs similarity index 99% rename from src/main.rs rename to src/main/main.rs index 0ea6039..a87a564 100644 --- a/src/main.rs +++ b/src/main/main.rs @@ -68,8 +68,8 @@ fn test_increasing_notes() { fn mk_node(pos: usize) -> Node { let mut bb = [0u8; 32]; bb[0..8].copy_from_slice(&pos.to_be_bytes()); - let node = Node::new(bb); - node + + Node::new(bb) } fn test_increasing_gap(run_normal: bool, run_warp: bool) { diff --git a/src/mempool.rs b/src/mempool.rs index b687ff6..8219435 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -1,13 +1,12 @@ use crate::chain::to_output_description; -use crate::{ - connect_lightwalletd, get_latest_height, CompactTx, CompactTxStreamerClient, DbAdapter, Exclude, -}; +use crate::{CompactTx, CompactTxStreamerClient, Exclude}; use std::collections::HashMap; use tonic::transport::Channel; use tonic::Request; -use zcash_client_backend::encoding::decode_extended_full_viewing_key; -use zcash_params::coin::{get_coin_chain, CoinChain, CoinType}; -use zcash_primitives::consensus::{BlockHeight, Parameters}; + +use crate::coinconfig::CoinConfig; +use zcash_params::coin::CoinChain; +use zcash_primitives::consensus::BlockHeight; use zcash_primitives::sapling::note_encryption::try_sapling_compact_note_decryption; use zcash_primitives::sapling::SaplingIvk; @@ -20,85 +19,38 @@ struct MemPoolTransacton { } pub struct MemPool { - coin_type: CoinType, - db_path: String, - account: u32, - ivk: Option, - height: BlockHeight, + coin: u8, transactions: HashMap, MemPoolTransacton>, nfs: HashMap, u64>, balance: i64, - ld_url: String, } impl MemPool { - pub fn new(coin_type: CoinType, db_path: &str) -> MemPool { + pub fn new(coin: u8) -> MemPool { MemPool { - coin_type, - db_path: db_path.to_string(), - account: 0, - ivk: None, - height: BlockHeight::from(0), + coin, transactions: HashMap::new(), nfs: HashMap::new(), balance: 0, - ld_url: "".to_string(), } } - pub fn set_account(&mut self, account: u32) -> anyhow::Result<()> { - let db = DbAdapter::new(self.coin_type, &self.db_path)?; - let ivk = db.get_ivk(account)?; - self.account = account; - self.set_ivk(&ivk); - self.clear(0)?; - Ok(()) - } - - fn set_ivk(&mut self, ivk: &str) { - let fvk = decode_extended_full_viewing_key( - self.chain() - .network() - .hrp_sapling_extended_full_viewing_key(), - &ivk, - ) - .unwrap() - .unwrap(); - let ivk = fvk.fvk.vk.ivk(); - self.ivk = Some(ivk); - } - - pub async fn scan(&mut self) -> anyhow::Result { - if self.ivk.is_some() { - let ivk = self.ivk.as_ref().unwrap().clone(); - let mut client = connect_lightwalletd(&self.ld_url).await?; - let height = get_latest_height(&mut client).await?; - if self.height != BlockHeight::from(height) { - // New blocks invalidate the mempool - self.clear(height)?; - } - self.update(&mut client, &ivk).await?; - } - - Ok(self.balance) - } - pub fn get_unconfirmed_balance(&self) -> i64 { self.balance } - pub fn clear(&mut self, height: u32) -> anyhow::Result<()> { - let db = DbAdapter::new(self.coin_type, &self.db_path)?; - self.height = BlockHeight::from_u32(height); - self.nfs = db.get_nullifier_amounts(self.account, true)?; + pub fn clear(&mut self) -> anyhow::Result<()> { + let c = CoinConfig::get(self.coin); + self.nfs = c.db()?.get_nullifier_amounts(c.id_account, true)?; self.transactions.clear(); self.balance = 0; Ok(()) } - async fn update( + pub async fn update( &mut self, client: &mut CompactTxStreamerClient, + height: u32, ivk: &SaplingIvk, ) -> anyhow::Result<()> { let filter: Vec<_> = self @@ -122,7 +74,7 @@ impl MemPool { tx.exclude_len += 1; // server sent us the same tx: make the filter more specific } None => { - let balance = self.scan_transaction(&tx, ivk); + let balance = self.scan_transaction(height, &tx, ivk); let mempool_tx = MemPoolTransacton { balance, exclude_len: DEFAULT_EXCLUDE_LEN, @@ -136,7 +88,8 @@ impl MemPool { Ok(()) } - fn scan_transaction(&self, tx: &CompactTx, ivk: &SaplingIvk) -> i64 { + fn scan_transaction(&self, height: u32, tx: &CompactTx, ivk: &SaplingIvk) -> i64 { + let c = CoinConfig::get_active(); let mut balance = 0i64; for cs in tx.spends.iter() { if let Some(&value) = self.nfs.get(&*cs.nf) { @@ -146,46 +99,16 @@ impl MemPool { } for co in tx.outputs.iter() { let od = to_output_description(co); - if let Some((note, _)) = - try_sapling_compact_note_decryption(self.chain().network(), self.height, ivk, &od) - { + if let Some((note, _)) = try_sapling_compact_note_decryption( + c.chain.network(), + BlockHeight::from_u32(height), + ivk, + &od, + ) { balance += note.value as i64; // value is incoming } } balance } - - pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> { - self.ld_url = ld_url.to_string(); - Ok(()) - } - - fn chain(&self) -> &dyn CoinChain { - get_coin_chain(self.coin_type) - } -} - -#[cfg(test)] -mod tests { - use crate::db::DEFAULT_DB_PATH; - use crate::mempool::MemPool; - use crate::{DbAdapter, LWD_URL}; - use std::time::Duration; - use zcash_params::coin::CoinType; - - #[tokio::test] - async fn test_mempool() { - let db = DbAdapter::new(CoinType::Zcash, DEFAULT_DB_PATH).unwrap(); - let ivk = db.get_ivk(1).unwrap(); - let mut mempool = MemPool::new(CoinType::Zcash, "zec.db"); - mempool.set_lwd_url(LWD_URL).unwrap(); - mempool.set_ivk(&ivk); - loop { - mempool.scan().await.unwrap(); - let unconfirmed = mempool.get_unconfirmed_balance(); - println!("{}", unconfirmed); - tokio::time::sleep(Duration::from_secs(10)).await; - } - } } diff --git a/src/pay.rs b/src/pay.rs index 858a89f..8e52b76 100644 --- a/src/pay.rs +++ b/src/pay.rs @@ -1,8 +1,8 @@ use crate::db::SpendableNote; -use crate::wallet::RecipientMemo; -use crate::{ - connect_lightwalletd, get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction, -}; +// use crate::wallet::RecipientMemo; +use crate::api::payment::RecipientMemo; +use crate::coinconfig::CoinConfig; +use crate::{get_latest_height, hex_to_hash, GetAddressUtxosReply, RawTransaction}; use anyhow::anyhow; use jubjub::Fr; use rand::prelude::SliceRandom; @@ -115,7 +115,7 @@ impl TxBuilder { self.chain() .network() .hrp_sapling_extended_full_viewing_key(), - &fvk, + fvk, ), amount: u64::from(amount), rseed: hex::encode(rseed), @@ -249,7 +249,7 @@ impl TxBuilder { ) -> anyhow::Result<()> { let ovk = &fvk.fvk.ovk; let (_, change) = fvk.default_address(); - self.set_change(&ovk, &change)?; + self.set_change(ovk, &change)?; for r in recipients.iter() { let to_addr = RecipientAddress::decode(self.chain().network(), &r.address) @@ -274,7 +274,7 @@ impl TxBuilder { match &to_addr { RecipientAddress::Shielded(_pa) => { log::info!("Sapling output: {}", r.amount); - self.add_z_output(&r.address, ovk, note_amount, &memo) + self.add_z_output(&r.address, ovk, note_amount, memo) } RecipientAddress::Transparent(_address) => { self.add_t_output(&r.address, note_amount) @@ -391,8 +391,9 @@ impl Tx { } } -pub async fn broadcast_tx(tx: &[u8], ld_url: &str) -> anyhow::Result { - let mut client = connect_lightwalletd(ld_url).await?; +pub async fn broadcast_tx(tx: &[u8]) -> anyhow::Result { + let c = CoinConfig::get_active(); + let mut client = c.connect_lwd().await?; let latest_height = get_latest_height(&mut client).await?; let raw_tx = RawTransaction { data: tx.to_vec(), diff --git a/src/print.rs b/src/print.rs index 1648784..ce7a55e 100644 --- a/src/print.rs +++ b/src/print.rs @@ -25,7 +25,7 @@ pub fn print_witness(w: &IncrementalWitness) { print_node(n); } println!("Cursor"); - w.cursor.as_ref().map(|c| print_tree(c)); + w.cursor.as_ref().map(print_tree); } pub fn print_ctree(t: &CTree) { diff --git a/src/scan.rs b/src/scan.rs index cbe9209..e9af3a2 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -76,7 +76,8 @@ impl std::fmt::Debug for Blocks { } } -pub type ProgressCallback = Arc>; +pub type ProgressCallback = dyn Fn(u32) + Send; +pub type AMProgressCallback = Arc>; #[derive(PartialEq, PartialOrd, Debug, Hash, Eq)] pub struct TxIdHeight { @@ -91,14 +92,14 @@ pub async fn sync_async( get_tx: bool, db_path: &str, target_height_offset: u32, - progress_callback: ProgressCallback, + progress_callback: AMProgressCallback, ld_url: &str, ) -> anyhow::Result<()> { let ld_url = ld_url.to_owned(); let db_path = db_path.to_string(); let network = { let chain = get_coin_chain(coin_type); - chain.network().clone() + *chain.network() }; let mut client = connect_lightwalletd(&ld_url).await?; @@ -308,7 +309,7 @@ pub async fn sync_async( if c == Ordering::Equal { return a.index.cmp(&b.index); } - return c; + c }); let ids: Vec<_> = ids.into_iter().map(|e| e.id_tx).collect(); retrieve_tx_info(coin_type, &mut client, &db_path2, &ids) diff --git a/src/taddr.rs b/src/taddr.rs index b1ac78b..a4dec1c 100644 --- a/src/taddr.rs +++ b/src/taddr.rs @@ -1,3 +1,4 @@ +use crate::coinconfig::CoinConfig; use crate::{ AddressList, CompactTxStreamerClient, DbAdapter, GetAddressUtxosArg, GetAddressUtxosReply, }; @@ -28,35 +29,30 @@ pub async fn get_taddr_balance( pub async fn get_utxos( client: &mut CompactTxStreamerClient, - db: &DbAdapter, - account: u32, + t_address: &str, + _account: u32, ) -> anyhow::Result> { - let t_address = db.get_taddr(account)?; - if let Some(t_address) = t_address { - let req = GetAddressUtxosArg { - addresses: vec![t_address.to_string()], - start_height: 0, - max_entries: 0, - }; - let utxo_rep = client - .get_address_utxos(Request::new(req)) - .await? - .into_inner(); - Ok(utxo_rep.address_utxos) - } else { - Ok(vec![]) - } + let req = GetAddressUtxosArg { + addresses: vec![t_address.to_string()], + start_height: 0, + max_entries: 0, + }; + let utxo_rep = client + .get_address_utxos(Request::new(req)) + .await? + .into_inner(); + Ok(utxo_rep.address_utxos) } pub async fn scan_transparent_accounts( network: &Network, client: &mut CompactTxStreamerClient, - db: &DbAdapter, - account: u32, gap_limit: usize, ) -> anyhow::Result<()> { + let c = CoinConfig::get_active(); let mut addresses = vec![]; - let (seed, mut index) = db.get_seed(account)?; + let db = c.db()?; + let (seed, mut index) = db.get_seed(c.id_account)?; if let Some(seed) = seed { let mut gap = 0; while gap < gap_limit { @@ -86,10 +82,10 @@ pub fn derive_tkeys( phrase: &str, path: &str, ) -> anyhow::Result<(String, String)> { - let mnemonic = Mnemonic::from_phrase(&phrase, Language::English)?; + let mnemonic = Mnemonic::from_phrase(phrase, Language::English)?; let seed = Seed::new(&mnemonic, ""); let secp = Secp256k1::::new(); - let ext = ExtendedPrivKey::derive(&seed.as_bytes(), path).unwrap(); + let ext = ExtendedPrivKey::derive(seed.as_bytes(), path).unwrap(); let secret_key = SecretKey::from_slice(&ext.secret()).unwrap(); let pub_key = PublicKey::from_secret_key(&secp, &secret_key); let pub_key = pub_key.serialize(); diff --git a/src/transaction.rs b/src/transaction.rs index f38a066..87c0d92 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -1,5 +1,6 @@ use crate::contact::{Contact, ContactDecoder}; -use crate::wallet::decode_memo; +// use crate::wallet::decode_memo; +use crate::api::payment::decode_memo; use crate::{CompactTxStreamerClient, DbAdapter, TxFilter}; use anyhow::anyhow; use futures::StreamExt; @@ -54,7 +55,7 @@ pub async fn decode_transaction( timestamp: u32, index: u32, ) -> anyhow::Result { - let consensus_branch_id = get_branch(network, u32::from(height)); + let consensus_branch_id = get_branch(network, height); let ivk = fvk.fvk.vk.ivk(); let ovk = fvk.fvk.ovk; @@ -112,7 +113,7 @@ pub async fn decode_transaction( tx_memo = memo; } } else if let Some((_note, pa, memo)) = - try_sapling_output_recovery(network, height, &ovk, &output) + try_sapling_output_recovery(network, height, &ovk, output) { zaddress = encode_payment_address(network.hrp_sapling_payment_address(), &pa); let memo = Memo::try_from(memo)?; @@ -176,7 +177,7 @@ pub async fn retrieve_tx_info( ) -> anyhow::Result<()> { let network = { let chain = get_coin_chain(coin_type); - chain.network().clone() + *chain.network() }; let db = DbAdapter::new(coin_type, db_path)?; diff --git a/src/ua.rs b/src/ua.rs index 681c873..eeb7f6b 100644 --- a/src/ua.rs +++ b/src/ua.rs @@ -10,7 +10,7 @@ pub struct MyReceiver { impl FromAddress for MyReceiver { fn from_sapling(net: Network, data: [u8; 43]) -> Result { Ok(MyReceiver { - net: net, + net, receiver: Receiver::Sapling(data), }) } @@ -20,19 +20,19 @@ impl FromAddress for MyReceiver { match r { Receiver::Sapling(data) => { return Ok(MyReceiver { - net: net, - receiver: Receiver::Sapling(data.clone()), + net, + receiver: Receiver::Sapling(*data), }); } _ => (), } } - return FromAddress::from_unified(net, data); + FromAddress::from_unified(net, data) } fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Result { Ok(MyReceiver { - net: net, + net, receiver: Receiver::P2pkh(data), }) } diff --git a/src/wallet.rs b/src/wallet.rs index 81770fe..29527b7 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -5,7 +5,7 @@ use crate::key::KeyHelpers; use crate::pay::Tx; use crate::pay::TxBuilder; use crate::prices::fetch_historical_prices; -use crate::scan::ProgressCallback; +use crate::scan::AM_ProgressCallback; use crate::taddr::{get_taddr_balance, get_utxos, scan_transparent_accounts}; use crate::{ broadcast_tx, connect_lightwalletd, get_latest_height, BlockId, CTree, CompactTxStreamerClient, @@ -92,7 +92,7 @@ pub struct RecipientMemo { } impl RecipientMemo { - fn from_recipient(from: &str, r: &Recipient) -> Self { + pub fn from_recipient(from: &str, r: &Recipient) -> Self { let memo = if !r.reply_to && r.subject.is_empty() { r.memo.clone() } else { @@ -224,7 +224,7 @@ impl Wallet { db_path: &str, chunk_size: u32, target_height_offset: u32, - progress_callback: ProgressCallback, + progress_callback: AM_ProgressCallback, ld_url: &str, ) -> anyhow::Result<()> { crate::scan::sync_async(