2019-01-07 11:33:07 +01:00
|
|
|
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
|
|
|
|
// This file is part of Parity Ethereum.
|
2016-06-20 10:06:49 +02:00
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is free software: you can redistribute it and/or modify
|
2016-06-20 10:06:49 +02:00
|
|
|
// 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.
|
|
|
|
|
2019-01-07 11:33:07 +01:00
|
|
|
// Parity Ethereum is distributed in the hope that it will be useful,
|
2016-06-20 10:06:49 +02:00
|
|
|
// 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
|
2019-01-07 11:33:07 +01:00
|
|
|
// along with Parity Ethereum. If not, see <http://www.gnu.org/licenses/>.
|
2016-06-20 10:06:49 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
use parking_lot::{Mutex, RwLock};
|
2017-10-20 20:20:41 +02:00
|
|
|
use std::{
|
2017-01-30 11:44:09 +01:00
|
|
|
collections::{BTreeMap, HashMap},
|
2019-02-06 17:53:34 +01:00
|
|
|
num::NonZeroU32,
|
2017-02-03 13:56:48 +01:00
|
|
|
path::PathBuf,
|
2017-10-20 20:20:41 +02:00
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2016-12-09 10:45:34 +01:00
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
use account::SafeAccount;
|
2017-12-24 09:34:43 +01:00
|
|
|
use accounts_dir::{KeyDirectory, SetKeyError, VaultKey, VaultKeyDirectory};
|
2018-06-22 15:09:15 +02:00
|
|
|
use ethkey::{
|
|
|
|
self, Address, ExtendedKeyPair, KeyPair, Message, Password, Public, Secret, Signature,
|
2020-08-05 06:08:03 +02:00
|
|
|
};
|
2017-03-23 13:23:03 +01:00
|
|
|
use json::{self, OpaqueKeyFile, Uuid};
|
2016-08-10 17:57:40 +02:00
|
|
|
use presale::PresaleWallet;
|
2017-03-23 13:23:03 +01:00
|
|
|
use random::Random;
|
2017-06-06 18:06:40 +02:00
|
|
|
use Derivation;
|
|
|
|
use Error;
|
|
|
|
use OpaqueSecret;
|
|
|
|
use SecretStore;
|
|
|
|
use SecretVaultRef;
|
|
|
|
use SimpleSecretStore;
|
|
|
|
use StoreAccountRef;
|
2019-02-06 17:53:34 +01:00
|
|
|
|
|
|
|
lazy_static! {
|
|
|
|
static ref KEY_ITERATIONS: NonZeroU32 =
|
|
|
|
NonZeroU32::new(crypto::KEY_ITERATIONS as u32).expect("KEY_ITERATIONS > 0; qed");
|
|
|
|
}
|
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
/// Accounts store.
|
2016-06-20 00:10:34 +02:00
|
|
|
pub struct EthStore {
|
2016-11-30 15:08:38 +01:00
|
|
|
store: EthMultiStore,
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EthStore {
|
2017-03-23 13:23:03 +01:00
|
|
|
/// Open a new accounts store with given key directory backend.
|
2020-07-29 10:36:15 +02:00
|
|
|
pub fn open(directory: Box<dyn KeyDirectory>) -> Result<Self, Error> {
|
2019-02-06 17:53:34 +01:00
|
|
|
Self::open_with_iterations(directory, *KEY_ITERATIONS)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
/// Open a new account store with given key directory backend and custom number of iterations.
|
2019-02-06 17:53:34 +01:00
|
|
|
pub fn open_with_iterations(
|
2020-07-29 10:36:15 +02:00
|
|
|
directory: Box<dyn KeyDirectory>,
|
2019-02-06 17:53:34 +01:00
|
|
|
iterations: NonZeroU32,
|
|
|
|
) -> Result<Self, Error> {
|
2016-11-30 15:08:38 +01:00
|
|
|
Ok(EthStore {
|
2016-12-27 12:53:56 +01:00
|
|
|
store: EthMultiStore::open_with_iterations(directory, iterations)?,
|
2016-11-30 15:08:38 +01:00
|
|
|
})
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-12-22 04:33:49 +01:00
|
|
|
/// Modify account refresh timeout - how often they are re-read from `KeyDirectory`.
|
|
|
|
///
|
|
|
|
/// Setting this to low values (or 0) will cause new accounts to be picked up quickly,
|
|
|
|
/// although it may induce heavy disk reads and is not recommended if you manage many keys (say over 10k).
|
|
|
|
///
|
|
|
|
/// By default refreshing is disabled, so only accounts created using this instance of `EthStore` are taken into account.
|
|
|
|
pub fn set_refresh_time(&self, time: Duration) {
|
|
|
|
self.store.set_refresh_time(time)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn get(&self, account: &StoreAccountRef) -> Result<SafeAccount, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let mut accounts = self.store.get_accounts(account)?.into_iter();
|
2016-11-30 15:08:38 +01:00
|
|
|
accounts.next().ok_or(Error::InvalidAccount)
|
|
|
|
}
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
impl SimpleSecretStore for EthStore {
|
2018-06-22 15:09:15 +02:00
|
|
|
fn insert_account(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
secret: Secret,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.insert_account(vault, secret, password)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn insert_derived(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
2017-02-15 16:56:15 +01:00
|
|
|
) -> Result<StoreAccountRef, Error> {
|
|
|
|
self.store
|
|
|
|
.insert_derived(vault, account_ref, password, derivation)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn generate_derived(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
|
|
|
) -> Result<Address, Error> {
|
2017-02-15 16:56:15 +01:00
|
|
|
self.store
|
|
|
|
.generate_derived(account_ref, password, derivation)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
|
|
|
self.store.account_ref(address)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
|
2016-11-30 15:08:38 +01:00
|
|
|
self.store.accounts()
|
2016-07-31 10:44:17 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn change_password(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
old_password: &Password,
|
|
|
|
new_password: &Password,
|
|
|
|
) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store
|
|
|
|
.change_password(account, old_password, new_password)
|
2016-07-31 10:44:17 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn export_account(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<OpaqueKeyFile, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
self.store.export_account(account, password)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn remove_account(&self, account: &StoreAccountRef, password: &Password) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.remove_account(account, password)
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn sign(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
message: &Message,
|
|
|
|
) -> Result<Signature, Error> {
|
2017-02-15 16:56:15 +01:00
|
|
|
self.get(account)?.sign(password, message)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn sign_derived(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
|
|
|
message: &Message,
|
2017-02-15 16:56:15 +01:00
|
|
|
) -> Result<Signature, Error> {
|
|
|
|
self.store
|
|
|
|
.sign_derived(account_ref, password, derivation, message)
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn agree(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
other: &Public,
|
|
|
|
) -> Result<Secret, Error> {
|
2017-08-09 11:09:40 +02:00
|
|
|
self.store.agree(account, password, other)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn decrypt(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
2016-11-30 15:08:38 +01:00
|
|
|
shared_mac: &[u8],
|
|
|
|
message: &[u8],
|
|
|
|
) -> Result<Vec<u8>, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = self.get(account)?;
|
2017-08-09 11:09:40 +02:00
|
|
|
account.decrypt(password, shared_mac, message)
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.create_vault(name, password)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.open_vault(name, password)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn close_vault(&self, name: &str) -> Result<(), Error> {
|
|
|
|
self.store.close_vault(name)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
self.store.list_vaults()
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
self.store.list_opened_vaults()
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
self.store.change_vault_password(name, new_password)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn change_account_vault(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
account: StoreAccountRef,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
|
|
|
self.store.change_account_vault(vault, account)
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-08 13:53:39 +01:00
|
|
|
fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
|
|
|
self.store.get_vault_meta(name)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-08 13:53:39 +01:00
|
|
|
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
|
|
|
self.store.set_vault_meta(name, meta)
|
|
|
|
}
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
impl SecretStore for EthStore {
|
2018-06-22 15:09:15 +02:00
|
|
|
fn raw_secret(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<OpaqueSecret, Error> {
|
2017-06-06 18:06:40 +02:00
|
|
|
Ok(OpaqueSecret(self.get(account)?.crypto.secret(password)?))
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn import_presale(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
json: &[u8],
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2016-12-27 12:53:56 +01:00
|
|
|
let json_wallet = json::PresaleWallet::load(json)
|
|
|
|
.map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?;
|
2016-08-10 17:57:40 +02:00
|
|
|
let wallet = PresaleWallet::from(json_wallet);
|
2016-12-27 12:53:56 +01:00
|
|
|
let keypair = wallet
|
|
|
|
.decrypt(password)
|
|
|
|
.map_err(|_| Error::InvalidPassword)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
self.insert_account(vault, keypair.secret().clone(), password)
|
2016-08-10 17:57:40 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn import_wallet(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
json: &[u8],
|
|
|
|
password: &Password,
|
|
|
|
gen_id: bool,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2016-12-27 12:53:56 +01:00
|
|
|
let json_keyfile = json::KeyFile::load(json)
|
|
|
|
.map_err(|_| Error::InvalidKeyFile("Invalid JSON format".to_owned()))?;
|
2019-01-03 14:07:27 +01:00
|
|
|
let mut safe_account = SafeAccount::from_file(json_keyfile, None, &None)?;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-02-14 14:21:58 +01:00
|
|
|
if gen_id {
|
|
|
|
safe_account.id = Random::random();
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-27 12:53:56 +01:00
|
|
|
let secret = safe_account
|
|
|
|
.crypto
|
|
|
|
.secret(password)
|
|
|
|
.map_err(|_| Error::InvalidPassword)?;
|
|
|
|
safe_account.address = KeyPair::from_secret(secret)?.address();
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.import(vault, safe_account)
|
2016-08-10 17:57:40 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn test_password(&self, account: &StoreAccountRef, password: &Password) -> Result<bool, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = self.get(account)?;
|
2016-11-30 15:08:38 +01:00
|
|
|
Ok(account.check_password(password))
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn copy_account(
|
|
|
|
&self,
|
2020-07-29 10:36:15 +02:00
|
|
|
new_store: &dyn SimpleSecretStore,
|
2018-06-22 15:09:15 +02:00
|
|
|
new_vault: SecretVaultRef,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
2016-12-27 12:53:56 +01:00
|
|
|
new_password: &Password,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
let account = self.get(account)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
let secret = account.crypto.secret(password)?;
|
2018-06-22 15:09:15 +02:00
|
|
|
new_store.insert_account(new_vault, secret, new_password)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn public(&self, account: &StoreAccountRef, password: &Password) -> Result<Public, Error> {
|
2016-12-27 12:53:56 +01:00
|
|
|
let account = self.get(account)?;
|
2016-10-15 14:44:08 +02:00
|
|
|
account.public(password)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn uuid(&self, account: &StoreAccountRef) -> Result<Uuid, Error> {
|
|
|
|
let account = self.get(account)?;
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(account.id.into())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn name(&self, account: &StoreAccountRef) -> Result<String, Error> {
|
|
|
|
let account = self.get(account)?;
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(account.name.clone())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn meta(&self, account: &StoreAccountRef) -> Result<String, Error> {
|
|
|
|
let account = self.get(account)?;
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(account.meta.clone())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn set_name(&self, account_ref: &StoreAccountRef, name: String) -> Result<(), Error> {
|
|
|
|
let old = self.get(account_ref)?;
|
|
|
|
let mut safe_account = old.clone();
|
|
|
|
safe_account.name = name;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
// save to file
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.update(account_ref, old, safe_account)
|
2016-07-24 17:38:21 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn set_meta(&self, account_ref: &StoreAccountRef, meta: String) -> Result<(), Error> {
|
|
|
|
let old = self.get(account_ref)?;
|
|
|
|
let mut safe_account = old.clone();
|
|
|
|
safe_account.meta = meta;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
// save to file
|
2017-01-30 11:44:09 +01:00
|
|
|
self.store.update(account_ref, old, safe_account)
|
2016-07-24 17:38:21 +02:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-03 13:56:48 +01:00
|
|
|
fn local_path(&self) -> PathBuf {
|
|
|
|
self.store.dir.path().cloned().unwrap_or_else(PathBuf::new)
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2016-11-30 13:47:14 +01:00
|
|
|
|
|
|
|
/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address`
|
|
|
|
pub struct EthMultiStore {
|
2020-07-29 10:36:15 +02:00
|
|
|
dir: Box<dyn KeyDirectory>,
|
2019-02-06 17:53:34 +01:00
|
|
|
iterations: NonZeroU32,
|
2017-01-30 11:44:09 +01:00
|
|
|
// order lock: cache, then vaults
|
|
|
|
cache: RwLock<BTreeMap<StoreAccountRef, Vec<SafeAccount>>>,
|
2020-07-29 10:36:15 +02:00
|
|
|
vaults: Mutex<HashMap<String, Box<dyn VaultKeyDirectory>>>,
|
2017-10-20 20:20:41 +02:00
|
|
|
timestamp: Mutex<Timestamp>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Timestamp {
|
|
|
|
dir_hash: Option<u64>,
|
|
|
|
last_checked: Instant,
|
2017-12-22 04:33:49 +01:00
|
|
|
refresh_time: Duration,
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl EthMultiStore {
|
2017-03-23 13:23:03 +01:00
|
|
|
/// Open new multi-accounts store with given key directory backend.
|
2020-07-29 10:36:15 +02:00
|
|
|
pub fn open(directory: Box<dyn KeyDirectory>) -> Result<Self, Error> {
|
2019-02-06 17:53:34 +01:00
|
|
|
Self::open_with_iterations(directory, *KEY_ITERATIONS)
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
/// Open new multi-accounts store with given key directory backend and custom number of iterations for new keys.
|
2019-02-06 17:53:34 +01:00
|
|
|
pub fn open_with_iterations(
|
2020-07-29 10:36:15 +02:00
|
|
|
directory: Box<dyn KeyDirectory>,
|
2019-02-06 17:53:34 +01:00
|
|
|
iterations: NonZeroU32,
|
|
|
|
) -> Result<Self, Error> {
|
2016-11-30 15:08:38 +01:00
|
|
|
let store = EthMultiStore {
|
2016-11-30 13:47:14 +01:00
|
|
|
dir: directory,
|
2017-01-30 11:44:09 +01:00
|
|
|
vaults: Mutex::new(HashMap::new()),
|
2016-11-30 13:47:14 +01:00
|
|
|
iterations: iterations,
|
|
|
|
cache: Default::default(),
|
2017-10-20 20:20:41 +02:00
|
|
|
timestamp: Mutex::new(Timestamp {
|
|
|
|
dir_hash: None,
|
|
|
|
last_checked: Instant::now(),
|
2017-12-22 04:33:49 +01:00
|
|
|
// by default we never refresh accounts
|
|
|
|
refresh_time: Duration::from_secs(u64::max_value()),
|
2017-10-20 20:20:41 +02:00
|
|
|
}),
|
2016-11-30 13:47:14 +01:00
|
|
|
};
|
2016-12-27 12:53:56 +01:00
|
|
|
store.reload_accounts()?;
|
2016-11-30 13:47:14 +01:00
|
|
|
Ok(store)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-12-22 04:33:49 +01:00
|
|
|
/// Modify account refresh timeout - how often they are re-read from `KeyDirectory`.
|
|
|
|
///
|
|
|
|
/// Setting this to low values (or 0) will cause new accounts to be picked up quickly,
|
|
|
|
/// although it may induce heavy disk reads and is not recommended if you manage many keys (say over 10k).
|
|
|
|
///
|
|
|
|
/// By default refreshing is disabled, so only accounts created using this instance of `EthStore` are taken into account.
|
|
|
|
pub fn set_refresh_time(&self, time: Duration) {
|
|
|
|
self.timestamp.lock().refresh_time = time;
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-16 20:10:29 +01:00
|
|
|
fn reload_if_changed(&self) -> Result<(), Error> {
|
2017-10-20 20:20:41 +02:00
|
|
|
let mut last_timestamp = self.timestamp.lock();
|
|
|
|
let now = Instant::now();
|
2017-12-22 04:33:49 +01:00
|
|
|
if now - last_timestamp.last_checked > last_timestamp.refresh_time {
|
2017-10-20 20:20:41 +02:00
|
|
|
let dir_hash = Some(self.dir.unique_repr()?);
|
|
|
|
last_timestamp.last_checked = now;
|
|
|
|
if last_timestamp.dir_hash == dir_hash {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
self.reload_accounts()?;
|
|
|
|
last_timestamp.dir_hash = dir_hash;
|
2017-02-16 20:10:29 +01:00
|
|
|
}
|
2017-03-23 13:23:03 +01:00
|
|
|
Ok(())
|
2017-02-16 20:10:29 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
fn reload_accounts(&self) -> Result<(), Error> {
|
|
|
|
let mut cache = self.cache.write();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
let mut new_accounts = BTreeMap::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
for account in self.dir.load()? {
|
|
|
|
let account_ref = StoreAccountRef::root(account.address);
|
|
|
|
new_accounts
|
|
|
|
.entry(account_ref)
|
|
|
|
.or_insert_with(Vec::new)
|
|
|
|
.push(account);
|
|
|
|
}
|
|
|
|
for (vault_name, vault) in &*self.vaults.lock() {
|
|
|
|
for account in vault.load()? {
|
|
|
|
let account_ref = StoreAccountRef::vault(vault_name, account.address);
|
|
|
|
new_accounts
|
|
|
|
.entry(account_ref)
|
|
|
|
.or_insert_with(Vec::new)
|
|
|
|
.push(account);
|
|
|
|
}
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2020-07-29 10:57:15 +02:00
|
|
|
*cache = new_accounts;
|
2016-11-30 13:47:14 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn get_accounts(&self, account: &StoreAccountRef) -> Result<Vec<SafeAccount>, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let from_cache = |account| {
|
|
|
|
let cache = self.cache.read();
|
|
|
|
if let Some(accounts) = cache.get(account) {
|
2016-11-30 13:47:14 +01:00
|
|
|
if !accounts.is_empty() {
|
2017-12-22 04:33:49 +01:00
|
|
|
return Some(accounts.clone());
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
2017-12-22 04:33:49 +01:00
|
|
|
match from_cache(account) {
|
|
|
|
Some(accounts) => Ok(accounts),
|
|
|
|
None => {
|
|
|
|
self.reload_if_changed()?;
|
|
|
|
from_cache(account).ok_or(Error::InvalidAccount)
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
fn get_matching(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
2018-06-22 15:09:15 +02:00
|
|
|
password: &Password,
|
2017-03-23 13:23:03 +01:00
|
|
|
) -> Result<Vec<SafeAccount>, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let accounts = self.get_accounts(account)?;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-12-22 04:33:49 +01:00
|
|
|
Ok(accounts
|
|
|
|
.into_iter()
|
2018-06-22 15:09:15 +02:00
|
|
|
.filter(|acc| acc.check_password(password))
|
|
|
|
.collect())
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn import(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
account: SafeAccount,
|
2017-01-30 11:44:09 +01:00
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2016-12-09 09:31:58 +01:00
|
|
|
// save to file
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = match vault {
|
|
|
|
SecretVaultRef::Root => self.dir.insert(account)?,
|
|
|
|
SecretVaultRef::Vault(ref vault_name) => self
|
|
|
|
.vaults
|
|
|
|
.lock()
|
|
|
|
.get_mut(vault_name)
|
|
|
|
.ok_or(Error::VaultNotFound)?
|
|
|
|
.insert(account)?,
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
// update cache
|
2017-01-30 11:44:09 +01:00
|
|
|
let account_ref = StoreAccountRef::new(vault, account.address.clone());
|
2016-11-30 13:47:14 +01:00
|
|
|
let mut cache = self.cache.write();
|
2017-01-30 11:44:09 +01:00
|
|
|
cache
|
|
|
|
.entry(account_ref.clone())
|
|
|
|
.or_insert_with(Vec::new)
|
|
|
|
.push(account);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(account_ref)
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn update(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
old: SafeAccount,
|
|
|
|
new: SafeAccount,
|
|
|
|
) -> Result<(), Error> {
|
2016-12-09 09:31:58 +01:00
|
|
|
// save to file
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = match account_ref.vault {
|
|
|
|
SecretVaultRef::Root => self.dir.update(new)?,
|
|
|
|
SecretVaultRef::Vault(ref vault_name) => self
|
|
|
|
.vaults
|
|
|
|
.lock()
|
|
|
|
.get_mut(vault_name)
|
|
|
|
.ok_or(Error::VaultNotFound)?
|
|
|
|
.update(new)?,
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 09:31:58 +01:00
|
|
|
// update cache
|
|
|
|
let mut cache = self.cache.write();
|
2017-10-15 15:10:20 +02:00
|
|
|
let accounts = cache.entry(account_ref.clone()).or_insert_with(Vec::new);
|
2016-12-09 09:31:58 +01:00
|
|
|
// Remove old account
|
|
|
|
accounts.retain(|acc| acc != &old);
|
|
|
|
// And push updated to the end
|
|
|
|
accounts.push(account);
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
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)?,
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
if is_empty {
|
|
|
|
cache.remove(account_ref);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
return Ok(());
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
fn generate(&self, secret: Secret, derivation: Derivation) -> Result<ExtendedKeyPair, Error> {
|
|
|
|
let mut extended = ExtendedKeyPair::new(secret);
|
|
|
|
match derivation {
|
|
|
|
Derivation::Hierarchical(path) => {
|
|
|
|
for path_item in path {
|
|
|
|
extended = extended.derive(if path_item.soft {
|
|
|
|
ethkey::Derivation::Soft(path_item.index)
|
|
|
|
} else {
|
|
|
|
ethkey::Derivation::Hard(path_item.index)
|
|
|
|
})?;
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
2017-02-15 16:56:15 +01:00
|
|
|
}
|
|
|
|
Derivation::SoftHash(h256) => {
|
|
|
|
extended = extended.derive(ethkey::Derivation::Soft(h256))?;
|
|
|
|
}
|
|
|
|
Derivation::HardHash(h256) => {
|
|
|
|
extended = extended.derive(ethkey::Derivation::Hard(h256))?;
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
2017-02-15 16:56:15 +01:00
|
|
|
Ok(extended)
|
|
|
|
}
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl SimpleSecretStore for EthMultiStore {
|
2018-06-22 15:09:15 +02:00
|
|
|
fn insert_account(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
secret: Secret,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2016-12-27 12:53:56 +01:00
|
|
|
let keypair = KeyPair::from_secret(secret).map_err(|_| Error::CreationFailed)?;
|
2016-11-30 15:08:38 +01:00
|
|
|
let id: [u8; 16] = Random::random();
|
2018-05-05 11:02:33 +02:00
|
|
|
let account = SafeAccount::create(
|
|
|
|
&keypair,
|
|
|
|
id,
|
|
|
|
password,
|
|
|
|
self.iterations,
|
|
|
|
"".to_owned(),
|
|
|
|
"{}".to_owned(),
|
|
|
|
)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
self.import(vault, account)
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn insert_derived(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
2017-02-15 16:56:15 +01:00
|
|
|
) -> Result<StoreAccountRef, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(account_ref, password)?;
|
2017-02-15 16:56:15 +01:00
|
|
|
for account in accounts {
|
|
|
|
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
|
|
|
return self.insert_account(vault, extended.secret().as_raw().clone(), password);
|
|
|
|
}
|
|
|
|
Err(Error::InvalidPassword)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn generate_derived(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
2017-02-15 16:56:15 +01:00
|
|
|
) -> Result<Address, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(&account_ref, password)?;
|
2017-02-15 16:56:15 +01:00
|
|
|
for account in accounts {
|
|
|
|
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
|
|
|
return Ok(ethkey::public_to_address(extended.public().public()));
|
|
|
|
}
|
|
|
|
Err(Error::InvalidPassword)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn sign_derived(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
derivation: Derivation,
|
|
|
|
message: &Message,
|
2017-02-15 16:56:15 +01:00
|
|
|
) -> Result<Signature, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(&account_ref, password)?;
|
2017-02-15 16:56:15 +01:00
|
|
|
for account in accounts {
|
|
|
|
let extended = self.generate(account.crypto.secret(password)?, derivation)?;
|
|
|
|
let secret = extended.secret().as_raw();
|
|
|
|
return Ok(ethkey::sign(&secret, message)?);
|
|
|
|
}
|
|
|
|
Err(Error::InvalidPassword)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn account_ref(&self, address: &Address) -> Result<StoreAccountRef, Error> {
|
2017-12-22 04:33:49 +01:00
|
|
|
let read_from_cache = |address: &Address| {
|
|
|
|
use std::collections::Bound;
|
|
|
|
let cache = self.cache.read();
|
|
|
|
let mut r = cache.range((Bound::Included(*address), Bound::Included(*address)));
|
|
|
|
r.next().map(|(k, _)| k.clone())
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-12-22 04:33:49 +01:00
|
|
|
match read_from_cache(address) {
|
|
|
|
Some(account) => Ok(account),
|
|
|
|
None => {
|
|
|
|
self.reload_if_changed()?;
|
|
|
|
read_from_cache(address).ok_or(Error::InvalidAccount)
|
|
|
|
}
|
2017-02-05 16:17:56 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn accounts(&self) -> Result<Vec<StoreAccountRef>, Error> {
|
2017-02-16 20:10:29 +01:00
|
|
|
self.reload_if_changed()?;
|
2016-11-30 15:08:38 +01:00
|
|
|
Ok(self.cache.read().keys().cloned().collect())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn remove_account(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<(), Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(account_ref, password)?;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
for account in accounts {
|
2017-02-05 16:17:56 +01:00
|
|
|
return self.remove_safe_account(account_ref, &account);
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
Err(Error::InvalidPassword)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn change_password(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
old_password: &Password,
|
|
|
|
new_password: &Password,
|
|
|
|
) -> Result<(), Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(account_ref, old_password)?;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
if accounts.is_empty() {
|
|
|
|
return Err(Error::InvalidPassword);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
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)?;
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
Ok(())
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn export_account(
|
|
|
|
&self,
|
|
|
|
account_ref: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
) -> Result<OpaqueKeyFile, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
self.get_matching(account_ref, password)?
|
|
|
|
.into_iter()
|
|
|
|
.nth(0)
|
|
|
|
.map(Into::into)
|
|
|
|
.ok_or(Error::InvalidPassword)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn sign(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
message: &Message,
|
|
|
|
) -> Result<Signature, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(account, password)?;
|
2017-08-09 11:09:40 +02:00
|
|
|
match accounts.first() {
|
|
|
|
Some(ref account) => account.sign(password, message),
|
|
|
|
None => Err(Error::InvalidPassword),
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn decrypt(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
shared_mac: &[u8],
|
|
|
|
message: &[u8],
|
|
|
|
) -> Result<Vec<u8>, Error> {
|
2017-03-23 13:23:03 +01:00
|
|
|
let accounts = self.get_matching(account, password)?;
|
2017-08-09 11:09:40 +02:00
|
|
|
match accounts.first() {
|
|
|
|
Some(ref account) => account.decrypt(password, shared_mac, message),
|
|
|
|
None => Err(Error::InvalidPassword),
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn agree(
|
|
|
|
&self,
|
|
|
|
account: &StoreAccountRef,
|
|
|
|
password: &Password,
|
|
|
|
other: &Public,
|
|
|
|
) -> Result<Secret, Error> {
|
2017-08-09 11:09:40 +02:00
|
|
|
let accounts = self.get_matching(account, password)?;
|
|
|
|
match accounts.first() {
|
|
|
|
Some(ref account) => account.agree(password, other),
|
|
|
|
None => Err(Error::InvalidPassword),
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn create_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let is_vault_created = {
|
|
|
|
// lock border
|
|
|
|
let mut vaults = self.vaults.lock();
|
|
|
|
if !vaults.contains_key(&name.to_owned()) {
|
|
|
|
let vault_provider = self
|
|
|
|
.dir
|
|
|
|
.as_vault_provider()
|
|
|
|
.ok_or(Error::VaultsAreNotSupported)?;
|
|
|
|
let vault =
|
|
|
|
vault_provider.create(name, VaultKey::new(password, self.iterations))?;
|
|
|
|
vaults.insert(name.to_owned(), vault);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
if is_vault_created {
|
|
|
|
self.reload_accounts()?;
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn open_vault(&self, name: &str, password: &Password) -> Result<(), Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let is_vault_opened = {
|
|
|
|
// lock border
|
|
|
|
let mut vaults = self.vaults.lock();
|
|
|
|
if !vaults.contains_key(&name.to_owned()) {
|
|
|
|
let vault_provider = self
|
|
|
|
.dir
|
|
|
|
.as_vault_provider()
|
|
|
|
.ok_or(Error::VaultsAreNotSupported)?;
|
|
|
|
let vault = vault_provider.open(name, VaultKey::new(password, self.iterations))?;
|
|
|
|
vaults.insert(name.to_owned(), vault);
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
if is_vault_opened {
|
|
|
|
self.reload_accounts()?;
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
fn close_vault(&self, name: &str) -> Result<(), Error> {
|
|
|
|
let is_vault_removed = self.vaults.lock().remove(&name.to_owned()).is_some();
|
|
|
|
if is_vault_removed {
|
|
|
|
self.reload_accounts()?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
let vault_provider = self
|
|
|
|
.dir
|
|
|
|
.as_vault_provider()
|
|
|
|
.ok_or(Error::VaultsAreNotSupported)?;
|
|
|
|
vault_provider.list_vaults()
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
Ok(self.vaults.lock().keys().cloned().collect())
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
fn change_vault_password(&self, name: &str, new_password: &Password) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
let old_key = self
|
|
|
|
.vaults
|
|
|
|
.lock()
|
|
|
|
.get(name)
|
|
|
|
.map(|v| v.key())
|
|
|
|
.ok_or(Error::VaultNotFound)?;
|
2017-01-30 11:44:09 +01:00
|
|
|
let vault_provider = self
|
|
|
|
.dir
|
|
|
|
.as_vault_provider()
|
|
|
|
.ok_or(Error::VaultsAreNotSupported)?;
|
2017-02-05 16:17:56 +01:00
|
|
|
let vault = vault_provider.open(name, old_key)?;
|
|
|
|
match vault.set_key(VaultKey::new(new_password, self.iterations)) {
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(_) => self
|
|
|
|
.close_vault(name)
|
|
|
|
.and_then(|_| self.open_vault(name, new_password)),
|
|
|
|
Err(SetKeyError::Fatal(err)) => {
|
|
|
|
let _ = self.close_vault(name);
|
|
|
|
Err(err)
|
2017-02-05 16:17:56 +01:00
|
|
|
}
|
2017-01-30 11:44:09 +01:00
|
|
|
Err(SetKeyError::NonFatalNew(err)) => {
|
|
|
|
let _ = self
|
|
|
|
.close_vault(name)
|
|
|
|
.and_then(|_| self.open_vault(name, new_password));
|
|
|
|
Err(err)
|
|
|
|
}
|
|
|
|
Err(SetKeyError::NonFatalOld(err)) => Err(err),
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
fn change_account_vault(
|
|
|
|
&self,
|
|
|
|
vault: SecretVaultRef,
|
|
|
|
account_ref: StoreAccountRef,
|
|
|
|
) -> Result<StoreAccountRef, Error> {
|
|
|
|
if account_ref.vault == vault {
|
|
|
|
return Ok(account_ref);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
let account = self
|
|
|
|
.get_accounts(&account_ref)?
|
|
|
|
.into_iter()
|
|
|
|
.nth(0)
|
|
|
|
.ok_or(Error::InvalidAccount)?;
|
2017-02-05 16:17:56 +01:00
|
|
|
let new_account_ref = self.import(vault, account.clone())?;
|
|
|
|
self.remove_safe_account(&account_ref, &account)?;
|
|
|
|
self.reload_accounts()?;
|
|
|
|
Ok(new_account_ref)
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-08 13:53:39 +01:00
|
|
|
fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
2017-02-09 16:47:22 +01:00
|
|
|
// vault meta contains password hint
|
|
|
|
// => allow reading meta even if vault is not yet opened
|
2017-02-08 13:53:39 +01:00
|
|
|
self.vaults
|
|
|
|
.lock()
|
|
|
|
.get(name)
|
2017-02-09 16:47:22 +01:00
|
|
|
.and_then(|v| Some(v.meta()))
|
2017-02-08 13:53:39 +01:00
|
|
|
.ok_or(Error::VaultNotFound)
|
2017-02-09 16:47:22 +01:00
|
|
|
.or_else(|_| {
|
|
|
|
let vault_provider = self
|
|
|
|
.dir
|
|
|
|
.as_vault_provider()
|
|
|
|
.ok_or(Error::VaultsAreNotSupported)?;
|
|
|
|
vault_provider.vault_meta(name)
|
|
|
|
})
|
2017-02-08 13:53:39 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-08 13:53:39 +01:00
|
|
|
fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
|
|
|
self.vaults
|
|
|
|
.lock()
|
|
|
|
.get(name)
|
|
|
|
.ok_or(Error::VaultNotFound)
|
|
|
|
.and_then(|v| v.set_meta(meta))
|
|
|
|
}
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-04-11 10:24:56 +02:00
|
|
|
extern crate tempdir;
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-04-11 10:24:56 +02:00
|
|
|
use self::tempdir::TempDir;
|
2016-12-09 10:45:34 +01:00
|
|
|
use super::{EthMultiStore, EthStore};
|
2017-12-24 09:34:43 +01:00
|
|
|
use accounts_dir::{KeyDirectory, MemoryDirectory, RootDiskDirectory};
|
2018-01-10 13:35:18 +01:00
|
|
|
use ethereum_types::H256;
|
2016-12-09 10:45:34 +01:00
|
|
|
use ethkey::{Generator, KeyPair, Random};
|
2017-02-15 16:56:15 +01:00
|
|
|
use secret_store::{
|
|
|
|
Derivation, SecretStore, SecretVaultRef, SimpleSecretStore, StoreAccountRef,
|
2016-12-09 10:45:34 +01:00
|
|
|
};
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
fn keypair() -> KeyPair {
|
|
|
|
Random.generate().unwrap()
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
fn store() -> EthStore {
|
|
|
|
EthStore::open(Box::new(MemoryDirectory::default()))
|
|
|
|
.expect("MemoryDirectory always load successfuly; qed")
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
fn multi_store() -> EthMultiStore {
|
|
|
|
EthMultiStore::open(Box::new(MemoryDirectory::default()))
|
|
|
|
.expect("MemoryDirectory always load successfuly; qed")
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
struct RootDiskDirectoryGuard {
|
2020-07-29 10:36:15 +02:00
|
|
|
pub key_dir: Option<Box<dyn KeyDirectory>>,
|
2017-04-11 10:24:56 +02:00
|
|
|
_path: TempDir,
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
impl RootDiskDirectoryGuard {
|
2017-02-05 16:17:56 +01:00
|
|
|
pub fn new() -> Self {
|
2017-04-11 10:24:56 +02:00
|
|
|
let temp_path = TempDir::new("").unwrap();
|
|
|
|
let disk_dir = Box::new(RootDiskDirectory::create(temp_path.path()).unwrap());
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
RootDiskDirectoryGuard {
|
2017-02-05 16:17:56 +01:00
|
|
|
key_dir: Some(disk_dir),
|
|
|
|
_path: temp_path,
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
}
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
#[test]
|
|
|
|
fn should_insert_account_successfully() {
|
|
|
|
// given
|
|
|
|
let store = store();
|
|
|
|
let keypair = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "test".into();
|
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd)
|
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// then
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(address, StoreAccountRef::root(keypair.address()));
|
2018-06-22 15:09:15 +02:00
|
|
|
assert!(store.get(&address).is_ok(), "Should contain account.");
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(
|
2016-12-09 10:45:34 +01:00
|
|
|
store.accounts().unwrap().len(),
|
2020-08-05 06:08:03 +02:00
|
|
|
1,
|
2016-12-09 10:45:34 +01:00
|
|
|
"Should have one account."
|
2020-08-05 06:08:03 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2017-01-30 11:44:09 +01:00
|
|
|
fn should_update_meta_and_name() {
|
2020-08-05 06:08:03 +02:00
|
|
|
// given
|
2016-12-09 10:45:34 +01:00
|
|
|
let store = store();
|
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "test".into();
|
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd)
|
|
|
|
.unwrap();
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(&store.meta(&address).unwrap(), "{}");
|
|
|
|
assert_eq!(&store.name(&address).unwrap(), "");
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
2016-12-09 10:45:34 +01:00
|
|
|
store.set_meta(&address, "meta".into()).unwrap();
|
|
|
|
store.set_name(&address, "name".into()).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// then
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(&store.meta(&address).unwrap(), "meta");
|
|
|
|
assert_eq!(&store.name(&address).unwrap(), "name");
|
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-11-30 16:41:37 +01:00
|
|
|
#[test]
|
2016-12-09 10:45:34 +01:00
|
|
|
fn should_remove_account() {
|
|
|
|
// given
|
|
|
|
let store = store();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "test".into();
|
|
|
|
let keypair = keypair();
|
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd)
|
2016-12-09 10:45:34 +01:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
// when
|
|
|
|
store.remove_account(&address, &passwd).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(store.accounts().unwrap().len(), 0, "Should remove account.");
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
#[test]
|
|
|
|
fn should_return_true_if_password_is_correct() {
|
|
|
|
// given
|
|
|
|
let store = store();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd = "test".into();
|
2016-12-09 10:45:34 +01:00
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd)
|
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
let res1 = store.test_password(&address, &"x".into()).unwrap();
|
|
|
|
let res2 = store.test_password(&address, &passwd).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
assert!(!res1, "First password should be invalid.");
|
|
|
|
assert!(res2, "Second password should be correct.");
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
#[test]
|
|
|
|
fn multistore_should_be_able_to_have_the_same_account_twice() {
|
|
|
|
// given
|
|
|
|
let store = multi_store();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd1 = "test".into();
|
|
|
|
let passwd2 = "xyz".into();
|
2016-12-09 10:45:34 +01:00
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd1)
|
|
|
|
.unwrap();
|
|
|
|
let address2 = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd2)
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(address, address2);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
|
|
|
assert!(
|
2018-06-22 15:09:15 +02:00
|
|
|
store.remove_account(&address, &passwd1).is_ok(),
|
2016-12-09 10:45:34 +01:00
|
|
|
"First password should work."
|
|
|
|
);
|
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
assert!(
|
|
|
|
store.remove_account(&address, &passwd2).is_ok(),
|
|
|
|
"Second password should work too."
|
2020-08-05 06:08:03 +02:00
|
|
|
);
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
#[test]
|
|
|
|
fn should_copy_account() {
|
|
|
|
// given
|
|
|
|
let store = store();
|
2018-06-22 15:09:15 +02:00
|
|
|
let passwd1 = "test".into();
|
|
|
|
let passwd2 = "xzy".into();
|
2016-12-09 10:45:34 +01:00
|
|
|
let multi_store = multi_store();
|
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let address = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair.secret().clone(), &passwd1)
|
|
|
|
.unwrap();
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(multi_store.accounts().unwrap().len(), 0);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
|
|
|
store
|
2018-06-22 15:09:15 +02:00
|
|
|
.copy_account(
|
2016-12-09 10:45:34 +01:00
|
|
|
&multi_store,
|
2018-06-22 15:09:15 +02:00
|
|
|
SecretVaultRef::Root,
|
|
|
|
&address,
|
2020-08-05 06:08:03 +02:00
|
|
|
&passwd1,
|
2018-06-22 15:09:15 +02:00
|
|
|
&passwd2,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
// then
|
|
|
|
assert!(
|
|
|
|
store.test_password(&address, &passwd1).unwrap(),
|
|
|
|
"First password should work for store."
|
2020-08-05 06:08:03 +02:00
|
|
|
);
|
|
|
|
assert!(
|
2016-12-09 10:45:34 +01:00
|
|
|
multi_store
|
2018-06-22 15:09:15 +02:00
|
|
|
.sign(&address, &passwd2, &Default::default())
|
|
|
|
.is_ok(),
|
|
|
|
"Second password should work for second store."
|
|
|
|
);
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(multi_store.accounts().unwrap().len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2017-01-30 11:44:09 +01:00
|
|
|
fn should_create_and_open_vaults() {
|
2016-12-09 10:45:34 +01:00
|
|
|
// given
|
2018-06-22 15:09:15 +02:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
|
|
|
let name2 = "vault2";
|
|
|
|
let password2 = "password2".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair1 = keypair();
|
|
|
|
let keypair2 = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let keypair3 = keypair();
|
|
|
|
let password3 = "password3".into();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name1, &password1).unwrap();
|
|
|
|
store.create_vault(name2, &password2).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2016-12-09 10:45:34 +01:00
|
|
|
// then [can create vaults] ^^^
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2018-06-22 15:09:15 +02:00
|
|
|
// and when
|
|
|
|
store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name1.to_owned()),
|
|
|
|
keypair1.secret().clone(),
|
|
|
|
&password1,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2016-12-09 10:45:34 +01:00
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.insert_account(
|
2018-06-22 15:09:15 +02:00
|
|
|
SecretVaultRef::Vault(name2.to_owned()),
|
|
|
|
keypair2.secret().clone(),
|
|
|
|
&password2,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair3.secret().clone(), &password3)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault("vault3".to_owned()),
|
|
|
|
keypair1.secret().clone(),
|
|
|
|
&password3,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap_err();
|
2017-01-30 11:44:09 +01:00
|
|
|
let accounts = store.accounts().unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then [can create accounts in vaults]
|
|
|
|
assert_eq!(accounts.len(), 3);
|
|
|
|
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
|
|
|
|
assert!(accounts
|
|
|
|
.iter()
|
|
|
|
.any(|a| a.vault == SecretVaultRef::Vault(name1.to_owned())));
|
|
|
|
assert!(accounts
|
|
|
|
.iter()
|
|
|
|
.any(|a| a.vault == SecretVaultRef::Vault(name2.to_owned())));
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// and when
|
|
|
|
store.close_vault(name1).unwrap();
|
|
|
|
store.close_vault(name2).unwrap();
|
|
|
|
store.close_vault("vault3").unwrap();
|
|
|
|
let accounts = store.accounts().unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then [can close vaults + accounts from vaults disappear]
|
|
|
|
assert_eq!(accounts.len(), 1);
|
|
|
|
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// and when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.open_vault(name1, &password2).unwrap_err();
|
|
|
|
store.open_vault(name2, &password1).unwrap_err();
|
|
|
|
store.open_vault(name1, &password1).unwrap();
|
|
|
|
store.open_vault(name2, &password2).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
let accounts = store.accounts().unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then [can check vaults on open + can reopen vaults + accounts from vaults appear]
|
|
|
|
assert_eq!(accounts.len(), 3);
|
|
|
|
assert!(accounts.iter().any(|a| a.vault == SecretVaultRef::Root));
|
|
|
|
assert!(accounts
|
|
|
|
.iter()
|
|
|
|
.any(|a| a.vault == SecretVaultRef::Vault(name1.to_owned())));
|
|
|
|
assert!(accounts
|
|
|
|
.iter()
|
|
|
|
.any(|a| a.vault == SecretVaultRef::Vault(name2.to_owned())));
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
#[test]
|
|
|
|
fn should_move_vault_acounts() {
|
|
|
|
// given
|
2017-02-05 16:17:56 +01:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
|
|
|
let name2 = "vault2";
|
|
|
|
let password2 = "password2".into();
|
|
|
|
let password3 = "password3".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair1 = keypair();
|
|
|
|
let keypair2 = keypair();
|
|
|
|
let keypair3 = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
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,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
|
|
|
let account2 = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name1.to_owned()),
|
|
|
|
keypair2.secret().clone(),
|
|
|
|
&password1,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
|
|
|
let account3 = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair3.secret().clone(), &password3)
|
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then
|
2017-02-05 16:17:56 +01:00
|
|
|
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();
|
2017-01-30 11:44:09 +01:00
|
|
|
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())));
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// 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!(
|
2017-01-30 11:44:09 +01:00
|
|
|
store
|
2017-02-05 16:17:56 +01:00
|
|
|
.meta(&StoreAccountRef::vault("vault2", account3.address))
|
|
|
|
.unwrap(),
|
|
|
|
r#"{"vault":"vault2"}"#
|
|
|
|
);
|
2017-01-30 11:44:09 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
#[test]
|
|
|
|
fn should_not_remove_account_when_moving_to_self() {
|
|
|
|
// given
|
2017-02-05 16:17:56 +01:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let password1 = "password1".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair1 = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
let account1 = store
|
|
|
|
.insert_account(SecretVaultRef::Root, keypair1.secret().clone(), &password1)
|
|
|
|
.unwrap();
|
|
|
|
store
|
|
|
|
.change_account_vault(SecretVaultRef::Root, account1)
|
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// then
|
2017-01-30 11:44:09 +01:00
|
|
|
let accounts = store.accounts().unwrap();
|
|
|
|
assert_eq!(accounts.len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2017-01-30 11:44:09 +01:00
|
|
|
fn should_remove_account_from_vault() {
|
2020-08-05 06:08:03 +02:00
|
|
|
// given
|
2017-02-05 16:17:56 +01:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair1 = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name1, &password1).unwrap();
|
|
|
|
let account1 = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name1.to_owned()),
|
|
|
|
keypair1.secret().clone(),
|
|
|
|
&password1,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2016-12-09 10:45:34 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
|
|
|
// then
|
2018-06-22 15:09:15 +02:00
|
|
|
store.remove_account(&account1, &password1).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 0);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
#[test]
|
|
|
|
fn should_not_remove_account_from_vault_when_password_is_incorrect() {
|
|
|
|
// given
|
2017-02-05 16:17:56 +01:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
|
|
|
let password2 = "password2".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair1 = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name1, &password1).unwrap();
|
|
|
|
let account1 = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name1.to_owned()),
|
|
|
|
keypair1.secret().clone(),
|
|
|
|
&password1,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then
|
2018-06-22 15:09:15 +02:00
|
|
|
store.remove_account(&account1, &password2).unwrap_err();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
#[test]
|
|
|
|
fn should_change_vault_password() {
|
|
|
|
// given
|
2017-02-05 16:17:56 +01:00
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
2017-01-30 11:44:09 +01:00
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name = "vault";
|
|
|
|
let password = "password".into();
|
2017-01-30 11:44:09 +01:00
|
|
|
let keypair = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name, &password).unwrap();
|
|
|
|
store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name.to_owned()),
|
|
|
|
keypair.secret().clone(),
|
|
|
|
&password,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2018-06-22 15:09:15 +02:00
|
|
|
let new_password = "new_password".into();
|
|
|
|
store.change_vault_password(name, &new_password).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// and when
|
|
|
|
store.close_vault(name).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-01-30 11:44:09 +01:00
|
|
|
// then
|
2018-06-22 15:09:15 +02:00
|
|
|
store.open_vault(name, &new_password).unwrap();
|
2017-01-30 11:44:09 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
#[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();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name = "vault";
|
|
|
|
let password = "password".into();
|
|
|
|
let secret_password = "sec_password".into();
|
2017-02-05 16:17:56 +01:00
|
|
|
let keypair = keypair();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name, &password).unwrap();
|
|
|
|
let account_ref = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Vault(name.to_owned()),
|
|
|
|
keypair.secret().clone(),
|
|
|
|
&secret_password,
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
2018-06-22 15:09:15 +02:00
|
|
|
let new_secret_password = "new_sec_password".into();
|
|
|
|
store
|
|
|
|
.change_password(&account_ref, &secret_password, &new_secret_password)
|
|
|
|
.unwrap();
|
2017-02-05 16:17:56 +01:00
|
|
|
assert_eq!(store.accounts().unwrap().len(), 1);
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
#[test]
|
|
|
|
fn should_list_opened_vaults() {
|
|
|
|
// given
|
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
|
|
|
let name2 = "vault2";
|
|
|
|
let password2 = "password2".into();
|
|
|
|
let name3 = "vault3";
|
|
|
|
let password3 = "password3".into();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name1, &password1).unwrap();
|
|
|
|
store.create_vault(name2, &password2).unwrap();
|
|
|
|
store.create_vault(name3, &password3).unwrap();
|
2017-02-05 16:17:56 +01:00
|
|
|
store.close_vault(name2).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-05 16:17:56 +01:00
|
|
|
// 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));
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
#[test]
|
|
|
|
fn should_manage_vaults_meta() {
|
|
|
|
// given
|
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name1 = "vault1";
|
|
|
|
let password1 = "password1".into();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name1, &password1).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(store.get_vault_meta(name1).unwrap(), "{}".to_owned());
|
|
|
|
assert!(store.set_vault_meta(name1, "Hello, world!!!").is_ok());
|
|
|
|
assert_eq!(
|
|
|
|
store.get_vault_meta(name1).unwrap(),
|
|
|
|
"Hello, world!!!".to_owned()
|
|
|
|
);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// and when
|
|
|
|
store.close_vault(name1).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
store.open_vault(name1, &password1).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
store.get_vault_meta(name1).unwrap(),
|
|
|
|
"Hello, world!!!".to_owned()
|
|
|
|
);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// and when
|
|
|
|
store.close_vault(name1).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-09 16:47:22 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(
|
|
|
|
store.get_vault_meta(name1).unwrap(),
|
|
|
|
"Hello, world!!!".to_owned()
|
|
|
|
);
|
|
|
|
assert!(store.get_vault_meta("vault2").is_err());
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
#[test]
|
|
|
|
fn should_store_derived_keys() {
|
|
|
|
// given we have one account in the store
|
|
|
|
let store = store();
|
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let address = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Root,
|
|
|
|
keypair.secret().clone(),
|
|
|
|
&"test".into(),
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
// when we deriving from that account
|
|
|
|
let derived = store
|
|
|
|
.insert_derived(
|
|
|
|
SecretVaultRef::Root,
|
|
|
|
&address,
|
2018-06-22 15:09:15 +02:00
|
|
|
&"test".into(),
|
2017-02-15 16:56:15 +01:00
|
|
|
Derivation::HardHash(H256::from(0)),
|
|
|
|
)
|
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
// there should be 2 accounts in the store
|
|
|
|
let accounts = store.accounts().unwrap();
|
|
|
|
assert_eq!(accounts.len(), 2);
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
// and we can sign with the derived contract
|
2018-06-22 15:09:15 +02:00
|
|
|
assert!(
|
|
|
|
store
|
|
|
|
.sign(&derived, &"test".into(), &Default::default())
|
|
|
|
.is_ok(),
|
|
|
|
"Second password should work for second store."
|
|
|
|
);
|
2017-02-15 16:56:15 +01:00
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-23 19:01:12 +01:00
|
|
|
#[test]
|
|
|
|
fn should_save_meta_when_setting_before_password() {
|
|
|
|
// given
|
|
|
|
let mut dir = RootDiskDirectoryGuard::new();
|
|
|
|
let store = EthStore::open(dir.key_dir.take().unwrap()).unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
let name = "vault";
|
|
|
|
let password = "password1".into();
|
|
|
|
let new_password = "password2".into();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-23 19:01:12 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
store.create_vault(name, &password).unwrap();
|
2017-02-23 19:01:12 +01:00
|
|
|
store.set_vault_meta(name, "OldMeta").unwrap();
|
2018-06-22 15:09:15 +02:00
|
|
|
store.change_vault_password(name, &new_password).unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-02-23 19:01:12 +01:00
|
|
|
// then
|
|
|
|
assert_eq!(store.get_vault_meta(name).unwrap(), "OldMeta".to_owned());
|
|
|
|
}
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
#[test]
|
|
|
|
fn should_export_account() {
|
|
|
|
// given
|
|
|
|
let store = store();
|
|
|
|
let keypair = keypair();
|
2018-06-22 15:09:15 +02:00
|
|
|
let address = store
|
|
|
|
.insert_account(
|
|
|
|
SecretVaultRef::Root,
|
|
|
|
keypair.secret().clone(),
|
|
|
|
&"test".into(),
|
2020-08-05 06:08:03 +02:00
|
|
|
)
|
2018-06-22 15:09:15 +02:00
|
|
|
.unwrap();
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
// when
|
2018-06-22 15:09:15 +02:00
|
|
|
let exported = store.export_account(&address, &"test".into());
|
2020-08-05 06:08:03 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
// then
|
|
|
|
assert!(
|
|
|
|
exported.is_ok(),
|
|
|
|
"Should export single account: {:?}",
|
|
|
|
exported
|
|
|
|
);
|
|
|
|
}
|
2016-11-30 15:08:38 +01:00
|
|
|
}
|