Merge branch 'master' into lightcli

This commit is contained in:
Robert Habermeier 2017-03-23 14:12:34 +01:00
commit da837fa9d8
41 changed files with 754 additions and 430 deletions

3
Cargo.lock generated
View File

@ -52,6 +52,7 @@ dependencies = [
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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]] [[package]]
@ -1750,7 +1751,7 @@ dependencies = [
[[package]] [[package]]
name = "parity-ui-precompiled" name = "parity-ui-precompiled"
version = "1.4.0" 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 = [ dependencies = [
"parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-dapps-glue 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View File

@ -24,6 +24,7 @@ serde = "0.9"
serde_json = "0.9" serde_json = "0.9"
app_dirs = "1.1.1" app_dirs = "1.1.1"
fdlimit = "0.1" fdlimit = "0.1"
ws2_32-sys = "0.2"
hyper = { default-features = false, git = "https://github.com/paritytech/hyper" } hyper = { default-features = false, git = "https://github.com/paritytech/hyper" }
ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" }
jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" } jsonrpc-core = { git = "https://github.com/paritytech/jsonrpc.git", branch = "parity-1.7" }

View File

@ -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" }
}
}

View File

@ -24,14 +24,16 @@ use std::fmt;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use util::{RwLock}; use util::{RwLock};
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore, use ethstore::{
random_string, SecretVaultRef, StoreAccountRef}; SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
random_string, SecretVaultRef, StoreAccountRef,
};
use ethstore::dir::MemoryDirectory; use ethstore::dir::MemoryDirectory;
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator}; use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
use ethjson::misc::AccountMeta; use ethjson::misc::AccountMeta;
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath}; use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath};
pub use ethstore::ethkey::Signature; pub use ethstore::ethkey::Signature;
pub use ethstore::{Derivation, IndexDerivation}; pub use ethstore::{Derivation, IndexDerivation, KeyFile};
/// Type of unlock. /// Type of unlock.
#[derive(Clone)] #[derive(Clone)]
@ -500,6 +502,11 @@ impl AccountProvider {
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password) 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<KeyFile, Error> {
self.sstore.export_account(&self.sstore.account_ref(address)?, &password)
}
/// Helper method used for unlocking accounts. /// Helper method used for unlocking accounts.
fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> { fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> {
// verify password by signing dump message // verify password by signing dump message

View File

@ -82,7 +82,7 @@ pub struct AuthorityRound {
proposed: AtomicBool, proposed: AtomicBool,
client: RwLock<Option<Weak<EngineClient>>>, client: RwLock<Option<Weak<EngineClient>>>,
signer: EngineSigner, signer: EngineSigner,
validators: Box<ValidatorSet + Send + Sync>, validators: Box<ValidatorSet>,
/// Is this Engine just for testing (prevents step calibration). /// Is this Engine just for testing (prevents step calibration).
calibrate_step: bool, calibrate_step: bool,
} }

View File

@ -58,7 +58,7 @@ pub struct BasicAuthority {
gas_limit_bound_divisor: U256, gas_limit_bound_divisor: U256,
builtins: BTreeMap<Address, Builtin>, builtins: BTreeMap<Address, Builtin>,
signer: EngineSigner, signer: EngineSigner,
validators: Box<ValidatorSet + Send + Sync>, validators: Box<ValidatorSet>,
} }
impl BasicAuthority { impl BasicAuthority {

View File

@ -98,7 +98,7 @@ pub struct Tendermint {
/// Hash of the proposal parent block. /// Hash of the proposal parent block.
proposal_parent: RwLock<H256>, proposal_parent: RwLock<H256>,
/// Set used to determine the current validators. /// Set used to determine the current validators.
validators: Box<ValidatorSet + Send + Sync>, validators: Box<ValidatorSet>,
} }
impl Tendermint { impl Tendermint {

View File

@ -19,6 +19,7 @@
mod simple_list; mod simple_list;
mod safe_contract; mod safe_contract;
mod contract; mod contract;
mod multi;
use std::sync::Weak; use std::sync::Weak;
use util::{Address, H256}; use util::{Address, H256};
@ -27,23 +28,27 @@ use client::Client;
use self::simple_list::SimpleList; use self::simple_list::SimpleList;
use self::contract::ValidatorContract; use self::contract::ValidatorContract;
use self::safe_contract::ValidatorSafeContract; use self::safe_contract::ValidatorSafeContract;
use self::multi::Multi;
/// Creates a validator set from spec. /// Creates a validator set from spec.
pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet + Send + Sync> { pub fn new_validator_set(spec: ValidatorSpec) -> Box<ValidatorSet> {
match spec { match spec {
ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())), ValidatorSpec::List(list) => Box::new(SimpleList::new(list.into_iter().map(Into::into).collect())),
ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())), ValidatorSpec::SafeContract(address) => Box::new(ValidatorSafeContract::new(address.into())),
ValidatorSpec::Contract(address) => Box::new(ValidatorContract::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. /// 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. /// 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. /// Returns the current number of validators.
fn count(&self, bh: &H256) -> usize; fn count(&self, parent_block_hash: &H256) -> usize;
/// Notifies about malicious behaviour. /// Notifies about malicious behaviour.
fn report_malicious(&self, _validator: &Address) {} fn report_malicious(&self, _validator: &Address) {}
/// Notifies about benign misbehaviour. /// Notifies about benign misbehaviour.

View File

@ -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 <http://www.gnu.org/licenses/>.
/// 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<Fn(&H256) -> Result<BlockNumber, String> + Send + Sync + 'static>;
pub struct Multi {
sets: BTreeMap<BlockNumber, Box<ValidatorSet>>,
block_number: RwLock<BlockNumberLookup>,
}
impl Multi {
pub fn new(set_map: BTreeMap<BlockNumber, Box<ValidatorSet>>) -> 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<ValidatorSet>> {
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<Client>) {
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);
}
}

View File

@ -360,6 +360,10 @@ impl Spec {
/// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf". /// Account is marked with `reportBenign` it can be checked as disliked with "0xd8f2e0bf".
/// Validator can be removed with `reportMalicious`. /// Validator can be removed with `reportMalicious`.
pub fn new_validator_contract() -> Self { load_bundled!("validator_contract") } 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)] #[cfg(test)]

View File

@ -19,14 +19,22 @@ use {json, Error, crypto};
use account::Version; use account::Version;
use super::crypto::Crypto; use super::crypto::Crypto;
/// Account representation.
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
pub struct SafeAccount { pub struct SafeAccount {
/// Account ID
pub id: [u8; 16], pub id: [u8; 16],
/// Account version
pub version: Version, pub version: Version,
/// Account address
pub address: Address, pub address: Address,
/// Account private key derivation definition.
pub crypto: Crypto, pub crypto: Crypto,
/// Account filename
pub filename: Option<String>, pub filename: Option<String>,
/// Account name
pub name: String, pub name: String,
/// Account metadata
pub meta: String, pub meta: String,
} }
@ -44,6 +52,7 @@ impl Into<json::KeyFile> for SafeAccount {
} }
impl SafeAccount { impl SafeAccount {
/// Create a new account
pub fn create( pub fn create(
keypair: &KeyPair, keypair: &KeyPair,
id: [u8; 16], id: [u8; 16],
@ -114,21 +123,25 @@ impl SafeAccount {
}) })
} }
/// Sign a message.
pub fn sign(&self, password: &str, message: &Message) -> Result<Signature, Error> { pub fn sign(&self, password: &str, message: &Message) -> Result<Signature, Error> {
let secret = self.crypto.secret(password)?; let secret = self.crypto.secret(password)?;
sign(&secret, message).map_err(From::from) sign(&secret, message).map_err(From::from)
} }
/// Decrypt a message.
pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> { pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let secret = self.crypto.secret(password)?; let secret = self.crypto.secret(password)?;
crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from) crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
} }
/// Derive public key.
pub fn public(&self, password: &str) -> Result<Public, Error> { pub fn public(&self, password: &str) -> Result<Public, Error> {
let secret = self.crypto.secret(password)?; let secret = self.crypto.secret(password)?;
Ok(KeyPair::from_secret(secret)?.public().clone()) 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<Self, Error> { pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
let secret = self.crypto.secret(old_password)?; let secret = self.crypto.secret(old_password)?;
let result = SafeAccount { let result = SafeAccount {
@ -143,6 +156,7 @@ impl SafeAccount {
Ok(result) Ok(result)
} }
/// Check if password matches the account.
pub fn check_password(&self, password: &str) -> bool { pub fn check_password(&self, password: &str) -> bool {
self.crypto.secret(password).is_ok() self.crypto.secret(password).is_ok()
} }

View File

@ -22,7 +22,7 @@ use std::{env, process, fs};
use std::io::Read; use std::io::Read;
use docopt::Docopt; use docopt::Docopt;
use ethstore::ethkey::Address; 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, use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, Error, PresaleWallet,
SecretVaultRef, StoreAccountRef}; SecretVaultRef, StoreAccountRef};
@ -49,14 +49,14 @@ Usage:
Options: Options:
-h, --help Display this message and exit. -h, --help Display this message and exit.
--dir DIR Specify the secret store directory. It may be either --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]. or a path [default: parity].
--vault VAULT Specify vault to use in this operation. --vault VAULT Specify vault to use in this operation.
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note --vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
that this option is required when vault option is set. that this option is required when vault option is set.
Otherwise it is ignored. Otherwise it is ignored.
--src DIR Specify import source. It may be either --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]. or a path [default: geth].
Commands: Commands:
@ -116,10 +116,13 @@ fn main() {
fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> { fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
let dir: Box<KeyDirectory> = match location { let dir: Box<KeyDirectory> = match location {
"parity" => Box::new(ParityDirectory::create(DirectoryType::Main)?), "geth" => Box::new(RootDiskDirectory::create(paths::geth(false))?),
"parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?), "geth-test" => Box::new(RootDiskDirectory::create(paths::geth(true))?),
"geth" => Box::new(GethDirectory::create(DirectoryType::Main)?), path if path.starts_with("parity") => {
"geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?), 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)?), path => Box::new(RootDiskDirectory::create(path)?),
}; };
@ -254,4 +257,3 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
Ok(format!("{}", USAGE)) Ok(format!("{}", USAGE))
} }
} }

View File

@ -1,102 +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 <http://www.gnu.org/licenses/>.
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<Self, Error> {
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<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.insert(account)
}
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
fn unique_repr(&self) -> Result<u64, Error> {
self.dir.unique_repr()
}
}

View File

@ -22,6 +22,7 @@ use ethkey::Address;
use {SafeAccount, Error}; use {SafeAccount, Error};
use super::KeyDirectory; use super::KeyDirectory;
/// Accounts in-memory storage.
#[derive(Default)] #[derive(Default)]
pub struct MemoryDirectory { pub struct MemoryDirectory {
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>, accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,

View File

@ -14,19 +14,15 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Accounts Directory
use std::path::{PathBuf}; use std::path::{PathBuf};
use {SafeAccount, Error}; use {SafeAccount, Error};
mod disk; mod disk;
mod geth;
mod memory; mod memory;
mod parity;
mod vault; mod vault;
pub mod paths;
pub enum DirectoryType {
Testnet,
Main,
}
/// `VaultKeyDirectory::set_key` error /// `VaultKeyDirectory::set_key` error
#[derive(Debug)] #[derive(Debug)]
@ -54,7 +50,7 @@ pub trait KeyDirectory: Send + Sync {
fn load(&self) -> Result<Vec<SafeAccount>, Error>; fn load(&self) -> Result<Vec<SafeAccount>, Error>;
/// Insert new key to directory /// Insert new key to directory
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>; fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
//// Update key in directory /// Update key in the directory
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>; fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
/// Remove key from directory /// Remove key from directory
fn remove(&self, account: &SafeAccount) -> Result<(), Error>; fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
@ -95,9 +91,7 @@ pub trait VaultKeyDirectory: KeyDirectory {
} }
pub use self::disk::RootDiskDirectory; pub use self::disk::RootDiskDirectory;
pub use self::geth::GethDirectory;
pub use self::memory::MemoryDirectory; pub use self::memory::MemoryDirectory;
pub use self::parity::ParityDirectory;
pub use self::vault::VaultDiskDirectory; pub use self::vault::VaultDiskDirectory;
impl VaultKey { impl VaultKey {

View File

@ -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 <http://www.gnu.org/licenses/>.
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<Self, Error> {
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<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.insert(account)
}
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
fn unique_repr(&self) -> Result<u64, Error> {
self.dir.unique_repr()
}
}

96
ethstore/src/dir/paths.rs Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
//! 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
}

View File

@ -20,23 +20,40 @@ use ethkey::Error as EthKeyError;
use crypto::Error as EthCryptoError; use crypto::Error as EthCryptoError;
use ethkey::DerivationError; use ethkey::DerivationError;
/// Account-related errors.
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// IO error
Io(IoError), Io(IoError),
/// Invalid Password
InvalidPassword, InvalidPassword,
/// Account's secret is invalid.
InvalidSecret, InvalidSecret,
/// Invalid Vault Crypto meta.
InvalidCryptoMeta, InvalidCryptoMeta,
/// Invalid Account.
InvalidAccount, InvalidAccount,
/// Invalid Message.
InvalidMessage, InvalidMessage,
/// Invalid Key File
InvalidKeyFile(String), InvalidKeyFile(String),
/// Vaults are not supported.
VaultsAreNotSupported, VaultsAreNotSupported,
/// Unsupported vault
UnsupportedVault, UnsupportedVault,
/// Invalid vault name
InvalidVaultName, InvalidVaultName,
/// Vault not found
VaultNotFound, VaultNotFound,
/// Account creation failed.
CreationFailed, CreationFailed,
/// `EthKey` error
EthKey(EthKeyError), EthKey(EthKeyError),
/// `EthCrypto` error
EthCrypto(EthCryptoError), EthCrypto(EthCryptoError),
/// Derivation error
Derivation(DerivationError), Derivation(DerivationError),
/// Custom error
Custom(String), Custom(String),
} }

View File

@ -25,18 +25,21 @@ use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, Extende
use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
use account::SafeAccount; use account::SafeAccount;
use presale::PresaleWallet; use presale::PresaleWallet;
use json::{self, Uuid}; use json::{self, Uuid, OpaqueKeyFile};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
/// Accounts store.
pub struct EthStore { pub struct EthStore {
store: EthMultiStore, store: EthMultiStore,
} }
impl EthStore { impl EthStore {
/// Open a new accounts store with given key directory backend.
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> { pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32) 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<KeyDirectory>, iterations: u32) -> Result<Self, Error> { pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
Ok(EthStore { Ok(EthStore {
store: EthMultiStore::open_with_iterations(directory, iterations)?, store: EthMultiStore::open_with_iterations(directory, iterations)?,
@ -44,7 +47,7 @@ impl EthStore {
} }
fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> { fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
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) accounts.next().ok_or(Error::InvalidAccount)
} }
} }
@ -76,6 +79,10 @@ impl SimpleSecretStore for EthStore {
self.store.change_password(account, old_password, new_password) self.store.change_password(account, old_password, new_password)
} }
fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error> {
self.store.export_account(account, password)
}
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> { fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> {
self.store.remove_account(account, password) self.store.remove_account(account, password)
} }
@ -234,11 +241,12 @@ pub struct EthMultiStore {
} }
impl EthMultiStore { impl EthMultiStore {
/// Open new multi-accounts store with given key directory backend.
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> { pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32) 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<KeyDirectory>, iterations: u32) -> Result<Self, Error> { pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
let store = EthMultiStore { let store = EthMultiStore {
dir: directory, dir: directory,
@ -287,7 +295,7 @@ impl EthMultiStore {
Ok(()) Ok(())
} }
fn get(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> { fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> {
{ {
let cache = self.cache.read(); let cache = self.cache.read();
if let Some(accounts) = cache.get(account) { if let Some(accounts) = cache.get(account) {
@ -307,6 +315,15 @@ impl EthMultiStore {
} }
} }
fn get_matching(&self, account: &StoreAccountRef, password: &str) -> Result<Vec<SafeAccount>, 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<StoreAccountRef, Error> { fn import(&self, vault: SecretVaultRef, account: SafeAccount) -> Result<StoreAccountRef, Error> {
// save to file // save to file
let account = match vault { 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) fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<StoreAccountRef, Error> -> Result<StoreAccountRef, Error>
{ {
let accounts = self.get(account_ref)?; let accounts = self.get_matching(account_ref, password)?;
for account in accounts { 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 extended = self.generate(account.crypto.secret(password)?, derivation)?;
return self.insert_account(vault, extended.secret().as_raw().clone(), password); 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) fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<Address, Error> -> Result<Address, Error>
{ {
let accounts = self.get(&account_ref)?; let accounts = self.get_matching(&account_ref, password)?;
for account in accounts { 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 extended = self.generate(account.crypto.secret(password)?, derivation)?;
return Ok(ethkey::public_to_address(extended.public().public())); return Ok(ethkey::public_to_address(extended.public().public()));
} }
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
@ -429,18 +437,13 @@ impl SimpleSecretStore for EthMultiStore {
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message)
-> Result<Signature, Error> -> Result<Signature, Error>
{ {
let accounts = self.get(&account_ref)?; let accounts = self.get_matching(&account_ref, password)?;
for account in accounts { 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 extended = self.generate(account.crypto.secret(password)?, derivation)?;
let secret = extended.secret().as_raw(); let secret = extended.secret().as_raw();
return Ok(ethkey::sign(&secret, message)?) return Ok(ethkey::sign(&secret, message)?)
} }
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
} }
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> { fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
@ -457,47 +460,47 @@ impl SimpleSecretStore for EthMultiStore {
} }
fn remove_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result<(), Error> { 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 { for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
return self.remove_safe_account(account_ref, &account); return self.remove_safe_account(account_ref, &account);
} }
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
} }
fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> { 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 { for account in accounts {
// Change password // Change password
let new_account = account.change_password(old_password, new_password, self.iterations)?; let new_account = account.change_password(old_password, new_password, self.iterations)?;
self.update(account_ref, account, new_account)?; self.update(account_ref, account, new_account)?;
} }
Ok(()) Ok(())
} }
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> { fn export_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error> {
let accounts = self.get(account)?; self.get_matching(account_ref, password)?.into_iter().nth(0).map(Into::into).ok_or(Error::InvalidPassword)
for account in accounts { }
if account.check_password(password) {
return account.sign(password, message);
}
}
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> {
let accounts = self.get_matching(account, password)?;
for account in accounts {
return account.sign(password, message);
}
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
} }
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> { fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
let accounts = self.get(account)?; let accounts = self.get_matching(account, password)?;
for account in accounts { 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) Err(Error::InvalidPassword)
} }
@ -586,7 +589,7 @@ impl SimpleSecretStore for EthMultiStore {
return Ok(account_ref); 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())?; let new_account_ref = self.import(vault, account.clone())?;
self.remove_safe_account(&account_ref, &account)?; self.remove_safe_account(&account_ref, &account)?;
self.reload_accounts()?; self.reload_accounts()?;
@ -1032,4 +1035,18 @@ mod tests {
// then // then
assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned()); 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);
}
} }

View File

@ -16,9 +16,10 @@
use std::collections::HashSet; use std::collections::HashSet;
use ethkey::Address; use ethkey::Address;
use dir::{GethDirectory, KeyDirectory, DirectoryType}; use dir::{paths, KeyDirectory, RootDiskDirectory};
use Error; use Error;
/// Import all accounts from one directory to the other.
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> { pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
let accounts = src.load()?; let accounts = src.load()?;
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>(); let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
@ -34,13 +35,7 @@ pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Add
/// Provide a `HashSet` of all accounts available for import from the Geth keystore. /// Provide a `HashSet` of all accounts available for import from the Geth keystore.
pub fn read_geth_accounts(testnet: bool) -> Vec<Address> { pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
let t = if testnet { RootDiskDirectory::at(paths::geth(testnet))
DirectoryType::Testnet
} else {
DirectoryType::Main
};
GethDirectory::open(t)
.load() .load()
.map(|d| d.into_iter().map(|a| a.address).collect()) .map(|d| d.into_iter().map(|a| a.address).collect())
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
@ -48,13 +43,7 @@ pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
/// 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<Address>, testnet: bool) -> Result<Vec<Address>, Error> { pub fn import_geth_accounts(dst: &KeyDirectory, desired: HashSet<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
let t = if testnet { let src = RootDiskDirectory::at(paths::geth(testnet));
DirectoryType::Testnet
} else {
DirectoryType::Main
};
let src = GethDirectory::open(t);
let accounts = src.load()?; let accounts = src.load()?;
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>(); let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();

View File

@ -16,11 +16,31 @@
use std::fmt; use std::fmt;
use std::io::{Read, Write}; use std::io::{Read, Write};
use serde::{Deserialize, Deserializer}; use serde::{Serialize, Serializer, Deserialize, Deserializer};
use serde::de::{Error, Visitor, MapVisitor}; use serde::de::{Error, Visitor, MapVisitor};
use serde_json; use serde_json;
use super::{Uuid, Version, Crypto, H160}; 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<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where
S: Serializer,
{
self.key_file.serialize(serializer)
}
}
impl<T> From<T> for OpaqueKeyFile where T: Into<KeyFile> {
fn from(val: T) -> Self {
OpaqueKeyFile { key_file: val.into() }
}
}
#[derive(Debug, PartialEq, Serialize)] #[derive(Debug, PartialEq, Serialize)]
pub struct KeyFile { pub struct KeyFile {
pub id: Uuid, pub id: Uuid,

View File

@ -36,7 +36,7 @@ pub use self::error::Error;
pub use self::hash::{H128, H160, H256}; pub use self::hash::{H128, H160, H256};
pub use self::id::Uuid; pub use self::id::Uuid;
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams}; 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::presale::{PresaleWallet, Encseed};
pub use self::vault_file::VaultFile; 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}; pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};

View File

@ -14,6 +14,11 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ethereum key-management.
#![warn(missing_docs)]
extern crate libc; extern crate libc;
extern crate itertools; extern crate itertools;
extern crate smallvec; extern crate smallvec;
@ -52,10 +57,11 @@ mod presale;
mod random; mod random;
mod secret_store; mod secret_store;
pub use self::account::{SafeAccount}; pub use self::account::SafeAccount;
pub use self::error::Error; pub use self::error::Error;
pub use self::ethstore::{EthStore, EthMultiStore}; pub use self::ethstore::{EthStore, EthMultiStore};
pub use self::import::{import_accounts, read_geth_accounts}; pub use self::import::{import_accounts, read_geth_accounts};
pub use self::json::OpaqueKeyFile as KeyFile;
pub use self::presale::PresaleWallet; pub use self::presale::PresaleWallet;
pub use self::secret_store::{ pub use self::secret_store::{
SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore,

View File

@ -8,6 +8,7 @@ use ethkey::{Address, Secret, KeyPair};
use crypto::Keccak256; use crypto::Keccak256;
use {crypto, Error}; use {crypto, Error};
/// Pre-sale wallet.
pub struct PresaleWallet { pub struct PresaleWallet {
iv: [u8; 16], iv: [u8; 16],
ciphertext: Vec<u8>, ciphertext: Vec<u8>,
@ -31,6 +32,7 @@ impl From<json::PresaleWallet> for PresaleWallet {
} }
impl PresaleWallet { impl PresaleWallet {
/// Open a pre-sale wallet.
pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> { pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
let file = fs::File::open(path)?; let file = fs::File::open(path)?;
let presale = json::PresaleWallet::load(file) let presale = json::PresaleWallet::load(file)
@ -38,6 +40,7 @@ impl PresaleWallet {
Ok(PresaleWallet::from(presale)) Ok(PresaleWallet::from(presale))
} }
/// Decrypt the wallet.
pub fn decrypt(&self, password: &str) -> Result<KeyPair, Error> { pub fn decrypt(&self, password: &str) -> Result<KeyPair, Error> {
let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes()); let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes());
let mut derived_key = vec![0u8; 16]; let mut derived_key = vec![0u8; 16];

View File

@ -18,7 +18,7 @@ use std::hash::{Hash, Hasher};
use std::path::PathBuf; use std::path::PathBuf;
use ethkey::{Address, Message, Signature, Secret, Public}; use ethkey::{Address, Message, Signature, Secret, Public};
use Error; use Error;
use json::Uuid; use json::{Uuid, OpaqueKeyFile};
use util::H256; use util::H256;
/// Key directory reference /// Key directory reference
@ -39,16 +39,28 @@ pub struct StoreAccountRef {
pub address: Address, pub address: Address,
} }
/// Simple Secret Store API
pub trait SimpleSecretStore: Send + Sync { 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<StoreAccountRef, Error>; fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result<StoreAccountRef, Error>;
/// 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<StoreAccountRef, Error>; fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<StoreAccountRef, Error>;
/// Changes accounts password.
fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>; 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<OpaqueKeyFile, Error>;
/// Entirely removes account from the store and underlying storage.
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>; 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<Address, Error>; fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<Address, Error>;
/// Sign a message with given account.
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error>; fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error>;
/// Sign a message with derived account.
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result<Signature, Error>; fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) -> Result<Signature, Error>;
/// Decrypt a messages with given account.
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>; fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
/// Returns all accounts in this secret store.
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>; fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
/// Get reference to some account with given address. /// 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. /// 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>; fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>;
} }
/// Secret Store API
pub trait SecretStore: SimpleSecretStore { pub trait SecretStore: SimpleSecretStore {
/// Imports presale wallet
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>; fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
/// Imports existing JSON wallet
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>; fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
/// 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>; 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<bool, Error>; fn test_password(&self, account: &StoreAccountRef, password: &str) -> Result<bool, Error>;
/// Returns a public key for given account.
fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error>; fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error>;
/// Returns uuid of an account.
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error>; fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error>;
/// Returns account's name.
fn name(&self, account: &StoreAccountRef) -> Result<String, Error>; fn name(&self, account: &StoreAccountRef) -> Result<String, Error>;
/// Returns account's metadata.
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error>; fn meta(&self, account: &StoreAccountRef) -> Result<String, Error>;
/// Modifies account metadata.
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>; fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
/// Modifies account name.
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>; fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
/// Returns local path of the store.
fn local_path(&self) -> PathBuf; fn local_path(&self) -> PathBuf;
/// Lists all found geth accounts.
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>; fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
/// Imports geth accounts to the store/vault.
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>; fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "parity.js", "name": "parity.js",
"version": "1.7.26", "version": "1.7.29",
"main": "release/index.js", "main": "release/index.js",
"jsnext:main": "src/index.js", "jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>", "author": "Parity Team <admin@parity.io>",
@ -48,8 +48,10 @@
"lint": "npm run lint:css && npm run lint:js", "lint": "npm run lint:css && npm run lint:js",
"lint:cached": "npm run lint:css && npm run lint:js:cached", "lint:cached": "npm run lint:css && npm run lint:js:cached",
"lint:css": "stylelint ./src/**/*.css", "lint:css": "stylelint ./src/**/*.css",
"lint:fix": "npm run lint:js:fix",
"lint:js": "eslint --ignore-path .gitignore ./src/", "lint:js": "eslint --ignore-path .gitignore ./src/",
"lint:js:cached": "eslint --cache --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": "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:coverage": "NODE_ENV=test istanbul cover _mocha -- --compilers ejs:ejsify 'src/**/*.spec.js'",
"test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'", "test:e2e": "NODE_ENV=test mocha 'src/**/*.e2e.js'",

View File

@ -60,8 +60,11 @@ class PasswordManager extends Component {
store = new Store(this.context.api, this.props.account); store = new Store(this.context.api, this.props.account);
render () { render () {
const { busy } = this.store;
return ( return (
<Portal <Portal
busy={ busy }
buttons={ this.renderDialogActions() } buttons={ this.renderDialogActions() }
onClose={ this.onClose } onClose={ this.onClose }
open open
@ -133,8 +136,6 @@ class PasswordManager extends Component {
} }
renderPage () { renderPage () {
const { busy, isRepeatValid, newPassword, passwordHint } = this.store;
return ( return (
<Tabs <Tabs
inkBarStyle={ TABS_INKBAR_STYLE } inkBarStyle={ TABS_INKBAR_STYLE }
@ -149,29 +150,7 @@ class PasswordManager extends Component {
} }
onActive={ this.onActivateTestTab } onActive={ this.onActivateTestTab }
> >
<Form className={ styles.form }> { this.renderTabTest() }
<div>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.testPassword.hint'
defaultMessage='your account password'
/>
}
label={
<FormattedMessage
id='passwordChange.testPassword.label'
defaultMessage='password'
/>
}
onChange={ this.onEditTestPassword }
onSubmit={ this.testPassword }
submitOnBlur={ false }
type='password'
/>
</div>
</Form>
</Tab> </Tab>
<Tab <Tab
label={ label={
@ -182,103 +161,151 @@ class PasswordManager extends Component {
} }
onActive={ this.onActivateChangeTab } onActive={ this.onActivateChangeTab }
> >
<Form className={ styles.form }> { this.renderTabChange() }
<div>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.currentPassword.hint'
defaultMessage='your current password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.currentPassword.label'
defaultMessage='current password'
/>
}
onChange={ this.onEditCurrentPassword }
type='password'
/>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.passwordHint.hint'
defaultMessage='hint for the new password'
/>
}
label={
<FormattedMessage
id='passwordChange.passwordHint.label'
defaultMessage='(optional) new password hint'
/>
}
onChange={ this.onEditNewPasswordHint }
value={ passwordHint }
/>
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.newPassword.hint'
defaultMessage='the new password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.newPassword.label'
defaultMessage='new password'
/>
}
onChange={ this.onEditNewPassword }
onSubmit={ this.changePassword }
submitOnBlur={ false }
type='password'
/>
</div>
<div className={ styles.password }>
<Input
disabled={ busy }
error={
isRepeatValid
? null
: <FormattedMessage
id='passwordChange.repeatPassword.error'
defaultMessage='the supplied passwords do not match'
/>
}
hint={
<FormattedMessage
id='passwordChange.repeatPassword.hint'
defaultMessage='repeat the new password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.repeatPassword.label'
defaultMessage='repeat new password'
/>
}
onChange={ this.onEditNewPasswordRepeat }
onSubmit={ this.changePassword }
submitOnBlur={ false }
type='password'
/>
</div>
</div>
<PasswordStrength input={ newPassword } />
</div>
</Form>
</Tab> </Tab>
</Tabs> </Tabs>
); );
} }
renderTabTest () {
const { actionTab, busy } = this.store;
if (actionTab !== TEST_ACTION) {
return null;
}
return (
<Form className={ styles.form }>
<div>
<Input
autoFocus
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.testPassword.hint'
defaultMessage='your account password'
/>
}
label={
<FormattedMessage
id='passwordChange.testPassword.label'
defaultMessage='password'
/>
}
onChange={ this.onEditTestPassword }
onSubmit={ this.testPassword }
submitOnBlur={ false }
type='password'
/>
</div>
</Form>
);
}
renderTabChange () {
const { actionTab, busy, isRepeatValid, newPassword, passwordHint } = this.store;
if (actionTab !== CHANGE_ACTION) {
return null;
}
return (
<Form className={ styles.form }>
<div>
<Input
autoFocus
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.currentPassword.hint'
defaultMessage='your current password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.currentPassword.label'
defaultMessage='current password'
/>
}
onChange={ this.onEditCurrentPassword }
type='password'
/>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.passwordHint.hint'
defaultMessage='hint for the new password'
/>
}
label={
<FormattedMessage
id='passwordChange.passwordHint.label'
defaultMessage='(optional) new password hint'
/>
}
onChange={ this.onEditNewPasswordHint }
value={ passwordHint }
/>
<div className={ styles.passwords }>
<div className={ styles.password }>
<Input
disabled={ busy }
hint={
<FormattedMessage
id='passwordChange.newPassword.hint'
defaultMessage='the new password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.newPassword.label'
defaultMessage='new password'
/>
}
onChange={ this.onEditNewPassword }
onSubmit={ this.changePassword }
submitOnBlur={ false }
type='password'
/>
</div>
<div className={ styles.password }>
<Input
disabled={ busy }
error={
isRepeatValid
? null
: <FormattedMessage
id='passwordChange.repeatPassword.error'
defaultMessage='the supplied passwords do not match'
/>
}
hint={
<FormattedMessage
id='passwordChange.repeatPassword.hint'
defaultMessage='repeat the new password for this account'
/>
}
label={
<FormattedMessage
id='passwordChange.repeatPassword.label'
defaultMessage='repeat new password'
/>
}
onChange={ this.onEditNewPasswordRepeat }
onSubmit={ this.changePassword }
submitOnBlur={ false }
type='password'
/>
</div>
</div>
<PasswordStrength input={ newPassword } />
</div>
</Form>
);
}
renderDialogActions () { renderDialogActions () {
const { actionTab, busy, isRepeatValid } = this.store; const { actionTab, busy, isRepeatValid } = this.store;

View File

@ -24,6 +24,7 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
flex: 1; flex: 1;
height: 100%; height: 100%;
padding: 0em; padding: 0em;
max-width: 100%;
position: relative; position: relative;
/*transform: translateZ(0); /*transform: translateZ(0);
transition: $transitionAll;*/ transition: $transitionAll;*/

View File

@ -18,6 +18,7 @@
$transition: all 0.25s; $transition: all 0.25s;
$widthNormal: 33.33%; $widthNormal: 33.33%;
$widthExpanded: 42%; $widthExpanded: 42%;
$widthContracted: 29%;
.section { .section {
position: relative; position: relative;
@ -45,6 +46,7 @@ $widthExpanded: 42%;
display: flex; display: flex;
flex: 0 1 $widthNormal; flex: 0 1 $widthNormal;
max-width: $widthNormal; max-width: $widthNormal;
min-width: $widthContracted;
opacity: 0.85; opacity: 0.85;
padding: 0.25em; padding: 0.25em;

View File

@ -36,7 +36,7 @@ export default class RequestPending extends Component {
PropTypes.shape({ sign: PropTypes.object.isRequired }), PropTypes.shape({ sign: PropTypes.object.isRequired }),
PropTypes.shape({ signTransaction: PropTypes.object.isRequired }) PropTypes.shape({ signTransaction: PropTypes.object.isRequired })
]).isRequired, ]).isRequired,
signerstore: PropTypes.object.isRequired signerStore: PropTypes.object.isRequired
}; };
static defaultProps = { static defaultProps = {
@ -45,7 +45,7 @@ export default class RequestPending extends Component {
}; };
render () { 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) { if (payload.sign) {
const { sign } = payload; const { sign } = payload;
@ -63,7 +63,7 @@ export default class RequestPending extends Component {
onConfirm={ this.onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
origin={ origin } origin={ origin }
signerstore={ signerstore } signerStore={ signerStore }
/> />
); );
} }
@ -83,7 +83,7 @@ export default class RequestPending extends Component {
onConfirm={ this.onConfirm } onConfirm={ this.onConfirm }
onReject={ onReject } onReject={ onReject }
origin={ origin } origin={ origin }
signerstore={ signerstore } signerStore={ signerStore }
transaction={ transaction } transaction={ transaction }
/> />
); );

View File

@ -47,7 +47,7 @@ export default class SignRequest extends Component {
id: PropTypes.object.isRequired, id: PropTypes.object.isRequired,
isFinished: PropTypes.bool.isRequired, isFinished: PropTypes.bool.isRequired,
netVersion: PropTypes.string.isRequired, netVersion: PropTypes.string.isRequired,
signerstore: PropTypes.object.isRequired, signerStore: PropTypes.object.isRequired,
className: PropTypes.string, className: PropTypes.string,
focus: PropTypes.bool, focus: PropTypes.bool,
@ -67,9 +67,9 @@ export default class SignRequest extends Component {
}; };
componentWillMount () { componentWillMount () {
const { address, signerstore } = this.props; const { address, signerStore } = this.props;
signerstore.fetchBalance(address); signerStore.fetchBalance(address);
} }
render () { render () {
@ -106,8 +106,8 @@ export default class SignRequest extends Component {
renderDetails () { renderDetails () {
const { api } = this.context; const { api } = this.context;
const { address, data, netVersion, origin, signerstore } = this.props; const { address, data, netVersion, origin, signerStore } = this.props;
const { balances, externalLink } = signerstore; const { balances, externalLink } = signerStore;
const balance = balances[address]; const balance = balances[address];

View File

@ -28,7 +28,7 @@ const store = {
describe('views/Signer/components/SignRequest', () => { describe('views/Signer/components/SignRequest', () => {
it('renders', () => { it('renders', () => {
expect(shallow( expect(shallow(
<SignRequest signerstore={ store } />, <SignRequest signerStore={ store } />,
)).to.be.ok; )).to.be.ok;
}); });
}); });

View File

@ -48,7 +48,7 @@ class TransactionPending extends Component {
onConfirm: PropTypes.func.isRequired, onConfirm: PropTypes.func.isRequired,
onReject: PropTypes.func.isRequired, onReject: PropTypes.func.isRequired,
origin: PropTypes.any, origin: PropTypes.any,
signerstore: PropTypes.object.isRequired, signerStore: PropTypes.object.isRequired,
transaction: PropTypes.shape({ transaction: PropTypes.shape({
condition: PropTypes.object, condition: PropTypes.object,
data: PropTypes.string, data: PropTypes.string,
@ -75,10 +75,10 @@ class TransactionPending extends Component {
gasPrice: this.props.transaction.gasPrice.toFixed() gasPrice: this.props.transaction.gasPrice.toFixed()
}); });
hwstore = HardwareStore.get(this.context.api); hardwareStore = HardwareStore.get(this.context.api);
componentWillMount () { componentWillMount () {
const { signerstore, transaction } = this.props; const { signerStore, transaction } = this.props;
const { from, gas, gasPrice, to, value } = transaction; const { from, gas, gasPrice, to, value } = transaction;
const fee = tUtil.getFee(gas, gasPrice); // BigNumber object const fee = tUtil.getFee(gas, gasPrice); // BigNumber object
@ -88,7 +88,7 @@ class TransactionPending extends Component {
this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay }); this.setState({ gasPriceEthmDisplay, totalValue, gasToDisplay });
this.gasStore.setEthValue(value); this.gasStore.setEthValue(value);
signerstore.fetchBalances([from, to]); signerStore.fetchBalances([from, to]);
} }
render () { render () {
@ -98,13 +98,13 @@ class TransactionPending extends Component {
} }
renderTransaction () { 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 { totalValue } = this.state;
const { balances, externalLink } = signerstore; const { balances, externalLink } = signerStore;
const { from, value } = transaction; const { from, value } = transaction;
const fromBalance = balances[from]; const fromBalance = balances[from];
const account = accounts[from] || {}; const account = accounts[from] || {};
const disabled = account.hardware && !this.hwstore.isConnected(from); const disabled = account.hardware && !this.hardwareStore.isConnected(from);
return ( return (
<div className={ `${styles.container} ${className}` }> <div className={ `${styles.container} ${className}` }>

View File

@ -101,7 +101,7 @@ class Embedded extends Component {
onReject={ actions.startRejectRequest } onReject={ actions.startRejectRequest }
origin={ origin } origin={ origin }
payload={ payload } payload={ payload }
signerstore={ this.store } signerStore={ this.store }
/> />
); );
} }

View File

@ -141,7 +141,7 @@ class RequestsPage extends Component {
onReject={ actions.startRejectRequest } onReject={ actions.startRejectRequest }
origin={ origin } origin={ origin }
payload={ payload } payload={ payload }
signerstore={ this.store } signerStore={ this.store }
/> />
); );
} }

View File

@ -16,6 +16,8 @@
//! Validator set deserialization. //! Validator set deserialization.
use std::collections::BTreeMap;
use uint::Uint;
use hash::Address; use hash::Address;
/// Different ways of specifying validators. /// 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. /// Address of a contract that indicates the list of authorities and enables reporting of theor misbehaviour using transactions.
#[serde(rename="contract")] #[serde(rename="contract")]
Contract(Address), Contract(Address),
/// A map of starting blocks for each validator set.
#[serde(rename="multi")]
Multi(BTreeMap<Uint, ValidatorSet>),
} }
#[cfg(test)] #[cfg(test)]
@ -40,11 +45,17 @@ mod tests {
#[test] #[test]
fn validator_set_deserialization() { fn validator_set_deserialization() {
let s = r#"[{ 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<ValidatorSet> = serde_json::from_str(s).unwrap(); let _deserialized: Vec<ValidatorSet> = serde_json::from_str(s).unwrap();

View File

@ -26,7 +26,6 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength) let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
var parityPid: Int32? = nil var parityPid: Int32? = nil
var commandLine: [String] = [] 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}" let defaultDefaults = "{\"fat_db\":false,\"mode\":\"passive\",\"mode.alarm\":3600,\"mode.timeout\":300,\"pruning\":\"fast\",\"tracing\":false}"
func menuAppPath() -> String { func menuAppPath() -> String {
@ -51,7 +50,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func killParity() { func killParity() {
if let pid = self.parityPid { 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") 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 {} catch {}
} }

View File

@ -75,6 +75,9 @@ extern crate ethcore_secretstore;
#[cfg(feature = "dapps")] #[cfg(feature = "dapps")]
extern crate ethcore_dapps; extern crate ethcore_dapps;
#[cfg(windows)] extern crate ws2_32;
#[cfg(windows)] extern crate winapi;
macro_rules! dependency { macro_rules! dependency {
($dep_ty:ident, $url:expr) => { ($dep_ty:ident, $url:expr) => {
{ {
@ -123,7 +126,7 @@ mod stratum;
use std::{process, env}; use std::{process, env};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::{self as stdio, BufReader, Read, Write}; 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 std::path::PathBuf;
use util::sha3::sha3; use util::sha3::sha3;
use cli::Args; use cli::Args;
@ -210,10 +213,11 @@ fn latest_exe_path() -> Option<PathBuf> {
} }
fn set_spec_name_override(spec_name: String) { fn set_spec_name_override(spec_name: String) {
if let Err(e) = File::create(updates_path("spec_name_overide")) if let Err(e) = create_dir_all(default_hypervisor_path())
.and_then(|mut f| f.write_all(spec_name.as_bytes())) .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<String> {
#[cfg(windows)] #[cfg(windows)]
fn global_cleanup() { 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. // 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 // 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. // initialize it. There's at least 2 now.
for _ in 0.. 10 { 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. // Starts ~/.parity-updates/parity and returns the code it exits with.
fn run_parity() -> Option<i32> { fn run_parity() -> Option<i32> {
global_cleanup(); global_init();
use ::std::ffi::OsString; use ::std::ffi::OsString;
let prefix = vec![OsString::from("--can-restart"), OsString::from("--force-direct")]; 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::<Vec<_>>())) .args(&(env::args_os().skip(1).chain(prefix.into_iter()).collect::<Vec<_>>()))
.status() .status()
.map(|es| es.code().unwrap_or(128)) .map(|es| es.code().unwrap_or(128))
.ok() .ok()
) );
global_cleanup();
res
} }
const PLEASE_RESTART_EXIT_CODE: i32 = 69; const PLEASE_RESTART_EXIT_CODE: i32 = 69;
@ -257,10 +275,11 @@ const PLEASE_RESTART_EXIT_CODE: i32 = 69;
// Run our version of parity. // Run our version of parity.
// Returns the exit error code. // Returns the exit error code.
fn main_direct(can_restart: bool) -> i32 { fn main_direct(can_restart: bool) -> i32 {
global_init();
let mut alt_mains = HashMap::new(); let mut alt_mains = HashMap::new();
sync_main(&mut alt_mains); sync_main(&mut alt_mains);
stratum_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(); f();
0 0
} else { } else {
@ -280,7 +299,9 @@ fn main_direct(can_restart: bool) -> i32 {
1 1
}, },
} }
} };
global_cleanup();
res
} }
fn println_trace_main(s: String) { fn println_trace_main(s: String) {

View File

@ -20,6 +20,7 @@ use std::collections::BTreeMap;
use util::{Address}; use util::{Address};
use ethkey::{Brain, Generator, Secret}; use ethkey::{Brain, Generator, Secret};
use ethstore::KeyFile;
use ethcore::account_provider::AccountProvider; use ethcore::account_provider::AccountProvider;
use jsonrpc_core::Error; use jsonrpc_core::Error;
@ -315,6 +316,17 @@ impl ParityAccounts for ParityAccountsClient {
.map(Into::into) .map(Into::into)
.map_err(|e| errors::account("Could not derive account.", e)) .map_err(|e| errors::account("Could not derive account.", e))
} }
fn export_account(&self, addr: RpcH160, password: String) -> Result<KeyFile, Error> {
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, B>(a: Vec<A>) -> Vec<B> where fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where

View File

@ -472,3 +472,30 @@ fn derive_key_index() {
let res = tester.io.handle_request_sync(&request); let res = tester.io.handle_request_sync(&request);
assert_eq!(res, Some(response.into())); 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()));
}

View File

@ -18,6 +18,7 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use jsonrpc_core::Error; use jsonrpc_core::Error;
use ethstore::KeyFile;
use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical}; use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical};
build_rpc_trait! { build_rpc_trait! {
@ -175,5 +176,9 @@ build_rpc_trait! {
/// Resulting address can be either saved as a new account (with the same password). /// Resulting address can be either saved as a new account (with the same password).
#[rpc(name = "parity_deriveAddressIndex")] #[rpc(name = "parity_deriveAddressIndex")]
fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result<H160, Error>; fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result<H160, Error>;
/// Exports an account with given address if provided password matches.
#[rpc(name = "parity_exportAccount")]
fn export_account(&self, H160, String) -> Result<KeyFile, Error>;
} }
} }