From 6ec81352560e3e4268855115eda9d78f6e27875f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Thu, 16 Feb 2017 15:36:44 +0200 Subject: accounts/usbwallet, vendor: use hidapi instead of libusb directly --- accounts/usbwallet/ledger_wallet.go | 97 +++++++++---------------------------- 1 file changed, 22 insertions(+), 75 deletions(-) (limited to 'accounts/usbwallet/ledger_wallet.go') diff --git a/accounts/usbwallet/ledger_wallet.go b/accounts/usbwallet/ledger_wallet.go index a667f580a..235086d1e 100644 --- a/accounts/usbwallet/ledger_wallet.go +++ b/accounts/usbwallet/ledger_wallet.go @@ -18,8 +18,6 @@ // wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo: // https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc -// +build !ios - package usbwallet import ( @@ -39,7 +37,7 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" - "github.com/karalabe/gousb/usb" + "github.com/karalabe/hid" "golang.org/x/net/context" ) @@ -74,22 +72,22 @@ const ( ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address ) -// errReplyInvalidHeader is the error message returned by a Ledfer data exchange +// errReplyInvalidHeader is the error message returned by a Ledger data exchange // if the device replies with a mismatching header. This usually means the device // is in browser mode. var errReplyInvalidHeader = errors.New("invalid reply header") +// errInvalidVersionReply is the error message returned by a Ledger version retrieval +// when a response does arrive, but it does not contain the expected data. +var errInvalidVersionReply = errors.New("invalid version reply") + // ledgerWallet represents a live USB Ledger hardware wallet. type ledgerWallet struct { - context *usb.Context // USB context to interface libusb through - hardwareID deviceID // USB identifiers to identify this device type - locationID uint16 // USB bus and address to identify this device instance - url *accounts.URL // Textual URL uniquely identifying this wallet + url *accounts.URL // Textual URL uniquely identifying this wallet - device *usb.Device // USB device advertising itself as a Ledger wallet - input usb.Endpoint // Input endpoint to send data to this device - output usb.Endpoint // Output endpoint to receive data from this device - failure error // Any failure that would make the device unusable + info hid.DeviceInfo // Known USB device infos about the wallet + device *hid.Device // USB device advertising itself as a Ledger wallet + failure error // Any failure that would make the device unusable version [3]byte // Current version of the Ledger Ethereum app (zero if app is offline) browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch) @@ -183,59 +181,12 @@ func (w *ledgerWallet) Open(passphrase string) error { return accounts.ErrWalletAlreadyOpen } // Otherwise iterate over all USB devices and find this again (no way to directly do this) - // Iterate over all attached devices and fetch those seemingly Ledger - devices, err := w.context.ListDevices(func(desc *usb.Descriptor) bool { - // Only open this single specific device - return desc.Vendor == w.hardwareID.Vendor && desc.Product == w.hardwareID.Product && - uint16(desc.Bus)<<8+uint16(desc.Address) == w.locationID - }) + device, err := w.info.Open() if err != nil { return err } - if len(devices) == 0 { - return accounts.ErrUnknownWallet - } - // Device opened, attach to the input and output endpoints - device := devices[0] - - var invalid string - switch { - case len(device.Descriptor.Configs) == 0: - invalid = "no endpoint config available" - case len(device.Descriptor.Configs[0].Interfaces) == 0: - invalid = "no endpoint interface available" - case len(device.Descriptor.Configs[0].Interfaces[0].Setups) == 0: - invalid = "no endpoint setup available" - case len(device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints) < 2: - invalid = "not enough IO endpoints available" - } - if invalid != "" { - device.Close() - return fmt.Errorf("ledger wallet [%s] invalid: %s", w.url, invalid) - } - // Open the input and output endpoints to the device - input, err := device.OpenEndpoint( - device.Descriptor.Configs[0].Config, - device.Descriptor.Configs[0].Interfaces[0].Number, - device.Descriptor.Configs[0].Interfaces[0].Setups[0].Number, - device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints[1].Address, - ) - if err != nil { - device.Close() - return fmt.Errorf("ledger wallet [%s] input open failed: %v", w.url, err) - } - output, err := device.OpenEndpoint( - device.Descriptor.Configs[0].Config, - device.Descriptor.Configs[0].Interfaces[0].Number, - device.Descriptor.Configs[0].Interfaces[0].Setups[0].Number, - device.Descriptor.Configs[0].Interfaces[0].Setups[0].Endpoints[0].Address, - ) - if err != nil { - device.Close() - return fmt.Errorf("ledger wallet [%s] output open failed: %v", w.url, err) - } // Wallet seems to be successfully opened, guess if the Ethereum app is running - w.device, w.input, w.output = device, input, output + w.device = device w.commsLock = make(chan struct{}, 1) w.commsLock <- struct{}{} // Enable lock @@ -298,13 +249,13 @@ func (w *ledgerWallet) heartbeat() { w.commsLock <- struct{}{} w.stateLock.RUnlock() - if err == usb.ERROR_IO || err == usb.ERROR_NO_DEVICE { + if err != nil && err != errInvalidVersionReply { w.stateLock.Lock() // Lock state to tear the wallet down w.failure = err w.close() w.stateLock.Unlock() } - // Ignore uninteresting errors + // Ignore non hardware related errors err = nil } // In case of error, wait for termination @@ -363,13 +314,13 @@ func (w *ledgerWallet) close() error { return nil } // Close the device, clear everything, then return - err := w.device.Close() + w.device.Close() + w.device = nil - w.device, w.input, w.output = nil, nil, nil w.browser, w.version = false, [3]byte{} w.accounts, w.paths = nil, nil - return err + return nil } // Accounts implements accounts.Wallet, returning the list of accounts pinned to @@ -664,7 +615,7 @@ func (w *ledgerWallet) ledgerVersion() ([3]byte, error) { return [3]byte{}, err } if len(reply) != 4 { - return [3]byte{}, errors.New("reply not of correct size") + return [3]byte{}, errInvalidVersionReply } // Cache the version for future reference var version [3]byte @@ -768,10 +719,6 @@ func (w *ledgerWallet) ledgerDerive(derivationPath []uint32) (common.Address, er // signature R | 32 bytes // signature S | 32 bytes func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { - // We need to modify the timeouts to account for user feedback - defer func(old time.Duration) { w.device.ReadTimeout = old }(w.device.ReadTimeout) - w.device.ReadTimeout = time.Hour * 24 * 30 // Timeout requires a Ledger power cycle, only if you must - // Flatten the derivation path into the Ledger request path := make([]byte, 1+4*len(derivationPath)) path[0] = byte(len(derivationPath)) @@ -903,9 +850,9 @@ func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l } // Send over to the device if glog.V(logger.Detail) { - glog.Infof("-> %03d.%03d: %x", w.device.Bus, w.device.Address, chunk) + glog.Infof("-> %s: %x", w.device.Path, chunk) } - if _, err := w.input.Write(chunk); err != nil { + if _, err := w.device.Write(chunk); err != nil { return nil, err } } @@ -914,11 +861,11 @@ func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l chunk = chunk[:64] // Yeah, we surely have enough space for { // Read the next chunk from the Ledger wallet - if _, err := io.ReadFull(w.output, chunk); err != nil { + if _, err := io.ReadFull(w.device, chunk); err != nil { return nil, err } if glog.V(logger.Detail) { - glog.Infof("<- %03d.%03d: %x", w.device.Bus, w.device.Address, chunk) + glog.Infof("<- %s: %x", w.device.Path, chunk) } // Make sure the transport header matches if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { -- cgit v1.2.3