EthMultiStore

This commit is contained in:
Tomasz Drwięga 2016-11-30 13:47:14 +01:00
parent 79e79473bf
commit ad440a12bd
7 changed files with 169 additions and 24 deletions

View File

@ -163,12 +163,19 @@ impl AddressBook {
}
}
fn transient_sstore() -> Box<SecretStore> {
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<HashMap<Address, AccountData>>,
sstore: Box<SecretStore>,
address_book: Mutex<AddressBook>,
unlocked: Mutex<HashMap<Address, AccountData>>,
/// Accounts on disk
sstore: Box<SecretStore>,
/// Accounts unlocked with rolling tokens
transient_sstore: Box<SecretStore>,
}
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();
}
}

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -14,7 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
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<Vec<SafeAccount>, Error>;
fn insert(&self, account: SafeAccount) -> Result<SafeAccount, Error>;
fn remove(&self, address: &Address) -> Result<(), Error>;
fn remove(&self, account: &SafeAccount) -> Result<(), Error>;
fn path(&self) -> Option<&PathBuf> { None }
}

View File

@ -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)
}
}

View File

@ -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<KeyDirectory>,
iterations: u32,
cache: RwLock<BTreeMap<Address, Vec<SafeAccount>>>,
}
impl EthMultiStore {
pub fn open(directory: Box<KeyDirectory>) -> Result<Self, Error> {
Self::open_with_iterations(directory, KEY_ITERATIONS as u32)
}
pub fn open_with_iterations(directory: Box<KeyDirectory>, iterations: u32) -> Result<Self, Error> {
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<Vec<SafeAccount>, 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<Signature, Error> {
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<Vec<u8>, 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)
}
}

View File

@ -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)
}
}