|
|
@ -5,7 +5,7 @@ use rand::{rngs::OsRng, seq::SliceRandom}; |
|
|
|
|
|
|
|
use std::sync::{Arc, RwLock}; |
|
|
|
use std::sync::atomic::{AtomicU64, AtomicI32, AtomicUsize, Ordering}; |
|
|
|
use std::path::Path; |
|
|
|
use std::path::{Path, PathBuf}; |
|
|
|
use std::fs::File; |
|
|
|
use std::collections::HashMap; |
|
|
|
use std::io; |
|
|
@ -38,12 +38,13 @@ pub struct LightClientConfig { |
|
|
|
pub consensus_branch_id : String, |
|
|
|
pub anchor_offset : u32, |
|
|
|
pub no_cert_verification : bool, |
|
|
|
pub data_dir : Option<String> |
|
|
|
} |
|
|
|
|
|
|
|
impl LightClientConfig { |
|
|
|
|
|
|
|
// Create an unconnected (to any server) config to test for local wallet etc...
|
|
|
|
pub fn create_unconnected(chain_name: String) -> LightClientConfig { |
|
|
|
pub fn create_unconnected(chain_name: String, dir: Option<String>) -> LightClientConfig { |
|
|
|
LightClientConfig { |
|
|
|
server : http::Uri::default(), |
|
|
|
chain_name : chain_name, |
|
|
@ -51,6 +52,7 @@ impl LightClientConfig { |
|
|
|
consensus_branch_id : "".to_string(), |
|
|
|
anchor_offset : ANCHOR_OFFSET, |
|
|
|
no_cert_verification : false, |
|
|
|
data_dir : dir, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
@ -67,6 +69,7 @@ impl LightClientConfig { |
|
|
|
consensus_branch_id : info.consensus_branch_id, |
|
|
|
anchor_offset : ANCHOR_OFFSET, |
|
|
|
no_cert_verification : dangerous, |
|
|
|
data_dir : None, |
|
|
|
}; |
|
|
|
|
|
|
|
Ok((config, info.block_height)) |
|
|
@ -74,20 +77,24 @@ impl LightClientConfig { |
|
|
|
|
|
|
|
pub fn get_zcash_data_path(&self) -> Box<Path> { |
|
|
|
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!"); |
|
|
|
zcash_data_location.push("HUSH3"); |
|
|
|
if self.data_dir.is_some() { |
|
|
|
zcash_data_location = PathBuf::from(&self.data_dir.as_ref().unwrap()); |
|
|
|
} else { |
|
|
|
zcash_data_location = dirs::home_dir().expect("Couldn't determine home directory!"); |
|
|
|
zcash_data_location.push(".komodo/HUSH3/"); |
|
|
|
}; |
|
|
|
if cfg!(target_os="macos") || cfg!(target_os="windows") { |
|
|
|
zcash_data_location = dirs::data_dir().expect("Couldn't determine app data directory!"); |
|
|
|
zcash_data_location.push("HUSH3"); |
|
|
|
} else { |
|
|
|
zcash_data_location = dirs::home_dir().expect("Couldn't determine home directory!"); |
|
|
|
zcash_data_location.push(".komodo/HUSH3/"); |
|
|
|
}; |
|
|
|
|
|
|
|
match &self.chain_name[..] { |
|
|
|
"main" => {}, |
|
|
|
"test" => zcash_data_location.push("testnet3"), |
|
|
|
"regtest" => zcash_data_location.push("regtest"), |
|
|
|
c => panic!("Unknown chain {}", c), |
|
|
|
}; |
|
|
|
match &self.chain_name[..] { |
|
|
|
"main" => {}, |
|
|
|
"test" => zcash_data_location.push("testnet3"), |
|
|
|
"regtest" => zcash_data_location.push("regtest"), |
|
|
|
c => panic!("Unknown chain {}", c), |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
zcash_data_location.into_boxed_path() |
|
|
|
} |
|
|
@ -226,8 +233,8 @@ impl LightClient { |
|
|
|
|
|
|
|
/// Method to create a test-only version of the LightClient
|
|
|
|
#[allow(dead_code)] |
|
|
|
fn unconnected(seed_phrase: String) -> io::Result<Self> { |
|
|
|
let config = LightClientConfig::create_unconnected("test".to_string()); |
|
|
|
fn unconnected(seed_phrase: String, dir: Option<String>) -> io::Result<Self> { |
|
|
|
let config = LightClientConfig::create_unconnected("test".to_string(), dir); |
|
|
|
let mut l = LightClient { |
|
|
|
wallet : Arc::new(RwLock::new(LightWallet::new(Some(seed_phrase), &config, 0)?)), |
|
|
|
config : config.clone(), |
|
|
@ -320,6 +327,47 @@ impl LightClient { |
|
|
|
Ok(lc) |
|
|
|
} |
|
|
|
|
|
|
|
pub fn attempt_recover_seed(config: &LightClientConfig) -> Result<String, String> { |
|
|
|
use std::io::prelude::*; |
|
|
|
use byteorder::{LittleEndian, ReadBytesExt,}; |
|
|
|
use bip39::{Mnemonic, Language}; |
|
|
|
use zcash_primitives::serialize::Vector; |
|
|
|
|
|
|
|
let mut reader = BufReader::new(File::open(config.get_wallet_path()).unwrap()); |
|
|
|
let version = reader.read_u64::<LittleEndian>().unwrap(); |
|
|
|
println!("Reading wallet version {}", version); |
|
|
|
|
|
|
|
let encrypted = if version >= 4 { |
|
|
|
reader.read_u8().unwrap() > 0 |
|
|
|
} else { |
|
|
|
false |
|
|
|
}; |
|
|
|
|
|
|
|
if encrypted { |
|
|
|
return Err("The wallet is encrypted!".to_string()); |
|
|
|
} |
|
|
|
|
|
|
|
let mut enc_seed = [0u8; 48]; |
|
|
|
if version >= 4 { |
|
|
|
reader.read_exact(&mut enc_seed).unwrap(); |
|
|
|
} |
|
|
|
|
|
|
|
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 = Mnemonic::from_entropy(&seed_bytes, Language::English,).unwrap().phrase().to_string(); |
|
|
|
|
|
|
|
Ok(phrase) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
pub fn last_scanned_height(&self) -> u64 { |
|
|
|
self.wallet.read().unwrap().last_scanned_height() as u64 |
|
|
|
} |
|
|
@ -764,6 +812,8 @@ impl LightClient { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 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); |
|
|
|
match block { |
|
|
@ -911,10 +961,11 @@ impl LightClient { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)] |
|
|
|
pub mod tests { |
|
|
|
use lazy_static::lazy_static; |
|
|
|
//use super::LightClient;
|
|
|
|
use tempdir::TempDir; |
|
|
|
use super::{LightClient, LightClientConfig}; |
|
|
|
|
|
|
|
lazy_static!{ |
|
|
|
static ref TEST_SEED: String = "youth strong sweet gorilla hammer unhappy congress stamp left stereo riot salute road tag clean toilet artefact fork certain leopard entire civil degree wonder".to_string(); |
|
|
@ -922,7 +973,7 @@ pub mod tests { |
|
|
|
|
|
|
|
#[test] |
|
|
|
pub fn test_encrypt_decrypt() { |
|
|
|
let lc = super::LightClient::unconnected(TEST_SEED.to_string()).unwrap(); |
|
|
|
let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); |
|
|
|
|
|
|
|
assert!(!lc.do_export(None).is_err()); |
|
|
|
assert!(!lc.do_new_address("z").is_err()); |
|
|
@ -947,7 +998,7 @@ pub mod tests { |
|
|
|
|
|
|
|
#[test] |
|
|
|
pub fn test_addresses() { |
|
|
|
let lc = super::LightClient::unconnected(TEST_SEED.to_string()).unwrap(); |
|
|
|
let lc = super::LightClient::unconnected(TEST_SEED.to_string(), None).unwrap(); |
|
|
|
|
|
|
|
// Add new z and t addresses
|
|
|
|
|
|
|
@ -966,5 +1017,72 @@ pub mod tests { |
|
|
|
assert_eq!(addresses["t_addresses"][2], taddr2); |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
pub fn test_wallet_creation() { |
|
|
|
// Create a new tmp director
|
|
|
|
{ |
|
|
|
let tmp = TempDir::new("lctest").unwrap(); |
|
|
|
let dir_name = tmp.path().to_str().map(|s| s.to_string()); |
|
|
|
|
|
|
|
// A lightclient to a new, empty directory works.
|
|
|
|
let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); |
|
|
|
let lc = LightClient::new(&config, 0).unwrap(); |
|
|
|
let seed = lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string(); |
|
|
|
lc.do_save().unwrap(); |
|
|
|
|
|
|
|
// Doing another new will fail, because the wallet file now already exists
|
|
|
|
assert!(LightClient::new(&config, 0).is_err()); |
|
|
|
|
|
|
|
// new_from_phrase will not work either, again, because wallet file exists
|
|
|
|
assert!(LightClient::new_from_phrase(TEST_SEED.to_string(), &config, 0).is_err()); |
|
|
|
|
|
|
|
// Creating a lightclient to the same dir without a seed should re-read the same wallet
|
|
|
|
// file and therefore the same seed phrase
|
|
|
|
let lc2 = LightClient::read_from_disk(&config).unwrap(); |
|
|
|
assert_eq!(seed, lc2.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string()); |
|
|
|
} |
|
|
|
|
|
|
|
// Now, get a new directory, and try to read from phrase
|
|
|
|
{ |
|
|
|
let tmp = TempDir::new("lctest").unwrap(); |
|
|
|
let dir_name = tmp.path().to_str().map(|s| s.to_string()); |
|
|
|
|
|
|
|
let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); |
|
|
|
|
|
|
|
// read_from_disk will fail, because the dir doesn't exist
|
|
|
|
assert!(LightClient::read_from_disk(&config).is_err()); |
|
|
|
|
|
|
|
// New from phrase should work becase a file doesn't exist already
|
|
|
|
let lc = LightClient::new_from_phrase(TEST_SEED.to_string(), &config, 0).unwrap(); |
|
|
|
assert_eq!(TEST_SEED.to_string(), lc.do_seed_phrase().unwrap()["seed"].as_str().unwrap().to_string()); |
|
|
|
lc.do_save().unwrap(); |
|
|
|
|
|
|
|
// Now a new will fail because wallet exists
|
|
|
|
assert!(LightClient::new(&config, 0).is_err()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
#[test] |
|
|
|
pub fn test_recover_seed() { |
|
|
|
// Create a new tmp director
|
|
|
|
{ |
|
|
|
let tmp = TempDir::new("lctest").unwrap(); |
|
|
|
let dir_name = tmp.path().to_str().map(|s| s.to_string()); |
|
|
|
|
|
|
|
// A lightclient to a new, empty directory works.
|
|
|
|
let config = LightClientConfig::create_unconnected("test".to_string(), dir_name); |
|
|
|
let lc = LightClient::new(&config, 0).unwrap(); |
|
|
|
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()); |
|
|
|
|
|
|
|
// Now encrypt and save the file
|
|
|
|
lc.wallet.write().unwrap().encrypt("password".to_string()).unwrap(); |
|
|
|
lc.do_save().unwrap(); |
|
|
|
|
|
|
|
assert!(LightClient::attempt_recover_seed(&config).is_err()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |