Merge branch 'master' into lightrpc

This commit is contained in:
Robert Habermeier 2017-02-07 17:13:18 +01:00
commit 1fa5b07321
34 changed files with 1195 additions and 249 deletions

23
Cargo.lock generated
View File

@ -162,7 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "byteorder"
version = "0.5.3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@ -324,8 +324,8 @@ dependencies = [
[[package]]
name = "eth-secp256k1"
version = "0.5.4"
source = "git+https://github.com/ethcore/rust-secp256k1#a9a0b1be1f39560ca86e8fc8e55e205a753ff25c"
version = "0.5.6"
source = "git+https://github.com/ethcore/rust-secp256k1#edab95f5569e4fb97579dc8947be96e7ac789c16"
dependencies = [
"arrayvec 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
@ -363,7 +363,7 @@ version = "1.6.0"
dependencies = [
"bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@ -668,7 +668,7 @@ dependencies = [
"clippy 0.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
"elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)",
"env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)",
"eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)",
"ethcore-bigint 0.1.2",
"ethcore-bloom-journal 0.1.0",
"ethcore-devtools 1.6.0",
@ -699,7 +699,7 @@ dependencies = [
name = "ethcrypto"
version = "0.1.0"
dependencies = [
"eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)",
"eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)",
"ethcore-bigint 0.1.2",
"ethkey 0.2.0",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
@ -721,11 +721,13 @@ dependencies = [
name = "ethkey"
version = "0.2.0"
dependencies = [
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)",
"eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)",
"ethcore-bigint 0.1.2",
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
"tiny-keccak 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -735,6 +737,7 @@ name = "ethstore"
version = "0.1.0"
dependencies = [
"docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)",
"ethcore-devtools 1.6.0",
"ethcore-util 1.6.0",
"ethcrypto 0.1.0",
"ethkey 0.2.0",
@ -1572,7 +1575,7 @@ dependencies = [
[[package]]
name = "parity-ui-precompiled"
version = "1.4.0"
source = "git+https://github.com/ethcore/js-precompiled.git#4110b5bc85a15ae3f0b5c02b1c3caf8423f51b50"
source = "git+https://github.com/ethcore/js-precompiled.git#a590186c6acf75e31b7cff259721793960ded4e1"
dependencies = [
"parity-dapps-glue 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
@ -2491,7 +2494,7 @@ dependencies = [
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum blastfig 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "09640e0509d97d5cdff03a9f5daf087a8e04c735c3b113a75139634a19cfc7b2"
"checksum bloomchain 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f421095d2a76fc24cd3fb3f912b90df06be7689912b1bdb423caefae59c258d"
"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
"checksum bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c129aff112dcc562970abb69e2508b40850dd24c274761bb50fb8a0067ba6c27"
"checksum bytes 0.4.0-dev (git+https://github.com/carllerche/bytes)" = "<none>"
"checksum cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de1e760d7b6535af4241fca8bd8adf68e2e7edacc6b29f5d399050c5e48cf88c"
@ -2512,7 +2515,7 @@ dependencies = [
"checksum either 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2b503c86dad62aaf414ecf2b8c527439abedb3f8d812537f0b12bfd6f32a91"
"checksum elastic-array 0.6.0 (git+https://github.com/ethcore/elastic-array)" = "<none>"
"checksum env_logger 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "aba65b63ffcc17ffacd6cf5aa843da7c5a25e3bd4bbe0b7def8b214e411250e5"
"checksum eth-secp256k1 0.5.4 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
"checksum eth-secp256k1 0.5.6 (git+https://github.com/ethcore/rust-secp256k1)" = "<none>"
"checksum ethabi 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0c53453517f620847be51943db329276ae52f2e210cfc659e81182864be2f"
"checksum fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1ee15a7050e5580b3712877157068ea713b245b080ff302ae2ca973cfcd9baa"
"checksum flate2 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "3eeb481e957304178d2e782f2da1257f1434dfecbae883bafb61ada2a9fea3bb"

View File

@ -2,7 +2,7 @@ FROM ubuntu:14.04
WORKDIR /build
# install tools and dependencies
RUN apt-get update && \
apt-get install --force-yes --no-install-recommends \
apt-get install -y --force-yes --no-install-recommends \
# make
build-essential \
# add-apt-repository
@ -56,7 +56,7 @@ g++ -v
RUN git clone https://github.com/ethcore/parity && \
cd parity && \
git pull && \
cargo build --release --features final --verbose && \
cargo build --release --features final && \
ls /build/parity/target/release/parity && \
strip /build/parity/target/release/parity

View File

@ -24,7 +24,7 @@ semver = "0.5"
bit-set = "0.4"
time = "0.1"
rand = "0.3"
byteorder = "0.5"
byteorder = "1.0"
transient-hashmap = "0.1"
linked-hash-map = "0.3.0"
evmjit = { path = "../evmjit", optional = true }

View File

@ -280,7 +280,7 @@ impl AccountProvider {
/// Returns each account along with name and meta.
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
Ok(AccountMeta {
name: self.sstore.name(&account)?,
meta: self.sstore.meta(&account)?,
@ -290,38 +290,38 @@ impl AccountProvider {
/// Returns each account along with name and meta.
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
self.sstore.set_name(&StoreAccountRef::root(address), name)?;
self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?;
Ok(())
}
/// Returns each account along with name and meta.
pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> {
self.sstore.set_meta(&StoreAccountRef::root(address), meta)?;
self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?;
Ok(())
}
/// Returns `true` if the password for `account` is `password`. `false` if not.
pub fn test_password(&self, address: &Address, password: &str) -> Result<bool, Error> {
self.sstore.test_password(&StoreAccountRef::root(address.clone()), password)
self.sstore.test_password(&self.sstore.account_ref(&address)?, password)
.map_err(Into::into)
}
/// Permanently removes an account.
pub fn kill_account(&self, address: &Address, password: &str) -> Result<(), Error> {
self.sstore.remove_account(&StoreAccountRef::root(address.clone()), &password)?;
self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?;
Ok(())
}
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
pub fn change_password(&self, account: &Address, password: String, new_password: String) -> Result<(), Error> {
self.sstore.change_password(&StoreAccountRef::root(account.clone()), &password, &new_password)
pub fn change_password(&self, address: &Address, password: String, new_password: String) -> Result<(), Error> {
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
}
/// Helper method used for unlocking accounts.
fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> {
// verify password by signing dump message
// result may be discarded
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
let _ = self.sstore.sign(&account, &password, &Default::default())?;
// check if account is already unlocked pernamently, if it is, do nothing
@ -374,20 +374,21 @@ impl AccountProvider {
/// Checks if given account is unlocked
pub fn is_unlocked(&self, address: Address) -> bool {
let unlocked = self.unlocked.read();
let account = StoreAccountRef::root(address);
unlocked.get(&account).is_some()
self.sstore.account_ref(&address)
.map(|r| unlocked.get(&r).is_some())
.unwrap_or(false)
}
/// Signs the message. If password is not provided the account must be unlocked.
pub fn sign(&self, address: Address, password: Option<String>, message: Message) -> Result<Signature, SignError> {
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
Ok(self.sstore.sign(&account, &password, &message)?)
}
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> {
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
let is_std_password = self.sstore.test_password(&account, &token)?;
let new_token = random_string(16);
@ -410,7 +411,7 @@ impl AccountProvider {
pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
-> Result<(Vec<u8>, AccountToken), SignError>
{
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
let is_std_password = self.sstore.test_password(&account, &token)?;
let new_token = random_string(16);
@ -431,7 +432,7 @@ impl AccountProvider {
/// Decrypts a message. If password is not provided the account must be unlocked.
pub fn decrypt(&self, address: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, SignError> {
let account = StoreAccountRef::root(address);
let account = self.sstore.account_ref(&address)?;
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?)
}
@ -447,6 +448,51 @@ impl AccountProvider {
.map(|a| a.into_iter().map(|a| a.address).collect())
.map_err(Into::into)
}
/// Create new vault.
pub fn create_vault(&self, name: &str, password: &str) -> Result<(), Error> {
self.sstore.create_vault(name, password)
.map_err(Into::into)
}
/// Open existing vault.
pub fn open_vault(&self, name: &str, password: &str) -> Result<(), Error> {
self.sstore.open_vault(name, password)
.map_err(Into::into)
}
/// Close previously opened vault.
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
self.sstore.close_vault(name)
.map_err(Into::into)
}
/// List all vaults
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
self.sstore.list_vaults()
.map_err(Into::into)
}
/// List all currently opened vaults
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
self.sstore.list_opened_vaults()
.map_err(Into::into)
}
/// Change vault password.
pub fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> {
self.sstore.change_vault_password(name, new_password)
.map_err(Into::into)
}
/// Change vault of the given address.
pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> {
let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) };
let old_account_ref = self.sstore.account_ref(&address)?;
self.sstore.change_account_vault(new_vault_ref, old_account_ref)
.map_err(Into::into)
.map(|_| ())
}
}
#[cfg(test)]

View File

@ -233,7 +233,7 @@ impl Decodable for BlockReceipts {
impl Encodable for BlockReceipts {
fn rlp_append(&self, s: &mut RlpStream) {
s.append(&self.receipts);
Encodable::rlp_append(&self.receipts, s);
}
}
@ -242,3 +242,21 @@ impl HeapSizeOf for BlockReceipts {
self.receipts.heap_size_of_children()
}
}
#[cfg(test)]
mod tests {
use rlp::*;
use super::BlockReceipts;
#[test]
fn encode_block_receipts() {
let br = BlockReceipts::new(Vec::new());
let mut s = RlpStream::new_list(2);
s.append(&br);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&br);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
}

View File

@ -20,7 +20,7 @@ use util::*;
use super::{Height, View, BlockHash, Step};
use error::Error;
use header::Header;
use rlp::{Rlp, UntrustedRlp, RlpStream, Stream, Encodable, Decodable, Decoder, DecoderError, View as RlpView};
use rlp::{Rlp, UntrustedRlp, RlpStream, Stream, RlpEncodable, Encodable, Decodable, Decoder, DecoderError, View as RlpView};
use ethkey::{recover, public_to_address};
use super::super::vote_collector::Message;
@ -162,7 +162,7 @@ impl Decodable for Step {
impl Encodable for Step {
fn rlp_append(&self, s: &mut RlpStream) {
s.append(&self.number());
RlpEncodable::rlp_append(&self.number(), s);
}
}
@ -193,8 +193,7 @@ impl Encodable for ConsensusMessage {
}
pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes {
// TODO: figure out whats wrong with nested list encoding
let mut s = RlpStream::new_list(5);
let mut s = RlpStream::new_list(4);
s.append(&vote_step.height).append(&vote_step.view).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero));
s.out()
}
@ -215,6 +214,18 @@ mod tests {
use super::super::Step;
use super::*;
#[test]
fn encode_step() {
let step = Step::Precommit;
let mut s = RlpStream::new_list(2);
s.append(&step);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&step);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
#[test]
fn encode_decode() {
let message = ConsensusMessage {

View File

@ -46,7 +46,7 @@ impl Encodable for CallType {
CallType::CallCode => 2,
CallType::DelegateCall => 3,
};
s.append(&v);
Encodable::rlp_append(&v, s);
}
}
@ -212,12 +212,28 @@ impl fmt::Display for CallError {
/// Transaction execution result.
pub type ExecutionResult = Result<Executed, ExecutionError>;
#[test]
fn should_encode_and_decode_call_type() {
use rlp;
#[cfg(test)]
mod tests {
use rlp::*;
use super::CallType;
#[test]
fn encode_call_type() {
let ct = CallType::Call;
let mut s = RlpStream::new_list(2);
s.append(&ct);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&ct);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
#[test]
fn should_encode_and_decode_call_type() {
let original = CallType::Call;
let encoded = rlp::encode(&original);
let decoded = rlp::decode(&encoded);
let encoded = encode(&original);
let decoded = decode(&encoded);
assert_eq!(original, decoded);
}
}

View File

@ -17,7 +17,7 @@
//! Trace errors.
use std::fmt;
use rlp::{Encodable, RlpStream, Decodable, Decoder, DecoderError, Stream, View};
use rlp::{RlpEncodable, Encodable, RlpStream, Decodable, Decoder, DecoderError, View};
use evm::Error as EvmError;
/// Trace evm errors.
@ -79,7 +79,7 @@ impl Encodable for Error {
OutOfStack => 4,
Internal => 5,
};
s.append(&value);
RlpEncodable::rlp_append(&value, s);
}
}
@ -98,3 +98,21 @@ impl Decodable for Error {
}
}
}
#[cfg(test)]
mod tests {
use rlp::*;
use super::Error;
#[test]
fn encode_error() {
let err = Error::BadJumpDestination;
let mut s = RlpStream::new_list(2);
s.append(&err);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&err);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
}

View File

@ -103,7 +103,7 @@ impl FlatTransactionTraces {
impl Encodable for FlatTransactionTraces {
fn rlp_append(&self, s: &mut RlpStream) {
s.append(&self.0);
Encodable::rlp_append(&self.0, s);
}
}
@ -144,7 +144,7 @@ impl FlatBlockTraces {
impl Encodable for FlatBlockTraces {
fn rlp_append(&self, s: &mut RlpStream) {
s.append(&self.0);
Encodable::rlp_append(&self.0, s);
}
}
@ -162,10 +162,35 @@ impl Into<Vec<FlatTransactionTraces>> for FlatBlockTraces {
#[cfg(test)]
mod tests {
use rlp::*;
use super::{FlatBlockTraces, FlatTransactionTraces, FlatTrace};
use trace::trace::{Action, Res, CallResult, Call, Suicide};
use types::executed::CallType;
#[test]
fn encode_flat_transaction_traces() {
let ftt = FlatTransactionTraces::from(Vec::new());
let mut s = RlpStream::new_list(2);
s.append(&ftt);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&ftt);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
#[test]
fn encode_flat_block_traces() {
let fbt = FlatBlockTraces::from(Vec::new());
let mut s = RlpStream::new_list(2);
s.append(&fbt);
assert!(!s.is_finished(), "List shouldn't finished yet");
s.append(&fbt);
assert!(s.is_finished(), "List should be finished now");
s.out();
}
#[test]
fn test_trace_serialization() {
// block #51921

View File

@ -11,6 +11,8 @@ eth-secp256k1 = { git = "https://github.com/ethcore/rust-secp256k1" }
rustc-serialize = "0.3"
docopt = { version = "0.6", optional = true }
ethcore-bigint = { path = "../util/bigint" }
rust-crypto = "0.2"
byteorder = "1.0"
[features]
default = []

448
ethkey/src/extended.rs Normal file
View File

@ -0,0 +1,448 @@
// 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/>.
//! Extended keys
use secret::Secret;
use Public;
use bigint::hash::{H256, FixedHash};
pub use self::derivation::Error as DerivationError;
/// Extended secret key, allows deterministic derivation of subsequent keys.
pub struct ExtendedSecret {
secret: Secret,
chain_code: H256,
}
impl ExtendedSecret {
/// New extended key from given secret and chain code.
pub fn with_code(secret: Secret, chain_code: H256) -> ExtendedSecret {
ExtendedSecret {
secret: secret,
chain_code: chain_code,
}
}
/// New extended key from given secret with the random chain code.
pub fn new_random(secret: Secret) -> ExtendedSecret {
ExtendedSecret::with_code(secret, H256::random())
}
/// New extended key from given secret.
/// Chain code will be derived from the secret itself (in a deterministic way).
pub fn new(secret: Secret) -> ExtendedSecret {
let chain_code = derivation::chain_code(*secret);
ExtendedSecret::with_code(secret, chain_code)
}
/// Derive new private key
pub fn derive(&self, index: u32) -> ExtendedSecret {
let (derived_key, next_chain_code) = derivation::private(*self.secret, self.chain_code, index);
let derived_secret = Secret::from_slice(&*derived_key)
.expect("Derivation always produced a valid private key; qed");
ExtendedSecret::with_code(derived_secret, next_chain_code)
}
/// Private key component of the extended key.
pub fn secret(&self) -> &Secret {
&self.secret
}
}
/// Extended public key, allows deterministic derivation of subsequent keys.
pub struct ExtendedPublic {
public: Public,
chain_code: H256,
}
impl ExtendedPublic {
/// New extended public key from known parent and chain code
pub fn new(public: Public, chain_code: H256) -> Self {
ExtendedPublic { public: public, chain_code: chain_code }
}
/// Create new extended public key from known secret
pub fn from_secret(secret: &ExtendedSecret) -> Result<Self, DerivationError> {
Ok(
ExtendedPublic::new(
derivation::point(**secret.secret())?,
secret.chain_code.clone(),
)
)
}
/// Derive new public key
/// Operation is defined only for index belongs [0..2^31)
pub fn derive(&self, index: u32) -> Result<Self, DerivationError> {
let (derived_key, next_chain_code) = derivation::public(self.public, self.chain_code, index)?;
Ok(ExtendedPublic::new(derived_key, next_chain_code))
}
pub fn public(&self) -> &Public {
&self.public
}
}
pub struct ExtendedKeyPair {
secret: ExtendedSecret,
public: ExtendedPublic,
}
impl ExtendedKeyPair {
pub fn new(secret: Secret) -> Self {
let extended_secret = ExtendedSecret::new(secret);
let extended_public = ExtendedPublic::from_secret(&extended_secret)
.expect("Valid `Secret` always produces valid public; qed");
ExtendedKeyPair {
secret: extended_secret,
public: extended_public,
}
}
pub fn with_code(secret: Secret, public: Public, chain_code: H256) -> Self {
ExtendedKeyPair {
secret: ExtendedSecret::with_code(secret, chain_code.clone()),
public: ExtendedPublic::new(public, chain_code),
}
}
pub fn with_secret(secret: Secret, chain_code: H256) -> Self {
let extended_secret = ExtendedSecret::with_code(secret, chain_code);
let extended_public = ExtendedPublic::from_secret(&extended_secret)
.expect("Valid `Secret` always produces valid public; qed");
ExtendedKeyPair {
secret: extended_secret,
public: extended_public,
}
}
pub fn with_seed(seed: &[u8]) -> Result<ExtendedKeyPair, DerivationError> {
let (master_key, chain_code) = derivation::seed_pair(seed);
Ok(ExtendedKeyPair::with_secret(
Secret::from_slice(&*master_key).map_err(|_| DerivationError::InvalidSeed)?,
chain_code,
))
}
pub fn secret(&self) -> &ExtendedSecret {
&self.secret
}
pub fn public(&self) -> &ExtendedPublic {
&self.public
}
pub fn derive(&self, index: u32) -> Result<Self, DerivationError> {
let derived = self.secret.derive(index);
Ok(ExtendedKeyPair {
public: ExtendedPublic::from_secret(&derived)?,
secret: derived,
})
}
}
// Derivation functions for private and public keys
// Work is based on BIP0032
// https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
mod derivation {
use rcrypto::hmac::Hmac;
use rcrypto::mac::Mac;
use rcrypto::sha2::Sha512;
use bigint::hash::{H512, H256, FixedHash};
use bigint::prelude::{U256, U512, Uint};
use byteorder::{BigEndian, ByteOrder};
use secp256k1;
use secp256k1::key::{SecretKey, PublicKey};
use SECP256K1;
use keccak;
#[derive(Debug)]
pub enum Error {
InvalidHardenedUse,
InvalidPoint,
MissingIndex,
InvalidSeed,
}
// Deterministic derivation of the key using secp256k1 elliptic curve.
// Derivation can be either hardened or not.
// For hardened derivation, pass index at least 2^31
//
// Can panic if passed `private_key` is not a valid secp256k1 private key
// (outside of (0..curve_n()]) field
pub fn private(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
if index < (2 << 30) {
private_soft(private_key, chain_code, index)
}
else {
private_hard(private_key, chain_code, index)
}
}
fn hmac_pair(data: [u8; 37], private_key: H256, chain_code: H256) -> (H256, H256) {
let private: U256 = private_key.into();
// produces 512-bit derived hmac (I)
let mut hmac = Hmac::new(Sha512::new(), &*chain_code);
let mut i_512 = [0u8; 64];
hmac.input(&data[..]);
hmac.raw_result(&mut i_512);
// left most 256 bits are later added to original private key
let hmac_key: U256 = H256::from_slice(&i_512[0..32]).into();
// right most 256 bits are new chain code for later derivations
let next_chain_code = H256::from(&i_512[32..64]);
let child_key = private_add(hmac_key, private).into();
(child_key, next_chain_code)
}
// Can panic if passed `private_key` is not a valid secp256k1 private key
// (outside of (0..curve_n()]) field
fn private_soft(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
let mut data = [0u8; 37];
let sec_private = SecretKey::from_slice(&SECP256K1, &*private_key)
.expect("Caller should provide valid private key");
let sec_public = PublicKey::from_secret_key(&SECP256K1, &sec_private)
.expect("Caller should provide valid private key");
let public_serialized = sec_public.serialize_vec(&SECP256K1, true);
// curve point (compressed public key) -- index
// 0.33 -- 33..37
data[0..33].copy_from_slice(&public_serialized);
BigEndian::write_u32(&mut data[33..37], index);
hmac_pair(data, private_key, chain_code)
}
// Deterministic derivation of the key using secp256k1 elliptic curve
// This is hardened derivation and does not allow to associate
// corresponding public keys of the original and derived private keys
fn private_hard(private_key: H256, chain_code: H256, index: u32) -> (H256, H256) {
let mut data = [0u8; 37];
let private: U256 = private_key.into();
// 0x00 (padding) -- private_key -- index
// 0 -- 1..33 -- 33..37
private.to_big_endian(&mut data[1..33]);
BigEndian::write_u32(&mut data[33..37], index);
hmac_pair(data, private_key, chain_code)
}
fn private_add(k1: U256, k2: U256) -> U256 {
let sum = U512::from(k1) + U512::from(k2);
modulo(sum, curve_n())
}
// todo: surely can be optimized
fn modulo(u1: U512, u2: U256) -> U256 {
let dv = u1 / U512::from(u2);
let md = u1 - (dv * U512::from(u2));
md.into()
}
// returns n (for mod(n)) for the secp256k1 elliptic curve
// todo: maybe lazy static
fn curve_n() -> U256 {
H256::from_slice(&secp256k1::constants::CURVE_ORDER).into()
}
pub fn public(public_key: H512, chain_code: H256, index: u32) -> Result<(H512, H256), Error> {
if index >= (2 << 30) {
// public derivation is only defined on 'soft' index space [0..2^31)
return Err(Error::InvalidHardenedUse)
}
let mut public_sec_raw = [0u8; 65];
public_sec_raw[0] = 4;
public_sec_raw[1..65].copy_from_slice(&*public_key);
let public_sec = PublicKey::from_slice(&SECP256K1, &public_sec_raw).map_err(|_| Error::InvalidPoint)?;
let public_serialized = public_sec.serialize_vec(&SECP256K1, true);
let mut data = [0u8; 37];
// curve point (compressed public key) -- index
// 0.33 -- 33..37
data[0..33].copy_from_slice(&public_serialized);
BigEndian::write_u32(&mut data[33..37], index);
// HMAC512SHA produces [derived private(256); new chain code(256)]
let mut hmac = Hmac::new(Sha512::new(), &*chain_code);
let mut i_512 = [0u8; 64];
hmac.input(&data[..]);
hmac.raw_result(&mut i_512);
let new_private = H256::from(&i_512[0..32]);
let new_chain_code = H256::from(&i_512[32..64]);
// Generated private key can (extremely rarely) be out of secp256k1 key field
if curve_n() <= new_private.clone().into() { return Err(Error::MissingIndex); }
let new_private_sec = SecretKey::from_slice(&SECP256K1, &*new_private)
.expect("Private key belongs to the field [0..CURVE_ORDER) (checked above); So initializing can never fail; qed");
let mut new_public = PublicKey::from_secret_key(&SECP256K1, &new_private_sec)
.expect("Valid private key produces valid public key");
// Adding two points on the elliptic curves (combining two public keys)
new_public.add_assign(&SECP256K1, &public_sec)
.expect("Addition of two valid points produce valid point");
let serialized = new_public.serialize_vec(&SECP256K1, false);
Ok((
H512::from(&serialized[1..65]),
new_chain_code,
))
}
fn sha3(slc: &[u8]) -> H256 {
keccak::Keccak256::keccak256(slc).into()
}
pub fn chain_code(secret: H256) -> H256 {
// 10,000 rounds of sha3
let mut running_sha3 = sha3(&*secret);
for _ in 0..99999 { running_sha3 = sha3(&*running_sha3); }
running_sha3
}
pub fn point(secret: H256) -> Result<H512, Error> {
let sec = SecretKey::from_slice(&SECP256K1, &*secret)
.map_err(|_| Error::InvalidPoint)?;
let public_sec = PublicKey::from_secret_key(&SECP256K1, &sec)
.map_err(|_| Error::InvalidPoint)?;
let serialized = public_sec.serialize_vec(&SECP256K1, false);
Ok(H512::from(&serialized[1..65]))
}
pub fn seed_pair(seed: &[u8]) -> (H256, H256) {
let mut hmac = Hmac::new(Sha512::new(), b"Bitcoin seed");
let mut i_512 = [0u8; 64];
hmac.input(seed);
hmac.raw_result(&mut i_512);
let master_key = H256::from_slice(&i_512[0..32]);
let chain_code = H256::from_slice(&i_512[32..64]);
(master_key, chain_code)
}
}
#[cfg(test)]
mod tests {
use super::{ExtendedSecret, ExtendedPublic, ExtendedKeyPair};
use secret::Secret;
use std::str::FromStr;
use bigint::hash::{H128, H256};
use super::derivation;
fn master_chain_basic() -> (H256, H256) {
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
.expect("Seed should be valid H128")
.to_vec();
derivation::seed_pair(&*seed)
}
fn test_extended<F>(f: F, test_private: H256) where F: Fn(ExtendedSecret) -> ExtendedSecret {
let (private_seed, chain_code) = master_chain_basic();
let extended_secret = ExtendedSecret::with_code(Secret::from_slice(&*private_seed).unwrap(), chain_code);
let derived = f(extended_secret);
assert_eq!(**derived.secret(), test_private);
}
#[test]
fn smoky() {
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
let extended_secret = ExtendedSecret::with_code(secret.clone(), 0u64.into());
// hardened
assert_eq!(&**extended_secret.secret(), &*secret);
assert_eq!(&**extended_secret.derive(2147483648).secret(), &"0927453daed47839608e414a3738dfad10aed17c459bbd9ab53f89b026c834b6".into());
assert_eq!(&**extended_secret.derive(2147483649).secret(), &"44238b6a29c6dcbe9b401364141ba11e2198c289a5fed243a1c11af35c19dc0f".into());
// normal
assert_eq!(&**extended_secret.derive(0).secret(), &"bf6a74e3f7b36fc4c96a1e12f31abc817f9f5904f5a8fc27713163d1f0b713f6".into());
assert_eq!(&**extended_secret.derive(1).secret(), &"bd4fca9eb1f9c201e9448c1eecd66e302d68d4d313ce895b8c134f512205c1bc".into());
assert_eq!(&**extended_secret.derive(2).secret(), &"86932b542d6cab4d9c65490c7ef502d89ecc0e2a5f4852157649e3251e2a3268".into());
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
let derived_public = extended_public.derive(0).expect("First derivation of public should succeed");
assert_eq!(&*derived_public.public(), &"f7b3244c96688f92372bfd4def26dc4151529747bab9f188a4ad34e141d47bd66522ff048bc6f19a0a4429b04318b1a8796c000265b4fa200dae5f6dda92dd94".into());
let keypair = ExtendedKeyPair::with_secret(
Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap(),
064.into(),
);
assert_eq!(&**keypair.derive(2147483648).expect("Derivation of keypair should succeed").secret().secret(), &"edef54414c03196557cf73774bc97a645c9a1df2164ed34f0c2a78d1375a930c".into());
}
#[test]
fn match_() {
let secret = Secret::from_str("a100df7a048e50ed308ea696dc600215098141cb391e9527329df289f9383f65").unwrap();
let extended_secret = ExtendedSecret::with_code(secret.clone(), 1.into());
let extended_public = ExtendedPublic::from_secret(&extended_secret).expect("Extended public should be created");
let derived_secret0 = extended_secret.derive(0);
let derived_public0 = extended_public.derive(0).expect("First derivation of public should succeed");
let public_from_secret0 = ExtendedPublic::from_secret(&derived_secret0).expect("Extended public should be created");
assert_eq!(public_from_secret0.public(), derived_public0.public());
}
#[test]
fn test_seeds() {
let seed = H128::from_str("000102030405060708090a0b0c0d0e0f")
.expect("Seed should be valid H128")
.to_vec();
/// private key from bitcoin test vector
/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
let test_private = H256::from_str("e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35")
.expect("Private should be decoded ok");
let (private_seed, _) = derivation::seed_pair(&*seed);
assert_eq!(private_seed, test_private);
}
#[test]
fn test_vector_1() {
/// xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7
/// H(0)
test_extended(
|secret| secret.derive(2147483648),
H256::from_str("edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea")
.expect("Private should be decoded ok")
);
}
#[test]
fn test_vector_2() {
/// xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs
/// H(0)/1
test_extended(
|secret| secret.derive(2147483648).derive(1),
H256::from_str("3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368")
.expect("Private should be decoded ok")
);
}
}

View File

@ -21,6 +21,8 @@ extern crate tiny_keccak;
extern crate secp256k1;
extern crate rustc_serialize;
extern crate ethcore_bigint as bigint;
extern crate crypto as rcrypto;
extern crate byteorder;
mod brain;
mod error;
@ -30,6 +32,7 @@ mod prefix;
mod random;
mod signature;
mod secret;
mod extended;
lazy_static! {
pub static ref SECP256K1: secp256k1::Secp256k1 = secp256k1::Secp256k1::new();
@ -48,6 +51,7 @@ pub use self::prefix::Prefix;
pub use self::random::Random;
pub use self::signature::{sign, verify_public, verify_address, recover, Signature};
pub use self::secret::Secret;
pub use self::extended::{ExtendedPublic, ExtendedSecret, ExtendedKeyPair, DerivationError};
use bigint::hash::{H160, H256, H512};

View File

@ -23,6 +23,7 @@ parking_lot = "0.3"
ethcrypto = { path = "../ethcrypto" }
ethcore-util = { path = "../util" }
smallvec = "0.3.1"
ethcore-devtools = { path = "../devtools" }
[build-dependencies]
serde_codegen = { version = "0.8", optional = true }

View File

@ -21,7 +21,7 @@ use time;
use {json, SafeAccount, Error};
use json::Uuid;
use super::{KeyDirectory, VaultKeyDirectory, VaultKeyDirectoryProvider, VaultKey};
use super::vault::VaultDiskDirectory;
use super::vault::{VAULT_FILE_NAME, VaultDiskDirectory};
const IGNORED_FILES: &'static [&'static str] = &[
"thumbs.db",
@ -193,7 +193,7 @@ impl<T> KeyDirectory for DiskDirectory<T> where T: KeyFileManager {
// and find entry with given address
let to_remove = self.files()?
.into_iter()
.find(|&(_, ref acc)| acc == account);
.find(|&(_, ref acc)| acc.id == account.id && acc.address == account.address);
// remove it
match to_remove {
@ -219,6 +219,21 @@ impl<T> VaultKeyDirectoryProvider for DiskDirectory<T> where T: KeyFileManager {
let vault_dir = VaultDiskDirectory::at(&self.path, name, key)?;
Ok(Box::new(vault_dir))
}
fn list_vaults(&self) -> Result<Vec<String>, Error> {
Ok(fs::read_dir(&self.path)?
.filter_map(|e| e.ok().map(|e| e.path()))
.filter_map(|path| {
let mut vault_file_path = path.clone();
vault_file_path.push(VAULT_FILE_NAME);
if vault_file_path.is_file() {
path.file_name().and_then(|f| f.to_str()).map(|f| f.to_owned())
} else {
None
}
})
.collect())
}
}
impl KeyFileManager for DiskKeyFileManager {
@ -240,6 +255,7 @@ mod test {
use dir::{KeyDirectory, VaultKey};
use account::SafeAccount;
use ethkey::{Random, Generator};
use devtools::RandomTempPath;
#[test]
fn should_create_new_account() {
@ -295,4 +311,20 @@ mod test {
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn should_list_vaults() {
// given
let temp_path = RandomTempPath::new();
let directory = RootDiskDirectory::create(&temp_path).unwrap();
let vault_provider = directory.as_vault_provider().unwrap();
vault_provider.create("vault1", VaultKey::new("password1", 1)).unwrap();
vault_provider.create("vault2", VaultKey::new("password2", 1)).unwrap();
// then
let vaults = vault_provider.list_vaults().unwrap();
assert_eq!(vaults.len(), 2);
assert!(vaults.iter().any(|v| &*v == "vault1"));
assert!(vaults.iter().any(|v| &*v == "vault2"));
}
}

View File

@ -70,6 +70,8 @@ pub trait VaultKeyDirectoryProvider {
fn create(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>;
/// Open existing vault with given key
fn open(&self, name: &str, key: VaultKey) -> Result<Box<VaultKeyDirectory>, Error>;
/// List all vaults
fn list_vaults(&self) -> Result<Vec<String>, Error>;
}
/// Vault directory
@ -78,8 +80,10 @@ pub trait VaultKeyDirectory: KeyDirectory {
fn as_key_directory(&self) -> &KeyDirectory;
/// Vault name
fn name(&self) -> &str;
/// Get vault key
fn key(&self) -> VaultKey;
/// Set new key for vault
fn set_key(&self, old_key: VaultKey, key: VaultKey) -> Result<(), SetKeyError>;
fn set_key(&self, key: VaultKey) -> Result<(), SetKeyError>;
}
pub use self::disk::RootDiskDirectory;

View File

@ -22,13 +22,15 @@ use super::super::account::Crypto;
use super::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
use super::disk::{DiskDirectory, KeyFileManager};
const VAULT_FILE_NAME: &'static str = "vault.json";
/// Name of vault metadata file
pub const VAULT_FILE_NAME: &'static str = "vault.json";
/// Vault directory implementation
pub type VaultDiskDirectory = DiskDirectory<VaultKeyFileManager>;
/// Vault key file manager
pub struct VaultKeyFileManager {
name: String,
key: VaultKey,
}
@ -48,7 +50,7 @@ impl VaultDiskDirectory {
return Err(err);
}
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key)))
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key)))
}
/// Open existing vault directory with given key
@ -62,7 +64,7 @@ impl VaultDiskDirectory {
// check that passed key matches vault file
check_vault_file(&vault_dir_path, &key)?;
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(key)))
Ok(DiskDirectory::new(vault_dir_path, VaultKeyFileManager::new(name, key)))
}
fn create_temp_vault(&self, key: VaultKey) -> Result<VaultDiskDirectory, Error> {
@ -84,12 +86,10 @@ impl VaultDiskDirectory {
}
}
fn copy_to_vault(&self, vault: &VaultDiskDirectory, vault_key: &VaultKey) -> Result<(), Error> {
let password = &self.key_manager().key.password;
fn copy_to_vault(&self, vault: &VaultDiskDirectory) -> Result<(), Error> {
for account in self.load()? {
let filename = account.filename.clone().expect("self is instance of DiskDirectory; DiskDirectory fills filename in load; qed");
let new_account = account.change_password(password, &vault_key.password, vault_key.iterations)?;
vault.insert_with_filename(new_account, filename)?;
vault.insert_with_filename(account, filename)?;
}
Ok(())
@ -107,19 +107,14 @@ impl VaultKeyDirectory for VaultDiskDirectory {
}
fn name(&self) -> &str {
self.path()
.expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed")
.file_name()
.expect("last component of path is checked in make_vault_dir_path; it contains no fs-specific characters; file_name only returns None if last component is fs-specific; qed")
.to_str()
.expect("last component of path is checked in make_vault_dir_path; it contains only valid unicode characters; to_str fails when file_name is not valid unicode; qed")
&self.key_manager().name
}
fn set_key(&self, key: VaultKey, new_key: VaultKey) -> Result<(), SetKeyError> {
if self.key_manager().key != key {
return Err(SetKeyError::NonFatalOld(Error::InvalidPassword));
fn key(&self) -> VaultKey {
self.key_manager().key.clone()
}
fn set_key(&self, new_key: VaultKey) -> Result<(), SetKeyError> {
let temp_vault = VaultDiskDirectory::create_temp_vault(self, new_key.clone()).map_err(|err| SetKeyError::NonFatalOld(err))?;
let mut source_path = temp_vault.path().expect("temp_vault is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
let mut target_path = self.path().expect("self is instance of DiskDirectory; DiskDirectory always returns path; qed").clone();
@ -127,7 +122,7 @@ impl VaultKeyDirectory for VaultDiskDirectory {
source_path.push("next");
target_path.push("next");
let temp_accounts = self.copy_to_vault(&temp_vault, &new_key)
let temp_accounts = self.copy_to_vault(&temp_vault)
.and_then(|_| temp_vault.load())
.map_err(|err| {
// ignore error, as we already processing error
@ -153,8 +148,9 @@ impl VaultKeyDirectory for VaultDiskDirectory {
}
impl VaultKeyFileManager {
pub fn new(key: VaultKey) -> Self {
pub fn new(name: &str, key: VaultKey) -> Self {
VaultKeyFileManager {
name: name.into(),
key: key,
}
}
@ -163,20 +159,16 @@ impl VaultKeyFileManager {
impl KeyFileManager for VaultKeyFileManager {
fn read<T>(&self, filename: Option<String>, reader: T) -> Result<SafeAccount, Error> where T: io::Read {
let vault_file = json::VaultKeyFile::load(reader).map_err(|e| Error::Custom(format!("{:?}", e)))?;
let safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
if !safe_account.check_password(&self.key.password) {
warn!("Invalid vault key file: {:?}", filename);
return Err(Error::InvalidPassword);
}
let mut safe_account = SafeAccount::from_vault_file(&self.key.password, vault_file, filename.clone())?;
safe_account.meta = json::insert_vault_name_to_json_meta(&safe_account.meta, &self.name)
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
Ok(safe_account)
}
fn write<T>(&self, account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
// all accounts share the same password
if !account.check_password(&self.key.password) {
return Err(Error::InvalidPassword);
}
fn write<T>(&self, mut account: SafeAccount, writer: &mut T) -> Result<(), Error> where T: io::Write {
account.meta = json::remove_vault_name_from_json_meta(&account.meta)
.map_err(|err| Error::Custom(format!("{:?}", err)))?;
let vault_file: json::VaultKeyFile = account.into_vault_file(self.key.iterations, &self.key.password)?;
vault_file.write(writer).map_err(|e| Error::Custom(format!("{:?}", e)))
@ -243,10 +235,12 @@ fn check_vault_file<P>(vault_dir_path: P, key: &VaultKey) -> Result<(), Error> w
#[cfg(test)]
mod test {
use std::{env, fs};
use std::fs;
use std::io::Write;
use std::path::PathBuf;
use dir::VaultKey;
use super::{VAULT_FILE_NAME, check_vault_name, make_vault_dir_path, create_vault_file, check_vault_file, VaultDiskDirectory};
use devtools::RandomTempPath;
#[test]
fn check_vault_name_succeeds() {
@ -282,10 +276,9 @@ mod test {
#[test]
fn create_vault_file_succeeds() {
// given
let temp_path = RandomTempPath::new();
let key = VaultKey::new("password", 1024);
let mut dir = env::temp_dir();
dir.push("create_vault_file_succeeds");
let mut vault_dir = dir.clone();
let mut vault_dir: PathBuf = temp_path.as_path().into();
vault_dir.push("vault");
fs::create_dir_all(&vault_dir).unwrap();
@ -297,20 +290,16 @@ mod test {
let mut vault_file_path = vault_dir.clone();
vault_file_path.push(VAULT_FILE_NAME);
assert!(vault_file_path.exists() && vault_file_path.is_file());
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn check_vault_file_succeeds() {
// given
let temp_path = RandomTempPath::create_dir();
let key = VaultKey::new("password", 1024);
let vault_file_contents = r#"{"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"758696c8dc6378ab9b25bb42790da2f5"},"ciphertext":"54eb50683717d41caaeb12ea969f2c159daada5907383f26f327606a37dc7168","kdf":"pbkdf2","kdfparams":{"c":1024,"dklen":32,"prf":"hmac-sha256","salt":"3c320fa566a1a7963ac8df68a19548d27c8f40bf92ef87c84594dcd5bbc402b6"},"mac":"9e5c2314c2a0781962db85611417c614bd6756666b6b1e93840f5b6ed895f003"}}"#;
let mut dir = env::temp_dir();
dir.push("check_vault_file_succeeds");
fs::create_dir_all(&dir).unwrap();
let mut vault_file_path = dir.clone();
let dir: PathBuf = temp_path.as_path().into();
let mut vault_file_path: PathBuf = dir.clone();
vault_file_path.push(VAULT_FILE_NAME);
{
let mut vault_file = fs::File::create(vault_file_path).unwrap();
@ -322,20 +311,16 @@ mod test {
// then
assert!(result.is_ok());
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn check_vault_file_fails() {
// given
let temp_path = RandomTempPath::create_dir();
let key = VaultKey::new("password1", 1024);
let mut dir = env::temp_dir();
dir.push("check_vault_file_fails");
let mut vault_file_path = dir.clone();
let dir: PathBuf = temp_path.as_path().into();
let mut vault_file_path: PathBuf = dir.clone();
vault_file_path.push(VAULT_FILE_NAME);
fs::create_dir_all(&dir).unwrap();
// when
let result = check_vault_file(&dir, &key);
@ -355,17 +340,14 @@ mod test {
// then
assert!(result.is_err());
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn vault_directory_can_be_created() {
// given
let temp_path = RandomTempPath::new();
let key = VaultKey::new("password", 1024);
let mut dir = env::temp_dir();
dir.push("vault_directory_can_be_created");
let dir: PathBuf = temp_path.as_path().into();
// when
let vault = VaultDiskDirectory::create(&dir, "vault", key.clone());
@ -378,17 +360,14 @@ mod test {
// then
assert!(vault.is_ok());
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn vault_directory_cannot_be_created_if_already_exists() {
// given
let temp_path = RandomTempPath::new();
let key = VaultKey::new("password", 1024);
let mut dir = env::temp_dir();
dir.push("vault_directory_cannot_be_created_if_already_exists");
let dir: PathBuf = temp_path.as_path().into();
let mut vault_dir = dir.clone();
vault_dir.push("vault");
fs::create_dir_all(&vault_dir).unwrap();
@ -398,25 +377,19 @@ mod test {
// then
assert!(vault.is_err());
// cleanup
let _ = fs::remove_dir_all(dir);
}
#[test]
fn vault_directory_cannot_be_opened_if_not_exists() {
// given
let temp_path = RandomTempPath::create_dir();
let key = VaultKey::new("password", 1024);
let mut dir = env::temp_dir();
dir.push("vault_directory_cannot_be_opened_if_not_exists");
let dir: PathBuf = temp_path.as_path().into();
// when
let vault = VaultDiskDirectory::at(&dir, "vault", key);
// then
assert!(vault.is_err());
// cleanup
let _ = fs::remove_dir_all(dir);
}
}

View File

@ -54,6 +54,10 @@ impl SimpleSecretStore for EthStore {
self.store.insert_account(vault, secret, password)
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
self.store.account_ref(address)
}
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
self.store.accounts()
}
@ -88,8 +92,20 @@ impl SimpleSecretStore for EthStore {
self.store.close_vault(name)
}
fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> {
self.store.change_vault_password(name, password, new_password)
fn list_vaults(&self) -> Result<Vec<String>, Error> {
self.store.list_vaults()
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
self.store.list_opened_vaults()
}
fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> {
self.store.change_vault_password(name, new_password)
}
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error> {
self.store.change_account_vault(vault, account)
}
}
@ -121,12 +137,6 @@ impl SecretStore for EthStore {
Ok(())
}
fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error> {
self.copy_account(new_store, new_vault, account, password, new_password)?;
self.remove_account(account, password)?;
Ok(())
}
fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error> {
let account = self.get(account)?;
account.public(password)
@ -296,6 +306,32 @@ impl EthMultiStore {
}
fn remove_safe_account(&self, account_ref: &StoreAccountRef, account: &SafeAccount) -> Result<(), Error> {
// Remove from dir
match account_ref.vault {
SecretVaultRef::Root => self.dir.remove(&account)?,
SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?,
};
// Remove from cache
let mut cache = self.cache.write();
let is_empty = {
if let Some(accounts) = cache.get_mut(account_ref) {
if let Some(position) = accounts.iter().position(|acc| acc == account) {
accounts.remove(position);
}
accounts.is_empty()
} else {
false
}
};
if is_empty {
cache.remove(account_ref);
}
return Ok(());
}
}
impl SimpleSecretStore for EthMultiStore {
@ -306,6 +342,14 @@ impl SimpleSecretStore for EthMultiStore {
self.import(vault, account)
}
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
self.reload_accounts()?;
self.cache.read().keys()
.find(|r| &r.address == address)
.cloned()
.ok_or(Error::InvalidAccount)
}
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
self.reload_accounts()?;
Ok(self.cache.read().keys().cloned().collect())
@ -320,37 +364,12 @@ impl SimpleSecretStore for EthMultiStore {
continue;
}
// Remove from dir
match account_ref.vault {
SecretVaultRef::Root => self.dir.remove(&account)?,
SecretVaultRef::Vault(ref vault_name) => self.vaults.lock().get(vault_name).ok_or(Error::VaultNotFound)?.remove(&account)?,
};
// Remove from cache
let mut cache = self.cache.write();
let is_empty = {
if let Some(accounts) = cache.get_mut(account_ref) {
if let Some(position) = accounts.iter().position(|acc| acc == &account) {
accounts.remove(position);
}
accounts.is_empty()
} else {
false
}
};
if is_empty {
cache.remove(account_ref);
}
return Ok(());
return self.remove_safe_account(account_ref, &account);
}
Err(Error::InvalidPassword)
}
fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> {
match account_ref.vault {
SecretVaultRef::Root => {
let accounts = self.get(account_ref)?;
for account in accounts {
@ -359,11 +378,6 @@ impl SimpleSecretStore for EthMultiStore {
self.update(account_ref, account, new_account)?;
}
Ok(())
},
SecretVaultRef::Vault(ref vault_name) => {
self.change_vault_password(vault_name, old_password, new_password)
},
}
}
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> {
@ -435,10 +449,20 @@ impl SimpleSecretStore for EthMultiStore {
Ok(())
}
fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error> {
fn list_vaults(&self) -> Result<Vec<String>, Error> {
let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?;
let vault = vault_provider.open(name, VaultKey::new(password, self.iterations))?;
match vault.set_key(VaultKey::new(password, self.iterations), VaultKey::new(new_password, self.iterations)) {
vault_provider.list_vaults()
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
Ok(self.vaults.lock().keys().cloned().collect())
}
fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> {
let old_key = self.vaults.lock().get(name).map(|v| v.key()).ok_or(Error::VaultNotFound)?;
let vault_provider = self.dir.as_vault_provider().ok_or(Error::VaultsAreNotSupported)?;
let vault = vault_provider.open(name, old_key)?;
match vault.set_key(VaultKey::new(new_password, self.iterations)) {
Ok(_) => {
self.close_vault(name)
.and_then(|_| self.open_vault(name, new_password))
@ -446,7 +470,7 @@ impl SimpleSecretStore for EthMultiStore {
Err(SetKeyError::Fatal(err)) => {
let _ = self.close_vault(name);
Err(err)
}
},
Err(SetKeyError::NonFatalNew(err)) => {
let _ = self.close_vault(name)
.and_then(|_| self.open_vault(name, new_password));
@ -455,17 +479,28 @@ impl SimpleSecretStore for EthMultiStore {
Err(SetKeyError::NonFatalOld(err)) => Err(err),
}
}
fn change_account_vault(&self, vault: SecretVaultRef, account_ref: StoreAccountRef) -> Result<StoreAccountRef, Error> {
if account_ref.vault == vault {
return Ok(account_ref);
}
let account = self.get(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?;
let new_account_ref = self.import(vault, account.clone())?;
self.remove_safe_account(&account_ref, &account)?;
self.reload_accounts()?;
Ok(new_account_ref)
}
}
#[cfg(test)]
mod tests {
use std::{env, fs};
use std::path::PathBuf;
use dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory};
use ethkey::{Random, Generator, KeyPair};
use secret_store::{SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef};
use super::{EthStore, EthMultiStore};
use devtools::RandomTempPath;
fn keypair() -> KeyPair {
Random.generate().unwrap()
@ -481,26 +516,17 @@ mod tests {
struct RootDiskDirectoryGuard {
pub key_dir: Option<Box<KeyDirectory>>,
path: Option<PathBuf>,
_path: RandomTempPath,
}
impl RootDiskDirectoryGuard {
pub fn new(test_name: &str) -> Self {
let mut path = env::temp_dir();
path.push(test_name);
fs::create_dir_all(&path).unwrap();
pub fn new() -> Self {
let temp_path = RandomTempPath::new();
let disk_dir = Box::new(RootDiskDirectory::create(temp_path.as_path()).unwrap());
RootDiskDirectoryGuard {
key_dir: Some(Box::new(RootDiskDirectory::create(&path).unwrap())),
path: Some(path),
}
}
}
impl Drop for RootDiskDirectoryGuard {
fn drop(&mut self) {
if let Some(path) = self.path.take() {
let _ = fs::remove_dir_all(path);
key_dir: Some(disk_dir),
_path: temp_path,
}
}
}
@ -606,7 +632,7 @@ mod tests {
#[test]
fn should_create_and_open_vaults() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_create_and_open_vaults");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let name2 = "vault2"; let password2 = "password2";
@ -660,7 +686,7 @@ mod tests {
#[test]
fn should_move_vault_acounts() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_move_vault_acounts");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let name2 = "vault2"; let password2 = "password2";
@ -677,72 +703,42 @@ mod tests {
let account3 = store.insert_account(SecretVaultRef::Root, keypair3.secret().clone(), password3).unwrap();
// then
store.move_account(&store, SecretVaultRef::Root, &account1, password1, password2).unwrap();
store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account2, password1, password2).unwrap();
store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account3, password3, password2).unwrap();
let account1 = store.change_account_vault(SecretVaultRef::Root, account1.clone()).unwrap();
let account2 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account2.clone()).unwrap();
let account3 = store.change_account_vault(SecretVaultRef::Vault(name2.to_owned()), account3).unwrap();
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 3);
assert!(accounts.iter().any(|a| a == &StoreAccountRef::root(account1.address.clone())));
assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account2.address.clone())));
assert!(accounts.iter().any(|a| a == &StoreAccountRef::vault(name2, account3.address.clone())));
// and then
assert_eq!(store.meta(&StoreAccountRef::root(account1.address)).unwrap(), r#"{}"#);
assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account2.address)).unwrap(), r#"{"vault":"vault2"}"#);
assert_eq!(store.meta(&StoreAccountRef::vault("vault2", account3.address)).unwrap(), r#"{"vault":"vault2"}"#);
}
#[test]
fn should_not_remove_account_when_moving_to_self() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_when_moving_to_self");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let password1 = "password1";
let keypair1 = keypair();
// when
let account1 = store.insert_account(SecretVaultRef::Root, keypair1.secret().clone(), password1).unwrap();
store.move_account(&store, SecretVaultRef::Root, &account1, password1, password1).unwrap();
store.change_account_vault(SecretVaultRef::Root, account1).unwrap();
// then
let accounts = store.accounts().unwrap();
assert_eq!(accounts.len(), 1);
}
#[test]
fn should_not_move_account_when_vault_password_incorrect() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_not_move_account_when_vault_password_incorrect");
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let name2 = "vault2"; let password2 = "password2";
let keypair1 = keypair();
// when
store.create_vault(name1, password1).unwrap();
store.create_vault(name2, password2).unwrap();
let account1 = store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password1).unwrap();
// then
store.move_account(&store, SecretVaultRef::Root, &account1, password2, password1).unwrap_err();
store.move_account(&store, SecretVaultRef::Vault(name2.to_owned()), &account1, password1, password1).unwrap_err();
}
#[test]
fn should_not_insert_account_when_vault_password_incorrect() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_not_insert_account_when_vault_password_incorrect");
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let password2 = "password2";
let keypair1 = keypair();
// when
store.create_vault(name1, password1).unwrap();
// then
store.insert_account(SecretVaultRef::Vault(name1.to_owned()), keypair1.secret().clone(), password2).unwrap_err();
}
#[test]
fn should_remove_account_from_vault() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_remove_account_from_vault");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let keypair1 = keypair();
@ -760,7 +756,7 @@ mod tests {
#[test]
fn should_not_remove_account_from_vault_when_password_is_incorrect() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_not_remove_account_from_vault_when_password_is_incorrect");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let password2 = "password2";
@ -779,7 +775,7 @@ mod tests {
#[test]
fn should_change_vault_password() {
// given
let mut dir = RootDiskDirectoryGuard::new("should_change_vault_password");
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name = "vault"; let password = "password";
let keypair = keypair();
@ -791,9 +787,7 @@ mod tests {
// then
assert_eq!(store.accounts().unwrap().len(), 1);
let new_password = "new_password";
store.change_vault_password(name, "bad_password", new_password).unwrap_err();
assert_eq!(store.accounts().unwrap().len(), 1);
store.change_vault_password(name, password, new_password).unwrap();
store.change_vault_password(name, new_password).unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
// and when
@ -803,4 +797,46 @@ mod tests {
store.open_vault(name, new_password).unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_have_different_passwords_for_vault_secret_and_meta() {
// given
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name = "vault"; let password = "password";
let secret_password = "sec_password";
let keypair = keypair();
// when
store.create_vault(name, password).unwrap();
let account_ref = store.insert_account(SecretVaultRef::Vault(name.to_owned()), keypair.secret().clone(), secret_password).unwrap();
// then
assert_eq!(store.accounts().unwrap().len(), 1);
let new_secret_password = "new_sec_password";
store.change_password(&account_ref, secret_password, new_secret_password).unwrap();
assert_eq!(store.accounts().unwrap().len(), 1);
}
#[test]
fn should_list_opened_vaults() {
// given
let mut dir = RootDiskDirectoryGuard::new();
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
let name1 = "vault1"; let password1 = "password1";
let name2 = "vault2"; let password2 = "password2";
let name3 = "vault3"; let password3 = "password3";
// when
store.create_vault(name1, password1).unwrap();
store.create_vault(name2, password2).unwrap();
store.create_vault(name3, password3).unwrap();
store.close_vault(name2).unwrap();
// then
let opened_vaults = store.list_opened_vaults().unwrap();
assert_eq!(opened_vaults.len(), 2);
assert!(opened_vaults.iter().any(|v| &*v == name1));
assert!(opened_vaults.iter().any(|v| &*v == name3));
}
}

View File

@ -21,6 +21,6 @@ pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
pub use self::key_file::KeyFile;
pub use self::presale::{PresaleWallet, Encseed};
pub use self::vault_file::VaultFile;
pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta};
pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
pub use self::version::Version;

View File

@ -18,8 +18,13 @@ use std::io::{Read, Write};
use serde::{Deserialize, Deserializer, Error};
use serde::de::{Visitor, MapVisitor};
use serde_json;
use serde_json::value::Value;
use serde_json::error;
use super::{Uuid, Version, Crypto, H160};
/// Meta key name for vault field
const VAULT_NAME_META_KEY: &'static str = "vault";
/// Key file as stored in vaults
#[derive(Debug, PartialEq, Serialize)]
pub struct VaultKeyFile {
@ -27,9 +32,9 @@ pub struct VaultKeyFile {
pub id: Uuid,
/// Key version
pub version: Version,
/// Encrypted secret
/// Secret, encrypted with account password
pub crypto: Crypto,
/// Encrypted serialized `VaultKeyMeta`
/// Serialized `VaultKeyMeta`, encrypted with vault password
pub metacrypto: Crypto,
}
@ -44,6 +49,38 @@ pub struct VaultKeyMeta {
pub meta: Option<String>,
}
/// Insert vault name to the JSON meta field
pub fn insert_vault_name_to_json_meta(meta: &str, vault_name: &str) -> Result<String, error::Error> {
let mut meta = if meta.is_empty() {
Value::Object(serde_json::Map::new())
} else {
serde_json::from_str(meta)?
};
if let Some(meta_obj) = meta.as_object_mut() {
meta_obj.insert(VAULT_NAME_META_KEY.to_owned(), Value::String(vault_name.to_owned()));
serde_json::to_string(meta_obj)
} else {
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
}
}
/// Remove vault name from the JSON meta field
pub fn remove_vault_name_from_json_meta(meta: &str) -> Result<String, error::Error> {
let mut meta = if meta.is_empty() {
Value::Object(serde_json::Map::new())
} else {
serde_json::from_str(meta)?
};
if let Some(meta_obj) = meta.as_object_mut() {
meta_obj.remove(VAULT_NAME_META_KEY);
serde_json::to_string(meta_obj)
} else {
Err(error::Error::custom("Meta is expected to be a serialized JSON object"))
}
}
enum VaultKeyFileField {
Id,
Version,
@ -244,7 +281,8 @@ impl VaultKeyMeta {
#[cfg(test)]
mod test {
use serde_json;
use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf};
use json::{VaultKeyFile, Version, Crypto, Cipher, Aes128Ctr, Kdf, Pbkdf2, Prf,
insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
#[test]
fn to_and_from_json() {
@ -284,4 +322,28 @@ mod test {
assert_eq!(file, deserialized);
}
#[test]
fn vault_name_inserted_to_json_meta() {
assert_eq!(insert_vault_name_to_json_meta(r#""#, "MyVault").unwrap(), r#"{"vault":"MyVault"}"#);
assert_eq!(insert_vault_name_to_json_meta(r#"{"tags":["kalabala"]}"#, "MyVault").unwrap(), r#"{"tags":["kalabala"],"vault":"MyVault"}"#);
}
#[test]
fn vault_name_not_inserted_to_json_meta() {
assert!(insert_vault_name_to_json_meta(r#"///3533"#, "MyVault").is_err());
assert!(insert_vault_name_to_json_meta(r#""string""#, "MyVault").is_err());
}
#[test]
fn vault_name_removed_from_json_meta() {
assert_eq!(remove_vault_name_from_json_meta(r#"{"vault":"MyVault"}"#).unwrap(), r#"{}"#);
assert_eq!(remove_vault_name_from_json_meta(r#"{"tags":["kalabala"],"vault":"MyVault"}"#).unwrap(), r#"{"tags":["kalabala"]}"#);
}
#[test]
fn vault_name_not_removed_from_json_meta() {
assert!(remove_vault_name_from_json_meta(r#"///3533"#).is_err());
assert!(remove_vault_name_from_json_meta(r#""string""#).is_err());
}
}

View File

@ -28,6 +28,7 @@ extern crate rustc_serialize;
extern crate crypto as rcrypto;
extern crate tiny_keccak;
extern crate parking_lot;
extern crate ethcore_devtools as devtools;
// reexport it nicely
extern crate ethkey as _ethkey;

View File

@ -47,6 +47,9 @@ pub trait SimpleSecretStore: Send + Sync {
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error>;
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error>;
/// Get reference to some account with given address.
/// This method could be removed if we will guarantee that there is max(1) account for given address.
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error>;
/// Create new vault with given password
fn create_vault(&self, name: &str, password: &str) -> Result<(), Error>;
@ -54,15 +57,20 @@ pub trait SimpleSecretStore: Send + Sync {
fn open_vault(&self, name: &str, password: &str) -> Result<(), Error>;
/// Close vault
fn close_vault(&self, name: &str) -> Result<(), Error>;
/// List all vaults
fn list_vaults(&self) -> Result<Vec<String>, Error>;
/// List all currently opened vaults
fn list_opened_vaults(&self) -> Result<Vec<String>, Error>;
/// Change vault password
fn change_vault_password(&self, name: &str, password: &str, new_password: &str) -> Result<(), Error>;
fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error>;
/// Cnage account' vault
fn change_account_vault(&self, vault: SecretVaultRef, account: StoreAccountRef) -> Result<StoreAccountRef, Error>;
}
pub trait SecretStore: SimpleSecretStore {
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
fn import_wallet(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
fn copy_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>;
fn move_account(&self, new_store: &SimpleSecretStore, new_vault: SecretVaultRef, account: &StoreAccountRef, password: &str, new_password: &str) -> Result<(), Error>;
fn test_password(&self, account: &StoreAccountRef, password: &str) -> Result<bool, Error>;
fn public(&self, account: &StoreAccountRef, password: &str) -> Result<Public, Error>;

View File

@ -1,6 +1,6 @@
{
"name": "parity.js",
"version": "0.3.66",
"version": "0.3.70",
"main": "release/index.js",
"jsnext:main": "src/index.js",
"author": "Parity Team <admin@parity.io>",

View File

@ -55,6 +55,7 @@ if (process.env.NODE_ENV === 'development') {
const AUTH_HASH = '#/auth?';
const parityUrl = process.env.PARITY_URL || window.location.host;
const urlScheme = window.location.href.match(/^https/) ? 'wss://' : 'ws://';
let token = null;
@ -62,7 +63,7 @@ if (window.location.hash && window.location.hash.indexOf(AUTH_HASH) === 0) {
token = qs.parse(window.location.hash.substr(AUTH_HASH.length)).token;
}
const api = new SecureApi(`ws://${parityUrl}`, token);
const api = new SecureApi(`${urlScheme}${parityUrl}`, token);
patchApi(api);
ContractInstances.create(api);

View File

@ -30,6 +30,10 @@
.list {
margin-bottom: 1.5em;
&:last-child {
margin-bottom: 0;
}
.background {
padding: 0.5em 0;
}

View File

@ -31,8 +31,10 @@
}
}
.selected, .unselected {
.selected,
.unselected {
margin-bottom: 0.25em;
width: 100%;
&:focus {
outline: none;

View File

@ -23,10 +23,11 @@
.compact,
.padded {
background-color: transparent !important;
border-radius: 0 !important;
height: 100%;
position: relative;
overflow: auto;
background-color: transparent !important;
}
.compact {

View File

@ -50,6 +50,7 @@ $popoverZ: 3600;
left: 0;
right: 0;
opacity: 0.25;
z-index: -1;
}
.overlay {

View File

@ -34,7 +34,11 @@ export default class Portal extends Component {
activeStep: PropTypes.number,
busy: PropTypes.bool,
busySteps: PropTypes.array,
buttons: PropTypes.array,
buttons: PropTypes.oneOfType([
PropTypes.array,
PropTypes.node,
PropTypes.object
]),
children: PropTypes.node,
className: PropTypes.string,
hideClose: PropTypes.bool,

View File

@ -21,9 +21,12 @@ const MAX_GAS_ESTIMATION = '50000000';
const NULL_ADDRESS = '0000000000000000000000000000000000000000';
const DOMAIN = '.web3.site';
export {
DEFAULT_GAS,
DEFAULT_GASPRICE,
MAX_GAS_ESTIMATION,
NULL_ADDRESS
NULL_ADDRESS,
DOMAIN
};

View File

@ -16,7 +16,9 @@
import base32 from 'base32.js';
const BASE_URL = '.web.web3.site';
import { DOMAIN } from './constants';
const BASE_URL = `.web${DOMAIN}`;
const ENCODER_OPTS = { type: 'crockford' };
export function encodePath (token, url) {

View File

@ -21,6 +21,8 @@ import { action, computed, observable } from 'mobx';
import store from 'store';
import browser from 'useragent.js/lib/browser';
import { DOMAIN } from '~/util/constants';
const A_DAY = 24 * 60 * 60 * 1000;
const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay';
@ -68,6 +70,19 @@ export default class Store {
installExtension = () => {
this.setInstalling(true);
if (window.location.hostname.toString().endsWith(DOMAIN)) {
return this.inlineInstall()
.catch((error) => {
console.warn('Unable to perform direct install', error);
window.open(EXTENSION_PAGE, '_blank');
});
}
window.open(EXTENSION_PAGE, '_blank');
return Promise.resolve(true);
}
inlineInstall = () => {
return new Promise((resolve, reject) => {
const link = document.createElement('link');
@ -80,10 +95,6 @@ export default class Store {
} else {
reject(new Error('Direct installation failed.'));
}
})
.catch((error) => {
console.warn('Unable to perform direct install', error);
window.open(EXTENSION_PAGE, '_blank');
});
}
}

View File

@ -201,6 +201,53 @@ impl ParityAccounts for ParityAccountsClient {
Ok(into_vec(store.list_geth_accounts(false)))
}
fn create_vault(&self, name: String, password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.create_vault(&name, &password)
.map_err(|e| errors::account("Could not create vault.", e))
.map(|_| true)
}
fn open_vault(&self, name: String, password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.open_vault(&name, &password)
.map_err(|e| errors::account("Could not open vault.", e))
.map(|_| true)
}
fn close_vault(&self, name: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.close_vault(&name)
.map_err(|e| errors::account("Could not close vault.", e))
.map(|_| true)
}
fn list_vaults(&self) -> Result<Vec<String>, Error> {
take_weak!(self.accounts)
.list_vaults()
.map_err(|e| errors::account("Could not list vaults.", e))
}
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
take_weak!(self.accounts)
.list_opened_vaults()
.map_err(|e| errors::account("Could not list vaults.", e))
}
fn change_vault_password(&self, name: String, new_password: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.change_vault_password(&name, &new_password)
.map_err(|e| errors::account("Could not change vault password.", e))
.map(|_| true)
}
fn change_vault(&self, address: RpcH160, new_vault: String) -> Result<bool, Error> {
take_weak!(self.accounts)
.change_vault(address.into(), &new_vault)
.map_err(|e| errors::account("Could not change vault.", e))
.map(|_| true)
}
}
fn into_vec<A, B>(a: Vec<A>) -> Vec<B> where

View File

@ -17,6 +17,9 @@
use std::sync::Arc;
use ethcore::account_provider::AccountProvider;
use ethstore::EthStore;
use ethstore::dir::RootDiskDirectory;
use devtools::RandomTempPath;
use jsonrpc_core::IoHandler;
use v1::{ParityAccounts, ParityAccountsClient};
@ -30,21 +33,33 @@ fn accounts_provider() -> Arc<AccountProvider> {
Arc::new(AccountProvider::transient_provider())
}
fn setup() -> ParityAccountsTester {
let accounts = accounts_provider();
let parity_accounts = ParityAccountsClient::new(&accounts);
fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> {
let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap();
let secret_store = EthStore::open(Box::new(root_keys_dir)).unwrap();
Arc::new(AccountProvider::new(Box::new(secret_store)))
}
fn setup_with_accounts_provider(accounts_provider: Arc<AccountProvider>) -> ParityAccountsTester {
let parity_accounts = ParityAccountsClient::new(&accounts_provider);
let mut io = IoHandler::default();
io.extend_with(parity_accounts.to_delegate());
let tester = ParityAccountsTester {
accounts: accounts,
accounts: accounts_provider,
io: io,
};
tester
}
fn setup() -> ParityAccountsTester {
setup_with_accounts_provider(accounts_provider())
}
fn setup_with_vaults_support(temp_path: &str) -> ParityAccountsTester {
setup_with_accounts_provider(accounts_provider_with_vaults_support(temp_path))
}
#[test]
fn should_be_able_to_get_account_info() {
let tester = setup();
@ -217,3 +232,122 @@ fn should_be_able_to_remove_address() {
let response = r#"{"jsonrpc":"2.0","result":{},"id":4}"#;
assert_eq!(res, Some(response.into()));
}
#[test]
fn rpc_parity_new_vault() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
let request = r#"{"jsonrpc": "2.0", "method": "parity_newVault", "params":["vault1", "password1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
assert!(tester.accounts.close_vault("vault1").is_ok());
assert!(tester.accounts.open_vault("vault1", "password1").is_ok());
}
#[test]
fn rpc_parity_open_vault() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
assert!(tester.accounts.close_vault("vault1").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_openVault", "params":["vault1", "password1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_close_vault() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_closeVault", "params":["vault1"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_change_vault_password() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_changeVaultPassword", "params":["vault1", "password2"], "id": 1}"#;
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_change_vault() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
let (address, _) = tester.accounts.new_account_and_public("root_password").unwrap();
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
let request = format!(r#"{{"jsonrpc": "2.0", "method": "parity_changeVault", "params":["0x{}", "vault1"], "id": 1}}"#, address.hex());
let response = r#"{"jsonrpc":"2.0","result":true,"id":1}"#;
assert_eq!(tester.io.handle_request_sync(&request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_vault_adds_vault_field_to_acount_meta() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
let (address1, _) = tester.accounts.new_account_and_public("root_password1").unwrap();
let uuid1 = tester.accounts.account_meta(address1.clone()).unwrap().uuid.unwrap();
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
assert!(tester.accounts.change_vault(address1, "vault1").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_allAccountsInfo", "params":[], "id": 1}"#;
let response = format!(r#"{{"jsonrpc":"2.0","result":{{"0x{}":{{"meta":"{{\"vault\":\"vault1\"}}","name":"","uuid":"{}"}}}},"id":1}}"#, address1.hex(), uuid1);
assert_eq!(tester.io.handle_request_sync(request), Some(response.to_owned()));
}
#[test]
fn rpc_parity_list_vaults() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
assert!(tester.accounts.create_vault("vault2", "password2").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_listVaults", "params":[], "id": 1}"#;
let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault2"],"id":1}"#;
let response2 = r#"{"jsonrpc":"2.0","result":["vault2","vault1"],"id":1}"#;
let actual_response = tester.io.handle_request_sync(request);
assert!(actual_response == Some(response1.to_owned())
|| actual_response == Some(response2.to_owned()));
}
#[test]
fn rpc_parity_list_opened_vaults() {
let temp_path = RandomTempPath::new();
let tester = setup_with_vaults_support(temp_path.as_str());
assert!(tester.accounts.create_vault("vault1", "password1").is_ok());
assert!(tester.accounts.create_vault("vault2", "password2").is_ok());
assert!(tester.accounts.create_vault("vault3", "password3").is_ok());
assert!(tester.accounts.close_vault("vault2").is_ok());
let request = r#"{"jsonrpc": "2.0", "method": "parity_listOpenedVaults", "params":[], "id": 1}"#;
let response1 = r#"{"jsonrpc":"2.0","result":["vault1","vault3"],"id":1}"#;
let response2 = r#"{"jsonrpc":"2.0","result":["vault3","vault1"],"id":1}"#;
let actual_response = tester.io.handle_request_sync(request);
assert!(actual_response == Some(response1.to_owned())
|| actual_response == Some(response2.to_owned()));
}

View File

@ -105,5 +105,33 @@ build_rpc_trait! {
/// Returns the accounts available for importing from Geth.
#[rpc(name = "parity_listGethAccounts")]
fn geth_accounts(&self) -> Result<Vec<H160>, Error>;
/// Create new vault.
#[rpc(name = "parity_newVault")]
fn create_vault(&self, String, String) -> Result<bool, Error>;
/// Open existing vault.
#[rpc(name = "parity_openVault")]
fn open_vault(&self, String, String) -> Result<bool, Error>;
/// Close previously opened vault.
#[rpc(name = "parity_closeVault")]
fn close_vault(&self, String) -> Result<bool, Error>;
/// List all vaults.
#[rpc(name = "parity_listVaults")]
fn list_vaults(&self) -> Result<Vec<String>, Error>;
/// List all currently opened vaults.
#[rpc(name = "parity_listOpenedVaults")]
fn list_opened_vaults(&self) -> Result<Vec<String>, Error>;
/// Change vault password.
#[rpc(name = "parity_changeVaultPassword")]
fn change_vault_password(&self, String, String) -> Result<bool, Error>;
/// Change vault of the given address.
#[rpc(name = "parity_changeVault")]
fn change_vault(&self, H160, String) -> Result<bool, Error>;
}
}