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
393
hw/src/ledger.rs
393
hw/src/ledger.rs
File diff suppressed because one or more lines are too long
132
hw/src/lib.rs
132
hw/src/lib.rs
@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
//! Hardware wallet management.
|
//! Hardware wallet management.
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#[warn(missing_docs)]
|
||||||
|
#[warn(warnings)]
|
||||||
|
|
||||||
extern crate ethereum_types;
|
extern crate ethereum_types;
|
||||||
extern crate ethkey;
|
extern crate ethkey;
|
||||||
@ -38,12 +39,60 @@ use std::fmt;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic;
|
use std::sync::atomic;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
|
||||||
use ethereum_types::U256;
|
use ethereum_types::U256;
|
||||||
|
|
||||||
const USB_DEVICE_CLASS_DEVICE: u8 = 0;
|
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.
|
/// Hardware wallet error.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -144,70 +193,13 @@ pub struct HardwareWalletManager {
|
|||||||
trezor: Arc<trezor::Manager>,
|
trezor: Arc<trezor::Manager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl HardwareWalletManager {
|
impl HardwareWalletManager {
|
||||||
/// Hardware wallet constructor
|
/// Hardware wallet constructor
|
||||||
pub fn new() -> Result<HardwareWalletManager, Error> {
|
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 exiting = Arc::new(AtomicBool::new(false));
|
||||||
let thread_exiting_ledger = exiting.clone();
|
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().map_err(|e| Error::Hid(e.to_string().clone()))?));
|
||||||
let thread_exiting_trezor = exiting.clone();
|
let ledger = ledger::Manager::new(hidapi.clone(), exiting.clone())?;
|
||||||
let l = ledger.clone();
|
let trezor = trezor::Manager::new(hidapi.clone(), exiting.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();
|
|
||||||
|
|
||||||
Ok(HardwareWalletManager {
|
Ok(HardwareWalletManager {
|
||||||
exiting: exiting,
|
exiting: exiting,
|
||||||
@ -217,6 +209,8 @@ impl HardwareWalletManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Select key derivation path for a chain.
|
/// 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) {
|
pub fn set_key_path(&self, key_path: KeyPath) {
|
||||||
self.ledger.set_key_path(key_path);
|
self.ledger.set_key_path(key_path);
|
||||||
self.trezor.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
|
/// 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> {
|
pub fn list_locked_wallets(&self) -> Result<Vec<String>, Error> {
|
||||||
Ok(self.trezor.list_locked_devices())
|
Ok(self.trezor.list_locked_devices())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get connected wallet info.
|
/// Get connected wallet info.
|
||||||
pub fn wallet_info(&self, address: &Address) -> Option<WalletInfo> {
|
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)
|
Some(info)
|
||||||
} else {
|
} else {
|
||||||
self.trezor.device_info(address)
|
self.trezor.get_wallet(address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign transaction data with wallet managing `address`.
|
/// Sign transaction data with wallet managing `address`.
|
||||||
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo, encoded_transaction: &[u8]) -> Result<Signature, Error> {
|
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)?)
|
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)?)
|
Ok(self.trezor.sign_transaction(address, t_info)?)
|
||||||
} else {
|
} else {
|
||||||
Err(Error::KeyNotFound)
|
Err(Error::KeyNotFound)
|
||||||
@ -256,6 +252,8 @@ impl HardwareWalletManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a pin to a device at a certain path to unlock it
|
/// 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> {
|
pub fn pin_matrix_ack(&self, path: &str, pin: &str) -> Result<bool, Error> {
|
||||||
self.trezor.pin_matrix_ack(path, pin).map_err(Error::TrezorDevice)
|
self.trezor.pin_matrix_ack(path, pin).map_err(Error::TrezorDevice)
|
||||||
}
|
}
|
||||||
|
340
hw/src/trezor.rs
340
hw/src/trezor.rs
@ -19,12 +19,15 @@
|
|||||||
//! and https://github.com/trezor/trezor-common/blob/master/protob/protocol.md
|
//! and https://github.com/trezor/trezor-common/blob/master/protob/protocol.md
|
||||||
//! for protocol details.
|
//! 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::cmp::{min, max};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
|
use std::sync::atomic;
|
||||||
|
use std::sync::atomic::AtomicBool;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
use ethereum_types::{U256, H256, Address};
|
use ethereum_types::{U256, H256, Address};
|
||||||
use ethkey::Signature;
|
use ethkey::Signature;
|
||||||
@ -37,9 +40,9 @@ use protobuf::{Message, ProtobufEnum};
|
|||||||
use trezor_sys::messages::{EthereumAddress, PinMatrixAck, MessageType, EthereumTxRequest, EthereumSignTx, EthereumGetAddress, EthereumTxAck, ButtonAck};
|
use trezor_sys::messages::{EthereumAddress, PinMatrixAck, MessageType, EthereumTxRequest, EthereumSignTx, EthereumGetAddress, EthereumTxAck, ButtonAck};
|
||||||
|
|
||||||
/// Trezor v1 vendor ID
|
/// Trezor v1 vendor ID
|
||||||
pub const TREZOR_VID: u16 = 0x534c;
|
const TREZOR_VID: u16 = 0x534c;
|
||||||
/// Trezor product IDs
|
/// 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 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
|
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>,
|
key_path: RwLock<KeyPath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Device {
|
|
||||||
path: String,
|
|
||||||
info: WalletInfo,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// HID Version used for the Trezor device
|
/// HID Version used for the Trezor device
|
||||||
enum HidVersion {
|
enum HidVersion {
|
||||||
V1,
|
V1,
|
||||||
@ -108,102 +105,42 @@ enum HidVersion {
|
|||||||
|
|
||||||
impl Manager {
|
impl Manager {
|
||||||
/// Create a new instance.
|
/// Create a new instance.
|
||||||
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>) -> Manager {
|
pub fn new(hidapi: Arc<Mutex<hidapi::HidApi>>, exiting: Arc<AtomicBool>) -> Result<Arc<Manager>, libusb::Error> {
|
||||||
Manager {
|
let manager = Arc::new(Manager {
|
||||||
usb: hidapi,
|
usb: hidapi,
|
||||||
devices: RwLock::new(Vec::new()),
|
devices: RwLock::new(Vec::new()),
|
||||||
locked_devices: RwLock::new(Vec::new()),
|
locked_devices: RwLock::new(Vec::new()),
|
||||||
key_path: RwLock::new(KeyPath::Ethereum),
|
key_path: RwLock::new(KeyPath::Ethereum),
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
/// Re-populate device list
|
let usb_context = Arc::new(libusb::Context::new()?);
|
||||||
pub fn update_devices(&self) -> Result<usize, Error> {
|
let m = manager.clone();
|
||||||
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!(
|
// Subscribe to TREZOR V1
|
||||||
"Checking device: {:?}, trezor: {:?}, prod: {:?}, valid: {:?}",
|
// Note, this support only TREZOR V1 because TREZOR V2 has a different vendorID for some reason
|
||||||
usb_device,
|
// Also, we now only support one product as the second argument specifies
|
||||||
is_trezor,
|
usb_context.register_callback(
|
||||||
is_supported_product,
|
Some(TREZOR_VID), Some(TREZOR_PIDS[0]), Some(USB_DEVICE_CLASS_DEVICE),
|
||||||
is_valid,
|
Box::new(EventHandler::new(Arc::downgrade(&manager))))?;
|
||||||
);
|
|
||||||
if !is_trezor || !is_supported_product || !is_valid {
|
// Trezor event thread
|
||||||
continue;
|
thread::Builder::new()
|
||||||
}
|
.name("hw_wallet_trezor".to_string())
|
||||||
match self.read_device_info(&usb, &usb_device) {
|
.spawn(move || {
|
||||||
Ok(device) => new_devices.push(device),
|
if let Err(e) = m.update_devices() {
|
||||||
Err(Error::LockedDevice(path)) => locked_devices.push(path.to_string()),
|
debug!(target: "hw", "Trezor couldn't connect at startup, error: {}", e);
|
||||||
Err(e) => {
|
|
||||||
warn!("Error reading device: {:?}", e);
|
|
||||||
error = Some(e);
|
|
||||||
}
|
}
|
||||||
}
|
loop {
|
||||||
}
|
usb_context.handle_events(Some(Duration::from_millis(500)))
|
||||||
let count = new_devices.len();
|
.unwrap_or_else(|e| debug!(target: "hw", "Trezor event handler error: {}", e));
|
||||||
trace!("Got devices: {:?}, closed: {:?}", new_devices, locked_devices);
|
if exiting.load(atomic::Ordering::Acquire) {
|
||||||
*self.devices.write() = new_devices;
|
break;
|
||||||
*self.locked_devices.write() = locked_devices;
|
}
|
||||||
match error {
|
}
|
||||||
Some(e) => Err(e),
|
})
|
||||||
None => Ok(count),
|
.ok();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_device_info(&self, usb: &hidapi::HidApi, dev_info: &hidapi::HidDeviceInfo) -> Result<Device, Error> {
|
Ok(manager)
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pin_matrix_ack(&self, device_path: &str, pin: &str) -> Result<bool, Error> {
|
pub fn pin_matrix_ack(&self, device_path: &str, pin: &str) -> Result<bool, Error> {
|
||||||
@ -227,61 +164,6 @@ impl Manager {
|
|||||||
unlocked
|
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> {
|
fn u256_to_be_vec(&self, val: &U256) -> Vec<u8> {
|
||||||
let mut buf = [0u8; 32];
|
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`
|
// 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 {
|
fn try_connect_polling(trezor: Arc<Manager>, duration: Duration) -> bool {
|
||||||
let start_time = Instant::now();
|
let start_time = Instant::now();
|
||||||
@ -412,11 +440,11 @@ fn try_connect_polling(trezor: Arc<Manager>, duration: Duration) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Trezor event handler
|
/// 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
|
/// 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
|
/// therefore starve other events for being process with a spinlock or similar
|
||||||
pub struct EventHandler {
|
struct EventHandler {
|
||||||
trezor: Weak<Manager>,
|
trezor: Weak<Manager>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,10 +483,10 @@ fn test_signature() {
|
|||||||
use ethereum_types::{H160, H256, U256};
|
use ethereum_types::{H160, H256, U256};
|
||||||
|
|
||||||
let hidapi = Arc::new(Mutex::new(hidapi::HidApi::new().unwrap()));
|
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");
|
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 {
|
let t_info = TransactionInfo {
|
||||||
nonce: U256::from(1),
|
nonce: U256::from(1),
|
||||||
|
Loading…
Reference in New Issue
Block a user