diff --git a/Cargo.lock b/Cargo.lock index afbaa72ce..d572dcf79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,7 @@ dependencies = [ "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1750,7 +1751,7 @@ dependencies = [ [[package]] name = "parity-ui-precompiled" version = "1.4.0" -source = "git+https://github.com/ethcore/js-precompiled.git#9651995aa0fa718b9b9b58f1c7281900643bdf8f" +source = "git+https://github.com/ethcore/js-precompiled.git#a44b1cb29b80e4d3372ee47494499a61db7a8116" dependencies = [ "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index f0978f420..8420c5459 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ serde = "0.9" serde_json = "0.9" app_dirs = "1.1.1" fdlimit = "0.1" +ws2_32-sys = "0.2" hyper = { default-features = false, git = "https://github.com/paritytech/hyper" } ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } diff --git a/ethcore/res/validator_multi.json b/ethcore/res/validator_multi.json new file mode 100644 index 000000000..5d51b73da --- /dev/null +++ b/ethcore/res/validator_multi.json @@ -0,0 +1,42 @@ +{ + "name": "TestMutiValidator", + "engine": { + "basicAuthority": { + "params": { + "gasLimitBoundDivisor": "0x0400", + "durationLimit": "0x0d", + "validators": { + "multi": { + "0": { "list": ["0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"] }, + "2": { "list": ["0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"] } + } + } + } + } + }, + "params": { + "accountStartNonce": "0x0", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x69" + }, + "genesis": { + "seal": { + "generic": "0xc180" + }, + "difficulty": "0x20000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x", + "gasLimit": "0x2fefd8" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1": { "balance": "99999999999999999999999" }, + "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e": { "balance": "99999999999999999999999" } + } +} diff --git a/ethcore/src/account_provider/mod.rs b/ethcore/src/account_provider/mod.rs index f9b7727db..0ecbf3b17 100755 --- a/ethcore/src/account_provider/mod.rs +++ b/ethcore/src/account_provider/mod.rs @@ -24,14 +24,16 @@ use std::fmt; use std::collections::{HashMap, HashSet}; use std::time::{Instant, Duration}; use util::{RwLock}; -use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, - random_string, SecretVaultRef, StoreAccountRef}; +use ethstore::{ + SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, + random_string, SecretVaultRef, StoreAccountRef, +}; use ethstore::dir::MemoryDirectory; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethjson::misc::AccountMeta; use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath}; pub use ethstore::ethkey::Signature; -pub use ethstore::{Derivation, IndexDerivation}; +pub use ethstore::{Derivation, IndexDerivation, KeyFile}; /// Type of unlock. #[derive(Clone)] @@ -500,6 +502,11 @@ impl AccountProvider { self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password) } + /// Exports an account for given address. + pub fn export_account(&self, address: &Address, password: String) -> Result { + self.sstore.export_account(&self.sstore.account_ref(address)?, &password) + } + /// Helper method used for unlocking accounts. fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> { // verify password by signing dump message diff --git a/ethcore/src/engines/authority_round.rs b/ethcore/src/engines/authority_round.rs index 2a18c748d..4f823fa8d 100644 --- a/ethcore/src/engines/authority_round.rs +++ b/ethcore/src/engines/authority_round.rs @@ -82,7 +82,7 @@ pub struct AuthorityRound { proposed: AtomicBool, client: RwLock>>, signer: EngineSigner, - validators: Box, + validators: Box, /// Is this Engine just for testing (prevents step calibration). calibrate_step: bool, } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index d8a1df947..e5a53d4e9 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -58,7 +58,7 @@ pub struct BasicAuthority { gas_limit_bound_divisor: U256, builtins: BTreeMap, signer: EngineSigner, - validators: Box, + validators: Box, } impl BasicAuthority { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 46e67a2a8..464e102de 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -98,7 +98,7 @@ pub struct Tendermint { /// Hash of the proposal parent block. proposal_parent: RwLock, /// Set used to determine the current validators. - validators: Box, + validators: Box, } impl Tendermint { diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs index 3e86c357f..cbbedfb33 100644 --- a/ethcore/src/engines/validator_set/mod.rs +++ b/ethcore/src/engines/validator_set/mod.rs @@ -19,6 +19,7 @@ mod simple_list; mod safe_contract; mod contract; +mod multi; use std::sync::Weak; use util::{Address, H256}; @@ -27,23 +28,27 @@ use client::Client; use self::simple_list::SimpleList; use self::contract::ValidatorContract; use self::safe_contract::ValidatorSafeContract; +use self::multi::Multi; /// Creates a validator set from spec. -pub fn new_validator_set(spec: ValidatorSpec) -> Box { +pub fn new_validator_set(spec: ValidatorSpec) -> Box { match spec { ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), ValidatorSpec::Contract(address) => Box::new(ValidatorContract::new(address.into())), + ValidatorSpec::Multi(sequence) => Box::new( + Multi::new(sequence.into_iter().map(|(block, set)| (block.into(), new_validator_set(set))).collect()) + ), } } -pub trait ValidatorSet { +pub trait ValidatorSet: Send + Sync { /// Checks if a given address is a validator. - fn contains(&self, bh: &H256, address: &Address) -> bool; + fn contains(&self, parent_block_hash: &H256, address: &Address) -> bool; /// Draws an validator nonce modulo number of validators. - fn get(&self, bh: &H256, nonce: usize) -> Address; + fn get(&self, parent_block_hash: &H256, nonce: usize) -> Address; /// Returns the current number of validators. - fn count(&self, bh: &H256) -> usize; + fn count(&self, parent_block_hash: &H256) -> usize; /// Notifies about malicious behaviour. fn report_malicious(&self, _validator: &Address) {} /// Notifies about benign misbehaviour. diff --git a/ethcore/src/engines/validator_set/multi.rs b/ethcore/src/engines/validator_set/multi.rs new file mode 100644 index 000000000..5027f23cd --- /dev/null +++ b/ethcore/src/engines/validator_set/multi.rs @@ -0,0 +1,158 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +/// Validator set changing at fork blocks. + +use std::collections::BTreeMap; +use std::sync::Weak; +use util::{H256, Address, RwLock}; +use ids::BlockId; +use header::BlockNumber; +use client::{Client, BlockChainClient}; +use super::ValidatorSet; + +type BlockNumberLookup = Box Result + Send + Sync + 'static>; + +pub struct Multi { + sets: BTreeMap>, + block_number: RwLock, +} + +impl Multi { + pub fn new(set_map: BTreeMap>) -> Self { + assert!(set_map.get(&0u64).is_some(), "ValidatorSet has to be specified from block 0."); + Multi { + sets: set_map, + block_number: RwLock::new(Box::new(move |_| Err("No client!".into()))), + } + } + + fn correct_set(&self, bh: &H256) -> Option<&Box> { + match self + .block_number + .read()(bh) + .map(|parent_block| self + .sets + .iter() + .rev() + .find(|&(block, _)| *block <= parent_block + 1) + .expect("constructor validation ensures that there is at least one validator set for block 0; + block 0 is less than any uint; + qed") + ) { + Ok((block, set)) => { + trace!(target: "engine", "Multi ValidatorSet retrieved for block {}.", block); + Some(set) + }, + Err(e) => { + debug!(target: "engine", "ValidatorSet could not be recovered: {}", e); + None + }, + } + } +} + +impl ValidatorSet for Multi { + fn contains(&self, bh: &H256, address: &Address) -> bool { + self.correct_set(bh).map_or(false, |set| set.contains(bh, address)) + } + + fn get(&self, bh: &H256, nonce: usize) -> Address { + self.correct_set(bh).map_or_else(Default::default, |set| set.get(bh, nonce)) + } + + fn count(&self, bh: &H256) -> usize { + self.correct_set(bh).map_or_else(usize::max_value, |set| set.count(bh)) + } + + fn report_malicious(&self, validator: &Address) { + for set in self.sets.values() { + set.report_malicious(validator); + } + } + + fn report_benign(&self, validator: &Address) { + for set in self.sets.values() { + set.report_benign(validator); + } + } + + fn register_contract(&self, client: Weak) { + for set in self.sets.values() { + set.register_contract(client.clone()); + } + *self.block_number.write() = Box::new(move |hash| client + .upgrade() + .ok_or("No client!".into()) + .and_then(|c| c.block_number(BlockId::Hash(*hash)).ok_or("Unknown block".into()))); + } +} + +#[cfg(test)] +mod tests { + use util::*; + use types::ids::BlockId; + use spec::Spec; + use account_provider::AccountProvider; + use client::{BlockChainClient, EngineClient}; + use ethkey::Secret; + use miner::MinerService; + use tests::helpers::{generate_dummy_client_with_spec_and_accounts, generate_dummy_client_with_spec_and_data}; + + #[test] + fn uses_current_set() { + ::env_logger::init().unwrap(); + let tap = Arc::new(AccountProvider::transient_provider()); + let s0 = Secret::from_slice(&"0".sha3()).unwrap(); + let v0 = tap.insert_account(s0.clone(), "").unwrap(); + let v1 = tap.insert_account(Secret::from_slice(&"1".sha3()).unwrap(), "").unwrap(); + let client = generate_dummy_client_with_spec_and_accounts(Spec::new_validator_multi, Some(tap)); + client.engine().register_client(Arc::downgrade(&client)); + + // Make sure txs go through. + client.miner().set_gas_floor_target(1_000_000.into()); + + // Wrong signer for the first block. + client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 0); + // Right signer for the first block. + client.miner().set_engine_signer(v0, "".into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 1); + // This time v0 is wrong. + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 1); + client.miner().set_engine_signer(v1, "".into()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 2); + // v1 is still good. + client.transact_contract(Default::default(), Default::default()).unwrap(); + client.update_sealing(); + assert_eq!(client.chain_info().best_block_number, 3); + + // Check syncing. + let sync_client = generate_dummy_client_with_spec_and_data(Spec::new_validator_multi, 0, 0, &[]); + sync_client.engine().register_client(Arc::downgrade(&sync_client)); + for i in 1..4 { + sync_client.import_block(client.block(BlockId::Number(i)).unwrap().into_inner()).unwrap(); + } + sync_client.flush_queue(); + assert_eq!(sync_client.chain_info().best_block_number, 3); + } +} diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index 21c07c9a3..455d0745f 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -360,6 +360,10 @@ impl Spec { /// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf". /// Validator can be removed with `reportMalicious`. pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") } + + /// Create a new Spec with BasicAuthority which uses multiple validator sets changing with height. + /// Account with secrets "0".sha3() is the validator for block 1 and with "1".sha3() onwards. + pub fn new_validator_multi() -> Self { load_bundled!("validator_multi") } } #[cfg(test)] diff --git a/ethstore/src/account/safe_account.rs b/ethstore/src/account/safe_account.rs index d628b56ac..e0512fe8d 100755 --- a/ethstore/src/account/safe_account.rs +++ b/ethstore/src/account/safe_account.rs @@ -19,14 +19,22 @@ use {json, Error, crypto}; use account::Version; use super::crypto::Crypto; +/// Account representation. #[derive(Debug, PartialEq, Clone)] pub struct SafeAccount { + /// Account ID pub id: [u8; 16], + /// Account version pub version: Version, + /// Account address pub address: Address, + /// Account private key derivation definition. pub crypto: Crypto, + /// Account filename pub filename: Option, + /// Account name pub name: String, + /// Account metadata pub meta: String, } @@ -44,6 +52,7 @@ impl Into for SafeAccount { } impl SafeAccount { + /// Create a new account pub fn create( keypair: &KeyPair, id: [u8; 16], @@ -114,21 +123,25 @@ impl SafeAccount { }) } + /// Sign a message. pub fn sign(&self, password: &str, message: &Message) -> Result { let secret = self.crypto.secret(password)?; sign(&secret, message).map_err(From::from) } + /// Decrypt a message. pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { let secret = self.crypto.secret(password)?; crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from) } + /// Derive public key. pub fn public(&self, password: &str) -> Result { let secret = self.crypto.secret(password)?; Ok(KeyPair::from_secret(secret)?.public().clone()) } + /// Change account's password. pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result { let secret = self.crypto.secret(old_password)?; let result = SafeAccount { @@ -143,6 +156,7 @@ impl SafeAccount { Ok(result) } + /// Check if password matches the account. pub fn check_password(&self, password: &str) -> bool { self.crypto.secret(password).is_ok() } diff --git a/ethstore/src/bin/ethstore.rs b/ethstore/src/bin/ethstore.rs index 20411a629..3e8df3a35 100644 --- a/ethstore/src/bin/ethstore.rs +++ b/ethstore/src/bin/ethstore.rs @@ -22,7 +22,7 @@ use std::{env, process, fs}; use std::io::Read; use docopt::Docopt; use ethstore::ethkey::Address; -use ethstore::dir::{KeyDirectory, ParityDirectory, RootDiskDirectory, GethDirectory, DirectoryType}; +use ethstore::dir::{paths, KeyDirectory, RootDiskDirectory}; use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, Error, PresaleWallet, SecretVaultRef, StoreAccountRef}; @@ -49,14 +49,14 @@ Usage: Options: -h, --help Display this message and exit. --dir DIR Specify the secret store directory. It may be either - parity, parity-test, geth, geth-test + parity, parity-(chain), geth, geth-test or a path [default: parity]. --vault VAULT Specify vault to use in this operation. --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note that this option is required when vault option is set. Otherwise it is ignored. --src DIR Specify import source. It may be either - parity, parity-test, get, geth-test + parity, parity-(chain), get, geth-test or a path [default: geth]. Commands: @@ -116,10 +116,13 @@ fn main() { fn key_dir(location: &str) -> Result, Error> { let dir: Box = match location { - "parity" => Box::new(ParityDirectory::create(DirectoryType::Main)?), - "parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?), - "geth" => Box::new(GethDirectory::create(DirectoryType::Main)?), - "geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?), + "geth" => Box::new(RootDiskDirectory::create(paths::geth(false))?), + "geth-test" => Box::new(RootDiskDirectory::create(paths::geth(true))?), + path if path.starts_with("parity") => { + let chain = path.split('-').nth(1).unwrap_or("ethereum"); + let path = paths::parity(chain); + Box::new(RootDiskDirectory::create(path)?) + }, path => Box::new(RootDiskDirectory::create(path)?), }; @@ -254,4 +257,3 @@ fn execute(command: I) -> Result where I: IntoIterator. - -use std::env; -use std::path::PathBuf; -use {SafeAccount, Error}; -use super::{KeyDirectory, RootDiskDirectory, DirectoryType}; - -#[cfg(target_os = "macos")] -fn geth_dir_path() -> PathBuf { - let mut home = env::home_dir().expect("Failed to get home dir"); - home.push("Library"); - home.push("Ethereum"); - home -} - -#[cfg(windows)] -/// Default path for ethereum installation on Windows -pub fn geth_dir_path() -> PathBuf { - let mut home = env::home_dir().expect("Failed to get home dir"); - home.push("AppData"); - home.push("Roaming"); - home.push("Ethereum"); - home -} - -#[cfg(not(any(target_os = "macos", windows)))] -/// Default path for ethereum installation on posix system which is not Mac OS -pub fn geth_dir_path() -> PathBuf { - let mut home = env::home_dir().expect("Failed to get home dir"); - home.push(".ethereum"); - home -} - -fn geth_keystore(t: DirectoryType) -> PathBuf { - let mut dir = geth_dir_path(); - match t { - DirectoryType::Testnet => { - dir.push("testnet"); - dir.push("keystore"); - }, - DirectoryType::Main => { - dir.push("keystore"); - } - } - dir -} - -pub struct GethDirectory { - dir: RootDiskDirectory, -} - -impl GethDirectory { - pub fn create(t: DirectoryType) -> Result { - let result = GethDirectory { - dir: RootDiskDirectory::create(geth_keystore(t))?, - }; - - Ok(result) - } - - pub fn open(t: DirectoryType) -> Self { - GethDirectory { - dir: RootDiskDirectory::at(geth_keystore(t)), - } - } -} - -impl KeyDirectory for GethDirectory { - fn load(&self) -> Result, Error> { - self.dir.load() - } - - fn insert(&self, account: SafeAccount) -> Result { - self.dir.insert(account) - } - - fn update(&self, account: SafeAccount) -> Result { - self.dir.update(account) - } - - fn remove(&self, account: &SafeAccount) -> Result<(), Error> { - self.dir.remove(account) - } - - fn unique_repr(&self) -> Result { - self.dir.unique_repr() - } -} diff --git a/ethstore/src/dir/memory.rs b/ethstore/src/dir/memory.rs index 955afc5b0..b8c2ad9ff 100644 --- a/ethstore/src/dir/memory.rs +++ b/ethstore/src/dir/memory.rs @@ -22,6 +22,7 @@ use ethkey::Address; use {SafeAccount, Error}; use super::KeyDirectory; +/// Accounts in-memory storage. #[derive(Default)] pub struct MemoryDirectory { accounts: RwLock>>, diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index 83e978707..fb22c06ee 100755 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -14,19 +14,15 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Accounts Directory + use std::path::{PathBuf}; use {SafeAccount, Error}; mod disk; -mod geth; mod memory; -mod parity; mod vault; - -pub enum DirectoryType { - Testnet, - Main, -} +pub mod paths; /// `VaultKeyDirectory::set_key` error #[derive(Debug)] @@ -54,7 +50,7 @@ pub trait KeyDirectory: Send + Sync { fn load(&self) -> Result, Error>; /// Insert new key to directory fn insert(&self, account: SafeAccount) -> Result; - //// Update key in directory + /// Update key in the directory fn update(&self, account: SafeAccount) -> Result; /// Remove key from directory fn remove(&self, account: &SafeAccount) -> Result<(), Error>; @@ -95,9 +91,7 @@ pub trait VaultKeyDirectory: KeyDirectory { } pub use self::disk::RootDiskDirectory; -pub use self::geth::GethDirectory; pub use self::memory::MemoryDirectory; -pub use self::parity::ParityDirectory; pub use self::vault::VaultDiskDirectory; impl VaultKey { diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs deleted file mode 100755 index df03260d3..000000000 --- a/ethstore/src/dir/parity.rs +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2015-2017 Parity Technologies (UK) Ltd. -// This file is part of Parity. - -// Parity is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Parity is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Parity. If not, see . - -use std::env; -use std::path::PathBuf; -use {SafeAccount, Error}; -use super::{KeyDirectory, RootDiskDirectory, DirectoryType}; - -fn parity_dir_path() -> PathBuf { - let mut home = env::home_dir().expect("Failed to get home dir"); - home.push(".parity"); - home -} - -fn parity_keystore(t: DirectoryType) -> PathBuf { - let mut dir = parity_dir_path(); - match t { - DirectoryType::Testnet => { - dir.push("testnet_keys"); - }, - DirectoryType::Main => { - dir.push("keys"); - } - } - dir -} - -pub struct ParityDirectory { - dir: RootDiskDirectory, -} - -impl ParityDirectory { - pub fn create(t: DirectoryType) -> Result { - let result = ParityDirectory { - dir: RootDiskDirectory::create(parity_keystore(t))?, - }; - - Ok(result) - } - - pub fn open(t: DirectoryType) -> Self { - ParityDirectory { - dir: RootDiskDirectory::at(parity_keystore(t)), - } - } -} - -impl KeyDirectory for ParityDirectory { - fn load(&self) -> Result, Error> { - self.dir.load() - } - - fn insert(&self, account: SafeAccount) -> Result { - self.dir.insert(account) - } - - fn update(&self, account: SafeAccount) -> Result { - self.dir.update(account) - } - - fn remove(&self, account: &SafeAccount) -> Result<(), Error> { - self.dir.remove(account) - } - - fn unique_repr(&self) -> Result { - self.dir.unique_repr() - } -} diff --git a/ethstore/src/dir/paths.rs b/ethstore/src/dir/paths.rs new file mode 100644 index 000000000..db3178cff --- /dev/null +++ b/ethstore/src/dir/paths.rs @@ -0,0 +1,96 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +//! Common tools paths. + +use std::env; +use std::path::PathBuf; + +fn home() -> PathBuf { + env::home_dir().expect("Failed to get home dir") +} + +/// Geth path +pub fn geth(testnet: bool) -> PathBuf { + let mut base = geth_base(); + if testnet { + base.push("testnet"); + } + base.push("keystore"); + base +} + +/// Parity path for specific chain +pub fn parity(chain: &str) -> PathBuf { + let mut base = parity_base(); + base.push(chain); + base +} + +#[cfg(target_os = "macos")] +fn parity_base() -> PathBuf { + let mut home = home(); + home.push("Library"); + home.push("Application Support"); + home.push("io.parity.ethereum"); + home.push("keys"); + home +} + +#[cfg(windows)] +fn parity_base() -> PathBuf { + let mut home = home(); + home.push("AppData"); + home.push("Roaming"); + home.push("Parity"); + home.push("Ethereum"); + home.push("keys"); + home +} + +#[cfg(not(any(target_os = "macos", windows)))] +fn parity_base() -> PathBuf { + let mut home = home(); + home.push(".local"); + home.push("share"); + home.push("io.parity.ethereum"); + home.push("keys"); + home +} + +#[cfg(target_os = "macos")] +fn geth_base() -> PathBuf { + let mut home = home(); + home.push("Library"); + home.push("Ethereum"); + home +} + +#[cfg(windows)] +fn geth_base() -> PathBuf { + let mut home = home(); + home.push("AppData"); + home.push("Roaming"); + home.push("Ethereum"); + home +} + +#[cfg(not(any(target_os = "macos", windows)))] +fn geth_base() -> PathBuf { + let mut home = home(); + home.push(".ethereum"); + home +} diff --git a/ethstore/src/error.rs b/ethstore/src/error.rs index 8a2eb5e8b..f7e0b0bfa 100755 --- a/ethstore/src/error.rs +++ b/ethstore/src/error.rs @@ -20,23 +20,40 @@ use ethkey::Error as EthKeyError; use crypto::Error as EthCryptoError; use ethkey::DerivationError; +/// Account-related errors. #[derive(Debug)] pub enum Error { + /// IO error Io(IoError), + /// Invalid Password InvalidPassword, + /// Account's secret is invalid. InvalidSecret, + /// Invalid Vault Crypto meta. InvalidCryptoMeta, + /// Invalid Account. InvalidAccount, + /// Invalid Message. InvalidMessage, + /// Invalid Key File InvalidKeyFile(String), + /// Vaults are not supported. VaultsAreNotSupported, + /// Unsupported vault UnsupportedVault, + /// Invalid vault name InvalidVaultName, + /// Vault not found VaultNotFound, + /// Account creation failed. CreationFailed, + /// `EthKey` error EthKey(EthKeyError), + /// `EthCrypto` error EthCrypto(EthCryptoError), + /// Derivation error Derivation(DerivationError), + /// Custom error Custom(String), } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index cacb6054f..5fb76791e 100755 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -25,18 +25,21 @@ use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, Extende use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use account::SafeAccount; use presale::PresaleWallet; -use json::{self, Uuid}; +use json::{self, Uuid, OpaqueKeyFile}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; +/// Accounts store. pub struct EthStore { store: EthMultiStore, } impl EthStore { + /// Open a new accounts store with given key directory backend. pub fn open(directory: Box) -> Result { Self::open_with_iterations(directory, KEY_ITERATIONS as u32) } + /// Open a new account store with given key directory backend and custom number of iterations. pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { Ok(EthStore { store: EthMultiStore::open_with_iterations(directory, iterations)?, @@ -44,7 +47,7 @@ impl EthStore { } fn get(&self, account: &StoreAccountRef) -> Result { - let mut accounts = self.store.get(account)?.into_iter(); + let mut accounts = self.store.get_accounts(account)?.into_iter(); accounts.next().ok_or(Error::InvalidAccount) } } @@ -76,6 +79,10 @@ impl SimpleSecretStore for EthStore { self.store.change_password(account, old_password, new_password) } + fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result { + self.store.export_account(account, password) + } + fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> { self.store.remove_account(account, password) } @@ -234,11 +241,12 @@ pub struct EthMultiStore { } impl EthMultiStore { - + /// Open new multi-accounts store with given key directory backend. pub fn open(directory: Box) -> Result { Self::open_with_iterations(directory, KEY_ITERATIONS as u32) } + /// Open new multi-accounts store with given key directory backend and custom number of iterations for new keys. pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { let store = EthMultiStore { dir: directory, @@ -259,7 +267,7 @@ impl EthMultiStore { } self.reload_accounts()?; *last_dir_hash = dir_hash; - Ok(()) + Ok(()) } fn reload_accounts(&self) -> Result<(), Error> { @@ -287,7 +295,7 @@ impl EthMultiStore { Ok(()) } - fn get(&self, account: &StoreAccountRef) -> Result, Error> { + fn get_accounts(&self, account: &StoreAccountRef) -> Result, Error> { { let cache = self.cache.read(); if let Some(accounts) = cache.get(account) { @@ -307,6 +315,15 @@ impl EthMultiStore { } } + fn get_matching(&self, account: &StoreAccountRef, password: &str) -> Result, Error> { + let accounts = self.get_accounts(account)?; + + Ok(accounts.into_iter() + .filter(|acc| acc.check_password(password)) + .collect() + ) + } + fn import(&self, vault: SecretVaultRef, account: SafeAccount) -> Result { // save to file let account = match vault { @@ -398,12 +415,8 @@ impl SimpleSecretStore for EthMultiStore { fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result { - let accounts = self.get(account_ref)?; + let accounts = self.get_matching(account_ref, password)?; for account in accounts { - // Skip if password is invalid - if !account.check_password(password) { - continue; - } let extended = self.generate(account.crypto.secret(password)?, derivation)?; return self.insert_account(vault, extended.secret().as_raw().clone(), password); } @@ -413,14 +426,9 @@ impl SimpleSecretStore for EthMultiStore { fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result { - let accounts = self.get(&account_ref)?; + let accounts = self.get_matching(&account_ref, password)?; for account in accounts { - // Skip if password is invalid - if !account.check_password(password) { - continue; - } let extended = self.generate(account.crypto.secret(password)?, derivation)?; - return Ok(ethkey::public_to_address(extended.public().public())); } Err(Error::InvalidPassword) @@ -429,18 +437,13 @@ impl SimpleSecretStore for EthMultiStore { fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result { - let accounts = self.get(&account_ref)?; + let accounts = self.get_matching(&account_ref, password)?; for account in accounts { - // Skip if password is invalid - if !account.check_password(password) { - continue; - } let extended = self.generate(account.crypto.secret(password)?, derivation)?; let secret = extended.secret().as_raw(); return Ok(ethkey::sign(&secret, message)?) } Err(Error::InvalidPassword) - } fn account_ref(&self, address: &Address) -> Result { @@ -457,47 +460,47 @@ impl SimpleSecretStore for EthMultiStore { } fn remove_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result<(), Error> { - let accounts = self.get(account_ref)?; + let accounts = self.get_matching(account_ref, password)?; for account in accounts { - // Skip if password is invalid - if !account.check_password(password) { - continue; - } - return self.remove_safe_account(account_ref, &account); } + Err(Error::InvalidPassword) } fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> { - let accounts = self.get(account_ref)?; + let accounts = self.get_matching(account_ref, old_password)?; + + if accounts.is_empty() { + return Err(Error::InvalidPassword); + } for account in accounts { // Change password let new_account = account.change_password(old_password, new_password, self.iterations)?; self.update(account_ref, account, new_account)?; } + Ok(()) } - fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result { - let accounts = self.get(account)?; - for account in accounts { - if account.check_password(password) { - return account.sign(password, message); - } - } + fn export_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result { + self.get_matching(account_ref, password)?.into_iter().nth(0).map(Into::into).ok_or(Error::InvalidPassword) + } + fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result { + let accounts = self.get_matching(account, password)?; + for account in accounts { + return account.sign(password, message); + } Err(Error::InvalidPassword) } fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { - let accounts = self.get(account)?; + let accounts = self.get_matching(account, password)?; for account in accounts { - if account.check_password(password) { - return account.decrypt(password, shared_mac, message); - } + return account.decrypt(password, shared_mac, message); } Err(Error::InvalidPassword) } @@ -586,7 +589,7 @@ impl SimpleSecretStore for EthMultiStore { return Ok(account_ref); } - let account = self.get(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?; + let account = self.get_accounts(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?; let new_account_ref = self.import(vault, account.clone())?; self.remove_safe_account(&account_ref, &account)?; self.reload_accounts()?; @@ -1032,4 +1035,18 @@ mod tests { // then assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned()); } + + #[test] + fn should_export_account() { + // given + let store = store(); + let keypair = keypair(); + let address = store.insert_account(SecretVaultRef::Root, keypair.secret().clone(), "test").unwrap(); + + // when + let exported = store.export_account(&address, "test"); + + // then + assert!(exported.is_ok(), "Should export single account: {:?}", exported); + } } diff --git a/ethstore/src/import.rs b/ethstore/src/import.rs index 0090631bd..b7497c9ff 100644 --- a/ethstore/src/import.rs +++ b/ethstore/src/import.rs @@ -16,9 +16,10 @@ use std::collections::HashSet; use ethkey::Address; -use dir::{GethDirectory, KeyDirectory, DirectoryType}; +use dir::{paths, KeyDirectory, RootDiskDirectory}; use Error; +/// Import all accounts from one directory to the other. pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result, Error> { let accounts = src.load()?; let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); @@ -34,27 +35,15 @@ pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result Vec
{ - let t = if testnet { - DirectoryType::Testnet - } else { - DirectoryType::Main - }; - - GethDirectory::open(t) + RootDiskDirectory::at(paths::geth(testnet)) .load() .map(|d| d.into_iter().map(|a| a.address).collect()) .unwrap_or_else(|_| Vec::new()) } -/// Import specific `desired` accounts from the Geth keystore into `dst`. +/// Import specific `desired` accounts from the Geth keystore into `dst`. pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet
, testnet: bool) -> Result, Error> { - let t = if testnet { - DirectoryType::Testnet - } else { - DirectoryType::Main - }; - - let src = GethDirectory::open(t); + let src = RootDiskDirectory::at(paths::geth(testnet)); let accounts = src.load()?; let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::>(); diff --git a/ethstore/src/json/key_file.rs b/ethstore/src/json/key_file.rs index 21711df8f..a1c20acf2 100644 --- a/ethstore/src/json/key_file.rs +++ b/ethstore/src/json/key_file.rs @@ -16,11 +16,31 @@ use std::fmt; use std::io::{Read, Write}; -use serde::{Deserialize, Deserializer}; +use serde::{Serialize, Serializer, Deserialize, Deserializer}; use serde::de::{Error, Visitor, MapVisitor}; use serde_json; use super::{Uuid, Version, Crypto, H160}; +/// Public opaque type representing serializable `KeyFile`. +#[derive(Debug, PartialEq)] +pub struct OpaqueKeyFile { + key_file: KeyFile +} + +impl Serialize for OpaqueKeyFile { + fn serialize(&self, serializer: S) -> Result where + S: Serializer, + { + self.key_file.serialize(serializer) + } +} + +impl From for OpaqueKeyFile where T: Into { + fn from(val: T) -> Self { + OpaqueKeyFile { key_file: val.into() } + } +} + #[derive(Debug, PartialEq, Serialize)] pub struct KeyFile { pub id: Uuid, diff --git a/ethstore/src/json/mod.rs b/ethstore/src/json/mod.rs index 98033effd..865b75dea 100644 --- a/ethstore/src/json/mod.rs +++ b/ethstore/src/json/mod.rs @@ -36,7 +36,7 @@ pub use self::error::Error; pub use self::hash::{H128, H160, H256}; pub use self::id::Uuid; pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; -pub use self::key_file::KeyFile; +pub use self::key_file::{KeyFile, OpaqueKeyFile}; pub use self::presale::{PresaleWallet, Encseed}; pub use self::vault_file::VaultFile; pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta}; diff --git a/ethstore/src/lib.rs b/ethstore/src/lib.rs index f092c3fe6..8203feeec 100755 --- a/ethstore/src/lib.rs +++ b/ethstore/src/lib.rs @@ -14,6 +14,11 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . +//! Ethereum key-management. + + +#![warn(missing_docs)] + extern crate libc; extern crate itertools; extern crate smallvec; @@ -52,10 +57,11 @@ mod presale; mod random; mod secret_store; -pub use self::account::{SafeAccount}; +pub use self::account::SafeAccount; pub use self::error::Error; pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::import::{import_accounts, read_geth_accounts}; +pub use self::json::OpaqueKeyFile as KeyFile; pub use self::presale::PresaleWallet; pub use self::secret_store::{ SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore, diff --git a/ethstore/src/presale.rs b/ethstore/src/presale.rs index 45d127664..dbbdcdc8d 100644 --- a/ethstore/src/presale.rs +++ b/ethstore/src/presale.rs @@ -8,6 +8,7 @@ use ethkey::{Address, Secret, KeyPair}; use crypto::Keccak256; use {crypto, Error}; +/// Pre-sale wallet. pub struct PresaleWallet { iv: [u8; 16], ciphertext: Vec, @@ -31,6 +32,7 @@ impl From for PresaleWallet { } impl PresaleWallet { + /// Open a pre-sale wallet. pub fn open

(path: P) -> Result where P: AsRef { let file = fs::File::open(path)?; let presale = json::PresaleWallet::load(file) @@ -38,6 +40,7 @@ impl PresaleWallet { Ok(PresaleWallet::from(presale)) } + /// Decrypt the wallet. pub fn decrypt(&self, password: &str) -> Result { let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes()); let mut derived_key = vec![0u8; 16]; diff --git a/ethstore/src/secret_store.rs b/ethstore/src/secret_store.rs index 1eff95335..fd7eea50d 100755 --- a/ethstore/src/secret_store.rs +++ b/ethstore/src/secret_store.rs @@ -18,7 +18,7 @@ use std::hash::{Hash, Hasher}; use std::path::PathBuf; use ethkey::{Address, Message, Signature, Secret, Public}; use Error; -use json::Uuid; +use json::{Uuid, OpaqueKeyFile}; use util::H256; /// Key directory reference @@ -39,16 +39,28 @@ pub struct StoreAccountRef { pub address: Address, } +/// Simple Secret Store API pub trait SimpleSecretStore: Send + Sync { + /// Inserts new accounts to the store (or vault) with given password. fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result; + /// Inserts new derived account to the store (or vault) with given password. fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; + /// Changes accounts password. fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>; + /// Exports key details for account. + fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result; + /// Entirely removes account from the store and underlying storage. fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>; + /// Generates new derived account. fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result; + /// Sign a message with given account. fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result; + /// Sign a message with derived account. fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result; + /// Decrypt a messages with given account. fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error>; + /// Returns all accounts in this secret store. fn accounts(&self) -> Result, Error>; /// Get reference to some account with given address. /// This method could be removed if we will guarantee that there is max(1) account for given address. @@ -74,23 +86,37 @@ pub trait SimpleSecretStore: Send + Sync { fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>; } +/// Secret Store API pub trait SecretStore: SimpleSecretStore { + /// Imports presale wallet fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; + /// Imports existing JSON wallet fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result; + /// Copies account between stores and vaults. fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>; + /// Checks if password matches given account. fn test_password(&self, account: &StoreAccountRef, password: &str) -> Result; + /// Returns a public key for given account. fn public(&self, account: &StoreAccountRef, password: &str) -> Result; + /// Returns uuid of an account. fn uuid(&self, account: &StoreAccountRef) -> Result; + /// Returns account's name. fn name(&self, account: &StoreAccountRef) -> Result; + /// Returns account's metadata. fn meta(&self, account: &StoreAccountRef) -> Result; + /// Modifies account metadata. fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>; + /// Modifies account name. fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>; + /// Returns local path of the store. fn local_path(&self) -> PathBuf; + /// Lists all found geth accounts. fn list_geth_accounts(&self, testnet: bool) -> Vec

; + /// Imports geth accounts to the store/vault. fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec
, testnet: bool) -> Result, Error>; } diff --git a/js/package.json b/js/package.json index 54cc076fe..6a1e63fb1 100644 --- a/js/package.json +++ b/js/package.json @@ -1,6 +1,6 @@ { "name": "parity.js", - "version": "1.7.26", + "version": "1.7.29", "main": "release/index.js", "jsnext:main": "src/index.js", "author": "Parity Team ", @@ -48,8 +48,10 @@ "lint": "npm run lint:css && npm run lint:js", "lint:cached": "npm run lint:css && npm run lint:js:cached", "lint:css": "stylelint ./src/**/*.css", + "lint:fix": "npm run lint:js:fix", "lint:js": "eslint --ignore-path .gitignore ./src/", "lint:js:cached": "eslint --cache --ignore-path .gitignore ./src/", + "lint:js:fix": "eslint --fix --ignore-path .gitignore ./src/", "test": "NODE_ENV=test mocha --compilers ejs:ejsify 'src/**/*.spec.js'", "test:coverage": "NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'", "test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'", diff --git a/js/src/modals/PasswordManager/passwordManager.js b/js/src/modals/PasswordManager/passwordManager.js index 91ac9ebf2..6436bc4cc 100644 --- a/js/src/modals/PasswordManager/passwordManager.js +++ b/js/src/modals/PasswordManager/passwordManager.js @@ -60,8 +60,11 @@ class PasswordManager extends Component { store = new Store(this.context.api, this.props.account); render () { + const { busy } = this.store; + return ( -
-
- - } - label={ - - } - onChange={ this.onEditTestPassword } - onSubmit={ this.testPassword } - submitOnBlur={ false } - type='password' - /> -
-
+ { this.renderTabTest() } -
-
- - } - label={ - - } - onChange={ this.onEditCurrentPassword } - type='password' - /> - - } - label={ - - } - onChange={ this.onEditNewPasswordHint } - value={ passwordHint } - /> -
-
- - } - label={ - - } - onChange={ this.onEditNewPassword } - onSubmit={ this.changePassword } - submitOnBlur={ false } - type='password' - /> -
-
- - } - hint={ - - } - label={ - - } - onChange={ this.onEditNewPasswordRepeat } - onSubmit={ this.changePassword } - submitOnBlur={ false } - type='password' - /> -
-
- - -
-
+ { this.renderTabChange() }
); } + renderTabTest () { + const { actionTab, busy } = this.store; + + if (actionTab !== TEST_ACTION) { + return null; + } + + return ( +
+
+ + } + label={ + + } + onChange={ this.onEditTestPassword } + onSubmit={ this.testPassword } + submitOnBlur={ false } + type='password' + /> +
+
+ ); + } + + renderTabChange () { + const { actionTab, busy, isRepeatValid, newPassword, passwordHint } = this.store; + + if (actionTab !== CHANGE_ACTION) { + return null; + } + + return ( +
+
+ + } + label={ + + } + onChange={ this.onEditCurrentPassword } + type='password' + /> + + } + label={ + + } + onChange={ this.onEditNewPasswordHint } + value={ passwordHint } + /> +
+
+ + } + label={ + + } + onChange={ this.onEditNewPassword } + onSubmit={ this.changePassword } + submitOnBlur={ false } + type='password' + /> +
+
+ + } + hint={ + + } + label={ + + } + onChange={ this.onEditNewPasswordRepeat } + onSubmit={ this.changePassword } + submitOnBlur={ false } + type='password' + /> +
+
+ + +
+
+ ); + } + renderDialogActions () { const { actionTab, busy, isRepeatValid } = this.store; diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index d496931b9..3d66d2655 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -24,6 +24,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); flex: 1; height: 100%; padding: 0em; + max-width: 100%; position: relative; /*transform: translateZ(0); transition: $transitionAll;*/ diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css index 4613371ad..e81d8ce07 100644 --- a/js/src/ui/SectionList/sectionList.css +++ b/js/src/ui/SectionList/sectionList.css @@ -18,6 +18,7 @@ $transition: all 0.25s; $widthNormal: 33.33%; $widthExpanded: 42%; +$widthContracted: 29%; .section { position: relative; @@ -45,6 +46,7 @@ $widthExpanded: 42%; display: flex; flex: 0 1 $widthNormal; max-width: $widthNormal; + min-width: $widthContracted; opacity: 0.85; padding: 0.25em; diff --git a/js/src/views/Signer/components/RequestPending/requestPending.js b/js/src/views/Signer/components/RequestPending/requestPending.js index 671df6cbb..3d8ea0c15 100644 --- a/js/src/views/Signer/components/RequestPending/requestPending.js +++ b/js/src/views/Signer/components/RequestPending/requestPending.js @@ -36,7 +36,7 @@ export default class RequestPending extends Component { PropTypes.shape({ sign: PropTypes.object.isRequired }), PropTypes.shape({ signTransaction: PropTypes.object.isRequired }) ]).isRequired, - signerstore: PropTypes.object.isRequired + signerStore: PropTypes.object.isRequired }; static defaultProps = { @@ -45,7 +45,7 @@ export default class RequestPending extends Component { }; render () { - const { className, date, focus, gasLimit, id, isSending, netVersion, onReject, payload, signerstore, origin } = this.props; + const { className, date, focus, gasLimit, id, isSending, netVersion, onReject, payload, signerStore, origin } = this.props; if (payload.sign) { const { sign } = payload; @@ -63,7 +63,7 @@ export default class RequestPending extends Component { onConfirm={ this.onConfirm } onReject={ onReject } origin={ origin } - signerstore={ signerstore } + signerStore={ signerStore } /> ); } @@ -83,7 +83,7 @@ export default class RequestPending extends Component { onConfirm={ this.onConfirm } onReject={ onReject } origin={ origin } - signerstore={ signerstore } + signerStore={ signerStore } transaction={ transaction } /> ); diff --git a/js/src/views/Signer/components/SignRequest/signRequest.js b/js/src/views/Signer/components/SignRequest/signRequest.js index 373262d41..8c9984b4b 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.js @@ -47,7 +47,7 @@ export default class SignRequest extends Component { id: PropTypes.object.isRequired, isFinished: PropTypes.bool.isRequired, netVersion: PropTypes.string.isRequired, - signerstore: PropTypes.object.isRequired, + signerStore: PropTypes.object.isRequired, className: PropTypes.string, focus: PropTypes.bool, @@ -67,9 +67,9 @@ export default class SignRequest extends Component { }; componentWillMount () { - const { address, signerstore } = this.props; + const { address, signerStore } = this.props; - signerstore.fetchBalance(address); + signerStore.fetchBalance(address); } render () { @@ -106,8 +106,8 @@ export default class SignRequest extends Component { renderDetails () { const { api } = this.context; - const { address, data, netVersion, origin, signerstore } = this.props; - const { balances, externalLink } = signerstore; + const { address, data, netVersion, origin, signerStore } = this.props; + const { balances, externalLink } = signerStore; const balance = balances[address]; diff --git a/js/src/views/Signer/components/SignRequest/signRequest.spec.js b/js/src/views/Signer/components/SignRequest/signRequest.spec.js index cf7d7e9f6..c5c87d509 100644 --- a/js/src/views/Signer/components/SignRequest/signRequest.spec.js +++ b/js/src/views/Signer/components/SignRequest/signRequest.spec.js @@ -28,7 +28,7 @@ const store = { describe('views/Signer/components/SignRequest', () => { it('renders', () => { expect(shallow( - , + , )).to.be.ok; }); }); diff --git a/js/src/views/Signer/components/TransactionPending/transactionPending.js b/js/src/views/Signer/components/TransactionPending/transactionPending.js index 9b0b91ef6..90ea75b22 100644 --- a/js/src/views/Signer/components/TransactionPending/transactionPending.js +++ b/js/src/views/Signer/components/TransactionPending/transactionPending.js @@ -48,7 +48,7 @@ class TransactionPending extends Component { onConfirm: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired, origin: PropTypes.any, - signerstore: PropTypes.object.isRequired, + signerStore: PropTypes.object.isRequired, transaction: PropTypes.shape({ condition: PropTypes.object, data: PropTypes.string, @@ -75,10 +75,10 @@ class TransactionPending extends Component { gasPrice: this.props.transaction.gasPrice.toFixed() }); - hwstore = HardwareStore.get(this.context.api); + hardwareStore = HardwareStore.get(this.context.api); componentWillMount () { - const { signerstore, transaction } = this.props; + const { signerStore, transaction } = this.props; const { from, gas, gasPrice, to, value } = transaction; const fee = tUtil.getFee(gas, gasPrice); // BigNumber object @@ -88,7 +88,7 @@ class TransactionPending extends Component { this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay }); this.gasStore.setEthValue(value); - signerstore.fetchBalances([from, to]); + signerStore.fetchBalances([from, to]); } render () { @@ -98,13 +98,13 @@ class TransactionPending extends Component { } renderTransaction () { - const { accounts, className, focus, id, isSending, netVersion, origin, signerstore, transaction } = this.props; + const { accounts, className, focus, id, isSending, netVersion, origin, signerStore, transaction } = this.props; const { totalValue } = this.state; - const { balances, externalLink } = signerstore; + const { balances, externalLink } = signerStore; const { from, value } = transaction; const fromBalance = balances[from]; const account = accounts[from] || {}; - const disabled = account.hardware && !this.hwstore.isConnected(from); + const disabled = account.hardware && !this.hardwareStore.isConnected(from); return (
diff --git a/js/src/views/Signer/containers/Embedded/embedded.js b/js/src/views/Signer/containers/Embedded/embedded.js index adac35621..b79b4f203 100644 --- a/js/src/views/Signer/containers/Embedded/embedded.js +++ b/js/src/views/Signer/containers/Embedded/embedded.js @@ -101,7 +101,7 @@ class Embedded extends Component { onReject={ actions.startRejectRequest } origin={ origin } payload={ payload } - signerstore={ this.store } + signerStore={ this.store } /> ); } diff --git a/js/src/views/Signer/containers/RequestsPage/requestsPage.js b/js/src/views/Signer/containers/RequestsPage/requestsPage.js index c0cddb8d0..8af2e5fd5 100644 --- a/js/src/views/Signer/containers/RequestsPage/requestsPage.js +++ b/js/src/views/Signer/containers/RequestsPage/requestsPage.js @@ -141,7 +141,7 @@ class RequestsPage extends Component { onReject={ actions.startRejectRequest } origin={ origin } payload={ payload } - signerstore={ this.store } + signerStore={ this.store } /> ); } diff --git a/json/src/spec/validator_set.rs b/json/src/spec/validator_set.rs index 080a36c50..f433caa03 100644 --- a/json/src/spec/validator_set.rs +++ b/json/src/spec/validator_set.rs @@ -16,6 +16,8 @@ //! Validator set deserialization. +use std::collections::BTreeMap; +use uint::Uint; use hash::Address; /// Different ways of specifying validators. @@ -30,6 +32,9 @@ pub enum ValidatorSet { /// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions. #[serde(rename="contract")] Contract(Address), + /// A map of starting blocks for each validator set. + #[serde(rename="multi")] + Multi(BTreeMap), } #[cfg(test)] @@ -40,11 +45,17 @@ mod tests { #[test] fn validator_set_deserialization() { let s = r#"[{ - "list" : ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] + "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] }, { - "safeContract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "safeContract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" }, { - "contract" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" + }, { + "multi": { + "0": { "list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "10": { "list": ["0xd6d9d2cd449a754c494264e1809c50e34d64562b"] }, + "20": { "contract": "0xc6d9d2cd449a754c494264e1809c50e34d64562b" } + } }]"#; let _deserialized: Vec = serde_json::from_str(s).unwrap(); diff --git a/mac/Parity/AppDelegate.swift b/mac/Parity/AppDelegate.swift index 3bf3bdd31..c017e79bd 100644 --- a/mac/Parity/AppDelegate.swift +++ b/mac/Parity/AppDelegate.swift @@ -26,7 +26,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength) var parityPid: Int32? = nil var commandLine: [String] = [] - let defaultConfig = "[network]\nwarp = true" let defaultDefaults = "{\"fat_db\":false,\"mode\":\"passive\",\"mode.alarm\":3600,\"mode.timeout\":300,\"pruning\":\"fast\",\"tracing\":false}" func menuAppPath() -> String { @@ -51,7 +50,7 @@ class AppDelegate: NSObject, NSApplicationDelegate { func killParity() { if let pid = self.parityPid { - kill(pid, SIGINT) + kill(pid, SIGKILL) } } @@ -81,9 +80,6 @@ class AppDelegate: NSObject, NSApplicationDelegate { } let configFile = basePath?.appendingPathComponent("config.toml") - if !FileManager.default.fileExists(atPath: configFile!.path) { - try defaultConfig.write(to: configFile!, atomically: false, encoding: String.Encoding.utf8) - } } catch {} } diff --git a/parity/main.rs b/parity/main.rs index 3e499d483..2044b3ee0 100644 --- a/parity/main.rs +++ b/parity/main.rs @@ -75,6 +75,9 @@ extern crate ethcore_secretstore; #[cfg(feature = "dapps")] extern crate ethcore_dapps; +#[cfg(windows)] extern crate ws2_32; +#[cfg(windows)] extern crate winapi; + macro_rules! dependency { ($dep_ty:ident, $url:expr) => { { @@ -123,7 +126,7 @@ mod stratum; use std::{process, env}; use std::collections::HashMap; use std::io::{self as stdio, BufReader, Read, Write}; -use std::fs::{remove_file, metadata, File}; +use std::fs::{remove_file, metadata, File, create_dir_all}; use std::path::PathBuf; use util::sha3::sha3; use cli::Args; @@ -210,10 +213,11 @@ fn latest_exe_path() -> Option { } fn set_spec_name_override(spec_name: String) { - if let Err(e) = File::create(updates_path("spec_name_overide")) - .and_then(|mut f| f.write_all(spec_name.as_bytes())) + if let Err(e) = create_dir_all(default_hypervisor_path()) + .and_then(|_| File::create(updates_path("spec_name_overide")) + .and_then(|mut f| f.write_all(spec_name.as_bytes()))) { - warn!("Couldn't override chain spec: {}", e); + warn!("Couldn't override chain spec: {} at {:?}", e, updates_path("spec_name_overide")); } } @@ -227,12 +231,24 @@ fn take_spec_name_override() -> Option { #[cfg(windows)] fn global_cleanup() { - extern "system" { pub fn WSACleanup() -> i32; } // We need to cleanup all sockets before spawning another Parity process. This makes shure everything is cleaned up. // The loop is required because of internal refernce counter for winsock dll. We don't know how many crates we use do // initialize it. There's at least 2 now. for _ in 0.. 10 { - unsafe { WSACleanup(); } + unsafe { ::ws2_32::WSACleanup(); } + } +} + +#[cfg(not(windows))] +fn global_init() {} + +#[cfg(windows)] +fn global_init() { + // When restarting in the same process this reinits windows sockets. + unsafe { + const WS_VERSION: u16 = 0x202; + let mut wsdata: ::winapi::winsock2::WSADATA = ::std::mem::zeroed(); + ::ws2_32::WSAStartup(WS_VERSION, &mut wsdata); } } @@ -241,15 +257,17 @@ fn global_cleanup() {} // Starts ~/.parity-updates/parity and returns the code it exits with. fn run_parity() -> Option { - global_cleanup(); + global_init(); use ::std::ffi::OsString; let prefix = vec![OsString::from("--can-restart"), OsString::from("--force-direct")]; - latest_exe_path().and_then(|exe| process::Command::new(exe) + let res = latest_exe_path().and_then(|exe| process::Command::new(exe) .args(&(env::args_os().skip(1).chain(prefix.into_iter()).collect::>())) .status() .map(|es| es.code().unwrap_or(128)) .ok() - ) + ); + global_cleanup(); + res } const PLEASE_RESTART_EXIT_CODE: i32 = 69; @@ -257,10 +275,11 @@ const PLEASE_RESTART_EXIT_CODE: i32 = 69; // Run our version of parity. // Returns the exit error code. fn main_direct(can_restart: bool) -> i32 { + global_init(); let mut alt_mains = HashMap::new(); sync_main(&mut alt_mains); stratum_main(&mut alt_mains); - if let Some(f) = std::env::args().nth(1).and_then(|arg| alt_mains.get(&arg.to_string())) { + let res = if let Some(f) = std::env::args().nth(1).and_then(|arg| alt_mains.get(&arg.to_string())) { f(); 0 } else { @@ -280,7 +299,9 @@ fn main_direct(can_restart: bool) -> i32 { 1 }, } - } + }; + global_cleanup(); + res } fn println_trace_main(s: String) { diff --git a/rpc/src/v1/impls/parity_accounts.rs b/rpc/src/v1/impls/parity_accounts.rs index 60b615897..828dbf8f4 100644 --- a/rpc/src/v1/impls/parity_accounts.rs +++ b/rpc/src/v1/impls/parity_accounts.rs @@ -20,6 +20,7 @@ use std::collections::BTreeMap; use util::{Address}; use ethkey::{Brain, Generator, Secret}; +use ethstore::KeyFile; use ethcore::account_provider::AccountProvider; use jsonrpc_core::Error; @@ -315,6 +316,17 @@ impl ParityAccounts for ParityAccountsClient { .map(Into::into) .map_err(|e| errors::account("Could not derive account.", e)) } + + fn export_account(&self, addr: RpcH160, password: String) -> Result { + let addr = addr.into(); + take_weak!(self.accounts) + .export_account( + &addr, + password, + ) + .map(Into::into) + .map_err(|e| errors::account("Could not export account.", e)) + } } fn into_vec(a: Vec) -> Vec where diff --git a/rpc/src/v1/tests/mocked/parity_accounts.rs b/rpc/src/v1/tests/mocked/parity_accounts.rs index ae4f74b49..ef356cd42 100644 --- a/rpc/src/v1/tests/mocked/parity_accounts.rs +++ b/rpc/src/v1/tests/mocked/parity_accounts.rs @@ -472,3 +472,30 @@ fn derive_key_index() { let res = tester.io.handle_request_sync(&request); assert_eq!(res, Some(response.into())); } + + +#[test] +fn should_export_account() { + // given + let tester = setup(); + let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#; + tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap(); + let accounts = tester.accounts.accounts().unwrap(); + assert_eq!(accounts.len(), 1); + + // invalid password + let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","123"],"id":1}"#; + let response = r#"{"jsonrpc":"2.0","error":{"code":-32023,"message":"Could not export account.","data":"InvalidPassword"},"id":1}"#; + let res = tester.io.handle_request_sync(&request); + assert_eq!(res, Some(response.into())); + + // correct password + let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","parity-export-test"],"id":1}"#; + + let response = r#"{"jsonrpc":"2.0","result":{"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}","name":"parity-export-test","version":3},"id":1}"#; + let result = tester.io.handle_request_sync(&request); + + println!("Result: {:?}", result); + println!("Response: {:?}", response); + assert_eq!(result, Some(response.into())); +} diff --git a/rpc/src/v1/traits/parity_accounts.rs b/rpc/src/v1/traits/parity_accounts.rs index a3a9a8d9f..46372560c 100644 --- a/rpc/src/v1/traits/parity_accounts.rs +++ b/rpc/src/v1/traits/parity_accounts.rs @@ -18,6 +18,7 @@ use std::collections::BTreeMap; use jsonrpc_core::Error; +use ethstore::KeyFile; use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical}; build_rpc_trait! { @@ -175,5 +176,9 @@ build_rpc_trait! { /// Resulting address can be either saved as a new account (with the same password). #[rpc(name = "parity_deriveAddressIndex")] fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result; + + /// Exports an account with given address if provided password matches. + #[rpc(name = "parity_exportAccount")] + fn export_account(&self, H160, String) -> Result; } }