2017-01-25 18:51:41 +01:00
|
|
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
2016-06-20 00:10:34 +02:00
|
|
|
// 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/>.
|
|
|
|
|
|
|
|
//! Account management.
|
|
|
|
|
2016-11-22 11:56:27 +01:00
|
|
|
mod stores;
|
|
|
|
|
2016-12-10 12:34:20 +01:00
|
|
|
use self::stores::{AddressBook, DappsSettingsStore, NewDappsPolicy};
|
2016-11-22 11:56:27 +01:00
|
|
|
|
|
|
|
use std::fmt;
|
2017-02-03 13:56:48 +01:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2016-08-15 15:09:00 +02:00
|
|
|
use std::time::{Instant, Duration};
|
2017-09-02 20:09:13 +02:00
|
|
|
use parking_lot::RwLock;
|
2017-03-23 13:23:03 +01:00
|
|
|
use ethstore::{
|
|
|
|
SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMultiStore,
|
2017-06-06 18:06:40 +02:00
|
|
|
random_string, SecretVaultRef, StoreAccountRef, OpaqueSecret,
|
2017-03-23 13:23:03 +01:00
|
|
|
};
|
2017-12-24 09:34:43 +01:00
|
|
|
use ethstore::accounts_dir::MemoryDirectory;
|
2016-10-15 14:44:08 +02:00
|
|
|
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
|
2016-08-15 15:09:00 +02:00
|
|
|
use ethjson::misc::AccountMeta;
|
2017-09-14 19:28:43 +02:00
|
|
|
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath, TransactionInfo};
|
|
|
|
use super::transaction::{Action, Transaction};
|
2016-08-15 15:09:00 +02:00
|
|
|
pub use ethstore::ethkey::Signature;
|
2017-03-23 13:23:03 +01:00
|
|
|
pub use ethstore::{Derivation, IndexDerivation, KeyFile};
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
/// Type of unlock.
|
2017-06-06 18:06:40 +02:00
|
|
|
#[derive(Clone, PartialEq)]
|
2016-06-20 00:10:34 +02:00
|
|
|
enum Unlock {
|
|
|
|
/// If account is unlocked temporarily, it should be locked after first usage.
|
2017-06-06 18:06:40 +02:00
|
|
|
OneTime,
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Account unlocked permantently can always sign message.
|
|
|
|
/// Use with caution.
|
|
|
|
Perm,
|
2016-08-05 23:33:14 +02:00
|
|
|
/// Account unlocked with a timeout
|
2016-10-31 16:55:30 +01:00
|
|
|
Timed(Instant),
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Data associated with account.
|
|
|
|
#[derive(Clone)]
|
|
|
|
struct AccountData {
|
|
|
|
unlock: Unlock,
|
|
|
|
password: String,
|
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
/// Signing error
|
2016-06-20 00:10:34 +02:00
|
|
|
#[derive(Debug)]
|
2017-01-30 21:08:36 +01:00
|
|
|
pub enum SignError {
|
|
|
|
/// Account is not unlocked
|
2016-06-20 00:10:34 +02:00
|
|
|
NotUnlocked,
|
2017-02-10 01:07:06 +01:00
|
|
|
/// Account does not exist.
|
|
|
|
NotFound,
|
|
|
|
/// Low-level hardware device error.
|
|
|
|
Hardware(HardwareError),
|
2017-01-30 21:08:36 +01:00
|
|
|
/// Low-level error from store
|
2017-07-12 08:52:18 +02:00
|
|
|
SStore(SSError),
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
impl fmt::Display for SignError {
|
2016-06-20 00:10:34 +02:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
|
|
|
match *self {
|
2017-01-30 21:08:36 +01:00
|
|
|
SignError::NotUnlocked => write!(f, "Account is locked"),
|
2017-02-10 01:07:06 +01:00
|
|
|
SignError::NotFound => write!(f, "Account does not exist"),
|
|
|
|
SignError::Hardware(ref e) => write!(f, "{}", e),
|
2017-01-30 21:08:36 +01:00
|
|
|
SignError::SStore(ref e) => write!(f, "{}", e),
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-10 01:07:06 +01:00
|
|
|
impl From<HardwareError> for SignError {
|
|
|
|
fn from(e: HardwareError) -> Self {
|
|
|
|
SignError::Hardware(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
impl From<SSError> for SignError {
|
2016-06-20 00:10:34 +02:00
|
|
|
fn from(e: SSError) -> Self {
|
2017-01-30 21:08:36 +01:00
|
|
|
SignError::SStore(e)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
/// `AccountProvider` errors.
|
|
|
|
pub type Error = SSError;
|
|
|
|
|
2016-11-22 11:56:27 +01:00
|
|
|
/// Dapp identifier
|
2017-01-30 10:59:46 +01:00
|
|
|
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
|
|
|
pub struct DappId(String);
|
|
|
|
|
|
|
|
impl From<DappId> for String {
|
|
|
|
fn from(id: DappId) -> String { id.0 }
|
|
|
|
}
|
|
|
|
impl From<String> for DappId {
|
|
|
|
fn from(id: String) -> DappId { DappId(id) }
|
|
|
|
}
|
|
|
|
impl<'a> From<&'a str> for DappId {
|
|
|
|
fn from(id: &'a str) -> DappId { DappId(id.to_owned()) }
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
fn transient_sstore() -> EthMultiStore {
|
2016-12-09 10:45:34 +01:00
|
|
|
EthMultiStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
type AccountToken = String;
|
2016-08-11 18:31:28 +02:00
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Account management.
|
|
|
|
/// Responsible for unlocking accounts.
|
|
|
|
pub struct AccountProvider {
|
2017-06-06 18:06:40 +02:00
|
|
|
/// For performance reasons some methods can re-use unlocked secrets.
|
|
|
|
unlocked_secrets: RwLock<HashMap<StoreAccountRef, OpaqueSecret>>,
|
|
|
|
/// Unlocked account data.
|
2017-01-30 11:44:09 +01:00
|
|
|
unlocked: RwLock<HashMap<StoreAccountRef, AccountData>>,
|
2017-06-06 18:06:40 +02:00
|
|
|
/// Address book.
|
2016-11-22 11:56:27 +01:00
|
|
|
address_book: RwLock<AddressBook>,
|
2017-06-06 18:06:40 +02:00
|
|
|
/// Dapps settings.
|
2016-11-22 11:56:27 +01:00
|
|
|
dapps_settings: RwLock<DappsSettingsStore>,
|
2016-11-30 13:47:14 +01:00
|
|
|
/// Accounts on disk
|
2016-06-20 00:10:34 +02:00
|
|
|
sstore: Box<SecretStore>,
|
2016-11-30 13:47:14 +01:00
|
|
|
/// Accounts unlocked with rolling tokens
|
2016-11-30 15:08:38 +01:00
|
|
|
transient_sstore: EthMultiStore,
|
2017-02-10 01:07:06 +01:00
|
|
|
/// Accounts in hardware wallets.
|
|
|
|
hardware_store: Option<HardwareWalletManager>,
|
2017-06-14 12:06:15 +02:00
|
|
|
/// When unlocking account permanently we additionally keep a raw secret in memory
|
|
|
|
/// to increase the performance of transaction signing.
|
|
|
|
unlock_keep_secret: bool,
|
2017-06-07 11:34:53 +02:00
|
|
|
/// Disallowed accounts.
|
|
|
|
blacklisted_accounts: Vec<Address>,
|
2017-02-10 01:07:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Account management settings.
|
|
|
|
pub struct AccountProviderSettings {
|
|
|
|
/// Enable hardware wallet support.
|
|
|
|
pub enable_hardware_wallets: bool,
|
|
|
|
/// Use the classic chain key on the hardware wallet.
|
|
|
|
pub hardware_wallet_classic_key: bool,
|
2017-06-14 12:06:15 +02:00
|
|
|
/// Store raw account secret when unlocking the account permanently.
|
|
|
|
pub unlock_keep_secret: bool,
|
2017-06-07 11:34:53 +02:00
|
|
|
/// Disallowed accounts.
|
|
|
|
pub blacklisted_accounts: Vec<Address>,
|
2017-02-10 01:07:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for AccountProviderSettings {
|
|
|
|
fn default() -> Self {
|
|
|
|
AccountProviderSettings {
|
|
|
|
enable_hardware_wallets: false,
|
|
|
|
hardware_wallet_classic_key: false,
|
2017-06-14 12:06:15 +02:00
|
|
|
unlock_keep_secret: false,
|
2017-06-07 11:34:53 +02:00
|
|
|
blacklisted_accounts: vec![],
|
2017-02-10 01:07:06 +01:00
|
|
|
}
|
|
|
|
}
|
2016-07-24 17:38:21 +02:00
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
impl AccountProvider {
|
|
|
|
/// Creates new account provider.
|
2017-02-10 01:07:06 +01:00
|
|
|
pub fn new(sstore: Box<SecretStore>, settings: AccountProviderSettings) -> Self {
|
|
|
|
let mut hardware_store = None;
|
|
|
|
if settings.enable_hardware_wallets {
|
|
|
|
match HardwareWalletManager::new() {
|
|
|
|
Ok(manager) => {
|
|
|
|
manager.set_key_path(if settings.hardware_wallet_classic_key { KeyPath::EthereumClassic } else { KeyPath::Ethereum });
|
|
|
|
hardware_store = Some(manager)
|
|
|
|
},
|
2017-03-10 10:25:40 +01:00
|
|
|
Err(e) => debug!("Error initializing hardware wallets: {}", e),
|
2017-02-10 01:07:06 +01:00
|
|
|
}
|
|
|
|
}
|
2017-06-07 11:34:53 +02:00
|
|
|
|
2017-10-24 08:40:53 +02:00
|
|
|
if let Ok(accounts) = sstore.accounts() {
|
|
|
|
for account in accounts.into_iter().filter(|a| settings.blacklisted_accounts.contains(&a.address)) {
|
|
|
|
warn!("Local Account {} has a blacklisted (known to be weak) address and will be ignored",
|
|
|
|
account.address);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-07 11:34:53 +02:00
|
|
|
// Remove blacklisted accounts from address book.
|
|
|
|
let mut address_book = AddressBook::new(&sstore.local_path());
|
|
|
|
for addr in &settings.blacklisted_accounts {
|
|
|
|
address_book.remove(*addr);
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
AccountProvider {
|
2017-06-06 18:06:40 +02:00
|
|
|
unlocked_secrets: RwLock::new(HashMap::new()),
|
2016-12-09 09:31:58 +01:00
|
|
|
unlocked: RwLock::new(HashMap::new()),
|
2017-06-07 11:34:53 +02:00
|
|
|
address_book: RwLock::new(address_book),
|
2017-02-03 13:56:48 +01:00
|
|
|
dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
|
2016-06-20 00:10:34 +02:00
|
|
|
sstore: sstore,
|
2016-11-30 13:47:14 +01:00
|
|
|
transient_sstore: transient_sstore(),
|
2017-02-10 01:07:06 +01:00
|
|
|
hardware_store: hardware_store,
|
2017-06-14 12:06:15 +02:00
|
|
|
unlock_keep_secret: settings.unlock_keep_secret,
|
2017-06-07 11:34:53 +02:00
|
|
|
blacklisted_accounts: settings.blacklisted_accounts,
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates not disk backed provider.
|
|
|
|
pub fn transient_provider() -> Self {
|
|
|
|
AccountProvider {
|
2017-06-06 18:06:40 +02:00
|
|
|
unlocked_secrets: RwLock::new(HashMap::new()),
|
2016-12-09 09:31:58 +01:00
|
|
|
unlocked: RwLock::new(HashMap::new()),
|
2016-11-22 11:56:27 +01:00
|
|
|
address_book: RwLock::new(AddressBook::transient()),
|
|
|
|
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
|
2016-12-09 10:45:34 +01:00
|
|
|
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
|
2016-11-30 13:47:14 +01:00
|
|
|
transient_sstore: transient_sstore(),
|
2017-02-10 01:07:06 +01:00
|
|
|
hardware_store: None,
|
2017-06-14 12:06:15 +02:00
|
|
|
unlock_keep_secret: false,
|
2017-06-07 11:34:53 +02:00
|
|
|
blacklisted_accounts: vec![],
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates new random account.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn new_account(&self, password: &str) -> Result<Address, Error> {
|
2016-10-15 14:44:08 +02:00
|
|
|
self.new_account_and_public(password).map(|d| d.0)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates new random account and returns address and public key
|
|
|
|
pub fn new_account_and_public(&self, password: &str) -> Result<(Address, Public), Error> {
|
2016-10-20 23:41:15 +02:00
|
|
|
let acc = Random.generate().expect("secp context has generation capabilities; qed");
|
2016-10-15 14:44:08 +02:00
|
|
|
let public = acc.public().clone();
|
|
|
|
let secret = acc.secret().clone();
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
|
|
|
Ok((account.address, public))
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Inserts new account into underlying store.
|
|
|
|
/// Does not unlock account!
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn insert_account(&self, secret: Secret, password: &str) -> Result<Address, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = self.sstore.insert_account(SecretVaultRef::Root, secret, password)?;
|
2017-06-07 11:34:53 +02:00
|
|
|
if self.blacklisted_accounts.contains(&account.address) {
|
|
|
|
self.sstore.remove_account(&account, password)?;
|
|
|
|
return Err(SSError::InvalidAccount.into());
|
|
|
|
}
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(account.address)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
/// Generates new derived account based on the existing one
|
|
|
|
/// If password is not provided, account must be unlocked
|
|
|
|
/// New account will be created with the same password (if save: true)
|
|
|
|
pub fn derive_account(&self, address: &Address, password: Option<String>, derivation: Derivation, save: bool)
|
|
|
|
-> Result<Address, SignError>
|
|
|
|
{
|
|
|
|
let account = self.sstore.account_ref(&address)?;
|
|
|
|
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
|
|
|
Ok(
|
|
|
|
if save { self.sstore.insert_derived(SecretVaultRef::Root, &account, &password, derivation)?.address }
|
|
|
|
else { self.sstore.generate_derived(&account, &password, derivation)? }
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-08-10 17:57:40 +02:00
|
|
|
/// Import a new presale wallet.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn import_presale(&self, presale_json: &[u8], password: &str) -> Result<Address, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
let account = self.sstore.import_presale(SecretVaultRef::Root, presale_json, password)?;
|
|
|
|
Ok(Address::from(account.address).into())
|
2016-08-10 17:57:40 +02:00
|
|
|
}
|
|
|
|
|
2018-02-14 14:21:58 +01:00
|
|
|
/// Import a new wallet.
|
|
|
|
pub fn import_wallet(&self, json: &[u8], password: &str, gen_id: bool) -> Result<Address, Error> {
|
|
|
|
let account = self.sstore.import_wallet(SecretVaultRef::Root, json, password, gen_id)?;
|
2017-06-07 11:34:53 +02:00
|
|
|
if self.blacklisted_accounts.contains(&account.address) {
|
|
|
|
self.sstore.remove_account(&account, password)?;
|
|
|
|
return Err(SSError::InvalidAccount.into());
|
|
|
|
}
|
2017-01-30 11:44:09 +01:00
|
|
|
Ok(Address::from(account.address).into())
|
2016-08-10 17:57:40 +02:00
|
|
|
}
|
|
|
|
|
2017-01-04 12:50:50 +01:00
|
|
|
/// Checks whether an account with a given address is present.
|
|
|
|
pub fn has_account(&self, address: Address) -> Result<bool, Error> {
|
2017-10-20 20:20:41 +02:00
|
|
|
Ok(self.sstore.account_ref(&address).is_ok() && !self.blacklisted_accounts.contains(&address))
|
2017-01-04 12:50:50 +01:00
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Returns addresses of all accounts.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn accounts(&self) -> Result<Vec<Address>, Error> {
|
2016-12-27 12:53:56 +01:00
|
|
|
let accounts = self.sstore.accounts()?;
|
2017-06-07 11:34:53 +02:00
|
|
|
Ok(accounts
|
|
|
|
.into_iter()
|
|
|
|
.map(|a| a.address)
|
|
|
|
.filter(|address| !self.blacklisted_accounts.contains(address))
|
|
|
|
.collect()
|
|
|
|
)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2017-02-10 01:07:06 +01:00
|
|
|
/// Returns addresses of hardware accounts.
|
|
|
|
pub fn hardware_accounts(&self) -> Result<Vec<Address>, Error> {
|
|
|
|
let accounts = self.hardware_store.as_ref().map_or(Vec::new(), |h| h.list_wallets());
|
|
|
|
Ok(accounts.into_iter().map(|a| a.address).collect())
|
|
|
|
}
|
|
|
|
|
2017-09-14 19:28:43 +02:00
|
|
|
/// Get a list of paths to locked hardware wallets
|
|
|
|
pub fn locked_hardware_accounts(&self) -> Result<Vec<String>, SignError> {
|
|
|
|
match self.hardware_store.as_ref().map(|h| h.list_locked_wallets()) {
|
|
|
|
None => Err(SignError::NotFound),
|
|
|
|
Some(Err(e)) => Err(SignError::Hardware(e)),
|
|
|
|
Some(Ok(s)) => Ok(s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Provide a pin to a locked hardware wallet on USB path to unlock it
|
|
|
|
pub fn hardware_pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, SignError> {
|
|
|
|
match self.hardware_store.as_ref().map(|h| h.pin_matrix_ack(path, pin)) {
|
|
|
|
None => Err(SignError::NotFound),
|
|
|
|
Some(Err(e)) => Err(SignError::Hardware(e)),
|
|
|
|
Some(Ok(s)) => Ok(s),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Sets addresses of accounts exposed for unknown dapps.
|
2016-12-10 12:34:20 +01:00
|
|
|
/// `None` means that all accounts will be visible.
|
2017-02-20 16:33:12 +01:00
|
|
|
/// If not `None` or empty it will also override default account.
|
|
|
|
pub fn set_new_dapps_addresses(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
|
|
|
|
let current_default = self.new_dapps_default_address()?;
|
|
|
|
|
2016-12-10 12:34:20 +01:00
|
|
|
self.dapps_settings.write().set_policy(match accounts {
|
2017-02-20 16:33:12 +01:00
|
|
|
None => NewDappsPolicy::AllAccounts {
|
|
|
|
default: current_default,
|
|
|
|
},
|
2016-12-10 12:34:20 +01:00
|
|
|
Some(accounts) => NewDappsPolicy::Whitelist(accounts),
|
|
|
|
});
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Gets addresses of accounts exposed for unknown dapps.
|
2016-12-10 12:34:20 +01:00
|
|
|
/// `None` means that all accounts will be visible.
|
2017-02-20 16:33:12 +01:00
|
|
|
pub fn new_dapps_addresses(&self) -> Result<Option<Vec<Address>>, Error> {
|
2016-12-10 12:34:20 +01:00
|
|
|
Ok(match self.dapps_settings.read().policy() {
|
2017-02-20 16:33:12 +01:00
|
|
|
NewDappsPolicy::AllAccounts { .. } => None,
|
2016-12-10 12:34:20 +01:00
|
|
|
NewDappsPolicy::Whitelist(accounts) => Some(accounts),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Sets a default account for unknown dapps.
|
|
|
|
/// This account will always be returned as the first one.
|
|
|
|
pub fn set_new_dapps_default_address(&self, address: Address) -> Result<(), Error> {
|
|
|
|
if !self.valid_addresses()?.contains(&address) {
|
|
|
|
return Err(SSError::InvalidAccount.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut settings = self.dapps_settings.write();
|
|
|
|
let new_policy = match settings.policy() {
|
|
|
|
NewDappsPolicy::AllAccounts { .. } => NewDappsPolicy::AllAccounts { default: address },
|
|
|
|
NewDappsPolicy::Whitelist(list) => NewDappsPolicy::Whitelist(Self::insert_default(list, address)),
|
|
|
|
};
|
|
|
|
settings.set_policy(new_policy);
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Inserts given address as first in the vector, preventing duplicates.
|
|
|
|
fn insert_default(mut addresses: Vec<Address>, default: Address) -> Vec<Address> {
|
|
|
|
if let Some(position) = addresses.iter().position(|address| address == &default) {
|
|
|
|
addresses.swap(0, position);
|
|
|
|
} else {
|
|
|
|
addresses.insert(0, default);
|
|
|
|
}
|
|
|
|
|
|
|
|
addresses
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a list of accounts that new dapp should see.
|
|
|
|
/// First account is always the default account.
|
|
|
|
fn new_dapps_addresses_list(&self) -> Result<Vec<Address>, Error> {
|
|
|
|
match self.dapps_settings.read().policy() {
|
|
|
|
NewDappsPolicy::AllAccounts { default } => if default.is_zero() {
|
|
|
|
self.accounts()
|
|
|
|
} else {
|
|
|
|
Ok(Self::insert_default(self.accounts()?, default))
|
|
|
|
},
|
|
|
|
NewDappsPolicy::Whitelist(accounts) => {
|
|
|
|
let addresses = self.filter_addresses(accounts)?;
|
|
|
|
if addresses.is_empty() {
|
|
|
|
Ok(vec![self.accounts()?.get(0).cloned().unwrap_or(0.into())])
|
|
|
|
} else {
|
|
|
|
Ok(addresses)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Gets a default account for new dapps
|
|
|
|
/// Will return zero address in case the default is not set and there are no accounts configured.
|
|
|
|
pub fn new_dapps_default_address(&self) -> Result<Address, Error> {
|
|
|
|
Ok(self.new_dapps_addresses_list()?
|
|
|
|
.get(0)
|
|
|
|
.cloned()
|
|
|
|
.unwrap_or(0.into())
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-12-10 12:34:20 +01:00
|
|
|
/// Gets a list of dapps recently requesting accounts.
|
2017-01-30 10:59:46 +01:00
|
|
|
pub fn recent_dapps(&self) -> Result<HashMap<DappId, u64>, Error> {
|
2016-12-10 12:34:20 +01:00
|
|
|
Ok(self.dapps_settings.read().recent_dapps())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Marks dapp as recently used.
|
|
|
|
pub fn note_dapp_used(&self, dapp: DappId) -> Result<(), Error> {
|
|
|
|
let mut dapps = self.dapps_settings.write();
|
|
|
|
dapps.mark_dapp_used(dapp.clone());
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Gets addresses visible for given dapp.
|
|
|
|
pub fn dapp_addresses(&self, dapp: DappId) -> Result<Vec<Address>, Error> {
|
|
|
|
let accounts = self.dapps_settings.read().settings().get(&dapp).map(|settings| {
|
|
|
|
(settings.accounts.clone(), settings.default.clone())
|
|
|
|
});
|
2016-12-10 12:34:20 +01:00
|
|
|
|
|
|
|
match accounts {
|
2017-02-20 16:33:12 +01:00
|
|
|
Some((Some(accounts), Some(default))) => self.filter_addresses(Self::insert_default(accounts, default)),
|
|
|
|
Some((Some(accounts), None)) => self.filter_addresses(accounts),
|
|
|
|
Some((None, Some(default))) => self.filter_addresses(Self::insert_default(self.new_dapps_addresses_list()?, default)),
|
|
|
|
_ => self.new_dapps_addresses_list(),
|
2016-12-10 12:34:20 +01:00
|
|
|
}
|
2016-11-22 11:56:27 +01:00
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
/// Returns default account for particular dapp falling back to other allowed accounts if necessary.
|
2017-02-20 16:33:12 +01:00
|
|
|
pub fn dapp_default_address(&self, dapp: DappId) -> Result<Address, Error> {
|
|
|
|
let dapp_default = self.dapp_addresses(dapp)?
|
2017-01-30 21:08:36 +01:00
|
|
|
.get(0)
|
2017-02-20 16:33:12 +01:00
|
|
|
.cloned();
|
|
|
|
|
|
|
|
match dapp_default {
|
|
|
|
Some(default) => Ok(default),
|
|
|
|
None => self.new_dapps_default_address(),
|
|
|
|
}
|
2017-01-30 21:08:36 +01:00
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Sets default address for given dapp.
|
|
|
|
/// Does not alter dapp addresses, but this account will always be returned as the first one.
|
|
|
|
pub fn set_dapp_default_address(&self, dapp: DappId, address: Address) -> Result<(), Error> {
|
|
|
|
if !self.valid_addresses()?.contains(&address) {
|
|
|
|
return Err(SSError::InvalidAccount.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.dapps_settings.write().set_default(dapp, address);
|
2016-11-22 11:56:27 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
/// Sets addresses visible for given dapp.
|
|
|
|
/// If `None` - falls back to dapps addresses
|
|
|
|
/// If not `None` and not empty it will also override default account.
|
|
|
|
pub fn set_dapp_addresses(&self, dapp: DappId, addresses: Option<Vec<Address>>) -> Result<(), Error> {
|
|
|
|
let (addresses, default) = match addresses {
|
|
|
|
Some(addresses) => {
|
|
|
|
let addresses = self.filter_addresses(addresses)?;
|
|
|
|
let default = addresses.get(0).cloned();
|
|
|
|
(Some(addresses), default)
|
|
|
|
},
|
|
|
|
None => (None, None),
|
|
|
|
};
|
|
|
|
|
|
|
|
let mut settings = self.dapps_settings.write();
|
|
|
|
if let Some(default) = default {
|
|
|
|
settings.set_default(dapp.clone(), default);
|
|
|
|
}
|
|
|
|
settings.set_accounts(dapp, addresses);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn valid_addresses(&self) -> Result<HashSet<Address>, Error> {
|
|
|
|
Ok(self.addresses_info().into_iter()
|
2017-02-03 13:56:48 +01:00
|
|
|
.map(|(address, _)| address)
|
|
|
|
.chain(self.accounts()?)
|
2017-02-20 16:33:12 +01:00
|
|
|
.collect())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Removes addresses that are neither accounts nor in address book.
|
|
|
|
fn filter_addresses(&self, addresses: Vec<Address>) -> Result<Vec<Address>, Error> {
|
|
|
|
let valid = self.valid_addresses()?;
|
2017-02-03 13:56:48 +01:00
|
|
|
|
|
|
|
Ok(addresses.into_iter()
|
|
|
|
.filter(|a| valid.contains(&a))
|
|
|
|
.collect()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2016-08-11 18:31:28 +02:00
|
|
|
/// Returns each address along with metadata.
|
2017-02-03 13:56:48 +01:00
|
|
|
pub fn addresses_info(&self) -> HashMap<Address, AccountMeta> {
|
|
|
|
self.address_book.read().get()
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each address along with metadata.
|
2017-02-03 13:56:48 +01:00
|
|
|
pub fn set_address_name(&self, account: Address, name: String) {
|
|
|
|
self.address_book.write().set_name(account, name)
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each address along with metadata.
|
2017-02-03 13:56:48 +01:00
|
|
|
pub fn set_address_meta(&self, account: Address, meta: String) {
|
|
|
|
self.address_book.write().set_meta(account, meta)
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
|
|
|
|
2016-12-07 16:53:46 +01:00
|
|
|
/// Removes and address from the addressbook
|
2017-02-03 13:56:48 +01:00
|
|
|
pub fn remove_address(&self, addr: Address) {
|
|
|
|
self.address_book.write().remove(addr)
|
2016-12-07 16:53:46 +01:00
|
|
|
}
|
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
/// Returns each account along with name and meta.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
2017-02-10 01:07:06 +01:00
|
|
|
let r = self.sstore.accounts()?
|
2016-07-24 17:38:21 +02:00
|
|
|
.into_iter()
|
2017-06-07 11:34:53 +02:00
|
|
|
.filter(|a| !self.blacklisted_accounts.contains(&a.address))
|
2017-01-30 11:44:09 +01:00
|
|
|
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
|
2016-07-24 17:38:21 +02:00
|
|
|
.collect();
|
|
|
|
Ok(r)
|
|
|
|
}
|
|
|
|
|
2017-02-10 01:07:06 +01:00
|
|
|
/// Returns each hardware account along with name and meta.
|
|
|
|
pub fn hardware_accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
|
|
|
|
let r = self.hardware_accounts()?
|
|
|
|
.into_iter()
|
|
|
|
.map(|address| (address.clone(), self.account_meta(address).ok().unwrap_or_default()))
|
|
|
|
.collect();
|
|
|
|
Ok(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each hardware account along with name and meta.
|
2017-11-01 11:23:18 +01:00
|
|
|
pub fn is_hardware_address(&self, address: &Address) -> bool {
|
|
|
|
self.hardware_store.as_ref().and_then(|s| s.wallet_info(address)).is_some()
|
2017-02-10 01:07:06 +01:00
|
|
|
}
|
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
/// Returns each account along with name and meta.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
|
2017-02-10 01:07:06 +01:00
|
|
|
if let Some(info) = self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)) {
|
|
|
|
Ok(AccountMeta {
|
|
|
|
name: info.name,
|
|
|
|
meta: info.manufacturer,
|
|
|
|
uuid: None,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
let account = self.sstore.account_ref(&address)?;
|
|
|
|
Ok(AccountMeta {
|
|
|
|
name: self.sstore.name(&account)?,
|
|
|
|
meta: self.sstore.meta(&account)?,
|
|
|
|
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid
|
|
|
|
})
|
|
|
|
}
|
2016-07-24 17:38:21 +02:00
|
|
|
}
|
|
|
|
|
2017-08-09 11:09:40 +02:00
|
|
|
/// Returns account public key.
|
|
|
|
pub fn account_public(&self, address: Address, password: &str) -> Result<Public, Error> {
|
|
|
|
self.sstore.public(&self.sstore.account_ref(&address)?, password)
|
|
|
|
}
|
|
|
|
|
2016-07-24 17:38:21 +02:00
|
|
|
/// Returns each account along with name and meta.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
self.sstore.set_name(&self.sstore.account_ref(&address)?, name)?;
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns each account along with name and meta.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn set_account_meta(&self, address: Address, meta: String) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
self.sstore.set_meta(&self.sstore.account_ref(&address)?, meta)?;
|
2016-07-24 17:38:21 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-10-22 15:24:02 +02:00
|
|
|
/// Returns `true` if the password for `account` is `password`. `false` if not.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn test_password(&self, address: &Address, password: &str) -> Result<bool, Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
self.sstore.test_password(&self.sstore.account_ref(&address)?, password)
|
2016-11-30 15:08:38 +01:00
|
|
|
.map_err(Into::into)
|
2016-10-27 08:28:12 +02:00
|
|
|
}
|
2016-10-22 15:24:02 +02:00
|
|
|
|
2016-11-20 16:17:57 +01:00
|
|
|
/// Permanently removes an account.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn kill_account(&self, address: &Address, password: &str) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
self.sstore.remove_account(&self.sstore.account_ref(&address)?, &password)?;
|
2016-11-20 16:17:57 +01:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2016-10-22 15:24:02 +02:00
|
|
|
/// Changes the password of `account` from `password` to `new_password`. Fails if incorrect `password` given.
|
2017-02-05 16:17:56 +01:00
|
|
|
pub fn change_password(&self, address: &Address, password: String, new_password: String) -> Result<(), Error> {
|
|
|
|
self.sstore.change_password(&self.sstore.account_ref(address)?, &password, &new_password)
|
2016-10-27 08:28:12 +02:00
|
|
|
}
|
2016-10-22 15:24:02 +02:00
|
|
|
|
2017-03-23 13:23:03 +01:00
|
|
|
/// 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)
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Helper method used for unlocking accounts.
|
2017-01-30 11:44:09 +01:00
|
|
|
fn unlock_account(&self, address: Address, password: String, unlock: Unlock) -> Result<(), Error> {
|
2017-02-05 16:17:56 +01:00
|
|
|
let account = self.sstore.account_ref(&address)?;
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
// check if account is already unlocked pernamently, if it is, do nothing
|
2016-12-09 09:31:58 +01:00
|
|
|
let mut unlocked = self.unlocked.write();
|
2016-08-05 23:33:14 +02:00
|
|
|
if let Some(data) = unlocked.get(&account) {
|
|
|
|
if let Unlock::Perm = data.unlock {
|
|
|
|
return Ok(())
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-01 11:23:18 +01:00
|
|
|
if self.unlock_keep_secret && unlock == Unlock::Perm {
|
2017-06-06 18:06:40 +02:00
|
|
|
// verify password and get the secret
|
|
|
|
let secret = self.sstore.raw_secret(&account, &password)?;
|
|
|
|
self.unlocked_secrets.write().insert(account.clone(), secret);
|
|
|
|
} else {
|
|
|
|
// verify password by signing dump message
|
|
|
|
// result may be discarded
|
|
|
|
let _ = self.sstore.sign(&account, &password, &Default::default())?;
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
let data = AccountData {
|
|
|
|
unlock: unlock,
|
|
|
|
password: password,
|
|
|
|
};
|
|
|
|
|
|
|
|
unlocked.insert(account, data);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-01-30 21:08:36 +01:00
|
|
|
fn password(&self, account: &StoreAccountRef) -> Result<String, SignError> {
|
2016-12-09 09:31:58 +01:00
|
|
|
let mut unlocked = self.unlocked.write();
|
2017-01-30 21:08:36 +01:00
|
|
|
let data = unlocked.get(account).ok_or(SignError::NotUnlocked)?.clone();
|
2017-06-06 18:06:40 +02:00
|
|
|
if let Unlock::OneTime = data.unlock {
|
2016-10-15 14:44:08 +02:00
|
|
|
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
|
|
|
}
|
2016-10-31 16:55:30 +01:00
|
|
|
if let Unlock::Timed(ref end) = data.unlock {
|
|
|
|
if Instant::now() > *end {
|
2016-10-15 14:44:08 +02:00
|
|
|
unlocked.remove(account).expect("data exists: so key must exist: qed");
|
2017-01-30 21:08:36 +01:00
|
|
|
return Err(SignError::NotUnlocked);
|
2016-10-15 14:44:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(data.password.clone())
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
/// Unlocks account permanently.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn unlock_account_permanently(&self, account: Address, password: String) -> Result<(), Error> {
|
2016-06-20 00:10:34 +02:00
|
|
|
self.unlock_account(account, password, Unlock::Perm)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Unlocks account temporarily (for one signing).
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn unlock_account_temporarily(&self, account: Address, password: String) -> Result<(), Error> {
|
2017-06-06 18:06:40 +02:00
|
|
|
self.unlock_account(account, password, Unlock::OneTime)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2016-08-05 23:33:14 +02:00
|
|
|
/// Unlocks account temporarily with a timeout.
|
2018-04-14 21:35:58 +02:00
|
|
|
pub fn unlock_account_timed(&self, account: Address, password: String, duration: Duration) -> Result<(), Error> {
|
|
|
|
self.unlock_account(account, password, Unlock::Timed(Instant::now() + duration))
|
2016-08-05 23:33:14 +02:00
|
|
|
}
|
|
|
|
|
2016-06-22 21:32:26 +02:00
|
|
|
/// Checks if given account is unlocked
|
2017-11-01 11:23:18 +01:00
|
|
|
pub fn is_unlocked(&self, address: &Address) -> bool {
|
2016-12-09 09:31:58 +01:00
|
|
|
let unlocked = self.unlocked.read();
|
2017-06-06 18:06:40 +02:00
|
|
|
let unlocked_secrets = self.unlocked_secrets.read();
|
2017-11-01 11:23:18 +01:00
|
|
|
self.sstore.account_ref(address)
|
2017-06-06 18:06:40 +02:00
|
|
|
.map(|r| unlocked.get(&r).is_some() || unlocked_secrets.get(&r).is_some())
|
2017-02-05 16:17:56 +01:00
|
|
|
.unwrap_or(false)
|
2016-06-22 21:32:26 +02:00
|
|
|
}
|
|
|
|
|
2017-11-01 11:23:18 +01:00
|
|
|
/// Checks if given account is unlocked permanently
|
|
|
|
pub fn is_unlocked_permanently(&self, address: &Address) -> bool {
|
|
|
|
let unlocked = self.unlocked.read();
|
|
|
|
self.sstore.account_ref(address)
|
|
|
|
.map(|r| unlocked.get(&r).map_or(false, |account| account.unlock == Unlock::Perm))
|
|
|
|
.unwrap_or(false)
|
|
|
|
}
|
|
|
|
|
2016-10-15 14:44:08 +02:00
|
|
|
/// Signs the message. If password is not provided the account must be unlocked.
|
2017-01-30 21:08:36 +01:00
|
|
|
pub fn sign(&self, address: Address, password: Option<String>, message: Message) -> Result<Signature, SignError> {
|
2017-02-05 16:17:56 +01:00
|
|
|
let account = self.sstore.account_ref(&address)?;
|
2017-06-06 18:06:40 +02:00
|
|
|
match self.unlocked_secrets.read().get(&account) {
|
|
|
|
Some(secret) => {
|
|
|
|
Ok(self.sstore.sign_with_secret(&secret, &message)?)
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
|
|
|
Ok(self.sstore.sign(&account, &password, &message)?)
|
|
|
|
}
|
|
|
|
}
|
2016-09-22 14:48:22 +02:00
|
|
|
}
|
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
/// Signs message using the derived secret. If password is not provided the account must be unlocked.
|
|
|
|
pub fn sign_derived(&self, address: &Address, password: Option<String>, derivation: Derivation, message: Message)
|
|
|
|
-> Result<Signature, SignError>
|
|
|
|
{
|
|
|
|
let account = self.sstore.account_ref(address)?;
|
|
|
|
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
|
|
|
Ok(self.sstore.sign_derived(&account, &password, derivation, &message)?)
|
|
|
|
}
|
|
|
|
|
2016-11-30 15:08:38 +01:00
|
|
|
/// Signs given message with supplied token. Returns a token to use in next signing within this session.
|
2017-01-30 21:08:36 +01:00
|
|
|
pub fn sign_with_token(&self, address: Address, token: AccountToken, message: Message) -> Result<(Signature, AccountToken), SignError> {
|
2017-02-05 16:17:56 +01:00
|
|
|
let account = self.sstore.account_ref(&address)?;
|
2016-12-27 12:53:56 +01:00
|
|
|
let is_std_password = self.sstore.test_password(&account, &token)?;
|
2016-11-30 15:08:38 +01:00
|
|
|
|
|
|
|
let new_token = random_string(16);
|
|
|
|
let signature = if is_std_password {
|
|
|
|
// Insert to transient store
|
2017-01-30 11:44:09 +01:00
|
|
|
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
2016-11-30 15:08:38 +01:00
|
|
|
// sign
|
2016-12-27 12:53:56 +01:00
|
|
|
self.sstore.sign(&account, &token, &message)?
|
2016-11-30 15:08:38 +01:00
|
|
|
} else {
|
|
|
|
// check transient store
|
2016-12-27 12:53:56 +01:00
|
|
|
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
2016-11-30 15:08:38 +01:00
|
|
|
// and sign
|
2016-12-27 12:53:56 +01:00
|
|
|
self.transient_sstore.sign(&account, &new_token, &message)?
|
2016-11-30 15:08:38 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok((signature, new_token))
|
|
|
|
}
|
|
|
|
|
2016-11-30 16:11:41 +01:00
|
|
|
/// Decrypts a message with given token. Returns a token to use in next operation for this account.
|
2017-01-30 11:44:09 +01:00
|
|
|
pub fn decrypt_with_token(&self, address: Address, token: AccountToken, shared_mac: &[u8], message: &[u8])
|
2017-01-30 21:08:36 +01:00
|
|
|
-> Result<(Vec<u8>, AccountToken), SignError>
|
2016-11-30 16:11:41 +01:00
|
|
|
{
|
2017-02-05 16:17:56 +01:00
|
|
|
let account = self.sstore.account_ref(&address)?;
|
2016-12-27 12:53:56 +01:00
|
|
|
let is_std_password = self.sstore.test_password(&account, &token)?;
|
2016-11-30 16:11:41 +01:00
|
|
|
|
|
|
|
let new_token = random_string(16);
|
|
|
|
let message = if is_std_password {
|
|
|
|
// Insert to transient store
|
2017-01-30 11:44:09 +01:00
|
|
|
self.sstore.copy_account(&self.transient_sstore, SecretVaultRef::Root, &account, &token, &new_token)?;
|
2016-11-30 16:11:41 +01:00
|
|
|
// decrypt
|
2016-12-27 12:53:56 +01:00
|
|
|
self.sstore.decrypt(&account, &token, shared_mac, message)?
|
2016-11-30 16:11:41 +01:00
|
|
|
} else {
|
|
|
|
// check transient store
|
2016-12-27 12:53:56 +01:00
|
|
|
self.transient_sstore.change_password(&account, &token, &new_token)?;
|
2016-11-30 16:11:41 +01:00
|
|
|
// and decrypt
|
2016-12-27 12:53:56 +01:00
|
|
|
self.transient_sstore.decrypt(&account, &token, shared_mac, message)?
|
2016-11-30 16:11:41 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
Ok((message, new_token))
|
|
|
|
}
|
|
|
|
|
2016-10-15 14:44:08 +02:00
|
|
|
/// Decrypts a message. If password is not provided the account must be unlocked.
|
2017-01-30 21:08:36 +01:00
|
|
|
pub fn decrypt(&self, address: Address, password: Option<String>, shared_mac: &[u8], message: &[u8]) -> Result<Vec<u8>, SignError> {
|
2017-02-05 16:17:56 +01:00
|
|
|
let account = self.sstore.account_ref(&address)?;
|
2016-12-27 12:53:56 +01:00
|
|
|
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
|
|
|
Ok(self.sstore.decrypt(&account, &password, shared_mac, message)?)
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2016-08-11 18:31:28 +02:00
|
|
|
|
2017-08-09 11:09:40 +02:00
|
|
|
/// Agree on shared key.
|
|
|
|
pub fn agree(&self, address: Address, password: Option<String>, other_public: &Public) -> Result<Secret, SignError> {
|
|
|
|
let account = self.sstore.account_ref(&address)?;
|
|
|
|
let password = password.map(Ok).unwrap_or_else(|| self.password(&account))?;
|
|
|
|
Ok(self.sstore.agree(&account, &password, other_public)?)
|
|
|
|
}
|
|
|
|
|
2016-08-11 18:31:28 +02:00
|
|
|
/// Returns the underlying `SecretStore` reference if one exists.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn list_geth_accounts(&self, testnet: bool) -> Vec<Address> {
|
2016-08-11 18:31:28 +02:00
|
|
|
self.sstore.list_geth_accounts(testnet).into_iter().map(|a| Address::from(a).into()).collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns the underlying `SecretStore` reference if one exists.
|
2016-08-15 15:09:00 +02:00
|
|
|
pub fn import_geth_accounts(&self, desired: Vec<Address>, testnet: bool) -> Result<Vec<Address>, Error> {
|
2017-01-30 11:44:09 +01:00
|
|
|
self.sstore.import_geth_accounts(SecretVaultRef::Root, desired, testnet)
|
|
|
|
.map(|a| a.into_iter().map(|a| a.address).collect())
|
|
|
|
.map_err(Into::into)
|
2016-08-11 18:31:28 +02:00
|
|
|
}
|
2017-02-05 16:17:56 +01:00
|
|
|
|
|
|
|
/// Create new vault.
|
|
|
|
pub fn create_vault(&self, name: &str, password: &str) -> Result<(), Error> {
|
|
|
|
self.sstore.create_vault(name, password)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Open existing vault.
|
|
|
|
pub fn open_vault(&self, name: &str, password: &str) -> Result<(), Error> {
|
|
|
|
self.sstore.open_vault(name, password)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Close previously opened vault.
|
|
|
|
pub fn close_vault(&self, name: &str) -> Result<(), Error> {
|
|
|
|
self.sstore.close_vault(name)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List all vaults
|
|
|
|
pub fn list_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
self.sstore.list_vaults()
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// List all currently opened vaults
|
|
|
|
pub fn list_opened_vaults(&self) -> Result<Vec<String>, Error> {
|
|
|
|
self.sstore.list_opened_vaults()
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Change vault password.
|
|
|
|
pub fn change_vault_password(&self, name: &str, new_password: &str) -> Result<(), Error> {
|
|
|
|
self.sstore.change_vault_password(name, new_password)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Change vault of the given address.
|
|
|
|
pub fn change_vault(&self, address: Address, new_vault: &str) -> Result<(), Error> {
|
|
|
|
let new_vault_ref = if new_vault.is_empty() { SecretVaultRef::Root } else { SecretVaultRef::Vault(new_vault.to_owned()) };
|
|
|
|
let old_account_ref = self.sstore.account_ref(&address)?;
|
|
|
|
self.sstore.change_account_vault(new_vault_ref, old_account_ref)
|
|
|
|
.map_err(Into::into)
|
|
|
|
.map(|_| ())
|
|
|
|
}
|
2017-02-08 13:53:39 +01:00
|
|
|
|
|
|
|
/// Get vault metadata string.
|
|
|
|
pub fn get_vault_meta(&self, name: &str) -> Result<String, Error> {
|
|
|
|
self.sstore.get_vault_meta(name)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Set vault metadata string.
|
|
|
|
pub fn set_vault_meta(&self, name: &str, meta: &str) -> Result<(), Error> {
|
|
|
|
self.sstore.set_vault_meta(name, meta)
|
|
|
|
.map_err(Into::into)
|
|
|
|
}
|
2017-02-10 01:07:06 +01:00
|
|
|
|
|
|
|
/// Sign transaction with hardware wallet.
|
2017-09-14 19:28:43 +02:00
|
|
|
pub fn sign_with_hardware(&self, address: Address, transaction: &Transaction, chain_id: Option<u64>, rlp_encoded_transaction: &[u8]) -> Result<Signature, SignError> {
|
|
|
|
let t_info = TransactionInfo {
|
|
|
|
nonce: transaction.nonce,
|
|
|
|
gas_price: transaction.gas_price,
|
|
|
|
gas_limit: transaction.gas,
|
|
|
|
to: match transaction.action {
|
|
|
|
Action::Create => None,
|
|
|
|
Action::Call(ref to) => Some(to.clone()),
|
|
|
|
},
|
|
|
|
value: transaction.value,
|
|
|
|
data: transaction.data.to_vec(),
|
|
|
|
chain_id: chain_id,
|
|
|
|
};
|
|
|
|
match self.hardware_store.as_ref().map(|s| s.sign_transaction(&address, &t_info, rlp_encoded_transaction)) {
|
2017-02-10 01:07:06 +01:00
|
|
|
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
|
|
|
|
Some(Err(e)) => Err(From::from(e)),
|
|
|
|
Some(Ok(s)) => Ok(s),
|
|
|
|
}
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2017-01-30 10:59:46 +01:00
|
|
|
use super::{AccountProvider, Unlock, DappId};
|
2018-04-14 21:35:58 +02:00
|
|
|
use std::time::{Duration, Instant};
|
2017-06-07 11:34:53 +02:00
|
|
|
use ethstore::ethkey::{Generator, Random, Address};
|
2017-02-15 16:56:15 +01:00
|
|
|
use ethstore::{StoreAccountRef, Derivation};
|
2018-01-10 13:35:18 +01:00
|
|
|
use ethereum_types::H256;
|
2016-06-20 00:10:34 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlock_account_temp() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test1".into()).is_err());
|
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
2016-10-15 14:44:08 +02:00
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
|
|
|
|
2017-02-15 16:56:15 +01:00
|
|
|
#[test]
|
|
|
|
fn derived_account_nosave() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
|
|
|
|
|
|
|
let derived_addr = ap.derive_account(
|
|
|
|
&kp.address(),
|
|
|
|
None,
|
|
|
|
Derivation::SoftHash(H256::from(999)),
|
|
|
|
false,
|
|
|
|
).expect("Derivation should not fail");
|
|
|
|
|
|
|
|
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_err(),
|
|
|
|
"There should be an error because account is not supposed to be saved");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn derived_account_save() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
|
|
|
|
|
|
|
let derived_addr = ap.derive_account(
|
|
|
|
&kp.address(),
|
|
|
|
None,
|
|
|
|
Derivation::SoftHash(H256::from(999)),
|
|
|
|
true,
|
|
|
|
).expect("Derivation should not fail");
|
|
|
|
|
|
|
|
assert!(ap.unlock_account_permanently(derived_addr, "base_wrong".into()).is_err(),
|
|
|
|
"There should be an error because password is invalid");
|
|
|
|
|
|
|
|
assert!(ap.unlock_account_permanently(derived_addr, "base".into()).is_ok(),
|
|
|
|
"Should be ok because account is saved and password is valid");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn derived_account_sign() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "base").is_ok());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "base".into()).is_ok());
|
|
|
|
|
|
|
|
let derived_addr = ap.derive_account(
|
|
|
|
&kp.address(),
|
|
|
|
None,
|
|
|
|
Derivation::SoftHash(H256::from(1999)),
|
|
|
|
true,
|
|
|
|
).expect("Derivation should not fail");
|
|
|
|
ap.unlock_account_permanently(derived_addr, "base".into())
|
|
|
|
.expect("Should be ok because account is saved and password is valid");
|
|
|
|
|
|
|
|
let msg = Default::default();
|
|
|
|
let signed_msg1 = ap.sign(derived_addr, None, msg)
|
|
|
|
.expect("Signing with existing unlocked account should not fail");
|
|
|
|
let signed_msg2 = ap.sign_derived(
|
|
|
|
&kp.address(),
|
|
|
|
None,
|
|
|
|
Derivation::SoftHash(H256::from(1999)),
|
|
|
|
msg,
|
|
|
|
).expect("Derived signing with existing unlocked account should not fail");
|
|
|
|
|
|
|
|
assert_eq!(signed_msg1, signed_msg2,
|
|
|
|
"Signed messages should match");
|
|
|
|
}
|
|
|
|
|
2016-06-20 00:10:34 +02:00
|
|
|
#[test]
|
|
|
|
fn unlock_account_perm() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "test1".into()).is_err());
|
|
|
|
assert!(ap.unlock_account_permanently(kp.address(), "test".into()).is_ok());
|
2016-10-15 14:44:08 +02:00
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
2016-06-20 00:10:34 +02:00
|
|
|
assert!(ap.unlock_account_temporarily(kp.address(), "test".into()).is_ok());
|
2016-10-15 14:44:08 +02:00
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|
2016-08-05 23:33:14 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn unlock_account_timer() {
|
|
|
|
let kp = Random.generate().unwrap();
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
2018-04-14 21:35:58 +02:00
|
|
|
assert!(ap.unlock_account_timed(kp.address(), "test1".into(), Duration::from_secs(60)).is_err());
|
|
|
|
assert!(ap.unlock_account_timed(kp.address(), "test".into(), Duration::from_secs(60)).is_ok());
|
2016-10-15 14:44:08 +02:00
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_ok());
|
2017-01-30 11:44:09 +01:00
|
|
|
ap.unlocked.write().get_mut(&StoreAccountRef::root(kp.address())).unwrap().unlock = Unlock::Timed(Instant::now());
|
2016-10-15 14:44:08 +02:00
|
|
|
assert!(ap.sign(kp.address(), None, Default::default()).is_err());
|
2016-08-05 23:33:14 +02:00
|
|
|
}
|
2016-11-22 11:56:27 +01:00
|
|
|
|
2016-11-30 13:47:14 +01:00
|
|
|
#[test]
|
|
|
|
fn should_sign_and_return_token() {
|
|
|
|
// given
|
2016-11-30 16:11:41 +01:00
|
|
|
let kp = Random.generate().unwrap();
|
2016-11-30 13:47:14 +01:00
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
assert!(ap.insert_account(kp.secret().clone(), "test").is_ok());
|
|
|
|
|
|
|
|
// when
|
2016-11-30 15:08:38 +01:00
|
|
|
let (_signature, token) = ap.sign_with_token(kp.address(), "test".into(), Default::default()).unwrap();
|
|
|
|
|
|
|
|
// then
|
|
|
|
ap.sign_with_token(kp.address(), token.clone(), Default::default())
|
|
|
|
.expect("First usage of token should be correct.");
|
|
|
|
assert!(ap.sign_with_token(kp.address(), token, Default::default()).is_err(), "Second usage of the same token should fail.");
|
2016-11-30 13:47:14 +01:00
|
|
|
}
|
2016-12-09 08:37:34 +01:00
|
|
|
|
2016-11-22 11:56:27 +01:00
|
|
|
#[test]
|
2017-02-20 16:33:12 +01:00
|
|
|
fn should_reset_dapp_addresses_to_default() {
|
2016-11-22 11:56:27 +01:00
|
|
|
// given
|
|
|
|
let ap = AccountProvider::transient_provider();
|
2017-01-30 10:59:46 +01:00
|
|
|
let app = DappId("app1".into());
|
2017-02-20 16:33:12 +01:00
|
|
|
// add accounts to address book
|
|
|
|
ap.set_address_name(1.into(), "1".into());
|
|
|
|
ap.set_address_name(2.into(), "2".into());
|
2016-12-11 17:51:34 +01:00
|
|
|
// set `AllAccounts` policy
|
2017-02-20 16:33:12 +01:00
|
|
|
ap.set_new_dapps_addresses(Some(vec![1.into(), 2.into()])).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
|
|
|
|
|
|
|
// Alter and check
|
|
|
|
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 3.into()])).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
|
|
|
|
|
|
|
|
// Reset back to default
|
|
|
|
ap.set_dapp_addresses(app.clone(), None).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_set_dapps_default_address() {
|
|
|
|
// given
|
|
|
|
let ap = AccountProvider::transient_provider();
|
|
|
|
let app = DappId("app1".into());
|
|
|
|
// set `AllAccounts` policy
|
|
|
|
ap.set_new_dapps_addresses(None).unwrap();
|
2017-02-03 13:56:48 +01:00
|
|
|
// add accounts to address book
|
|
|
|
ap.set_address_name(1.into(), "1".into());
|
|
|
|
ap.set_address_name(2.into(), "2".into());
|
2016-11-22 11:56:27 +01:00
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
ap.set_dapp_addresses(app.clone(), Some(vec![1.into(), 2.into(), 3.into()])).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into(), 2.into()]);
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
2016-11-22 11:56:27 +01:00
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
// when setting empty list
|
|
|
|
ap.set_dapp_addresses(app.clone(), Some(vec![])).unwrap();
|
|
|
|
|
|
|
|
// then default account is intact
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![1.into()]);
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
|
|
|
|
|
|
|
// alter default account
|
|
|
|
ap.set_dapp_default_address("app1".into(), 2.into()).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses(app.clone()).unwrap(), vec![2.into()]);
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 2.into());
|
2016-11-22 11:56:27 +01:00
|
|
|
}
|
2016-12-10 12:34:20 +01:00
|
|
|
|
|
|
|
#[test]
|
2017-02-20 16:33:12 +01:00
|
|
|
fn should_set_dapps_policy_and_default_account() {
|
2016-12-10 12:34:20 +01:00
|
|
|
// given
|
|
|
|
let ap = AccountProvider::transient_provider();
|
2017-02-20 16:33:12 +01:00
|
|
|
|
|
|
|
// default_account should be always available
|
|
|
|
assert_eq!(ap.new_dapps_default_address().unwrap(), 0.into());
|
|
|
|
|
2016-12-10 12:34:20 +01:00
|
|
|
let address = ap.new_account("test").unwrap();
|
2017-02-03 13:56:48 +01:00
|
|
|
ap.set_address_name(1.into(), "1".into());
|
2016-12-11 17:51:34 +01:00
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
// Default account set to first account by default
|
|
|
|
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
|
|
|
|
|
|
|
|
// Even when returning nothing
|
|
|
|
ap.set_new_dapps_addresses(Some(vec![])).unwrap();
|
|
|
|
// Default account is still returned
|
|
|
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
2016-12-11 17:51:34 +01:00
|
|
|
|
|
|
|
// change to all
|
2017-02-20 16:33:12 +01:00
|
|
|
ap.set_new_dapps_addresses(None).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
2016-12-10 12:34:20 +01:00
|
|
|
|
2017-02-03 13:56:48 +01:00
|
|
|
// change to non-existent account
|
2017-02-20 16:33:12 +01:00
|
|
|
ap.set_new_dapps_addresses(Some(vec![2.into()])).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![address]);
|
|
|
|
|
|
|
|
// change to a addresses
|
|
|
|
ap.set_new_dapps_addresses(Some(vec![1.into()])).unwrap();
|
|
|
|
assert_eq!(ap.dapp_addresses("app1".into()).unwrap(), vec![1.into()]);
|
|
|
|
|
|
|
|
// it overrides default account
|
|
|
|
assert_eq!(ap.new_dapps_default_address().unwrap(), 1.into());
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), 1.into());
|
2017-02-03 13:56:48 +01:00
|
|
|
|
2017-02-20 16:33:12 +01:00
|
|
|
ap.set_new_dapps_default_address(address).unwrap();
|
|
|
|
assert_eq!(ap.new_dapps_default_address().unwrap(), address);
|
|
|
|
assert_eq!(ap.dapp_default_address("app1".into()).unwrap(), address);
|
2016-12-10 12:34:20 +01:00
|
|
|
}
|
2017-06-07 11:34:53 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn should_not_return_blacklisted_account() {
|
|
|
|
// given
|
|
|
|
let mut ap = AccountProvider::transient_provider();
|
|
|
|
let acc = ap.new_account("test").unwrap();
|
|
|
|
ap.blacklisted_accounts = vec![acc];
|
|
|
|
|
|
|
|
// then
|
|
|
|
assert_eq!(ap.accounts_info().unwrap().keys().cloned().collect::<Vec<Address>>(), vec![]);
|
|
|
|
assert_eq!(ap.accounts().unwrap(), vec![]);
|
|
|
|
}
|
2016-06-20 00:10:34 +02:00
|
|
|
}
|