Export account RPC (#4967)

* Export account RPC

* Removing GethDirectory and ParityDirectory

* Updating ethstore-cli help.
This commit is contained in:
Tomasz Drwięga 2017-03-23 13:23:03 +01:00 committed by Gav Wood
parent 9fdd0e3a0a
commit bb1bbebfd6
19 changed files with 318 additions and 265 deletions

View File

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

View File

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

View File

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

View File

@ -1,102 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::path::PathBuf;
use {SafeAccount, Error};
use super::{KeyDirectory, RootDiskDirectory, DirectoryType};
#[cfg(target_os = "macos")]
fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push("Library");
home.push("Ethereum");
home
}
#[cfg(windows)]
/// Default path for ethereum installation on Windows
pub fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push("AppData");
home.push("Roaming");
home.push("Ethereum");
home
}
#[cfg(not(any(target_os = "macos", windows)))]
/// Default path for ethereum installation on posix system which is not Mac OS
pub fn geth_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push(".ethereum");
home
}
fn geth_keystore(t: DirectoryType) -> PathBuf {
let mut dir = geth_dir_path();
match t {
DirectoryType::Testnet => {
dir.push("testnet");
dir.push("keystore");
},
DirectoryType::Main => {
dir.push("keystore");
}
}
dir
}
pub struct GethDirectory {
dir: RootDiskDirectory,
}
impl GethDirectory {
pub fn create(t: DirectoryType) -> Result<Self, Error> {
let result = GethDirectory {
dir: RootDiskDirectory::create(geth_keystore(t))?,
};
Ok(result)
}
pub fn open(t: DirectoryType) -> Self {
GethDirectory {
dir: RootDiskDirectory::at(geth_keystore(t)),
}
}
}
impl KeyDirectory for GethDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.insert(account)
}
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
fn unique_repr(&self) -> Result<u64, Error> {
self.dir.unique_repr()
}
}

View File

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

View File

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

View File

@ -1,81 +0,0 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::env;
use std::path::PathBuf;
use {SafeAccount, Error};
use super::{KeyDirectory, RootDiskDirectory, DirectoryType};
fn parity_dir_path() -> PathBuf {
let mut home = env::home_dir().expect("Failed to get home dir");
home.push(".parity");
home
}
fn parity_keystore(t: DirectoryType) -> PathBuf {
let mut dir = parity_dir_path();
match t {
DirectoryType::Testnet => {
dir.push("testnet_keys");
},
DirectoryType::Main => {
dir.push("keys");
}
}
dir
}
pub struct ParityDirectory {
dir: RootDiskDirectory,
}
impl ParityDirectory {
pub fn create(t: DirectoryType) -> Result<Self, Error> {
let result = ParityDirectory {
dir: RootDiskDirectory::create(parity_keystore(t))?,
};
Ok(result)
}
pub fn open(t: DirectoryType) -> Self {
ParityDirectory {
dir: RootDiskDirectory::at(parity_keystore(t)),
}
}
}
impl KeyDirectory for ParityDirectory {
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
self.dir.load()
}
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.insert(account)
}
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
self.dir.update(account)
}
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
self.dir.remove(account)
}
fn unique_repr(&self) -> Result<u64, Error> {
self.dir.unique_repr()
}
}

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

@ -0,0 +1,96 @@
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Common tools paths.
use std::env;
use std::path::PathBuf;
fn home() -> PathBuf {
env::home_dir().expect("Failed to get home dir")
}
/// Geth path
pub fn geth(testnet: bool) -> PathBuf {
let mut base = geth_base();
if testnet {
base.push("testnet");
}
base.push("keystore");
base
}
/// Parity path for specific chain
pub fn parity(chain: &str) -> PathBuf {
let mut base = parity_base();
base.push(chain);
base
}
#[cfg(target_os = "macos")]
fn parity_base() -> PathBuf {
let mut home = home();
home.push("Library");
home.push("Application Support");
home.push("io.parity.ethereum");
home.push("keys");
home
}
#[cfg(windows)]
fn parity_base() -> PathBuf {
let mut home = home();
home.push("AppData");
home.push("Roaming");
home.push("Parity");
home.push("Ethereum");
home.push("keys");
home
}
#[cfg(not(any(target_os = "macos", windows)))]
fn parity_base() -> PathBuf {
let mut home = home();
home.push(".local");
home.push("share");
home.push("io.parity.ethereum");
home.push("keys");
home
}
#[cfg(target_os = "macos")]
fn geth_base() -> PathBuf {
let mut home = home();
home.push("Library");
home.push("Ethereum");
home
}
#[cfg(windows)]
fn geth_base() -> PathBuf {
let mut home = home();
home.push("AppData");
home.push("Roaming");
home.push("Ethereum");
home
}
#[cfg(not(any(target_os = "macos", windows)))]
fn geth_base() -> PathBuf {
let mut home = home();
home.push(".ethereum");
home
}

View File

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

View File

@ -25,18 +25,21 @@ use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, Extende
use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError}; use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
use account::SafeAccount; use account::SafeAccount;
use presale::PresaleWallet; use presale::PresaleWallet;
use json::{self, Uuid}; use json::{self, Uuid, OpaqueKeyFile};
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation}; use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
/// Accounts store.
pub struct EthStore { pub struct EthStore {
store: EthMultiStore, store: EthMultiStore,
} }
impl EthStore { impl EthStore {
/// Open a new accounts store with given key directory backend.
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> { pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32) Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
} }
/// Open a new account store with given key directory backend and custom number of iterations.
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> { pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
Ok(EthStore { Ok(EthStore {
store: EthMultiStore::open_with_iterations(directory, iterations)?, store: EthMultiStore::open_with_iterations(directory, iterations)?,
@ -44,7 +47,7 @@ impl EthStore {
} }
fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> { fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
let mut accounts = self.store.get(account)?.into_iter(); let mut accounts = self.store.get_accounts(account)?.into_iter();
accounts.next().ok_or(Error::InvalidAccount) accounts.next().ok_or(Error::InvalidAccount)
} }
} }
@ -76,6 +79,10 @@ impl SimpleSecretStore for EthStore {
self.store.change_password(account, old_password, new_password) self.store.change_password(account, old_password, new_password)
} }
fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error> {
self.store.export_account(account, password)
}
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> { fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> {
self.store.remove_account(account, password) self.store.remove_account(account, password)
} }
@ -234,11 +241,12 @@ pub struct EthMultiStore {
} }
impl EthMultiStore { impl EthMultiStore {
/// Open new multi-accounts store with given key directory backend.
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> { pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32) Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
} }
/// Open new multi-accounts store with given key directory backend and custom number of iterations for new keys.
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> { pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
let store = EthMultiStore { let store = EthMultiStore {
dir: directory, dir: directory,
@ -287,7 +295,7 @@ impl EthMultiStore {
Ok(()) Ok(())
} }
fn get(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> { fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> {
{ {
let cache = self.cache.read(); let cache = self.cache.read();
if let Some(accounts) = cache.get(account) { if let Some(accounts) = cache.get(account) {
@ -307,6 +315,15 @@ impl EthMultiStore {
} }
} }
fn get_matching(&self, account: &StoreAccountRef, password: &str) -> Result<Vec<SafeAccount>, Error> {
let accounts = self.get_accounts(account)?;
Ok(accounts.into_iter()
.filter(|acc| acc.check_password(password))
.collect()
)
}
fn import(&self, vault: SecretVaultRef, account: SafeAccount) -> Result<StoreAccountRef, Error> { fn import(&self, vault: SecretVaultRef, account: SafeAccount) -> Result<StoreAccountRef, Error> {
// save to file // save to file
let account = match vault { let account = match vault {
@ -398,12 +415,8 @@ impl SimpleSecretStore for EthMultiStore {
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<StoreAccountRef, Error> -> Result<StoreAccountRef, Error>
{ {
let accounts = self.get(account_ref)?; let accounts = self.get_matching(account_ref, password)?;
for account in accounts { for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?; let extended = self.generate(account.crypto.secret(password)?, derivation)?;
return self.insert_account(vault, extended.secret().as_raw().clone(), password); return self.insert_account(vault, extended.secret().as_raw().clone(), password);
} }
@ -413,14 +426,9 @@ impl SimpleSecretStore for EthMultiStore {
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
-> Result<Address, Error> -> Result<Address, Error>
{ {
let accounts = self.get(&account_ref)?; let accounts = self.get_matching(&account_ref, password)?;
for account in accounts { for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?; let extended = self.generate(account.crypto.secret(password)?, derivation)?;
return Ok(ethkey::public_to_address(extended.public().public())); return Ok(ethkey::public_to_address(extended.public().public()));
} }
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
@ -429,18 +437,13 @@ impl SimpleSecretStore for EthMultiStore {
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message) fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message)
-> Result<Signature, Error> -> Result<Signature, Error>
{ {
let accounts = self.get(&account_ref)?; let accounts = self.get_matching(&account_ref, password)?;
for account in accounts { for account in accounts {
// Skip if password is invalid
if !account.check_password(password) {
continue;
}
let extended = self.generate(account.crypto.secret(password)?, derivation)?; let extended = self.generate(account.crypto.secret(password)?, derivation)?;
let secret = extended.secret().as_raw(); let secret = extended.secret().as_raw();
return Ok(ethkey::sign(&secret, message)?) return Ok(ethkey::sign(&secret, message)?)
} }
Err(Error::InvalidPassword) Err(Error::InvalidPassword)
} }
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> { fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
@ -457,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);
}
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -472,3 +472,30 @@ fn derive_key_index() {
let res = tester.io.handle_request_sync(&request); let res = tester.io.handle_request_sync(&request);
assert_eq!(res, Some(response.into())); assert_eq!(res, Some(response.into()));
} }
#[test]
fn should_export_account() {
// given
let tester = setup();
let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#;
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap();
let accounts = tester.accounts.accounts().unwrap();
assert_eq!(accounts.len(), 1);
// invalid password
let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","123"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","error":{"code":-32023,"message":"Could not export account.","data":"InvalidPassword"},"id":1}"#;
let res = tester.io.handle_request_sync(&request);
assert_eq!(res, Some(response.into()));
// correct password
let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","parity-export-test"],"id":1}"#;
let response = r#"{"jsonrpc":"2.0","result":{"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}","name":"parity-export-test","version":3},"id":1}"#;
let result = tester.io.handle_request_sync(&request);
println!("Result: {:?}", result);
println!("Response: {:?}", response);
assert_eq!(result, Some(response.into()));
}

View File

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