Merge branch 'master' into lightcli
This commit is contained in:
commit
da837fa9d8
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -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)",
|
||||||
]
|
]
|
||||||
|
@ -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" }
|
||||||
|
42
ethcore/res/validator_multi.json
Normal file
42
ethcore/res/validator_multi.json
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
158
ethcore/src/engines/validator_set/multi.rs
Normal file
158
ethcore/src/engines/validator_set/multi.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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)]
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>>>,
|
||||||
|
@ -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 {
|
||||||
|
@ -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
96
ethstore/src/dir/paths.rs
Normal 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
|
||||||
|
}
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,48 +460,48 @@ 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<_>>();
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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};
|
||||||
|
@ -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,
|
||||||
|
@ -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];
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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'",
|
||||||
|
@ -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,9 +150,35 @@ class PasswordManager extends Component {
|
|||||||
}
|
}
|
||||||
onActive={ this.onActivateTestTab }
|
onActive={ this.onActivateTestTab }
|
||||||
>
|
>
|
||||||
|
{ this.renderTabTest() }
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id='passwordChange.tabChange.label'
|
||||||
|
defaultMessage='Change Password'
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onActive={ this.onActivateChangeTab }
|
||||||
|
>
|
||||||
|
{ this.renderTabChange() }
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTabTest () {
|
||||||
|
const { actionTab, busy } = this.store;
|
||||||
|
|
||||||
|
if (actionTab !== TEST_ACTION) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Form className={ styles.form }>
|
<Form className={ styles.form }>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
|
autoFocus
|
||||||
disabled={ busy }
|
disabled={ busy }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -172,19 +199,21 @@ class PasswordManager extends Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Tab>
|
);
|
||||||
<Tab
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id='passwordChange.tabChange.label'
|
|
||||||
defaultMessage='Change Password'
|
|
||||||
/>
|
|
||||||
}
|
}
|
||||||
onActive={ this.onActivateChangeTab }
|
|
||||||
>
|
renderTabChange () {
|
||||||
|
const { actionTab, busy, isRepeatValid, newPassword, passwordHint } = this.store;
|
||||||
|
|
||||||
|
if (actionTab !== CHANGE_ACTION) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
<Form className={ styles.form }>
|
<Form className={ styles.form }>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
|
autoFocus
|
||||||
disabled={ busy }
|
disabled={ busy }
|
||||||
hint={
|
hint={
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@ -274,8 +303,6 @@ class PasswordManager extends Component {
|
|||||||
<PasswordStrength input={ newPassword } />
|
<PasswordStrength input={ newPassword } />
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</Tab>
|
|
||||||
</Tabs>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;*/
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -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];
|
||||||
|
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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}` }>
|
||||||
|
@ -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 }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
@ -45,6 +50,12 @@ mod tests {
|
|||||||
"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();
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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()));
|
||||||
|
}
|
||||||
|
@ -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>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user