Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Trezor Support #6403

Merged
merged 44 commits into from
Sep 14, 2017
Merged

Trezor Support #6403

merged 44 commits into from
Sep 14, 2017

Conversation

folsen
Copy link
Contributor

@folsen folsen commented Aug 28, 2017

This PR addresses #4500 and extends what already exists for Ledgers to work for Trezor devices as well. Among other things it adds

  • Pin Matrix modal to the UI that pops up when a device is plugged in
  • RPC method to send requests to the Trezor device (such as pin)
  • Library code to talk to the Trezor over USB HID

folsen added 16 commits August 23, 2017 19:34
The keepkey PinMatrix modal needs to be the same for Trezor, but we
should probably try to keep it general since it can be used for both.
This is a result of much trial-and-error and a couple of dead-ends in
how to communicate and wire everything up.

Code here is still a bit WIP with lots of debug prints and stuff.

The test works though, it is possible to sign a transaction.
This is kind of ugly and needs some cleanup and generalization. I’ve
just copy-pasted some things to bring in the trezor wallets. I’ve also
had to add a lock to the USB API so that only one thing talks to the
USB at once.
We need to be able to get “locked” devices from the frontend to figure
out if we’re going to display the PinMatrix or not. Then we need to be
able to send a pin to a device.
There’s a bug somewhere here because signing a transaction fails if you
take too long to press the confirm button on the device.
As my fork has been merged in.
Turns out the Trezor was adjusting the v part of the signature, and
we’re already doing that so it was done twice.
BE-encoded U256 is almost the same as RLP encoded without the
size-byte, except for <u8 sized values. What’s really done is
BE-encoded U256 and then left-trimmed to the smallest size. Kind of
obvious in hindsight.
The device will not repeat a ButtonRequest when you read from it, so
you need to have a blocking `read` for whatever amount of time that you
want to give the user to click. You could also have a shorter timeout
but keep retrying for some amount of time, but it would amount to the
same thing.
@folsen folsen added A0-pleasereview 🤓 Pull request needs code review. M4-core ⛓ Core client code / Rust. M7-ui labels Aug 28, 2017
@parity-cla-bot
Copy link

It looks like @folsen hasn'signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement at the following URL: https://cla.parity.io

Once you've signed, plesae reply to this thread with [clabot:check] to prove it.

Many thanks,

Parity Technologies CLA Bot

@@ -360,6 +360,32 @@ export default {
}
},

keepkey: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does not match up with the method exposed (e.g. parity_trezor)

values={ {
enterPin: (
<span>
Please enter the <i>pin</i> for your { device.manufacturer } hardware wallet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be FormattedMessage for translation as well (as used lower down)

@folsen
Copy link
Contributor Author

folsen commented Aug 28, 2017

[clabot:check]

@parity-cla-bot
Copy link

It looks like @folsen signed our Contributor License Agreement. 👍

Many thanks,

Parity Technologies CLA Bot

hw/Cargo.toml Outdated
hidapi = { git = "https://github.com/paritytech/hidapi-rs" }
libusb = { git = "https://github.com/paritytech/libusb-rs" }
ethkey = { path = "../ethkey" }
ethcore-util = { path = "../util" }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the only two imports from util are Bytes and U256 it would be better to avoid it. Currently, importing util brings to many redundant dependencies (like rocksdb), and we want to split it into smaller modules soon.

hw/src/lib.rs Outdated
use ethkey::{Address, Signature};

pub use ledger::KeyPath;
use util::{Bytes, U256};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u can write use bigint::bigint::U256; instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I didn't know it was there too, will change!

/// USB error.
Usb(libusb::Error),
/// Hardware wallet not found for specified key.
KeyNotFound,
}

/// Hardware waller information.
/// This is the transaction info we need to supply to Trezor message. It's more
/// or less a duplicate of ethcore::transaction::Transaction, but we can't
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I like that this is a separate structure. I'm just not sure, why is the hardwallet module a dependency of ethcore? I know, that you are not the author of this, but do you know if it could be easily separated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently ethcore/src/account_provider/mod.rs imports the following from the hardware wallet crate:

use hardware_wallet::{Error as HardwareError, HardwareWalletManager, KeyPath, TransactionInfo};

and it's using it for providing accounts given by hardware wallets as well as signing transactions with hardware wallets (the sign_with_hardware function). I don't think you could remove the dependency without abstracting out both account-fetching and tx-signing to their own crates.

Copy link
Contributor

@rphmeier rphmeier Aug 29, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those things probably can be taken out into their own crates anyway. I would suggest making a single crate for account management, which encompasses both software and hardware wallets. obviously not for this PR, but further down the line

@@ -0,0 +1,2 @@
pub mod types;
pub mod messages;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible to generate those files at compile time rather than committing 25k loc? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doubling request, this code should be committed only if compile-time generation is absolutely impossible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While not impossible, there is no existing way to do it. Down the line we could write a macro to do some compile-time generation, but it's pretty hard to do without requiring the protobuf C-library from google. An acceptable intermediate solution that I've now pushed is to put all the generated code in the trezor-sys crate and import what we need from there.

@5chdn 5chdn added the B7-releasenotes 📜 Changes should be mentioned in the release notes of the next minor version release. label Aug 29, 2017
@folsen folsen added A0-pleasereview 🤓 Pull request needs code review. and removed A3-inprogress ⏳ Pull request is in progress. No review needed at this stage. labels Sep 11, 2017
@gavofyork
Copy link
Contributor

needs resolving. and another review eventually...

@tomusdrw
Copy link
Collaborator

Sorry, forgot about this. I will submit the fixes in a separate PR.

Copy link
Collaborator

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few more grumbles regarding the RPC interface.

hw/src/trezor.rs Outdated
pub fn message(&self, message_type: &TrezorMessageType, device_path: &Option<String>, message: &Option<String>) -> Result<String, Error> {
match *message_type {
TrezorMessageType::GetDevices => {
serde_json::to_string(&self.closed_devices).map_err(Error::SerdeError)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would really prefer to have a serializable enum return type as well, passing the strings around feels a bit hacky.
TBH since we only have two possible messages why not split it into separate methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's what I was asking above as well. If we change it to two methods, then getLockedDevices could be lockedHardwareAccountsInfo to match with the hardwareAccountsInfo method and a hardwarePinMatrixAck method (that could live under parity_accounts to be more protected than just getting a list of locked devices).

That would also solve the "enum problem" by removing it altogether. I ended up not doing an enum return type because the workaround for lack of impl Trait felt hackier than returning a string.

If you agree then I think I'll split it into two methods and remove this whole message-passing thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, makes sense!

hw/src/trezor.rs Outdated
pub enum TrezorMessageType {
/// Get a list of locked devices.
#[serde(rename="getDevices")]
GetDevices,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetLockedDevices might be a better name to distinguish it from hardwareAccounts that we already have (and where trezor shows up as well after it's unlocked).

hw/src/trezor.rs Outdated
GetDevices,
/// Supply a pin for the Pin Matrix Ack
#[serde(rename="pinMatrixAck")]
PinMatrixAck,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the parameters (device and pin) are required only for this variant why not encode them here in the enum?

@@ -151,6 +152,11 @@ impl<C, M, U> Parity for ParityClient<C, M, U> where
)
}

fn trezor(&self, message_type: TrezorMessageType, device_path: Option<String>, message: Option<String>) -> Result<String, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be moved to parity_accounts to prevent unauthorized access.

@tomusdrw tomusdrw added A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. and removed A0-pleasereview 🤓 Pull request needs code review. labels Sep 12, 2017
@folsen folsen added A0-pleasereview 🤓 Pull request needs code review. and removed A5-grumble 🔥 Pull request has minor issues that must be addressed before merging. labels Sep 13, 2017
Needed to make tests pass
Copy link
Collaborator

@tomusdrw tomusdrw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Few last grumbles regarding lock order.

hw/src/ledger.rs Outdated
}

/// Sign transaction data with wallet managing `address`.
pub fn sign_transaction(&self, address: &Address, data: &[u8]) -> Result<Signature, Error> {
let device = self.devices.iter().find(|d| &d.info.address == address)
.ok_or(Error::KeyNotFound)?;
let devices = self.devices.read();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap the lock order to prevent deadlock.

hw/src/trezor.rs Outdated
pub fn sign_transaction(&self, address: &Address, t_info: &TransactionInfo) -> Result<Signature, Error> {
let devices = self.devices.read();
let device = devices.iter().find(|d| &d.info.address == address).ok_or(Error::KeyNotFound)?;
let usb = self.usb.lock();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap the lock order to prevent deadlock.

return this._api.parity
.lockedHardwareAccountsInfo()
.then((paths) => {
this.pinMatrixRequest = paths.map((path) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As @jacogr pointed out in one of my requests Mobx may not update the listeners in case the assignment doesn't happen in an action:

@action
setPinMatrixRequest= (req) => {
  this.pinMatrixRequest = req
};

[...]
.then((paths) => {
  this.setPinMatrixRequest(paths.map(...));
});

@@ -55,6 +58,21 @@ export default class HardwareStore {
}, HW_SCAN_INTERVAL);
}

scanTrezor () {
return this._api.parity
.lockedHardwareAccountsInfo()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved to pubsub API, but we can keep it for a separate PR.

@tomusdrw tomusdrw added A8-looksgood 🦄 Pull request is reviewed well. and removed A0-pleasereview 🤓 Pull request needs code review. labels Sep 14, 2017
@gavofyork gavofyork merged commit 75b6a31 into master Sep 14, 2017
@gavofyork gavofyork deleted the fh-4500-trezor-support branch September 14, 2017 17:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
A8-looksgood 🦄 Pull request is reviewed well. B7-releasenotes 📜 Changes should be mentioned in the release notes of the next minor version release. M4-core ⛓ Core client code / Rust.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants