Hardware Wallet trait (#8071)
* getting started with replacing HardwareWalletManager * trezor and ledger impls the new trait with some drawbacks * Everything move to the new trait * It required lifetime annotations in the trait because [u8] in unsized * Lets now start moving entry point from HardwareWalletManager * rename trait to Wallet * move thread management to the actual wallets * Moved thread management to each respective Wallet * Cleaned up pub items that is needed to be pub * Wallet trait more or less finished * Cleaned up docs * fix tests * omit removed docs * fix spelling, naming och remove old comments * ledger test is broken, add correct logging format * So locally on my machine Linux Ubuntu 17.10 the test doesn't panic but on the CI server libusb::Context::new() fails which I don't understand because it has worked before * Additionally the ledger test is optional so I lean toward ignoring it the CI Server * ignore hardware tests by default * more verbose checking in ledger test
This commit is contained in:
parent
7a76916143
commit
849f5d9a44
359
hw/src/ledger.rs
359
hw/src/ledger.rs
@ -20,8 +20,11 @@
|
||||
use std::cmp::min;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::thread;
|
||||
|
||||
use ethereum_types::{H256, Address};
|
||||
use ethkey::Signature;
|
||||
@ -29,12 +32,12 @@ use hidapi;
|
||||
use libusb;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
|
||||
use super::{WalletInfo, KeyPath};
|
||||
use super::{WalletInfo, KeyPath, Device, Wallet, USB_DEVICE_CLASS_DEVICE};
|
||||
|
||||
/// Ledger vendor ID
|
||||
pub const LEDGER_VID: u16 = 0x2c97;
|
||||
/// Legder product IDs: [Nano S and Blue]
|
||||
pub const LEDGER_PIDS: [u16; 2] = [0x0000, 0x0001];
|
||||
const LEDGER_VID: u16 = 0x2c97;
|
||||
/// Ledger product IDs: [Nano S and Blue]
|
||||
const LEDGER_PIDS: [u16; 2] = [0x0000, 0x0001];
|
||||
|
||||
const ETH_DERIVATION_PATH_BE: [u8; 17] = [4, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0, 0, 0, 0, 0, 0, 0]; // 44'/60'/0'/0
|
||||
const ETC_DERIVATION_PATH_BE: [u8; 21] = [5, 0x80, 0, 0, 44, 0x80, 0, 0, 60, 0x80, 0x02, 0x73, 0xd0, 0x80, 0, 0, 0, 0, 0, 0, 0]; // 44'/60'/160720'/0'/0
|
||||
@ -64,8 +67,10 @@ pub enum Error {
|
||||
KeyNotFound,
|
||||
/// Signing has been cancelled by user.
|
||||
UserCancel,
|
||||
/// Invalid Device
|
||||
/// Invalid device
|
||||
InvalidDevice,
|
||||
/// Impossible error
|
||||
ImpossibleError,
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
@ -77,6 +82,7 @@ impl fmt::Display for Error {
|
||||
Error::KeyNotFound => write!(f, "Key not found"),
|
||||
Error::UserCancel => write!(f, "Operation has been cancelled"),
|
||||
Error::InvalidDevice => write!(f, "Unsupported product was entered"),
|
||||
Error::ImpossibleError => write!(f, "Placeholder error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -100,157 +106,43 @@ pub struct Manager {
|
||||
key_path: RwLock<KeyPath>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Device {
|
||||
path: String,
|
||||
info: WalletInfo,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
/// Create a new instance.
|
||||
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>) -> Manager {
|
||||
Manager {
|
||||
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>, exiting: Arc<AtomicBool>) -> Result<Arc<Manager>, libusb::Error> {
|
||||
let manager = Arc::new(Manager {
|
||||
usb: hidapi,
|
||||
devices: RwLock::new(Vec::new()),
|
||||
key_path: RwLock::new(KeyPath::Ethereum),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// Re-populate device list. Only those devices that have Ethereum app open will be added.
|
||||
pub fn update_devices(&self) -> Result<usize, Error> {
|
||||
let mut usb = self.usb.lock();
|
||||
usb.refresh_devices();
|
||||
let devices = usb.devices();
|
||||
let mut new_devices = Vec::new();
|
||||
let mut num_new_devices = 0;
|
||||
for device in devices {
|
||||
trace!("Checking device: {:?}", device);
|
||||
if device.vendor_id != LEDGER_VID || !LEDGER_PIDS.contains(&device.product_id) {
|
||||
continue;
|
||||
}
|
||||
match self.read_device_info(&usb, &device) {
|
||||
Ok(info) => {
|
||||
debug!("Found device: {:?}", info);
|
||||
if !self.devices.read().iter().any(|d| d.path == info.path) {
|
||||
num_new_devices += 1;
|
||||
}
|
||||
new_devices.push(info);
|
||||
let usb_context = Arc::new(libusb::Context::new()?);
|
||||
let m = manager.clone();
|
||||
|
||||
}
|
||||
Err(e) => debug!("Error reading device info: {}", e),
|
||||
};
|
||||
}
|
||||
*self.devices.write() = new_devices;
|
||||
Ok(num_new_devices)
|
||||
}
|
||||
// Subscribe to all Ledger devices
|
||||
// This means that we need to check that the given productID is supported
|
||||
// None => LIBUSB_HOTPLUG_MATCH_ANY, in other words that all are subscribed to
|
||||
// More info can be found: <http://libusb.sourceforge.net/api-1.0/group__hotplug.html#gae6c5f1add6cc754005549c7259dc35ea>
|
||||
usb_context.register_callback(
|
||||
Some(LEDGER_VID), None, Some(USB_DEVICE_CLASS_DEVICE),
|
||||
Box::new(EventHandler::new(Arc::downgrade(&manager)))).expect("usb_callback");
|
||||
|
||||
/// Select key derivation path for a known chain.
|
||||
pub fn set_key_path(&self, key_path: KeyPath) {
|
||||
*self.key_path.write() = key_path;
|
||||
// Ledger event handler thread
|
||||
thread::Builder::new()
|
||||
.spawn(move || {
|
||||
if let Err(e) = m.update_devices() {
|
||||
debug!(target: "hw", "Ledger couldn't connect at startup, error: {}", e);
|
||||
}
|
||||
|
||||
fn read_device_info(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
||||
let mut handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let address = Self::read_wallet_address(&mut handle, *self.key_path.read())?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or("Unknown".to_owned());
|
||||
Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name: name,
|
||||
manufacturer: manufacturer,
|
||||
serial: serial,
|
||||
address: address,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn read_wallet_address(handle: &hidapi::HidDevice, key_path: KeyPath) -> Result<Address, Error> {
|
||||
let ver = Self::send_apdu(handle, commands::GET_APP_CONFIGURATION, 0, 0, &[])?;
|
||||
if ver.len() != 4 {
|
||||
return Err(Error::Protocol("Version packet size mismatch"));
|
||||
}
|
||||
|
||||
let (major, minor, patch) = (ver[1], ver[2], ver[3]);
|
||||
if major < 1 || (major == 1 && minor == 0 && patch < 3) {
|
||||
return Err(Error::Protocol("App version 1.0.3 is required."));
|
||||
}
|
||||
|
||||
let eth_path = Ð_DERIVATION_PATH_BE[..];
|
||||
let etc_path = &ETC_DERIVATION_PATH_BE[..];
|
||||
let derivation_path = match key_path {
|
||||
KeyPath::Ethereum => eth_path,
|
||||
KeyPath::EthereumClassic => etc_path,
|
||||
};
|
||||
let key_and_address = Self::send_apdu(handle, commands::GET_ETH_PUBLIC_ADDRESS, 0, 0, derivation_path)?;
|
||||
if key_and_address.len() != 107 { // 1 + 65 PK + 1 + 40 Addr (ascii-hex)
|
||||
return Err(Error::Protocol("Key packet size mismatch"));
|
||||
}
|
||||
let address_string = ::std::str::from_utf8(&key_and_address[67..107])
|
||||
.map_err(|_| Error::Protocol("Invalid address string"))?;
|
||||
|
||||
let address = Address::from_str(&address_string)
|
||||
.map_err(|_| Error::Protocol("Invalid address string"))?;
|
||||
|
||||
Ok(address)
|
||||
}
|
||||
|
||||
/// List connected wallets. This only returns wallets that are ready to be used.
|
||||
pub fn list_devices(&self) -> Vec<WalletInfo> {
|
||||
self.devices.read().iter().map(|d| d.info.clone()).collect()
|
||||
}
|
||||
|
||||
/// Get wallet info.
|
||||
pub fn device_info(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
pub fn sign_transaction(&self, address: &Address, data: &[u8]) -> Result<Signature, Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
|
||||
let eth_path = Ð_DERIVATION_PATH_BE[..];
|
||||
let etc_path = &ETC_DERIVATION_PATH_BE[..];
|
||||
let derivation_path = match *self.key_path.read() {
|
||||
KeyPath::Ethereum => eth_path,
|
||||
KeyPath::EthereumClassic => etc_path,
|
||||
};
|
||||
const MAX_CHUNK_SIZE: usize = 255;
|
||||
let mut chunk: [u8; MAX_CHUNK_SIZE] = [0; MAX_CHUNK_SIZE];
|
||||
&mut chunk[0..derivation_path.len()].copy_from_slice(derivation_path);
|
||||
let mut dest_offset = derivation_path.len();
|
||||
let mut data_pos = 0;
|
||||
let mut result;
|
||||
loop {
|
||||
let p1 = if data_pos == 0 { 0x00 } else { 0x80 };
|
||||
let dest_left = MAX_CHUNK_SIZE - dest_offset;
|
||||
let chunk_data_size = min(dest_left, data.len() - data_pos);
|
||||
&mut chunk[dest_offset..][0..chunk_data_size].copy_from_slice(&data[data_pos..][0..chunk_data_size]);
|
||||
result = Self::send_apdu(&handle, commands::SIGN_ETH_TRANSACTION, p1, 0, &chunk[0..(dest_offset + chunk_data_size)])?;
|
||||
dest_offset = 0;
|
||||
data_pos += chunk_data_size;
|
||||
if data_pos == data.len() {
|
||||
usb_context.handle_events(Some(Duration::from_millis(500)))
|
||||
.unwrap_or_else(|e| debug!(target: "hw", "Ledger event handler error: {}", e));
|
||||
if exiting.load(atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
if result.len() != 65 {
|
||||
return Err(Error::Protocol("Signature packet size mismatch"));
|
||||
}
|
||||
let v = (result[0] + 1) % 2;
|
||||
let r = H256::from_slice(&result[1..33]);
|
||||
let s = H256::from_slice(&result[33..65]);
|
||||
Ok(Signature::from_rsv(&r, &s, v))
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
fn send_apdu(handle: &hidapi::HidDevice, command: u8, p1: u8, p2: u8, data: &[u8]) -> Result<Vec<u8>, Error> {
|
||||
@ -285,7 +177,7 @@ impl Manager {
|
||||
chunk_index += 1;
|
||||
}
|
||||
|
||||
// read response
|
||||
// Read response
|
||||
chunk_index = 0;
|
||||
let mut message_size = 0;
|
||||
let mut message = Vec::new();
|
||||
@ -303,7 +195,7 @@ impl Manager {
|
||||
|
||||
let mut offset = 5;
|
||||
if seq == 0 {
|
||||
// read message size and status word.
|
||||
// Read message size and status word.
|
||||
if chunk_size < 7 {
|
||||
return Err(Error::Protocol("Unexpected chunk header"));
|
||||
}
|
||||
@ -321,7 +213,7 @@ impl Manager {
|
||||
return Err(Error::Protocol("No status word"));
|
||||
}
|
||||
let status = (message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize);
|
||||
debug!("Read status {:x}", status);
|
||||
debug!(target: "hw", "Read status {:x}", status);
|
||||
match status {
|
||||
0x6700 => Err(Error::Protocol("Incorrect length")),
|
||||
0x6982 => Err(Error::Protocol("Security status not satisfied (Canceled by user)")),
|
||||
@ -365,18 +257,166 @@ fn try_connect_polling(ledger: Arc<Manager>, timeout: Duration) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
impl <'a>Wallet<'a> for Manager {
|
||||
type Error = Error;
|
||||
type Transaction = &'a [u8];
|
||||
|
||||
fn sign_transaction(&self, address: &Address, transaction: Self::Transaction) -> Result<Signature, Self::Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
|
||||
let eth_path = Ð_DERIVATION_PATH_BE[..];
|
||||
let etc_path = &ETC_DERIVATION_PATH_BE[..];
|
||||
let derivation_path = match *self.key_path.read() {
|
||||
KeyPath::Ethereum => eth_path,
|
||||
KeyPath::EthereumClassic => etc_path,
|
||||
};
|
||||
const MAX_CHUNK_SIZE: usize = 255;
|
||||
let mut chunk: [u8; MAX_CHUNK_SIZE] = [0; MAX_CHUNK_SIZE];
|
||||
&mut chunk[0..derivation_path.len()].copy_from_slice(derivation_path);
|
||||
let mut dest_offset = derivation_path.len();
|
||||
let mut data_pos = 0;
|
||||
let mut result;
|
||||
loop {
|
||||
let p1 = if data_pos == 0 { 0x00 } else { 0x80 };
|
||||
let dest_left = MAX_CHUNK_SIZE - dest_offset;
|
||||
let chunk_data_size = min(dest_left, transaction.len() - data_pos);
|
||||
&mut chunk[dest_offset..][0..chunk_data_size].copy_from_slice(&transaction[data_pos..][0..chunk_data_size]);
|
||||
result = Self::send_apdu(&handle, commands::SIGN_ETH_TRANSACTION, p1, 0, &chunk[0..(dest_offset + chunk_data_size)])?;
|
||||
dest_offset = 0;
|
||||
data_pos += chunk_data_size;
|
||||
if data_pos == transaction.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if result.len() != 65 {
|
||||
return Err(Error::Protocol("Signature packet size mismatch"));
|
||||
}
|
||||
let v = (result[0] + 1) % 2;
|
||||
let r = H256::from_slice(&result[1..33]);
|
||||
let s = H256::from_slice(&result[33..65]);
|
||||
Ok(Signature::from_rsv(&r, &s, v))
|
||||
}
|
||||
|
||||
fn set_key_path(&self, key_path: KeyPath) {
|
||||
*self.key_path.write() = key_path;
|
||||
}
|
||||
|
||||
fn update_devices(&self) -> Result<usize, Self::Error> {
|
||||
let mut usb = self.usb.lock();
|
||||
usb.refresh_devices();
|
||||
let devices = usb.devices();
|
||||
let mut new_devices = Vec::new();
|
||||
let mut num_new_devices = 0;
|
||||
for device in devices {
|
||||
trace!("Checking device: {:?}", device);
|
||||
if device.vendor_id != LEDGER_VID || !LEDGER_PIDS.contains(&device.product_id) {
|
||||
continue;
|
||||
}
|
||||
match self.read_device(&usb, &device) {
|
||||
Ok(info) => {
|
||||
debug!(target: "hw", "Found device: {:?}", info);
|
||||
if !self.devices.read().iter().any(|d| d.path == info.path) {
|
||||
num_new_devices += 1;
|
||||
}
|
||||
new_devices.push(info);
|
||||
|
||||
}
|
||||
Err(e) => debug!(target: "hw", "Error reading device info: {}", e),
|
||||
};
|
||||
}
|
||||
*self.devices.write() = new_devices;
|
||||
Ok(num_new_devices)
|
||||
}
|
||||
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Self::Error> {
|
||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or("Unknown".to_owned());
|
||||
match self.get_address(&handle) {
|
||||
Ok(Some(addr)) => {
|
||||
Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name: name,
|
||||
manufacturer: manufacturer,
|
||||
serial: serial,
|
||||
address: addr,
|
||||
},
|
||||
})
|
||||
}
|
||||
// This variant is not possible, but the trait forces this return type
|
||||
Ok(None) => Err(Error::ImpossibleError),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_devices(&self) -> Vec<WalletInfo> {
|
||||
self.devices.read().iter().map(|d| d.info.clone()).collect()
|
||||
}
|
||||
|
||||
// Not used because it is not supported by Ledger
|
||||
fn list_locked_devices(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Self::Error> {
|
||||
let ver = Self::send_apdu(device, commands::GET_APP_CONFIGURATION, 0, 0, &[])?;
|
||||
if ver.len() != 4 {
|
||||
return Err(Error::Protocol("Version packet size mismatch"));
|
||||
}
|
||||
|
||||
let (major, minor, patch) = (ver[1], ver[2], ver[3]);
|
||||
if major < 1 || (major == 1 && minor == 0 && patch < 3) {
|
||||
return Err(Error::Protocol("App version 1.0.3 is required."));
|
||||
}
|
||||
|
||||
let eth_path = Ð_DERIVATION_PATH_BE[..];
|
||||
let etc_path = &ETC_DERIVATION_PATH_BE[..];
|
||||
let derivation_path = match *self.key_path.read() {
|
||||
KeyPath::Ethereum => eth_path,
|
||||
KeyPath::EthereumClassic => etc_path,
|
||||
};
|
||||
let key_and_address = Self::send_apdu(device, commands::GET_ETH_PUBLIC_ADDRESS, 0, 0, derivation_path)?;
|
||||
if key_and_address.len() != 107 { // 1 + 65 PK + 1 + 40 Addr (ascii-hex)
|
||||
return Err(Error::Protocol("Key packet size mismatch"));
|
||||
}
|
||||
let address_string = ::std::str::from_utf8(&key_and_address[67..107])
|
||||
.map_err(|_| Error::Protocol("Invalid address string"))?;
|
||||
|
||||
let address = Address::from_str(&address_string)
|
||||
.map_err(|_| Error::Protocol("Invalid address string"))?;
|
||||
|
||||
Ok(Some(address))
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Self::Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ledger event handler
|
||||
/// A seperate thread is hanedling incoming events
|
||||
/// A separate thread is handling incoming events
|
||||
///
|
||||
/// Note, that this run to completion and race-conditions can't occur but this can
|
||||
/// therefore starve other events for being process with a spinlock or similar
|
||||
pub struct EventHandler {
|
||||
struct EventHandler {
|
||||
ledger: Weak<Manager>,
|
||||
}
|
||||
|
||||
impl EventHandler {
|
||||
/// Ledger event handler constructor
|
||||
pub fn new(ledger: Weak<Manager>) -> Self {
|
||||
fn new(ledger: Weak<Manager>) -> Self {
|
||||
Self { ledger: ledger }
|
||||
}
|
||||
}
|
||||
@ -402,16 +442,24 @@ impl libusb::Hotplug for EventHandler {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
/// This test can't be run without an actual ledger device connected
|
||||
fn smoke() {
|
||||
use rustc_hex::FromHex;
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().unwrap()));
|
||||
let manager = Manager::new(hidapi.clone());
|
||||
manager.update_devices().unwrap();
|
||||
for d in &*manager.devices.read() {
|
||||
println!("Device: {:?}", d);
|
||||
}
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().expect("HidApi")));
|
||||
let manager = Manager::new(hidapi.clone(), Arc::new(AtomicBool::new(false))).expect("Ledger Manager");
|
||||
|
||||
// Update device list
|
||||
assert_eq!(try_connect_polling(manager.clone(), Duration::from_millis(500)), true);
|
||||
|
||||
// Fetch the ethereum address of a connected ledger device
|
||||
let address = manager.list_devices()
|
||||
.iter()
|
||||
.filter(|d| d.manufacturer == "Ledger".to_string())
|
||||
.nth(0)
|
||||
.map(|d| d.address.clone())
|
||||
.expect("No ledger device detected");
|
||||
|
||||
if let Some(address) = manager.list_devices().first().map(|d| d.address.clone()) {
|
||||
let tx = FromHex::from_hex("eb018504a817c80082520894a6ca2e6707f2cc189794a9dd459d5b05ed1bcd1c8703f26fcfb7a22480018080").unwrap();
|
||||
let signature = manager.sign_transaction(&address, &tx);
|
||||
println!("Got {:?}", signature);
|
||||
@ -424,5 +472,4 @@ fn smoke() {
|
||||
let signature = manager.sign_transaction(&address, &huge_tx);
|
||||
println!("Got {:?}", signature);
|
||||
assert!(signature.is_ok());
|
||||
}
|
||||
}
|
||||
|
132
hw/src/lib.rs
132
hw/src/lib.rs
@ -16,7 +16,8 @@
|
||||
|
||||
//! Hardware wallet management.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#[warn(missing_docs)]
|
||||
#[warn(warnings)]
|
||||
|
||||
extern crate ethereum_types;
|
||||
extern crate ethkey;
|
||||
@ -38,12 +39,60 @@ use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use ethereum_types::U256;
|
||||
|
||||
const USB_DEVICE_CLASS_DEVICE: u8 = 0;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
path: String,
|
||||
info: WalletInfo,
|
||||
}
|
||||
|
||||
pub trait Wallet<'a> {
|
||||
/// Error
|
||||
type Error;
|
||||
/// Transaction data format
|
||||
type Transaction;
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
fn sign_transaction(&self, address: &Address, transaction: Self::Transaction) -> Result<Signature, Self::Error>;
|
||||
|
||||
/// Set key derivation path for a chain.
|
||||
fn set_key_path(&self, key_path: KeyPath);
|
||||
|
||||
/// Re-populate device list
|
||||
/// Note, this assumes all devices are iterated over and updated
|
||||
fn update_devices(&self) -> Result<usize, Self::Error>;
|
||||
|
||||
/// Read device info
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Self::Error>;
|
||||
|
||||
/// List connected and acknowledged wallets
|
||||
fn list_devices(&self) -> Vec<WalletInfo>;
|
||||
|
||||
/// List locked wallets
|
||||
/// This may be moved if it is the wrong assumption, for example this is not supported by Ledger
|
||||
/// Then this method return a empty vector
|
||||
fn list_locked_devices(&self) -> Vec<String>;
|
||||
|
||||
/// Get wallet info.
|
||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo>;
|
||||
|
||||
/// Generate ethereum address for a Wallet
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Self::Error>;
|
||||
|
||||
/// Open a device using `device path`
|
||||
/// Note, f - is a closure that borrows HidResult<HidDevice>
|
||||
/// HidDevice is in turn a type alias for a `c_void function pointer`
|
||||
/// For further information see:
|
||||
/// * <https://github.com/paritytech/hidapi-rs>
|
||||
/// * <https://github.com/rust-lang/libc>
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Self::Error>
|
||||
where F: Fn() -> Result<R, &'static str>;
|
||||
}
|
||||
|
||||
|
||||
/// Hardware wallet error.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
@ -144,70 +193,13 @@ pub struct HardwareWalletManager {
|
||||
trezor: Arc<trezor::Manager>,
|
||||
}
|
||||
|
||||
|
||||
impl HardwareWalletManager {
|
||||
/// Hardware wallet constructor
|
||||
pub fn new() -> Result<HardwareWalletManager, Error> {
|
||||
let usb_context_trezor = Arc::new(libusb::Context::new()?);
|
||||
let usb_context_ledger = Arc::new(libusb::Context::new()?);
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?));
|
||||
let ledger = Arc::new(ledger::Manager::new(hidapi.clone()));
|
||||
let trezor = Arc::new(trezor::Manager::new(hidapi.clone()));
|
||||
|
||||
// Subscribe to TREZOR V1
|
||||
// Note, this support only TREZOR V1 becasue TREZOR V2 has another vendorID for some reason
|
||||
// Also, we now only support one product as the second argument specifies
|
||||
usb_context_trezor.register_callback(
|
||||
Some(trezor::TREZOR_VID), Some(trezor::TREZOR_PIDS[0]), Some(USB_DEVICE_CLASS_DEVICE),
|
||||
Box::new(trezor::EventHandler::new(Arc::downgrade(&trezor))))?;
|
||||
|
||||
// Subscribe to all Ledger Devices
|
||||
// This means that we need to check that the given productID is supported
|
||||
// None => LIBUSB_HOTPLUG_MATCH_ANY, in other words that all are subscribed to
|
||||
// More info can be found: http://libusb.sourceforge.net/api-1.0/group__hotplug.html#gae6c5f1add6cc754005549c7259dc35ea
|
||||
usb_context_ledger.register_callback(
|
||||
Some(ledger::LEDGER_VID), None, Some(USB_DEVICE_CLASS_DEVICE),
|
||||
Box::new(ledger::EventHandler::new(Arc::downgrade(&ledger))))?;
|
||||
|
||||
let exiting = Arc::new(AtomicBool::new(false));
|
||||
let thread_exiting_ledger = exiting.clone();
|
||||
let thread_exiting_trezor = exiting.clone();
|
||||
let l = ledger.clone();
|
||||
let t = trezor.clone();
|
||||
|
||||
// Ledger event thread
|
||||
thread::Builder::new()
|
||||
.name("hw_wallet_ledger".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = l.update_devices() {
|
||||
debug!(target: "hw", "Ledger couldn't connect at startup, error: {}", e);
|
||||
}
|
||||
loop {
|
||||
usb_context_ledger.handle_events(Some(Duration::from_millis(500)))
|
||||
.unwrap_or_else(|e| debug!(target: "hw", "Ledger event handler error: {}", e));
|
||||
if thread_exiting_ledger.load(atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
// Trezor event thread
|
||||
thread::Builder::new()
|
||||
.name("hw_wallet_trezor".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = t.update_devices() {
|
||||
debug!(target: "hw", "Trezor couldn't connect at startup, error: {}", e);
|
||||
}
|
||||
loop {
|
||||
usb_context_trezor.handle_events(Some(Duration::from_millis(500)))
|
||||
.unwrap_or_else(|e| debug!(target: "hw", "Trezor event handler error: {}", e));
|
||||
if thread_exiting_trezor.load(atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?));
|
||||
let ledger = ledger::Manager::new(hidapi.clone(), exiting.clone())?;
|
||||
let trezor = trezor::Manager::new(hidapi.clone(), exiting.clone())?;
|
||||
|
||||
Ok(HardwareWalletManager {
|
||||
exiting: exiting,
|
||||
@ -217,6 +209,8 @@ impl HardwareWalletManager {
|
||||
}
|
||||
|
||||
/// Select key derivation path for a chain.
|
||||
/// Currently, only one hard-coded keypath is supported
|
||||
/// It is managed by `ethcore/account_provider`
|
||||
pub fn set_key_path(&self, key_path: KeyPath) {
|
||||
self.ledger.set_key_path(key_path);
|
||||
self.trezor.set_key_path(key_path);
|
||||
@ -231,24 +225,26 @@ impl HardwareWalletManager {
|
||||
}
|
||||
|
||||
/// Return a list of paths to locked hardware wallets
|
||||
/// This is only applicable to Trezor because Ledger only appears as
|
||||
/// a device when it is unlocked
|
||||
pub fn list_locked_wallets(&self) -> Result<Vec<String>, Error> {
|
||||
Ok(self.trezor.list_locked_devices())
|
||||
}
|
||||
|
||||
/// Get connected wallet info.
|
||||
pub fn wallet_info(&self, address: &Address) -> Option<WalletInfo> {
|
||||
if let Some(info) = self.ledger.device_info(address) {
|
||||
if let Some(info) = self.ledger.get_wallet(address) {
|
||||
Some(info)
|
||||
} else {
|
||||
self.trezor.device_info(address)
|
||||
self.trezor.get_wallet(address)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo, encoded_transaction: &[u8]) -> Result<Signature, Error> {
|
||||
if self.ledger.device_info(address).is_some() {
|
||||
if self.ledger.get_wallet(address).is_some() {
|
||||
Ok(self.ledger.sign_transaction(address, encoded_transaction)?)
|
||||
} else if self.trezor.device_info(address).is_some() {
|
||||
} else if self.trezor.get_wallet(address).is_some() {
|
||||
Ok(self.trezor.sign_transaction(address, t_info)?)
|
||||
} else {
|
||||
Err(Error::KeyNotFound)
|
||||
@ -256,6 +252,8 @@ impl HardwareWalletManager {
|
||||
}
|
||||
|
||||
/// Send a pin to a device at a certain path to unlock it
|
||||
/// This is only applicable to Trezor because Ledger only appears as
|
||||
/// a device when it is unlocked
|
||||
pub fn pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, Error> {
|
||||
self.trezor.pin_matrix_ack(path, pin).map_err(Error::TrezorDevice)
|
||||
}
|
||||
|
338
hw/src/trezor.rs
338
hw/src/trezor.rs
@ -19,12 +19,15 @@
|
||||
//! and https://github.com/trezor/trezor-common/blob/master/protob/protocol.md
|
||||
//! for protocol details.
|
||||
|
||||
use super::{WalletInfo, TransactionInfo, KeyPath};
|
||||
use super::{WalletInfo, TransactionInfo, KeyPath, Wallet, Device, USB_DEVICE_CLASS_DEVICE};
|
||||
|
||||
use std::cmp::{min, max};
|
||||
use std::fmt;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::thread;
|
||||
|
||||
use ethereum_types::{U256, H256, Address};
|
||||
use ethkey::Signature;
|
||||
@ -37,9 +40,9 @@ use protobuf::{Message, ProtobufEnum};
|
||||
use trezor_sys::messages::{EthereumAddress, PinMatrixAck, MessageType, EthereumTxRequest, EthereumSignTx, EthereumGetAddress, EthereumTxAck, ButtonAck};
|
||||
|
||||
/// Trezor v1 vendor ID
|
||||
pub const TREZOR_VID: u16 = 0x534c;
|
||||
const TREZOR_VID: u16 = 0x534c;
|
||||
/// Trezor product IDs
|
||||
pub const TREZOR_PIDS: [u16; 1] = [0x0001];
|
||||
const TREZOR_PIDS: [u16; 1] = [0x0001];
|
||||
|
||||
const ETH_DERIVATION_PATH: [u32; 5] = [0x8000002C, 0x8000003C, 0x80000000, 0, 0]; // m/44'/60'/0'/0/0
|
||||
const ETC_DERIVATION_PATH: [u32; 5] = [0x8000002C, 0x8000003D, 0x80000000, 0, 0]; // m/44'/61'/0'/0/0
|
||||
@ -94,12 +97,6 @@ pub struct Manager {
|
||||
key_path: RwLock<KeyPath>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Device {
|
||||
path: String,
|
||||
info: WalletInfo,
|
||||
}
|
||||
|
||||
/// HID Version used for the Trezor device
|
||||
enum HidVersion {
|
||||
V1,
|
||||
@ -108,102 +105,42 @@ enum HidVersion {
|
||||
|
||||
impl Manager {
|
||||
/// Create a new instance.
|
||||
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>) -> Manager {
|
||||
Manager {
|
||||
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>, exiting: Arc<AtomicBool>) -> Result<Arc<Manager>, libusb::Error> {
|
||||
let manager = Arc::new(Manager {
|
||||
usb: hidapi,
|
||||
devices: RwLock::new(Vec::new()),
|
||||
locked_devices: RwLock::new(Vec::new()),
|
||||
key_path: RwLock::new(KeyPath::Ethereum),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// Re-populate device list
|
||||
pub fn update_devices(&self) -> Result<usize, Error> {
|
||||
let mut usb = self.usb.lock();
|
||||
usb.refresh_devices();
|
||||
let devices = usb.devices();
|
||||
let mut new_devices = Vec::new();
|
||||
let mut locked_devices = Vec::new();
|
||||
let mut error = None;
|
||||
for usb_device in devices {
|
||||
let is_trezor = usb_device.vendor_id == TREZOR_VID;
|
||||
let is_supported_product = TREZOR_PIDS.contains(&usb_device.product_id);
|
||||
let is_valid = usb_device.usage_page == 0xFF00 || usb_device.interface_number == 0;
|
||||
let usb_context = Arc::new(libusb::Context::new()?);
|
||||
let m = manager.clone();
|
||||
|
||||
trace!(
|
||||
"Checking device: {:?}, trezor: {:?}, prod: {:?}, valid: {:?}",
|
||||
usb_device,
|
||||
is_trezor,
|
||||
is_supported_product,
|
||||
is_valid,
|
||||
);
|
||||
if !is_trezor || !is_supported_product || !is_valid {
|
||||
continue;
|
||||
}
|
||||
match self.read_device_info(&usb, &usb_device) {
|
||||
Ok(device) => new_devices.push(device),
|
||||
Err(Error::LockedDevice(path)) => locked_devices.push(path.to_string()),
|
||||
Err(e) => {
|
||||
warn!("Error reading device: {:?}", e);
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let count = new_devices.len();
|
||||
trace!("Got devices: {:?}, closed: {:?}", new_devices, locked_devices);
|
||||
*self.devices.write() = new_devices;
|
||||
*self.locked_devices.write() = locked_devices;
|
||||
match error {
|
||||
Some(e) => Err(e),
|
||||
None => Ok(count),
|
||||
}
|
||||
}
|
||||
// Subscribe to TREZOR V1
|
||||
// Note, this support only TREZOR V1 because TREZOR V2 has a different vendorID for some reason
|
||||
// Also, we now only support one product as the second argument specifies
|
||||
usb_context.register_callback(
|
||||
Some(TREZOR_VID), Some(TREZOR_PIDS[0]), Some(USB_DEVICE_CLASS_DEVICE),
|
||||
Box::new(EventHandler::new(Arc::downgrade(&manager))))?;
|
||||
|
||||
fn read_device_info(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or("Unknown".to_owned());
|
||||
match self.get_address(&handle) {
|
||||
Ok(Some(addr)) => {
|
||||
Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name: name,
|
||||
manufacturer: manufacturer,
|
||||
serial: serial,
|
||||
address: addr,
|
||||
},
|
||||
// Trezor event thread
|
||||
thread::Builder::new()
|
||||
.name("hw_wallet_trezor".to_string())
|
||||
.spawn(move || {
|
||||
if let Err(e) = m.update_devices() {
|
||||
debug!(target: "hw", "Trezor couldn't connect at startup, error: {}", e);
|
||||
}
|
||||
loop {
|
||||
usb_context.handle_events(Some(Duration::from_millis(500)))
|
||||
.unwrap_or_else(|e| debug!(target: "hw", "Trezor event handler error: {}", e));
|
||||
if exiting.load(atomic::Ordering::Acquire) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Ok(None) => Err(Error::LockedDevice(dev_info.path.clone())),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
.ok();
|
||||
|
||||
/// Select key derivation path for a known chain.
|
||||
pub fn set_key_path(&self, key_path: KeyPath) {
|
||||
*self.key_path.write() = key_path;
|
||||
}
|
||||
|
||||
/// List connected wallets. This only returns wallets that are ready to be used.
|
||||
pub fn list_devices(&self) -> Vec<WalletInfo> {
|
||||
self.devices.read().iter().map(|d| d.info.clone()).collect()
|
||||
}
|
||||
|
||||
pub fn list_locked_devices(&self) -> Vec<String> {
|
||||
(*self.locked_devices.read()).clone()
|
||||
}
|
||||
|
||||
/// Get wallet info.
|
||||
pub fn device_info(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
Ok(manager)
|
||||
}
|
||||
|
||||
pub fn pin_matrix_ack(&self, device_path: &str, pin: &str) -> Result<bool, Error> {
|
||||
@ -227,61 +164,6 @@ impl Manager {
|
||||
unlocked
|
||||
}
|
||||
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Error> {
|
||||
let typ = MessageType::MessageType_EthereumGetAddress;
|
||||
let mut message = EthereumGetAddress::new();
|
||||
match *self.key_path.read() {
|
||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
||||
}
|
||||
message.set_show_display(false);
|
||||
self.send_device_message(&device, &typ, &message)?;
|
||||
|
||||
let (resp_type, bytes) = self.read_device_response(&device)?;
|
||||
match resp_type {
|
||||
MessageType::MessageType_EthereumAddress => {
|
||||
let response: EthereumAddress = protobuf::core::parse_from_bytes(&bytes)?;
|
||||
Ok(Some(From::from(response.get_address())))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sign transaction data with wallet managing `address`.
|
||||
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo) -> Result<Signature, Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
let msg_type = MessageType::MessageType_EthereumSignTx;
|
||||
let mut message = EthereumSignTx::new();
|
||||
match *self.key_path.read() {
|
||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
||||
}
|
||||
message.set_nonce(self.u256_to_be_vec(&t_info.nonce));
|
||||
message.set_gas_limit(self.u256_to_be_vec(&t_info.gas_limit));
|
||||
message.set_gas_price(self.u256_to_be_vec(&t_info.gas_price));
|
||||
message.set_value(self.u256_to_be_vec(&t_info.value));
|
||||
|
||||
match t_info.to {
|
||||
Some(addr) => {
|
||||
message.set_to(addr.to_vec())
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
let first_chunk_length = min(t_info.data.len(), 1024);
|
||||
let chunk = &t_info.data[0..first_chunk_length];
|
||||
message.set_data_initial_chunk(chunk.to_vec());
|
||||
message.set_data_length(t_info.data.len() as u32);
|
||||
if let Some(c_id) = t_info.chain_id {
|
||||
message.set_chain_id(c_id as u32);
|
||||
}
|
||||
|
||||
self.send_device_message(&handle, &msg_type, &message)?;
|
||||
|
||||
self.signing_loop(&handle, &t_info.chain_id, &t_info.data[first_chunk_length..])
|
||||
}
|
||||
|
||||
fn u256_to_be_vec(&self, val: &U256) -> Vec<u8> {
|
||||
let mut buf = [0u8; 32];
|
||||
@ -400,6 +282,152 @@ impl Manager {
|
||||
}
|
||||
}
|
||||
|
||||
impl <'a>Wallet<'a> for Manager {
|
||||
type Error = Error;
|
||||
type Transaction = &'a TransactionInfo;
|
||||
|
||||
fn sign_transaction(&self, address: &Address, t_info: Self::Transaction) ->
|
||||
Result<Signature, Error> {
|
||||
let usb = self.usb.lock();
|
||||
let devices = self.devices.read();
|
||||
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
|
||||
let handle = self.open_path(|| usb.open_path(&device.path))?;
|
||||
let msg_type = MessageType::MessageType_EthereumSignTx;
|
||||
let mut message = EthereumSignTx::new();
|
||||
match *self.key_path.read() {
|
||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
||||
}
|
||||
message.set_nonce(self.u256_to_be_vec(&t_info.nonce));
|
||||
message.set_gas_limit(self.u256_to_be_vec(&t_info.gas_limit));
|
||||
message.set_gas_price(self.u256_to_be_vec(&t_info.gas_price));
|
||||
message.set_value(self.u256_to_be_vec(&t_info.value));
|
||||
|
||||
match t_info.to {
|
||||
Some(addr) => {
|
||||
message.set_to(addr.to_vec())
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
let first_chunk_length = min(t_info.data.len(), 1024);
|
||||
let chunk = &t_info.data[0..first_chunk_length];
|
||||
message.set_data_initial_chunk(chunk.to_vec());
|
||||
message.set_data_length(t_info.data.len() as u32);
|
||||
if let Some(c_id) = t_info.chain_id {
|
||||
message.set_chain_id(c_id as u32);
|
||||
}
|
||||
|
||||
self.send_device_message(&handle, &msg_type, &message)?;
|
||||
|
||||
self.signing_loop(&handle, &t_info.chain_id, &t_info.data[first_chunk_length..])
|
||||
}
|
||||
|
||||
fn set_key_path(&self, key_path: KeyPath) {
|
||||
*self.key_path.write() = key_path;
|
||||
}
|
||||
|
||||
fn update_devices(&self) -> Result<usize, Error> {
|
||||
let mut usb = self.usb.lock();
|
||||
usb.refresh_devices();
|
||||
let devices = usb.devices();
|
||||
let mut new_devices = Vec::new();
|
||||
let mut locked_devices = Vec::new();
|
||||
let mut error = None;
|
||||
for usb_device in devices {
|
||||
let is_trezor = usb_device.vendor_id == TREZOR_VID;
|
||||
let is_supported_product = TREZOR_PIDS.contains(&usb_device.product_id);
|
||||
let is_valid = usb_device.usage_page == 0xFF00 || usb_device.interface_number == 0;
|
||||
|
||||
trace!(
|
||||
"Checking device: {:?}, trezor: {:?}, prod: {:?}, valid: {:?}",
|
||||
usb_device,
|
||||
is_trezor,
|
||||
is_supported_product,
|
||||
is_valid,
|
||||
);
|
||||
if !is_trezor || !is_supported_product || !is_valid {
|
||||
continue;
|
||||
}
|
||||
match self.read_device(&usb, &usb_device) {
|
||||
Ok(device) => new_devices.push(device),
|
||||
Err(Error::LockedDevice(path)) => locked_devices.push(path.to_string()),
|
||||
Err(e) => {
|
||||
warn!("Error reading device: {:?}", e);
|
||||
error = Some(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let count = new_devices.len();
|
||||
trace!("Got devices: {:?}, closed: {:?}", new_devices, locked_devices);
|
||||
*self.devices.write() = new_devices;
|
||||
*self.locked_devices.write() = locked_devices;
|
||||
match error {
|
||||
Some(e) => Err(e),
|
||||
None => Ok(count),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_device(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
||||
let handle = self.open_path(|| usb.open_path(&dev_info.path))?;
|
||||
let manufacturer = dev_info.manufacturer_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let name = dev_info.product_string.clone().unwrap_or("Unknown".to_owned());
|
||||
let serial = dev_info.serial_number.clone().unwrap_or("Unknown".to_owned());
|
||||
match self.get_address(&handle) {
|
||||
Ok(Some(addr)) => {
|
||||
Ok(Device {
|
||||
path: dev_info.path.clone(),
|
||||
info: WalletInfo {
|
||||
name: name,
|
||||
manufacturer: manufacturer,
|
||||
serial: serial,
|
||||
address: addr,
|
||||
},
|
||||
})
|
||||
}
|
||||
Ok(None) => Err(Error::LockedDevice(dev_info.path.clone())),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn list_devices(&self) -> Vec<WalletInfo> {
|
||||
self.devices.read().iter().map(|d| d.info.clone()).collect()
|
||||
}
|
||||
|
||||
fn list_locked_devices(&self) -> Vec<String> {
|
||||
(*self.locked_devices.read()).clone()
|
||||
}
|
||||
|
||||
fn get_wallet(&self, address: &Address) -> Option<WalletInfo> {
|
||||
self.devices.read().iter().find(|d| &d.info.address == address).map(|d| d.info.clone())
|
||||
}
|
||||
|
||||
fn get_address(&self, device: &hidapi::HidDevice) -> Result<Option<Address>, Error> {
|
||||
let typ = MessageType::MessageType_EthereumGetAddress;
|
||||
let mut message = EthereumGetAddress::new();
|
||||
match *self.key_path.read() {
|
||||
KeyPath::Ethereum => message.set_address_n(ETH_DERIVATION_PATH.to_vec()),
|
||||
KeyPath::EthereumClassic => message.set_address_n(ETC_DERIVATION_PATH.to_vec()),
|
||||
}
|
||||
message.set_show_display(false);
|
||||
self.send_device_message(&device, &typ, &message)?;
|
||||
|
||||
let (resp_type, bytes) = self.read_device_response(&device)?;
|
||||
match resp_type {
|
||||
MessageType::MessageType_EthereumAddress => {
|
||||
let response: EthereumAddress = protobuf::core::parse_from_bytes(&bytes)?;
|
||||
Ok(Some(From::from(response.get_address())))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn open_path<R, F>(&self, f: F) -> Result<R, Error>
|
||||
where F: Fn() -> Result<R, &'static str>
|
||||
{
|
||||
f().map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to connect to the device using polling in at most the time specified by the `timeout`
|
||||
fn try_connect_polling(trezor: Arc<Manager>, duration: Duration) -> bool {
|
||||
let start_time = Instant::now();
|
||||
@ -412,11 +440,11 @@ fn try_connect_polling(trezor: Arc<Manager>, duration: Duration) -> bool {
|
||||
}
|
||||
|
||||
/// Trezor event handler
|
||||
/// A separate thread is handeling incoming events
|
||||
/// A separate thread is handling incoming events
|
||||
///
|
||||
/// Note, that this run to completion and race-conditions can't occur but this can
|
||||
/// therefore starve other events for being process with a spinlock or similar
|
||||
pub struct EventHandler {
|
||||
struct EventHandler {
|
||||
trezor: Weak<Manager>,
|
||||
}
|
||||
|
||||
@ -455,10 +483,10 @@ fn test_signature() {
|
||||
use ethereum_types::{H160, H256, U256};
|
||||
|
||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().unwrap()));
|
||||
let manager = Manager::new(hidapi.clone());
|
||||
let manager = Manager::new(hidapi.clone(), Arc::new(AtomicBool::new(false))).unwrap();
|
||||
let addr: Address = H160::from("some_addr");
|
||||
|
||||
manager.update_devices().unwrap();
|
||||
assert_eq!(try_connect_polling(manager.clone(), Duration::from_millis(500)), true);
|
||||
|
||||
let t_info = TransactionInfo {
|
||||
nonce: U256::from(1),
|
||||
|
Loading…
Reference in New Issue
Block a user