Browse Source

newline cleanup

master
Cryptoforge 4 years ago
parent
commit
e000ff3bc9
  1. 12
      README.md
  2. 20
      lib/src/commands.rs
  3. 126
      lib/src/lightclient.rs
  4. 184
      lib/src/lightwallet.rs
  5. 20
      lib/src/lightwallet/bugs.rs
  6. 2
      lib/src/lightwallet/data.rs

12
README.md

@ -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`
This will launch the interactive prompt. Type `help` to get a list of commands
## 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.
Run `zecwallet-cli help` to see a list of all commands.
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.
## Privacy
## Privacy
* 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...
* Also remember that t-addresses don't provide any privacy protection.
@ -42,9 +42,9 @@ cargo build --release
```
## 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`
* `--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"`

20
lib/src/commands.rs

@ -79,7 +79,7 @@ impl Command for SyncStatusCommand {
false => object!{ "syncing" => "false" },
true => object!{ "syncing" => "true",
"synced_blocks" => status.synced_blocks,
"total_blocks" => status.total_blocks }
"total_blocks" => status.total_blocks }
}.pretty(2)
}
}
@ -120,7 +120,7 @@ impl Command for ClearCommand {
h.push("clear");
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.join("\n")
}
@ -130,9 +130,9 @@ impl Command for ClearCommand {
fn exec(&self, _args: &[&str], lightclient: &LightClient) -> String {
lightclient.clear_state();
let result = object!{ "result" => "success" };
result.pretty(2)
}
}
@ -197,7 +197,7 @@ impl Command for InfoCommand {
"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()
}
}
@ -436,7 +436,7 @@ impl Command for LockCommand {
let mut h = vec![];
h.push("Extra arguments to lock. Did you mean 'encrypt'?");
h.push("");
return format!("{}\n{}", h.join("\n"), self.help());
}
@ -584,9 +584,9 @@ impl Command for SaveCommand {
r.pretty(2)
},
Err(e) => {
let r = object!{
let r = object!{
"result" => "error",
"error" => e
"error" => e
};
r.pretty(2)
}
@ -715,7 +715,7 @@ impl Command for NotesCommand {
}
fn exec(&self, args: &[&str], lightclient: &LightClient) -> String {
// Parse the args.
// Parse the args.
if args.len() > 1 {
return self.short_help();
}
@ -843,6 +843,6 @@ pub mod tests {
#[test]
pub fn test_nosync_commands() {
// The following commands should run
// The following commands should run
}
}

126
lib/src/lightclient.rs

@ -140,7 +140,7 @@ impl LightClientConfig {
}
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() {
zcash_data_location = PathBuf::from(&self.data_dir.as_ref().unwrap());
} else {
@ -178,7 +178,7 @@ impl LightClientConfig {
pub fn get_wallet_path(&self) -> Box<Path> {
let mut wallet_location = self.get_zcash_data_path().into_path_buf();
wallet_location.push(WALLET_NAME);
wallet_location.into_boxed_path()
}
@ -296,7 +296,7 @@ pub struct LightClient {
}
impl LightClient {
pub fn set_wallet_initial_state(&self, height: u64) {
use std::convert::TryInto;
@ -349,14 +349,14 @@ impl LightClient {
let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0)?)),
config : config.clone(),
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
};
l.set_wallet_initial_state(0);
#[cfg(feature = "embed_params")]
l.read_sapling_params();
@ -366,11 +366,11 @@ impl LightClient {
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
pub fn new(config: &LightClientConfig, latest_block: u64) -> io::Result<Self> {
#[cfg(all(not(target_os="ios"), not(target_os="android")))]
{
{
if config.wallet_exists() {
return Err(Error::new(ErrorKind::AlreadyExists,
"Cannot create a new wallet from seed, because a wallet already exists"));
@ -380,14 +380,14 @@ impl LightClient {
let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(None, config, latest_block)?)),
config : config.clone(),
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
};
l.set_wallet_initial_state(latest_block);
#[cfg(feature = "embed_params")]
l.read_sapling_params();
@ -412,7 +412,7 @@ impl LightClient {
let mut l = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), config, birthday)?)),
config : config.clone(),
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -420,7 +420,7 @@ impl LightClient {
println!("Setting birthday to {}", birthday);
l.set_wallet_initial_state(birthday);
#[cfg(feature = "embed_params")]
l.read_sapling_params();
@ -438,7 +438,7 @@ impl LightClient {
let mut lc = LightClient {
wallet : Arc::new(RwLock::new(wallet)),
config : config.clone(),
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::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 wallet = LightWallet::read(&mut file_buffer, config)?;
let mut lc = LightClient {
wallet : Arc::new(RwLock::new(wallet)),
config : config.clone(),
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -547,16 +547,16 @@ impl LightClient {
Ok(s) => s,
Err(_) => return Err("Decryption failed. Is your password correct?".to_string())
};
Mnemonic::from_entropy(&seed, Language::English)
} else {
// Seed
let mut seed_bytes = [0u8; 32];
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));
phrase.map(|m| m.phrase().to_string())
}
@ -640,7 +640,7 @@ impl LightClient {
let t_addresses = wallet.taddresses.read().unwrap().iter().map( |address| {
// Get the balance for this address
let balance = wallet.tbalance(Some(address.clone()));
object!{
"address" => address.clone(),
"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
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.
{
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
@ -686,7 +686,7 @@ impl LightClient {
let mut file = File::create(self.config.get_wallet_path()).unwrap();
file.write_all(&wallet_bytes).map_err(|e| format!("{}", e))?;
Ok(())
},
},
Err(e) => {
let err = format!("ERR: {}", e);
error!("{}", err);
@ -715,7 +715,7 @@ impl LightClient {
}
}
}
}
}
let mut buffer: Vec<u8> = vec![];
match self.wallet.write().unwrap().write(&mut buffer) {
@ -774,7 +774,7 @@ impl LightClient {
let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter()
.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() {
None
} else {
@ -801,16 +801,16 @@ impl LightClient {
}
});
}
let mut unspent_utxos: Vec<JsonValue> = vec![];
let mut spent_utxos : Vec<JsonValue> = vec![];
let mut pending_utxos: Vec<JsonValue> = vec![];
{
let wallet = self.wallet.read().unwrap();
wallet.txs.read().unwrap().iter()
.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() {
None
} else {
@ -945,12 +945,12 @@ impl LightClient {
// Collect outgoing metadata
let outgoing_json = wtx.outgoing_metadata.iter()
.map(|om|
.map(|om|
object!{
"address" => om.address.clone(),
"value" => om.value,
"memo" => LightWallet::memo_str(&Some(om.memo.clone())),
}).collect::<Vec<JsonValue>>();
}).collect::<Vec<JsonValue>>();
object! {
"block_height" => wtx.block,
@ -1004,16 +1004,16 @@ impl LightClient {
// Then set the initial block
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> {
if !self.wallet.read().unwrap().is_unlocked_for_spending() {
warn!("Wallet is locked, new HD addresses won't be added!");
}
info!("Rescan starting");
self.clear_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
// 1. Get the latest block
// 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
let mut last_scanned_height = self.wallet.read().unwrap().last_scanned_height() as u64;
// This will hold the latest block fetched from the RPC
let latest_block = fetch_latest_block(&self.get_server_uri())?.height;
if latest_block < last_scanned_height {
let w = format!("Server's latest block({}) is behind ours({})", latest_block, last_scanned_height);
warn!("{}", w);
@ -1145,7 +1145,7 @@ impl LightClient {
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.
let block: Result<zcash_client_backend::proto::compact_formats::CompactBlock, _>
= 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());
let t = self.wallet.read().unwrap();
let mut d = t.total_scan_duration.write().unwrap();
d.clear();
d.push(std::time::Duration::new(0, 0));
}
// 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);
@ -1192,8 +1192,8 @@ impl LightClient {
if total_reorg > (crate::lightwallet::MAX_REORG - 1) as u64 {
error!("Reorg has now exceeded {} blocks!", crate::lightwallet::MAX_REORG);
return Err(format!("Reorg has exceeded {} blocks. Aborting.", crate::lightwallet::MAX_REORG));
}
}
if invalid_height > 0 {
// Reset the scanning heights
last_scanned_height = (invalid_height - 1) as u64;
@ -1204,17 +1204,17 @@ impl LightClient {
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
total_reorg = 0;
// 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()
.taddresses.read().unwrap().iter().map(|a| a.clone())
.collect::<Vec<String>>();
// Create a channel so the fetch_transparent_txids can send the results back
let (ctx, crx) = channel();
let num_addresses = addresses.len();
@ -1236,7 +1236,7 @@ impl LightClient {
let ctx = ctx.clone();
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
let r = fetch_transparent_txids(&server_uri, address, transparent_start_height, end_height,
move |tx_bytes: &[u8], height: u64| {
@ -1244,17 +1244,17 @@ impl LightClient {
// Scan this Tx for transparent inputs and outputs
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();
});
}
// 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
crx.iter().take(num_addresses).collect::<Result<Vec<()>, String>>()?;
}
}
// Do block height accounting
last_scanned_height = end_height;
end_height = last_scanned_height + 1000;
@ -1269,7 +1269,7 @@ impl LightClient {
if print_updates{
println!(""); // New line to finish up the updates
}
info!("Synced to {}, Downloaded {} kB", latest_block, bytes_downloaded.load(Ordering::SeqCst) / 1024);
{
let mut status = self.sync_status.write().unwrap();
@ -1292,13 +1292,13 @@ impl LightClient {
txids_to_fetch.sort();
txids_to_fetch.dedup();
let mut rng = OsRng;
let mut rng = OsRng;
txids_to_fetch.shuffle(&mut rng);
let num_fetches = txids_to_fetch.len();
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
for (txid, height) in txids_to_fetch {
let light_wallet_clone = self.wallet.clone();
@ -1306,19 +1306,19 @@ impl LightClient {
let pool = pool.clone();
let server_uri = self.get_server_uri();
let ctx = ctx.clone();
pool.execute(move || {
info!("Fetching full Tx: {}", txid);
match fetch_full_tx(&server_uri, txid) {
Ok(tx_bytes) => {
let tx = Transaction::read(&tx_bytes[..]).unwrap();
light_wallet_clone.read().unwrap().scan_full_tx(&tx, height, 0);
ctx.send(Ok(())).unwrap();
},
Err(e) => ctx.send(Err(e)).unwrap()
};
};
});
};
@ -1331,7 +1331,7 @@ impl LightClient {
"downloaded_bytes" => bytes_downloaded.load(Ordering::SeqCst)
}),
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> {
@ -1350,7 +1350,7 @@ impl LightClient {
info!("Transaction Complete");
match rawtx {
Ok(txbytes) => broadcast_raw_tx(&self.get_server_uri(), txbytes),
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
assert!(!lc.do_new_address("t").is_err());
lc.wallet.write().unwrap().unlock("password".to_string()).unwrap();
assert!(!lc.do_new_address("z").is_err());
}
#[test]
pub fn test_addresses() {
let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap();
{
let addresses = lc.do_address();
// When restoring from seed, there should be 5+1 addresses
assert_eq!(addresses["z_addresses"].len(), 6);
assert_eq!(addresses["t_addresses"].len(), 6);
}
// Add new z and t addresses
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 zaddr2 = lc.do_new_address("z").unwrap()[0].as_str().unwrap().to_string();
let addresses = lc.do_address();
assert_eq!(addresses["z_addresses"].len(), 8);
assert_eq!(addresses["z_addresses"][6], zaddr1);
@ -1431,7 +1431,7 @@ pub mod tests {
let lc = LightClient {
wallet : Arc::new(RwLock::new(LightWallet::new(None, &config, 0).unwrap())),
config : config,
sapling_output : vec![],
sapling_output : vec![],
sapling_spend : vec![],
sync_lock : Mutex::new(()),
sync_status : Arc::new(RwLock::new(WalletStatus::new())),
@ -1523,8 +1523,8 @@ pub mod tests {
use crate::SaplingParams;
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());
}
}
}

184
lib/src/lightwallet.rs

@ -99,16 +99,16 @@ impl ToBase58Check for [u8] {
}
pub struct LightWallet {
// 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
encrypted: bool,
// 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
encrypted: bool,
// 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?
unlocked: bool,
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
@ -118,17 +118,17 @@ pub struct LightWallet {
extfvks: Arc<RwLock<Vec<ExtendedFullViewingKey>>>,
pub zaddress: Arc<RwLock<Vec<PaymentAddress<Bls12>>>>,
// 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>>>,
pub taddresses: Arc<RwLock<Vec<String>>>,
blocks: Arc<RwLock<Vec<BlockData>>>,
pub txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// Transactions that are only in the mempool, but haven't been confirmed yet.
// This is not stored to disk.
// Transactions that are only in the mempool, but haven't been confirmed yet.
// This is not stored to disk.
pub mempool_txs: Arc<RwLock<HashMap<TxId, WalletTx>>>,
// 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) ->
(ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress<Bls12>) {
assert_eq!(bip39_seed.len(), 64);
let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path(
&ExtendedSpendingKey::master(bip39_seed),
&[
@ -180,12 +180,12 @@ impl LightWallet {
pub fn is_shielded_address(addr: &String, config: &LightClientConfig) -> bool {
match address::RecipientAddress::from_str(addr,
config.hrp_sapling_address(),
config.base58_pubkey_address(),
config.hrp_sapling_address(),
config.base58_pubkey_address(),
config.base58_script_address()) {
Some(address::RecipientAddress::Shielded(_)) => true,
_ => false,
}
}
}
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];
if seed_phrase.is_none() {
// Create a random seed.
// Create a random seed.
let mut system_rng = OsRng;
system_rng.fill(&mut seed_bytes);
} else {
@ -205,11 +205,11 @@ impl LightWallet {
return Err(io::Error::new(ErrorKind::InvalidData, e));
}
};
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
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 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
let (extsk, extfvk, address)
= LightWallet::get_zaddr_from_bip39seed(&config, &bip39_seed.as_bytes(), 0);
@ -262,7 +262,7 @@ impl LightWallet {
println!("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)
let mut reader: Box<dyn Read> = if version != 5 {
info!("Reading direct");
@ -278,12 +278,12 @@ impl LightWallet {
false
};
info!("Wallet Encryption {:?}", encrypted);
let mut enc_seed = [0u8; 48];
if version >= 4 {
reader.read_exact(&mut enc_seed)?;
}
let nonce = if version >= 4 {
Vector::read(&mut reader, |r| r.read_u8())?
} else {
@ -293,10 +293,10 @@ impl LightWallet {
// Seed
let mut seed_bytes = [0u8; 32];
reader.read_exact(&mut seed_bytes)?;
// Read the spending keys
let extsks = Vector::read(&mut reader, |r| ExtendedSpendingKey::read(r))?;
let extfvks = if version >= 4 {
// Read the viewing keys
Vector::read(&mut reader, |r| ExtendedFullViewingKey::read(r))?
@ -305,7 +305,7 @@ impl LightWallet {
extsks.iter().map(|sk| ExtendedFullViewingKey::from(sk))
.collect::<Vec<ExtendedFullViewingKey>>()
};
// Calculate the addresses
let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 )
.collect::<Vec<PaymentAddress<Bls12>>>();
@ -314,8 +314,8 @@ impl LightWallet {
let mut tpk_bytes = [0u8; 32];
r.read_exact(&mut tpk_bytes)?;
secp256k1::SecretKey::from_slice(&tpk_bytes).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
})?;
})?;
let taddresses = if version >= 4 {
// Read the addresses
Vector::read(&mut reader, |r| utils::read_string(r))?
@ -323,9 +323,9 @@ impl LightWallet {
// Calculate the addresses
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 txs_tuples = Vector::read(&mut reader, |r| {
let mut txid_bytes = [0u8; 32];
r.read_exact(&mut txid_bytes)?;
@ -345,7 +345,7 @@ impl LightWallet {
Ok(LightWallet{
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,
nonce: nonce,
seed: seed_bytes,
@ -365,7 +365,7 @@ impl LightWallet {
pub fn write<W: Write>(&self, mut writer: W) -> io::Result<()> {
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.")));
}
@ -408,7 +408,7 @@ impl LightWallet {
)?;
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
// deterministically saved
{
@ -471,7 +471,7 @@ impl LightWallet {
/// Get all t-address private keys. Returns a Vector of (address, secretkey)
pub fn get_t_secret_keys(&self) -> Vec<(String, String)> {
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]))
}).collect::<Vec<(String, String)>>()
}
@ -508,7 +508,7 @@ impl LightWallet {
let pos = self.tkeys.read().unwrap().len() as u32;
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 address = self.address_from_sk(&sk);
@ -637,7 +637,7 @@ impl LightWallet {
pub fn address_from_sk(&self, sk: &secp256k1::SecretKey) -> String {
LightWallet::address_from_prefix_sk(&self.config.base58_pubkey_address(), sk)
}
pub fn address_from_pubkeyhash(&self, ta: Option<TransparentAddress>) -> Option<String> {
match ta {
Some(TransparentAddress::PublicKey(hash)) => {
@ -655,7 +655,7 @@ impl LightWallet {
return "".to_string();
}
Mnemonic::from_entropy(&self.seed,
Mnemonic::from_entropy(&self.seed,
Language::English,
).unwrap().phrase().to_string()
}
@ -672,7 +672,7 @@ impl LightWallet {
let nonce = secretbox::gen_nonce();
let cipher = secretbox::seal(&self.seed, &nonce, &key);
self.enc_seed.copy_from_slice(&cipher);
self.nonce = vec![];
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
// 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
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);
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])));
}
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])));
}
@ -756,7 +756,7 @@ impl LightWallet {
let address = self.address_from_sk(&sk);
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])));
}
@ -767,7 +767,7 @@ impl LightWallet {
self.extsks = Arc::new(RwLock::new(extsks));
self.tkeys = Arc::new(RwLock::new(tkeys));
self.seed.copy_from_slice(&seed);
self.encrypted = true;
self.unlocked = true;
@ -776,7 +776,7 @@ impl LightWallet {
// Removing encryption means unlocking it and setting the self.encrypted = false,
// 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 {
return Err(Error::new(ErrorKind::AlreadyExists, "Wallet is not encrypted"));
}
@ -785,7 +785,7 @@ impl LightWallet {
if !self.unlocked {
self.unlock(passwd)?;
}
// Permanantly remove the encryption
self.encrypted = false;
self.nonce = vec![];
@ -807,7 +807,7 @@ impl LightWallet {
.values()
.map(|tx| {
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() {
Some(a) => a == encode_payment_address(
self.config.hrp_sapling_address(),
@ -861,7 +861,7 @@ impl LightWallet {
if tx.block as u32 <= anchor_height {
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() {
Some(a) => a == encode_payment_address(
self.config.hrp_sapling_address(),
@ -894,7 +894,7 @@ impl LightWallet {
match tx_entry.utxos.iter().find(|utxo| {
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);
}
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.
pub fn ensure_hd_taddresses(&self, address: &String) {
// 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) {
let last_addresses = {
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) {
None => {
None => {
return;
},
Some(pos) => {
@ -935,7 +935,7 @@ impl LightWallet {
for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) {
// 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.
// 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();
}
}
@ -949,7 +949,7 @@ impl LightWallet {
.map(|s| encode_payment_address(self.config.hrp_sapling_address(), s))
.collect::<Vec<String>>()
};
match last_addresses.iter().position(|s| *s == *address) {
None => {
return;
@ -960,7 +960,7 @@ impl LightWallet {
for _ in 0..(GAP_RULE_UNUSED_ADDRESSES - pos) {
// 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.
// 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();
}
}
@ -1006,7 +1006,7 @@ impl LightWallet {
let tx_entry = WalletTx::new(height, datetime, &tx.txid());
txs.insert(tx.txid().clone(), tx_entry);
}
txs.get_mut(&tx.txid()).unwrap()
.total_transparent_value_spent = total_transparent_spend;
}
@ -1093,11 +1093,11 @@ impl LightWallet {
{
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();
// Update memo if we have this Tx.
// Update memo if we have this Tx.
match txs.get_mut(&tx.txid())
.and_then(|t| {
t.notes.iter_mut().find(|nd| nd.note == note)
@ -1127,13 +1127,13 @@ impl LightWallet {
for (_account, ovk) in ovks.iter().enumerate() {
match try_sapling_output_recovery(ovk,
&output.cv,
&output.cmu,
&output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(),
&output.cv,
&output.cmu,
&output.ephemeral_key.as_prime_order(&JUBJUB).unwrap(),
&output.enc_ciphertext,
&output.out_ciphertext) {
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);
// Check if this is a change address
@ -1183,9 +1183,9 @@ impl LightWallet {
let mut num_invalidated = 0;
// First remove the blocks
{
{
let mut blks = self.blocks.write().unwrap();
while blks.last().unwrap().height >= at_height {
blks.pop();
num_invalidated += 1;
@ -1232,7 +1232,7 @@ impl LightWallet {
}
}
}
num_invalidated as u64
}
@ -1275,7 +1275,7 @@ impl LightWallet {
tx.send(Some(None))
}
};
match r {
Ok(_) => {},
Err(e) => println!("Send error {:?}", e)
@ -1301,7 +1301,7 @@ impl LightWallet {
for _i in 0..ivks.len() {
let n = rx.recv().unwrap();
let epk = epk.clone();
let wso = match n {
None => panic!("Got a none!"),
Some(None) => None,
@ -1313,7 +1313,7 @@ impl LightWallet {
// - Notes created by consolidation transactions.
// - Notes sent from one account to itself.
//let is_change = spent_from_accounts.contains(&account);
Some(WalletShieldedOutput {
index, cmu, epk, account, note, to, is_change: false,
witness: IncrementalWitness::from_tree(tree),
@ -1322,7 +1322,7 @@ impl LightWallet {
};
wsos.push(wso);
}
match wsos.into_iter().find(|wso| wso.is_some()) {
Some(Some(wso)) => Some(wso),
_ => None
@ -1513,12 +1513,12 @@ impl LightWallet {
.map(|block| block.tree.clone())
.unwrap_or(CommitmentTree::new()),
};
// These are filled in inside the block
let new_txs;
let nfs: Vec<_>;
{
// Create a write lock
// Create a write lock
let mut txs = self.txs.write().unwrap();
// 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.
let all_txs = if !new_txs.is_empty() {
block.vtx.iter().map(|vtx| {
@ -1588,7 +1588,7 @@ impl LightWallet {
};
for tx in new_txs {
// Create a write lock
// Create a write lock
let mut txs = self.txs.write().unwrap();
// Mark notes as spent.
@ -1596,7 +1596,7 @@ impl LightWallet {
info!("Txid {} belongs to wallet", tx.txid);
for spend in &tx.shielded_spends {
for spend in &tx.shielded_spends {
let txid = nfs
.iter()
.find(|(nf, _, _)| &nf[..] == &spend.nf[..])
@ -1609,7 +1609,7 @@ impl LightWallet {
.iter_mut()
.find(|nd| &nd.nullifier[..] == &spend.nf[..])
.unwrap();
// Mark the note as spent, and remove the unconfirmed part of it
info!("Marked a note as spent");
spent_note.spent = Some(tx.txid);
@ -1641,13 +1641,13 @@ impl LightWallet {
match tx_entry.notes.iter().find(|nd| nd.nullifier == new_note.nullifier) {
None => tx_entry.notes.push(new_note),
Some(_) => warn!("Tried to insert duplicate note for Tx {}", tx.txid)
};
};
}
}
{
let mut blks = self.blocks.write().unwrap();
// Store scanned data for this block.
blks.push(block_data);
@ -1658,7 +1658,7 @@ impl LightWallet {
blks.drain(..drain_first);
}
}
{
// Cleanup mempool tx after adding a block, to remove all txns that got mined
self.cleanup_mempool();
@ -1701,9 +1701,9 @@ impl LightWallet {
// Convert address (str) to RecepientAddress and value to Amount
let recepients = tos.iter().map(|to| {
let ra = match address::RecipientAddress::from_str(to.0,
self.config.hrp_sapling_address(),
self.config.base58_pubkey_address(),
let ra = match address::RecipientAddress::from_str(to.0,
self.config.hrp_sapling_address(),
self.config.base58_pubkey_address(),
self.config.base58_script_address()) {
Some(to) => to,
None => {
@ -1756,8 +1756,8 @@ impl LightWallet {
builder.set_fee(Amount::from_u64(*fee).unwrap());
// A note on t addresses
// Funds received by t-addresses can't be explicitly spent in ZecWallet.
// ZecWallet will lazily consolidate all t address funds into your shielded addresses.
// Funds received by t-addresses can't be explicitly spent in ZecWallet.
// 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,
// ZecWallet will add all your t-address funds into that transaction, and send them to your shielded
// address as change.
@ -1766,8 +1766,8 @@ impl LightWallet {
.filter(|utxo| utxo.unconfirmed_spent.is_none()) // Remove any unconfirmed spends
.map(|utxo| utxo.clone())
.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
let address_to_sk = self.tkeys.read().unwrap().iter()
.map(|sk| (self.address_from_sk(&sk), sk.clone()))
@ -1777,7 +1777,7 @@ impl LightWallet {
tinputs.iter()
.map(|utxo| {
let outpoint: OutPoint = utxo.to_outpoint();
let coin = TxOut {
value: Amount::from_u64(utxo.value).unwrap(),
script_pubkey: Script { 0: utxo.script.clone() },
@ -1793,14 +1793,14 @@ impl LightWallet {
Err(zcash_primitives::transaction::builder::Error::InvalidAddress)
}
}
})
.collect::<Result<Vec<_>, _>>()
.map_err(|e| format!("{:?}", e))?;
// 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>();
if selected_value < u64::from(target_value) {
@ -1895,7 +1895,7 @@ impl LightWallet {
return Err(e);
}
}
println!("{}: Building transaction", now() - start_time);
let (tx, _) = match builder.build(
@ -1952,7 +1952,7 @@ impl LightWallet {
Memo::from_bytes(s.as_bytes()).unwrap()
} else {
Memo::default()
}
}
}
},
}
@ -1962,7 +1962,7 @@ impl LightWallet {
let mut wtx = WalletTx::new(height as i32, now() as u64, &tx.txid());
wtx.outgoing_metadata = outgoing_metadata;
// Add it into the mempool
// Add it into the mempool
mempool_txs.insert(tx.txid(), wtx);
},
Some(_) => {
@ -1989,7 +1989,7 @@ impl LightWallet {
{
// Remove all expired Txns
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)]
pub mod tests;
pub mod tests;

20
lib/src/lightwallet/bugs.rs

@ -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
/// 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
/// 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.
///
/// 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
///
///
/// To fix the bug, we need to:
/// 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
@ -32,7 +32,7 @@ impl BugBip39Derivation {
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
let bip39_seed = bip39::Seed::new(&Mnemonic::from_entropy(&wallet.seed, Language::English).unwrap(), "");
@ -70,13 +70,13 @@ impl BugBip39Derivation {
};
return r.pretty(2);
}
}
// Tranfer money
// 1. The desination is z address #0
let zaddr = client.do_address()["z_addresses"][0].as_str().unwrap().to_string();
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();
let txid = if amount > 0 {
@ -127,4 +127,4 @@ impl BugBip39Derivation {
return r.pretty(2);
}
}
}

2
lib/src/lightwallet/data.rs

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

Loading…
Cancel
Save