Export account RPC (#4967)
* Export account RPC * Removing GethDirectory and ParityDirectory * Updating ethstore-cli help.
This commit is contained in:
parent
9fdd0e3a0a
commit
bb1bbebfd6
@ -24,14 +24,16 @@ use std::fmt;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::time::{Instant, Duration};
|
||||
use util::{RwLock};
|
||||
use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
||||
random_string, SecretVaultRef, StoreAccountRef};
|
||||
use ethstore::{
|
||||
SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
||||
random_string, SecretVaultRef, StoreAccountRef,
|
||||
};
|
||||
use ethstore::dir::MemoryDirectory;
|
||||
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
||||
use ethjson::misc::AccountMeta;
|
||||
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath};
|
||||
pub use ethstore::ethkey::Signature;
|
||||
pub use ethstore::{Derivation, IndexDerivation};
|
||||
pub use ethstore::{Derivation, IndexDerivation, KeyFile};
|
||||
|
||||
/// Type of unlock.
|
||||
#[derive(Clone)]
|
||||
@ -500,6 +502,11 @@ impl AccountProvider {
|
||||
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
||||
}
|
||||
|
||||
/// Exports an account for given address.
|
||||
pub fn export_account(&self, address: &Address, password: String) -> Result<KeyFile, Error> {
|
||||
self.sstore.export_account(&self.sstore.account_ref(address)?, &password)
|
||||
}
|
||||
|
||||
/// Helper method used for unlocking accounts.
|
||||
fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> {
|
||||
// verify password by signing dump message
|
||||
|
@ -19,14 +19,22 @@ use {json, Error, crypto};
|
||||
use account::Version;
|
||||
use super::crypto::Crypto;
|
||||
|
||||
/// Account representation.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct SafeAccount {
|
||||
/// Account ID
|
||||
pub id: [u8; 16],
|
||||
/// Account version
|
||||
pub version: Version,
|
||||
/// Account address
|
||||
pub address: Address,
|
||||
/// Account private key derivation definition.
|
||||
pub crypto: Crypto,
|
||||
/// Account filename
|
||||
pub filename: Option<String>,
|
||||
/// Account name
|
||||
pub name: String,
|
||||
/// Account metadata
|
||||
pub meta: String,
|
||||
}
|
||||
|
||||
@ -44,6 +52,7 @@ impl Into<json::KeyFile> for SafeAccount {
|
||||
}
|
||||
|
||||
impl SafeAccount {
|
||||
/// Create a new account
|
||||
pub fn create(
|
||||
keypair: &KeyPair,
|
||||
id: [u8; 16],
|
||||
@ -114,21 +123,25 @@ impl SafeAccount {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sign a message.
|
||||
pub fn sign(&self, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||
let secret = self.crypto.secret(password)?;
|
||||
sign(&secret, message).map_err(From::from)
|
||||
}
|
||||
|
||||
/// Decrypt a message.
|
||||
pub fn decrypt(&self, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let secret = self.crypto.secret(password)?;
|
||||
crypto::ecies::decrypt(&secret, shared_mac, message).map_err(From::from)
|
||||
}
|
||||
|
||||
/// Derive public key.
|
||||
pub fn public(&self, password: &str) -> Result<Public, Error> {
|
||||
let secret = self.crypto.secret(password)?;
|
||||
Ok(KeyPair::from_secret(secret)?.public().clone())
|
||||
}
|
||||
|
||||
/// Change account's password.
|
||||
pub fn change_password(&self, old_password: &str, new_password: &str, iterations: u32) -> Result<Self, Error> {
|
||||
let secret = self.crypto.secret(old_password)?;
|
||||
let result = SafeAccount {
|
||||
@ -143,6 +156,7 @@ impl SafeAccount {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Check if password matches the account.
|
||||
pub fn check_password(&self, password: &str) -> bool {
|
||||
self.crypto.secret(password).is_ok()
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use std::{env, process, fs};
|
||||
use std::io::Read;
|
||||
use docopt::Docopt;
|
||||
use ethstore::ethkey::Address;
|
||||
use ethstore::dir::{KeyDirectory, ParityDirectory, RootDiskDirectory, GethDirectory, DirectoryType};
|
||||
use ethstore::dir::{paths, KeyDirectory, RootDiskDirectory};
|
||||
use ethstore::{EthStore, SimpleSecretStore, SecretStore, import_accounts, Error, PresaleWallet,
|
||||
SecretVaultRef, StoreAccountRef};
|
||||
|
||||
@ -49,14 +49,14 @@ Usage:
|
||||
Options:
|
||||
-h, --help Display this message and exit.
|
||||
--dir DIR Specify the secret store directory. It may be either
|
||||
parity, parity-test, geth, geth-test
|
||||
parity, parity-(chain), geth, geth-test
|
||||
or a path [default: parity].
|
||||
--vault VAULT Specify vault to use in this operation.
|
||||
--vault-pwd VAULTPWD Specify vault password to use in this operation. Please note
|
||||
that this option is required when vault option is set.
|
||||
Otherwise it is ignored.
|
||||
--src DIR Specify import source. It may be either
|
||||
parity, parity-test, get, geth-test
|
||||
parity, parity-(chain), get, geth-test
|
||||
or a path [default: geth].
|
||||
|
||||
Commands:
|
||||
@ -116,10 +116,13 @@ fn main() {
|
||||
|
||||
fn key_dir(location: &str) -> Result<Box<KeyDirectory>, Error> {
|
||||
let dir: Box<KeyDirectory> = match location {
|
||||
"parity" => Box::new(ParityDirectory::create(DirectoryType::Main)?),
|
||||
"parity-test" => Box::new(ParityDirectory::create(DirectoryType::Testnet)?),
|
||||
"geth" => Box::new(GethDirectory::create(DirectoryType::Main)?),
|
||||
"geth-test" => Box::new(GethDirectory::create(DirectoryType::Testnet)?),
|
||||
"geth" => Box::new(RootDiskDirectory::create(paths::geth(false))?),
|
||||
"geth-test" => Box::new(RootDiskDirectory::create(paths::geth(true))?),
|
||||
path if path.starts_with("parity") => {
|
||||
let chain = path.split('-').nth(1).unwrap_or("ethereum");
|
||||
let path = paths::parity(chain);
|
||||
Box::new(RootDiskDirectory::create(path)?)
|
||||
},
|
||||
path => Box::new(RootDiskDirectory::create(path)?),
|
||||
};
|
||||
|
||||
@ -254,4 +257,3 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
|
||||
Ok(format!("{}", USAGE))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,102 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use {SafeAccount, Error};
|
||||
use super::{KeyDirectory, RootDiskDirectory, DirectoryType};
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push("Library");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
/// Default path for ethereum installation on Windows
|
||||
pub fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push("AppData");
|
||||
home.push("Roaming");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
/// Default path for ethereum installation on posix system which is not Mac OS
|
||||
pub fn geth_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push(".ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
fn geth_keystore(t: DirectoryType) -> PathBuf {
|
||||
let mut dir = geth_dir_path();
|
||||
match t {
|
||||
DirectoryType::Testnet => {
|
||||
dir.push("testnet");
|
||||
dir.push("keystore");
|
||||
},
|
||||
DirectoryType::Main => {
|
||||
dir.push("keystore");
|
||||
}
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub struct GethDirectory {
|
||||
dir: RootDiskDirectory,
|
||||
}
|
||||
|
||||
impl GethDirectory {
|
||||
pub fn create(t: DirectoryType) -> Result<Self, Error> {
|
||||
let result = GethDirectory {
|
||||
dir: RootDiskDirectory::create(geth_keystore(t))?,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn open(t: DirectoryType) -> Self {
|
||||
GethDirectory {
|
||||
dir: RootDiskDirectory::at(geth_keystore(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for GethDirectory {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
self.dir.load()
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||
self.dir.insert(account)
|
||||
}
|
||||
|
||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||
self.dir.update(account)
|
||||
}
|
||||
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
self.dir.remove(account)
|
||||
}
|
||||
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
self.dir.unique_repr()
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ use ethkey::Address;
|
||||
use {SafeAccount, Error};
|
||||
use super::KeyDirectory;
|
||||
|
||||
/// Accounts in-memory storage.
|
||||
#[derive(Default)]
|
||||
pub struct MemoryDirectory {
|
||||
accounts: RwLock<HashMap<Address, Vec<SafeAccount>>>,
|
||||
|
@ -14,19 +14,15 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Accounts Directory
|
||||
|
||||
use std::path::{PathBuf};
|
||||
use {SafeAccount, Error};
|
||||
|
||||
mod disk;
|
||||
mod geth;
|
||||
mod memory;
|
||||
mod parity;
|
||||
mod vault;
|
||||
|
||||
pub enum DirectoryType {
|
||||
Testnet,
|
||||
Main,
|
||||
}
|
||||
pub mod paths;
|
||||
|
||||
/// `VaultKeyDirectory::set_key` error
|
||||
#[derive(Debug)]
|
||||
@ -54,7 +50,7 @@ pub trait KeyDirectory: Send + Sync {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error>;
|
||||
/// Insert new key to directory
|
||||
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>;
|
||||
/// Remove key from directory
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
|
||||
@ -95,9 +91,7 @@ pub trait VaultKeyDirectory: KeyDirectory {
|
||||
}
|
||||
|
||||
pub use self::disk::RootDiskDirectory;
|
||||
pub use self::geth::GethDirectory;
|
||||
pub use self::memory::MemoryDirectory;
|
||||
pub use self::parity::ParityDirectory;
|
||||
pub use self::vault::VaultDiskDirectory;
|
||||
|
||||
impl VaultKey {
|
||||
|
@ -1,81 +0,0 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use {SafeAccount, Error};
|
||||
use super::{KeyDirectory, RootDiskDirectory, DirectoryType};
|
||||
|
||||
fn parity_dir_path() -> PathBuf {
|
||||
let mut home = env::home_dir().expect("Failed to get home dir");
|
||||
home.push(".parity");
|
||||
home
|
||||
}
|
||||
|
||||
fn parity_keystore(t: DirectoryType) -> PathBuf {
|
||||
let mut dir = parity_dir_path();
|
||||
match t {
|
||||
DirectoryType::Testnet => {
|
||||
dir.push("testnet_keys");
|
||||
},
|
||||
DirectoryType::Main => {
|
||||
dir.push("keys");
|
||||
}
|
||||
}
|
||||
dir
|
||||
}
|
||||
|
||||
pub struct ParityDirectory {
|
||||
dir: RootDiskDirectory,
|
||||
}
|
||||
|
||||
impl ParityDirectory {
|
||||
pub fn create(t: DirectoryType) -> Result<Self, Error> {
|
||||
let result = ParityDirectory {
|
||||
dir: RootDiskDirectory::create(parity_keystore(t))?,
|
||||
};
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn open(t: DirectoryType) -> Self {
|
||||
ParityDirectory {
|
||||
dir: RootDiskDirectory::at(parity_keystore(t)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyDirectory for ParityDirectory {
|
||||
fn load(&self) -> Result<Vec<SafeAccount>, Error> {
|
||||
self.dir.load()
|
||||
}
|
||||
|
||||
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||
self.dir.insert(account)
|
||||
}
|
||||
|
||||
fn update(&self, account: SafeAccount) -> Result<SafeAccount, Error> {
|
||||
self.dir.update(account)
|
||||
}
|
||||
|
||||
fn remove(&self, account: &SafeAccount) -> Result<(), Error> {
|
||||
self.dir.remove(account)
|
||||
}
|
||||
|
||||
fn unique_repr(&self) -> Result<u64, Error> {
|
||||
self.dir.unique_repr()
|
||||
}
|
||||
}
|
96
ethstore/src/dir/paths.rs
Normal file
96
ethstore/src/dir/paths.rs
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Parity.
|
||||
|
||||
// Parity is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Parity is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Common tools paths.
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn home() -> PathBuf {
|
||||
env::home_dir().expect("Failed to get home dir")
|
||||
}
|
||||
|
||||
/// Geth path
|
||||
pub fn geth(testnet: bool) -> PathBuf {
|
||||
let mut base = geth_base();
|
||||
if testnet {
|
||||
base.push("testnet");
|
||||
}
|
||||
base.push("keystore");
|
||||
base
|
||||
}
|
||||
|
||||
/// Parity path for specific chain
|
||||
pub fn parity(chain: &str) -> PathBuf {
|
||||
let mut base = parity_base();
|
||||
base.push(chain);
|
||||
base
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn parity_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push("Library");
|
||||
home.push("Application Support");
|
||||
home.push("io.parity.ethereum");
|
||||
home.push("keys");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn parity_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push("AppData");
|
||||
home.push("Roaming");
|
||||
home.push("Parity");
|
||||
home.push("Ethereum");
|
||||
home.push("keys");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
fn parity_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push(".local");
|
||||
home.push("share");
|
||||
home.push("io.parity.ethereum");
|
||||
home.push("keys");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn geth_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push("Library");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn geth_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push("AppData");
|
||||
home.push("Roaming");
|
||||
home.push("Ethereum");
|
||||
home
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", windows)))]
|
||||
fn geth_base() -> PathBuf {
|
||||
let mut home = home();
|
||||
home.push(".ethereum");
|
||||
home
|
||||
}
|
@ -20,23 +20,40 @@ use ethkey::Error as EthKeyError;
|
||||
use crypto::Error as EthCryptoError;
|
||||
use ethkey::DerivationError;
|
||||
|
||||
/// Account-related errors.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// IO error
|
||||
Io(IoError),
|
||||
/// Invalid Password
|
||||
InvalidPassword,
|
||||
/// Account's secret is invalid.
|
||||
InvalidSecret,
|
||||
/// Invalid Vault Crypto meta.
|
||||
InvalidCryptoMeta,
|
||||
/// Invalid Account.
|
||||
InvalidAccount,
|
||||
/// Invalid Message.
|
||||
InvalidMessage,
|
||||
/// Invalid Key File
|
||||
InvalidKeyFile(String),
|
||||
/// Vaults are not supported.
|
||||
VaultsAreNotSupported,
|
||||
/// Unsupported vault
|
||||
UnsupportedVault,
|
||||
/// Invalid vault name
|
||||
InvalidVaultName,
|
||||
/// Vault not found
|
||||
VaultNotFound,
|
||||
/// Account creation failed.
|
||||
CreationFailed,
|
||||
/// `EthKey` error
|
||||
EthKey(EthKeyError),
|
||||
/// `EthCrypto` error
|
||||
EthCrypto(EthCryptoError),
|
||||
/// Derivation error
|
||||
Derivation(DerivationError),
|
||||
/// Custom error
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
|
@ -25,18 +25,21 @@ use ethkey::{self, Signature, Address, Message, Secret, Public, KeyPair, Extende
|
||||
use dir::{KeyDirectory, VaultKeyDirectory, VaultKey, SetKeyError};
|
||||
use account::SafeAccount;
|
||||
use presale::PresaleWallet;
|
||||
use json::{self, Uuid};
|
||||
use json::{self, Uuid, OpaqueKeyFile};
|
||||
use {import, Error, SimpleSecretStore, SecretStore, SecretVaultRef, StoreAccountRef, Derivation};
|
||||
|
||||
/// Accounts store.
|
||||
pub struct EthStore {
|
||||
store: EthMultiStore,
|
||||
}
|
||||
|
||||
impl EthStore {
|
||||
/// Open a new accounts store with given key directory backend.
|
||||
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
|
||||
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> {
|
||||
Ok(EthStore {
|
||||
store: EthMultiStore::open_with_iterations(directory, iterations)?,
|
||||
@ -44,7 +47,7 @@ impl EthStore {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -76,6 +79,10 @@ impl SimpleSecretStore for EthStore {
|
||||
self.store.change_password(account, old_password, new_password)
|
||||
}
|
||||
|
||||
fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error> {
|
||||
self.store.export_account(account, password)
|
||||
}
|
||||
|
||||
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error> {
|
||||
self.store.remove_account(account, password)
|
||||
}
|
||||
@ -234,11 +241,12 @@ pub struct EthMultiStore {
|
||||
}
|
||||
|
||||
impl EthMultiStore {
|
||||
|
||||
/// Open new multi-accounts store with given key directory backend.
|
||||
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
|
||||
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> {
|
||||
let store = EthMultiStore {
|
||||
dir: directory,
|
||||
@ -287,7 +295,7 @@ impl EthMultiStore {
|
||||
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();
|
||||
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> {
|
||||
// save to file
|
||||
let account = match vault {
|
||||
@ -398,12 +415,8 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
fn insert_derived(&self, vault: SecretVaultRef, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
|
||||
-> Result<StoreAccountRef, Error>
|
||||
{
|
||||
let accounts = self.get(account_ref)?;
|
||||
let accounts = self.get_matching(account_ref, password)?;
|
||||
for account in accounts {
|
||||
// Skip if password is invalid
|
||||
if !account.check_password(password) {
|
||||
continue;
|
||||
}
|
||||
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
||||
return self.insert_account(vault, extended.secret().as_raw().clone(), password);
|
||||
}
|
||||
@ -413,14 +426,9 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation)
|
||||
-> Result<Address, Error>
|
||||
{
|
||||
let accounts = self.get(&account_ref)?;
|
||||
let accounts = self.get_matching(&account_ref, password)?;
|
||||
for account in accounts {
|
||||
// Skip if password is invalid
|
||||
if !account.check_password(password) {
|
||||
continue;
|
||||
}
|
||||
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
||||
|
||||
return Ok(ethkey::public_to_address(extended.public().public()));
|
||||
}
|
||||
Err(Error::InvalidPassword)
|
||||
@ -429,18 +437,13 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
fn sign_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation, message: &Message)
|
||||
-> Result<Signature, Error>
|
||||
{
|
||||
let accounts = self.get(&account_ref)?;
|
||||
let accounts = self.get_matching(&account_ref, password)?;
|
||||
for account in accounts {
|
||||
// Skip if password is invalid
|
||||
if !account.check_password(password) {
|
||||
continue;
|
||||
}
|
||||
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
||||
let secret = extended.secret().as_raw();
|
||||
return Ok(ethkey::sign(&secret, message)?)
|
||||
}
|
||||
Err(Error::InvalidPassword)
|
||||
|
||||
}
|
||||
|
||||
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
||||
@ -457,47 +460,47 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
}
|
||||
|
||||
fn remove_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result<(), Error> {
|
||||
let accounts = self.get(account_ref)?;
|
||||
let accounts = self.get_matching(account_ref, password)?;
|
||||
|
||||
for account in accounts {
|
||||
// Skip if password is invalid
|
||||
if !account.check_password(password) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return self.remove_safe_account(account_ref, &account);
|
||||
}
|
||||
|
||||
Err(Error::InvalidPassword)
|
||||
}
|
||||
|
||||
fn change_password(&self, account_ref: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error> {
|
||||
let accounts = self.get(account_ref)?;
|
||||
let accounts = self.get_matching(account_ref, old_password)?;
|
||||
|
||||
if accounts.is_empty() {
|
||||
return Err(Error::InvalidPassword);
|
||||
}
|
||||
|
||||
for account in accounts {
|
||||
// Change password
|
||||
let new_account = account.change_password(old_password, new_password, self.iterations)?;
|
||||
self.update(account_ref, account, new_account)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||
let accounts = self.get(account)?;
|
||||
for account in accounts {
|
||||
if account.check_password(password) {
|
||||
return account.sign(password, message);
|
||||
}
|
||||
}
|
||||
fn export_account(&self, account_ref: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error> {
|
||||
self.get_matching(account_ref, password)?.into_iter().nth(0).map(Into::into).ok_or(Error::InvalidPassword)
|
||||
}
|
||||
|
||||
fn sign(&self, account: &StoreAccountRef, password: &str, message: &Message) -> Result<Signature, Error> {
|
||||
let accounts = self.get_matching(account, password)?;
|
||||
for account in accounts {
|
||||
return account.sign(password, message);
|
||||
}
|
||||
Err(Error::InvalidPassword)
|
||||
}
|
||||
|
||||
fn decrypt(&self, account: &StoreAccountRef, password: &str, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
let accounts = self.get(account)?;
|
||||
let accounts = self.get_matching(account, password)?;
|
||||
for account in accounts {
|
||||
if account.check_password(password) {
|
||||
return account.decrypt(password, shared_mac, message);
|
||||
}
|
||||
return account.decrypt(password, shared_mac, message);
|
||||
}
|
||||
Err(Error::InvalidPassword)
|
||||
}
|
||||
@ -586,7 +589,7 @@ impl SimpleSecretStore for EthMultiStore {
|
||||
return Ok(account_ref);
|
||||
}
|
||||
|
||||
let account = self.get(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?;
|
||||
let account = self.get_accounts(&account_ref)?.into_iter().nth(0).ok_or(Error::InvalidAccount)?;
|
||||
let new_account_ref = self.import(vault, account.clone())?;
|
||||
self.remove_safe_account(&account_ref, &account)?;
|
||||
self.reload_accounts()?;
|
||||
@ -1032,4 +1035,18 @@ mod tests {
|
||||
// then
|
||||
assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_export_account() {
|
||||
// given
|
||||
let store = store();
|
||||
let keypair = keypair();
|
||||
let address = store.insert_account(SecretVaultRef::Root, keypair.secret().clone(), "test").unwrap();
|
||||
|
||||
// when
|
||||
let exported = store.export_account(&address, "test");
|
||||
|
||||
// then
|
||||
assert!(exported.is_ok(), "Should export single account: {:?}", exported);
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,10 @@
|
||||
|
||||
use std::collections::HashSet;
|
||||
use ethkey::Address;
|
||||
use dir::{GethDirectory, KeyDirectory, DirectoryType};
|
||||
use dir::{paths, KeyDirectory, RootDiskDirectory};
|
||||
use Error;
|
||||
|
||||
/// Import all accounts from one directory to the other.
|
||||
pub fn import_accounts(src: &KeyDirectory, dst: &KeyDirectory) -> Result<Vec<Address>, Error> {
|
||||
let accounts = src.load()?;
|
||||
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.
|
||||
pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
|
||||
let t = if testnet {
|
||||
DirectoryType::Testnet
|
||||
} else {
|
||||
DirectoryType::Main
|
||||
};
|
||||
|
||||
GethDirectory::open(t)
|
||||
RootDiskDirectory::at(paths::geth(testnet))
|
||||
.load()
|
||||
.map(|d| d.into_iter().map(|a| a.address).collect())
|
||||
.unwrap_or_else(|_| Vec::new())
|
||||
@ -48,13 +43,7 @@ pub fn read_geth_accounts(testnet: bool) -> Vec<Address> {
|
||||
|
||||
/// 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> {
|
||||
let t = if testnet {
|
||||
DirectoryType::Testnet
|
||||
} else {
|
||||
DirectoryType::Main
|
||||
};
|
||||
|
||||
let src = GethDirectory::open(t);
|
||||
let src = RootDiskDirectory::at(paths::geth(testnet));
|
||||
let accounts = src.load()?;
|
||||
let existing_accounts = dst.load()?.into_iter().map(|a| a.address).collect::<HashSet<_>>();
|
||||
|
||||
|
@ -16,11 +16,31 @@
|
||||
|
||||
use std::fmt;
|
||||
use std::io::{Read, Write};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{Serialize, Serializer, Deserialize, Deserializer};
|
||||
use serde::de::{Error, Visitor, MapVisitor};
|
||||
use serde_json;
|
||||
use super::{Uuid, Version, Crypto, H160};
|
||||
|
||||
/// Public opaque type representing serializable `KeyFile`.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct OpaqueKeyFile {
|
||||
key_file: KeyFile
|
||||
}
|
||||
|
||||
impl Serialize for OpaqueKeyFile {
|
||||
fn serialize<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)]
|
||||
pub struct KeyFile {
|
||||
pub id: Uuid,
|
||||
|
@ -36,7 +36,7 @@ pub use self::error::Error;
|
||||
pub use self::hash::{H128, H160, H256};
|
||||
pub use self::id::Uuid;
|
||||
pub use self::kdf::{Kdf, KdfSer, Prf, Pbkdf2, Scrypt, KdfSerParams};
|
||||
pub use self::key_file::KeyFile;
|
||||
pub use self::key_file::{KeyFile, OpaqueKeyFile};
|
||||
pub use self::presale::{PresaleWallet, Encseed};
|
||||
pub use self::vault_file::VaultFile;
|
||||
pub use self::vault_key_file::{VaultKeyFile, VaultKeyMeta, insert_vault_name_to_json_meta, remove_vault_name_from_json_meta};
|
||||
|
@ -14,6 +14,11 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Ethereum key-management.
|
||||
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
extern crate libc;
|
||||
extern crate itertools;
|
||||
extern crate smallvec;
|
||||
@ -52,10 +57,11 @@ mod presale;
|
||||
mod random;
|
||||
mod secret_store;
|
||||
|
||||
pub use self::account::{SafeAccount};
|
||||
pub use self::account::SafeAccount;
|
||||
pub use self::error::Error;
|
||||
pub use self::ethstore::{EthStore, EthMultiStore};
|
||||
pub use self::import::{import_accounts, read_geth_accounts};
|
||||
pub use self::json::OpaqueKeyFile as KeyFile;
|
||||
pub use self::presale::PresaleWallet;
|
||||
pub use self::secret_store::{
|
||||
SecretVaultRef, StoreAccountRef, SimpleSecretStore, SecretStore,
|
||||
|
@ -8,6 +8,7 @@ use ethkey::{Address, Secret, KeyPair};
|
||||
use crypto::Keccak256;
|
||||
use {crypto, Error};
|
||||
|
||||
/// Pre-sale wallet.
|
||||
pub struct PresaleWallet {
|
||||
iv: [u8; 16],
|
||||
ciphertext: Vec<u8>,
|
||||
@ -31,6 +32,7 @@ impl From<json::PresaleWallet> for PresaleWallet {
|
||||
}
|
||||
|
||||
impl PresaleWallet {
|
||||
/// Open a pre-sale wallet.
|
||||
pub fn open<P>(path: P) -> Result<Self, Error> where P: AsRef<Path> {
|
||||
let file = fs::File::open(path)?;
|
||||
let presale = json::PresaleWallet::load(file)
|
||||
@ -38,6 +40,7 @@ impl PresaleWallet {
|
||||
Ok(PresaleWallet::from(presale))
|
||||
}
|
||||
|
||||
/// Decrypt the wallet.
|
||||
pub fn decrypt(&self, password: &str) -> Result<KeyPair, Error> {
|
||||
let mut h_mac = Hmac::new(Sha256::new(), password.as_bytes());
|
||||
let mut derived_key = vec![0u8; 16];
|
||||
|
@ -18,7 +18,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use ethkey::{Address, Message, Signature, Secret, Public};
|
||||
use Error;
|
||||
use json::Uuid;
|
||||
use json::{Uuid, OpaqueKeyFile};
|
||||
use util::H256;
|
||||
|
||||
/// Key directory reference
|
||||
@ -39,16 +39,28 @@ pub struct StoreAccountRef {
|
||||
pub address: Address,
|
||||
}
|
||||
|
||||
/// Simple Secret Store API
|
||||
pub trait SimpleSecretStore: Send + Sync {
|
||||
/// Inserts new accounts to the store (or vault) with given password.
|
||||
fn insert_account(&self, vault: SecretVaultRef, secret: Secret, password: &str) -> Result<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>;
|
||||
/// Changes accounts password.
|
||||
fn change_password(&self, account: &StoreAccountRef, old_password: &str, new_password: &str) -> Result<(), Error>;
|
||||
/// Exports key details for account.
|
||||
fn export_account(&self, account: &StoreAccountRef, password: &str) -> Result<OpaqueKeyFile, Error>;
|
||||
/// Entirely removes account from the store and underlying storage.
|
||||
fn remove_account(&self, account: &StoreAccountRef, password: &str) -> Result<(), Error>;
|
||||
/// Generates new derived account.
|
||||
fn generate_derived(&self, account_ref: &StoreAccountRef, password: &str, derivation: Derivation) -> Result<Address, Error>;
|
||||
/// Sign a message with given account.
|
||||
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>;
|
||||
/// Decrypt a messages with given account.
|
||||
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>;
|
||||
/// Get reference to some account with given address.
|
||||
/// This method could be removed if we will guarantee that there is max(1) account for given address.
|
||||
@ -74,23 +86,37 @@ pub trait SimpleSecretStore: Send + Sync {
|
||||
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
/// Secret Store API
|
||||
pub trait SecretStore: SimpleSecretStore {
|
||||
/// Imports presale wallet
|
||||
fn import_presale(&self, vault: SecretVaultRef, json: &[u8], password: &str) -> Result<StoreAccountRef, Error>;
|
||||
/// Imports existing JSON wallet
|
||||
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>;
|
||||
/// Checks if password matches given account.
|
||||
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>;
|
||||
|
||||
/// Returns uuid of an account.
|
||||
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error>;
|
||||
/// Returns account's name.
|
||||
fn name(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
||||
/// Returns account's metadata.
|
||||
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error>;
|
||||
|
||||
/// Modifies account metadata.
|
||||
fn set_name(&self, account: &StoreAccountRef, name: String) -> Result<(), Error>;
|
||||
/// Modifies account name.
|
||||
fn set_meta(&self, account: &StoreAccountRef, meta: String) -> Result<(), Error>;
|
||||
|
||||
/// Returns local path of the store.
|
||||
fn local_path(&self) -> PathBuf;
|
||||
/// Lists all found geth accounts.
|
||||
fn list_geth_accounts(&self, testnet: bool) -> Vec<Address>;
|
||||
/// Imports geth accounts to the store/vault.
|
||||
fn import_geth_accounts(&self, vault: SecretVaultRef, desired: Vec<Address>, testnet: bool) -> Result<Vec<StoreAccountRef>, Error>;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@ use std::collections::BTreeMap;
|
||||
use util::{Address};
|
||||
|
||||
use ethkey::{Brain, Generator, Secret};
|
||||
use ethstore::KeyFile;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
@ -315,6 +316,17 @@ impl ParityAccounts for ParityAccountsClient {
|
||||
.map(Into::into)
|
||||
.map_err(|e| errors::account("Could not derive account.", e))
|
||||
}
|
||||
|
||||
fn export_account(&self, addr: RpcH160, password: String) -> Result<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
|
||||
|
@ -472,3 +472,30 @@ fn derive_key_index() {
|
||||
let res = tester.io.handle_request_sync(&request);
|
||||
assert_eq!(res, Some(response.into()));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn should_export_account() {
|
||||
// given
|
||||
let tester = setup();
|
||||
let wallet = r#"{"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","version":3,"crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","name":"parity-export-test","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}"}"#;
|
||||
tester.accounts.import_wallet(wallet.as_bytes(), "parity-export-test").unwrap();
|
||||
let accounts = tester.accounts.accounts().unwrap();
|
||||
assert_eq!(accounts.len(), 1);
|
||||
|
||||
// invalid password
|
||||
let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","123"],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","error":{"code":-32023,"message":"Could not export account.","data":"InvalidPassword"},"id":1}"#;
|
||||
let res = tester.io.handle_request_sync(&request);
|
||||
assert_eq!(res, Some(response.into()));
|
||||
|
||||
// correct password
|
||||
let request = r#"{"jsonrpc":"2.0","method":"parity_exportAccount","params":["0x0042e5d2a662eeaca8a7e828c174f98f35d8925b","parity-export-test"],"id":1}"#;
|
||||
|
||||
let response = r#"{"jsonrpc":"2.0","result":{"address":"0042e5d2a662eeaca8a7e828c174f98f35d8925b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"a1c6ff99070f8032ca1c4e8add006373"},"ciphertext":"df27e3db64aa18d984b6439443f73660643c2d119a6f0fa2fa9a6456fc802d75","kdf":"pbkdf2","kdfparams":{"c":10240,"dklen":32,"prf":"hmac-sha256","salt":"ddc325335cda5567a1719313e73b4842511f3e4a837c9658eeb78e51ebe8c815"},"mac":"3dc888ae79cbb226ff9c455669f6cf2d79be72120f2298f6cb0d444fddc0aa3d"},"id":"6a186c80-7797-cff2-bc2e-7c1d6a6cc76e","meta":"{\"passwordHint\":\"parity-export-test\",\"timestamp\":1490017814987}","name":"parity-export-test","version":3},"id":1}"#;
|
||||
let result = tester.io.handle_request_sync(&request);
|
||||
|
||||
println!("Result: {:?}", result);
|
||||
println!("Response: {:?}", response);
|
||||
assert_eq!(result, Some(response.into()));
|
||||
}
|
||||
|
@ -18,6 +18,7 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use jsonrpc_core::Error;
|
||||
use ethstore::KeyFile;
|
||||
use v1::types::{H160, H256, DappId, DeriveHash, DeriveHierarchical};
|
||||
|
||||
build_rpc_trait! {
|
||||
@ -175,5 +176,9 @@ build_rpc_trait! {
|
||||
/// Resulting address can be either saved as a new account (with the same password).
|
||||
#[rpc(name = "parity_deriveAddressIndex")]
|
||||
fn derive_key_index(&self, H160, String, DeriveHierarchical, bool) -> Result<H160, Error>;
|
||||
|
||||
/// Exports an account with given address if provided password matches.
|
||||
#[rpc(name = "parity_exportAccount")]
|
||||
fn export_account(&self, H160, String) -> Result<KeyFile, Error>;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user