newline cleanup

This commit is contained in:
Cryptoforge 2020-08-05 21:55:33 -07:00
parent 6aa383daa3
commit e000ff3bc9
6 changed files with 182 additions and 182 deletions

View File

@ -1,14 +1,14 @@
## Zecwallet CLI - A command line ZecWallet light client. ## Zecwallet CLI - A command line ZecWallet light client.
`zecwallet-cli` is a command line ZecWallet light client. To use it, download the latest binary from the releases page and run `./zecwallet-cli` `zecwallet-cli` is a command line ZecWallet light client. To use it, download the latest binary from the releases page and run `./zecwallet-cli`
This will launch the interactive prompt. Type `help` to get a list of commands This will launch the interactive prompt. Type `help` to get a list of commands
## Running in non-interactive mode: ## Running in non-interactive mode:
You can also run `zecwallet-cli` in non-interactive mode by passing the command you want to run as an argument. For example, `zecwallet-cli addresses` will list all wallet addresses and exit. You can also run `zecwallet-cli` in non-interactive mode by passing the command you want to run as an argument. For example, `zecwallet-cli addresses` will list all wallet addresses and exit.
Run `zecwallet-cli help` to see a list of all commands. Run `zecwallet-cli help` to see a list of all commands.
## Privacy ## Privacy
* While all the keys and transaction detection happens on the client, the server can learn what blocks contain your shielded transactions. * While all the keys and transaction detection happens on the client, the server can learn what blocks contain your shielded transactions.
* The server also learns other metadata about you like your ip address etc... * The server also learns other metadata about you like your ip address etc...
* Also remember that t-addresses don't provide any privacy protection. * Also remember that t-addresses don't provide any privacy protection.
@ -42,9 +42,9 @@ cargo build --release
``` ```
## Options ## Options
Here are some CLI arguments you can pass to `zecwallet-cli`. Please run `zecwallet-cli --help` for the full list. Here are some CLI arguments you can pass to `zecwallet-cli`. Please run `zecwallet-cli --help` for the full list.
* `--server`: Connect to a custom zecwallet lightwalletd server. * `--server`: Connect to a custom zecwallet lightwalletd server.
* Example: `./zecwallet-cli --server 127.0.0.1:9067` * Example: `./zecwallet-cli --server 127.0.0.1:9067`
* `--seed`: Restore a wallet from a seed phrase. Note that this will fail if there is an existing wallet. Delete (or move) any existing wallet to restore from the 24-word seed phrase * `--seed`: Restore a wallet from a seed phrase. Note that this will fail if there is an existing wallet. Delete (or move) any existing wallet to restore from the 24-word seed phrase
* Example: `./zecwallet-cli --seed "twenty four words seed phrase"` * Example: `./zecwallet-cli --seed "twenty four words seed phrase"`

View File

@ -79,7 +79,7 @@ impl Command for SyncStatusCommand {
false => object!{ "syncing" => "false" }, false => object!{ "syncing" => "false" },
true => object!{ "syncing" => "true", true => object!{ "syncing" => "true",
"synced_blocks" => status.synced_blocks, "synced_blocks" => status.synced_blocks,
"total_blocks" => status.total_blocks } "total_blocks" => status.total_blocks }
}.pretty(2) }.pretty(2)
} }
} }
@ -120,7 +120,7 @@ impl Command for ClearCommand {
h.push("clear"); h.push("clear");
h.push(""); h.push("");
h.push("This command will clear all notes, utxos and transactions from the wallet, setting up the wallet to be synced from scratch."); h.push("This command will clear all notes, utxos and transactions from the wallet, setting up the wallet to be synced from scratch.");
h.join("\n") h.join("\n")
} }
@ -130,9 +130,9 @@ impl Command for ClearCommand {
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
lightclient.clear_state(); lightclient.clear_state();
let result = object!{ "result" => "success" }; let result = object!{ "result" => "success" };
result.pretty(2) result.pretty(2)
} }
} }
@ -197,7 +197,7 @@ impl Command for InfoCommand {
"Get the lightwalletd server's info".to_string() "Get the lightwalletd server's info".to_string()
} }
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String { fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
lightclient.do_info() lightclient.do_info()
} }
} }
@ -436,7 +436,7 @@ impl Command for LockCommand {
let mut h = vec![]; let mut h = vec![];
h.push("Extra arguments to lock. Did you mean 'encrypt'?"); h.push("Extra arguments to lock. Did you mean 'encrypt'?");
h.push(""); h.push("");
return format!("{}\n{}", h.join("\n"), self.help()); return format!("{}\n{}", h.join("\n"), self.help());
} }
@ -584,9 +584,9 @@ impl Command for SaveCommand {
r.pretty(2) r.pretty(2)
}, },
Err(e) => { Err(e) => {
let r = object!{ let r = object!{
"result" => "error", "result" => "error",
"error" => e "error" => e
}; };
r.pretty(2) r.pretty(2)
} }
@ -715,7 +715,7 @@ impl Command for NotesCommand {
} }
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
// Parse the args. // Parse the args.
if args.len() > 1 { if args.len() > 1 {
return self.short_help(); return self.short_help();
} }
@ -843,6 +843,6 @@ pub mod tests {
#[test] #[test]
pub fn test_nosync_commands() { pub fn test_nosync_commands() {
// The following commands should run // The following commands should run
} }
} }

View File

@ -140,7 +140,7 @@ impl LightClientConfig {
} }
pub fn get_zcash_data_path(&self) -> Box<Path> { pub fn get_zcash_data_path(&self) -> Box<Path> {
let mut zcash_data_location; let mut zcash_data_location;
if self.data_dir.is_some() { if self.data_dir.is_some() {
zcash_data_location = PathBuf::from(&self.data_dir.as_ref().unwrap()); zcash_data_location = PathBuf::from(&self.data_dir.as_ref().unwrap());
} else { } else {
@ -178,7 +178,7 @@ impl LightClientConfig {
pub fn get_wallet_path(&self) -> Box<Path> { pub fn get_wallet_path(&self) -> Box<Path> {
let mut wallet_location = self.get_zcash_data_path().into_path_buf(); let mut wallet_location = self.get_zcash_data_path().into_path_buf();
wallet_location.push(WALLET_NAME); wallet_location.push(WALLET_NAME);
wallet_location.into_boxed_path() wallet_location.into_boxed_path()
} }
@ -296,7 +296,7 @@ pub struct LightClient {
} }
impl LightClient { impl LightClient {
pub fn set_wallet_initial_state(&self, height: u64) { pub fn set_wallet_initial_state(&self, height: u64) {
use std::convert::TryInto; use std::convert::TryInto;
@ -349,14 +349,14 @@ impl LightClient {
let mut l = LightClient { let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0)?)), wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0)?)),
config : config.clone(), config : config.clone(),
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
}; };
l.set_wallet_initial_state(0); l.set_wallet_initial_state(0);
#[cfg(feature = "embed_params")] #[cfg(feature = "embed_params")]
l.read_sapling_params(); l.read_sapling_params();
@ -366,11 +366,11 @@ impl LightClient {
Ok(l) Ok(l)
} }
/// Create a brand new wallet with a new seed phrase. Will fail if a wallet file /// Create a brand new wallet with a new seed phrase. Will fail if a wallet file
/// already exists on disk /// already exists on disk
pub fn new(config: &LightClientConfig, latest_block: u64) -> io::Result<Self> { pub fn new(config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
#[cfg(all(not(target_os="ios"), not(target_os="android")))] #[cfg(all(not(target_os="ios"), not(target_os="android")))]
{ {
if config.wallet_exists() { if config.wallet_exists() {
return Err(Error::new(ErrorKind::AlreadyExists, return Err(Error::new(ErrorKind::AlreadyExists,
"Cannot create a new wallet from seed, because a wallet already exists")); "Cannot create a new wallet from seed, because a wallet already exists"));
@ -380,14 +380,14 @@ impl LightClient {
let mut l = LightClient { let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(None, config, latest_block)?)), wallet : Arc::new(RwLock::new(LightWallet::new(None, config, latest_block)?)),
config : config.clone(), config : config.clone(),
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
}; };
l.set_wallet_initial_state(latest_block); l.set_wallet_initial_state(latest_block);
#[cfg(feature = "embed_params")] #[cfg(feature = "embed_params")]
l.read_sapling_params(); l.read_sapling_params();
@ -412,7 +412,7 @@ impl LightClient {
let mut l = LightClient { let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, birthday)?)), wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, birthday)?)),
config : config.clone(), config : config.clone(),
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -420,7 +420,7 @@ impl LightClient {
println!("Setting birthday to {}", birthday); println!("Setting birthday to {}", birthday);
l.set_wallet_initial_state(birthday); l.set_wallet_initial_state(birthday);
#[cfg(feature = "embed_params")] #[cfg(feature = "embed_params")]
l.read_sapling_params(); l.read_sapling_params();
@ -438,7 +438,7 @@ impl LightClient {
let mut lc = LightClient { let mut lc = LightClient {
wallet : Arc::new(RwLock::new(wallet)), wallet : Arc::new(RwLock::new(wallet)),
config : config.clone(), config : config.clone(),
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -460,12 +460,12 @@ impl LightClient {
} }
let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?); let mut file_buffer = BufReader::new(File::open(config.get_wallet_path())?);
let wallet = LightWallet::read(&mut file_buffer, config)?; let wallet = LightWallet::read(&mut file_buffer, config)?;
let mut lc = LightClient { let mut lc = LightClient {
wallet : Arc::new(RwLock::new(wallet)), wallet : Arc::new(RwLock::new(wallet)),
config : config.clone(), config : config.clone(),
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -547,16 +547,16 @@ impl LightClient {
Ok(s) => s, Ok(s) => s,
Err(_) => return Err("Decryption failed. Is your password correct?".to_string()) Err(_) => return Err("Decryption failed. Is your password correct?".to_string())
}; };
Mnemonic::from_entropy(&seed, Language::English) Mnemonic::from_entropy(&seed, Language::English)
} else { } else {
// Seed // Seed
let mut seed_bytes = [0u8; 32]; let mut seed_bytes = [0u8; 32];
reader.read_exact(&mut seed_bytes).unwrap(); reader.read_exact(&mut seed_bytes).unwrap();
Mnemonic::from_entropy(&seed_bytes, Language::English) Mnemonic::from_entropy(&seed_bytes, Language::English)
}.map_err(|e| format!("Failed to read seed. {:?}", e)); }.map_err(|e| format!("Failed to read seed. {:?}", e));
phrase.map(|m| m.phrase().to_string()) phrase.map(|m| m.phrase().to_string())
} }
@ -640,7 +640,7 @@ impl LightClient {
let t_addresses = wallet.taddresses.read().unwrap().iter().map( |address| { let t_addresses = wallet.taddresses.read().unwrap().iter().map( |address| {
// Get the balance for this address // Get the balance for this address
let balance = wallet.tbalance(Some(address.clone())); let balance = wallet.tbalance(Some(address.clone()));
object!{ object!{
"address" => address.clone(), "address" => address.clone(),
"balance" => balance, "balance" => balance,
@ -656,9 +656,9 @@ impl LightClient {
} }
} }
pub fn do_save(&self) -> Result<(), String> { pub fn do_save(&self) -> Result<(), String> {
// On mobile platforms, disable the save, because the saves will be handled by the native layer, and not in rust // On mobile platforms, disable the save, because the saves will be handled by the native layer, and not in rust
if cfg!(all(not(target_os="ios"), not(target_os="android"))) { if cfg!(all(not(target_os="ios"), not(target_os="android"))) {
// If the wallet is encrypted but unlocked, lock it again. // If the wallet is encrypted but unlocked, lock it again.
{ {
let mut wallet = self.wallet.write().unwrap(); let mut wallet = self.wallet.write().unwrap();
@ -672,7 +672,7 @@ impl LightClient {
} }
} }
} }
} }
{ {
// Prevent any overlapping syncs during save, and don't save in the middle of a sync // Prevent any overlapping syncs during save, and don't save in the middle of a sync
@ -686,7 +686,7 @@ impl LightClient {
let mut file = File::create(self.config.get_wallet_path()).unwrap(); let mut file = File::create(self.config.get_wallet_path()).unwrap();
file.write_all(&wallet_bytes).map_err(|e| format!("{}", e))?; file.write_all(&wallet_bytes).map_err(|e| format!("{}", e))?;
Ok(()) Ok(())
}, },
Err(e) => { Err(e) => {
let err = format!("ERR: {}", e); let err = format!("ERR: {}", e);
error!("{}", err); error!("{}", err);
@ -715,7 +715,7 @@ impl LightClient {
} }
} }
} }
} }
let mut buffer: Vec<u8> = vec![]; let mut buffer: Vec<u8> = vec![];
match self.wallet.write().unwrap().write(&mut buffer) { match self.wallet.write().unwrap().write(&mut buffer) {
@ -774,7 +774,7 @@ impl LightClient {
let wallet = self.wallet.read().unwrap(); let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter() wallet.txs.read().unwrap().iter()
.flat_map( |(txid, wtx)| { .flat_map( |(txid, wtx)| {
wtx.notes.iter().filter_map(move |nd| wtx.notes.iter().filter_map(move |nd|
if !all_notes && nd.spent.is_some() { if !all_notes && nd.spent.is_some() {
None None
} else { } else {
@ -801,16 +801,16 @@ impl LightClient {
} }
}); });
} }
let mut unspent_utxos: Vec<JsonValue> = vec![]; let mut unspent_utxos: Vec<JsonValue> = vec![];
let mut spent_utxos : Vec<JsonValue> = vec![]; let mut spent_utxos : Vec<JsonValue> = vec![];
let mut pending_utxos: Vec<JsonValue> = vec![]; let mut pending_utxos: Vec<JsonValue> = vec![];
{ {
let wallet = self.wallet.read().unwrap(); let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter() wallet.txs.read().unwrap().iter()
.flat_map( |(txid, wtx)| { .flat_map( |(txid, wtx)| {
wtx.utxos.iter().filter_map(move |utxo| wtx.utxos.iter().filter_map(move |utxo|
if !all_notes && utxo.spent.is_some() { if !all_notes && utxo.spent.is_some() {
None None
} else { } else {
@ -945,12 +945,12 @@ impl LightClient {
// Collect outgoing metadata // Collect outgoing metadata
let outgoing_json = wtx.outgoing_metadata.iter() let outgoing_json = wtx.outgoing_metadata.iter()
.map(|om| .map(|om|
object!{ object!{
"address" => om.address.clone(), "address" => om.address.clone(),
"value" => om.value, "value" => om.value,
"memo" => LightWallet::memo_str(&Some(om.memo.clone())), "memo" => LightWallet::memo_str(&Some(om.memo.clone())),
}).collect::<Vec<JsonValue>>(); }).collect::<Vec<JsonValue>>();
object! { object! {
"block_height" => wtx.block, "block_height" => wtx.block,
@ -1004,16 +1004,16 @@ impl LightClient {
// Then set the initial block // Then set the initial block
self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday()); self.set_wallet_initial_state(self.wallet.read().unwrap().get_birthday());
info!("Cleared wallet state"); info!("Cleared wallet state");
} }
pub fn do_rescan(&self) -> Result<JsonValue, String> { pub fn do_rescan(&self) -> Result<JsonValue, String> {
if !self.wallet.read().unwrap().is_unlocked_for_spending() { if !self.wallet.read().unwrap().is_unlocked_for_spending() {
warn!("Wallet is locked, new HD addresses won't be added!"); warn!("Wallet is locked, new HD addresses won't be added!");
} }
info!("Rescan starting"); info!("Rescan starting");
self.clear_state(); self.clear_state();
// Then, do a sync, which will force a full rescan from the initial state // Then, do a sync, which will force a full rescan from the initial state
@ -1056,13 +1056,13 @@ impl LightClient {
// Sync is 3 parts // Sync is 3 parts
// 1. Get the latest block // 1. Get the latest block
// 2. Get all the blocks that we don't have // 2. Get all the blocks that we don't have
// 3. Find all new Txns that don't have the full Tx, and get them as full transactions // 3. Find all new Txns that don't have the full Tx, and get them as full transactions
// and scan them, mainly to get the memos // and scan them, mainly to get the memos
let mut last_scanned_height = self.wallet.read().unwrap().last_scanned_height() as u64; let mut last_scanned_height = self.wallet.read().unwrap().last_scanned_height() as u64;
// This will hold the latest block fetched from the RPC // This will hold the latest block fetched from the RPC
let latest_block = fetch_latest_block(&self.get_server_uri())?.height; let latest_block = fetch_latest_block(&self.get_server_uri())?.height;
if latest_block < last_scanned_height { if latest_block < last_scanned_height {
let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height); let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height);
warn!("{}", w); warn!("{}", w);
@ -1145,7 +1145,7 @@ impl LightClient {
return; return;
} }
// Parse the block and save it's time. We'll use this timestamp for // Parse the block and save it's time. We'll use this timestamp for
// transactions in this block that might belong to us. // transactions in this block that might belong to us.
let block: Result<zcash_client_backend::proto::compact_formats::CompactBlock, _> let block: Result<zcash_client_backend::proto::compact_formats::CompactBlock, _>
= parse_from_bytes(encoded_block); = parse_from_bytes(encoded_block);
@ -1172,13 +1172,13 @@ impl LightClient {
{ {
// println!("Total scan duration: {:?}", self.wallet.read().unwrap().total_scan_duration.read().unwrap().get(0).unwrap().as_millis()); // println!("Total scan duration: {:?}", self.wallet.read().unwrap().total_scan_duration.read().unwrap().get(0).unwrap().as_millis());
let t = self.wallet.read().unwrap(); let t = self.wallet.read().unwrap();
let mut d = t.total_scan_duration.write().unwrap(); let mut d = t.total_scan_duration.write().unwrap();
d.clear(); d.clear();
d.push(std::time::Duration::new(0, 0)); d.push(std::time::Duration::new(0, 0));
} }
// Check if there was any invalid block, which means we might have to do a reorg // Check if there was any invalid block, which means we might have to do a reorg
let invalid_height = last_invalid_height.load(Ordering::SeqCst); let invalid_height = last_invalid_height.load(Ordering::SeqCst);
@ -1192,8 +1192,8 @@ impl LightClient {
if total_reorg > (crate::lightwallet::MAX_REORG - 1) as u64 { if total_reorg > (crate::lightwallet::MAX_REORG - 1) as u64 {
error!("Reorg has now exceeded {} blocks!", crate::lightwallet::MAX_REORG); error!("Reorg has now exceeded {} blocks!", crate::lightwallet::MAX_REORG);
return Err(format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG)); return Err(format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG));
} }
if invalid_height > 0 { if invalid_height > 0 {
// Reset the scanning heights // Reset the scanning heights
last_scanned_height = (invalid_height - 1) as u64; last_scanned_height = (invalid_height - 1) as u64;
@ -1204,17 +1204,17 @@ impl LightClient {
continue; continue;
} }
// If it got here, that means the blocks are scanning properly now. // If it got here, that means the blocks are scanning properly now.
// So, reset the total_reorg // So, reset the total_reorg
total_reorg = 0; total_reorg = 0;
// We'll also fetch all the txids that our transparent addresses are involved with // We'll also fetch all the txids that our transparent addresses are involved with
{ {
// Copy over addresses so as to not lock up the wallet, which we'll use inside the callback below. // Copy over addresses so as to not lock up the wallet, which we'll use inside the callback below.
let addresses = self.wallet.read().unwrap() let addresses = self.wallet.read().unwrap()
.taddresses.read().unwrap().iter().map(|a| a.clone()) .taddresses.read().unwrap().iter().map(|a| a.clone())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
// Create a channel so the fetch_transparent_txids can send the results back // Create a channel so the fetch_transparent_txids can send the results back
let (ctx, crx) = channel(); let (ctx, crx) = channel();
let num_addresses = addresses.len(); let num_addresses = addresses.len();
@ -1236,7 +1236,7 @@ impl LightClient {
let ctx = ctx.clone(); let ctx = ctx.clone();
pool.execute(move || { pool.execute(move || {
// Fetch the transparent transactions for this address, and send the results // Fetch the transparent transactions for this address, and send the results
// via the channel // via the channel
let r = fetch_transparent_txids(&server_uri, address, transparent_start_height, end_height, let r = fetch_transparent_txids(&server_uri, address, transparent_start_height, end_height,
move |tx_bytes: &[u8], height: u64| { move |tx_bytes: &[u8], height: u64| {
@ -1244,17 +1244,17 @@ impl LightClient {
// Scan this Tx for transparent inputs and outputs // Scan this Tx for transparent inputs and outputs
let datetime = block_times_inner.read().unwrap().get(&height).map(|v| *v).unwrap_or(0); let datetime = block_times_inner.read().unwrap().get(&height).map(|v| *v).unwrap_or(0);
wallet.read().unwrap().scan_full_tx(&tx, height as i32, datetime as u64); wallet.read().unwrap().scan_full_tx(&tx, height as i32, datetime as u64);
}); });
ctx.send(r).unwrap(); ctx.send(r).unwrap();
}); });
} }
// Collect all results from the transparent fetches, and make sure everything was OK. // Collect all results from the transparent fetches, and make sure everything was OK.
// If it was not, we return an error, which will go back to the retry // If it was not, we return an error, which will go back to the retry
crx.iter().take(num_addresses).collect::<Result<Vec<()>, String>>()?; crx.iter().take(num_addresses).collect::<Result<Vec<()>, String>>()?;
} }
// Do block height accounting // Do block height accounting
last_scanned_height = end_height; last_scanned_height = end_height;
end_height = last_scanned_height + 1000; end_height = last_scanned_height + 1000;
@ -1269,7 +1269,7 @@ impl LightClient {
if print_updates{ if print_updates{
println!(""); // New line to finish up the updates println!(""); // New line to finish up the updates
} }
info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024); info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024);
{ {
let mut status = self.sync_status.write().unwrap(); let mut status = self.sync_status.write().unwrap();
@ -1292,13 +1292,13 @@ impl LightClient {
txids_to_fetch.sort(); txids_to_fetch.sort();
txids_to_fetch.dedup(); txids_to_fetch.dedup();
let mut rng = OsRng; let mut rng = OsRng;
txids_to_fetch.shuffle(&mut rng); txids_to_fetch.shuffle(&mut rng);
let num_fetches = txids_to_fetch.len(); let num_fetches = txids_to_fetch.len();
let (ctx, crx) = channel(); let (ctx, crx) = channel();
// And go and fetch the txids, getting the full transaction, so we can // And go and fetch the txids, getting the full transaction, so we can
// read the memos // read the memos
for (txid, height) in txids_to_fetch { for (txid, height) in txids_to_fetch {
let light_wallet_clone = self.wallet.clone(); let light_wallet_clone = self.wallet.clone();
@ -1306,19 +1306,19 @@ impl LightClient {
let pool = pool.clone(); let pool = pool.clone();
let server_uri = self.get_server_uri(); let server_uri = self.get_server_uri();
let ctx = ctx.clone(); let ctx = ctx.clone();
pool.execute(move || { pool.execute(move || {
info!("Fetching full Tx: {}", txid); info!("Fetching full Tx: {}", txid);
match fetch_full_tx(&server_uri, txid) { match fetch_full_tx(&server_uri, txid) {
Ok(tx_bytes) => { Ok(tx_bytes) => {
let tx = Transaction::read(&tx_bytes[..]).unwrap(); let tx = Transaction::read(&tx_bytes[..]).unwrap();
light_wallet_clone.read().unwrap().scan_full_tx(&tx, height, 0); light_wallet_clone.read().unwrap().scan_full_tx(&tx, height, 0);
ctx.send(Ok(())).unwrap(); ctx.send(Ok(())).unwrap();
}, },
Err(e) => ctx.send(Err(e)).unwrap() Err(e) => ctx.send(Err(e)).unwrap()
}; };
}); });
}; };
@ -1331,7 +1331,7 @@ impl LightClient {
"downloaded_bytes" => bytes_downloaded.load(Ordering::SeqCst) "downloaded_bytes" => bytes_downloaded.load(Ordering::SeqCst)
}), }),
Err(e) => Err(format!("Error fetching all txns for memos: {}", e)) Err(e) => Err(format!("Error fetching all txns for memos: {}", e))
} }
} }
pub fn do_send(&self, from: &str, addrs: Vec<(&str, u64, Option<String>)>, fee: &u64) -> Result<String, String> { pub fn do_send(&self, from: &str, addrs: Vec<(&str, u64, Option<String>)>, fee: &u64) -> Result<String, String> {
@ -1350,7 +1350,7 @@ impl LightClient {
info!("Transaction Complete"); info!("Transaction Complete");
match rawtx { match rawtx {
Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes), Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes),
Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e)) Err(e) => Err(format!("Error: No Tx to broadcast. Error was: {}", e))
@ -1393,27 +1393,27 @@ pub mod tests {
// This will lock the wallet again, so after this, we'll need to unlock again // This will lock the wallet again, so after this, we'll need to unlock again
assert!(!lc.do_new_address("t").is_err()); assert!(!lc.do_new_address("t").is_err());
lc.wallet.write().unwrap().unlock("password".to_string()).unwrap(); lc.wallet.write().unwrap().unlock("password".to_string()).unwrap();
assert!(!lc.do_new_address("z").is_err()); assert!(!lc.do_new_address("z").is_err());
} }
#[test] #[test]
pub fn test_addresses() { pub fn test_addresses() {
let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap();
{ {
let addresses = lc.do_address(); let addresses = lc.do_address();
// When restoring from seed, there should be 5+1 addresses // When restoring from seed, there should be 5+1 addresses
assert_eq!(addresses["z_addresses"].len(), 6); assert_eq!(addresses["z_addresses"].len(), 6);
assert_eq!(addresses["t_addresses"].len(), 6); assert_eq!(addresses["t_addresses"].len(), 6);
} }
// Add new z and t addresses // Add new z and t addresses
let taddr1 = lc.do_new_address("t").unwrap()[0].as_str().unwrap().to_string(); let taddr1 = lc.do_new_address("t").unwrap()[0].as_str().unwrap().to_string();
let taddr2 = lc.do_new_address("t").unwrap()[0].as_str().unwrap().to_string(); let taddr2 = lc.do_new_address("t").unwrap()[0].as_str().unwrap().to_string();
let zaddr1 = lc.do_new_address("z").unwrap()[0].as_str().unwrap().to_string(); let zaddr1 = lc.do_new_address("z").unwrap()[0].as_str().unwrap().to_string();
let zaddr2 = lc.do_new_address("z").unwrap()[0].as_str().unwrap().to_string(); let zaddr2 = lc.do_new_address("z").unwrap()[0].as_str().unwrap().to_string();
let addresses = lc.do_address(); let addresses = lc.do_address();
assert_eq!(addresses["z_addresses"].len(), 8); assert_eq!(addresses["z_addresses"].len(), 8);
assert_eq!(addresses["z_addresses"][6], zaddr1); assert_eq!(addresses["z_addresses"][6], zaddr1);
@ -1431,7 +1431,7 @@ pub mod tests {
let lc = LightClient { let lc = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(None, &config, 0).unwrap())), wallet : Arc::new(RwLock::new(LightWallet::new(None, &config, 0).unwrap())),
config : config, config : config,
sapling_output : vec![], sapling_output : vec![],
sapling_spend : vec![], sapling_spend : vec![],
sync_lock : Mutex::new(()), sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())), sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -1523,8 +1523,8 @@ pub mod tests {
use crate::SaplingParams; use crate::SaplingParams;
assert!(lc.set_sapling_params( assert!(lc.set_sapling_params(
SaplingParams::get("sapling-output.params").unwrap().as_ref(), SaplingParams::get("sapling-output.params").unwrap().as_ref(),
SaplingParams::get("sapling-spend.params").unwrap().as_ref()).is_ok()); SaplingParams::get("sapling-spend.params").unwrap().as_ref()).is_ok());
} }
} }

View File

@ -99,16 +99,16 @@ impl ToBase58Check for [u8] {
} }
pub struct LightWallet { pub struct LightWallet {
// Is the wallet encrypted? If it is, then when writing to disk, the seed is always encrypted // Is the wallet encrypted? If it is, then when writing to disk, the seed is always encrypted
// and the individual spending keys are not written // and the individual spending keys are not written
encrypted: bool, encrypted: bool,
// In memory only (i.e, this field is not written to disk). Is the wallet unlocked and are // In memory only (i.e, this field is not written to disk). Is the wallet unlocked and are
// the spending keys present to allow spending from this wallet? // the spending keys present to allow spending from this wallet?
unlocked: bool, unlocked: bool,
enc_seed: [u8; 48], // If locked, this contains the encrypted seed enc_seed: [u8; 48], // If locked, this contains the encrypted seed
nonce: Vec<u8>, // Nonce used to encrypt the wallet. nonce: Vec<u8>, // Nonce used to encrypt the wallet.
seed: [u8; 32], // Seed phrase for this wallet. If wallet is locked, this is 0 seed: [u8; 32], // Seed phrase for this wallet. If wallet is locked, this is 0
@ -118,17 +118,17 @@ pub struct LightWallet {
extfvks: Arc<RwLock<Vec<ExtendedFullViewingKey>>>, extfvks: Arc<RwLock<Vec<ExtendedFullViewingKey>>>,
pub zaddress: Arc<RwLock<Vec<PaymentAddress<Bls12>>>>, pub zaddress: Arc<RwLock<Vec<PaymentAddress<Bls12>>>>,
// Transparent keys. If the wallet is locked, then the secret keys will be encrypted, // Transparent keys. If the wallet is locked, then the secret keys will be encrypted,
// but the addresses will be present. // but the addresses will be present.
tkeys: Arc<RwLock<Vec<secp256k1::SecretKey>>>, tkeys: Arc<RwLock<Vec<secp256k1::SecretKey>>>,
pub taddresses: Arc<RwLock<Vec<String>>>, pub taddresses: Arc<RwLock<Vec<String>>>,
blocks: Arc<RwLock<Vec<BlockData>>>, blocks: Arc<RwLock<Vec<BlockData>>>,
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>, pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// Transactions that are only in the mempool, but haven't been confirmed yet. // Transactions that are only in the mempool, but haven't been confirmed yet.
// This is not stored to disk. // This is not stored to disk.
pub mempool_txs: Arc<RwLock<HashMap<TxId, WalletTx>>>, pub mempool_txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// The block at which this wallet was born. Rescans // The block at which this wallet was born. Rescans
@ -163,7 +163,7 @@ impl LightWallet {
fn get_zaddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) -> fn get_zaddr_from_bip39seed(config: &LightClientConfig, bip39_seed: &[u8], pos: u32) ->
(ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) { (ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) {
assert_eq!(bip39_seed.len(), 64); assert_eq!(bip39_seed.len(), 64);
let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(bip39_seed), &ExtendedSpendingKey::master(bip39_seed),
&[ &[
@ -180,12 +180,12 @@ impl LightWallet {
pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool { pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool {
match address::RecipientAddress::from_str(addr, match address::RecipientAddress::from_str(addr,
config.hrp_sapling_address(), config.hrp_sapling_address(),
config.base58_pubkey_address(), config.base58_pubkey_address(),
config.base58_script_address()) { config.base58_script_address()) {
Some(address::RecipientAddress::Shielded(_)) => true, Some(address::RecipientAddress::Shielded(_)) => true,
_ => false, _ => false,
} }
} }
pub fn new(seed_phrase: Option<String>, config: &LightClientConfig, latest_block: u64) -> io::Result<Self> { pub fn new(seed_phrase: Option<String>, config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
@ -193,7 +193,7 @@ impl LightWallet {
let mut seed_bytes = [0u8; 32]; let mut seed_bytes = [0u8; 32];
if seed_phrase.is_none() { if seed_phrase.is_none() {
// Create a random seed. // Create a random seed.
let mut system_rng = OsRng; let mut system_rng = OsRng;
system_rng.fill(&mut seed_bytes); system_rng.fill(&mut seed_bytes);
} else { } else {
@ -205,11 +205,11 @@ impl LightWallet {
return Err(io::Error::new(ErrorKind::InvalidData, e)); return Err(io::Error::new(ErrorKind::InvalidData, e));
} }
}; };
seed_bytes.copy_from_slice(&phrase.entropy()); seed_bytes.copy_from_slice(&phrase.entropy());
} }
// The seed bytes is the raw entropy. To pass it to HD wallet generation, // The seed bytes is the raw entropy. To pass it to HD wallet generation,
// we need to get the 64 byte bip39 entropy // we need to get the 64 byte bip39 entropy
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed_bytes, Language::English).unwrap(), ""); let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed_bytes, Language::English).unwrap(), "");
@ -217,7 +217,7 @@ impl LightWallet {
let tpk = LightWallet::get_taddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0); let tpk = LightWallet::get_taddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0);
let taddr = LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), &tpk); let taddr = LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), &tpk);
// TODO: We need to monitor addresses, and always keep 1 "free" address, so // TODO: We need to monitor addresses, and always keep 1 "free" address, so
// users can import a seed phrase and automatically get all used addresses // users can import a seed phrase and automatically get all used addresses
let (extsk, extfvk, address) let (extsk, extfvk, address)
= LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0); = LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0);
@ -262,7 +262,7 @@ impl LightWallet {
println!("Reading wallet version {}", version); println!("Reading wallet version {}", version);
info!("Reading wallet version {}", version); info!("Reading wallet version {}", version);
// At version 5, we're writing the rest of the file as a compressed stream (gzip) // At version 5, we're writing the rest of the file as a compressed stream (gzip)
let mut reader: Box<dyn Read> = if version != 5 { let mut reader: Box<dyn Read> = if version != 5 {
info!("Reading direct"); info!("Reading direct");
@ -278,12 +278,12 @@ impl LightWallet {
false false
}; };
info!("Wallet Encryption {:?}", encrypted); info!("Wallet Encryption {:?}", encrypted);
let mut enc_seed = [0u8; 48]; let mut enc_seed = [0u8; 48];
if version >= 4 { if version >= 4 {
reader.read_exact(&mut enc_seed)?; reader.read_exact(&mut enc_seed)?;
} }
let nonce = if version >= 4 { let nonce = if version >= 4 {
Vector::read(&mut reader, |r| r.read_u8())? Vector::read(&mut reader, |r| r.read_u8())?
} else { } else {
@ -293,10 +293,10 @@ impl LightWallet {
// Seed // Seed
let mut seed_bytes = [0u8; 32]; let mut seed_bytes = [0u8; 32];
reader.read_exact(&mut seed_bytes)?; reader.read_exact(&mut seed_bytes)?;
// Read the spending keys // Read the spending keys
let extsks = Vector::read(&mut reader, |r| ExtendedSpendingKey::read(r))?; let extsks = Vector::read(&mut reader, |r| ExtendedSpendingKey::read(r))?;
let extfvks = if version >= 4 { let extfvks = if version >= 4 {
// Read the viewing keys // Read the viewing keys
Vector::read(&mut reader, |r| ExtendedFullViewingKey::read(r))? Vector::read(&mut reader, |r| ExtendedFullViewingKey::read(r))?
@ -305,7 +305,7 @@ impl LightWallet {
extsks.iter().map(|sk| ExtendedFullViewingKey::from(sk)) extsks.iter().map(|sk| ExtendedFullViewingKey::from(sk))
.collect::<Vec<ExtendedFullViewingKey>>() .collect::<Vec<ExtendedFullViewingKey>>()
}; };
// Calculate the addresses // Calculate the addresses
let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 ) let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 )
.collect::<Vec<PaymentAddress<Bls12>>>(); .collect::<Vec<PaymentAddress<Bls12>>>();
@ -314,8 +314,8 @@ impl LightWallet {
let mut tpk_bytes = [0u8; 32]; let mut tpk_bytes = [0u8; 32];
r.read_exact(&mut tpk_bytes)?; r.read_exact(&mut tpk_bytes)?;
secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e)) secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
})?; })?;
let taddresses = if version >= 4 { let taddresses = if version >= 4 {
// Read the addresses // Read the addresses
Vector::read(&mut reader, |r| utils::read_string(r))? Vector::read(&mut reader, |r| utils::read_string(r))?
@ -323,9 +323,9 @@ impl LightWallet {
// Calculate the addresses // Calculate the addresses
tkeys.iter().map(|sk| LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), sk)).collect() tkeys.iter().map(|sk| LightWallet::address_from_prefix_sk(&config.base58_pubkey_address(), sk)).collect()
}; };
let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?; let blocks = Vector::read(&mut reader, |r| BlockData::read(r))?;
let txs_tuples = Vector::read(&mut reader, |r| { let txs_tuples = Vector::read(&mut reader, |r| {
let mut txid_bytes = [0u8; 32]; let mut txid_bytes = [0u8; 32];
r.read_exact(&mut txid_bytes)?; r.read_exact(&mut txid_bytes)?;
@ -345,7 +345,7 @@ impl LightWallet {
Ok(LightWallet{ Ok(LightWallet{
encrypted: encrypted, encrypted: encrypted,
unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked. unlocked: !encrypted, // When reading from disk, if wallet is encrypted, it starts off locked.
enc_seed: enc_seed, enc_seed: enc_seed,
nonce: nonce, nonce: nonce,
seed: seed_bytes, seed: seed_bytes,
@ -365,7 +365,7 @@ impl LightWallet {
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> { pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
if self.encrypted && self.unlocked { if self.encrypted && self.unlocked {
return Err(Error::new(ErrorKind::InvalidInput, return Err(Error::new(ErrorKind::InvalidInput,
format!("Cannot write while wallet is unlocked while encrypted."))); format!("Cannot write while wallet is unlocked while encrypted.")));
} }
@ -408,7 +408,7 @@ impl LightWallet {
)?; )?;
Vector::write(&mut writer, &self.blocks.read().unwrap(), |w, b| b.write(w))?; Vector::write(&mut writer, &self.blocks.read().unwrap(), |w, b| b.write(w))?;
// The hashmap, write as a set of tuples. Store them sorted so that wallets are // The hashmap, write as a set of tuples. Store them sorted so that wallets are
// deterministically saved // deterministically saved
{ {
@ -471,7 +471,7 @@ impl LightWallet {
/// Get all t-address private keys. Returns a Vector of (address, secretkey) /// Get all t-address private keys. Returns a Vector of (address, secretkey)
pub fn get_t_secret_keys(&self) -> Vec<(String, String)> { pub fn get_t_secret_keys(&self) -> Vec<(String, String)> {
self.tkeys.read().unwrap().iter().map(|sk| { self.tkeys.read().unwrap().iter().map(|sk| {
(self.address_from_sk(sk), (self.address_from_sk(sk),
sk[..].to_base58check(&self.config.base58_secretkey_prefix(), &[0x01])) sk[..].to_base58check(&self.config.base58_secretkey_prefix(), &[0x01]))
}).collect::<Vec<(String, String)>>() }).collect::<Vec<(String, String)>>()
} }
@ -508,7 +508,7 @@ impl LightWallet {
let pos = self.tkeys.read().unwrap().len() as u32; let pos = self.tkeys.read().unwrap().len() as u32;
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), ""); let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&self.seed, Language::English).unwrap(), "");
let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos); let sk = LightWallet::get_taddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos);
let address = self.address_from_sk(&sk); let address = self.address_from_sk(&sk);
@ -637,7 +637,7 @@ impl LightWallet {
pub fn address_from_sk(&self, sk: &secp256k1::SecretKey) -> String { pub fn address_from_sk(&self, sk: &secp256k1::SecretKey) -> String {
LightWallet::address_from_prefix_sk(&self.config.base58_pubkey_address(), sk) LightWallet::address_from_prefix_sk(&self.config.base58_pubkey_address(), sk)
} }
pub fn address_from_pubkeyhash(&self, ta: Option<TransparentAddress>) -> Option<String> { pub fn address_from_pubkeyhash(&self, ta: Option<TransparentAddress>) -> Option<String> {
match ta { match ta {
Some(TransparentAddress::PublicKey(hash)) => { Some(TransparentAddress::PublicKey(hash)) => {
@ -655,7 +655,7 @@ impl LightWallet {
return "".to_string(); return "".to_string();
} }
Mnemonic::from_entropy(&self.seed, Mnemonic::from_entropy(&self.seed,
Language::English, Language::English,
).unwrap().phrase().to_string() ).unwrap().phrase().to_string()
} }
@ -672,7 +672,7 @@ impl LightWallet {
let nonce = secretbox::gen_nonce(); let nonce = secretbox::gen_nonce();
let cipher = secretbox::seal(&self.seed, &nonce, &key); let cipher = secretbox::seal(&self.seed, &nonce, &key);
self.enc_seed.copy_from_slice(&cipher); self.enc_seed.copy_from_slice(&cipher);
self.nonce = vec![]; self.nonce = vec![];
self.nonce.extend_from_slice(nonce.as_ref()); self.nonce.extend_from_slice(nonce.as_ref());
@ -725,7 +725,7 @@ impl LightWallet {
// Now that we have the seed, we'll generate the extsks and tkeys, and verify the fvks and addresses // Now that we have the seed, we'll generate the extsks and tkeys, and verify the fvks and addresses
// respectively match // respectively match
// The seed bytes is the raw entropy. To pass it to HD wallet generation, // The seed bytes is the raw entropy. To pass it to HD wallet generation,
// we need to get the 64 byte bip39 entropy // we need to get the 64 byte bip39 entropy
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed, Language::English).unwrap(), ""); let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&seed, Language::English).unwrap(), "");
@ -736,12 +736,12 @@ impl LightWallet {
LightWallet::get_zaddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos as u32); LightWallet::get_zaddr_from_bip39seed(&self.config, &bip39_seed.as_bytes(), pos as u32);
if address != self.zaddress.read().unwrap()[pos] { if address != self.zaddress.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData, return Err(io::Error::new(ErrorKind::InvalidData,
format!("zaddress mismatch at {}. {:?} vs {:?}", pos, address, self.zaddress.read().unwrap()[pos]))); format!("zaddress mismatch at {}. {:?} vs {:?}", pos, address, self.zaddress.read().unwrap()[pos])));
} }
if extfvk != self.extfvks.read().unwrap()[pos] { if extfvk != self.extfvks.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData, return Err(io::Error::new(ErrorKind::InvalidData,
format!("fvk mismatch at {}. {:?} vs {:?}", pos, extfvk, self.extfvks.read().unwrap()[pos]))); format!("fvk mismatch at {}. {:?} vs {:?}", pos, extfvk, self.extfvks.read().unwrap()[pos])));
} }
@ -756,7 +756,7 @@ impl LightWallet {
let address = self.address_from_sk(&sk); let address = self.address_from_sk(&sk);
if address != self.taddresses.read().unwrap()[pos] { if address != self.taddresses.read().unwrap()[pos] {
return Err(io::Error::new(ErrorKind::InvalidData, return Err(io::Error::new(ErrorKind::InvalidData,
format!("taddress mismatch at {}. {} vs {}", pos, address, self.taddresses.read().unwrap()[pos]))); format!("taddress mismatch at {}. {} vs {}", pos, address, self.taddresses.read().unwrap()[pos])));
} }
@ -767,7 +767,7 @@ impl LightWallet {
self.extsks = Arc::new(RwLock::new(extsks)); self.extsks = Arc::new(RwLock::new(extsks));
self.tkeys = Arc::new(RwLock::new(tkeys)); self.tkeys = Arc::new(RwLock::new(tkeys));
self.seed.copy_from_slice(&seed); self.seed.copy_from_slice(&seed);
self.encrypted = true; self.encrypted = true;
self.unlocked = true; self.unlocked = true;
@ -776,7 +776,7 @@ impl LightWallet {
// Removing encryption means unlocking it and setting the self.encrypted = false, // Removing encryption means unlocking it and setting the self.encrypted = false,
// permanantly removing the encryption // permanantly removing the encryption
pub fn remove_encryption(&mut self, passwd: String) -> io::Result<()> { pub fn remove_encryption(&mut self, passwd: String) -> io::Result<()> {
if !self.encrypted { if !self.encrypted {
return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted")); return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted"));
} }
@ -785,7 +785,7 @@ impl LightWallet {
if !self.unlocked { if !self.unlocked {
self.unlock(passwd)?; self.unlock(passwd)?;
} }
// Permanantly remove the encryption // Permanantly remove the encryption
self.encrypted = false; self.encrypted = false;
self.nonce = vec![]; self.nonce = vec![];
@ -807,7 +807,7 @@ impl LightWallet {
.values() .values()
.map(|tx| { .map(|tx| {
tx.notes.iter() tx.notes.iter()
.filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it. .filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr.clone() { match addr.clone() {
Some(a) => a == encode_payment_address( Some(a) => a == encode_payment_address(
self.config.hrp_sapling_address(), self.config.hrp_sapling_address(),
@ -861,7 +861,7 @@ impl LightWallet {
if tx.block as u32 <= anchor_height { if tx.block as u32 <= anchor_height {
tx.notes tx.notes
.iter() .iter()
.filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it. .filter(|nd| { // TODO, this whole section is shared with verified_balance. Refactor it.
match addr.clone() { match addr.clone() {
Some(a) => a == encode_payment_address( Some(a) => a == encode_payment_address(
self.config.hrp_sapling_address(), self.config.hrp_sapling_address(),
@ -894,7 +894,7 @@ impl LightWallet {
match tx_entry.utxos.iter().find(|utxo| { match tx_entry.utxos.iter().find(|utxo| {
utxo.txid == *txid && utxo.output_index == n && Amount::from_u64(utxo.value).unwrap() == vout.value utxo.txid == *txid && utxo.output_index == n && Amount::from_u64(utxo.value).unwrap() == vout.value
}) { }) {
Some(utxo) => { Some(utxo) => {
info!("Already have {}:{}", utxo.txid, utxo.output_index); info!("Already have {}:{}", utxo.txid, utxo.output_index);
} }
None => { None => {
@ -919,14 +919,14 @@ impl LightWallet {
} }
} }
// If one of the last 'n' taddress was used, ensure we add the next HD taddress to the wallet. // If one of the last 'n' taddress was used, ensure we add the next HD taddress to the wallet.
pub fn ensure_hd_taddresses(&self, address: &String) { pub fn ensure_hd_taddresses(&self, address: &String) {
let last_addresses = { let last_addresses = {
self.taddresses.read().unwrap().iter().rev().take(GAP_RULE_UNUSED_ADDRESSES).map(|s| s.clone()).collect::<Vec<String>>() self.taddresses.read().unwrap().iter().rev().take(GAP_RULE_UNUSED_ADDRESSES).map(|s| s.clone()).collect::<Vec<String>>()
}; };
match last_addresses.iter().position(|s| *s == *address) { match last_addresses.iter().position(|s| *s == *address) {
None => { None => {
return; return;
}, },
Some(pos) => { Some(pos) => {
@ -935,7 +935,7 @@ impl LightWallet {
for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) { for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) {
// If the wallet is locked, this is a no-op. That is fine, since we really // If the wallet is locked, this is a no-op. That is fine, since we really
// need to only add new addresses when restoring a new wallet, when it will not be locked. // need to only add new addresses when restoring a new wallet, when it will not be locked.
// Also, if it is locked, the user can't create new addresses anyway. // Also, if it is locked, the user can't create new addresses anyway.
self.add_taddr(); self.add_taddr();
} }
} }
@ -949,7 +949,7 @@ impl LightWallet {
.map(|s| encode_payment_address(self.config.hrp_sapling_address(), s)) .map(|s| encode_payment_address(self.config.hrp_sapling_address(), s))
.collect::<Vec<String>>() .collect::<Vec<String>>()
}; };
match last_addresses.iter().position(|s| *s == *address) { match last_addresses.iter().position(|s| *s == *address) {
None => { None => {
return; return;
@ -960,7 +960,7 @@ impl LightWallet {
for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) { for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) {
// If the wallet is locked, this is a no-op. That is fine, since we really // If the wallet is locked, this is a no-op. That is fine, since we really
// need to only add new addresses when restoring a new wallet, when it will not be locked. // need to only add new addresses when restoring a new wallet, when it will not be locked.
// Also, if it is locked, the user can't create new addresses anyway. // Also, if it is locked, the user can't create new addresses anyway.
self.add_zaddr(); self.add_zaddr();
} }
} }
@ -1006,7 +1006,7 @@ impl LightWallet {
let tx_entry = WalletTx::new(height, datetime, &tx.txid()); let tx_entry = WalletTx::new(height, datetime, &tx.txid());
txs.insert(tx.txid().clone(), tx_entry); txs.insert(tx.txid().clone(), tx_entry);
} }
txs.get_mut(&tx.txid()).unwrap() txs.get_mut(&tx.txid()).unwrap()
.total_transparent_value_spent = total_transparent_spend; .total_transparent_value_spent = total_transparent_spend;
} }
@ -1093,11 +1093,11 @@ impl LightWallet {
{ {
info!("A sapling note was sent in {}, getting memo", tx.txid()); info!("A sapling note was sent in {}, getting memo", tx.txid());
// Do it in a short scope because of the write lock. // Do it in a short scope because of the write lock.
let mut txs = self.txs.write().unwrap(); let mut txs = self.txs.write().unwrap();
// Update memo if we have this Tx. // Update memo if we have this Tx.
match txs.get_mut(&tx.txid()) match txs.get_mut(&tx.txid())
.and_then(|t| { .and_then(|t| {
t.notes.iter_mut().find(|nd| nd.note == note) t.notes.iter_mut().find(|nd| nd.note == note)
@ -1127,13 +1127,13 @@ impl LightWallet {
for (_account, ovk) in ovks.iter().enumerate() { for (_account, ovk) in ovks.iter().enumerate() {
match try_sapling_output_recovery(ovk, match try_sapling_output_recovery(ovk,
&output.cv, &output.cv,
&output.cmu, &output.cmu,
&output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(), &output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(),
&output.enc_ciphertext, &output.enc_ciphertext,
&output.out_ciphertext) { &output.out_ciphertext) {
Some((note, payment_address, memo)) => { Some((note, payment_address, memo)) => {
let address = encode_payment_address(self.config.hrp_sapling_address(), let address = encode_payment_address(self.config.hrp_sapling_address(),
&payment_address); &payment_address);
// Check if this is a change address // Check if this is a change address
@ -1183,9 +1183,9 @@ impl LightWallet {
let mut num_invalidated = 0; let mut num_invalidated = 0;
// First remove the blocks // First remove the blocks
{ {
let mut blks = self.blocks.write().unwrap(); let mut blks = self.blocks.write().unwrap();
while blks.last().unwrap().height >= at_height { while blks.last().unwrap().height >= at_height {
blks.pop(); blks.pop();
num_invalidated += 1; num_invalidated += 1;
@ -1232,7 +1232,7 @@ impl LightWallet {
} }
} }
} }
num_invalidated as u64 num_invalidated as u64
} }
@ -1275,7 +1275,7 @@ impl LightWallet {
tx.send(Some(None)) tx.send(Some(None))
} }
}; };
match r { match r {
Ok(_) => {}, Ok(_) => {},
Err(e) => println!("Send error {:?}", e) Err(e) => println!("Send error {:?}", e)
@ -1301,7 +1301,7 @@ impl LightWallet {
for _i in 0..ivks.len() { for _i in 0..ivks.len() {
let n = rx.recv().unwrap(); let n = rx.recv().unwrap();
let epk = epk.clone(); let epk = epk.clone();
let wso = match n { let wso = match n {
None => panic!("Got a none!"), None => panic!("Got a none!"),
Some(None) => None, Some(None) => None,
@ -1313,7 +1313,7 @@ impl LightWallet {
// - Notes created by consolidation transactions. // - Notes created by consolidation transactions.
// - Notes sent from one account to itself. // - Notes sent from one account to itself.
//let is_change = spent_from_accounts.contains(&account); //let is_change = spent_from_accounts.contains(&account);
Some(WalletShieldedOutput { Some(WalletShieldedOutput {
index, cmu, epk, account, note, to, is_change: false, index, cmu, epk, account, note, to, is_change: false,
witness: IncrementalWitness::from_tree(tree), witness: IncrementalWitness::from_tree(tree),
@ -1322,7 +1322,7 @@ impl LightWallet {
}; };
wsos.push(wso); wsos.push(wso);
} }
match wsos.into_iter().find(|wso| wso.is_some()) { match wsos.into_iter().find(|wso| wso.is_some()) {
Some(Some(wso)) => Some(wso), Some(Some(wso)) => Some(wso),
_ => None _ => None
@ -1513,12 +1513,12 @@ impl LightWallet {
.map(|block| block.tree.clone()) .map(|block| block.tree.clone())
.unwrap_or(CommitmentTree::new()), .unwrap_or(CommitmentTree::new()),
}; };
// These are filled in inside the block // These are filled in inside the block
let new_txs; let new_txs;
let nfs: Vec<_>; let nfs: Vec<_>;
{ {
// Create a write lock // Create a write lock
let mut txs = self.txs.write().unwrap(); let mut txs = self.txs.write().unwrap();
// Create a Vec containing all unspent nullifiers. // Create a Vec containing all unspent nullifiers.
@ -1574,8 +1574,8 @@ impl LightWallet {
) )
}; };
} }
// If this block had any new Txs, return the list of ALL txids in this block, // If this block had any new Txs, return the list of ALL txids in this block,
// so the wallet can fetch them all as a decoy. // so the wallet can fetch them all as a decoy.
let all_txs = if !new_txs.is_empty() { let all_txs = if !new_txs.is_empty() {
block.vtx.iter().map(|vtx| { block.vtx.iter().map(|vtx| {
@ -1588,7 +1588,7 @@ impl LightWallet {
}; };
for tx in new_txs { for tx in new_txs {
// Create a write lock // Create a write lock
let mut txs = self.txs.write().unwrap(); let mut txs = self.txs.write().unwrap();
// Mark notes as spent. // Mark notes as spent.
@ -1596,7 +1596,7 @@ impl LightWallet {
info!("Txid {} belongs to wallet", tx.txid); info!("Txid {} belongs to wallet", tx.txid);
for spend in &tx.shielded_spends { for spend in &tx.shielded_spends {
let txid = nfs let txid = nfs
.iter() .iter()
.find(|(nf, _, _)| &nf[..] == &spend.nf[..]) .find(|(nf, _, _)| &nf[..] == &spend.nf[..])
@ -1609,7 +1609,7 @@ impl LightWallet {
.iter_mut() .iter_mut()
.find(|nd| &nd.nullifier[..] == &spend.nf[..]) .find(|nd| &nd.nullifier[..] == &spend.nf[..])
.unwrap(); .unwrap();
// Mark the note as spent, and remove the unconfirmed part of it // Mark the note as spent, and remove the unconfirmed part of it
info!("Marked a note as spent"); info!("Marked a note as spent");
spent_note.spent = Some(tx.txid); spent_note.spent = Some(tx.txid);
@ -1641,13 +1641,13 @@ impl LightWallet {
match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) { match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) {
None => tx_entry.notes.push(new_note), None => tx_entry.notes.push(new_note),
Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid) Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid)
}; };
} }
} }
{ {
let mut blks = self.blocks.write().unwrap(); let mut blks = self.blocks.write().unwrap();
// Store scanned data for this block. // Store scanned data for this block.
blks.push(block_data); blks.push(block_data);
@ -1658,7 +1658,7 @@ impl LightWallet {
blks.drain(..drain_first); blks.drain(..drain_first);
} }
} }
{ {
// Cleanup mempool tx after adding a block, to remove all txns that got mined // Cleanup mempool tx after adding a block, to remove all txns that got mined
self.cleanup_mempool(); self.cleanup_mempool();
@ -1701,9 +1701,9 @@ impl LightWallet {
// Convert address (str) to RecepientAddress and value to Amount // Convert address (str) to RecepientAddress and value to Amount
let recepients = tos.iter().map(|to| { let recepients = tos.iter().map(|to| {
let ra = match address::RecipientAddress::from_str(to.0, let ra = match address::RecipientAddress::from_str(to.0,
self.config.hrp_sapling_address(), self.config.hrp_sapling_address(),
self.config.base58_pubkey_address(), self.config.base58_pubkey_address(),
self.config.base58_script_address()) { self.config.base58_script_address()) {
Some(to) => to, Some(to) => to,
None => { None => {
@ -1756,8 +1756,8 @@ impl LightWallet {
builder.set_fee(Amount::from_u64(*fee).unwrap()); builder.set_fee(Amount::from_u64(*fee).unwrap());
// A note on t addresses // A note on t addresses
// Funds received by t-addresses can't be explicitly spent in ZecWallet. // Funds received by t-addresses can't be explicitly spent in ZecWallet.
// ZecWallet will lazily consolidate all t address funds into your shielded addresses. // ZecWallet will lazily consolidate all t address funds into your shielded addresses.
// Specifically, if you send an outgoing transaction that is sent to a shielded address, // Specifically, if you send an outgoing transaction that is sent to a shielded address,
// ZecWallet will add all your t-address funds into that transaction, and send them to your shielded // ZecWallet will add all your t-address funds into that transaction, and send them to your shielded
// address as change. // address as change.
@ -1766,8 +1766,8 @@ impl LightWallet {
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends .filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
.map(|utxo| utxo.clone()) .map(|utxo| utxo.clone())
.collect(); .collect();
// Create a map from address -> sk for all taddrs, so we can spend from the // Create a map from address -> sk for all taddrs, so we can spend from the
// right address // right address
let address_to_sk = self.tkeys.read().unwrap().iter() let address_to_sk = self.tkeys.read().unwrap().iter()
.map(|sk| (self.address_from_sk(&sk), sk.clone())) .map(|sk| (self.address_from_sk(&sk), sk.clone()))
@ -1777,7 +1777,7 @@ impl LightWallet {
tinputs.iter() tinputs.iter()
.map(|utxo| { .map(|utxo| {
let outpoint: OutPoint = utxo.to_outpoint(); let outpoint: OutPoint = utxo.to_outpoint();
let coin = TxOut { let coin = TxOut {
value: Amount::from_u64(utxo.value).unwrap(), value: Amount::from_u64(utxo.value).unwrap(),
script_pubkey: Script { 0: utxo.script.clone() }, script_pubkey: Script { 0: utxo.script.clone() },
@ -1793,14 +1793,14 @@ impl LightWallet {
Err(zcash_primitives::transaction::builder::Error::InvalidAddress) Err(zcash_primitives::transaction::builder::Error::InvalidAddress)
} }
} }
}) })
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("{:?}", e))?; .map_err(|e| format!("{:?}", e))?;
// Confirm we were able to select sufficient value // Confirm we were able to select sufficient value
let selected_value = notes.iter().map(|selected| selected.note.value).sum::<u64>() let selected_value = notes.iter().map(|selected| selected.note.value).sum::<u64>()
+ tinputs.iter().map::<u64, _>(|utxo| utxo.value.into()).sum::<u64>(); + tinputs.iter().map::<u64, _>(|utxo| utxo.value.into()).sum::<u64>();
if selected_value < u64::from(target_value) { if selected_value < u64::from(target_value) {
@ -1895,7 +1895,7 @@ impl LightWallet {
return Err(e); return Err(e);
} }
} }
println!("{}: Building transaction", now() - start_time); println!("{}: Building transaction", now() - start_time);
let (tx, _) = match builder.build( let (tx, _) = match builder.build(
@ -1952,7 +1952,7 @@ impl LightWallet {
Memo::from_bytes(s.as_bytes()).unwrap() Memo::from_bytes(s.as_bytes()).unwrap()
} else { } else {
Memo::default() Memo::default()
} }
} }
}, },
} }
@ -1962,7 +1962,7 @@ impl LightWallet {
let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid()); let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid());
wtx.outgoing_metadata = outgoing_metadata; wtx.outgoing_metadata = outgoing_metadata;
// Add it into the mempool // Add it into the mempool
mempool_txs.insert(tx.txid(), wtx); mempool_txs.insert(tx.txid(), wtx);
}, },
Some(_) => { Some(_) => {
@ -1989,7 +1989,7 @@ impl LightWallet {
{ {
// Remove all expired Txns // Remove all expired Txns
self.mempool_txs.write().unwrap().retain( | _, wtx| { self.mempool_txs.write().unwrap().retain( | _, wtx| {
current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA) current_height < (wtx.block + DEFAULT_TX_EXPIRY_DELTA)
}); });
} }
@ -2003,4 +2003,4 @@ impl LightWallet {
} }
#[cfg(test)] #[cfg(test)]
pub mod tests; pub mod tests;

View File

@ -1,10 +1,10 @@
/// ///
/// In v1.0 of zecwallet-cli, there was a bug that incorrectly derived HD wallet keys after the first key. That is, the /// In v1.0 of zecwallet-cli, there was a bug that incorrectly derived HD wallet keys after the first key. That is, the
/// first key, address was correct, but subsequent ones were not. /// first key, address was correct, but subsequent ones were not.
/// ///
/// The issue was that the 32-byte seed was directly being used to derive then subsequent addresses instead of the /// The issue was that the 32-byte seed was directly being used to derive then subsequent addresses instead of the
/// 64-byte pkdf2(seed). The issue affected both t and z addresses /// 64-byte pkdf2(seed). The issue affected both t and z addresses
/// ///
/// To fix the bug, we need to: /// To fix the bug, we need to:
/// 1. Check if the wallet has more than 1 address for t or z addresses /// 1. Check if the wallet has more than 1 address for t or z addresses
/// 2. Move any funds in these addresses to the first address /// 2. Move any funds in these addresses to the first address
@ -32,7 +32,7 @@ impl BugBip39Derivation {
return false; return false;
} }
// The seed bytes is the raw entropy. To pass it to HD wallet generation, // The seed bytes is the raw entropy. To pass it to HD wallet generation,
// we need to get the 64 byte bip39 entropy // we need to get the 64 byte bip39 entropy
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&wallet.seed, Language::English).unwrap(), ""); let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&wallet.seed, Language::English).unwrap(), "");
@ -70,13 +70,13 @@ impl BugBip39Derivation {
}; };
return r.pretty(2); return r.pretty(2);
} }
// Tranfer money // Tranfer money
// 1. The desination is z address #0 // 1. The desination is z address #0
let zaddr = client.do_address()["z_addresses"][0].as_str().unwrap().to_string(); let zaddr = client.do_address()["z_addresses"][0].as_str().unwrap().to_string();
let balance_json = client.do_balance(); let balance_json = client.do_balance();
let amount: u64 = balance_json["zbalance"].as_u64().unwrap() let amount: u64 = balance_json["zbalance"].as_u64().unwrap()
+ balance_json["tbalance"].as_u64().unwrap(); + balance_json["tbalance"].as_u64().unwrap();
let txid = if amount > 0 { let txid = if amount > 0 {
@ -127,4 +127,4 @@ impl BugBip39Derivation {
return r.pretty(2); return r.pretty(2);
} }
} }

View File

@ -10,7 +10,7 @@ use zcash_primitives::{
sapling::Node, sapling::Node,
serialize::{Vector, Optional}, serialize::{Vector, Optional},
transaction::{ transaction::{
components::{OutPoint}, components::{OutPoint},
TxId, TxId,
}, },
note_encryption::{Memo,}, note_encryption::{Memo,},