From ad440a12bdad91766657d039d3423a703bed32ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= Date: Wed, 30 Nov 2016 13:47:14 +0100 Subject: [PATCH] EthMultiStore --- ethcore/src/account_provider.rs | 27 ++++- ethstore/src/dir/disk.rs | 5 +- ethstore/src/dir/geth.rs | 5 +- ethstore/src/dir/mod.rs | 3 +- ethstore/src/dir/parity.rs | 5 +- ethstore/src/ethstore.rs | 143 +++++++++++++++++++++++++-- ethstore/tests/util/transient_dir.rs | 5 +- 7 files changed, 169 insertions(+), 24 deletions(-) diff --git a/ethcore/src/account_provider.rs b/ethcore/src/account_provider.rs index 917ae8b8b..e2ccd1d83 100644 --- a/ethcore/src/account_provider.rs +++ b/ethcore/src/account_provider.rs @@ -163,12 +163,19 @@ impl AddressBook { } } +fn transient_sstore() -> Box { + Box::new(EthStore::open(Box::new(NullDir::default())).expect("NullDir load always succeeds; qed"))) +} + /// Account management. /// Responsible for unlocking accounts. pub struct AccountProvider { - unlocked: Mutex>, - sstore: Box, address_book: Mutex, + unlocked: Mutex>, + /// Accounts on disk + sstore: Box, + /// Accounts unlocked with rolling tokens + transient_sstore: Box, } impl AccountProvider { @@ -178,6 +185,7 @@ impl AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::new(sstore.local_path().into())), sstore: sstore, + transient_sstore: transient_sstore(), } } @@ -186,8 +194,8 @@ impl AccountProvider { AccountProvider { unlocked: Mutex::new(HashMap::new()), address_book: Mutex::new(AddressBook::transient()), - sstore: Box::new(EthStore::open(Box::new(NullDir::default())) - .expect("NullDir load always succeeds; qed")) + sstore: transient_sstore(), + transient_sstore: transient_sstore(), } } @@ -433,4 +441,15 @@ mod tests { ap.unlocked.lock().get_mut(&kp.address()).unwrap().unlock = Unlock::Timed(Instant::now()); assert!(ap.sign(kp.address(), None, Default::default()).is_err()); } + + #[test] + fn should_sign_and_return_token() { + let kp = Random.generate().unwrap(); + // given + let ap = AccountProvider::transient_provider(); + assert!(ap.insert_account(kp.secret().clone(), "test").is_ok()); + + // when + let (_signature, token) = ap.sign_with_token(kp.address(), "test", Default::default()).unwrap(); + } } diff --git a/ethstore/src/dir/disk.rs b/ethstore/src/dir/disk.rs index 56b2c1ccb..22093171e 100644 --- a/ethstore/src/dir/disk.rs +++ b/ethstore/src/dir/disk.rs @@ -18,7 +18,6 @@ use std::{fs, io}; use std::path::{PathBuf, Path}; use std::collections::HashMap; use time; -use ethkey::Address; use {json, SafeAccount, Error}; use json::UUID; use super::KeyDirectory; @@ -138,12 +137,12 @@ impl KeyDirectory for DiskDirectory { Ok(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { // enumerate all entries in keystore // and find entry with given address let to_remove = try!(self.files()) .into_iter() - .find(|&(_, ref account)| &account.address == address); + .find(|&(_, ref acc)| acc == account); // remove it match to_remove { diff --git a/ethstore/src/dir/geth.rs b/ethstore/src/dir/geth.rs index f63ebbea2..a5367f98d 100644 --- a/ethstore/src/dir/geth.rs +++ b/ethstore/src/dir/geth.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -89,7 +88,7 @@ impl KeyDirectory for GethDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/dir/mod.rs b/ethstore/src/dir/mod.rs index e29bd1ec4..0da4d71fb 100644 --- a/ethstore/src/dir/mod.rs +++ b/ethstore/src/dir/mod.rs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -use ethkey::Address; use std::path::{PathBuf}; use {SafeAccount, Error}; @@ -30,7 +29,7 @@ pub enum DirectoryType { pub trait KeyDirectory: Send + Sync { fn load(&self) -> Result, Error>; fn insert(&self, account: SafeAccount) -> Result; - fn remove(&self, address: &Address) -> Result<(), Error>; + fn remove(&self, account: &SafeAccount) -> Result<(), Error>; fn path(&self) -> Option<&PathBuf> { None } } diff --git a/ethstore/src/dir/parity.rs b/ethstore/src/dir/parity.rs index 7aa50c80b..c5d0057d8 100644 --- a/ethstore/src/dir/parity.rs +++ b/ethstore/src/dir/parity.rs @@ -16,7 +16,6 @@ use std::env; use std::path::PathBuf; -use ethkey::Address; use {SafeAccount, Error}; use super::{KeyDirectory, DiskDirectory, DirectoryType}; @@ -68,7 +67,7 @@ impl KeyDirectory for ParityDirectory { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } } diff --git a/ethstore/src/ethstore.rs b/ethstore/src/ethstore.rs index 4991c4714..f83e5fd3a 100644 --- a/ethstore/src/ethstore.rs +++ b/ethstore/src/ethstore.rs @@ -53,7 +53,7 @@ impl EthStore { fn save(&self, account: SafeAccount) -> Result<(), Error> { // save to file - let account = try!(self.dir.insert(account.clone())); + let account = try!(self.dir.insert(account)); // update cache let mut cache = self.cache.write(); @@ -124,13 +124,11 @@ impl SecretStore for EthStore { } fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { - let can_remove = { - let account = try!(self.get(address)); - account.check_password(password) - }; + let account = try!(self.get(address)); + let can_remove = account.check_password(password); if can_remove { - try!(self.dir.remove(address)); + try!(self.dir.remove(&account)); let mut cache = self.cache.write(); cache.remove(address); Ok(()) @@ -197,3 +195,136 @@ impl SecretStore for EthStore { import::import_geth_accounts(&*self.dir, desired.into_iter().collect(), testnet) } } + +/// Similar to `EthStore` but may store many accounts (with different passwords) for the same `Address` +pub struct EthMultiStore { + dir: Box, + iterations: u32, + cache: RwLock>>, +} + +impl EthMultiStore { + + pub fn open(directory: Box) -> Result { + Self::open_with_iterations(directory, KEY_ITERATIONS as u32) + } + + pub fn open_with_iterations(directory: Box, iterations: u32) -> Result { + let mut store = EthMultiStore { + dir: directory, + iterations: iterations, + cache: Default::default(), + }; + try!(store.reload_accounts()); + Ok(store) + } + + fn reload_accounts(&self) -> Result<(), Error> { + let mut cache = self.cache.write(); + let accounts = try!(self.dir.load()); + + let mut new_accounts = BTreeMap::new(); + for account in accounts { + let mut entry = new_accounts.entry(account.address.clone()).or_insert_with(Vec::new); + entry.push(account); + } + mem::replace(&mut *cache, new_accounts); + Ok(()) + } + + fn get(&self, address: &Address) -> Result, Error> { + { + let cache = self.cache.read(); + if let Some(accounts) = cache.get(address) { + if !accounts.is_empty() { + return Ok(accounts.clone()) + } + } + } + + try!(self.reload_accounts()); + let cache = self.cache.read(); + let accounts = try!(cache.get(address).cloned().ok_or(Error::InvalidAccount)); + if accounts.is_empty() { + Err(Error::InvalidAccount) + } else { + Ok(accounts) + } + } + + pub fn insert_account(&self, account: SafeAccount) -> Result<(), Error> { + //save to file + let account = try!(self.dir.insert(account)); + + // update cache + let mut cache = self.cache.write(); + let mut accounts = cache.entry(account.address.clone()).or_insert_with(Vec::new); + accounts.push(account); + Ok(()) + } + + pub fn remove_account(&self, address: &Address, password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + + for account in accounts { + // Skip if password is invalid + if !account.check_password(password) { + continue; + } + + // Remove from dir + try!(self.dir.remove(&account)); + + // Remove from cache + let mut cache = self.cache.write(); + let is_empty = { + let mut accounts = cache.get_mut(address).expect("Entry exists, because it was returned by `get`; qed"); + if let Some(position) = accounts.iter().position(|acc| acc == &account) { + accounts.remove(position); + } + accounts.is_empty() + }; + + if is_empty { + cache.remove(address); + } + + return Ok(()); + } + Err(Error::InvalidPassword) + } + + pub fn change_password(&self, address: &Address, old_password: &str, new_password: &str) -> Result<(), Error> { + let accounts = try!(self.get(address)); + for account in accounts { + // First remove + try!(self.remove_account(&address, old_password)); + // Then insert back with new password + let new_account = try!(account.change_password(old_password, new_password, self.iterations)); + try!(self.insert_account(new_account)); + } + Ok(()) + } + + pub fn sign(&self, address: &Address, password: &str, message: &Message) -> Result { + let accounts = try!(self.get(address)); + for account in accounts { + if account.check_password(password) { + return account.sign(password, message); + } + } + + Err(Error::InvalidPassword) + } + + pub fn decrypt(&self, account: &Address, password: &str, shared_mac: &[u8], message: &[u8]) -> Result, Error> { + let accounts = try!(self.get(account)); + for account in accounts { + if account.check_password(password) { + return account.decrypt(password, shared_mac, message); + } + } + Err(Error::InvalidPassword) + } +} + diff --git a/ethstore/tests/util/transient_dir.rs b/ethstore/tests/util/transient_dir.rs index 23523e48c..76010182e 100644 --- a/ethstore/tests/util/transient_dir.rs +++ b/ethstore/tests/util/transient_dir.rs @@ -18,7 +18,6 @@ use std::path::PathBuf; use std::{env, fs}; use rand::{Rng, OsRng}; use ethstore::dir::{KeyDirectory, DiskDirectory}; -use ethstore::ethkey::Address; use ethstore::{Error, SafeAccount}; pub fn random_dir() -> PathBuf { @@ -68,7 +67,7 @@ impl KeyDirectory for TransientDir { self.dir.insert(account) } - fn remove(&self, address: &Address) -> Result<(), Error> { - self.dir.remove(address) + fn remove(&self, account: &SafeAccount) -> Result<(), Error> { + self.dir.remove(account) } }