From cb7a2e04b94f4682b8d7096ee6d35c6d8170568e Mon Sep 17 00:00:00 2001 From: Hanh Date: Fri, 9 Jul 2021 21:33:05 +0800 Subject: [PATCH] taddrs --- Cargo.toml | 5 ++ src/chain.rs | 12 ++--- src/db.rs | 71 ++++++++++++++++++++++++- src/lib.rs | 1 + src/main/warp_cli.rs | 6 +-- src/mempool.rs | 10 ++-- src/scan.rs | 10 ++-- src/taddr.rs | 120 +++++++++++++++++++++++++++++++++++++++++++ src/wallet.rs | 54 ++++++++++++++----- 9 files changed, 257 insertions(+), 32 deletions(-) create mode 100644 src/taddr.rs diff --git a/Cargo.toml b/Cargo.toml index 4931d7c..0cbbf2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,10 @@ bls12_381 = "^0.4.0" ff = "^0.9" group = "^0.9" byteorder = "^1.4" +secp256k1 = "0.20.2" +tiny-hderive = "0.3.0" +ripemd160 = "0.9.1" +sha2 = "0.9.5" # librustzcash synced to 35023ed8ca2fb1061e78fd740b640d4eefcc5edd @@ -46,6 +50,7 @@ path = "/home/hanh/projects/librustzcash/zcash_client_backend" [dependencies.zcash_primitives] path = "/home/hanh/projects/librustzcash/zcash_primitives" +features = [ "transparent-inputs" ] [dependencies.zcash_proofs] path = "/home/hanh/projects/librustzcash/zcash_proofs" diff --git a/src/chain.rs b/src/chain.rs index a3c3c6f..17ba113 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -1,7 +1,7 @@ use crate::commitment::{CTree, Witness}; use crate::lw_rpc::compact_tx_streamer_client::CompactTxStreamerClient; use crate::lw_rpc::*; -use crate::{advance_tree, NETWORK, LWD_URL}; +use crate::{advance_tree, NETWORK}; use ff::PrimeField; use group::GroupEncoding; use log::info; @@ -316,9 +316,9 @@ pub fn calculate_tree_state_v2(cbs: &[CompactBlock], blocks: &[DecryptedBlock]) new_witnesses } -pub async fn connect_lightwalletd() -> anyhow::Result> { - let mut channel = tonic::transport::Channel::from_shared(LWD_URL)?; - if LWD_URL.starts_with("https") { +pub async fn connect_lightwalletd(url: &str) -> anyhow::Result> { + let mut channel = tonic::transport::Channel::from_shared(url.to_owned())?; + if url.starts_with("https") { let pem = include_bytes!("ca.pem"); let ca = Certificate::from_pem(pem); let tls = ClientTlsConfig::new().ca_certificate(ca); @@ -328,7 +328,7 @@ pub async fn connect_lightwalletd() -> anyhow::Result) -> anyhow::Result<()> { +pub async fn sync(fvks: &HashMap, ld_url: &str) -> anyhow::Result<()> { let fvks: HashMap<_, _> = fvks.iter().map(|(&account, fvk)| { let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &fvk) @@ -337,7 +337,7 @@ pub async fn sync(fvks: &HashMap) -> anyhow::Result<()> { (account, fvk) }).collect(); let decrypter = DecryptNode::new(fvks); - let mut client = connect_lightwalletd().await?; + let mut client = connect_lightwalletd(ld_url).await?; let start_height: u32 = crate::NETWORK .activation_height(NetworkUpgrade::Sapling) .unwrap() diff --git a/src/db.rs b/src/db.rs index 618022c..3b09675 100644 --- a/src/db.rs +++ b/src/db.rs @@ -6,6 +6,7 @@ use zcash_primitives::consensus::{NetworkUpgrade, Parameters}; use zcash_primitives::merkle_tree::IncrementalWitness; use zcash_primitives::sapling::{Diversifier, Node, Note, Rseed}; use zcash_primitives::zip32::{ExtendedFullViewingKey, DiversifierIndex}; +use crate::taddr::{derive_tkeys, BIP44_PATH}; #[allow(dead_code)] pub const DEFAULT_DB_PATH: &str = "zec.db"; @@ -105,6 +106,14 @@ impl DbAdapter { NO_PARAMS, )?; + self.connection.execute( + "CREATE TABLE IF NOT EXISTS taddrs ( + account INTEGER PRIMARY KEY NOT NULL, + sk TEXT NOT NULL, + address TEXT NOT NULL)", + NO_PARAMS, + )?; + Ok(()) } @@ -447,6 +456,20 @@ impl DbAdapter { Ok((seed, sk, ivk)) } + pub fn get_seed(&self, account: u32) -> anyhow::Result> { + log::info!("+get_seed"); + let seed = self.connection.query_row( + "SELECT seed FROM accounts WHERE id_account = ?1", + params![account], + |row| { + let sk: String = row.get(0)?; + Ok(sk) + }, + ).optional()?; + log::info!("-get_seed"); + Ok(seed) + } + pub fn get_sk(&self, account: u32) -> anyhow::Result { log::info!("+get_sk"); let sk = self.connection.query_row( @@ -475,6 +498,20 @@ impl DbAdapter { Ok(ivk) } + pub fn get_address(&self, account: u32) -> anyhow::Result { + log::debug!("+get_address"); + let address = self.connection.query_row( + "SELECT address FROM accounts WHERE id_account = ?1", + params![account], + |row| { + let address: String = row.get(0)?; + Ok(address) + }, + )?; + log::debug!("-get_address"); + Ok(address) + } + pub fn get_diversifier(&self, account: u32) -> anyhow::Result { let diversifier_index = self.connection.query_row( "SELECT diversifier_index FROM diversifiers WHERE account = ?1", @@ -484,7 +521,7 @@ impl DbAdapter { let mut div = [0u8; 11]; div.copy_from_slice(&d); Ok(div) - } + }, ).optional()?.unwrap_or_else(|| [0u8; 11]); Ok(DiversifierIndex(diversifier_index)) } @@ -497,6 +534,38 @@ impl DbAdapter { params![account, diversifier_bytes])?; Ok(()) } + + pub fn get_taddr(&self, account: u32) -> anyhow::Result> { + let address = self.connection.query_row( + "SELECT address FROM taddrs WHERE account = ?1", + params![account], + |row| { + let address: String = row.get(0)?; + Ok(address) + }).optional()?; + Ok(address) + } + + pub fn get_tsk(&self, account: u32) -> anyhow::Result { + let sk = self.connection.query_row( + "SELECT sk FROM taddrs WHERE account = ?1", + params![account], + |row| { + let address: String = row.get(0)?; + Ok(address) + })?; + Ok(sk) + } + + pub fn create_taddr(&self, account: u32) -> anyhow::Result<()> { + let seed = self.get_seed(account)?; + if let Some(seed) = seed { + let (sk, address) = derive_tkeys(&seed, BIP44_PATH)?; + self.connection.execute("INSERT INTO taddrs(account, sk, address) VALUES (?1, ?2, ?3) \ + ON CONFLICT DO NOTHING", params![account, &sk, &address])?; + } + Ok(()) + } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index c23f9df..d3af4ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ mod key; mod mempool; mod print; mod scan; +mod taddr; mod wallet; pub use crate::builder::advance_tree; diff --git a/src/main/warp_cli.rs b/src/main/warp_cli.rs index 4119e65..91ee378 100644 --- a/src/main/warp_cli.rs +++ b/src/main/warp_cli.rs @@ -1,7 +1,7 @@ use bip39::{Language, Mnemonic}; use rand::rngs::OsRng; use rand::RngCore; -use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2}; +use sync::{DbAdapter, Wallet, ChainError, Witness, print_witness2, LWD_URL}; use rusqlite::NO_PARAMS; const DB_NAME: &str = "zec.db"; @@ -22,7 +22,7 @@ async fn test() -> anyhow::Result<()> { let progress = |height| { log::info!("Height = {}", height); }; - let wallet = Wallet::new(DB_NAME); + let wallet = Wallet::new(DB_NAME, LWD_URL); wallet.new_account_with_key("test", &seed).unwrap(); let res = wallet.sync(progress).await; if let Err(err) = res { @@ -34,7 +34,7 @@ async fn test() -> anyhow::Result<()> { } } let tx_id = wallet - .send_payment(1, &address, 50000, u64::max_value(), move |progress| { println!("{}", progress.cur()); }) + .send_payment(1, &address, 50000, u64::max_value(), 2, move |progress| { println!("{}", progress.cur()); }) .await .unwrap(); println!("TXID = {}", tx_id); diff --git a/src/mempool.rs b/src/mempool.rs index f16bac3..54945e3 100644 --- a/src/mempool.rs +++ b/src/mempool.rs @@ -27,10 +27,11 @@ pub struct MemPool { transactions: HashMap, MemPoolTransacton>, nfs: HashMap, u64>, balance: i64, + ld_url: String, } impl MemPool { - pub fn new(db_path: &str) -> MemPool { + pub fn new(db_path: &str, ld_url: &str) -> MemPool { MemPool { db_path: db_path.to_string(), account: 0, @@ -39,6 +40,7 @@ impl MemPool { transactions: HashMap::new(), nfs: HashMap::new(), balance: 0, + ld_url: ld_url.to_string(), } } @@ -63,7 +65,7 @@ impl MemPool { 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().await?; + let mut client = connect_lightwalletd(&self.ld_url).await?; let height = BlockHeight::from(get_latest_height(&mut client).await?); if self.height != height { // New blocks invalidate the mempool @@ -154,14 +156,14 @@ impl MemPool { mod tests { use crate::db::DEFAULT_DB_PATH; use crate::mempool::MemPool; - use crate::DbAdapter; + use crate::{DbAdapter, LWD_URL}; use std::time::Duration; #[tokio::test] async fn test_mempool() { let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap(); let ivk = db.get_ivk(1).unwrap(); - let mut mempool = MemPool::new("zec.db"); + let mut mempool = MemPool::new("zec.db", LWD_URL); mempool.set_ivk(&ivk); loop { mempool.scan().await.unwrap(); diff --git a/src/scan.rs b/src/scan.rs index 99f0c03..e251709 100644 --- a/src/scan.rs +++ b/src/scan.rs @@ -75,10 +75,12 @@ pub async fn sync_async( db_path: &str, target_height_offset: u32, progress_callback: ProgressCallback, + ld_url: &str ) -> anyhow::Result<()> { + let ld_url = ld_url.to_owned(); let db_path = db_path.to_string(); - let mut client = connect_lightwalletd().await?; + let mut client = connect_lightwalletd(&ld_url).await?; let (start_height, mut prev_hash, fvks) = { let db = DbAdapter::new(&db_path)?; let height = db.get_db_height()?; @@ -102,7 +104,7 @@ pub async fn sync_async( let (processor_tx, mut processor_rx) = mpsc::channel::(1); let downloader = tokio::spawn(async move { - let mut client = connect_lightwalletd().await?; + let mut client = connect_lightwalletd(&ld_url).await?; while let Some(range) = download_rx.recv().await { log::info!("+ {:?}", range); let blocks = download_chain(&mut client, range.start, range.end, prev_hash).await?; @@ -302,8 +304,8 @@ pub async fn sync_async( Ok(()) } -pub async fn latest_height() -> anyhow::Result { - let mut client = connect_lightwalletd().await?; +pub async fn latest_height(ld_url: &str) -> anyhow::Result { + let mut client = connect_lightwalletd(ld_url).await?; let height = get_latest_height(&mut client).await?; Ok(height) } diff --git a/src/taddr.rs b/src/taddr.rs new file mode 100644 index 0000000..e8df366 --- /dev/null +++ b/src/taddr.rs @@ -0,0 +1,120 @@ +use zcash_primitives::transaction::builder::Builder; +use crate::{CompactTxStreamerClient, AddressList, DbAdapter, NETWORK, connect_lightwalletd, get_latest_height, GetAddressUtxosArg}; +use tonic::transport::Channel; +use tonic::Request; +use zcash_primitives::consensus::{BlockHeight, Parameters, BranchId}; +use zcash_primitives::transaction::components::amount::DEFAULT_FEE; +use zcash_primitives::transaction::components::{Amount, OutPoint, TxOut}; +use std::str::FromStr; +use anyhow::Context; +use zcash_primitives::legacy::{Script, TransparentAddress}; +use zcash_client_backend::encoding::{decode_extended_full_viewing_key, decode_payment_address, encode_transparent_address}; +use crate::chain::send_transaction; +use zcash_proofs::prover::LocalTxProver; +use ripemd160::{Ripemd160, Digest}; +use sha2::Sha256; +use secp256k1::{SecretKey, PublicKey, Secp256k1, All}; +use tiny_hderive::bip32::ExtendedPrivKey; +use bip39::{Mnemonic, Language, Seed}; + +pub const BIP44_PATH: &str = "m/44'/133'/0'/0/0"; + +pub async fn get_taddr_balance(client: &mut CompactTxStreamerClient, address: &str) -> anyhow::Result { + let req = AddressList { + addresses: vec![address.to_string()], + }; + let rep = client.get_taddress_balance(Request::new(req)).await?.into_inner(); + Ok(rep.value_zat as u64) +} + +pub async fn shield_taddr(db: &DbAdapter, account: u32, prover: &LocalTxProver, ld_url: &str) -> anyhow::Result { + let mut client = connect_lightwalletd(ld_url).await?; + let last_height = get_latest_height(&mut client).await?; + let ivk = db.get_ivk(account)?; + let fvk = decode_extended_full_viewing_key(NETWORK.hrp_sapling_extended_full_viewing_key(), &ivk)?.unwrap(); + let z_address = db.get_address(account)?; + let pa = decode_payment_address(NETWORK.hrp_sapling_payment_address(), &z_address)?.unwrap(); + let t_address = db.get_taddr(account)?; + if t_address.is_none() { anyhow::bail!("No transparent address"); } + let t_address = t_address.unwrap(); + let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height)); + let amount = Amount::from_u64(get_taddr_balance(&mut client, &t_address).await?).unwrap(); + if amount <= DEFAULT_FEE { anyhow::bail!("Not enough balance"); } + let amount = amount - DEFAULT_FEE; + + let sk = db.get_tsk(account)?; + let seckey = + secp256k1::SecretKey::from_str(&sk).context("Cannot parse secret key")?; + + 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(); + + for utxo in utxo_rep.address_utxos.iter() { + let mut tx_hash = [0u8; 32]; + tx_hash.copy_from_slice(&utxo.txid); + let op = OutPoint::new(tx_hash, utxo.index as u32); + let script = Script(utxo.script.clone()); + let txout = TxOut { + value: Amount::from_i64(utxo.value_zat).unwrap(), + script_pubkey: script, + }; + builder.add_transparent_input(seckey, op, txout)?; + } + + let ovk = fvk.fvk.ovk; + builder.add_sapling_output(Some(ovk), pa, amount, None)?; + let consensus_branch_id = + BranchId::for_height(&NETWORK, BlockHeight::from_u32(last_height)); + let (tx, _) = builder.build(consensus_branch_id, prover)?; + let mut raw_tx: Vec = vec![]; + tx.write(&mut raw_tx)?; + + let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?; + log::info!("Tx ID = {}", tx_id); + + Ok(tx_id) +} + +pub fn derive_tkeys(phrase: &str, path: &str) -> anyhow::Result<(String, String)> { + 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 secret_key = SecretKey::from_slice(&ext.secret()).unwrap(); + let pub_key = PublicKey::from_secret_key(&secp, &secret_key); + let pub_key = pub_key.serialize(); + let pub_key = Ripemd160::digest(&Sha256::digest(&pub_key)); + let address = TransparentAddress::PublicKey(pub_key.into()); + let address = encode_transparent_address(&NETWORK.b58_pubkey_address_prefix(), &NETWORK.b58_script_address_prefix(), &address); + let sk = secret_key.to_string(); + Ok((sk, address)) +} + +#[cfg(test)] +mod tests { + use crate::{DbAdapter, LWD_URL}; + use crate::db::DEFAULT_DB_PATH; + use crate::taddr::{shield_taddr, derive_tkeys}; + use zcash_proofs::prover::LocalTxProver; + + #[tokio::test] + async fn test_shield_addr() { + let prover = LocalTxProver::with_default_location().unwrap(); + let db = DbAdapter::new(DEFAULT_DB_PATH).unwrap(); + let txid = shield_taddr(&db, 1, &prover, LWD_URL).await.unwrap(); + println!("{}", txid); + } + + #[test] + fn test_derive() { + let seed = dotenv::var("SEED").unwrap(); + for i in 0..10 { + let (_sk, addr) = derive_tkeys(&seed, &format!("m/44'/133'/0'/0/{}", i)).unwrap(); + println!("{}", addr); + } + } +} diff --git a/src/wallet.rs b/src/wallet.rs index 6c3edc5..d3fcc3e 100644 --- a/src/wallet.rs +++ b/src/wallet.rs @@ -11,7 +11,6 @@ use std::sync::{mpsc, Arc}; use tokio::sync::Mutex; use tonic::Request; use zcash_client_backend::address::RecipientAddress; -use zcash_client_backend::data_api::wallet::ANCHOR_OFFSET; use zcash_client_backend::encoding::{decode_extended_spending_key, decode_extended_full_viewing_key, encode_payment_address}; use zcash_params::{OUTPUT_PARAMS, SPEND_PARAMS}; use zcash_primitives::consensus::{BlockHeight, BranchId, Parameters}; @@ -20,6 +19,7 @@ use zcash_primitives::transaction::components::amount::{DEFAULT_FEE, MAX_MONEY}; use zcash_primitives::transaction::components::Amount; use zcash_primitives::zip32::ExtendedFullViewingKey; use zcash_proofs::prover::LocalTxProver; +use crate::taddr::{get_taddr_balance, shield_taddr}; const DEFAULT_CHUNK_SIZE: u32 = 100_000; @@ -27,6 +27,7 @@ pub struct Wallet { pub db_path: String, db: DbAdapter, prover: LocalTxProver, + pub ld_url: String, } #[repr(C)] @@ -47,7 +48,7 @@ impl Default for WalletBalance { } impl Wallet { - pub fn new(db_path: &str) -> Wallet { + pub fn new(db_path: &str, ld_url: &str) -> Wallet { let prover = LocalTxProver::from_bytes(SPEND_PARAMS, OUTPUT_PARAMS); let db = DbAdapter::new(db_path).unwrap(); db.init_db().unwrap(); @@ -55,6 +56,7 @@ impl Wallet { db_path: db_path.to_string(), db, prover, + ld_url: ld_url.to_string(), } } @@ -95,6 +97,7 @@ impl Wallet { let account = self .db .store_account(name, seed.as_deref(), sk.as_deref(), &ivk, &pa)?; + self.db.create_taddr(account)?; Ok(account) } @@ -103,12 +106,13 @@ impl Wallet { chunk_size: u32, target_height_offset: u32, progress_callback: ProgressCallback, + ld_url: &str ) -> anyhow::Result<()> { - crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback).await + crate::scan::sync_async(chunk_size, db_path, target_height_offset, progress_callback, ld_url).await } - pub async fn get_latest_height() -> anyhow::Result { - let mut client = connect_lightwalletd().await?; + pub async fn get_latest_height(&self) -> anyhow::Result { + let mut client = connect_lightwalletd(&self.ld_url).await?; let last_height = get_latest_height(&mut client).await?; Ok(last_height) } @@ -117,10 +121,11 @@ impl Wallet { pub async fn sync_ex( db_path: &str, progress_callback: impl Fn(u32) + Send + 'static, + ld_url: &str ) -> anyhow::Result<()> { let cb = Arc::new(Mutex::new(progress_callback)); - Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone()).await?; - Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone()).await?; + Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 10, cb.clone(), ld_url).await?; + Self::scan_async(db_path, DEFAULT_CHUNK_SIZE, 0, cb.clone(), ld_url).await?; Ok(()) } @@ -128,11 +133,11 @@ impl Wallet { &self, progress_callback: impl Fn(u32) + Send + 'static, ) -> anyhow::Result<()> { - Self::sync_ex(&self.db_path, progress_callback).await + Self::sync_ex(&self.db_path, progress_callback, &self.ld_url).await } pub async fn skip_to_last_height(&self) -> anyhow::Result<()> { - let mut client = connect_lightwalletd().await?; + let mut client = connect_lightwalletd(&self.ld_url).await?; let last_height = get_latest_height(&mut client).await?; let block_id = BlockId { height: last_height as u64, @@ -160,6 +165,7 @@ impl Wallet { to_address: &str, amount: u64, max_amount_per_note: u64, + anchor_offset: u32, progress_callback: impl Fn(Progress) + Send + 'static, ) -> anyhow::Result { let secret_key = self.db.get_sk(account)?; @@ -172,13 +178,13 @@ impl Wallet { let extfvk = ExtendedFullViewingKey::from(&skey); let (_, change_address) = extfvk.default_address().unwrap(); let ovk = extfvk.fvk.ovk; - let last_height = Self::get_latest_height().await?; + let last_height = self.get_latest_height().await?; let mut builder = Builder::new(NETWORK, BlockHeight::from_u32(last_height)); let anchor_height = self .db .get_last_sync_height()? .ok_or_else(|| anyhow::anyhow!("No spendable notes"))?; - let anchor_height = anchor_height.min(last_height - ANCHOR_OFFSET); + let anchor_height = anchor_height.min(last_height - anchor_offset); log::info!("Anchor = {}", anchor_height); let mut notes = self .db @@ -252,7 +258,7 @@ impl Wallet { let mut raw_tx: Vec = vec![]; tx.write(&mut raw_tx)?; - let mut client = connect_lightwalletd().await?; + let mut client = connect_lightwalletd(&self.ld_url).await?; let tx_id = send_transaction(&mut client, &raw_tx, last_height).await?; log::info!("Tx ID = {}", tx_id); @@ -276,6 +282,25 @@ impl Wallet { let pa = encode_payment_address(NETWORK.hrp_sapling_payment_address(), &pa); Ok(pa) } + + pub async fn get_taddr_balance(&self, account: u32) -> anyhow::Result { + let mut client = connect_lightwalletd(&self.ld_url).await?; + let address = self.db.get_taddr(account)?; + let balance = match address { + None => 0u64, + Some(address) => get_taddr_balance(&mut client, &address).await?, + }; + Ok(balance) + } + + pub async fn shield_taddr(&self, account: u32) -> anyhow::Result { + shield_taddr(&self.db, account, &self.prover, &self.ld_url).await + } + + pub fn set_lwd_url(&mut self, ld_url: &str) -> anyhow::Result<()> { + self.ld_url = ld_url.to_string(); + Ok(()) + } } #[cfg(test)] @@ -283,6 +308,7 @@ mod tests { use crate::key::derive_secret_key; use crate::wallet::Wallet; use bip39::{Language, Mnemonic}; + use crate::LWD_URL; #[tokio::test] async fn test_wallet_seed() { @@ -290,7 +316,7 @@ mod tests { env_logger::init(); let seed = dotenv::var("SEED").unwrap(); - let wallet = Wallet::new("zec.db"); + let wallet = Wallet::new("zec.db", LWD_URL); wallet.new_account_with_key("test", &seed).unwrap(); } @@ -311,7 +337,7 @@ mod tests { #[test] pub fn test_diversified_address() { - let wallet = Wallet::new("zec.db"); + let wallet = Wallet::new("zec.db", LWD_URL); let address = wallet.new_diversified_address(1).unwrap(); println!("{}", address); }