diff --git a/rust-lightclient/Cargo.toml b/rust-lightclient/Cargo.toml index 90402ed..66f34b0 100644 --- a/rust-lightclient/Cargo.toml +++ b/rust-lightclient/Cargo.toml @@ -23,6 +23,8 @@ rustyline = "5.0.2" byteorder = "1" rand = "0.5.6" json = "0.12.0" +bip39 = "0.6.0-beta.1" +clap = "2.33" [dependencies.bellman] path = "../../librustzcash/bellman" diff --git a/rust-lightclient/src/commands.rs b/rust-lightclient/src/commands.rs index 2b008a6..885ea43 100644 --- a/rust-lightclient/src/commands.rs +++ b/rust-lightclient/src/commands.rs @@ -110,18 +110,23 @@ impl Command for SaveCommand { } } -struct ReadCommand {} -impl Command for ReadCommand { - fn help(&self) { - println!("Read wallet from disk"); +struct SeedCommand {} +impl Command for SeedCommand { + fn help(&self) { + println!("Show the seed phrase for the wallet"); } fn short_help(&self) -> String { - "Read wallet file from disk".to_string() + "Display the seed phrase".to_string() } fn exec(&self, _args: &[String], lightclient: &mut LightClient) { - lightclient.do_read(); + let phrase = lightclient.do_seed_phrase(); + + println!("Current seed phrase. PLEASE SAVE THIS CAREFULLY AND DO NOT SHARE IT"); + println!(); + println!("{}", phrase); + println!(); } } @@ -167,9 +172,9 @@ pub fn get_commands() -> Box>> { map.insert("info".to_string(), Box::new(InfoCommand{})); map.insert("send".to_string(), Box::new(SendCommand{})); map.insert("save".to_string(), Box::new(SaveCommand{})); - map.insert("read".to_string(), Box::new(ReadCommand{})); map.insert("quit".to_string(), Box::new(QuitCommand{})); map.insert("list".to_string(), Box::new(TransactionsCommand{})); + map.insert("seed".to_string(), Box::new(SeedCommand{})); Box::new(map) } diff --git a/rust-lightclient/src/lightclient.rs b/rust-lightclient/src/lightclient.rs index 0eba6ce..7697c0d 100644 --- a/rust-lightclient/src/lightclient.rs +++ b/rust-lightclient/src/lightclient.rs @@ -39,24 +39,44 @@ pub struct LightClient { } impl LightClient { - pub fn new() -> Self { - let mut w = LightClient { - wallet : Arc::new(LightWallet::new()), - sapling_output : vec![], - sapling_spend : vec![] + pub fn new(seed_phrase: Option<&str>) -> io::Result { + + let mut lc = if Path::new("wallet.dat").exists() { + // Make sure that if a wallet exists, there is no seed phrase being attempted + if !seed_phrase.is_none() { + return Err(io::Error::new(io::ErrorKind::AlreadyExists, + "Cannot restore from seed, because a wallet already exists")); + } + + let mut file_buffer = BufReader::new(File::open("wallet.dat")?); + + let wallet = LightWallet::read(&mut file_buffer)?; + LightClient { + wallet : Arc::new(wallet), + sapling_output : vec![], + sapling_spend : vec![] + } + } else { + let l = LightClient { + wallet : Arc::new(LightWallet::new(seed_phrase).unwrap()), + sapling_output : vec![], + sapling_spend : vec![] + }; + + l.wallet.set_initial_block(500000, + "004fada8d4dbc5e80b13522d2c6bd0116113c9b7197f0c6be69bc7a62f2824cd", + "01b733e839b5f844287a6a491409a991ec70277f39a50c99163ed378d23a829a0700100001916db36dfb9a0cf26115ed050b264546c0fa23459433c31fd72f63d188202f2400011f5f4e3bd18da479f48d674dbab64454f6995b113fa21c9d8853a9e764fb3e1f01df9d2c233ca60360e3c2bb73caf5839a1be634c8b99aea22d02abda2e747d9100001970d41722c078288101acd0a75612acfb4c434f2a55aab09fb4e812accc2ba7301485150f0deac7774dcd0fe32043bde9ba2b6bbfff787ad074339af68e88ee70101601324f1421e00a43ef57f197faf385ee4cac65aab58048016ecbd94e022973701e1b17f4bd9d1b6ca1107f619ac6d27b53dd3350d5be09b08935923cbed97906c0000000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"); + + l }; - + // Read Sapling Params - let mut f = File::open("/home/adityapk/.zcash-params/sapling-output.params").unwrap(); - f.read_to_end(&mut w.sapling_output).unwrap(); - let mut f = File::open("/home/adityapk/.zcash-params/sapling-spend.params").unwrap(); - f.read_to_end(&mut w.sapling_spend).unwrap(); + let mut f = File::open("/home/adityapk/.zcash-params/sapling-output.params")?; + f.read_to_end(&mut lc.sapling_output)?; + let mut f = File::open("/home/adityapk/.zcash-params/sapling-spend.params")?; + f.read_to_end(&mut lc.sapling_spend)?; - w.wallet.set_initial_block(500000, - "004fada8d4dbc5e80b13522d2c6bd0116113c9b7197f0c6be69bc7a62f2824cd", - "01b733e839b5f844287a6a491409a991ec70277f39a50c99163ed378d23a829a0700100001916db36dfb9a0cf26115ed050b264546c0fa23459433c31fd72f63d188202f2400011f5f4e3bd18da479f48d674dbab64454f6995b113fa21c9d8853a9e764fb3e1f01df9d2c233ca60360e3c2bb73caf5839a1be634c8b99aea22d02abda2e747d9100001970d41722c078288101acd0a75612acfb4c434f2a55aab09fb4e812accc2ba7301485150f0deac7774dcd0fe32043bde9ba2b6bbfff787ad074339af68e88ee70101601324f1421e00a43ef57f197faf385ee4cac65aab58048016ecbd94e022973701e1b17f4bd9d1b6ca1107f619ac6d27b53dd3350d5be09b08935923cbed97906c0000000000011f8322ef806eb2430dc4a7a41c1b344bea5be946efc7b4349c1c9edb14ff9d39"); - - return w; + Ok(lc) } pub fn last_scanned_height(&self) -> u64 { @@ -80,27 +100,6 @@ impl LightClient { } } - pub fn do_read(&mut self) { - if !Path::new("wallet.dat").exists() { - println!("No existing wallet"); - return; - } - - print!("Reading wallet..."); - io::stdout().flush().ok().expect("Could not flush stdout"); - let mut file_buffer = match File::open("wallet.dat") { - Ok(f) => BufReader::new(f), - Err(e) => { - println!("[Error: {}]", e.description()); - return; - } - }; - - let lw = LightWallet::read(&mut file_buffer).unwrap(); - self.wallet = Arc::new(lw); - println!("[OK]"); - } - pub fn do_save(&self) { print!("Saving wallet..."); io::stdout().flush().ok().expect("Could not flush stdout"); @@ -133,6 +132,10 @@ impl LightClient { tokio::runtime::current_thread::Runtime::new().unwrap().block_on(say_hello).unwrap() } + pub fn do_seed_phrase(&self) -> String { + self.wallet.get_seed_phrase() + } + pub fn do_list_transactions(&self) -> JsonValue { // Create a list of TransactionItems let mut tx_list = self.wallet.txs.read().unwrap().iter() @@ -168,14 +171,14 @@ impl LightClient { "amount" => nd.note.value as i64, "address" => nd.note_address().unwrap(), "memo" => match &nd.memo { - Some(memo) => { - match memo.to_utf8() { - Some(Ok(memo_str)) => Some(memo_str), - _ => None + Some(memo) => { + match memo.to_utf8() { + Some(Ok(memo_str)) => Some(memo_str), + _ => None + } } + _ => None } - _ => None - } }) ); diff --git a/rust-lightclient/src/lightwallet.rs b/rust-lightclient/src/lightwallet.rs index 5fe1e9f..4b6d001 100644 --- a/rust-lightclient/src/lightwallet.rs +++ b/rust-lightclient/src/lightwallet.rs @@ -6,7 +6,9 @@ use std::cmp; use std::collections::HashMap; use std::sync::{Arc, RwLock}; -use protobuf::*; +use protobuf::parse_from_bytes; + +use bip39::{Mnemonic, Language}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use pairing::bls12_381::{Bls12}; @@ -383,27 +385,33 @@ impl LightWallet { (extsk, extfvk, address) } - pub fn new() -> Self { + pub fn new(seed_phrase: Option<&str>) -> io::Result { use rand::{FromEntropy, ChaChaRng, Rng}; - // Create a random seed. - let mut system_rng = ChaChaRng::from_entropy(); let mut seed_bytes = [0u8; 32]; - system_rng.fill(&mut seed_bytes); + + if seed_phrase.is_none() { + // Create a random seed. + let mut system_rng = ChaChaRng::from_entropy(); + system_rng.fill(&mut seed_bytes); + } else { + seed_bytes.copy_from_slice(&Mnemonic::from_phrase(seed_phrase.expect("should have a seed phrase"), + Language::English).unwrap().entropy()); + } // Derive only the first address // 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_pk_from_seed(&seed_bytes); - LightWallet { + Ok(LightWallet { seed: seed_bytes, extsks: vec![extsk], extfvks: vec![extfvk], address: vec![address], blocks: Arc::new(RwLock::new(vec![])), txs: Arc::new(RwLock::new(HashMap::new())), - } + }) } pub fn read(mut reader: R) -> io::Result { @@ -539,6 +547,12 @@ impl LightWallet { encode_payment_address(HRP_SAPLING_PAYMENT_ADDRESS, &self.address[account]) } + pub fn get_seed_phrase(&self) -> String { + Mnemonic::from_entropy(&self.seed, + Language::English, + ).unwrap().phrase().to_string() + } + pub fn balance(&self, addr: Option) -> u64 { self.txs .read() diff --git a/rust-lightclient/src/main.rs b/rust-lightclient/src/main.rs index a0a4139..1eeb43c 100644 --- a/rust-lightclient/src/main.rs +++ b/rust-lightclient/src/main.rs @@ -16,8 +16,21 @@ pub mod grpc_client { pub fn main() { + use clap::{Arg, App}; + + let matches = App::new("Light Client") + .version("1.0") + .arg(Arg::with_name("seed") + .short("s") + .long("seed") + .value_name("seed_phrase") + .help("Create a new wallet with the given 24-word seed phrase. Will fail if wallet already exists") + .takes_value(true)) + .get_matches(); + + let mut lightclient = LightClient::new(matches.value_of("seed")).unwrap(); + println!("Starting Light Client"); - let mut lightclient = LightClient::new(); // At startup, read the wallet.dat commands::do_user_command(&"read".to_string(), &mut lightclient);