diff options
Diffstat (limited to 'accounts/usbwallet')
-rw-r--r-- | accounts/usbwallet/ledger_hub.go | 45 | ||||
-rw-r--r-- | accounts/usbwallet/ledger_wallet.go | 97 | ||||
-rw-r--r-- | accounts/usbwallet/usbwallet.go | 8 | ||||
-rw-r--r-- | accounts/usbwallet/usbwallet_ios.go | 38 |
4 files changed, 38 insertions, 150 deletions
diff --git a/accounts/usbwallet/ledger_hub.go b/accounts/usbwallet/ledger_hub.go index ad5940cd4..70396d314 100644 --- a/accounts/usbwallet/ledger_hub.go +++ b/accounts/usbwallet/ledger_hub.go @@ -18,18 +18,16 @@ // 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 ( - "fmt" + "errors" "sync" "time" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/event" - "github.com/karalabe/gousb/usb" + "github.com/karalabe/hid" ) // LedgerScheme is the protocol scheme prefixing account and wallet URLs. @@ -49,8 +47,6 @@ const ledgerRefreshThrottling = 500 * time.Millisecond // LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets. type LedgerHub struct { - ctx *usb.Context // Context interfacing with a libusb instance - refreshed time.Time // Time instance when the list of wallets was last refreshed wallets []accounts.Wallet // List of Ledger devices currently tracking updateFeed event.Feed // Event feed to notify wallet additions/removals @@ -63,18 +59,13 @@ type LedgerHub struct { // NewLedgerHub creates a new hardware wallet manager for Ledger devices. func NewLedgerHub() (*LedgerHub, error) { - // Initialize the USB library to access Ledgers through - ctx, err := usb.NewContext() - if err != nil { - return nil, err + if !hid.Supported() { + return nil, errors.New("unsupported platform") } - // Create the USB hub, start and return it hub := &LedgerHub{ - ctx: ctx, quit: make(chan chan error), } hub.refreshWallets() - return hub, nil } @@ -104,31 +95,23 @@ func (hub *LedgerHub) refreshWallets() { return } // Retrieve the current list of Ledger devices - var devIDs []deviceID - var busIDs []uint16 - - hub.ctx.ListDevices(func(desc *usb.Descriptor) bool { - // Gather Ledger devices, don't connect any just yet + var ledgers []hid.DeviceInfo + for _, info := range hid.Enumerate(0, 0) { // Can't enumerate directly, one valid ID is the 0 wildcard for _, id := range ledgerDeviceIDs { - if desc.Vendor == id.Vendor && desc.Product == id.Product { - devIDs = append(devIDs, deviceID{Vendor: desc.Vendor, Product: desc.Product}) - busIDs = append(busIDs, uint16(desc.Bus)<<8+uint16(desc.Address)) - return false + if info.VendorID == id.Vendor && info.ProductID == id.Product { + ledgers = append(ledgers, info) + break } } - // Not ledger, ignore and don't connect either - return false - }) + } // Transform the current list of wallets into the new one hub.lock.Lock() - wallets := make([]accounts.Wallet, 0, len(devIDs)) + wallets := make([]accounts.Wallet, 0, len(ledgers)) events := []accounts.WalletEvent{} - for i := 0; i < len(devIDs); i++ { - devID, busID := devIDs[i], busIDs[i] - - url := accounts.URL{Scheme: LedgerScheme, Path: fmt.Sprintf("%03d:%03d", busID>>8, busID&0xff)} + for _, ledger := range ledgers { + url := accounts.URL{Scheme: LedgerScheme, Path: ledger.Path} // Drop wallets in front of the next device or those that failed for some reason for len(hub.wallets) > 0 && (hub.wallets[0].URL().Cmp(url) < 0 || hub.wallets[0].(*ledgerWallet).failed()) { @@ -137,7 +120,7 @@ func (hub *LedgerHub) refreshWallets() { } // If there are no more wallets or the device is before the next, wrap new wallet if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { - wallet := &ledgerWallet{context: hub.ctx, hardwareID: devID, locationID: busID, url: &url} + wallet := &ledgerWallet{url: &url, info: ledger} events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true}) wallets = append(wallets, wallet) 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 { diff --git a/accounts/usbwallet/usbwallet.go b/accounts/usbwallet/usbwallet.go index 3989f3d02..938ab1e6a 100644 --- a/accounts/usbwallet/usbwallet.go +++ b/accounts/usbwallet/usbwallet.go @@ -14,16 +14,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. -// +build !ios - // Package usbwallet implements support for USB hardware wallets. package usbwallet -import "github.com/karalabe/gousb/usb" - // deviceID is a combined vendor/product identifier to uniquely identify a USB // hardware device. type deviceID struct { - Vendor usb.ID // The Vendor identifer - Product usb.ID // The Product identifier + Vendor uint16 // The Vendor identifer + Product uint16 // The Product identifier } diff --git a/accounts/usbwallet/usbwallet_ios.go b/accounts/usbwallet/usbwallet_ios.go deleted file mode 100644 index 17d342903..000000000 --- a/accounts/usbwallet/usbwallet_ios.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -// This file contains the implementation for interacting with the Ledger hardware -// 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 ( - "errors" - - "github.com/ethereum/go-ethereum/accounts" -) - -// Here be dragons! There is no USB support on iOS. - -// ErrIOSNotSupported is returned for all USB hardware backends on iOS. -var ErrIOSNotSupported = errors.New("no USB support on iOS") - -func NewLedgerHub() (accounts.Backend, error) { - return nil, ErrIOSNotSupported -} |