-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Conversation
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.
It looks like @folsen hasn'signed our Contributor License Agreement, yet.
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 Many thanks, Parity Technologies CLA Bot |
js/src/jsonrpc/interfaces/parity.js
Outdated
@@ -360,6 +360,32 @@ export default { | |||
} | |||
}, | |||
|
|||
keepkey: { |
There was a problem hiding this comment.
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
)
js/src/modals/PinMatrix/pinMatrix.js
Outdated
values={ { | ||
enterPin: ( | ||
<span> | ||
Please enter the <i>pin</i> for your { device.manufacturer } hardware wallet |
There was a problem hiding this comment.
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)
[clabot:check] |
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" } |
There was a problem hiding this comment.
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}; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
hw/src/trezor/gen/mod.rs
Outdated
@@ -0,0 +1,2 @@ | |||
pub mod types; | |||
pub mod messages; |
There was a problem hiding this comment.
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? :)
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
needs resolving. and another review eventually... |
Sorry, forgot about this. I will submit the fixes in a separate PR. |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
rpc/src/v1/impls/parity.rs
Outdated
@@ -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> { |
There was a problem hiding this comment.
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.
# Conflicts: # Cargo.lock # hardwareStore.js
It’s split into two more generic endpoints that should be suitable for any hardware wallets with the same behavior to sit behind.
Needed to make tests pass
There was a problem hiding this 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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
js/src/mobx/hardwareStore.js
Outdated
return this._api.parity | ||
.lockedHardwareAccountsInfo() | ||
.then((paths) => { | ||
this.pinMatrixRequest = paths.map((path) => { |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
This PR addresses #4500 and extends what already exists for Ledgers to work for Trezor devices as well. Among other things it adds