From 5b02282a661744ee4ead38f05b298bc9199f51a6 Mon Sep 17 00:00:00 2001 From: Aditya Kulkarni Date: Tue, 24 Sep 2019 11:03:43 -0700 Subject: [PATCH] Add export command --- src/commands.rs | 32 +++++++++++++++++++ src/lightclient.rs | 54 ++++++++++++++++++++++++++++++- src/lightwallet/mod.rs | 72 ++++++++++++++++++++++++++---------------- 3 files changed, 130 insertions(+), 28 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index 4f98559..61ce69f 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -168,6 +168,37 @@ impl Command for AddressCommand { } } +struct ExportCommand {} +impl Command for ExportCommand { + fn help(&self) -> String { + let mut h = vec![]; + h.push("Export private key for wallet addresses."); + h.push("Usage:"); + h.push("export [t-address or z-address]"); + h.push(""); + h.push("If no address is passed, private key for all addresses in the wallet are exported/"); + h.push(""); + h.push("Example:"); + h.push("export ztestsapling1x65nq4dgp0qfywgxcwk9n0fvm4fysmapgr2q00p85ju252h6l7mmxu2jg9cqqhtvzd69jwhgv8d"); + + h.join("\n") + } + + fn short_help(&self) -> String { + "Export private key for wallet addresses".to_string() + } + + fn exec(&self, args: &[&str], lightclient: &LightClient) -> String { + if args.len() > 1 { + return self.help(); + } + + let address = if args.is_empty() { None } else { Some(args[0].to_string()) }; + + format!("{}", lightclient.do_export(address).pretty(2)) + } +} + struct SendCommand {} impl Command for SendCommand { @@ -352,6 +383,7 @@ pub fn get_commands() -> Box>> { map.insert("help".to_string(), Box::new(HelpCommand{})); map.insert("balance".to_string(), Box::new(BalanceCommand{})); map.insert("address".to_string(), Box::new(AddressCommand{})); + map.insert("export".to_string(), Box::new(ExportCommand{})); map.insert("info".to_string(), Box::new(InfoCommand{})); map.insert("send".to_string(), Box::new(SendCommand{})); map.insert("save".to_string(), Box::new(SaveCommand{})); diff --git a/src/lightclient.rs b/src/lightclient.rs index 15f2fb1..5c16980 100644 --- a/src/lightclient.rs +++ b/src/lightclient.rs @@ -63,7 +63,6 @@ impl LightClientConfig { } pub fn get_zcash_data_path(&self) -> Box { - let mut zcash_data_location; if cfg!(target_os="macos") || cfg!(target_os="windows") { zcash_data_location = dirs::data_dir().expect("Couldn't determine app data directory!"); @@ -123,6 +122,15 @@ impl LightClientConfig { } } + pub fn hrp_sapling_private_key(&self) -> &str { + match &self.chain_name[..] { + "main" => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + "test" => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + "regtest" => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY, + c => panic!("Unknown chain {}", c) + } + } + pub fn base58_pubkey_address(&self) -> [u8; 2] { match &self.chain_name[..] { "main" => mainnet::B58_PUBKEY_ADDRESS_PREFIX, @@ -141,6 +149,15 @@ impl LightClientConfig { c => panic!("Unknown chain {}", c) } } + + pub fn base58_secretkey_prefix(&self) -> [u8; 1] { + match &self.chain_name[..] { + "main" => [0x80], + "test" => [0xEF], + "regtest" => [0xEF], + c => panic!("Unknown chain {}", c) + } + } } pub struct LightClient { @@ -222,6 +239,41 @@ impl LightClient { self.wallet.last_scanned_height() as u64 } + // Export private keys + pub fn do_export(&self, addr: Option) -> json::JsonValue { + // Clone address so it can be moved into the closure + let address = addr.clone(); + + // Go over all z addresses + let z_keys = self.wallet.get_z_private_keys().iter() + .filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr)) + .map( |(addr, pk)| + object!{ + "address" => addr.clone(), + "private_key" => pk.clone() + } + ).collect::>(); + + // Clone address so it can be moved into the closure + let address = addr.clone(); + + // Go over all t addresses + let t_keys = self.wallet.get_t_secret_keys().iter() + .filter( move |(addr, _)| address.is_none() || address.as_ref() == Some(addr)) + .map( |(addr, sk)| + object!{ + "address" => addr.clone(), + "private_key" => sk.clone(), + } + ).collect::>(); + + let mut all_keys = vec![]; + all_keys.extend_from_slice(&z_keys); + all_keys.extend_from_slice(&t_keys); + + all_keys.into() + } + pub fn do_address(&self) -> json::JsonValue { // Collect z addresses let z_addresses = self.wallet.address.iter().map( |ad| { diff --git a/src/lightwallet/mod.rs b/src/lightwallet/mod.rs index 62a2177..48f0041 100644 --- a/src/lightwallet/mod.rs +++ b/src/lightwallet/mod.rs @@ -3,7 +3,6 @@ use std::io::{self, Read, Write}; use std::cmp; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, RwLock}; -use std::fs::File; use std::io::{Error, ErrorKind}; use log::{info, warn, error}; @@ -16,7 +15,7 @@ use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use pairing::bls12_381::{Bls12}; use zcash_client_backend::{ - encoding::encode_payment_address, + encoding::{encode_payment_address, encode_extended_spending_key}, proto::compact_formats::CompactBlock, welding_rig::scan_block, }; @@ -131,27 +130,6 @@ impl LightWallet { return 2; } - fn get_sapling_params(config: &LightClientConfig) -> Result<(Vec, Vec), Error> { - // Read Sapling Params - let mut sapling_output = vec![]; - let mut f = match File::open(config.get_params_path("sapling-output.params")) { - Ok(file) => file, - Err(_) => return Err(Error::new(ErrorKind::NotFound, - format!("Couldn't read {}", config.get_params_path("sapling-output.params").display()))) - }; - f.read_to_end(&mut sapling_output)?; - - let mut sapling_spend = vec![]; - let mut f = match File::open(config.get_params_path("sapling-spend.params")) { - Ok(file) => file, - Err(_) => return Err(Error::new(ErrorKind::NotFound, - format!("Couldn't read {}", config.get_params_path("sapling-spend.params").display()))) - }; - f.read_to_end(&mut sapling_spend)?; - - Ok((sapling_spend, sapling_output)) - } - fn get_pk_from_bip39seed(bip39seed: &[u8]) -> (ExtendedSpendingKey, ExtendedFullViewingKey, PaymentAddress) { let extsk: ExtendedSpendingKey = ExtendedSpendingKey::from_path( @@ -306,6 +284,23 @@ impl LightWallet { } } + // Get all z-address private keys. Returns a Vector of (address, privatekey) + pub fn get_z_private_keys(&self) -> Vec<(String, String)> { + self.extsks.iter().map(|sk| { + (encode_payment_address(self.config.hrp_sapling_address(), + &ExtendedFullViewingKey::from(sk).default_address().unwrap().1), + encode_extended_spending_key(self.config.hrp_sapling_private_key(), &sk) + ) + }).collect::>() + } + + // Get all t-address private keys. Returns a Vector of (address, secretkey) + pub fn get_t_secret_keys(&self) -> Vec<(String, String)> { + self.tkeys.iter().map(|sk| { + (self.address_from_sk(sk), sk[..].to_base58check(&self.config.base58_secretkey_prefix(), &[0x01])) + }).collect::>() + } + // Clears all the downloaded blocks and resets the state back to the inital block. // After this, the wallet's initial state will need to be set // and the wallet will need to be rescanned @@ -1116,6 +1111,8 @@ impl LightWallet { #[cfg(test)] pub mod tests { use std::convert::TryInto; + use std::fs::File; + use std::io::{Read, Error, ErrorKind}; use ff::{Field, PrimeField, PrimeFieldRepr}; use pairing::bls12_381::Bls12; use rand_core::{RngCore, OsRng}; @@ -1146,6 +1143,27 @@ pub mod tests { use crate::LightClientConfig; use secp256k1::{Secp256k1, key::PublicKey, key::SecretKey}; + fn get_sapling_params(config: &LightClientConfig) -> Result<(Vec, Vec), Error> { + // Read Sapling Params + let mut sapling_output = vec![]; + let mut f = match File::open(config.get_params_path("sapling-output.params")) { + Ok(file) => file, + Err(_) => return Err(Error::new(ErrorKind::NotFound, + format!("Couldn't read {}", config.get_params_path("sapling-output.params").display()))) + }; + f.read_to_end(&mut sapling_output)?; + + let mut sapling_spend = vec![]; + let mut f = match File::open(config.get_params_path("sapling-spend.params")) { + Ok(file) => file, + Err(_) => return Err(Error::new(ErrorKind::NotFound, + format!("Couldn't read {}", config.get_params_path("sapling-spend.params").display()))) + }; + f.read_to_end(&mut sapling_spend)?; + + Ok((sapling_spend, sapling_output)) + } + struct FakeCompactBlock { block: CompactBlock, } @@ -1787,7 +1805,7 @@ pub mod tests { let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); - let (ss, so) = LightWallet::get_sapling_params(&config).unwrap(); + let (ss, so) = get_sapling_params(&config).unwrap(); // Create a tx and send to address let raw_tx = wallet.send_to_address(branch_id, &ss, &so, @@ -1848,7 +1866,7 @@ pub mod tests { let (wallet, config, txid1, block_hash) = get_test_wallet(AMOUNT1); let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); - let (ss, so) = LightWallet::get_sapling_params(&config).unwrap(); + let (ss, so) = get_sapling_params(&config).unwrap(); let taddr = wallet.address_from_sk(&SecretKey::from_slice(&[1u8; 32]).unwrap()); const AMOUNT_SENT: u64 = 30; @@ -1941,7 +1959,7 @@ pub mod tests { let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); - let (ss, so) = LightWallet::get_sapling_params(&config).unwrap(); + let (ss, so) = get_sapling_params(&config).unwrap(); // Create a tx and send to address. This should consume both the UTXO and the note let raw_tx = wallet.send_to_address(branch_id, &ss, &so, @@ -2015,7 +2033,7 @@ pub mod tests { let fee: u64 = DEFAULT_FEE.try_into().unwrap(); let branch_id = u32::from_str_radix("2bb40e60", 16).unwrap(); - let (ss, so) = LightWallet::get_sapling_params(&config).unwrap(); + let (ss, so) = get_sapling_params(&config).unwrap(); // Create a tx and send to address let raw_tx = wallet.send_to_address(branch_id, &ss, &so,