accounts: refactor API for generalized USB wallets
This commit is contained in:
		| @@ -42,8 +42,9 @@ type Wallet interface { | |||||||
| 	URL() URL | 	URL() URL | ||||||
|  |  | ||||||
| 	// Status returns a textual status to aid the user in the current state of the | 	// Status returns a textual status to aid the user in the current state of the | ||||||
| 	// wallet. | 	// wallet. It also returns an error indicating any failure the wallet might have | ||||||
| 	Status() string | 	// encountered. | ||||||
|  | 	Status() (string, error) | ||||||
|  |  | ||||||
| 	// Open initializes access to a wallet instance. It is not meant to unlock or | 	// Open initializes access to a wallet instance. It is not meant to unlock or | ||||||
| 	// decrypt account keys, rather simply to establish a connection to hardware | 	// decrypt account keys, rather simply to establish a connection to hardware | ||||||
|   | |||||||
| @@ -37,11 +37,11 @@ func TestHDPathParsing(t *testing.T) { | |||||||
| 		{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||||||
|  |  | ||||||
| 		// Plain relative derivation paths | 		// Plain relative derivation paths | ||||||
| 		{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | 		{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, | ||||||
| 		{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | 		{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, | ||||||
| 		{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, | ||||||
| 		{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | 		{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, | ||||||
| 		{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, | ||||||
|  |  | ||||||
| 		// Hexadecimal absolute derivation paths | 		// Hexadecimal absolute derivation paths | ||||||
| 		{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | 		{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||||||
| @@ -52,11 +52,11 @@ func TestHDPathParsing(t *testing.T) { | |||||||
| 		{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | ||||||
|  |  | ||||||
| 		// Hexadecimal relative derivation paths | 		// Hexadecimal relative derivation paths | ||||||
| 		{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | 		{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}}, | ||||||
| 		{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}}, | 		{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}}, | ||||||
| 		{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, | ||||||
| 		{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}}, | 		{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}}, | ||||||
| 		{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}}, | 		{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}}, | ||||||
|  |  | ||||||
| 		// Weird inputs just to ensure they work | 		// Weird inputs just to ensure they work | ||||||
| 		{"	m  /   44			'\n/\n   60	\n\n\t'   /\n0 ' /\t\t	0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | 		{"	m  /   44			'\n/\n   60	\n\n\t'   /\n0 ' /\t\t	0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}}, | ||||||
|   | |||||||
| @@ -36,16 +36,16 @@ func (w *keystoreWallet) URL() accounts.URL { | |||||||
| 	return w.account.URL | 	return w.account.URL | ||||||
| } | } | ||||||
|  |  | ||||||
| // Status implements accounts.Wallet, always returning "open", since there is no | // Status implements accounts.Wallet, returning whether the account held by the | ||||||
| // concept of open/close for plain keystore accounts. | // keystore wallet is unlocked or not. | ||||||
| func (w *keystoreWallet) Status() string { | func (w *keystoreWallet) Status() (string, error) { | ||||||
| 	w.keystore.mu.RLock() | 	w.keystore.mu.RLock() | ||||||
| 	defer w.keystore.mu.RUnlock() | 	defer w.keystore.mu.RUnlock() | ||||||
|  |  | ||||||
| 	if _, ok := w.keystore.unlocked[w.account.Address]; ok { | 	if _, ok := w.keystore.unlocked[w.account.Address]; ok { | ||||||
| 		return "Unlocked" | 		return "Unlocked", nil | ||||||
| 	} | 	} | ||||||
| 	return "Locked" | 	return "Locked", nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Open implements accounts.Wallet, but is a noop for plain wallets since there | // Open implements accounts.Wallet, but is a noop for plain wallets since there | ||||||
|   | |||||||
| @@ -29,24 +29,28 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // LedgerScheme is the protocol scheme prefixing account and wallet URLs. | // LedgerScheme is the protocol scheme prefixing account and wallet URLs. | ||||||
| var LedgerScheme = "ledger" | const LedgerScheme = "ledger" | ||||||
| 
 | 
 | ||||||
| // ledgerDeviceIDs are the known device IDs that Ledger wallets use. | // TrezorScheme is the protocol scheme prefixing account and wallet URLs. | ||||||
| var ledgerDeviceIDs = []deviceID{ | const TrezorScheme = "trezor" | ||||||
| 	{Vendor: 0x2c97, Product: 0x0000}, // Ledger Blue |  | ||||||
| 	{Vendor: 0x2c97, Product: 0x0001}, // Ledger Nano S |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| // Maximum time between wallet refreshes (if USB hotplug notifications don't work). | // refreshCycle is the maximum time between wallet refreshes (if USB hotplug | ||||||
| const ledgerRefreshCycle = time.Second | // notifications don't work). | ||||||
|  | const refreshCycle = time.Second | ||||||
| 
 | 
 | ||||||
| // Minimum time between wallet refreshes to avoid USB trashing. | // refreshThrottling is the minimum time between wallet refreshes to avoid USB | ||||||
| const ledgerRefreshThrottling = 500 * time.Millisecond | // trashing. | ||||||
|  | const refreshThrottling = 500 * time.Millisecond | ||||||
|  | 
 | ||||||
|  | // Hub is a accounts.Backend that can find and handle generic USB hardware wallets. | ||||||
|  | type Hub struct { | ||||||
|  | 	scheme     string                  // Protocol scheme prefixing account and wallet URLs. | ||||||
|  | 	vendorID   uint16                  // USB vendor identifier used for device discovery | ||||||
|  | 	productIDs []uint16                // USB product identifiers used for device discovery | ||||||
|  | 	makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver | ||||||
| 
 | 
 | ||||||
| // LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets. |  | ||||||
| type LedgerHub struct { |  | ||||||
| 	refreshed   time.Time               // Time instance when the list of wallets was last refreshed | 	refreshed   time.Time               // Time instance when the list of wallets was last refreshed | ||||||
| 	wallets     []accounts.Wallet       // List of Ledger devices currently tracking | 	wallets     []accounts.Wallet       // List of USB wallet devices currently tracking | ||||||
| 	updateFeed  event.Feed              // Event feed to notify wallet additions/removals | 	updateFeed  event.Feed              // Event feed to notify wallet additions/removals | ||||||
| 	updateScope event.SubscriptionScope // Subscription scope tracking current live listeners | 	updateScope event.SubscriptionScope // Subscription scope tracking current live listeners | ||||||
| 	updating    bool                    // Whether the event notification loop is running | 	updating    bool                    // Whether the event notification loop is running | ||||||
| @@ -61,20 +65,34 @@ type LedgerHub struct { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewLedgerHub creates a new hardware wallet manager for Ledger devices. | // NewLedgerHub creates a new hardware wallet manager for Ledger devices. | ||||||
| func NewLedgerHub() (*LedgerHub, error) { | func NewLedgerHub() (*Hub, error) { | ||||||
|  | 	return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, newLedgerDriver) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewTrezorHub creates a new hardware wallet manager for Trezor devices. | ||||||
|  | func NewTrezorHub() (*Hub, error) { | ||||||
|  | 	return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, newTrezorDriver) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // newHub creates a new hardware wallet manager for generic USB devices. | ||||||
|  | func newHub(scheme string, vendorID uint16, productIDs []uint16, makeDriver func(log.Logger) driver) (*Hub, error) { | ||||||
| 	if !hid.Supported() { | 	if !hid.Supported() { | ||||||
| 		return nil, errors.New("unsupported platform") | 		return nil, errors.New("unsupported platform") | ||||||
| 	} | 	} | ||||||
| 	hub := &LedgerHub{ | 	hub := &Hub{ | ||||||
| 		quit: make(chan chan error), | 		scheme:     scheme, | ||||||
|  | 		vendorID:   vendorID, | ||||||
|  | 		productIDs: productIDs, | ||||||
|  | 		makeDriver: makeDriver, | ||||||
|  | 		quit:       make(chan chan error), | ||||||
| 	} | 	} | ||||||
| 	hub.refreshWallets() | 	hub.refreshWallets() | ||||||
| 	return hub, nil | 	return hub, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Wallets implements accounts.Backend, returning all the currently tracked USB | // Wallets implements accounts.Backend, returning all the currently tracked USB | ||||||
| // devices that appear to be Ledger hardware wallets. | // devices that appear to be hardware wallets. | ||||||
| func (hub *LedgerHub) Wallets() []accounts.Wallet { | func (hub *Hub) Wallets() []accounts.Wallet { | ||||||
| 	// Make sure the list of wallets is up to date | 	// Make sure the list of wallets is up to date | ||||||
| 	hub.refreshWallets() | 	hub.refreshWallets() | ||||||
| 
 | 
 | ||||||
| @@ -88,17 +106,17 @@ func (hub *LedgerHub) Wallets() []accounts.Wallet { | |||||||
| 
 | 
 | ||||||
| // refreshWallets scans the USB devices attached to the machine and updates the | // refreshWallets scans the USB devices attached to the machine and updates the | ||||||
| // list of wallets based on the found devices. | // list of wallets based on the found devices. | ||||||
| func (hub *LedgerHub) refreshWallets() { | func (hub *Hub) refreshWallets() { | ||||||
| 	// Don't scan the USB like crazy it the user fetches wallets in a loop | 	// Don't scan the USB like crazy it the user fetches wallets in a loop | ||||||
| 	hub.stateLock.RLock() | 	hub.stateLock.RLock() | ||||||
| 	elapsed := time.Since(hub.refreshed) | 	elapsed := time.Since(hub.refreshed) | ||||||
| 	hub.stateLock.RUnlock() | 	hub.stateLock.RUnlock() | ||||||
| 
 | 
 | ||||||
| 	if elapsed < ledgerRefreshThrottling { | 	if elapsed < refreshThrottling { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	// Retrieve the current list of Ledger devices | 	// Retrieve the current list of USB wallet devices | ||||||
| 	var ledgers []hid.DeviceInfo | 	var devices []hid.DeviceInfo | ||||||
| 
 | 
 | ||||||
| 	if runtime.GOOS == "linux" { | 	if runtime.GOOS == "linux" { | ||||||
| 		// hidapi on Linux opens the device during enumeration to retrieve some infos, | 		// hidapi on Linux opens the device during enumeration to retrieve some infos, | ||||||
| @@ -113,10 +131,10 @@ func (hub *LedgerHub) refreshWallets() { | |||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	for _, info := range hid.Enumerate(0, 0) { // Can't enumerate directly, one valid ID is the 0 wildcard | 	for _, info := range hid.Enumerate(hub.vendorID, 0) { | ||||||
| 		for _, id := range ledgerDeviceIDs { | 		for _, id := range hub.productIDs { | ||||||
| 			if info.VendorID == id.Vendor && info.ProductID == id.Product { | 			if info.ProductID == id && info.Interface == 0 { | ||||||
| 				ledgers = append(ledgers, info) | 				devices = append(devices, info) | ||||||
| 				break | 				break | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -128,20 +146,27 @@ func (hub *LedgerHub) refreshWallets() { | |||||||
| 	// Transform the current list of wallets into the new one | 	// Transform the current list of wallets into the new one | ||||||
| 	hub.stateLock.Lock() | 	hub.stateLock.Lock() | ||||||
| 
 | 
 | ||||||
| 	wallets := make([]accounts.Wallet, 0, len(ledgers)) | 	wallets := make([]accounts.Wallet, 0, len(devices)) | ||||||
| 	events := []accounts.WalletEvent{} | 	events := []accounts.WalletEvent{} | ||||||
| 
 | 
 | ||||||
| 	for _, ledger := range ledgers { | 	for _, device := range devices { | ||||||
| 		url := accounts.URL{Scheme: LedgerScheme, Path: ledger.Path} | 		url := accounts.URL{Scheme: hub.scheme, Path: device.Path} | ||||||
| 
 | 
 | ||||||
| 		// Drop wallets in front of the next device or those that failed for some reason | 		// 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()) { | 		for len(hub.wallets) > 0 { | ||||||
|  | 			// Abort if we're past the current device and found an operational one | ||||||
|  | 			_, failure := hub.wallets[0].Status() | ||||||
|  | 			if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil { | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			// Drop the stale and failed devices | ||||||
| 			events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) | 			events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) | ||||||
| 			hub.wallets = hub.wallets[1:] | 			hub.wallets = hub.wallets[1:] | ||||||
| 		} | 		} | ||||||
| 		// If there are no more wallets or the device is before the next, wrap new wallet | 		// 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 { | 		if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 { | ||||||
| 			wallet := &ledgerWallet{hub: hub, url: &url, info: ledger, log: log.New("url", url)} | 			logger := log.New("url", url) | ||||||
|  | 			wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger} | ||||||
| 
 | 
 | ||||||
| 			events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) | 			events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) | ||||||
| 			wallets = append(wallets, wallet) | 			wallets = append(wallets, wallet) | ||||||
| @@ -169,8 +194,8 @@ func (hub *LedgerHub) refreshWallets() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Subscribe implements accounts.Backend, creating an async subscription to | // Subscribe implements accounts.Backend, creating an async subscription to | ||||||
| // receive notifications on the addition or removal of Ledger wallets. | // receive notifications on the addition or removal of USB wallets. | ||||||
| func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { | func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { | ||||||
| 	// We need the mutex to reliably start/stop the update loop | 	// We need the mutex to reliably start/stop the update loop | ||||||
| 	hub.stateLock.Lock() | 	hub.stateLock.Lock() | ||||||
| 	defer hub.stateLock.Unlock() | 	defer hub.stateLock.Unlock() | ||||||
| @@ -186,16 +211,13 @@ func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscrip | |||||||
| 	return sub | 	return sub | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // updater is responsible for maintaining an up-to-date list of wallets stored in | // updater is responsible for maintaining an up-to-date list of wallets managed | ||||||
| // the keystore, and for firing wallet addition/removal events. It listens for | // by the USB hub, and for firing wallet addition/removal events. | ||||||
| // account change events from the underlying account cache, and also periodically | func (hub *Hub) updater() { | ||||||
| // forces a manual refresh (only triggers for systems where the filesystem notifier |  | ||||||
| // is not running). |  | ||||||
| func (hub *LedgerHub) updater() { |  | ||||||
| 	for { | 	for { | ||||||
| 		// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout | 		// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout | ||||||
| 		// <-hub.changes | 		// <-hub.changes | ||||||
| 		time.Sleep(ledgerRefreshCycle) | 		time.Sleep(refreshCycle) | ||||||
| 
 | 
 | ||||||
| 		// Run the wallet refresher | 		// Run the wallet refresher | ||||||
| 		hub.refreshWallets() | 		hub.refreshWallets() | ||||||
							
								
								
									
										464
									
								
								accounts/usbwallet/ledger.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										464
									
								
								accounts/usbwallet/ledger.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,464 @@ | |||||||
|  | // 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 | ||||||
|  |  | ||||||
|  | package usbwallet | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"encoding/hex" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  |  | ||||||
|  | 	"github.com/ethereum/go-ethereum/accounts" | ||||||
|  | 	"github.com/ethereum/go-ethereum/common" | ||||||
|  | 	"github.com/ethereum/go-ethereum/common/hexutil" | ||||||
|  | 	"github.com/ethereum/go-ethereum/core/types" | ||||||
|  | 	"github.com/ethereum/go-ethereum/log" | ||||||
|  | 	"github.com/ethereum/go-ethereum/rlp" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ledgerOpcode is an enumeration encoding the supported Ledger opcodes. | ||||||
|  | type ledgerOpcode byte | ||||||
|  |  | ||||||
|  | // ledgerParam1 is an enumeration encoding the supported Ledger parameters for | ||||||
|  | // specific opcodes. The same parameter values may be reused between opcodes. | ||||||
|  | type ledgerParam1 byte | ||||||
|  |  | ||||||
|  | // ledgerParam2 is an enumeration encoding the supported Ledger parameters for | ||||||
|  | // specific opcodes. The same parameter values may be reused between opcodes. | ||||||
|  | type ledgerParam2 byte | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	ledgerOpRetrieveAddress  ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path | ||||||
|  | 	ledgerOpSignTransaction  ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters | ||||||
|  | 	ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration | ||||||
|  |  | ||||||
|  | 	ledgerP1DirectlyFetchAddress    ledgerParam1 = 0x00 // Return address directly from the wallet | ||||||
|  | 	ledgerP1ConfirmFetchAddress     ledgerParam1 = 0x01 // Require a user confirmation before returning the address | ||||||
|  | 	ledgerP1InitTransactionData     ledgerParam1 = 0x00 // First transaction data block for signing | ||||||
|  | 	ledgerP1ContTransactionData     ledgerParam1 = 0x80 // Subsequent transaction data block for signing | ||||||
|  | 	ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address | ||||||
|  | 	ledgerP2ReturnAddressChainCode  ledgerParam2 = 0x01 // Require a user confirmation before returning the address | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // errLedgerReplyInvalidHeader 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 errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header") | ||||||
|  |  | ||||||
|  | // errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval | ||||||
|  | // when a response does arrive, but it does not contain the expected data. | ||||||
|  | var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") | ||||||
|  |  | ||||||
|  | // ledgerDriver implements the communication with a Ledger hardware wallet. | ||||||
|  | type ledgerDriver struct { | ||||||
|  | 	device  io.ReadWriter // USB device connection to communicate through | ||||||
|  | 	version [3]byte       // Current version of the Ledger firmware (zero if app is offline) | ||||||
|  | 	browser bool          // Flag whether the Ledger is in browser mode (reply channel mismatch) | ||||||
|  | 	failure error         // Any failure that would make the device unusable | ||||||
|  | 	log     log.Logger    // Contextual logger to tag the ledger with its id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // newLedgerDriver creates a new instance of a Ledger USB protocol driver. | ||||||
|  | func newLedgerDriver(logger log.Logger) driver { | ||||||
|  | 	return &ledgerDriver{ | ||||||
|  | 		log: logger, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Status implements usbwallet.driver, returning various states the Ledger can | ||||||
|  | // currently be in. | ||||||
|  | func (w *ledgerDriver) Status() (string, error) { | ||||||
|  | 	if w.failure != nil { | ||||||
|  | 		return fmt.Sprintf("Failed: %v", w.failure), w.failure | ||||||
|  | 	} | ||||||
|  | 	if w.browser { | ||||||
|  | 		return "Ethereum app in browser mode", w.failure | ||||||
|  | 	} | ||||||
|  | 	if w.offline() { | ||||||
|  | 		return "Ethereum app offline", w.failure | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // offline returns whether the wallet and the Ethereum app is offline or not. | ||||||
|  | // | ||||||
|  | // The method assumes that the state lock is held! | ||||||
|  | func (w *ledgerDriver) offline() bool { | ||||||
|  | 	return w.version == [3]byte{0, 0, 0} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open implements usbwallet.driver, attempting to initialize the connection to the | ||||||
|  | // Ledger hardware wallet. The Ledger does not require a user passphrase, so that | ||||||
|  | // parameter is silently discarded. | ||||||
|  | func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error { | ||||||
|  | 	w.device, w.failure = device, nil | ||||||
|  |  | ||||||
|  | 	_, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath) | ||||||
|  | 	if err != nil { | ||||||
|  | 		// Ethereum app is not running or in browser mode, nothing more to do, return | ||||||
|  | 		if err == errLedgerReplyInvalidHeader { | ||||||
|  | 			w.browser = true | ||||||
|  | 		} | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	// Try to resolve the Ethereum app's version, will fail prior to v1.0.2 | ||||||
|  | 	if w.version, err = w.ledgerVersion(); err != nil { | ||||||
|  | 		w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements usbwallet.driver, cleaning up and metadata maintained within | ||||||
|  | // the Ledger driver. | ||||||
|  | func (w *ledgerDriver) Close() error { | ||||||
|  | 	w.browser, w.version = false, [3]byte{} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Heartbeat implements usbwallet.driver, performing a sanity check against the | ||||||
|  | // Ledger to see if it's still online. | ||||||
|  | func (w *ledgerDriver) Heartbeat() error { | ||||||
|  | 	if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { | ||||||
|  | 		w.failure = err | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Derive implements usbwallet.driver, sending a derivation request to the Ledger | ||||||
|  | // and returning the Ethereum address located on that derivation path. | ||||||
|  | func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) { | ||||||
|  | 	return w.ledgerDerive(path) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignTx implements usbwallet.driver, sending the transaction to the Ledger and | ||||||
|  | // waiting for the user to confirm or deny the transaction. | ||||||
|  | // | ||||||
|  | // Note, if the version of the Ethereum application running on the Ledger wallet is | ||||||
|  | // too old to sign EIP-155 transactions, but such is requested nonetheless, an error | ||||||
|  | // will be returned opposed to silently signing in Homestead mode. | ||||||
|  | func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { | ||||||
|  | 	// If the Ethereum app doesn't run, abort | ||||||
|  | 	if w.offline() { | ||||||
|  | 		return common.Address{}, nil, accounts.ErrWalletClosed | ||||||
|  | 	} | ||||||
|  | 	// Ensure the wallet is capable of signing the given transaction | ||||||
|  | 	if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { | ||||||
|  | 		return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) | ||||||
|  | 	} | ||||||
|  | 	// All infos gathered and metadata checks out, request signing | ||||||
|  | 	return w.ledgerSign(path, tx, chainID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ledgerVersion retrieves the current version of the Ethereum wallet app running | ||||||
|  | // on the Ledger wallet. | ||||||
|  | // | ||||||
|  | // The version retrieval protocol is defined as follows: | ||||||
|  | // | ||||||
|  | //   CLA | INS | P1 | P2 | Lc | Le | ||||||
|  | //   ----+-----+----+----+----+--- | ||||||
|  | //    E0 | 06  | 00 | 00 | 00 | 04 | ||||||
|  | // | ||||||
|  | // With no input data, and the output data being: | ||||||
|  | // | ||||||
|  | //   Description                                        | Length | ||||||
|  | //   ---------------------------------------------------+-------- | ||||||
|  | //   Flags 01: arbitrary data signature enabled by user | 1 byte | ||||||
|  | //   Application major version                          | 1 byte | ||||||
|  | //   Application minor version                          | 1 byte | ||||||
|  | //   Application patch version                          | 1 byte | ||||||
|  | func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { | ||||||
|  | 	// Send the request and wait for the response | ||||||
|  | 	reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return [3]byte{}, err | ||||||
|  | 	} | ||||||
|  | 	if len(reply) != 4 { | ||||||
|  | 		return [3]byte{}, errLedgerInvalidVersionReply | ||||||
|  | 	} | ||||||
|  | 	// Cache the version for future reference | ||||||
|  | 	var version [3]byte | ||||||
|  | 	copy(version[:], reply[1:]) | ||||||
|  | 	return version, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ledgerDerive retrieves the currently active Ethereum address from a Ledger | ||||||
|  | // wallet at the specified derivation path. | ||||||
|  | // | ||||||
|  | // The address derivation protocol is defined as follows: | ||||||
|  | // | ||||||
|  | //   CLA | INS | P1 | P2 | Lc  | Le | ||||||
|  | //   ----+-----+----+----+-----+--- | ||||||
|  | //    E0 | 02  | 00 return address | ||||||
|  | //               01 display address and confirm before returning | ||||||
|  | //                  | 00: do not return the chain code | ||||||
|  | //                  | 01: return the chain code | ||||||
|  | //                       | var | 00 | ||||||
|  | // | ||||||
|  | // Where the input data is: | ||||||
|  | // | ||||||
|  | //   Description                                      | Length | ||||||
|  | //   -------------------------------------------------+-------- | ||||||
|  | //   Number of BIP 32 derivations to perform (max 10) | 1 byte | ||||||
|  | //   First derivation index (big endian)              | 4 bytes | ||||||
|  | //   ...                                              | 4 bytes | ||||||
|  | //   Last derivation index (big endian)               | 4 bytes | ||||||
|  | // | ||||||
|  | // And the output data is: | ||||||
|  | // | ||||||
|  | //   Description             | Length | ||||||
|  | //   ------------------------+------------------- | ||||||
|  | //   Public Key length       | 1 byte | ||||||
|  | //   Uncompressed Public Key | arbitrary | ||||||
|  | //   Ethereum address length | 1 byte | ||||||
|  | //   Ethereum address        | 40 bytes hex ascii | ||||||
|  | //   Chain code if requested | 32 bytes | ||||||
|  | func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) { | ||||||
|  | 	// Flatten the derivation path into the Ledger request | ||||||
|  | 	path := make([]byte, 1+4*len(derivationPath)) | ||||||
|  | 	path[0] = byte(len(derivationPath)) | ||||||
|  | 	for i, component := range derivationPath { | ||||||
|  | 		binary.BigEndian.PutUint32(path[1+4*i:], component) | ||||||
|  | 	} | ||||||
|  | 	// Send the request and wait for the response | ||||||
|  | 	reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return common.Address{}, err | ||||||
|  | 	} | ||||||
|  | 	// Discard the public key, we don't need that for now | ||||||
|  | 	if len(reply) < 1 || len(reply) < 1+int(reply[0]) { | ||||||
|  | 		return common.Address{}, errors.New("reply lacks public key entry") | ||||||
|  | 	} | ||||||
|  | 	reply = reply[1+int(reply[0]):] | ||||||
|  |  | ||||||
|  | 	// Extract the Ethereum hex address string | ||||||
|  | 	if len(reply) < 1 || len(reply) < 1+int(reply[0]) { | ||||||
|  | 		return common.Address{}, errors.New("reply lacks address entry") | ||||||
|  | 	} | ||||||
|  | 	hexstr := reply[1 : 1+int(reply[0])] | ||||||
|  |  | ||||||
|  | 	// Decode the hex sting into an Ethereum address and return | ||||||
|  | 	var address common.Address | ||||||
|  | 	hex.Decode(address[:], hexstr) | ||||||
|  | 	return address, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ledgerSign sends the transaction to the Ledger wallet, and waits for the user | ||||||
|  | // to confirm or deny the transaction. | ||||||
|  | // | ||||||
|  | // The transaction signing protocol is defined as follows: | ||||||
|  | // | ||||||
|  | //   CLA | INS | P1 | P2 | Lc  | Le | ||||||
|  | //   ----+-----+----+----+-----+--- | ||||||
|  | //    E0 | 04  | 00: first transaction data block | ||||||
|  | //               80: subsequent transaction data block | ||||||
|  | //                  | 00 | variable | variable | ||||||
|  | // | ||||||
|  | // Where the input for the first transaction block (first 255 bytes) is: | ||||||
|  | // | ||||||
|  | //   Description                                      | Length | ||||||
|  | //   -------------------------------------------------+---------- | ||||||
|  | //   Number of BIP 32 derivations to perform (max 10) | 1 byte | ||||||
|  | //   First derivation index (big endian)              | 4 bytes | ||||||
|  | //   ...                                              | 4 bytes | ||||||
|  | //   Last derivation index (big endian)               | 4 bytes | ||||||
|  | //   RLP transaction chunk                            | arbitrary | ||||||
|  | // | ||||||
|  | // And the input for subsequent transaction blocks (first 255 bytes) are: | ||||||
|  | // | ||||||
|  | //   Description           | Length | ||||||
|  | //   ----------------------+---------- | ||||||
|  | //   RLP transaction chunk | arbitrary | ||||||
|  | // | ||||||
|  | // And the output data is: | ||||||
|  | // | ||||||
|  | //   Description | Length | ||||||
|  | //   ------------+--------- | ||||||
|  | //   signature V | 1 byte | ||||||
|  | //   signature R | 32 bytes | ||||||
|  | //   signature S | 32 bytes | ||||||
|  | func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { | ||||||
|  | 	// Flatten the derivation path into the Ledger request | ||||||
|  | 	path := make([]byte, 1+4*len(derivationPath)) | ||||||
|  | 	path[0] = byte(len(derivationPath)) | ||||||
|  | 	for i, component := range derivationPath { | ||||||
|  | 		binary.BigEndian.PutUint32(path[1+4*i:], component) | ||||||
|  | 	} | ||||||
|  | 	// Create the transaction RLP based on whether legacy or EIP155 signing was requeste | ||||||
|  | 	var ( | ||||||
|  | 		txrlp []byte | ||||||
|  | 		err   error | ||||||
|  | 	) | ||||||
|  | 	if chainID == nil { | ||||||
|  | 		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { | ||||||
|  | 			return common.Address{}, nil, err | ||||||
|  | 		} | ||||||
|  | 	} else { | ||||||
|  | 		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { | ||||||
|  | 			return common.Address{}, nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	payload := append(path, txrlp...) | ||||||
|  |  | ||||||
|  | 	// Send the request and wait for the response | ||||||
|  | 	var ( | ||||||
|  | 		op    = ledgerP1InitTransactionData | ||||||
|  | 		reply []byte | ||||||
|  | 	) | ||||||
|  | 	for len(payload) > 0 { | ||||||
|  | 		// Calculate the size of the next data chunk | ||||||
|  | 		chunk := 255 | ||||||
|  | 		if chunk > len(payload) { | ||||||
|  | 			chunk = len(payload) | ||||||
|  | 		} | ||||||
|  | 		// Send the chunk over, ensuring it's processed correctly | ||||||
|  | 		reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return common.Address{}, nil, err | ||||||
|  | 		} | ||||||
|  | 		// Shift the payload and ensure subsequent chunks are marked as such | ||||||
|  | 		payload = payload[chunk:] | ||||||
|  | 		op = ledgerP1ContTransactionData | ||||||
|  | 	} | ||||||
|  | 	// Extract the Ethereum signature and do a sanity validation | ||||||
|  | 	if len(reply) != 65 { | ||||||
|  | 		return common.Address{}, nil, errors.New("reply lacks signature") | ||||||
|  | 	} | ||||||
|  | 	signature := append(reply[1:], reply[0]) | ||||||
|  |  | ||||||
|  | 	// Create the correct signer and signature transform based on the chain ID | ||||||
|  | 	var signer types.Signer | ||||||
|  | 	if chainID == nil { | ||||||
|  | 		signer = new(types.HomesteadSigner) | ||||||
|  | 	} else { | ||||||
|  | 		signer = types.NewEIP155Signer(chainID) | ||||||
|  | 		signature[64] = signature[64] - byte(chainID.Uint64()*2+35) | ||||||
|  | 	} | ||||||
|  | 	signed, err := tx.WithSignature(signer, signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return common.Address{}, nil, err | ||||||
|  | 	} | ||||||
|  | 	sender, err := types.Sender(signer, signed) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return common.Address{}, nil, err | ||||||
|  | 	} | ||||||
|  | 	return sender, signed, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ledgerExchange performs a data exchange with the Ledger wallet, sending it a | ||||||
|  | // message and retrieving the response. | ||||||
|  | // | ||||||
|  | // The common transport header is defined as follows: | ||||||
|  | // | ||||||
|  | //  Description                           | Length | ||||||
|  | //  --------------------------------------+---------- | ||||||
|  | //  Communication channel ID (big endian) | 2 bytes | ||||||
|  | //  Command tag                           | 1 byte | ||||||
|  | //  Packet sequence index (big endian)    | 2 bytes | ||||||
|  | //  Payload                               | arbitrary | ||||||
|  | // | ||||||
|  | // The Communication channel ID allows commands multiplexing over the same | ||||||
|  | // physical link. It is not used for the time being, and should be set to 0101 | ||||||
|  | // to avoid compatibility issues with implementations ignoring a leading 00 byte. | ||||||
|  | // | ||||||
|  | // The Command tag describes the message content. Use TAG_APDU (0x05) for standard | ||||||
|  | // APDU payloads, or TAG_PING (0x02) for a simple link test. | ||||||
|  | // | ||||||
|  | // The Packet sequence index describes the current sequence for fragmented payloads. | ||||||
|  | // The first fragment index is 0x00. | ||||||
|  | // | ||||||
|  | // APDU Command payloads are encoded as follows: | ||||||
|  | // | ||||||
|  | //  Description              | Length | ||||||
|  | //  ----------------------------------- | ||||||
|  | //  APDU length (big endian) | 2 bytes | ||||||
|  | //  APDU CLA                 | 1 byte | ||||||
|  | //  APDU INS                 | 1 byte | ||||||
|  | //  APDU P1                  | 1 byte | ||||||
|  | //  APDU P2                  | 1 byte | ||||||
|  | //  APDU length              | 1 byte | ||||||
|  | //  Optional APDU data       | arbitrary | ||||||
|  | func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { | ||||||
|  | 	// Construct the message payload, possibly split into multiple chunks | ||||||
|  | 	apdu := make([]byte, 2, 7+len(data)) | ||||||
|  |  | ||||||
|  | 	binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) | ||||||
|  | 	apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) | ||||||
|  | 	apdu = append(apdu, data...) | ||||||
|  |  | ||||||
|  | 	// Stream all the chunks to the device | ||||||
|  | 	header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended | ||||||
|  | 	chunk := make([]byte, 64) | ||||||
|  | 	space := len(chunk) - len(header) | ||||||
|  |  | ||||||
|  | 	for i := 0; len(apdu) > 0; i++ { | ||||||
|  | 		// Construct the new message to stream | ||||||
|  | 		chunk = append(chunk[:0], header...) | ||||||
|  | 		binary.BigEndian.PutUint16(chunk[3:], uint16(i)) | ||||||
|  |  | ||||||
|  | 		if len(apdu) > space { | ||||||
|  | 			chunk = append(chunk, apdu[:space]...) | ||||||
|  | 			apdu = apdu[space:] | ||||||
|  | 		} else { | ||||||
|  | 			chunk = append(chunk, apdu...) | ||||||
|  | 			apdu = nil | ||||||
|  | 		} | ||||||
|  | 		// Send over to the device | ||||||
|  | 		w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) | ||||||
|  | 		if _, err := w.device.Write(chunk); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Stream the reply back from the wallet in 64 byte chunks | ||||||
|  | 	var reply []byte | ||||||
|  | 	chunk = chunk[:64] // Yeah, we surely have enough space | ||||||
|  | 	for { | ||||||
|  | 		// Read the next chunk from the Ledger wallet | ||||||
|  | 		if _, err := io.ReadFull(w.device, chunk); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
|  | 		w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) | ||||||
|  |  | ||||||
|  | 		// Make sure the transport header matches | ||||||
|  | 		if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { | ||||||
|  | 			return nil, errLedgerReplyInvalidHeader | ||||||
|  | 		} | ||||||
|  | 		// If it's the first chunk, retrieve the total message length | ||||||
|  | 		var payload []byte | ||||||
|  |  | ||||||
|  | 		if chunk[3] == 0x00 && chunk[4] == 0x00 { | ||||||
|  | 			reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) | ||||||
|  | 			payload = chunk[7:] | ||||||
|  | 		} else { | ||||||
|  | 			payload = chunk[5:] | ||||||
|  | 		} | ||||||
|  | 		// Append to the reply and stop when filled up | ||||||
|  | 		if left := cap(reply) - len(reply); left > len(payload) { | ||||||
|  | 			reply = append(reply, payload...) | ||||||
|  | 		} else { | ||||||
|  | 			reply = append(reply, payload[:left]...) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return reply[:len(reply)-2], nil | ||||||
|  | } | ||||||
| @@ -1,898 +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 |  | ||||||
|  |  | ||||||
| package usbwallet |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"encoding/hex" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"math/big" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	ethereum "github.com/ethereum/go-ethereum" |  | ||||||
| 	"github.com/ethereum/go-ethereum/accounts" |  | ||||||
| 	"github.com/ethereum/go-ethereum/common" |  | ||||||
| 	"github.com/ethereum/go-ethereum/common/hexutil" |  | ||||||
| 	"github.com/ethereum/go-ethereum/core/types" |  | ||||||
| 	"github.com/ethereum/go-ethereum/log" |  | ||||||
| 	"github.com/ethereum/go-ethereum/rlp" |  | ||||||
| 	"github.com/karalabe/hid" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // ledgerOpcode is an enumeration encoding the supported Ledger opcodes. |  | ||||||
| type ledgerOpcode byte |  | ||||||
|  |  | ||||||
| // ledgerParam1 is an enumeration encoding the supported Ledger parameters for |  | ||||||
| // specific opcodes. The same parameter values may be reused between opcodes. |  | ||||||
| type ledgerParam1 byte |  | ||||||
|  |  | ||||||
| // ledgerParam2 is an enumeration encoding the supported Ledger parameters for |  | ||||||
| // specific opcodes. The same parameter values may be reused between opcodes. |  | ||||||
| type ledgerParam2 byte |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	ledgerOpRetrieveAddress  ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path |  | ||||||
| 	ledgerOpSignTransaction  ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters |  | ||||||
| 	ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration |  | ||||||
|  |  | ||||||
| 	ledgerP1DirectlyFetchAddress    ledgerParam1 = 0x00 // Return address directly from the wallet |  | ||||||
| 	ledgerP1ConfirmFetchAddress     ledgerParam1 = 0x01 // Require a user confirmation before returning the address |  | ||||||
| 	ledgerP1InitTransactionData     ledgerParam1 = 0x00 // First transaction data block for signing |  | ||||||
| 	ledgerP1ContTransactionData     ledgerParam1 = 0x80 // Subsequent transaction data block for signing |  | ||||||
| 	ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address |  | ||||||
| 	ledgerP2ReturnAddressChainCode  ledgerParam2 = 0x01 // Require a user confirmation before returning the address |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // 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 { |  | ||||||
| 	hub *LedgerHub    // USB hub the device originates from (TODO(karalabe): remove if hotplug lands on Windows) |  | ||||||
| 	url *accounts.URL // Textual URL uniquely identifying this wallet |  | ||||||
|  |  | ||||||
| 	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) |  | ||||||
| 	accounts []accounts.Account                         // List of derive accounts pinned on the Ledger |  | ||||||
| 	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations |  | ||||||
|  |  | ||||||
| 	deriveNextPath accounts.DerivationPath   // Next derivation path for account auto-discovery |  | ||||||
| 	deriveNextAddr common.Address            // Next derived account address for auto-discovery |  | ||||||
| 	deriveChain    ethereum.ChainStateReader // Blockchain state reader to discover used account with |  | ||||||
| 	deriveReq      chan chan struct{}        // Channel to request a self-derivation on |  | ||||||
| 	deriveQuit     chan chan error           // Channel to terminate the self-deriver with |  | ||||||
|  |  | ||||||
| 	healthQuit chan chan error |  | ||||||
|  |  | ||||||
| 	// Locking a hardware wallet is a bit special. Since hardware devices are lower |  | ||||||
| 	// performing, any communication with them might take a non negligible amount of |  | ||||||
| 	// time. Worse still, waiting for user confirmation can take arbitrarily long, |  | ||||||
| 	// but exclusive communication must be upheld during. Locking the entire wallet |  | ||||||
| 	// in the mean time however would stall any parts of the system that don't want |  | ||||||
| 	// to communicate, just read some state (e.g. list the accounts). |  | ||||||
| 	// |  | ||||||
| 	// As such, a hardware wallet needs two locks to function correctly. A state |  | ||||||
| 	// lock can be used to protect the wallet's software-side internal state, which |  | ||||||
| 	// must not be held exlusively during hardware communication. A communication |  | ||||||
| 	// lock can be used to achieve exclusive access to the device itself, this one |  | ||||||
| 	// however should allow "skipping" waiting for operations that might want to |  | ||||||
| 	// use the device, but can live without too (e.g. account self-derivation). |  | ||||||
| 	// |  | ||||||
| 	// Since we have two locks, it's important to know how to properly use them: |  | ||||||
| 	//   - Communication requires the `device` to not change, so obtaining the |  | ||||||
| 	//     commsLock should be done after having a stateLock. |  | ||||||
| 	//   - Communication must not disable read access to the wallet state, so it |  | ||||||
| 	//     must only ever hold a *read* lock to stateLock. |  | ||||||
| 	commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked |  | ||||||
| 	stateLock sync.RWMutex  // Protects read and write access to the wallet struct fields |  | ||||||
|  |  | ||||||
| 	log log.Logger // Contextual logger to tag the ledger with its id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // URL implements accounts.Wallet, returning the URL of the Ledger device. |  | ||||||
| func (w *ledgerWallet) URL() accounts.URL { |  | ||||||
| 	return *w.url // Immutable, no need for a lock |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Status implements accounts.Wallet, always whether the Ledger is opened, closed |  | ||||||
| // or whether the Ethereum app was not started on it. |  | ||||||
| func (w *ledgerWallet) Status() string { |  | ||||||
| 	w.stateLock.RLock() // No device communication, state lock is enough |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	if w.failure != nil { |  | ||||||
| 		return fmt.Sprintf("Failed: %v", w.failure) |  | ||||||
| 	} |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		return "Closed" |  | ||||||
| 	} |  | ||||||
| 	if w.browser { |  | ||||||
| 		return "Ethereum app in browser mode" |  | ||||||
| 	} |  | ||||||
| 	if w.offline() { |  | ||||||
| 		return "Ethereum app offline" |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // offline returns whether the wallet and the Ethereum app is offline or not. |  | ||||||
| // |  | ||||||
| // The method assumes that the state lock is held! |  | ||||||
| func (w *ledgerWallet) offline() bool { |  | ||||||
| 	return w.version == [3]byte{0, 0, 0} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // failed returns if the USB device wrapped by the wallet failed for some reason. |  | ||||||
| // This is used by the device scanner to report failed wallets as departed. |  | ||||||
| // |  | ||||||
| // The method assumes that the state lock is *not* held! |  | ||||||
| func (w *ledgerWallet) failed() bool { |  | ||||||
| 	w.stateLock.RLock() // No device communication, state lock is enough |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	return w.failure != nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Open implements accounts.Wallet, attempting to open a USB connection to the |  | ||||||
| // Ledger hardware wallet. The Ledger does not require a user passphrase, so that |  | ||||||
| // parameter is silently discarded. |  | ||||||
| func (w *ledgerWallet) Open(passphrase string) error { |  | ||||||
| 	w.stateLock.Lock() // State lock is enough since there's no connection yet at this point |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	// If the wallet was already opened, don't try to open again |  | ||||||
| 	if w.device != nil { |  | ||||||
| 		return accounts.ErrWalletAlreadyOpen |  | ||||||
| 	} |  | ||||||
| 	// Otherwise iterate over all USB devices and find this again (no way to directly do this) |  | ||||||
| 	device, err := w.info.Open() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	// Wallet seems to be successfully opened, guess if the Ethereum app is running |  | ||||||
| 	w.device = device |  | ||||||
| 	w.commsLock = make(chan struct{}, 1) |  | ||||||
| 	w.commsLock <- struct{}{} // Enable lock |  | ||||||
|  |  | ||||||
| 	w.paths = make(map[common.Address]accounts.DerivationPath) |  | ||||||
|  |  | ||||||
| 	w.deriveReq = make(chan chan struct{}) |  | ||||||
| 	w.deriveQuit = make(chan chan error) |  | ||||||
| 	w.healthQuit = make(chan chan error) |  | ||||||
|  |  | ||||||
| 	defer func() { |  | ||||||
| 		go w.heartbeat() |  | ||||||
| 		go w.selfDerive() |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	if _, err = w.ledgerDerive(accounts.DefaultBaseDerivationPath); err != nil { |  | ||||||
| 		// Ethereum app is not running or in browser mode, nothing more to do, return |  | ||||||
| 		if err == errReplyInvalidHeader { |  | ||||||
| 			w.browser = true |  | ||||||
| 		} |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	// Try to resolve the Ethereum app's version, will fail prior to v1.0.2 |  | ||||||
| 	if w.version, err = w.ledgerVersion(); err != nil { |  | ||||||
| 		w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 |  | ||||||
| 	} |  | ||||||
| 	go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // heartbeat is a health check loop for the Ledger wallets to periodically verify |  | ||||||
| // whether they are still present or if they malfunctioned. It is needed because: |  | ||||||
| //  - libusb on Windows doesn't support hotplug, so we can't detect USB unplugs |  | ||||||
| //  - communication timeout on the Ledger requires a device power cycle to fix |  | ||||||
| func (w *ledgerWallet) heartbeat() { |  | ||||||
| 	w.log.Debug("Ledger health-check started") |  | ||||||
| 	defer w.log.Debug("Ledger health-check stopped") |  | ||||||
|  |  | ||||||
| 	// Execute heartbeat checks until termination or error |  | ||||||
| 	var ( |  | ||||||
| 		errc chan error |  | ||||||
| 		err  error |  | ||||||
| 	) |  | ||||||
| 	for errc == nil && err == nil { |  | ||||||
| 		// Wait until termination is requested or the heartbeat cycle arrives |  | ||||||
| 		select { |  | ||||||
| 		case errc = <-w.healthQuit: |  | ||||||
| 			// Termination requested |  | ||||||
| 			continue |  | ||||||
| 		case <-time.After(heartbeatCycle): |  | ||||||
| 			// Heartbeat time |  | ||||||
| 		} |  | ||||||
| 		// Execute a tiny data exchange to see responsiveness |  | ||||||
| 		w.stateLock.RLock() |  | ||||||
| 		if w.device == nil { |  | ||||||
| 			// Terminated while waiting for the lock |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		<-w.commsLock // Don't lock state while resolving version |  | ||||||
| 		_, err = w.ledgerVersion() |  | ||||||
| 		w.commsLock <- struct{}{} |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 		if err != nil && err != errInvalidVersionReply { |  | ||||||
| 			w.stateLock.Lock() // Lock state to tear the wallet down |  | ||||||
| 			w.failure = err |  | ||||||
| 			w.close() |  | ||||||
| 			w.stateLock.Unlock() |  | ||||||
| 		} |  | ||||||
| 		// Ignore non hardware related errors |  | ||||||
| 		err = nil |  | ||||||
| 	} |  | ||||||
| 	// In case of error, wait for termination |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.log.Debug("Ledger health-check failed", "err", err) |  | ||||||
| 		errc = <-w.healthQuit |  | ||||||
| 	} |  | ||||||
| 	errc <- err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Close implements accounts.Wallet, closing the USB connection to the Ledger. |  | ||||||
| func (w *ledgerWallet) Close() error { |  | ||||||
| 	// Ensure the wallet was opened |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	hQuit, dQuit := w.healthQuit, w.deriveQuit |  | ||||||
| 	w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// Terminate the health checks |  | ||||||
| 	var herr error |  | ||||||
| 	if hQuit != nil { |  | ||||||
| 		errc := make(chan error) |  | ||||||
| 		hQuit <- errc |  | ||||||
| 		herr = <-errc // Save for later, we *must* close the USB |  | ||||||
| 	} |  | ||||||
| 	// Terminate the self-derivations |  | ||||||
| 	var derr error |  | ||||||
| 	if dQuit != nil { |  | ||||||
| 		errc := make(chan error) |  | ||||||
| 		dQuit <- errc |  | ||||||
| 		derr = <-errc // Save for later, we *must* close the USB |  | ||||||
| 	} |  | ||||||
| 	// Terminate the device connection |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	w.healthQuit = nil |  | ||||||
| 	w.deriveQuit = nil |  | ||||||
| 	w.deriveReq = nil |  | ||||||
|  |  | ||||||
| 	if err := w.close(); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if herr != nil { |  | ||||||
| 		return herr |  | ||||||
| 	} |  | ||||||
| 	return derr |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // close is the internal wallet closer that terminates the USB connection and |  | ||||||
| // resets all the fields to their defaults. |  | ||||||
| // |  | ||||||
| // Note, close assumes the state lock is held! |  | ||||||
| func (w *ledgerWallet) close() error { |  | ||||||
| 	// Allow duplicate closes, especially for health-check failures |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	// Close the device, clear everything, then return |  | ||||||
| 	w.device.Close() |  | ||||||
| 	w.device = nil |  | ||||||
|  |  | ||||||
| 	w.browser, w.version = false, [3]byte{} |  | ||||||
| 	w.accounts, w.paths = nil, nil |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Accounts implements accounts.Wallet, returning the list of accounts pinned to |  | ||||||
| // the Ledger hardware wallet. If self-derivation was enabled, the account list |  | ||||||
| // is periodically expanded based on current chain state. |  | ||||||
| func (w *ledgerWallet) Accounts() []accounts.Account { |  | ||||||
| 	// Attempt self-derivation if it's running |  | ||||||
| 	reqc := make(chan struct{}, 1) |  | ||||||
| 	select { |  | ||||||
| 	case w.deriveReq <- reqc: |  | ||||||
| 		// Self-derivation request accepted, wait for it |  | ||||||
| 		<-reqc |  | ||||||
| 	default: |  | ||||||
| 		// Self-derivation offline, throttled or busy, skip |  | ||||||
| 	} |  | ||||||
| 	// Return whatever account list we ended up with |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	cpy := make([]accounts.Account, len(w.accounts)) |  | ||||||
| 	copy(cpy, w.accounts) |  | ||||||
| 	return cpy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // selfDerive is an account derivation loop that upon request attempts to find |  | ||||||
| // new non-zero accounts. |  | ||||||
| func (w *ledgerWallet) selfDerive() { |  | ||||||
| 	w.log.Debug("Ledger self-derivation started") |  | ||||||
| 	defer w.log.Debug("Ledger self-derivation stopped") |  | ||||||
|  |  | ||||||
| 	// Execute self-derivations until termination or error |  | ||||||
| 	var ( |  | ||||||
| 		reqc chan struct{} |  | ||||||
| 		errc chan error |  | ||||||
| 		err  error |  | ||||||
| 	) |  | ||||||
| 	for errc == nil && err == nil { |  | ||||||
| 		// Wait until either derivation or termination is requested |  | ||||||
| 		select { |  | ||||||
| 		case errc = <-w.deriveQuit: |  | ||||||
| 			// Termination requested |  | ||||||
| 			continue |  | ||||||
| 		case reqc = <-w.deriveReq: |  | ||||||
| 			// Account discovery requested |  | ||||||
| 		} |  | ||||||
| 		// Derivation needs a chain and device access, skip if either unavailable |  | ||||||
| 		w.stateLock.RLock() |  | ||||||
| 		if w.device == nil || w.deriveChain == nil || w.offline() { |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			reqc <- struct{}{} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		select { |  | ||||||
| 		case <-w.commsLock: |  | ||||||
| 		default: |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			reqc <- struct{}{} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		// Device lock obtained, derive the next batch of accounts |  | ||||||
| 		var ( |  | ||||||
| 			accs  []accounts.Account |  | ||||||
| 			paths []accounts.DerivationPath |  | ||||||
|  |  | ||||||
| 			nextAddr = w.deriveNextAddr |  | ||||||
| 			nextPath = w.deriveNextPath |  | ||||||
|  |  | ||||||
| 			context = context.Background() |  | ||||||
| 		) |  | ||||||
| 		for empty := false; !empty; { |  | ||||||
| 			// Retrieve the next derived Ethereum account |  | ||||||
| 			if nextAddr == (common.Address{}) { |  | ||||||
| 				if nextAddr, err = w.ledgerDerive(nextPath); err != nil { |  | ||||||
| 					w.log.Warn("Ledger account derivation failed", "err", err) |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// Check the account's status against the current chain state |  | ||||||
| 			var ( |  | ||||||
| 				balance *big.Int |  | ||||||
| 				nonce   uint64 |  | ||||||
| 			) |  | ||||||
| 			balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				w.log.Warn("Ledger balance retrieval failed", "err", err) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				w.log.Warn("Ledger nonce retrieval failed", "err", err) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			// If the next account is empty, stop self-derivation, but add it nonetheless |  | ||||||
| 			if balance.Sign() == 0 && nonce == 0 { |  | ||||||
| 				empty = true |  | ||||||
| 			} |  | ||||||
| 			// We've just self-derived a new account, start tracking it locally |  | ||||||
| 			path := make(accounts.DerivationPath, len(nextPath)) |  | ||||||
| 			copy(path[:], nextPath[:]) |  | ||||||
| 			paths = append(paths, path) |  | ||||||
|  |  | ||||||
| 			account := accounts.Account{ |  | ||||||
| 				Address: nextAddr, |  | ||||||
| 				URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |  | ||||||
| 			} |  | ||||||
| 			accs = append(accs, account) |  | ||||||
|  |  | ||||||
| 			// Display a log message to the user for new (or previously empty accounts) |  | ||||||
| 			if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) { |  | ||||||
| 				w.log.Info("Ledger discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce) |  | ||||||
| 			} |  | ||||||
| 			// Fetch the next potential account |  | ||||||
| 			if !empty { |  | ||||||
| 				nextAddr = common.Address{} |  | ||||||
| 				nextPath[len(nextPath)-1]++ |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// Self derivation complete, release device lock |  | ||||||
| 		w.commsLock <- struct{}{} |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 		// Insert any accounts successfully derived |  | ||||||
| 		w.stateLock.Lock() |  | ||||||
| 		for i := 0; i < len(accs); i++ { |  | ||||||
| 			if _, ok := w.paths[accs[i].Address]; !ok { |  | ||||||
| 				w.accounts = append(w.accounts, accs[i]) |  | ||||||
| 				w.paths[accs[i].Address] = paths[i] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// Shift the self-derivation forward |  | ||||||
| 		// TODO(karalabe): don't overwrite changes from wallet.SelfDerive |  | ||||||
| 		w.deriveNextAddr = nextAddr |  | ||||||
| 		w.deriveNextPath = nextPath |  | ||||||
| 		w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 		// Notify the user of termination and loop after a bit of time (to avoid trashing) |  | ||||||
| 		reqc <- struct{}{} |  | ||||||
| 		if err == nil { |  | ||||||
| 			select { |  | ||||||
| 			case errc = <-w.deriveQuit: |  | ||||||
| 				// Termination requested, abort |  | ||||||
| 			case <-time.After(selfDeriveThrottling): |  | ||||||
| 				// Waited enough, willing to self-derive again |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// In case of error, wait for termination |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.log.Debug("Ledger self-derivation failed", "err", err) |  | ||||||
| 		errc = <-w.deriveQuit |  | ||||||
| 	} |  | ||||||
| 	errc <- err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Contains implements accounts.Wallet, returning whether a particular account is |  | ||||||
| // or is not pinned into this Ledger instance. Although we could attempt to resolve |  | ||||||
| // unpinned accounts, that would be an non-negligible hardware operation. |  | ||||||
| func (w *ledgerWallet) Contains(account accounts.Account) bool { |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	_, exists := w.paths[account.Address] |  | ||||||
| 	return exists |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Derive implements accounts.Wallet, deriving a new account at the specific |  | ||||||
| // derivation path. If pin is set to true, the account will be added to the list |  | ||||||
| // of tracked accounts. |  | ||||||
| func (w *ledgerWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { |  | ||||||
| 	// Try to derive the actual account and update its URL if successful |  | ||||||
| 	w.stateLock.RLock() // Avoid device disappearing during derivation |  | ||||||
|  |  | ||||||
| 	if w.device == nil || w.offline() { |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
| 		return accounts.Account{}, accounts.ErrWalletClosed |  | ||||||
| 	} |  | ||||||
| 	<-w.commsLock // Avoid concurrent hardware access |  | ||||||
| 	address, err := w.ledgerDerive(path) |  | ||||||
| 	w.commsLock <- struct{}{} |  | ||||||
|  |  | ||||||
| 	w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// If an error occurred or no pinning was requested, return |  | ||||||
| 	if err != nil { |  | ||||||
| 		return accounts.Account{}, err |  | ||||||
| 	} |  | ||||||
| 	account := accounts.Account{ |  | ||||||
| 		Address: address, |  | ||||||
| 		URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |  | ||||||
| 	} |  | ||||||
| 	if !pin { |  | ||||||
| 		return account, nil |  | ||||||
| 	} |  | ||||||
| 	// Pinning needs to modify the state |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	if _, ok := w.paths[address]; !ok { |  | ||||||
| 		w.accounts = append(w.accounts, account) |  | ||||||
| 		w.paths[address] = path |  | ||||||
| 	} |  | ||||||
| 	return account, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SelfDerive implements accounts.Wallet, trying to discover accounts that the |  | ||||||
| // user used previously (based on the chain state), but ones that he/she did not |  | ||||||
| // explicitly pin to the wallet manually. To avoid chain head monitoring, self |  | ||||||
| // derivation only runs during account listing (and even then throttled). |  | ||||||
| func (w *ledgerWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) { |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	w.deriveNextPath = make(accounts.DerivationPath, len(base)) |  | ||||||
| 	copy(w.deriveNextPath[:], base[:]) |  | ||||||
|  |  | ||||||
| 	w.deriveNextAddr = common.Address{} |  | ||||||
| 	w.deriveChain = chain |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignHash implements accounts.Wallet, however signing arbitrary data is not |  | ||||||
| // supported for Ledger wallets, so this method will always return an error. |  | ||||||
| func (w *ledgerWallet) SignHash(acc accounts.Account, hash []byte) ([]byte, error) { |  | ||||||
| 	return nil, accounts.ErrNotSupported |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger |  | ||||||
| // wallet to request a confirmation from the user. It returns either the signed |  | ||||||
| // transaction or a failure if the user denied the transaction. |  | ||||||
| // |  | ||||||
| // Note, if the version of the Ethereum application running on the Ledger wallet is |  | ||||||
| // too old to sign EIP-155 transactions, but such is requested nonetheless, an error |  | ||||||
| // will be returned opposed to silently signing in Homestead mode. |  | ||||||
| func (w *ledgerWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |  | ||||||
| 	w.stateLock.RLock() // Comms have own mutex, this is for the state fields |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// If the wallet is closed, or the Ethereum app doesn't run, abort |  | ||||||
| 	if w.device == nil || w.offline() { |  | ||||||
| 		return nil, accounts.ErrWalletClosed |  | ||||||
| 	} |  | ||||||
| 	// Make sure the requested account is contained within |  | ||||||
| 	path, ok := w.paths[account.Address] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, accounts.ErrUnknownAccount |  | ||||||
| 	} |  | ||||||
| 	// Ensure the wallet is capable of signing the given transaction |  | ||||||
| 	if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { |  | ||||||
| 		return nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) |  | ||||||
| 	} |  | ||||||
| 	// All infos gathered and metadata checks out, request signing |  | ||||||
| 	<-w.commsLock |  | ||||||
| 	defer func() { w.commsLock <- struct{}{} }() |  | ||||||
|  |  | ||||||
| 	// Ensure the device isn't screwed with while user confirmation is pending |  | ||||||
| 	// TODO(karalabe): remove if hotplug lands on Windows |  | ||||||
| 	w.hub.commsLock.Lock() |  | ||||||
| 	w.hub.commsPend++ |  | ||||||
| 	w.hub.commsLock.Unlock() |  | ||||||
|  |  | ||||||
| 	defer func() { |  | ||||||
| 		w.hub.commsLock.Lock() |  | ||||||
| 		w.hub.commsPend-- |  | ||||||
| 		w.hub.commsLock.Unlock() |  | ||||||
| 	}() |  | ||||||
| 	return w.ledgerSign(path, account.Address, tx, chainID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary |  | ||||||
| // data is not supported for Ledger wallets, so this method will always return |  | ||||||
| // an error. |  | ||||||
| func (w *ledgerWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { |  | ||||||
| 	return nil, accounts.ErrNotSupported |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given |  | ||||||
| // transaction with the given account using passphrase as extra authentication. |  | ||||||
| // Since the Ledger does not support extra passphrases, it is silently ignored. |  | ||||||
| func (w *ledgerWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |  | ||||||
| 	return w.SignTx(account, tx, chainID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ledgerVersion retrieves the current version of the Ethereum wallet app running |  | ||||||
| // on the Ledger wallet. |  | ||||||
| // |  | ||||||
| // The version retrieval protocol is defined as follows: |  | ||||||
| // |  | ||||||
| //   CLA | INS | P1 | P2 | Lc | Le |  | ||||||
| //   ----+-----+----+----+----+--- |  | ||||||
| //    E0 | 06  | 00 | 00 | 00 | 04 |  | ||||||
| // |  | ||||||
| // With no input data, and the output data being: |  | ||||||
| // |  | ||||||
| //   Description                                        | Length |  | ||||||
| //   ---------------------------------------------------+-------- |  | ||||||
| //   Flags 01: arbitrary data signature enabled by user | 1 byte |  | ||||||
| //   Application major version                          | 1 byte |  | ||||||
| //   Application minor version                          | 1 byte |  | ||||||
| //   Application patch version                          | 1 byte |  | ||||||
| func (w *ledgerWallet) ledgerVersion() ([3]byte, error) { |  | ||||||
| 	// Send the request and wait for the response |  | ||||||
| 	reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return [3]byte{}, err |  | ||||||
| 	} |  | ||||||
| 	if len(reply) != 4 { |  | ||||||
| 		return [3]byte{}, errInvalidVersionReply |  | ||||||
| 	} |  | ||||||
| 	// Cache the version for future reference |  | ||||||
| 	var version [3]byte |  | ||||||
| 	copy(version[:], reply[1:]) |  | ||||||
| 	return version, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ledgerDerive retrieves the currently active Ethereum address from a Ledger |  | ||||||
| // wallet at the specified derivation path. |  | ||||||
| // |  | ||||||
| // The address derivation protocol is defined as follows: |  | ||||||
| // |  | ||||||
| //   CLA | INS | P1 | P2 | Lc  | Le |  | ||||||
| //   ----+-----+----+----+-----+--- |  | ||||||
| //    E0 | 02  | 00 return address |  | ||||||
| //               01 display address and confirm before returning |  | ||||||
| //                  | 00: do not return the chain code |  | ||||||
| //                  | 01: return the chain code |  | ||||||
| //                       | var | 00 |  | ||||||
| // |  | ||||||
| // Where the input data is: |  | ||||||
| // |  | ||||||
| //   Description                                      | Length |  | ||||||
| //   -------------------------------------------------+-------- |  | ||||||
| //   Number of BIP 32 derivations to perform (max 10) | 1 byte |  | ||||||
| //   First derivation index (big endian)              | 4 bytes |  | ||||||
| //   ...                                              | 4 bytes |  | ||||||
| //   Last derivation index (big endian)               | 4 bytes |  | ||||||
| // |  | ||||||
| // And the output data is: |  | ||||||
| // |  | ||||||
| //   Description             | Length |  | ||||||
| //   ------------------------+------------------- |  | ||||||
| //   Public Key length       | 1 byte |  | ||||||
| //   Uncompressed Public Key | arbitrary |  | ||||||
| //   Ethereum address length | 1 byte |  | ||||||
| //   Ethereum address        | 40 bytes hex ascii |  | ||||||
| //   Chain code if requested | 32 bytes |  | ||||||
| func (w *ledgerWallet) ledgerDerive(derivationPath []uint32) (common.Address, error) { |  | ||||||
| 	// Flatten the derivation path into the Ledger request |  | ||||||
| 	path := make([]byte, 1+4*len(derivationPath)) |  | ||||||
| 	path[0] = byte(len(derivationPath)) |  | ||||||
| 	for i, component := range derivationPath { |  | ||||||
| 		binary.BigEndian.PutUint32(path[1+4*i:], component) |  | ||||||
| 	} |  | ||||||
| 	// Send the request and wait for the response |  | ||||||
| 	reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return common.Address{}, err |  | ||||||
| 	} |  | ||||||
| 	// Discard the public key, we don't need that for now |  | ||||||
| 	if len(reply) < 1 || len(reply) < 1+int(reply[0]) { |  | ||||||
| 		return common.Address{}, errors.New("reply lacks public key entry") |  | ||||||
| 	} |  | ||||||
| 	reply = reply[1+int(reply[0]):] |  | ||||||
|  |  | ||||||
| 	// Extract the Ethereum hex address string |  | ||||||
| 	if len(reply) < 1 || len(reply) < 1+int(reply[0]) { |  | ||||||
| 		return common.Address{}, errors.New("reply lacks address entry") |  | ||||||
| 	} |  | ||||||
| 	hexstr := reply[1 : 1+int(reply[0])] |  | ||||||
|  |  | ||||||
| 	// Decode the hex sting into an Ethereum address and return |  | ||||||
| 	var address common.Address |  | ||||||
| 	hex.Decode(address[:], hexstr) |  | ||||||
| 	return address, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ledgerSign sends the transaction to the Ledger wallet, and waits for the user |  | ||||||
| // to confirm or deny the transaction. |  | ||||||
| // |  | ||||||
| // The transaction signing protocol is defined as follows: |  | ||||||
| // |  | ||||||
| //   CLA | INS | P1 | P2 | Lc  | Le |  | ||||||
| //   ----+-----+----+----+-----+--- |  | ||||||
| //    E0 | 04  | 00: first transaction data block |  | ||||||
| //               80: subsequent transaction data block |  | ||||||
| //                  | 00 | variable | variable |  | ||||||
| // |  | ||||||
| // Where the input for the first transaction block (first 255 bytes) is: |  | ||||||
| // |  | ||||||
| //   Description                                      | Length |  | ||||||
| //   -------------------------------------------------+---------- |  | ||||||
| //   Number of BIP 32 derivations to perform (max 10) | 1 byte |  | ||||||
| //   First derivation index (big endian)              | 4 bytes |  | ||||||
| //   ...                                              | 4 bytes |  | ||||||
| //   Last derivation index (big endian)               | 4 bytes |  | ||||||
| //   RLP transaction chunk                            | arbitrary |  | ||||||
| // |  | ||||||
| // And the input for subsequent transaction blocks (first 255 bytes) are: |  | ||||||
| // |  | ||||||
| //   Description           | Length |  | ||||||
| //   ----------------------+---------- |  | ||||||
| //   RLP transaction chunk | arbitrary |  | ||||||
| // |  | ||||||
| // And the output data is: |  | ||||||
| // |  | ||||||
| //   Description | Length |  | ||||||
| //   ------------+--------- |  | ||||||
| //   signature V | 1 byte |  | ||||||
| //   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) { |  | ||||||
| 	// Flatten the derivation path into the Ledger request |  | ||||||
| 	path := make([]byte, 1+4*len(derivationPath)) |  | ||||||
| 	path[0] = byte(len(derivationPath)) |  | ||||||
| 	for i, component := range derivationPath { |  | ||||||
| 		binary.BigEndian.PutUint32(path[1+4*i:], component) |  | ||||||
| 	} |  | ||||||
| 	// Create the transaction RLP based on whether legacy or EIP155 signing was requeste |  | ||||||
| 	var ( |  | ||||||
| 		txrlp []byte |  | ||||||
| 		err   error |  | ||||||
| 	) |  | ||||||
| 	if chainID == nil { |  | ||||||
| 		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	payload := append(path, txrlp...) |  | ||||||
|  |  | ||||||
| 	// Send the request and wait for the response |  | ||||||
| 	var ( |  | ||||||
| 		op    = ledgerP1InitTransactionData |  | ||||||
| 		reply []byte |  | ||||||
| 	) |  | ||||||
| 	for len(payload) > 0 { |  | ||||||
| 		// Calculate the size of the next data chunk |  | ||||||
| 		chunk := 255 |  | ||||||
| 		if chunk > len(payload) { |  | ||||||
| 			chunk = len(payload) |  | ||||||
| 		} |  | ||||||
| 		// Send the chunk over, ensuring it's processed correctly |  | ||||||
| 		reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		// Shift the payload and ensure subsequent chunks are marked as such |  | ||||||
| 		payload = payload[chunk:] |  | ||||||
| 		op = ledgerP1ContTransactionData |  | ||||||
| 	} |  | ||||||
| 	// Extract the Ethereum signature and do a sanity validation |  | ||||||
| 	if len(reply) != 65 { |  | ||||||
| 		return nil, errors.New("reply lacks signature") |  | ||||||
| 	} |  | ||||||
| 	signature := append(reply[1:], reply[0]) |  | ||||||
|  |  | ||||||
| 	// Create the correct signer and signature transform based on the chain ID |  | ||||||
| 	var signer types.Signer |  | ||||||
| 	if chainID == nil { |  | ||||||
| 		signer = new(types.HomesteadSigner) |  | ||||||
| 	} else { |  | ||||||
| 		signer = types.NewEIP155Signer(chainID) |  | ||||||
| 		signature[64] = signature[64] - byte(chainID.Uint64()*2+35) |  | ||||||
| 	} |  | ||||||
| 	// Inject the final signature into the transaction and sanity check the sender |  | ||||||
| 	signed, err := tx.WithSignature(signer, signature) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	sender, err := types.Sender(signer, signed) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if sender != address { |  | ||||||
| 		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", address.Hex(), sender.Hex()) |  | ||||||
| 	} |  | ||||||
| 	return signed, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ledgerExchange performs a data exchange with the Ledger wallet, sending it a |  | ||||||
| // message and retrieving the response. |  | ||||||
| // |  | ||||||
| // The common transport header is defined as follows: |  | ||||||
| // |  | ||||||
| //  Description                           | Length |  | ||||||
| //  --------------------------------------+---------- |  | ||||||
| //  Communication channel ID (big endian) | 2 bytes |  | ||||||
| //  Command tag                           | 1 byte |  | ||||||
| //  Packet sequence index (big endian)    | 2 bytes |  | ||||||
| //  Payload                               | arbitrary |  | ||||||
| // |  | ||||||
| // The Communication channel ID allows commands multiplexing over the same |  | ||||||
| // physical link. It is not used for the time being, and should be set to 0101 |  | ||||||
| // to avoid compatibility issues with implementations ignoring a leading 00 byte. |  | ||||||
| // |  | ||||||
| // The Command tag describes the message content. Use TAG_APDU (0x05) for standard |  | ||||||
| // APDU payloads, or TAG_PING (0x02) for a simple link test. |  | ||||||
| // |  | ||||||
| // The Packet sequence index describes the current sequence for fragmented payloads. |  | ||||||
| // The first fragment index is 0x00. |  | ||||||
| // |  | ||||||
| // APDU Command payloads are encoded as follows: |  | ||||||
| // |  | ||||||
| //  Description              | Length |  | ||||||
| //  ----------------------------------- |  | ||||||
| //  APDU length (big endian) | 2 bytes |  | ||||||
| //  APDU CLA                 | 1 byte |  | ||||||
| //  APDU INS                 | 1 byte |  | ||||||
| //  APDU P1                  | 1 byte |  | ||||||
| //  APDU P2                  | 1 byte |  | ||||||
| //  APDU length              | 1 byte |  | ||||||
| //  Optional APDU data       | arbitrary |  | ||||||
| func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { |  | ||||||
| 	// Construct the message payload, possibly split into multiple chunks |  | ||||||
| 	apdu := make([]byte, 2, 7+len(data)) |  | ||||||
|  |  | ||||||
| 	binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) |  | ||||||
| 	apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) |  | ||||||
| 	apdu = append(apdu, data...) |  | ||||||
|  |  | ||||||
| 	// Stream all the chunks to the device |  | ||||||
| 	header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended |  | ||||||
| 	chunk := make([]byte, 64) |  | ||||||
| 	space := len(chunk) - len(header) |  | ||||||
|  |  | ||||||
| 	for i := 0; len(apdu) > 0; i++ { |  | ||||||
| 		// Construct the new message to stream |  | ||||||
| 		chunk = append(chunk[:0], header...) |  | ||||||
| 		binary.BigEndian.PutUint16(chunk[3:], uint16(i)) |  | ||||||
|  |  | ||||||
| 		if len(apdu) > space { |  | ||||||
| 			chunk = append(chunk, apdu[:space]...) |  | ||||||
| 			apdu = apdu[space:] |  | ||||||
| 		} else { |  | ||||||
| 			chunk = append(chunk, apdu...) |  | ||||||
| 			apdu = nil |  | ||||||
| 		} |  | ||||||
| 		// Send over to the device |  | ||||||
| 		w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) |  | ||||||
| 		if _, err := w.device.Write(chunk); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Stream the reply back from the wallet in 64 byte chunks |  | ||||||
| 	var reply []byte |  | ||||||
| 	chunk = chunk[:64] // Yeah, we surely have enough space |  | ||||||
| 	for { |  | ||||||
| 		// Read the next chunk from the Ledger wallet |  | ||||||
| 		if _, err := io.ReadFull(w.device, chunk); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) |  | ||||||
|  |  | ||||||
| 		// Make sure the transport header matches |  | ||||||
| 		if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { |  | ||||||
| 			return nil, errReplyInvalidHeader |  | ||||||
| 		} |  | ||||||
| 		// If it's the first chunk, retrieve the total message length |  | ||||||
| 		var payload []byte |  | ||||||
|  |  | ||||||
| 		if chunk[3] == 0x00 && chunk[4] == 0x00 { |  | ||||||
| 			reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) |  | ||||||
| 			payload = chunk[7:] |  | ||||||
| 		} else { |  | ||||||
| 			payload = chunk[5:] |  | ||||||
| 		} |  | ||||||
| 		// Append to the reply and stop when filled up |  | ||||||
| 		if left := cap(reply) - len(reply); left > len(payload) { |  | ||||||
| 			reply = append(reply, payload...) |  | ||||||
| 		} else { |  | ||||||
| 			reply = append(reply, payload[:left]...) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return reply[:len(reply)-2], nil |  | ||||||
| } |  | ||||||
							
								
								
									
										330
									
								
								accounts/usbwallet/trezor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										330
									
								
								accounts/usbwallet/trezor.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,330 @@ | |||||||
|  | // 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 Trezor hardware | ||||||
|  | // wallets. The wire protocol spec can be found on the SatoshiLabs website: | ||||||
|  | // https://doc.satoshilabs.com/trezor-tech/api-protobuf.html | ||||||
|  |  | ||||||
|  | package usbwallet | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"encoding/binary" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  |  | ||||||
|  | 	"github.com/ethereum/go-ethereum/accounts" | ||||||
|  | 	"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor" | ||||||
|  | 	"github.com/ethereum/go-ethereum/common" | ||||||
|  | 	"github.com/ethereum/go-ethereum/common/hexutil" | ||||||
|  | 	"github.com/ethereum/go-ethereum/core/types" | ||||||
|  | 	"github.com/ethereum/go-ethereum/log" | ||||||
|  | 	"github.com/golang/protobuf/proto" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In | ||||||
|  | // this case, the calling application should display a pinpad and send back the | ||||||
|  | // encoded passphrase. | ||||||
|  | var ErrTrezorPINNeeded = errors.New("trezor: pin needed") | ||||||
|  |  | ||||||
|  | // errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange | ||||||
|  | // if the device replies with a mismatching header. This usually means the device | ||||||
|  | // is in browser mode. | ||||||
|  | var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header") | ||||||
|  |  | ||||||
|  | // trezorDriver implements the communication with a Trezor hardware wallet. | ||||||
|  | type trezorDriver struct { | ||||||
|  | 	device  io.ReadWriter // USB device connection to communicate through | ||||||
|  | 	version [3]uint32     // Current version of the Trezor firmware | ||||||
|  | 	label   string        // Current textual label of the Trezor device | ||||||
|  | 	pinwait bool          // Flags whether the device is waiting for PIN entry | ||||||
|  | 	failure error         // Any failure that would make the device unusable | ||||||
|  | 	log     log.Logger    // Contextual logger to tag the trezor with its id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // newTrezorDriver creates a new instance of a Trezor USB protocol driver. | ||||||
|  | func newTrezorDriver(logger log.Logger) driver { | ||||||
|  | 	return &trezorDriver{ | ||||||
|  | 		log: logger, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Status implements accounts.Wallet, always whether the Trezor is opened, closed | ||||||
|  | // or whether the Ethereum app was not started on it. | ||||||
|  | func (w *trezorDriver) Status() (string, error) { | ||||||
|  | 	if w.failure != nil { | ||||||
|  | 		return fmt.Sprintf("Failed: %v", w.failure), w.failure | ||||||
|  | 	} | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		return "Closed", w.failure | ||||||
|  | 	} | ||||||
|  | 	if w.pinwait { | ||||||
|  | 		return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open implements usbwallet.driver, attempting to initialize the connection to | ||||||
|  | // the Trezor hardware wallet. Initializing the Trezor is a two phase operation: | ||||||
|  | //  * The first phase is to initialize the connection and read the wallet's | ||||||
|  | //    features. This phase is invoked is the provided passphrase is empty. The | ||||||
|  | //    device will display the pinpad as a result and will return an appropriate | ||||||
|  | //    error to notify the user that a second open phase is needed. | ||||||
|  | //  * The second phase is to unlock access to the Trezor, which is done by the | ||||||
|  | //    user actually providing a passphrase mapping a keyboard keypad to the pin | ||||||
|  | //    number of the user (shuffled according to the pinpad displayed). | ||||||
|  | func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { | ||||||
|  | 	w.device, w.failure = device, nil | ||||||
|  |  | ||||||
|  | 	// If phase 1 is requested, init the connection and wait for user callback | ||||||
|  | 	if passphrase == "" { | ||||||
|  | 		// If we're already waiting for a PIN entry, insta-return | ||||||
|  | 		if w.pinwait { | ||||||
|  | 			return ErrTrezorPINNeeded | ||||||
|  | 		} | ||||||
|  | 		// Initialize a connection to the device | ||||||
|  | 		features := new(trezor.Features) | ||||||
|  | 		if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} | ||||||
|  | 		w.label = features.GetLabel() | ||||||
|  |  | ||||||
|  | 		// Do a manual ping, forcing the device to ask for its PIN | ||||||
|  | 		askPin := true | ||||||
|  | 		res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success)) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		// Only return the PIN request if the device wasn't unlocked until now | ||||||
|  | 		if res == 1 { | ||||||
|  | 			return nil // Device responded with trezor.Success | ||||||
|  | 		} | ||||||
|  | 		w.pinwait = true | ||||||
|  | 		return ErrTrezorPINNeeded | ||||||
|  | 	} | ||||||
|  | 	// Phase 2 requested with actual PIN entry | ||||||
|  | 	w.pinwait = false | ||||||
|  |  | ||||||
|  | 	if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil { | ||||||
|  | 		w.failure = err | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements usbwallet.driver, cleaning up and metadata maintained within | ||||||
|  | // the Trezor driver. | ||||||
|  | func (w *trezorDriver) Close() error { | ||||||
|  | 	w.version, w.label, w.pinwait = [3]uint32{}, "", false | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Heartbeat implements usbwallet.driver, performing a sanity check against the | ||||||
|  | // Trezor to see if it's still online. | ||||||
|  | func (w *trezorDriver) Heartbeat() error { | ||||||
|  | 	if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil { | ||||||
|  | 		w.failure = err | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Derive implements usbwallet.driver, sending a derivation request to the Trezor | ||||||
|  | // and returning the Ethereum address located on that derivation path. | ||||||
|  | func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) { | ||||||
|  | 	return w.trezorDerive(path) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignTx implements usbwallet.driver, sending the transaction to the Trezor and | ||||||
|  | // waiting for the user to confirm or deny the transaction. | ||||||
|  | func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		return common.Address{}, nil, accounts.ErrWalletClosed | ||||||
|  | 	} | ||||||
|  | 	return w.trezorSign(path, tx, chainID) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // trezorDerive sends a derivation request to the Trezor device and returns the | ||||||
|  | // Ethereum address located on that path. | ||||||
|  | func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) { | ||||||
|  | 	address := new(trezor.EthereumAddress) | ||||||
|  | 	if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil { | ||||||
|  | 		return common.Address{}, err | ||||||
|  | 	} | ||||||
|  | 	return common.BytesToAddress(address.GetAddress()), nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // trezorSign sends the transaction to the Trezor wallet, and waits for the user | ||||||
|  | // to confirm or deny the transaction. | ||||||
|  | func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { | ||||||
|  | 	// Create the transaction initiation message | ||||||
|  | 	data := tx.Data() | ||||||
|  | 	length := uint32(len(data)) | ||||||
|  |  | ||||||
|  | 	request := &trezor.EthereumSignTx{ | ||||||
|  | 		AddressN:   derivationPath, | ||||||
|  | 		Nonce:      new(big.Int).SetUint64(tx.Nonce()).Bytes(), | ||||||
|  | 		GasPrice:   tx.GasPrice().Bytes(), | ||||||
|  | 		GasLimit:   tx.Gas().Bytes(), | ||||||
|  | 		Value:      tx.Value().Bytes(), | ||||||
|  | 		DataLength: &length, | ||||||
|  | 	} | ||||||
|  | 	if to := tx.To(); to != nil { | ||||||
|  | 		request.To = (*to)[:] // Non contract deploy, set recipient explicitly | ||||||
|  | 	} | ||||||
|  | 	if length > 1024 { // Send the data chunked if that was requested | ||||||
|  | 		request.DataInitialChunk, data = data[:1024], data[1024:] | ||||||
|  | 	} else { | ||||||
|  | 		request.DataInitialChunk, data = data, nil | ||||||
|  | 	} | ||||||
|  | 	if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) | ||||||
|  | 		id := uint32(chainID.Int64()) | ||||||
|  | 		request.ChainId = &id | ||||||
|  | 	} | ||||||
|  | 	// Send the initiation message and stream content until a signature is returned | ||||||
|  | 	response := new(trezor.EthereumTxRequest) | ||||||
|  | 	if _, err := w.trezorExchange(request, response); err != nil { | ||||||
|  | 		return common.Address{}, nil, err | ||||||
|  | 	} | ||||||
|  | 	for response.DataLength != nil && int(*response.DataLength) <= len(data) { | ||||||
|  | 		chunk := data[:*response.DataLength] | ||||||
|  | 		data = data[*response.DataLength:] | ||||||
|  |  | ||||||
|  | 		if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil { | ||||||
|  | 			return common.Address{}, nil, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Extract the Ethereum signature and do a sanity validation | ||||||
|  | 	if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 { | ||||||
|  | 		return common.Address{}, nil, errors.New("reply lacks signature") | ||||||
|  | 	} | ||||||
|  | 	signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) | ||||||
|  |  | ||||||
|  | 	// Create the correct signer and signature transform based on the chain ID | ||||||
|  | 	var signer types.Signer | ||||||
|  | 	if chainID == nil { | ||||||
|  | 		signer = new(types.HomesteadSigner) | ||||||
|  | 	} else { | ||||||
|  | 		signer = types.NewEIP155Signer(chainID) | ||||||
|  | 		signature[64] = signature[64] - byte(chainID.Uint64()*2+35) | ||||||
|  | 	} | ||||||
|  | 	// Inject the final signature into the transaction and sanity check the sender | ||||||
|  | 	signed, err := tx.WithSignature(signer, signature) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return common.Address{}, nil, err | ||||||
|  | 	} | ||||||
|  | 	sender, err := types.Sender(signer, signed) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return common.Address{}, nil, err | ||||||
|  | 	} | ||||||
|  | 	return sender, signed, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // trezorExchange performs a data exchange with the Trezor wallet, sending it a | ||||||
|  | // message and retrieving the response. If multiple responses are possible, the | ||||||
|  | // method will also return the index of the destination object used. | ||||||
|  | func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) { | ||||||
|  | 	// Construct the original message payload to chunk up | ||||||
|  | 	data, err := proto.Marshal(req) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  | 	payload := make([]byte, 8+len(data)) | ||||||
|  | 	copy(payload, []byte{0x23, 0x23}) | ||||||
|  | 	binary.BigEndian.PutUint16(payload[2:], trezor.Type(req)) | ||||||
|  | 	binary.BigEndian.PutUint32(payload[4:], uint32(len(data))) | ||||||
|  | 	copy(payload[8:], data) | ||||||
|  |  | ||||||
|  | 	// Stream all the chunks to the device | ||||||
|  | 	chunk := make([]byte, 64) | ||||||
|  | 	chunk[0] = 0x3f // Report ID magic number | ||||||
|  |  | ||||||
|  | 	for len(payload) > 0 { | ||||||
|  | 		// Construct the new message to stream, padding with zeroes if needed | ||||||
|  | 		if len(payload) > 63 { | ||||||
|  | 			copy(chunk[1:], payload[:63]) | ||||||
|  | 			payload = payload[63:] | ||||||
|  | 		} else { | ||||||
|  | 			copy(chunk[1:], payload) | ||||||
|  | 			copy(chunk[1+len(payload):], make([]byte, 63-len(payload))) | ||||||
|  | 			payload = nil | ||||||
|  | 		} | ||||||
|  | 		// Send over to the device | ||||||
|  | 		w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk)) | ||||||
|  | 		if _, err := w.device.Write(chunk); err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Stream the reply back from the wallet in 64 byte chunks | ||||||
|  | 	var ( | ||||||
|  | 		kind  uint16 | ||||||
|  | 		reply []byte | ||||||
|  | 	) | ||||||
|  | 	for { | ||||||
|  | 		// Read the next chunk from the Trezor wallet | ||||||
|  | 		if _, err := io.ReadFull(w.device, chunk); err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 		w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk)) | ||||||
|  |  | ||||||
|  | 		// Make sure the transport header matches | ||||||
|  | 		if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) { | ||||||
|  | 			return 0, errTrezorReplyInvalidHeader | ||||||
|  | 		} | ||||||
|  | 		// If it's the first chunk, retrieve the reply message type and total message length | ||||||
|  | 		var payload []byte | ||||||
|  |  | ||||||
|  | 		if len(reply) == 0 { | ||||||
|  | 			kind = binary.BigEndian.Uint16(chunk[3:5]) | ||||||
|  | 			reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9]))) | ||||||
|  | 			payload = chunk[9:] | ||||||
|  | 		} else { | ||||||
|  | 			payload = chunk[1:] | ||||||
|  | 		} | ||||||
|  | 		// Append to the reply and stop when filled up | ||||||
|  | 		if left := cap(reply) - len(reply); left > len(payload) { | ||||||
|  | 			reply = append(reply, payload...) | ||||||
|  | 		} else { | ||||||
|  | 			reply = append(reply, payload[:left]...) | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// Try to parse the reply into the requested reply message | ||||||
|  | 	if kind == uint16(trezor.MessageType_MessageType_Failure) { | ||||||
|  | 		// Trezor returned a failure, extract and return the message | ||||||
|  | 		failure := new(trezor.Failure) | ||||||
|  | 		if err := proto.Unmarshal(reply, failure); err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 		return 0, errors.New("trezor: " + failure.GetMessage()) | ||||||
|  | 	} | ||||||
|  | 	if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { | ||||||
|  | 		// Trezor is waiting for user confirmation, ack and wait for the next message | ||||||
|  | 		return w.trezorExchange(&trezor.ButtonAck{}, results...) | ||||||
|  | 	} | ||||||
|  | 	for i, res := range results { | ||||||
|  | 		if trezor.Type(res) == kind { | ||||||
|  | 			return i, proto.Unmarshal(reply, res) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	expected := make([]string, len(results)) | ||||||
|  | 	for i, res := range results { | ||||||
|  | 		expected[i] = trezor.Name(trezor.Type(res)) | ||||||
|  | 	} | ||||||
|  | 	return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind)) | ||||||
|  | } | ||||||
| @@ -1,210 +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/>. |  | ||||||
|  |  | ||||||
| package usbwallet |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"runtime" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/ethereum/go-ethereum/accounts" |  | ||||||
| 	"github.com/ethereum/go-ethereum/event" |  | ||||||
| 	"github.com/ethereum/go-ethereum/log" |  | ||||||
| 	"github.com/karalabe/hid" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // TrezorScheme is the protocol scheme prefixing account and wallet URLs. |  | ||||||
| var TrezorScheme = "trezor" |  | ||||||
|  |  | ||||||
| // trezorVendorID is the USB vendor ID for SatoshiLabs. |  | ||||||
| var trezorVendorID = uint16(0x534c) |  | ||||||
|  |  | ||||||
| // trezorDeviceID is the USB device ID for the Trezor 1. |  | ||||||
| var trezorDeviceID = uint16(0x0001) |  | ||||||
|  |  | ||||||
| // Maximum time between wallet refreshes (if USB hotplug notifications don't work). |  | ||||||
| const trezorRefreshCycle = time.Second |  | ||||||
|  |  | ||||||
| // Minimum time between wallet refreshes to avoid USB trashing. |  | ||||||
| const trezorRefreshThrottling = 500 * time.Millisecond |  | ||||||
|  |  | ||||||
| // TrezorHub is a accounts.Backend that can find and handle Trezor hardware wallets. |  | ||||||
| type TrezorHub struct { |  | ||||||
| 	refreshed   time.Time               // Time instance when the list of wallets was last refreshed |  | ||||||
| 	wallets     []accounts.Wallet       // List of Trezor devices currently tracking |  | ||||||
| 	updateFeed  event.Feed              // Event feed to notify wallet additions/removals |  | ||||||
| 	updateScope event.SubscriptionScope // Subscription scope tracking current live listeners |  | ||||||
| 	updating    bool                    // Whether the event notification loop is running |  | ||||||
|  |  | ||||||
| 	quit chan chan error |  | ||||||
|  |  | ||||||
| 	stateLock sync.RWMutex // Protects the internals of the hub from racey access |  | ||||||
|  |  | ||||||
| 	// TODO(karalabe): remove if hotplug lands on Windows |  | ||||||
| 	commsPend int        // Number of operations blocking enumeration |  | ||||||
| 	commsLock sync.Mutex // Lock protecting the pending counter and enumeration |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewTrezorHub creates a new hardware wallet manager for Trezor devices. |  | ||||||
| func NewTrezorHub() (*TrezorHub, error) { |  | ||||||
| 	if !hid.Supported() { |  | ||||||
| 		return nil, errors.New("unsupported platform") |  | ||||||
| 	} |  | ||||||
| 	hub := &TrezorHub{ |  | ||||||
| 		quit: make(chan chan error), |  | ||||||
| 	} |  | ||||||
| 	hub.refreshWallets() |  | ||||||
| 	return hub, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Wallets implements accounts.Backend, returning all the currently tracked USB |  | ||||||
| // devices that appear to be Trezor hardware wallets. |  | ||||||
| func (hub *TrezorHub) Wallets() []accounts.Wallet { |  | ||||||
| 	// Make sure the list of wallets is up to date |  | ||||||
| 	hub.refreshWallets() |  | ||||||
|  |  | ||||||
| 	hub.stateLock.RLock() |  | ||||||
| 	defer hub.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	cpy := make([]accounts.Wallet, len(hub.wallets)) |  | ||||||
| 	copy(cpy, hub.wallets) |  | ||||||
| 	return cpy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // refreshWallets scans the USB devices attached to the machine and updates the |  | ||||||
| // list of wallets based on the found devices. |  | ||||||
| func (hub *TrezorHub) refreshWallets() { |  | ||||||
| 	// Don't scan the USB like crazy it the user fetches wallets in a loop |  | ||||||
| 	hub.stateLock.RLock() |  | ||||||
| 	elapsed := time.Since(hub.refreshed) |  | ||||||
| 	hub.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	if elapsed < trezorRefreshThrottling { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	// Retrieve the current list of Trezor devices |  | ||||||
| 	var trezors []hid.DeviceInfo |  | ||||||
|  |  | ||||||
| 	if runtime.GOOS == "linux" { |  | ||||||
| 		// hidapi on Linux opens the device during enumeration to retrieve some infos, |  | ||||||
| 		// breaking the Trezor protocol if that is waiting for user confirmation. This |  | ||||||
| 		// is a bug acknowledged at Trezor, but it won't be fixed on old devices so we |  | ||||||
| 		// need to prevent concurrent comms ourselves. The more elegant solution would |  | ||||||
| 		// be to ditch enumeration in favor of hutplug events, but that don't work yet |  | ||||||
| 		// on Windows so if we need to hack it anyway, this is more elegant for now. |  | ||||||
| 		hub.commsLock.Lock() |  | ||||||
| 		if hub.commsPend > 0 { // A confirmation is pending, don't refresh |  | ||||||
| 			hub.commsLock.Unlock() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	for _, info := range hid.Enumerate(trezorVendorID, trezorDeviceID) { |  | ||||||
| 		if info.Interface == 0 { // interface #1 is the debug link, skip it |  | ||||||
| 			trezors = append(trezors, info) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if runtime.GOOS == "linux" { |  | ||||||
| 		// See rationale before the enumeration why this is needed and only on Linux. |  | ||||||
| 		hub.commsLock.Unlock() |  | ||||||
| 	} |  | ||||||
| 	// Transform the current list of wallets into the new one |  | ||||||
| 	hub.stateLock.Lock() |  | ||||||
|  |  | ||||||
| 	wallets := make([]accounts.Wallet, 0, len(trezors)) |  | ||||||
| 	events := []accounts.WalletEvent{} |  | ||||||
|  |  | ||||||
| 	for _, trezor := range trezors { |  | ||||||
| 		url := accounts.URL{Scheme: TrezorScheme, Path: trezor.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].(*trezorWallet).failed()) { |  | ||||||
| 			events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped}) |  | ||||||
| 			hub.wallets = hub.wallets[1:] |  | ||||||
| 		} |  | ||||||
| 		// 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 := &trezorWallet{hub: hub, url: &url, info: trezor, log: log.New("url", url)} |  | ||||||
|  |  | ||||||
| 			events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) |  | ||||||
| 			wallets = append(wallets, wallet) |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		// If the device is the same as the first wallet, keep it |  | ||||||
| 		if hub.wallets[0].URL().Cmp(url) == 0 { |  | ||||||
| 			wallets = append(wallets, hub.wallets[0]) |  | ||||||
| 			hub.wallets = hub.wallets[1:] |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Drop any leftover wallets and set the new batch |  | ||||||
| 	for _, wallet := range hub.wallets { |  | ||||||
| 		events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) |  | ||||||
| 	} |  | ||||||
| 	hub.refreshed = time.Now() |  | ||||||
| 	hub.wallets = wallets |  | ||||||
| 	hub.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	// Fire all wallet events and return |  | ||||||
| 	for _, event := range events { |  | ||||||
| 		hub.updateFeed.Send(event) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Subscribe implements accounts.Backend, creating an async subscription to |  | ||||||
| // receive notifications on the addition or removal of Trezor wallets. |  | ||||||
| func (hub *TrezorHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { |  | ||||||
| 	// We need the mutex to reliably start/stop the update loop |  | ||||||
| 	hub.stateLock.Lock() |  | ||||||
| 	defer hub.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	// Subscribe the caller and track the subscriber count |  | ||||||
| 	sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) |  | ||||||
|  |  | ||||||
| 	// Subscribers require an active notification loop, start it |  | ||||||
| 	if !hub.updating { |  | ||||||
| 		hub.updating = true |  | ||||||
| 		go hub.updater() |  | ||||||
| 	} |  | ||||||
| 	return sub |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // updater is responsible for maintaining an up-to-date list of wallets stored in |  | ||||||
| // the keystore, and for firing wallet addition/removal events. It listens for |  | ||||||
| // account change events from the underlying account cache, and also periodically |  | ||||||
| // forces a manual refresh (only triggers for systems where the filesystem notifier |  | ||||||
| // is not running). |  | ||||||
| func (hub *TrezorHub) updater() { |  | ||||||
| 	for { |  | ||||||
| 		// Wait for a USB hotplug event (not supported yet) or a refresh timeout |  | ||||||
| 		select { |  | ||||||
| 		//case <-hub.changes: // reenable on hutplug implementation |  | ||||||
| 		case <-time.After(trezorRefreshCycle): |  | ||||||
| 		} |  | ||||||
| 		// Run the wallet refresher |  | ||||||
| 		hub.refreshWallets() |  | ||||||
|  |  | ||||||
| 		// If all our subscribers left, stop the updater |  | ||||||
| 		hub.stateLock.Lock() |  | ||||||
| 		if hub.updateScope.Count() == 0 { |  | ||||||
| 			hub.updating = false |  | ||||||
| 			hub.stateLock.Unlock() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		hub.stateLock.Unlock() |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,761 +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 Trezor hardware |  | ||||||
| // wallets. The wire protocol spec can be found on the SatoshiLabs website: |  | ||||||
| // https://doc.satoshilabs.com/trezor-tech/api-protobuf.html |  | ||||||
|  |  | ||||||
| package usbwallet |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"encoding/binary" |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"math/big" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	ethereum "github.com/ethereum/go-ethereum" |  | ||||||
| 	"github.com/ethereum/go-ethereum/accounts" |  | ||||||
| 	"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor" |  | ||||||
| 	"github.com/ethereum/go-ethereum/common" |  | ||||||
| 	"github.com/ethereum/go-ethereum/common/hexutil" |  | ||||||
| 	"github.com/ethereum/go-ethereum/core/types" |  | ||||||
| 	"github.com/ethereum/go-ethereum/log" |  | ||||||
| 	"github.com/golang/protobuf/proto" |  | ||||||
| 	"github.com/karalabe/hid" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In |  | ||||||
| // this case, the calling application should display a pinpad and send back the |  | ||||||
| // encoded passphrase. |  | ||||||
| var ErrTrezorPINNeeded = errors.New("trezor: pin needed") |  | ||||||
|  |  | ||||||
| // trezorWallet represents a live USB Trezor hardware wallet. |  | ||||||
| type trezorWallet struct { |  | ||||||
| 	hub *TrezorHub    // USB hub the device originates from (TODO(karalabe): remove if hotplug lands on Windows) |  | ||||||
| 	url *accounts.URL // Textual URL uniquely identifying this wallet |  | ||||||
|  |  | ||||||
| 	info    hid.DeviceInfo // Known USB device infos about the wallet |  | ||||||
| 	device  *hid.Device    // USB device advertising itself as a Trezor wallet |  | ||||||
| 	failure error          // Any failure that would make the device unusable |  | ||||||
|  |  | ||||||
| 	version  [3]uint32                                  // Current version of the Trezor formware (zero if app is offline) |  | ||||||
| 	label    string                                     // Current textual label of the Trezor device |  | ||||||
| 	pinwait  bool                                       // Flags whether the device is waiting for PIN entry |  | ||||||
| 	accounts []accounts.Account                         // List of derive accounts pinned on the Trezor |  | ||||||
| 	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations |  | ||||||
|  |  | ||||||
| 	deriveNextPath accounts.DerivationPath   // Next derivation path for account auto-discovery |  | ||||||
| 	deriveNextAddr common.Address            // Next derived account address for auto-discovery |  | ||||||
| 	deriveChain    ethereum.ChainStateReader // Blockchain state reader to discover used account with |  | ||||||
| 	deriveReq      chan chan struct{}        // Channel to request a self-derivation on |  | ||||||
| 	deriveQuit     chan chan error           // Channel to terminate the self-deriver with |  | ||||||
|  |  | ||||||
| 	healthQuit chan chan error |  | ||||||
|  |  | ||||||
| 	// Locking a hardware wallet is a bit special. Since hardware devices are lower |  | ||||||
| 	// performing, any communication with them might take a non negligible amount of |  | ||||||
| 	// time. Worse still, waiting for user confirmation can take arbitrarily long, |  | ||||||
| 	// but exclusive communication must be upheld during. Locking the entire wallet |  | ||||||
| 	// in the mean time however would stall any parts of the system that don't want |  | ||||||
| 	// to communicate, just read some state (e.g. list the accounts). |  | ||||||
| 	// |  | ||||||
| 	// As such, a hardware wallet needs two locks to function correctly. A state |  | ||||||
| 	// lock can be used to protect the wallet's software-side internal state, which |  | ||||||
| 	// must not be held exlusively during hardware communication. A communication |  | ||||||
| 	// lock can be used to achieve exclusive access to the device itself, this one |  | ||||||
| 	// however should allow "skipping" waiting for operations that might want to |  | ||||||
| 	// use the device, but can live without too (e.g. account self-derivation). |  | ||||||
| 	// |  | ||||||
| 	// Since we have two locks, it's important to know how to properly use them: |  | ||||||
| 	//   - Communication requires the `device` to not change, so obtaining the |  | ||||||
| 	//     commsLock should be done after having a stateLock. |  | ||||||
| 	//   - Communication must not disable read access to the wallet state, so it |  | ||||||
| 	//     must only ever hold a *read* lock to stateLock. |  | ||||||
| 	commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked |  | ||||||
| 	stateLock sync.RWMutex  // Protects read and write access to the wallet struct fields |  | ||||||
|  |  | ||||||
| 	log log.Logger // Contextual logger to tag the trezor with its id |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // URL implements accounts.Wallet, returning the URL of the Trezor device. |  | ||||||
| func (w *trezorWallet) URL() accounts.URL { |  | ||||||
| 	return *w.url // Immutable, no need for a lock |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Status implements accounts.Wallet, always whether the Trezor is opened, closed |  | ||||||
| // or whether the Ethereum app was not started on it. |  | ||||||
| func (w *trezorWallet) Status() string { |  | ||||||
| 	w.stateLock.RLock() // No device communication, state lock is enough |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	if w.failure != nil { |  | ||||||
| 		return fmt.Sprintf("Failed: %v", w.failure) |  | ||||||
| 	} |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		return "Closed" |  | ||||||
| 	} |  | ||||||
| 	if w.pinwait { |  | ||||||
| 		return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label) |  | ||||||
| 	} |  | ||||||
| 	return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // failed returns if the USB device wrapped by the wallet failed for some reason. |  | ||||||
| // This is used by the device scanner to report failed wallets as departed. |  | ||||||
| // |  | ||||||
| // The method assumes that the state lock is *not* held! |  | ||||||
| func (w *trezorWallet) failed() bool { |  | ||||||
| 	w.stateLock.RLock() // No device communication, state lock is enough |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	return w.failure != nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Open implements accounts.Wallet, attempting to open a USB connection to the |  | ||||||
| // Trezor hardware wallet. Connecting to the Trezor is a two phase operation: |  | ||||||
| //  * The first phase is to establish the USB connection, initialize it and read |  | ||||||
| //    the wallet's features. This phase is invoked is the provided passphrase is |  | ||||||
| //    empty. The device will display the pinpad as a result and will return an |  | ||||||
| //    appropriate error to notify the user that a second open phase is needed. |  | ||||||
| //  * The second phase is to unlock access to the Trezor, which is done by the |  | ||||||
| //    user actually providing a passphrase mapping a keyboard keypad to the pin |  | ||||||
| //    number of the user (shuffled according to the pinpad displayed). |  | ||||||
| func (w *trezorWallet) Open(passphrase string) error { |  | ||||||
| 	w.stateLock.Lock() // State lock is enough since there's no connection yet at this point |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	// If phase 1 is requested, init the connection and wait for user callback |  | ||||||
| 	if passphrase == "" { |  | ||||||
| 		// If we're already waiting for a PIN entry, insta-return |  | ||||||
| 		if w.pinwait { |  | ||||||
| 			return ErrTrezorPINNeeded |  | ||||||
| 		} |  | ||||||
| 		// Initialize a connection to the device |  | ||||||
| 		if err := w.openInit(); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		// Do a manual ping, forcing the device to ask for its PIN |  | ||||||
| 		askPin, pinRequest := true, new(trezor.PinMatrixRequest) |  | ||||||
| 		if err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, pinRequest); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		w.pinwait = true |  | ||||||
|  |  | ||||||
| 		return ErrTrezorPINNeeded |  | ||||||
| 	} |  | ||||||
| 	// Phase 2 requested with actual PIN entry |  | ||||||
| 	w.pinwait = false |  | ||||||
|  |  | ||||||
| 	success := new(trezor.Success) |  | ||||||
| 	if err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, success); err != nil { |  | ||||||
| 		w.failure = err |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) |  | ||||||
|  |  | ||||||
| 	// Trezor unlocked, start the heartbeat cycle and account derivation |  | ||||||
| 	w.paths = make(map[common.Address]accounts.DerivationPath) |  | ||||||
|  |  | ||||||
| 	w.deriveReq = make(chan chan struct{}) |  | ||||||
| 	w.deriveQuit = make(chan chan error) |  | ||||||
| 	w.healthQuit = make(chan chan error) |  | ||||||
|  |  | ||||||
| 	defer func() { |  | ||||||
| 		go w.heartbeat() |  | ||||||
| 		go w.selfDerive() |  | ||||||
| 	}() |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // openInit is the first phase of a Trezor opening mechanism which initializes |  | ||||||
| //  device connection and requests the device to display the pinpad. |  | ||||||
| func (w *trezorWallet) openInit() error { |  | ||||||
| 	// If the wallet was already opened, don't try to phase-1 open again |  | ||||||
| 	if w.device != nil { |  | ||||||
| 		return accounts.ErrWalletAlreadyOpen |  | ||||||
| 	} |  | ||||||
| 	// Otherwise iterate over all USB devices and find this again (no way to directly do this) |  | ||||||
| 	device, err := w.info.Open() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	// Wallet successfully connected to, init the connection and start the heartbeat |  | ||||||
| 	w.device = device |  | ||||||
| 	w.commsLock = make(chan struct{}, 1) |  | ||||||
| 	w.commsLock <- struct{}{} // Enable lock |  | ||||||
|  |  | ||||||
| 	// Retrieve the Trezor's version number and user label |  | ||||||
| 	features := new(trezor.Features) |  | ||||||
| 	if err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} |  | ||||||
| 	w.label = features.GetLabel() |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // heartbeat is a health check loop for the Trezor wallets to periodically verify |  | ||||||
| // whether they are still present or if they malfunctioned. It is needed because: |  | ||||||
| //  - libusb on Windows doesn't support hotplug, so we can't detect USB unplugs |  | ||||||
| func (w *trezorWallet) heartbeat() { |  | ||||||
| 	w.log.Debug("Trezor health-check started") |  | ||||||
| 	defer w.log.Debug("Trezor health-check stopped") |  | ||||||
|  |  | ||||||
| 	// Execute heartbeat checks until termination or error |  | ||||||
| 	var ( |  | ||||||
| 		errc chan error |  | ||||||
| 		err  error |  | ||||||
| 	) |  | ||||||
| 	for errc == nil && err == nil { |  | ||||||
| 		// Wait until termination is requested or the heartbeat cycle arrives |  | ||||||
| 		select { |  | ||||||
| 		case errc = <-w.healthQuit: |  | ||||||
| 			// Termination requested |  | ||||||
| 			continue |  | ||||||
| 		case <-time.After(heartbeatCycle): |  | ||||||
| 			// Heartbeat time |  | ||||||
| 		} |  | ||||||
| 		// Execute a tiny data exchange to see responsiveness |  | ||||||
| 		w.stateLock.RLock() |  | ||||||
| 		if w.device == nil { |  | ||||||
| 			// Terminated while waiting for the lock |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		<-w.commsLock // Don't lock state while executing ping |  | ||||||
|  |  | ||||||
| 		success := new(trezor.Success) |  | ||||||
| 		err = w.trezorExchange(&trezor.Ping{}, success) |  | ||||||
|  |  | ||||||
| 		w.commsLock <- struct{}{} |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 		if err != nil { |  | ||||||
| 			w.stateLock.Lock() // Lock state to tear the wallet down |  | ||||||
| 			w.failure = err |  | ||||||
| 			w.close() |  | ||||||
| 			w.stateLock.Unlock() |  | ||||||
| 		} |  | ||||||
| 		// Ignore non hardware related errors |  | ||||||
| 		err = nil |  | ||||||
| 	} |  | ||||||
| 	// In case of error, wait for termination |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.log.Debug("Trezor health-check failed", "err", err) |  | ||||||
| 		errc = <-w.healthQuit |  | ||||||
| 	} |  | ||||||
| 	errc <- err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Close implements accounts.Wallet, closing the USB connection to the Trezor. |  | ||||||
| func (w *trezorWallet) Close() error { |  | ||||||
| 	// Ensure the wallet was opened |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	hQuit, dQuit := w.healthQuit, w.deriveQuit |  | ||||||
| 	w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// Terminate the health checks |  | ||||||
| 	var herr error |  | ||||||
| 	if hQuit != nil { |  | ||||||
| 		errc := make(chan error) |  | ||||||
| 		hQuit <- errc |  | ||||||
| 		herr = <-errc // Save for later, we *must* close the USB |  | ||||||
| 	} |  | ||||||
| 	// Terminate the self-derivations |  | ||||||
| 	var derr error |  | ||||||
| 	if dQuit != nil { |  | ||||||
| 		errc := make(chan error) |  | ||||||
| 		dQuit <- errc |  | ||||||
| 		derr = <-errc // Save for later, we *must* close the USB |  | ||||||
| 	} |  | ||||||
| 	// Terminate the device connection |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	w.healthQuit = nil |  | ||||||
| 	w.deriveQuit = nil |  | ||||||
| 	w.deriveReq = nil |  | ||||||
|  |  | ||||||
| 	if err := w.close(); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if herr != nil { |  | ||||||
| 		return herr |  | ||||||
| 	} |  | ||||||
| 	return derr |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // close is the internal wallet closer that terminates the USB connection and |  | ||||||
| // resets all the fields to their defaults. |  | ||||||
| // |  | ||||||
| // Note, close assumes the state lock is held! |  | ||||||
| func (w *trezorWallet) close() error { |  | ||||||
| 	// Allow duplicate closes, especially for health-check failures |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	// Close the device, clear everything, then return |  | ||||||
| 	w.device.Close() |  | ||||||
| 	w.device = nil |  | ||||||
|  |  | ||||||
| 	w.label, w.version = "", [3]uint32{} |  | ||||||
| 	w.accounts, w.paths = nil, nil |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Accounts implements accounts.Wallet, returning the list of accounts pinned to |  | ||||||
| // the Trezor hardware wallet. If self-derivation was enabled, the account list |  | ||||||
| // is periodically expanded based on current chain state. |  | ||||||
| func (w *trezorWallet) Accounts() []accounts.Account { |  | ||||||
| 	// Attempt self-derivation if it's running |  | ||||||
| 	reqc := make(chan struct{}, 1) |  | ||||||
| 	select { |  | ||||||
| 	case w.deriveReq <- reqc: |  | ||||||
| 		// Self-derivation request accepted, wait for it |  | ||||||
| 		<-reqc |  | ||||||
| 	default: |  | ||||||
| 		// Self-derivation offline, throttled or busy, skip |  | ||||||
| 	} |  | ||||||
| 	// Return whatever account list we ended up with |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	cpy := make([]accounts.Account, len(w.accounts)) |  | ||||||
| 	copy(cpy, w.accounts) |  | ||||||
| 	return cpy |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // selfDerive is an account derivation loop that upon request attempts to find |  | ||||||
| // new non-zero accounts. |  | ||||||
| func (w *trezorWallet) selfDerive() { |  | ||||||
| 	w.log.Debug("Trezor self-derivation started") |  | ||||||
| 	defer w.log.Debug("Trezor self-derivation stopped") |  | ||||||
|  |  | ||||||
| 	// Execute self-derivations until termination or error |  | ||||||
| 	var ( |  | ||||||
| 		reqc chan struct{} |  | ||||||
| 		errc chan error |  | ||||||
| 		err  error |  | ||||||
| 	) |  | ||||||
| 	for errc == nil && err == nil { |  | ||||||
| 		// Wait until either derivation or termination is requested |  | ||||||
| 		select { |  | ||||||
| 		case errc = <-w.deriveQuit: |  | ||||||
| 			// Termination requested |  | ||||||
| 			continue |  | ||||||
| 		case reqc = <-w.deriveReq: |  | ||||||
| 			// Account discovery requested |  | ||||||
| 		} |  | ||||||
| 		// Derivation needs a chain and device access, skip if either unavailable |  | ||||||
| 		w.stateLock.RLock() |  | ||||||
| 		if w.device == nil || w.deriveChain == nil { |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			reqc <- struct{}{} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		select { |  | ||||||
| 		case <-w.commsLock: |  | ||||||
| 		default: |  | ||||||
| 			w.stateLock.RUnlock() |  | ||||||
| 			reqc <- struct{}{} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		// Device lock obtained, derive the next batch of accounts |  | ||||||
| 		var ( |  | ||||||
| 			accs  []accounts.Account |  | ||||||
| 			paths []accounts.DerivationPath |  | ||||||
|  |  | ||||||
| 			nextAddr = w.deriveNextAddr |  | ||||||
| 			nextPath = w.deriveNextPath |  | ||||||
|  |  | ||||||
| 			context = context.Background() |  | ||||||
| 		) |  | ||||||
| 		for empty := false; !empty; { |  | ||||||
| 			// Retrieve the next derived Ethereum account |  | ||||||
| 			if nextAddr == (common.Address{}) { |  | ||||||
| 				if nextAddr, err = w.trezorDerive(nextPath); err != nil { |  | ||||||
| 					w.log.Warn("Trezor account derivation failed", "err", err) |  | ||||||
| 					break |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 			// Check the account's status against the current chain state |  | ||||||
| 			var ( |  | ||||||
| 				balance *big.Int |  | ||||||
| 				nonce   uint64 |  | ||||||
| 			) |  | ||||||
| 			balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				w.log.Warn("Trezor balance retrieval failed", "err", err) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil) |  | ||||||
| 			if err != nil { |  | ||||||
| 				w.log.Warn("Trezor nonce retrieval failed", "err", err) |  | ||||||
| 				break |  | ||||||
| 			} |  | ||||||
| 			// If the next account is empty, stop self-derivation, but add it nonetheless |  | ||||||
| 			if balance.Sign() == 0 && nonce == 0 { |  | ||||||
| 				empty = true |  | ||||||
| 			} |  | ||||||
| 			// We've just self-derived a new account, start tracking it locally |  | ||||||
| 			path := make(accounts.DerivationPath, len(nextPath)) |  | ||||||
| 			copy(path[:], nextPath[:]) |  | ||||||
| 			paths = append(paths, path) |  | ||||||
|  |  | ||||||
| 			account := accounts.Account{ |  | ||||||
| 				Address: nextAddr, |  | ||||||
| 				URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |  | ||||||
| 			} |  | ||||||
| 			accs = append(accs, account) |  | ||||||
|  |  | ||||||
| 			// Display a log message to the user for new (or previously empty accounts) |  | ||||||
| 			if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) { |  | ||||||
| 				w.log.Info("Trezor discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce) |  | ||||||
| 			} |  | ||||||
| 			// Fetch the next potential account |  | ||||||
| 			if !empty { |  | ||||||
| 				nextAddr = common.Address{} |  | ||||||
| 				nextPath[len(nextPath)-1]++ |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// Self derivation complete, release device lock |  | ||||||
| 		w.commsLock <- struct{}{} |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 		// Insert any accounts successfully derived |  | ||||||
| 		w.stateLock.Lock() |  | ||||||
| 		for i := 0; i < len(accs); i++ { |  | ||||||
| 			if _, ok := w.paths[accs[i].Address]; !ok { |  | ||||||
| 				w.accounts = append(w.accounts, accs[i]) |  | ||||||
| 				w.paths[accs[i].Address] = paths[i] |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		// Shift the self-derivation forward |  | ||||||
| 		// TODO(karalabe): don't overwrite changes from wallet.SelfDerive |  | ||||||
| 		w.deriveNextAddr = nextAddr |  | ||||||
| 		w.deriveNextPath = nextPath |  | ||||||
| 		w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 		// Notify the user of termination and loop after a bit of time (to avoid trashing) |  | ||||||
| 		reqc <- struct{}{} |  | ||||||
| 		if err == nil { |  | ||||||
| 			select { |  | ||||||
| 			case errc = <-w.deriveQuit: |  | ||||||
| 				// Termination requested, abort |  | ||||||
| 			case <-time.After(selfDeriveThrottling): |  | ||||||
| 				// Waited enough, willing to self-derive again |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// In case of error, wait for termination |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.log.Debug("Trezor self-derivation failed", "err", err) |  | ||||||
| 		errc = <-w.deriveQuit |  | ||||||
| 	} |  | ||||||
| 	errc <- err |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Contains implements accounts.Wallet, returning whether a particular account is |  | ||||||
| // or is not pinned into this Trezor instance. Although we could attempt to resolve |  | ||||||
| // unpinned accounts, that would be an non-negligible hardware operation. |  | ||||||
| func (w *trezorWallet) Contains(account accounts.Account) bool { |  | ||||||
| 	w.stateLock.RLock() |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	_, exists := w.paths[account.Address] |  | ||||||
| 	return exists |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Derive implements accounts.Wallet, deriving a new account at the specific |  | ||||||
| // derivation path. If pin is set to true, the account will be added to the list |  | ||||||
| // of tracked accounts. |  | ||||||
| func (w *trezorWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { |  | ||||||
| 	// Try to derive the actual account and update its URL if successful |  | ||||||
| 	w.stateLock.RLock() // Avoid device disappearing during derivation |  | ||||||
|  |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		w.stateLock.RUnlock() |  | ||||||
| 		return accounts.Account{}, accounts.ErrWalletClosed |  | ||||||
| 	} |  | ||||||
| 	<-w.commsLock // Avoid concurrent hardware access |  | ||||||
| 	address, err := w.trezorDerive(path) |  | ||||||
| 	w.commsLock <- struct{}{} |  | ||||||
|  |  | ||||||
| 	w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// If an error occurred or no pinning was requested, return |  | ||||||
| 	if err != nil { |  | ||||||
| 		return accounts.Account{}, err |  | ||||||
| 	} |  | ||||||
| 	account := accounts.Account{ |  | ||||||
| 		Address: address, |  | ||||||
| 		URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, |  | ||||||
| 	} |  | ||||||
| 	if !pin { |  | ||||||
| 		return account, nil |  | ||||||
| 	} |  | ||||||
| 	// Pinning needs to modify the state |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	if _, ok := w.paths[address]; !ok { |  | ||||||
| 		w.accounts = append(w.accounts, account) |  | ||||||
| 		w.paths[address] = path |  | ||||||
| 	} |  | ||||||
| 	return account, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SelfDerive implements accounts.Wallet, trying to discover accounts that the |  | ||||||
| // user used previously (based on the chain state), but ones that he/she did not |  | ||||||
| // explicitly pin to the wallet manually. To avoid chain head monitoring, self |  | ||||||
| // derivation only runs during account listing (and even then throttled). |  | ||||||
| func (w *trezorWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) { |  | ||||||
| 	w.stateLock.Lock() |  | ||||||
| 	defer w.stateLock.Unlock() |  | ||||||
|  |  | ||||||
| 	w.deriveNextPath = make(accounts.DerivationPath, len(base)) |  | ||||||
| 	copy(w.deriveNextPath[:], base[:]) |  | ||||||
|  |  | ||||||
| 	w.deriveNextAddr = common.Address{} |  | ||||||
| 	w.deriveChain = chain |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignHash implements accounts.Wallet, however signing arbitrary data is not |  | ||||||
| // supported for Trezor wallets, so this method will always return an error. |  | ||||||
| func (w *trezorWallet) SignHash(acc accounts.Account, hash []byte) ([]byte, error) { |  | ||||||
| 	return nil, accounts.ErrNotSupported |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignTx implements accounts.Wallet. It sends the transaction over to the Trezor |  | ||||||
| // wallet to request a confirmation from the user. It returns either the signed |  | ||||||
| // transaction or a failure if the user denied the transaction. |  | ||||||
| func (w *trezorWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |  | ||||||
| 	w.stateLock.RLock() // Comms have own mutex, this is for the state fields |  | ||||||
| 	defer w.stateLock.RUnlock() |  | ||||||
|  |  | ||||||
| 	// If the wallet is closed, abort |  | ||||||
| 	if w.device == nil { |  | ||||||
| 		return nil, accounts.ErrWalletClosed |  | ||||||
| 	} |  | ||||||
| 	// Make sure the requested account is contained within |  | ||||||
| 	path, ok := w.paths[account.Address] |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil, accounts.ErrUnknownAccount |  | ||||||
| 	} |  | ||||||
| 	// All infos gathered and metadata checks out, request signing |  | ||||||
| 	<-w.commsLock |  | ||||||
| 	defer func() { w.commsLock <- struct{}{} }() |  | ||||||
|  |  | ||||||
| 	// Ensure the device isn't screwed with while user confirmation is pending |  | ||||||
| 	// TODO(karalabe): remove if hotplug lands on Windows |  | ||||||
| 	w.hub.commsLock.Lock() |  | ||||||
| 	w.hub.commsPend++ |  | ||||||
| 	w.hub.commsLock.Unlock() |  | ||||||
|  |  | ||||||
| 	defer func() { |  | ||||||
| 		w.hub.commsLock.Lock() |  | ||||||
| 		w.hub.commsPend-- |  | ||||||
| 		w.hub.commsLock.Unlock() |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	return w.trezorSign(path, account.Address, tx, chainID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary |  | ||||||
| // data is not supported for Trezor wallets, so this method will always return |  | ||||||
| // an error. |  | ||||||
| func (w *trezorWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { |  | ||||||
| 	return nil, accounts.ErrNotSupported |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given |  | ||||||
| // transaction with the given account using passphrase as extra authentication. |  | ||||||
| // Since the Trezor does not support extra passphrases, it is silently ignored. |  | ||||||
| func (w *trezorWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |  | ||||||
| 	return w.SignTx(account, tx, chainID) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // trezorDerive sends a derivation request to the Trezor device and returns the |  | ||||||
| // Ethereum address located on that path. |  | ||||||
| func (w *trezorWallet) trezorDerive(derivationPath []uint32) (common.Address, error) { |  | ||||||
| 	address := new(trezor.EthereumAddress) |  | ||||||
| 	if err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil { |  | ||||||
| 		return common.Address{}, err |  | ||||||
| 	} |  | ||||||
| 	return common.BytesToAddress(address.GetAddress()), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // trezorSign sends the transaction to the Trezor wallet, and waits for the user |  | ||||||
| // to confirm or deny the transaction. |  | ||||||
| func (w *trezorWallet) trezorSign(derivationPath []uint32, address common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { |  | ||||||
| 	// Create the transaction initiation message |  | ||||||
| 	data := tx.Data() |  | ||||||
| 	length := uint32(len(data)) |  | ||||||
|  |  | ||||||
| 	request := &trezor.EthereumSignTx{ |  | ||||||
| 		AddressN:   derivationPath, |  | ||||||
| 		Nonce:      new(big.Int).SetUint64(tx.Nonce()).Bytes(), |  | ||||||
| 		GasPrice:   tx.GasPrice().Bytes(), |  | ||||||
| 		GasLimit:   tx.Gas().Bytes(), |  | ||||||
| 		Value:      tx.Value().Bytes(), |  | ||||||
| 		DataLength: &length, |  | ||||||
| 	} |  | ||||||
| 	if to := tx.To(); to != nil { |  | ||||||
| 		request.To = (*to)[:] // Non contract deploy, set recipient explicitly |  | ||||||
| 	} |  | ||||||
| 	if length > 1024 { // Send the data chunked if that was requested |  | ||||||
| 		request.DataInitialChunk, data = data[:1024], data[1024:] |  | ||||||
| 	} else { |  | ||||||
| 		request.DataInitialChunk, data = data, nil |  | ||||||
| 	} |  | ||||||
| 	if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) |  | ||||||
| 		id := uint32(chainID.Int64()) |  | ||||||
| 		request.ChainId = &id |  | ||||||
| 	} |  | ||||||
| 	// Send the initiation message and stream content until a signature is returned |  | ||||||
| 	response := new(trezor.EthereumTxRequest) |  | ||||||
| 	if err := w.trezorExchange(request, response); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	for response.DataLength != nil && int(*response.DataLength) <= len(data) { |  | ||||||
| 		chunk := data[:*response.DataLength] |  | ||||||
| 		data = data[*response.DataLength:] |  | ||||||
|  |  | ||||||
| 		if err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Extract the Ethereum signature and do a sanity validation |  | ||||||
| 	if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 { |  | ||||||
| 		return nil, errors.New("reply lacks signature") |  | ||||||
| 	} |  | ||||||
| 	signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) |  | ||||||
|  |  | ||||||
| 	// Create the correct signer and signature transform based on the chain ID |  | ||||||
| 	var signer types.Signer |  | ||||||
| 	if chainID == nil { |  | ||||||
| 		signer = new(types.HomesteadSigner) |  | ||||||
| 	} else { |  | ||||||
| 		signer = types.NewEIP155Signer(chainID) |  | ||||||
| 		signature[64] = signature[64] - byte(chainID.Uint64()*2+35) |  | ||||||
| 	} |  | ||||||
| 	// Inject the final signature into the transaction and sanity check the sender |  | ||||||
| 	signed, err := tx.WithSignature(signer, signature) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	sender, err := types.Sender(signer, signed) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	if sender != address { |  | ||||||
| 		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", address.Hex(), sender.Hex()) |  | ||||||
| 	} |  | ||||||
| 	return signed, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // trezorExchange performs a data exchange with the Trezor wallet, sending it a |  | ||||||
| // message and retrieving the response. |  | ||||||
| func (w *trezorWallet) trezorExchange(req proto.Message, res proto.Message) error { |  | ||||||
| 	// Construct the original message payload to chunk up |  | ||||||
| 	data, err := proto.Marshal(req) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	payload := make([]byte, 8+len(data)) |  | ||||||
| 	copy(payload, []byte{0x23, 0x23}) |  | ||||||
| 	binary.BigEndian.PutUint16(payload[2:], trezor.Type(req)) |  | ||||||
| 	binary.BigEndian.PutUint32(payload[4:], uint32(len(data))) |  | ||||||
| 	copy(payload[8:], data) |  | ||||||
|  |  | ||||||
| 	// Stream all the chunks to the device |  | ||||||
| 	chunk := make([]byte, 64) |  | ||||||
| 	chunk[0] = 0x3f // Report ID magic number |  | ||||||
|  |  | ||||||
| 	for len(payload) > 0 { |  | ||||||
| 		// Construct the new message to stream, padding with zeroes if needed |  | ||||||
| 		if len(payload) > 63 { |  | ||||||
| 			copy(chunk[1:], payload[:63]) |  | ||||||
| 			payload = payload[63:] |  | ||||||
| 		} else { |  | ||||||
| 			copy(chunk[1:], payload) |  | ||||||
| 			copy(chunk[1+len(payload):], make([]byte, 63-len(payload))) |  | ||||||
| 			payload = nil |  | ||||||
| 		} |  | ||||||
| 		// Send over to the device |  | ||||||
| 		w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk)) |  | ||||||
| 		if _, err := w.device.Write(chunk); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Stream the reply back from the wallet in 64 byte chunks |  | ||||||
| 	var ( |  | ||||||
| 		kind  uint16 |  | ||||||
| 		reply []byte |  | ||||||
| 	) |  | ||||||
| 	for { |  | ||||||
| 		// Read the next chunk from the Trezor wallet |  | ||||||
| 		if _, err := io.ReadFull(w.device, chunk); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk)) |  | ||||||
|  |  | ||||||
| 		// Make sure the transport header matches |  | ||||||
| 		if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) { |  | ||||||
| 			return errReplyInvalidHeader |  | ||||||
| 		} |  | ||||||
| 		// If it's the first chunk, retrieve the reply message type and total message length |  | ||||||
| 		var payload []byte |  | ||||||
|  |  | ||||||
| 		if len(reply) == 0 { |  | ||||||
| 			kind = binary.BigEndian.Uint16(chunk[3:5]) |  | ||||||
| 			reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9]))) |  | ||||||
| 			payload = chunk[9:] |  | ||||||
| 		} else { |  | ||||||
| 			payload = chunk[1:] |  | ||||||
| 		} |  | ||||||
| 		// Append to the reply and stop when filled up |  | ||||||
| 		if left := cap(reply) - len(reply); left > len(payload) { |  | ||||||
| 			reply = append(reply, payload...) |  | ||||||
| 		} else { |  | ||||||
| 			reply = append(reply, payload[:left]...) |  | ||||||
| 			break |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	// Try to parse the reply into the requested reply message |  | ||||||
| 	if kind == uint16(trezor.MessageType_MessageType_Failure) { |  | ||||||
| 		// Trezor returned a failure, extract and return the message |  | ||||||
| 		failure := new(trezor.Failure) |  | ||||||
| 		if err := proto.Unmarshal(reply, failure); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 		return errors.New("trezor: " + failure.GetMessage()) |  | ||||||
| 	} |  | ||||||
| 	if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { |  | ||||||
| 		// Trezor is waitinf for user confirmation, ack and wait for the next message |  | ||||||
| 		return w.trezorExchange(&trezor.ButtonAck{}, res) |  | ||||||
| 	} |  | ||||||
| 	if want := trezor.Type(res); kind != want { |  | ||||||
| 		return fmt.Errorf("trezor: expected reply type %s, got %s", trezor.Name(want), trezor.Name(kind)) |  | ||||||
| 	} |  | ||||||
| 	return proto.Unmarshal(reply, res) |  | ||||||
| } |  | ||||||
| @@ -1,34 +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/>. |  | ||||||
|  |  | ||||||
| // Package usbwallet implements support for USB hardware wallets. |  | ||||||
| package usbwallet |  | ||||||
|  |  | ||||||
| import "time" |  | ||||||
|  |  | ||||||
| // deviceID is a combined vendor/product identifier to uniquely identify a USB |  | ||||||
| // hardware device. |  | ||||||
| type deviceID struct { |  | ||||||
| 	Vendor  uint16 // The Vendor identifer |  | ||||||
| 	Product uint16 // The Product identifier |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Maximum time between wallet health checks to detect USB unplugs. |  | ||||||
| const heartbeatCycle = time.Second |  | ||||||
|  |  | ||||||
| // Minimum time to wait between self derivation attempts, even it the user is |  | ||||||
| // requesting accounts like crazy. |  | ||||||
| const selfDeriveThrottling = time.Second |  | ||||||
							
								
								
									
										562
									
								
								accounts/usbwallet/wallet.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										562
									
								
								accounts/usbwallet/wallet.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,562 @@ | |||||||
|  | // 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/>. | ||||||
|  |  | ||||||
|  | // Package usbwallet implements support for USB hardware wallets. | ||||||
|  | package usbwallet | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"math/big" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
|  | 	ethereum "github.com/ethereum/go-ethereum" | ||||||
|  | 	"github.com/ethereum/go-ethereum/accounts" | ||||||
|  | 	"github.com/ethereum/go-ethereum/common" | ||||||
|  | 	"github.com/ethereum/go-ethereum/core/types" | ||||||
|  | 	"github.com/ethereum/go-ethereum/log" | ||||||
|  | 	"github.com/karalabe/hid" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Maximum time between wallet health checks to detect USB unplugs. | ||||||
|  | const heartbeatCycle = time.Second | ||||||
|  |  | ||||||
|  | // Minimum time to wait between self derivation attempts, even it the user is | ||||||
|  | // requesting accounts like crazy. | ||||||
|  | const selfDeriveThrottling = time.Second | ||||||
|  |  | ||||||
|  | // driver defines the vendor specific functionality hardware wallets instances | ||||||
|  | // must implement to allow using them with the wallet lifecycle management. | ||||||
|  | type driver interface { | ||||||
|  | 	// Status returns a textual status to aid the user in the current state of the | ||||||
|  | 	// wallet. It also returns an error indicating any failure the wallet might have | ||||||
|  | 	// encountered. | ||||||
|  | 	Status() (string, error) | ||||||
|  |  | ||||||
|  | 	// Open initializes access to a wallet instance. The passphrase parameter may | ||||||
|  | 	// or may not be used by the implementation of a particular wallet instance. | ||||||
|  | 	Open(device io.ReadWriter, passphrase string) error | ||||||
|  |  | ||||||
|  | 	// Close releases any resources held by an open wallet instance. | ||||||
|  | 	Close() error | ||||||
|  |  | ||||||
|  | 	// Heartbeat performs a sanity check against the hardware wallet to see if it | ||||||
|  | 	// is still online and healthy. | ||||||
|  | 	Heartbeat() error | ||||||
|  |  | ||||||
|  | 	// Derive sends a derivation request to the USB device and returns the Ethereum | ||||||
|  | 	// address located on that path. | ||||||
|  | 	Derive(path accounts.DerivationPath) (common.Address, error) | ||||||
|  |  | ||||||
|  | 	// SignTx sends the transaction to the USB device and waits for the user to confirm | ||||||
|  | 	// or deny the transaction. | ||||||
|  | 	SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // wallet represents the common functionality shared by all USB hardware | ||||||
|  | // wallets to prevent reimplementing the same complex maintenance mechanisms | ||||||
|  | // for different vendors. | ||||||
|  | type wallet struct { | ||||||
|  | 	hub    *Hub          // USB hub scanning | ||||||
|  | 	driver driver        // Hardware implementation of the low level device operations | ||||||
|  | 	url    *accounts.URL // Textual URL uniquely identifying this wallet | ||||||
|  |  | ||||||
|  | 	info   hid.DeviceInfo // Known USB device infos about the wallet | ||||||
|  | 	device *hid.Device    // USB device advertising itself as a hardware wallet | ||||||
|  |  | ||||||
|  | 	accounts []accounts.Account                         // List of derive accounts pinned on the hardware wallet | ||||||
|  | 	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations | ||||||
|  |  | ||||||
|  | 	deriveNextPath accounts.DerivationPath   // Next derivation path for account auto-discovery | ||||||
|  | 	deriveNextAddr common.Address            // Next derived account address for auto-discovery | ||||||
|  | 	deriveChain    ethereum.ChainStateReader // Blockchain state reader to discover used account with | ||||||
|  | 	deriveReq      chan chan struct{}        // Channel to request a self-derivation on | ||||||
|  | 	deriveQuit     chan chan error           // Channel to terminate the self-deriver with | ||||||
|  |  | ||||||
|  | 	healthQuit chan chan error | ||||||
|  |  | ||||||
|  | 	// Locking a hardware wallet is a bit special. Since hardware devices are lower | ||||||
|  | 	// performing, any communication with them might take a non negligible amount of | ||||||
|  | 	// time. Worse still, waiting for user confirmation can take arbitrarily long, | ||||||
|  | 	// but exclusive communication must be upheld during. Locking the entire wallet | ||||||
|  | 	// in the mean time however would stall any parts of the system that don't want | ||||||
|  | 	// to communicate, just read some state (e.g. list the accounts). | ||||||
|  | 	// | ||||||
|  | 	// As such, a hardware wallet needs two locks to function correctly. A state | ||||||
|  | 	// lock can be used to protect the wallet's software-side internal state, which | ||||||
|  | 	// must not be held exlusively during hardware communication. A communication | ||||||
|  | 	// lock can be used to achieve exclusive access to the device itself, this one | ||||||
|  | 	// however should allow "skipping" waiting for operations that might want to | ||||||
|  | 	// use the device, but can live without too (e.g. account self-derivation). | ||||||
|  | 	// | ||||||
|  | 	// Since we have two locks, it's important to know how to properly use them: | ||||||
|  | 	//   - Communication requires the `device` to not change, so obtaining the | ||||||
|  | 	//     commsLock should be done after having a stateLock. | ||||||
|  | 	//   - Communication must not disable read access to the wallet state, so it | ||||||
|  | 	//     must only ever hold a *read* lock to stateLock. | ||||||
|  | 	commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked | ||||||
|  | 	stateLock sync.RWMutex  // Protects read and write access to the wallet struct fields | ||||||
|  |  | ||||||
|  | 	log log.Logger // Contextual logger to tag the base with its id | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // URL implements accounts.Wallet, returning the URL of the USB hardware device. | ||||||
|  | func (w *wallet) URL() accounts.URL { | ||||||
|  | 	return *w.url // Immutable, no need for a lock | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Status implements accounts.Wallet, returning a custom status message from the | ||||||
|  | // underlying vendor-specific hardware wallet implementation. | ||||||
|  | func (w *wallet) Status() (string, error) { | ||||||
|  | 	w.stateLock.RLock() // No device communication, state lock is enough | ||||||
|  | 	defer w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	status, failure := w.driver.Status() | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		return "Closed", failure | ||||||
|  | 	} | ||||||
|  | 	return status, failure | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Open implements accounts.Wallet, attempting to open a USB connection to the | ||||||
|  | // hardware wallet. | ||||||
|  | func (w *wallet) Open(passphrase string) error { | ||||||
|  | 	w.stateLock.Lock() // State lock is enough since there's no connection yet at this point | ||||||
|  | 	defer w.stateLock.Unlock() | ||||||
|  |  | ||||||
|  | 	// If the device was already opened once, refuse to try again | ||||||
|  | 	if w.paths != nil { | ||||||
|  | 		return accounts.ErrWalletAlreadyOpen | ||||||
|  | 	} | ||||||
|  | 	// Make sure the actual device connection is done only once | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		device, err := w.info.Open() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		w.device = device | ||||||
|  | 		w.commsLock = make(chan struct{}, 1) | ||||||
|  | 		w.commsLock <- struct{}{} // Enable lock | ||||||
|  | 	} | ||||||
|  | 	// Delegate device initialization to the underlying driver | ||||||
|  | 	if err := w.driver.Open(w.device, passphrase); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	// Connection successful, start life-cycle management | ||||||
|  | 	w.paths = make(map[common.Address]accounts.DerivationPath) | ||||||
|  |  | ||||||
|  | 	w.deriveReq = make(chan chan struct{}) | ||||||
|  | 	w.deriveQuit = make(chan chan error) | ||||||
|  | 	w.healthQuit = make(chan chan error) | ||||||
|  |  | ||||||
|  | 	go w.heartbeat() | ||||||
|  | 	go w.selfDerive() | ||||||
|  |  | ||||||
|  | 	// Notify anyone listening for wallet events that a new device is accessible | ||||||
|  | 	go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // heartbeat is a health check loop for the USB wallets to periodically verify | ||||||
|  | // whether they are still present or if they malfunctioned. | ||||||
|  | func (w *wallet) heartbeat() { | ||||||
|  | 	w.log.Debug("USB wallet health-check started") | ||||||
|  | 	defer w.log.Debug("USB wallet health-check stopped") | ||||||
|  |  | ||||||
|  | 	// Execute heartbeat checks until termination or error | ||||||
|  | 	var ( | ||||||
|  | 		errc chan error | ||||||
|  | 		err  error | ||||||
|  | 	) | ||||||
|  | 	for errc == nil && err == nil { | ||||||
|  | 		// Wait until termination is requested or the heartbeat cycle arrives | ||||||
|  | 		select { | ||||||
|  | 		case errc = <-w.healthQuit: | ||||||
|  | 			// Termination requested | ||||||
|  | 			continue | ||||||
|  | 		case <-time.After(heartbeatCycle): | ||||||
|  | 			// Heartbeat time | ||||||
|  | 		} | ||||||
|  | 		// Execute a tiny data exchange to see responsiveness | ||||||
|  | 		w.stateLock.RLock() | ||||||
|  | 		if w.device == nil { | ||||||
|  | 			// Terminated while waiting for the lock | ||||||
|  | 			w.stateLock.RUnlock() | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		<-w.commsLock // Don't lock state while resolving version | ||||||
|  | 		err = w.driver.Heartbeat() | ||||||
|  | 		w.commsLock <- struct{}{} | ||||||
|  | 		w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 		if err != nil { | ||||||
|  | 			w.stateLock.Lock() // Lock state to tear the wallet down | ||||||
|  | 			w.close() | ||||||
|  | 			w.stateLock.Unlock() | ||||||
|  | 		} | ||||||
|  | 		// Ignore non hardware related errors | ||||||
|  | 		err = nil | ||||||
|  | 	} | ||||||
|  | 	// In case of error, wait for termination | ||||||
|  | 	if err != nil { | ||||||
|  | 		w.log.Debug("USB wallet health-check failed", "err", err) | ||||||
|  | 		errc = <-w.healthQuit | ||||||
|  | 	} | ||||||
|  | 	errc <- err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Close implements accounts.Wallet, closing the USB connection to the device. | ||||||
|  | func (w *wallet) Close() error { | ||||||
|  | 	// Ensure the wallet was opened | ||||||
|  | 	w.stateLock.RLock() | ||||||
|  | 	hQuit, dQuit := w.healthQuit, w.deriveQuit | ||||||
|  | 	w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	// Terminate the health checks | ||||||
|  | 	var herr error | ||||||
|  | 	if hQuit != nil { | ||||||
|  | 		errc := make(chan error) | ||||||
|  | 		hQuit <- errc | ||||||
|  | 		herr = <-errc // Save for later, we *must* close the USB | ||||||
|  | 	} | ||||||
|  | 	// Terminate the self-derivations | ||||||
|  | 	var derr error | ||||||
|  | 	if dQuit != nil { | ||||||
|  | 		errc := make(chan error) | ||||||
|  | 		dQuit <- errc | ||||||
|  | 		derr = <-errc // Save for later, we *must* close the USB | ||||||
|  | 	} | ||||||
|  | 	// Terminate the device connection | ||||||
|  | 	w.stateLock.Lock() | ||||||
|  | 	defer w.stateLock.Unlock() | ||||||
|  |  | ||||||
|  | 	w.healthQuit = nil | ||||||
|  | 	w.deriveQuit = nil | ||||||
|  | 	w.deriveReq = nil | ||||||
|  |  | ||||||
|  | 	if err := w.close(); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  | 	if herr != nil { | ||||||
|  | 		return herr | ||||||
|  | 	} | ||||||
|  | 	return derr | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // close is the internal wallet closer that terminates the USB connection and | ||||||
|  | // resets all the fields to their defaults. | ||||||
|  | // | ||||||
|  | // Note, close assumes the state lock is held! | ||||||
|  | func (w *wallet) close() error { | ||||||
|  | 	// Allow duplicate closes, especially for health-check failures | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	// Close the device, clear everything, then return | ||||||
|  | 	w.device.Close() | ||||||
|  | 	w.device = nil | ||||||
|  |  | ||||||
|  | 	w.accounts, w.paths = nil, nil | ||||||
|  | 	w.driver.Close() | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Accounts implements accounts.Wallet, returning the list of accounts pinned to | ||||||
|  | // the USB hardware wallet. If self-derivation was enabled, the account list is | ||||||
|  | // periodically expanded based on current chain state. | ||||||
|  | func (w *wallet) Accounts() []accounts.Account { | ||||||
|  | 	// Attempt self-derivation if it's running | ||||||
|  | 	reqc := make(chan struct{}, 1) | ||||||
|  | 	select { | ||||||
|  | 	case w.deriveReq <- reqc: | ||||||
|  | 		// Self-derivation request accepted, wait for it | ||||||
|  | 		<-reqc | ||||||
|  | 	default: | ||||||
|  | 		// Self-derivation offline, throttled or busy, skip | ||||||
|  | 	} | ||||||
|  | 	// Return whatever account list we ended up with | ||||||
|  | 	w.stateLock.RLock() | ||||||
|  | 	defer w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	cpy := make([]accounts.Account, len(w.accounts)) | ||||||
|  | 	copy(cpy, w.accounts) | ||||||
|  | 	return cpy | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // selfDerive is an account derivation loop that upon request attempts to find | ||||||
|  | // new non-zero accounts. | ||||||
|  | func (w *wallet) selfDerive() { | ||||||
|  | 	w.log.Debug("USB wallet self-derivation started") | ||||||
|  | 	defer w.log.Debug("USB wallet self-derivation stopped") | ||||||
|  |  | ||||||
|  | 	// Execute self-derivations until termination or error | ||||||
|  | 	var ( | ||||||
|  | 		reqc chan struct{} | ||||||
|  | 		errc chan error | ||||||
|  | 		err  error | ||||||
|  | 	) | ||||||
|  | 	for errc == nil && err == nil { | ||||||
|  | 		// Wait until either derivation or termination is requested | ||||||
|  | 		select { | ||||||
|  | 		case errc = <-w.deriveQuit: | ||||||
|  | 			// Termination requested | ||||||
|  | 			continue | ||||||
|  | 		case reqc = <-w.deriveReq: | ||||||
|  | 			// Account discovery requested | ||||||
|  | 		} | ||||||
|  | 		// Derivation needs a chain and device access, skip if either unavailable | ||||||
|  | 		w.stateLock.RLock() | ||||||
|  | 		if w.device == nil || w.deriveChain == nil { | ||||||
|  | 			w.stateLock.RUnlock() | ||||||
|  | 			reqc <- struct{}{} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		select { | ||||||
|  | 		case <-w.commsLock: | ||||||
|  | 		default: | ||||||
|  | 			w.stateLock.RUnlock() | ||||||
|  | 			reqc <- struct{}{} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// Device lock obtained, derive the next batch of accounts | ||||||
|  | 		var ( | ||||||
|  | 			accs  []accounts.Account | ||||||
|  | 			paths []accounts.DerivationPath | ||||||
|  |  | ||||||
|  | 			nextAddr = w.deriveNextAddr | ||||||
|  | 			nextPath = w.deriveNextPath | ||||||
|  |  | ||||||
|  | 			context = context.Background() | ||||||
|  | 		) | ||||||
|  | 		for empty := false; !empty; { | ||||||
|  | 			// Retrieve the next derived Ethereum account | ||||||
|  | 			if nextAddr == (common.Address{}) { | ||||||
|  | 				if nextAddr, err = w.driver.Derive(nextPath); err != nil { | ||||||
|  | 					w.log.Warn("USB wallet account derivation failed", "err", err) | ||||||
|  | 					break | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			// Check the account's status against the current chain state | ||||||
|  | 			var ( | ||||||
|  | 				balance *big.Int | ||||||
|  | 				nonce   uint64 | ||||||
|  | 			) | ||||||
|  | 			balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil) | ||||||
|  | 			if err != nil { | ||||||
|  | 				w.log.Warn("USB wallet balance retrieval failed", "err", err) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil) | ||||||
|  | 			if err != nil { | ||||||
|  | 				w.log.Warn("USB wallet nonce retrieval failed", "err", err) | ||||||
|  | 				break | ||||||
|  | 			} | ||||||
|  | 			// If the next account is empty, stop self-derivation, but add it nonetheless | ||||||
|  | 			if balance.Sign() == 0 && nonce == 0 { | ||||||
|  | 				empty = true | ||||||
|  | 			} | ||||||
|  | 			// We've just self-derived a new account, start tracking it locally | ||||||
|  | 			path := make(accounts.DerivationPath, len(nextPath)) | ||||||
|  | 			copy(path[:], nextPath[:]) | ||||||
|  | 			paths = append(paths, path) | ||||||
|  |  | ||||||
|  | 			account := accounts.Account{ | ||||||
|  | 				Address: nextAddr, | ||||||
|  | 				URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, | ||||||
|  | 			} | ||||||
|  | 			accs = append(accs, account) | ||||||
|  |  | ||||||
|  | 			// Display a log message to the user for new (or previously empty accounts) | ||||||
|  | 			if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) { | ||||||
|  | 				w.log.Info("USB wallet discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce) | ||||||
|  | 			} | ||||||
|  | 			// Fetch the next potential account | ||||||
|  | 			if !empty { | ||||||
|  | 				nextAddr = common.Address{} | ||||||
|  | 				nextPath[len(nextPath)-1]++ | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// Self derivation complete, release device lock | ||||||
|  | 		w.commsLock <- struct{}{} | ||||||
|  | 		w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 		// Insert any accounts successfully derived | ||||||
|  | 		w.stateLock.Lock() | ||||||
|  | 		for i := 0; i < len(accs); i++ { | ||||||
|  | 			if _, ok := w.paths[accs[i].Address]; !ok { | ||||||
|  | 				w.accounts = append(w.accounts, accs[i]) | ||||||
|  | 				w.paths[accs[i].Address] = paths[i] | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		// Shift the self-derivation forward | ||||||
|  | 		// TODO(karalabe): don't overwrite changes from wallet.SelfDerive | ||||||
|  | 		w.deriveNextAddr = nextAddr | ||||||
|  | 		w.deriveNextPath = nextPath | ||||||
|  | 		w.stateLock.Unlock() | ||||||
|  |  | ||||||
|  | 		// Notify the user of termination and loop after a bit of time (to avoid trashing) | ||||||
|  | 		reqc <- struct{}{} | ||||||
|  | 		if err == nil { | ||||||
|  | 			select { | ||||||
|  | 			case errc = <-w.deriveQuit: | ||||||
|  | 				// Termination requested, abort | ||||||
|  | 			case <-time.After(selfDeriveThrottling): | ||||||
|  | 				// Waited enough, willing to self-derive again | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	// In case of error, wait for termination | ||||||
|  | 	if err != nil { | ||||||
|  | 		w.log.Debug("USB wallet self-derivation failed", "err", err) | ||||||
|  | 		errc = <-w.deriveQuit | ||||||
|  | 	} | ||||||
|  | 	errc <- err | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Contains implements accounts.Wallet, returning whether a particular account is | ||||||
|  | // or is not pinned into this wallet instance. Although we could attempt to resolve | ||||||
|  | // unpinned accounts, that would be an non-negligible hardware operation. | ||||||
|  | func (w *wallet) Contains(account accounts.Account) bool { | ||||||
|  | 	w.stateLock.RLock() | ||||||
|  | 	defer w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	_, exists := w.paths[account.Address] | ||||||
|  | 	return exists | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Derive implements accounts.Wallet, deriving a new account at the specific | ||||||
|  | // derivation path. If pin is set to true, the account will be added to the list | ||||||
|  | // of tracked accounts. | ||||||
|  | func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { | ||||||
|  | 	// Try to derive the actual account and update its URL if successful | ||||||
|  | 	w.stateLock.RLock() // Avoid device disappearing during derivation | ||||||
|  |  | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		w.stateLock.RUnlock() | ||||||
|  | 		return accounts.Account{}, accounts.ErrWalletClosed | ||||||
|  | 	} | ||||||
|  | 	<-w.commsLock // Avoid concurrent hardware access | ||||||
|  | 	address, err := w.driver.Derive(path) | ||||||
|  | 	w.commsLock <- struct{}{} | ||||||
|  |  | ||||||
|  | 	w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	// If an error occurred or no pinning was requested, return | ||||||
|  | 	if err != nil { | ||||||
|  | 		return accounts.Account{}, err | ||||||
|  | 	} | ||||||
|  | 	account := accounts.Account{ | ||||||
|  | 		Address: address, | ||||||
|  | 		URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, | ||||||
|  | 	} | ||||||
|  | 	if !pin { | ||||||
|  | 		return account, nil | ||||||
|  | 	} | ||||||
|  | 	// Pinning needs to modify the state | ||||||
|  | 	w.stateLock.Lock() | ||||||
|  | 	defer w.stateLock.Unlock() | ||||||
|  |  | ||||||
|  | 	if _, ok := w.paths[address]; !ok { | ||||||
|  | 		w.accounts = append(w.accounts, account) | ||||||
|  | 		w.paths[address] = path | ||||||
|  | 	} | ||||||
|  | 	return account, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SelfDerive implements accounts.Wallet, trying to discover accounts that the | ||||||
|  | // user used previously (based on the chain state), but ones that he/she did not | ||||||
|  | // explicitly pin to the wallet manually. To avoid chain head monitoring, self | ||||||
|  | // derivation only runs during account listing (and even then throttled). | ||||||
|  | func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) { | ||||||
|  | 	w.stateLock.Lock() | ||||||
|  | 	defer w.stateLock.Unlock() | ||||||
|  |  | ||||||
|  | 	w.deriveNextPath = make(accounts.DerivationPath, len(base)) | ||||||
|  | 	copy(w.deriveNextPath[:], base[:]) | ||||||
|  |  | ||||||
|  | 	w.deriveNextAddr = common.Address{} | ||||||
|  | 	w.deriveChain = chain | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignHash implements accounts.Wallet, however signing arbitrary data is not | ||||||
|  | // supported for hardware wallets, so this method will always return an error. | ||||||
|  | func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { | ||||||
|  | 	return nil, accounts.ErrNotSupported | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger | ||||||
|  | // wallet to request a confirmation from the user. It returns either the signed | ||||||
|  | // transaction or a failure if the user denied the transaction. | ||||||
|  | // | ||||||
|  | // Note, if the version of the Ethereum application running on the Ledger wallet is | ||||||
|  | // too old to sign EIP-155 transactions, but such is requested nonetheless, an error | ||||||
|  | // will be returned opposed to silently signing in Homestead mode. | ||||||
|  | func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { | ||||||
|  | 	w.stateLock.RLock() // Comms have own mutex, this is for the state fields | ||||||
|  | 	defer w.stateLock.RUnlock() | ||||||
|  |  | ||||||
|  | 	// If the wallet is closed, abort | ||||||
|  | 	if w.device == nil { | ||||||
|  | 		return nil, accounts.ErrWalletClosed | ||||||
|  | 	} | ||||||
|  | 	// Make sure the requested account is contained within | ||||||
|  | 	path, ok := w.paths[account.Address] | ||||||
|  | 	if !ok { | ||||||
|  | 		return nil, accounts.ErrUnknownAccount | ||||||
|  | 	} | ||||||
|  | 	// All infos gathered and metadata checks out, request signing | ||||||
|  | 	<-w.commsLock | ||||||
|  | 	defer func() { w.commsLock <- struct{}{} }() | ||||||
|  |  | ||||||
|  | 	// Ensure the device isn't screwed with while user confirmation is pending | ||||||
|  | 	// TODO(karalabe): remove if hotplug lands on Windows | ||||||
|  | 	w.hub.commsLock.Lock() | ||||||
|  | 	w.hub.commsPend++ | ||||||
|  | 	w.hub.commsLock.Unlock() | ||||||
|  |  | ||||||
|  | 	defer func() { | ||||||
|  | 		w.hub.commsLock.Lock() | ||||||
|  | 		w.hub.commsPend-- | ||||||
|  | 		w.hub.commsLock.Unlock() | ||||||
|  | 	}() | ||||||
|  | 	// Sign the transaction and verify the sender to avoid hardware fault surprises | ||||||
|  | 	sender, signed, err := w.driver.SignTx(path, tx, chainID) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	if sender != account.Address { | ||||||
|  | 		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) | ||||||
|  | 	} | ||||||
|  | 	return signed, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary | ||||||
|  | // data is not supported for Ledger wallets, so this method will always return | ||||||
|  | // an error. | ||||||
|  | func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { | ||||||
|  | 	return w.SignHash(account, hash) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given | ||||||
|  | // transaction with the given account using passphrase as extra authentication. | ||||||
|  | // Since USB wallets don't rely on passphrases, these are silently ignored. | ||||||
|  | func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { | ||||||
|  | 	return w.SignTx(account, tx, chainID) | ||||||
|  | } | ||||||
| @@ -251,7 +251,9 @@ func startNode(ctx *cli.Context, stack *node.Node) { | |||||||
| 					log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) | 					log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err) | ||||||
| 				} | 				} | ||||||
| 			case accounts.WalletOpened: | 			case accounts.WalletOpened: | ||||||
| 				log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", event.Wallet.Status()) | 				status, _ := event.Wallet.Status() | ||||||
|  | 				log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status) | ||||||
|  |  | ||||||
| 				if event.Wallet.URL().Scheme == "ledger" { | 				if event.Wallet.URL().Scheme == "ledger" { | ||||||
| 					event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader) | 					event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader) | ||||||
| 				} else { | 				} else { | ||||||
|   | |||||||
| @@ -230,18 +230,25 @@ func (s *PrivateAccountAPI) ListAccounts() []common.Address { | |||||||
| type rawWallet struct { | type rawWallet struct { | ||||||
| 	URL      string             `json:"url"` | 	URL      string             `json:"url"` | ||||||
| 	Status   string             `json:"status"` | 	Status   string             `json:"status"` | ||||||
| 	Accounts []accounts.Account `json:"accounts"` | 	Failure  string             `json:"failure,omitempty"` | ||||||
|  | 	Accounts []accounts.Account `json:"accounts,omitempty"` | ||||||
| } | } | ||||||
|  |  | ||||||
| // ListWallets will return a list of wallets this node manages. | // ListWallets will return a list of wallets this node manages. | ||||||
| func (s *PrivateAccountAPI) ListWallets() []rawWallet { | func (s *PrivateAccountAPI) ListWallets() []rawWallet { | ||||||
| 	wallets := make([]rawWallet, 0) // return [] instead of nil if empty | 	wallets := make([]rawWallet, 0) // return [] instead of nil if empty | ||||||
| 	for _, wallet := range s.am.Wallets() { | 	for _, wallet := range s.am.Wallets() { | ||||||
| 		wallets = append(wallets, rawWallet{ | 		status, failure := wallet.Status() | ||||||
|  |  | ||||||
|  | 		raw := rawWallet{ | ||||||
| 			URL:      wallet.URL().String(), | 			URL:      wallet.URL().String(), | ||||||
| 			Status:   wallet.Status(), | 			Status:   status, | ||||||
| 			Accounts: wallet.Accounts(), | 			Accounts: wallet.Accounts(), | ||||||
| 		}) | 		} | ||||||
|  | 		if failure != nil { | ||||||
|  | 			raw.Failure = failure.Error() | ||||||
|  | 		} | ||||||
|  | 		wallets = append(wallets, raw) | ||||||
| 	} | 	} | ||||||
| 	return wallets | 	return wallets | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user