Ledger wallet support (#4486)
* Ledger devices support * structs for RPC types
This commit is contained in:
committed by
Gav Wood
parent
395a44e4a8
commit
a7e6d8727a
@@ -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]
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`.
|
||||
|
||||
Reference in New Issue
Block a user