Ledger wallet support (#4486)

* Ledger devices support

* structs for RPC types
This commit is contained in:
Arkadiy Paronyan
2017-02-10 01:07:06 +01:00
committed by Gav Wood
parent 395a44e4a8
commit a7e6d8727a
21 changed files with 810 additions and 34 deletions

View File

@@ -42,6 +42,7 @@ rlp = { path = "../util/rlp" }
ethcore-stratum = { path = "../stratum" }
lru-cache = "0.1.0"
ethcore-bloom-journal = { path = "../util/bloom" }
hardware-wallet = { path = "../hw" }
ethabi = "0.2.2"
[dependencies.hyper]

View File

@@ -29,6 +29,7 @@ use ethstore::{SimpleSecretStore, SecretStore, Error as SSError, EthStore, EthMu
use ethstore::dir::MemoryDirectory;
use ethstore::ethkey::{Address, Message, Public, Secret, Random, Generator};
use ethjson::misc::AccountMeta;
use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath};
pub use ethstore::ethkey::Signature;
/// Type of unlock.
@@ -55,6 +56,10 @@ struct AccountData {
pub enum SignError {
/// Account is not unlocked
NotUnlocked,
/// Account does not exist.
NotFound,
/// Low-level hardware device error.
Hardware(HardwareError),
/// Low-level error from store
SStore(SSError)
}
@@ -63,11 +68,19 @@ impl fmt::Display for SignError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
SignError::NotUnlocked => write!(f, "Account is locked"),
SignError::NotFound => write!(f, "Account does not exist"),
SignError::Hardware(ref e) => write!(f, "{}", e),
SignError::SStore(ref e) => write!(f, "{}", e),
}
}
}
impl From<HardwareError> for SignError {
fn from(e: HardwareError) -> Self {
SignError::Hardware(e)
}
}
impl From<SSError> for SignError {
fn from(e: SSError) -> Self {
SignError::SStore(e)
@@ -107,17 +120,47 @@ pub struct AccountProvider {
sstore: Box<SecretStore>,
/// Accounts unlocked with rolling tokens
transient_sstore: EthMultiStore,
/// Accounts in hardware wallets.
hardware_store: Option<HardwareWalletManager>,
}
/// 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,
}
impl Default for AccountProviderSettings {
fn default() -> Self {
AccountProviderSettings {
enable_hardware_wallets: false,
hardware_wallet_classic_key: false,
}
}
}
impl AccountProvider {
/// Creates new account provider.
pub fn new(sstore: Box<SecretStore>) -> Self {
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)
},
Err(e) => warn!("Error initializing hardware wallets: {}", e),
}
}
AccountProvider {
unlocked: RwLock::new(HashMap::new()),
address_book: RwLock::new(AddressBook::new(&sstore.local_path())),
dapps_settings: RwLock::new(DappsSettingsStore::new(&sstore.local_path())),
sstore: sstore,
transient_sstore: transient_sstore(),
hardware_store: hardware_store,
}
}
@@ -129,6 +172,7 @@ impl AccountProvider {
dapps_settings: RwLock::new(DappsSettingsStore::transient()),
sstore: Box::new(EthStore::open(Box::new(MemoryDirectory::default())).expect("MemoryDirectory load always succeeds; qed")),
transient_sstore: transient_sstore(),
hardware_store: None,
}
}
@@ -176,6 +220,12 @@ impl AccountProvider {
Ok(accounts.into_iter().map(|a| a.address).collect())
}
/// 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())
}
/// Sets a whitelist of accounts exposed for unknown dapps.
/// `None` means that all accounts will be visible.
pub fn set_new_dapps_whitelist(&self, accounts: Option<Vec<Address>>) -> Result<(), Error> {
@@ -271,21 +321,43 @@ impl AccountProvider {
/// Returns each account along with name and meta.
pub fn accounts_info(&self) -> Result<HashMap<Address, AccountMeta>, Error> {
let r: HashMap<Address, AccountMeta> = self.sstore.accounts()?
let r = self.sstore.accounts()?
.into_iter()
.map(|a| (a.address.clone(), self.account_meta(a.address).ok().unwrap_or_default()))
.collect();
Ok(r)
}
/// 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.
pub fn is_hardware_address(&self, address: Address) -> bool {
self.hardware_store.as_ref().and_then(|s| s.wallet_info(&address)).is_some()
}
/// Returns each account along with name and meta.
pub fn account_meta(&self, address: Address) -> Result<AccountMeta, Error> {
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
})
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
})
}
}
/// Returns each account along with name and meta.
@@ -505,6 +577,15 @@ impl AccountProvider {
self.sstore.set_vault_meta(name, meta)
.map_err(Into::into)
}
/// Sign transaction with hardware wallet.
pub fn sign_with_hardware(&self, address: Address, transaction: &[u8]) -> Result<Signature, SignError> {
match self.hardware_store.as_ref().map(|s| s.sign_transaction(&address, transaction)) {
None | Some(Err(HardwareError::KeyNotFound)) => Err(SignError::NotFound),
Some(Err(e)) => Err(From::from(e)),
Some(Ok(s)) => Ok(s),
}
}
}
#[cfg(test)]

View File

@@ -105,6 +105,7 @@ extern crate linked_hash_map;
extern crate lru_cache;
extern crate ethcore_stratum;
extern crate ethabi;
extern crate hardware_wallet;
#[macro_use]
extern crate log;

View File

@@ -154,7 +154,7 @@ impl Transaction {
pub fn hash(&self, network_id: Option<u64>) -> H256 {
let mut stream = RlpStream::new();
self.rlp_append_unsigned_transaction(&mut stream, network_id);
stream.out().sha3()
stream.as_raw().sha3()
}
/// Signs the transaction as coming from `sender`.