Ledger wallet support (#4486)
* Ledger devices support * structs for RPC types
This commit is contained in:
parent
395a44e4a8
commit
a7e6d8727a
53
Cargo.lock
generated
53
Cargo.lock
generated
@ -122,6 +122,14 @@ dependencies = [
|
||||
"rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.4.0"
|
||||
@ -381,6 +389,7 @@ dependencies = [
|
||||
"ethkey 0.2.0",
|
||||
"ethstore 0.1.0",
|
||||
"evmjit 1.6.0",
|
||||
"hardware-wallet 1.6.0",
|
||||
"heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)",
|
||||
"lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
@ -862,6 +871,18 @@ name = "hamming"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hardware-wallet"
|
||||
version = "1.6.0"
|
||||
dependencies = [
|
||||
"ethcore-bigint 0.1.2",
|
||||
"ethkey 0.2.0",
|
||||
"hidapi 0.3.1 (git+https://github.com/ethcore/hidapi-rs)",
|
||||
"libusb 0.3.0 (git+https://github.com/ethcore/libusb-rs)",
|
||||
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heapsize"
|
||||
version = "0.3.6"
|
||||
@ -870,6 +891,15 @@ dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hidapi"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/ethcore/hidapi-rs#9a127c1dca7e327e4fdd428406a76c9f5ef48563"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hpack"
|
||||
version = "0.2.0"
|
||||
@ -1107,6 +1137,25 @@ name = "libc"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "libusb"
|
||||
version = "0.3.0"
|
||||
source = "git+https://github.com/ethcore/libusb-rs#32bacf61abd981d5cbd4a8fecca5a2dc0b762a96"
|
||||
dependencies = [
|
||||
"bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libusb-sys"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/ethcore/libusb-sys#3c56a34bd040b0e60758824d839d9b700cfad2e0"
|
||||
dependencies = [
|
||||
"gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.2.1"
|
||||
@ -2486,6 +2535,7 @@ dependencies = [
|
||||
"checksum aster 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "07d344974f0a155f091948aa389fb1b912d3a58414fbdb9c8d446d193ee3496a"
|
||||
"checksum base32 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1b9605ba46d61df0410d8ac686b0007add8172eba90e8e909c347856fe794d8c"
|
||||
"checksum bigint 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2311bcd71b281e142a095311c22509f0d6bcd87b3000d7dbaa810929b9d6f6ae"
|
||||
"checksum bit-set 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e6e1e6fb1c9e3d6fcdec57216a74eaa03e41f52a22f13a16438251d8e88b89da"
|
||||
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
||||
"checksum bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5b97c2c8e8bbb4251754f559df8af22fb264853c7d009084a576cdf12565089d"
|
||||
"checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3"
|
||||
@ -2525,6 +2575,7 @@ dependencies = [
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum hamming 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "65043da274378d68241eb9a8f8f8aa54e349136f7b8e12f63e3ef44043cc30e1"
|
||||
"checksum heapsize 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "abb306abb8d398e053cfb1b3e7b72c2f580be048b85745c52652954f8ad1439c"
|
||||
"checksum hidapi 0.3.1 (git+https://github.com/ethcore/hidapi-rs)" = "<none>"
|
||||
"checksum hpack 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d2da7d3a34cf6406d9d700111b8eafafe9a251de41ae71d8052748259343b58"
|
||||
"checksum httparse 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46534074dbb80b070d60a5cb8ecadd8963a00a438ae1a95268850a7ef73b67ae"
|
||||
"checksum hyper 0.10.0-a.0 (git+https://github.com/ethcore/hyper)" = "<none>"
|
||||
@ -2547,6 +2598,8 @@ dependencies = [
|
||||
"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
|
||||
"checksum lazycell 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce12306c4739d86ee97c23139f3a34ddf0387bbf181bc7929d287025a8c3ef6b"
|
||||
"checksum libc 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)" = "408014cace30ee0f767b1c4517980646a573ec61a57957aeeabcac8ac0a02e8d"
|
||||
"checksum libusb 0.3.0 (git+https://github.com/ethcore/libusb-rs)" = "<none>"
|
||||
"checksum libusb-sys 0.2.3 (git+https://github.com/ethcore/libusb-sys)" = "<none>"
|
||||
"checksum linked-hash-map 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bda158e0dabeb97ee8a401f4d17e479d6b891a14de0bba79d5cc2d4d325b5e48"
|
||||
"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
|
||||
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
|
||||
|
@ -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,15 +321,36 @@ 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> {
|
||||
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)?,
|
||||
@ -287,6 +358,7 @@ impl AccountProvider {
|
||||
uuid: self.sstore.uuid(&account).ok().map(Into::into), // allowed to not have a Uuid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns each account along with name and meta.
|
||||
pub fn set_account_name(&self, address: Address, name: String) -> Result<(), Error> {
|
||||
@ -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`.
|
||||
|
18
hw/Cargo.toml
Normal file
18
hw/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
||||
[package]
|
||||
description = "Hardware wallet support."
|
||||
homepage = "http://parity.io"
|
||||
license = "GPL-3.0"
|
||||
name = "hardware-wallet"
|
||||
version = "1.6.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
|
||||
[dependencies]
|
||||
log = "0.3"
|
||||
parking_lot = "0.3"
|
||||
hidapi = { git = "https://github.com/ethcore/hidapi-rs" }
|
||||
libusb = { git = "https://github.com/ethcore/libusb-rs" }
|
||||
ethkey = { path = "../ethkey" }
|
||||
ethcore-bigint = { path = "../util/bigint" }
|
||||
|
||||
[dev-dependencies]
|
||||
rustc-serialize = "0.3"
|
364
hw/src/ledger.rs
Normal file
364
hw/src/ledger.rs
Normal file
File diff suppressed because one or more lines are too long
183
hw/src/lib.rs
Normal file
183
hw/src/lib.rs
Normal file
@ -0,0 +1,183 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// 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/>.
|
||||
|
||||
//! Hardware wallet management.
|
||||
|
||||
extern crate parking_lot;
|
||||
extern crate hidapi;
|
||||
extern crate libusb;
|
||||
extern crate ethkey;
|
||||
extern crate ethcore_bigint;
|
||||
#[macro_use] extern crate log;
|
||||
#[cfg(test)] extern crate rustc_serialize;
|
||||
|
||||
mod ledger;
|
||||
|
||||
use std::fmt;
|
||||
use std::thread;
|
||||
use std::sync::atomic;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::time::Duration;
|
||||
use parking_lot::Mutex;
|
||||
use ethkey::{Address, Signature};
|
||||
|
||||
pub use ledger::KeyPath;
|
||||
|
||||
/// Hardware waller error.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// Ledger device error.
|
||||
LedgerDevice(ledger::Error),
|
||||
/// USB error.
|
||||
Usb(libusb::Error),
|
||||
/// Hardware wallet not found for specified key.
|
||||
KeyNotFound,
|
||||
}
|
||||
|
||||
/// Hardware waller information.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WalletInfo {
|
||||
/// Wallet device name.
|
||||
pub name: String,
|
||||
/// Wallet device manufacturer.
|
||||
pub manufacturer: String,
|
||||
/// Wallet device serial number.
|
||||
pub serial: String,
|
||||
/// Ethereum address.
|
||||
pub address: Address,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Error::KeyNotFound => write!(f, "Key not found for given address."),
|
||||
Error::LedgerDevice(ref e) => write!(f, "{}", e),
|
||||
Error::Usb(ref e) => write!(f, "{}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ledger::Error> for Error {
|
||||
fn from(err: ledger::Error) -> Error {
|
||||
match err {
|
||||
ledger::Error::KeyNotFound => Error::KeyNotFound,
|
||||
_ => Error::LedgerDevice(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<libusb::Error> for Error {
|
||||
fn from(err: libusb::Error) -> Error {
|
||||
Error::Usb(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Hardware wallet management interface.
|
||||
pub struct HardwareWalletManager {
|
||||
update_thread: Option<thread::JoinHandle<()>>,
|
||||
exiting: Arc<AtomicBool>,
|
||||
ledger: Arc<Mutex<ledger::Manager>>,
|
||||
}
|
||||
|
||||
struct EventHandler {
|
||||
ledger: Weak<Mutex<ledger::Manager>>,
|
||||
}
|
||||
|
||||
impl libusb::Hotplug for EventHandler {
|
||||
fn device_arrived(&mut self, _device: libusb::Device) {
|
||||
debug!("USB Device arrived");
|
||||
if let Some(l) = self.ledger.upgrade() {
|
||||
for _ in 0..10 {
|
||||
// The device might not be visible right away. Try a few times.
|
||||
if l.lock().update_devices().unwrap_or_else(|e| {
|
||||
debug!("Error enumerating Ledger devices: {}", e);
|
||||
0
|
||||
}) > 0 {
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(200));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_left(&mut self, _device: libusb::Device) {
|
||||
debug!("USB Device lost");
|
||||
if let Some(l) = self.ledger.upgrade() {
|
||||
if let Err(e) = l.lock().update_devices() {
|
||||
debug!("Error enumerating Ledger devices: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareWalletManager {
|
||||
pub fn new() -> Result<HardwareWalletManager, Error> {
|
||||
let usb_context = Arc::new(libusb::Context::new()?);
|
||||
let ledger = Arc::new(Mutex::new(ledger::Manager::new()?));
|
||||
usb_context.register_callback(None, None, None, Box::new(EventHandler { ledger: Arc::downgrade(&ledger) }))?;
|
||||
let exiting = Arc::new(AtomicBool::new(false));
|
||||
let thread_exiting = exiting.clone();
|
||||
let l = ledger.clone();
|
||||
let thread = thread::Builder::new().name("hw_wallet".to_string()).spawn(move || {
|
||||
if let Err(e) = l.lock().update_devices() {
|
||||
debug!("Error updating ledger devices: {}", e);
|
||||
}
|
||||
loop {
|
||||
usb_context.handle_events(Some(Duration::from_millis(500))).unwrap_or_else(|e| debug!("Error processing USB events: {}", e));
|
||||
if thread_exiting.load(atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}).ok();
|
||||
Ok(HardwareWalletManager {
|
||||
update_thread: thread,
|
||||
exiting: exiting,
|
||||
ledger: ledger,
|
||||
})
|
||||
}
|
||||
|
||||
/// Select key derivation path for a chain.
|
||||
pub fn set_key_path(&self, key_path: KeyPath) {
|
||||
self.ledger.lock().set_key_path(key_path);
|
||||
}
|
||||
|
||||
|
||||
/// List connected wallets. This only returns wallets that are ready to be used.
|
||||
pub fn list_wallets(&self) -> Vec<WalletInfo> {
|
||||
self.ledger.lock().list_devices()
|
||||
}
|
||||
|
||||
/// Get connected wallet info.
|
||||
pub fn wallet_info(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.ledger.lock().device_info(address)
|
||||
}
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
pub fn sign_transaction(&self, address: &Address, data: &[u8]) -> Result<Signature, Error> {
|
||||
Ok(self.ledger.lock().sign_transaction(address, data)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HardwareWalletManager {
|
||||
fn drop(&mut self) {
|
||||
self.exiting.store(true, atomic::Ordering::Release);
|
||||
if let Some(thread) = self.update_thread.take() {
|
||||
thread.thread().unpark();
|
||||
thread.join().ok();
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ use std::path::PathBuf;
|
||||
use ethcore::ethstore::{EthStore, SecretStore, import_accounts, read_geth_accounts};
|
||||
use ethcore::ethstore::dir::RootDiskDirectory;
|
||||
use ethcore::ethstore::SecretVaultRef;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
use helpers::{password_prompt, password_from_file};
|
||||
use params::SpecType;
|
||||
|
||||
@ -92,7 +92,7 @@ fn new(n: NewAccount) -> Result<String, String> {
|
||||
|
||||
let dir = Box::new(keys_dir(n.path, n.spec)?);
|
||||
let secret_store = Box::new(secret_store(dir, Some(n.iterations))?);
|
||||
let acc_provider = AccountProvider::new(secret_store);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let new_account = acc_provider.new_account(&password).map_err(|e| format!("Could not create new account: {}", e))?;
|
||||
Ok(format!("{:?}", new_account))
|
||||
}
|
||||
@ -100,7 +100,7 @@ fn new(n: NewAccount) -> Result<String, String> {
|
||||
fn list(list_cmd: ListAccounts) -> Result<String, String> {
|
||||
let dir = Box::new(keys_dir(list_cmd.path, list_cmd.spec)?);
|
||||
let secret_store = Box::new(secret_store(dir, None)?);
|
||||
let acc_provider = AccountProvider::new(secret_store);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let accounts = acc_provider.accounts();
|
||||
let result = accounts.into_iter()
|
||||
.map(|a| format!("{:?}", a))
|
||||
|
@ -101,6 +101,9 @@ usage! {
|
||||
or |c: &Config| otry!(c.account).password.clone(),
|
||||
flag_keys_iterations: u32 = 10240u32,
|
||||
or |c: &Config| otry!(c.account).keys_iterations.clone(),
|
||||
flag_no_hardware_wallets: bool = false,
|
||||
or |c: &Config| otry!(c.account).disable_hardware.clone(),
|
||||
|
||||
|
||||
flag_force_ui: bool = false,
|
||||
or |c: &Config| otry!(c.ui).force.clone(),
|
||||
@ -347,6 +350,7 @@ struct Account {
|
||||
unlock: Option<Vec<String>>,
|
||||
password: Option<Vec<String>>,
|
||||
keys_iterations: Option<u32>,
|
||||
disable_hardware: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, PartialEq, RustcDecodable)]
|
||||
@ -583,6 +587,7 @@ mod tests {
|
||||
flag_unlock: Some("0xdeadbeefcafe0000000000000000000000000000".into()),
|
||||
flag_password: vec!["~/.safe/password.file".into()],
|
||||
flag_keys_iterations: 10240u32,
|
||||
flag_no_hardware_wallets: false,
|
||||
|
||||
flag_force_ui: false,
|
||||
flag_no_ui: false,
|
||||
@ -769,6 +774,7 @@ mod tests {
|
||||
unlock: Some(vec!["0x1".into(), "0x2".into(), "0x3".into()]),
|
||||
password: Some(vec!["passwdfile path".into()]),
|
||||
keys_iterations: None,
|
||||
disable_hardware: None,
|
||||
}),
|
||||
ui: Some(Ui {
|
||||
force: None,
|
||||
|
@ -78,6 +78,7 @@ Account Options:
|
||||
--keys-iterations NUM Specify the number of iterations to use when
|
||||
deriving key from the password (bigger is more
|
||||
secure) (default: {flag_keys_iterations}).
|
||||
--no-hardware-wallets Disables hardware wallet support. (default: {flag_no_hardware_wallets})
|
||||
|
||||
UI Options:
|
||||
--force-ui Enable Trusted UI WebSocket endpoint,
|
||||
|
@ -461,6 +461,7 @@ impl Configuration {
|
||||
testnet: self.args.flag_testnet,
|
||||
password_files: self.args.flag_password.clone(),
|
||||
unlocked_accounts: to_addresses(&self.args.flag_unlock)?,
|
||||
enable_hardware_wallets: !self.args.flag_no_hardware_wallets,
|
||||
};
|
||||
|
||||
Ok(cfg)
|
||||
|
@ -175,6 +175,7 @@ pub struct AccountsConfig {
|
||||
pub testnet: bool,
|
||||
pub password_files: Vec<String>,
|
||||
pub unlocked_accounts: Vec<Address>,
|
||||
pub enable_hardware_wallets: bool,
|
||||
}
|
||||
|
||||
impl Default for AccountsConfig {
|
||||
@ -184,6 +185,7 @@ impl Default for AccountsConfig {
|
||||
testnet: false,
|
||||
password_files: Vec::new(),
|
||||
unlocked_accounts: Vec::new(),
|
||||
enable_hardware_wallets: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use ethcore::ethstore::{PresaleWallet, EthStore};
|
||||
use ethcore::ethstore::dir::RootDiskDirectory;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
use helpers::{password_prompt, password_from_file};
|
||||
use params::SpecType;
|
||||
|
||||
@ -37,7 +37,7 @@ pub fn execute(cmd: ImportWallet) -> Result<String, String> {
|
||||
|
||||
let dir = Box::new(RootDiskDirectory::create(cmd.path).unwrap());
|
||||
let secret_store = Box::new(EthStore::open_with_iterations(dir, cmd.iterations).unwrap());
|
||||
let acc_provider = AccountProvider::new(secret_store);
|
||||
let acc_provider = AccountProvider::new(secret_store, AccountProviderSettings::default());
|
||||
let wallet = PresaleWallet::open(cmd.wallet_path).map_err(|_| "Unable to open presale wallet.")?;
|
||||
let kp = wallet.decrypt(&password).map_err(|_| "Invalid password.")?;
|
||||
let address = acc_provider.insert_account(kp.secret().clone(), &password).unwrap();
|
||||
|
@ -26,7 +26,7 @@ use ethcore_logger::{Config as LogConfig};
|
||||
use ethcore::miner::{StratumOptions, Stratum};
|
||||
use ethcore::client::{Mode, DatabaseCompactionProfile, VMType, BlockChainClient};
|
||||
use ethcore::service::ClientService;
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
use ethcore::miner::{Miner, MinerService, ExternalMiner, MinerOptions};
|
||||
use ethcore::snapshot;
|
||||
use ethcore::verification::queue::VerifierSettings;
|
||||
@ -516,9 +516,13 @@ fn prepare_account_provider(spec: &SpecType, dirs: &Directories, data_dir: &str,
|
||||
let path = dirs.keys_path(data_dir);
|
||||
upgrade_key_location(&dirs.legacy_keys_path(cfg.testnet), &path);
|
||||
let dir = Box::new(RootDiskDirectory::create(&path).map_err(|e| format!("Could not open keys directory: {}", e))?);
|
||||
let account_provider = AccountProvider::new(Box::new(
|
||||
EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?
|
||||
));
|
||||
let account_settings = AccountProviderSettings {
|
||||
enable_hardware_wallets: cfg.enable_hardware_wallets,
|
||||
hardware_wallet_classic_key: spec == &SpecType::Classic,
|
||||
};
|
||||
let account_provider = AccountProvider::new(
|
||||
Box::new(EthStore::open_with_iterations(dir, cfg.iterations).map_err(|e| format!("Could not open keys directory: {}", e))?),
|
||||
account_settings);
|
||||
|
||||
for a in cfg.unlocked_accounts {
|
||||
// Check if the account exists
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use rlp;
|
||||
use rlp::{self, Stream};
|
||||
use util::{Address, H520, H256, U256, Uint, Bytes};
|
||||
use util::bytes::ToPretty;
|
||||
use util::sha3::Hashable;
|
||||
@ -190,11 +190,26 @@ pub fn sign_no_dispatch<C, M>(client: &C, miner: &M, accounts: &AccountProvider,
|
||||
};
|
||||
|
||||
let hash = t.hash(network_id);
|
||||
if accounts.is_hardware_address(address) {
|
||||
let mut stream = rlp::RlpStream::new();
|
||||
t.rlp_append_unsigned_transaction(&mut stream, network_id);
|
||||
let signature = accounts.sign_with_hardware(address, &stream.as_raw())
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Error signing transaction with hardware wallet: {}", e);
|
||||
errors::account("Error signing transaction with hardware wallet", e)
|
||||
})?;
|
||||
WithToken::No(SignedTransaction::new(t.with_signature(signature, network_id))
|
||||
.map_err(|e| {
|
||||
debug!(target: "miner", "Hardware wallet has produced invalid signature: {}", e);
|
||||
errors::account("Invalid signature generated", e)
|
||||
})?)
|
||||
} else {
|
||||
let signature = signature(accounts, address, hash, password)?;
|
||||
signature.map(|sig| {
|
||||
SignedTransaction::new(t.with_signature(sig, network_id))
|
||||
.expect("Transaction was signed by AccountsProvider; it never produces invalid signatures; qed")
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(signed_transaction)
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ use v1::types::{
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
BlockNumber, ConsensusCapability, VersionInfo,
|
||||
OperationsInfo, DappId, ChainStatus,
|
||||
AccountInfo, HwAccountInfo
|
||||
};
|
||||
|
||||
/// Parity implementation.
|
||||
@ -111,7 +112,7 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
{
|
||||
type Metadata = Metadata;
|
||||
|
||||
fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error> {
|
||||
fn accounts_info(&self, dapp: Trailing<DappId>) -> Result<BTreeMap<H160, AccountInfo>, Error> {
|
||||
let dapp = dapp.0;
|
||||
|
||||
let store = take_weak!(self.accounts);
|
||||
@ -128,12 +129,17 @@ impl<C, M, S: ?Sized, U> Parity for ParityClient<C, M, S, U> where
|
||||
.into_iter()
|
||||
.chain(other.into_iter())
|
||||
.filter(|&(ref a, _)| dapp_accounts.contains(a))
|
||||
.map(|(a, v)| {
|
||||
let m = map![
|
||||
"name".to_owned() => v.name
|
||||
];
|
||||
(format!("0x{}", a.hex()), m)
|
||||
})
|
||||
.map(|(a, v)| (H160::from(a), AccountInfo { name: v.name }))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
fn hardware_accounts_info(&self) -> Result<BTreeMap<H160, HwAccountInfo>, Error> {
|
||||
let store = take_weak!(self.accounts);
|
||||
let info = store.hardware_accounts_info().map_err(|e| errors::account("Could not fetch account info.", e))?;
|
||||
Ok(info
|
||||
.into_iter()
|
||||
.map(|(a, v)| (H160::from(a), HwAccountInfo { name: v.name, manufacturer: v.meta }))
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use ethcore::account_provider::AccountProvider;
|
||||
use ethcore::account_provider::{AccountProvider, AccountProviderSettings};
|
||||
use ethstore::EthStore;
|
||||
use ethstore::dir::RootDiskDirectory;
|
||||
use devtools::RandomTempPath;
|
||||
@ -36,7 +36,7 @@ fn accounts_provider() -> Arc<AccountProvider> {
|
||||
fn accounts_provider_with_vaults_support(temp_path: &str) -> Arc<AccountProvider> {
|
||||
let root_keys_dir = RootDiskDirectory::create(temp_path).unwrap();
|
||||
let secret_store = EthStore::open(Box::new(root_keys_dir)).unwrap();
|
||||
Arc::new(AccountProvider::new(Box::new(secret_store)))
|
||||
Arc::new(AccountProvider::new(Box::new(secret_store), AccountProviderSettings::default()))
|
||||
}
|
||||
|
||||
fn setup_with_accounts_provider(accounts_provider: Arc<AccountProvider>) -> ParityAccountsTester {
|
||||
|
@ -28,6 +28,7 @@ use v1::types::{
|
||||
TransactionStats, LocalTransactionStatus,
|
||||
BlockNumber, ConsensusCapability, VersionInfo,
|
||||
OperationsInfo, DappId, ChainStatus,
|
||||
AccountInfo, HwAccountInfo,
|
||||
};
|
||||
|
||||
build_rpc_trait! {
|
||||
@ -37,7 +38,11 @@ build_rpc_trait! {
|
||||
|
||||
/// Returns accounts information.
|
||||
#[rpc(name = "parity_accountsInfo")]
|
||||
fn accounts_info(&self, Trailing<DappId>) -> Result<BTreeMap<String, BTreeMap<String, String>>, Error>;
|
||||
fn accounts_info(&self, Trailing<DappId>) -> Result<BTreeMap<H160, AccountInfo>, Error>;
|
||||
|
||||
/// Returns hardware accounts information.
|
||||
#[rpc(name = "parity_hardwareAccountsInfo")]
|
||||
fn hardware_accounts_info(&self) -> Result<BTreeMap<H160, HwAccountInfo>, Error>;
|
||||
|
||||
/// Returns default account for dapp.
|
||||
#[rpc(meta, name = "parity_defaultAccount")]
|
||||
|
33
rpc/src/v1/types/account_info.rs
Normal file
33
rpc/src/v1/types/account_info.rs
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||
// 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/>.
|
||||
|
||||
use serde::{Serialize};
|
||||
|
||||
/// Account information.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
|
||||
pub struct AccountInfo {
|
||||
/// Account name
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Hardware wallet information.
|
||||
#[derive(Debug, Default, Clone, PartialEq, Serialize)]
|
||||
pub struct HwAccountInfo {
|
||||
/// Device name.
|
||||
pub name: String,
|
||||
/// Device manufacturer.
|
||||
pub manufacturer: String,
|
||||
}
|
@ -14,6 +14,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
mod account_info;
|
||||
mod bytes;
|
||||
mod block;
|
||||
mod block_number;
|
||||
@ -65,3 +66,4 @@ pub use self::uint::{U128, U256};
|
||||
pub use self::work::Work;
|
||||
pub use self::histogram::Histogram;
|
||||
pub use self::consensus_status::*;
|
||||
pub use self::account_info::{AccountInfo, HwAccountInfo};
|
||||
|
Loading…
Reference in New Issue
Block a user