diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 6124fdd..8b34794 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -27,6 +27,10 @@ macro_rules! configure_clapapp { .long("recover") .help("Attempt to recover the seed from the wallet") .takes_value(false)) + .arg(Arg::with_name("password") + .long("password") + .help("When recovering seed, specify a password for the encrypted wallet") + .takes_value(true)) .arg(Arg::with_name("seed") .short("s") .long("seed") @@ -234,7 +238,7 @@ pub fn command_loop(lightclient: Arc) -> (Sender<(String, Vec) { // Create a Light Client Config in an attempt to recover the file. let config = LightClientConfig { server: "0.0.0.0:0".parse().unwrap(), @@ -246,7 +250,7 @@ pub fn attempt_recover_seed() { data_dir: None, }; - match LightClient::attempt_recover_seed(&config) { + match LightClient::attempt_recover_seed(&config, password) { Ok(seed) => println!("Recovered seed: '{}'", seed), Err(e) => eprintln!("Failed to recover seed. Error: {}", e) }; diff --git a/cli/src/main.rs b/cli/src/main.rs index 0a07850..1e2881c 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -13,9 +13,10 @@ pub fn main() { let fresh_app = App::new("Zecwallet CLI"); let configured_app = configure_clapapp!(fresh_app); let matches = configured_app.get_matches(); + if matches.is_present("recover") { // Create a Light Client Config in an attempt to recover the file. - attempt_recover_seed(); + attempt_recover_seed(matches.value_of("password").map(|s| s.to_string())); return; } @@ -55,8 +56,9 @@ pub fn main() { let (command_tx, resp_rx) = match startup(server, dangerous, seed, birthday, !nosync, command.is_none()) { Ok(c) => c, Err(e) => { - eprintln!("Error during startup: {}", e); - error!("Error during startup: {}", e); + let emsg = format!("Error during startup:{}\nIf you repeatedly run into this issue, you might have to restore your wallet from your seed phrase.", e); + eprintln!("{}", emsg); + error!("{}", emsg); if cfg!(target_os = "unix" ) { match e.raw_os_error() { Some(13) => report_permission_error(), diff --git a/lib/src/lightclient.rs b/lib/src/lightclient.rs index 3925c79..45a0987 100644 --- a/lib/src/lightclient.rs +++ b/lib/src/lightclient.rs @@ -418,7 +418,7 @@ impl LightClient { Ok(()) } - pub fn attempt_recover_seed(config: &LightClientConfig) -> Result { + pub fn attempt_recover_seed(config: &LightClientConfig, password: Option) -> Result { use std::io::prelude::*; use byteorder::{LittleEndian, ReadBytesExt}; use libflate::gzip::Decoder; @@ -442,8 +442,8 @@ impl LightClient { false }; - if encrypted { - return Err("The wallet is encrypted!".to_string()); + if encrypted && password.is_none() { + return Err("The wallet is encrypted and a password was not specified. Please specify the password with '--password'!".to_string()); } let mut enc_seed = [0u8; 48]; @@ -451,19 +451,35 @@ impl LightClient { reader.read_exact(&mut enc_seed).unwrap(); } - let _nonce = if version >= 4 { + let nonce = if version >= 4 { Vector::read(&mut reader, |r| r.read_u8()).unwrap() } else { vec![] }; - // Seed - let mut seed_bytes = [0u8; 32]; - reader.read_exact(&mut seed_bytes).unwrap(); + let phrase = if encrypted { + use sodiumoxide::crypto::secretbox; + use crate::lightwallet::double_sha256; - let phrase = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string(); + // Get the doublesha256 of the password, which is the right length + let key = secretbox::Key::from_slice(&double_sha256(password.unwrap().as_bytes())).unwrap(); + let nonce = secretbox::Nonce::from_slice(&nonce).unwrap(); - Ok(phrase) + let seed = match secretbox::open(&enc_seed, &nonce, &key) { + 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) + }.map_err(|e| format!("Failed to read seed. {:?}", e)); + + phrase.map(|m| m.phrase().to_string()) } @@ -1268,13 +1284,14 @@ pub mod tests { let seed = lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string(); lc.do_save().unwrap(); - assert_eq!(seed, LightClient::attempt_recover_seed(&config).unwrap()); + assert_eq!(seed, LightClient::attempt_recover_seed(&config, None).unwrap()); // Now encrypt and save the file - lc.wallet.write().unwrap().encrypt("password".to_string()).unwrap(); + let pwd = "password".to_string(); + lc.wallet.write().unwrap().encrypt(pwd.clone()).unwrap(); lc.do_save().unwrap(); - assert!(LightClient::attempt_recover_seed(&config).is_err()); + assert_eq!(seed, LightClient::attempt_recover_seed(&config, Some(pwd)).unwrap()); } } diff --git a/lib/src/lightwallet.rs b/lib/src/lightwallet.rs index 1ab90aa..c281795 100644 --- a/lib/src/lightwallet.rs +++ b/lib/src/lightwallet.rs @@ -239,12 +239,15 @@ impl LightWallet { return Err(io::Error::new(ErrorKind::InvalidData, e)); } + println!("Reading wallet version {}", version); info!("Reading wallet version {}", version); // After version 5, we're writing the rest of the file as a compressed stream (gzip) let mut reader: Box = if version <= 4 { + info!("Reading direct"); Box::new(inp) } else { + info!("Reading libflat"); Box::new(Decoder::new(inp).unwrap()) }; @@ -253,25 +256,25 @@ impl LightWallet { } else { 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 { vec![] }; - + // 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))?; - println!("reading version {}", version); let extfvks = if version >= 4 { // Read the viewing keys @@ -281,7 +284,7 @@ impl LightWallet { extsks.iter().map(|sk| ExtendedFullViewingKey::from(sk)) .collect::>() }; - + // Calculate the addresses let addresses = extfvks.iter().map( |fvk| fvk.default_address().unwrap().1 ) .collect::>>(); @@ -291,7 +294,7 @@ impl LightWallet { 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))? @@ -299,9 +302,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)?; @@ -363,7 +366,7 @@ impl LightWallet { writer.write_all(&self.seed)?; // Flush after writing the seed, so in case of a disaster, we can still recover the seed. - //writer.flush()?; + writer.flush()?; // Write all the spending keys Vector::write(&mut writer, &self.extsks.read().unwrap(),