github.com/theQRL/go-zond@v0.2.1/accounts/usbwallet/wallet.go (about)

     1  // Copyright 2017 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package usbwallet implements support for USB hardware wallets.
    18  package usbwallet
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"io"
    24  	"math/big"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/karalabe/usb"
    29  	"github.com/theQRL/go-zond"
    30  	"github.com/theQRL/go-zond/accounts"
    31  	"github.com/theQRL/go-zond/common"
    32  	"github.com/theQRL/go-zond/core/types"
    33  	"github.com/theQRL/go-zond/crypto"
    34  	"github.com/theQRL/go-zond/log"
    35  )
    36  
    37  // Maximum time between wallet health checks to detect USB unplugs.
    38  const heartbeatCycle = time.Second
    39  
    40  // Minimum time to wait between self derivation attempts, even it the user is
    41  // requesting accounts like crazy.
    42  const selfDeriveThrottling = time.Second
    43  
    44  // driver defines the vendor specific functionality hardware wallets instances
    45  // must implement to allow using them with the wallet lifecycle management.
    46  type driver interface {
    47  	// Status returns a textual status to aid the user in the current state of the
    48  	// wallet. It also returns an error indicating any failure the wallet might have
    49  	// encountered.
    50  	Status() (string, error)
    51  
    52  	// Open initializes access to a wallet instance. The passphrase parameter may
    53  	// or may not be used by the implementation of a particular wallet instance.
    54  	Open(device io.ReadWriter, passphrase string) error
    55  
    56  	// Close releases any resources held by an open wallet instance.
    57  	Close() error
    58  
    59  	// Heartbeat performs a sanity check against the hardware wallet to see if it
    60  	// is still online and healthy.
    61  	Heartbeat() error
    62  
    63  	// Derive sends a derivation request to the USB device and returns the Ethereum
    64  	// address located on that path.
    65  	Derive(path accounts.DerivationPath) (common.Address, error)
    66  
    67  	// SignTx sends the transaction to the USB device and waits for the user to confirm
    68  	// or deny the transaction.
    69  	SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
    70  
    71  	SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error)
    72  }
    73  
    74  // wallet represents the common functionality shared by all USB hardware
    75  // wallets to prevent reimplementing the same complex maintenance mechanisms
    76  // for different vendors.
    77  type wallet struct {
    78  	// hub    *Hub          // USB hub scanning
    79  	driver driver        // Hardware implementation of the low level device operations
    80  	url    *accounts.URL // Textual URL uniquely identifying this wallet
    81  
    82  	info   usb.DeviceInfo // Known USB device infos about the wallet
    83  	device usb.Device     // USB device advertising itself as a hardware wallet
    84  
    85  	accounts []accounts.Account                         // List of derive accounts pinned on the hardware wallet
    86  	paths    map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
    87  
    88  	deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported)
    89  	deriveNextAddrs []common.Address          // Next derived account addresses for auto-discovery (multiple bases supported)
    90  	deriveChain     zond.ChainStateReader     // Blockchain state reader to discover used account with
    91  	deriveReq       chan chan struct{}        // Channel to request a self-derivation on
    92  	deriveQuit      chan chan error           // Channel to terminate the self-deriver with
    93  
    94  	healthQuit chan chan error
    95  
    96  	// Locking a hardware wallet is a bit special. Since hardware devices are lower
    97  	// performing, any communication with them might take a non negligible amount of
    98  	// time. Worse still, waiting for user confirmation can take arbitrarily long,
    99  	// but exclusive communication must be upheld during. Locking the entire wallet
   100  	// in the mean time however would stall any parts of the system that don't want
   101  	// to communicate, just read some state (e.g. list the accounts).
   102  	//
   103  	// As such, a hardware wallet needs two locks to function correctly. A state
   104  	// lock can be used to protect the wallet's software-side internal state, which
   105  	// must not be held exclusively during hardware communication. A communication
   106  	// lock can be used to achieve exclusive access to the device itself, this one
   107  	// however should allow "skipping" waiting for operations that might want to
   108  	// use the device, but can live without too (e.g. account self-derivation).
   109  	//
   110  	// Since we have two locks, it's important to know how to properly use them:
   111  	//   - Communication requires the `device` to not change, so obtaining the
   112  	//     commsLock should be done after having a stateLock.
   113  	//   - Communication must not disable read access to the wallet state, so it
   114  	//     must only ever hold a *read* lock to stateLock.
   115  	commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked
   116  	stateLock sync.RWMutex  // Protects read and write access to the wallet struct fields
   117  
   118  	log log.Logger // Contextual logger to tag the base with its id
   119  }
   120  
   121  // URL implements accounts.Wallet, returning the URL of the USB hardware device.
   122  func (w *wallet) URL() accounts.URL {
   123  	return *w.url // Immutable, no need for a lock
   124  }
   125  
   126  // Status implements accounts.Wallet, returning a custom status message from the
   127  // underlying vendor-specific hardware wallet implementation.
   128  func (w *wallet) Status() (string, error) {
   129  	w.stateLock.RLock() // No device communication, state lock is enough
   130  	defer w.stateLock.RUnlock()
   131  
   132  	status, failure := w.driver.Status()
   133  	if w.device == nil {
   134  		return "Closed", failure
   135  	}
   136  	return status, failure
   137  }
   138  
   139  // Open implements accounts.Wallet, attempting to open a USB connection to the
   140  // hardware wallet.
   141  func (w *wallet) Open(passphrase string) error {
   142  	w.stateLock.Lock() // State lock is enough since there's no connection yet at this point
   143  	defer w.stateLock.Unlock()
   144  
   145  	// If the device was already opened once, refuse to try again
   146  	if w.paths != nil {
   147  		return accounts.ErrWalletAlreadyOpen
   148  	}
   149  	// Make sure the actual device connection is done only once
   150  	if w.device == nil {
   151  		device, err := w.info.Open()
   152  		if err != nil {
   153  			return err
   154  		}
   155  		w.device = device
   156  		w.commsLock = make(chan struct{}, 1)
   157  		w.commsLock <- struct{}{} // Enable lock
   158  	}
   159  	// Delegate device initialization to the underlying driver
   160  	if err := w.driver.Open(w.device, passphrase); err != nil {
   161  		return err
   162  	}
   163  	// Connection successful, start life-cycle management
   164  	w.paths = make(map[common.Address]accounts.DerivationPath)
   165  
   166  	w.deriveReq = make(chan chan struct{})
   167  	w.deriveQuit = make(chan chan error)
   168  	w.healthQuit = make(chan chan error)
   169  
   170  	go w.heartbeat()
   171  	go w.selfDerive()
   172  
   173  	// TODO(now.youtrack.cloud/issue/TGZ-4)
   174  	// Notify anyone listening for wallet events that a new device is accessible
   175  	// go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened})
   176  
   177  	return nil
   178  }
   179  
   180  // heartbeat is a health check loop for the USB wallets to periodically verify
   181  // whether they are still present or if they malfunctioned.
   182  func (w *wallet) heartbeat() {
   183  	w.log.Debug("USB wallet health-check started")
   184  	defer w.log.Debug("USB wallet health-check stopped")
   185  
   186  	// Execute heartbeat checks until termination or error
   187  	var (
   188  		errc chan error
   189  		err  error
   190  	)
   191  	for errc == nil && err == nil {
   192  		// Wait until termination is requested or the heartbeat cycle arrives
   193  		select {
   194  		case errc = <-w.healthQuit:
   195  			// Termination requested
   196  			continue
   197  		case <-time.After(heartbeatCycle):
   198  			// Heartbeat time
   199  		}
   200  		// Execute a tiny data exchange to see responsiveness
   201  		w.stateLock.RLock()
   202  		if w.device == nil {
   203  			// Terminated while waiting for the lock
   204  			w.stateLock.RUnlock()
   205  			continue
   206  		}
   207  		<-w.commsLock // Don't lock state while resolving version
   208  		err = w.driver.Heartbeat()
   209  		w.commsLock <- struct{}{}
   210  		w.stateLock.RUnlock()
   211  
   212  		if err != nil {
   213  			w.stateLock.Lock() // Lock state to tear the wallet down
   214  			w.close()
   215  			w.stateLock.Unlock()
   216  		}
   217  		// Ignore non hardware related errors
   218  		err = nil
   219  	}
   220  	// In case of error, wait for termination
   221  	if err != nil {
   222  		w.log.Debug("USB wallet health-check failed", "err", err)
   223  		errc = <-w.healthQuit
   224  	}
   225  	errc <- err
   226  }
   227  
   228  // Close implements accounts.Wallet, closing the USB connection to the device.
   229  func (w *wallet) Close() error {
   230  	// Ensure the wallet was opened
   231  	w.stateLock.RLock()
   232  	hQuit, dQuit := w.healthQuit, w.deriveQuit
   233  	w.stateLock.RUnlock()
   234  
   235  	// Terminate the health checks
   236  	var herr error
   237  	if hQuit != nil {
   238  		errc := make(chan error)
   239  		hQuit <- errc
   240  		herr = <-errc // Save for later, we *must* close the USB
   241  	}
   242  	// Terminate the self-derivations
   243  	var derr error
   244  	if dQuit != nil {
   245  		errc := make(chan error)
   246  		dQuit <- errc
   247  		derr = <-errc // Save for later, we *must* close the USB
   248  	}
   249  	// Terminate the device connection
   250  	w.stateLock.Lock()
   251  	defer w.stateLock.Unlock()
   252  
   253  	w.healthQuit = nil
   254  	w.deriveQuit = nil
   255  	w.deriveReq = nil
   256  
   257  	if err := w.close(); err != nil {
   258  		return err
   259  	}
   260  	if herr != nil {
   261  		return herr
   262  	}
   263  	return derr
   264  }
   265  
   266  // close is the internal wallet closer that terminates the USB connection and
   267  // resets all the fields to their defaults.
   268  //
   269  // Note, close assumes the state lock is held!
   270  func (w *wallet) close() error {
   271  	// Allow duplicate closes, especially for health-check failures
   272  	if w.device == nil {
   273  		return nil
   274  	}
   275  	// Close the device, clear everything, then return
   276  	w.device.Close()
   277  	w.device = nil
   278  
   279  	w.accounts, w.paths = nil, nil
   280  	return w.driver.Close()
   281  }
   282  
   283  // Accounts implements accounts.Wallet, returning the list of accounts pinned to
   284  // the USB hardware wallet. If self-derivation was enabled, the account list is
   285  // periodically expanded based on current chain state.
   286  func (w *wallet) Accounts() []accounts.Account {
   287  	// Attempt self-derivation if it's running
   288  	reqc := make(chan struct{}, 1)
   289  	select {
   290  	case w.deriveReq <- reqc:
   291  		// Self-derivation request accepted, wait for it
   292  		<-reqc
   293  	default:
   294  		// Self-derivation offline, throttled or busy, skip
   295  	}
   296  	// Return whatever account list we ended up with
   297  	w.stateLock.RLock()
   298  	defer w.stateLock.RUnlock()
   299  
   300  	cpy := make([]accounts.Account, len(w.accounts))
   301  	copy(cpy, w.accounts)
   302  	return cpy
   303  }
   304  
   305  // selfDerive is an account derivation loop that upon request attempts to find
   306  // new non-zero accounts.
   307  func (w *wallet) selfDerive() {
   308  	w.log.Debug("USB wallet self-derivation started")
   309  	defer w.log.Debug("USB wallet self-derivation stopped")
   310  
   311  	// Execute self-derivations until termination or error
   312  	var (
   313  		reqc chan struct{}
   314  		errc chan error
   315  		err  error
   316  	)
   317  	for errc == nil && err == nil {
   318  		// Wait until either derivation or termination is requested
   319  		select {
   320  		case errc = <-w.deriveQuit:
   321  			// Termination requested
   322  			continue
   323  		case reqc = <-w.deriveReq:
   324  			// Account discovery requested
   325  		}
   326  		// Derivation needs a chain and device access, skip if either unavailable
   327  		w.stateLock.RLock()
   328  		if w.device == nil || w.deriveChain == nil {
   329  			w.stateLock.RUnlock()
   330  			reqc <- struct{}{}
   331  			continue
   332  		}
   333  		select {
   334  		case <-w.commsLock:
   335  		default:
   336  			w.stateLock.RUnlock()
   337  			reqc <- struct{}{}
   338  			continue
   339  		}
   340  		// Device lock obtained, derive the next batch of accounts
   341  		var (
   342  			accs  []accounts.Account
   343  			paths []accounts.DerivationPath
   344  
   345  			nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...)
   346  			nextAddrs = append([]common.Address{}, w.deriveNextAddrs...)
   347  
   348  			context = context.Background()
   349  		)
   350  		for i := 0; i < len(nextAddrs); i++ {
   351  			for empty := false; !empty; {
   352  				// Retrieve the next derived Ethereum account
   353  				if nextAddrs[i] == (common.Address{}) {
   354  					if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil {
   355  						w.log.Warn("USB wallet account derivation failed", "err", err)
   356  						break
   357  					}
   358  				}
   359  				// Check the account's status against the current chain state
   360  				var (
   361  					balance *big.Int
   362  					nonce   uint64
   363  				)
   364  				balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil)
   365  				if err != nil {
   366  					w.log.Warn("USB wallet balance retrieval failed", "err", err)
   367  					break
   368  				}
   369  				nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil)
   370  				if err != nil {
   371  					w.log.Warn("USB wallet nonce retrieval failed", "err", err)
   372  					break
   373  				}
   374  				// We've just self-derived a new account, start tracking it locally
   375  				// unless the account was empty.
   376  				path := make(accounts.DerivationPath, len(nextPaths[i]))
   377  				copy(path[:], nextPaths[i][:])
   378  				if balance.Sign() == 0 && nonce == 0 {
   379  					empty = true
   380  					// If it indeed was empty, make a log output for it anyway. In the case
   381  					// of legacy-ledger, the first account on the legacy-path will
   382  					// be shown to the user, even if we don't actively track it
   383  					if i < len(nextAddrs)-1 {
   384  						w.log.Info("Skipping tracking first account on legacy path, use personal.deriveAccount(<url>,<path>, false) to track",
   385  							"path", path, "address", nextAddrs[i])
   386  						break
   387  					}
   388  				}
   389  				paths = append(paths, path)
   390  				account := accounts.Account{
   391  					Address: nextAddrs[i],
   392  					URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
   393  				}
   394  				accs = append(accs, account)
   395  
   396  				// Display a log message to the user for new (or previously empty accounts)
   397  				if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) {
   398  					w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce)
   399  				}
   400  				// Fetch the next potential account
   401  				if !empty {
   402  					nextAddrs[i] = common.Address{}
   403  					nextPaths[i][len(nextPaths[i])-1]++
   404  				}
   405  			}
   406  		}
   407  		// Self derivation complete, release device lock
   408  		w.commsLock <- struct{}{}
   409  		w.stateLock.RUnlock()
   410  
   411  		// Insert any accounts successfully derived
   412  		w.stateLock.Lock()
   413  		for i := 0; i < len(accs); i++ {
   414  			if _, ok := w.paths[accs[i].Address]; !ok {
   415  				w.accounts = append(w.accounts, accs[i])
   416  				w.paths[accs[i].Address] = paths[i]
   417  			}
   418  		}
   419  		// Shift the self-derivation forward
   420  		// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
   421  		w.deriveNextAddrs = nextAddrs
   422  		w.deriveNextPaths = nextPaths
   423  		w.stateLock.Unlock()
   424  
   425  		// Notify the user of termination and loop after a bit of time (to avoid trashing)
   426  		reqc <- struct{}{}
   427  		if err == nil {
   428  			select {
   429  			case errc = <-w.deriveQuit:
   430  				// Termination requested, abort
   431  			case <-time.After(selfDeriveThrottling):
   432  				// Waited enough, willing to self-derive again
   433  			}
   434  		}
   435  	}
   436  	// In case of error, wait for termination
   437  	if err != nil {
   438  		w.log.Debug("USB wallet self-derivation failed", "err", err)
   439  		errc = <-w.deriveQuit
   440  	}
   441  	errc <- err
   442  }
   443  
   444  // Contains implements accounts.Wallet, returning whether a particular account is
   445  // or is not pinned into this wallet instance. Although we could attempt to resolve
   446  // unpinned accounts, that would be an non-negligible hardware operation.
   447  func (w *wallet) Contains(account accounts.Account) bool {
   448  	w.stateLock.RLock()
   449  	defer w.stateLock.RUnlock()
   450  
   451  	_, exists := w.paths[account.Address]
   452  	return exists
   453  }
   454  
   455  // Derive implements accounts.Wallet, deriving a new account at the specific
   456  // derivation path. If pin is set to true, the account will be added to the list
   457  // of tracked accounts.
   458  func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
   459  	// Try to derive the actual account and update its URL if successful
   460  	w.stateLock.RLock() // Avoid device disappearing during derivation
   461  
   462  	if w.device == nil {
   463  		w.stateLock.RUnlock()
   464  		return accounts.Account{}, accounts.ErrWalletClosed
   465  	}
   466  	<-w.commsLock // Avoid concurrent hardware access
   467  	address, err := w.driver.Derive(path)
   468  	w.commsLock <- struct{}{}
   469  
   470  	w.stateLock.RUnlock()
   471  
   472  	// If an error occurred or no pinning was requested, return
   473  	if err != nil {
   474  		return accounts.Account{}, err
   475  	}
   476  	account := accounts.Account{
   477  		Address: address,
   478  		URL:     accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
   479  	}
   480  	if !pin {
   481  		return account, nil
   482  	}
   483  	// Pinning needs to modify the state
   484  	w.stateLock.Lock()
   485  	defer w.stateLock.Unlock()
   486  
   487  	if _, ok := w.paths[address]; !ok {
   488  		w.accounts = append(w.accounts, account)
   489  		w.paths[address] = make(accounts.DerivationPath, len(path))
   490  		copy(w.paths[address], path)
   491  	}
   492  	return account, nil
   493  }
   494  
   495  // SelfDerive sets a base account derivation path from which the wallet attempts
   496  // to discover non zero accounts and automatically add them to list of tracked
   497  // accounts.
   498  //
   499  // Note, self derivation will increment the last component of the specified path
   500  // opposed to descending into a child path to allow discovering accounts starting
   501  // from non zero components.
   502  //
   503  // Some hardware wallets switched derivation paths through their evolution, so
   504  // this method supports providing multiple bases to discover old user accounts
   505  // too. Only the last base will be used to derive the next empty account.
   506  //
   507  // You can disable automatic account discovery by calling SelfDerive with a nil
   508  // chain state reader.
   509  func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain zond.ChainStateReader) {
   510  	w.stateLock.Lock()
   511  	defer w.stateLock.Unlock()
   512  
   513  	w.deriveNextPaths = make([]accounts.DerivationPath, len(bases))
   514  	for i, base := range bases {
   515  		w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base))
   516  		copy(w.deriveNextPaths[i][:], base[:])
   517  	}
   518  	w.deriveNextAddrs = make([]common.Address, len(bases))
   519  	w.deriveChain = chain
   520  }
   521  
   522  // signHash implements accounts.Wallet, however signing arbitrary data is not
   523  // supported for hardware wallets, so this method will always return an error.
   524  func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) {
   525  	return nil, accounts.ErrNotSupported
   526  }
   527  
   528  // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
   529  func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
   530  	// Unless we are doing 712 signing, simply dispatch to signHash
   531  	if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) {
   532  		return w.signHash(account, crypto.Keccak256(data))
   533  	}
   534  
   535  	// dispatch to 712 signing if the mimetype is TypedData and the format matches
   536  	w.stateLock.RLock() // Comms have own mutex, this is for the state fields
   537  	defer w.stateLock.RUnlock()
   538  
   539  	// If the wallet is closed, abort
   540  	if w.device == nil {
   541  		return nil, accounts.ErrWalletClosed
   542  	}
   543  	// Make sure the requested account is contained within
   544  	path, ok := w.paths[account.Address]
   545  	if !ok {
   546  		return nil, accounts.ErrUnknownAccount
   547  	}
   548  	// All infos gathered and metadata checks out, request signing
   549  	<-w.commsLock
   550  	defer func() { w.commsLock <- struct{}{} }()
   551  
   552  	// TODO(now.youtrack.cloud/issue/TGZ-4)
   553  	// Ensure the device isn't screwed with while user confirmation is pending
   554  	// TODO(karalabe): remove if hotplug lands on Windows
   555  	// w.hub.commsLock.Lock()
   556  	// w.hub.commsPend++
   557  	// w.hub.commsLock.Unlock()
   558  
   559  	// TODO(now.youtrack.cloud/issue/TGZ-4)
   560  	// defer func() {
   561  	// 	w.hub.commsLock.Lock()
   562  	// 	w.hub.commsPend--
   563  	// 	w.hub.commsLock.Unlock()
   564  	// }()
   565  	// Sign the transaction
   566  	signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66])
   567  	if err != nil {
   568  		return nil, err
   569  	}
   570  	return signature, nil
   571  }
   572  
   573  // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
   574  // data with the given account using passphrase as extra authentication.
   575  // Since USB wallets don't rely on passphrases, these are silently ignored.
   576  func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) {
   577  	return w.SignData(account, mimeType, data)
   578  }
   579  
   580  func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
   581  	return w.signHash(account, accounts.TextHash(text))
   582  }
   583  
   584  // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
   585  // wallet to request a confirmation from the user. It returns either the signed
   586  // transaction or a failure if the user denied the transaction.
   587  //
   588  // Note, if the version of the Ethereum application running on the Ledger wallet is
   589  // too old to sign EIP-155 transactions, but such is requested nonetheless, an error
   590  // will be returned opposed to silently signing in Homestead mode.
   591  func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
   592  	w.stateLock.RLock() // Comms have own mutex, this is for the state fields
   593  	defer w.stateLock.RUnlock()
   594  
   595  	// If the wallet is closed, abort
   596  	if w.device == nil {
   597  		return nil, accounts.ErrWalletClosed
   598  	}
   599  	// Make sure the requested account is contained within
   600  	path, ok := w.paths[account.Address]
   601  	if !ok {
   602  		return nil, accounts.ErrUnknownAccount
   603  	}
   604  	// All infos gathered and metadata checks out, request signing
   605  	<-w.commsLock
   606  	defer func() { w.commsLock <- struct{}{} }()
   607  
   608  	// TODO(now.youtrack.cloud/issue/TGZ-4)
   609  	// // Ensure the device isn't screwed with while user confirmation is pending
   610  	// // TODO(karalabe): remove if hotplug lands on Windows
   611  	// w.hub.commsLock.Lock()
   612  	// w.hub.commsPend++
   613  	// w.hub.commsLock.Unlock()
   614  
   615  	// TODO(now.youtrack.cloud/issue/TGZ-4)
   616  	// defer func() {
   617  	// 	w.hub.commsLock.Lock()
   618  	// 	w.hub.commsPend--
   619  	// 	w.hub.commsLock.Unlock()
   620  	// }()
   621  	// Sign the transaction and verify the sender to avoid hardware fault surprises
   622  	sender, signed, err := w.driver.SignTx(path, tx, chainID)
   623  	if err != nil {
   624  		return nil, err
   625  	}
   626  	if sender != account.Address {
   627  		return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
   628  	}
   629  	return signed, nil
   630  }
   631  
   632  // SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary
   633  // data is not supported for Ledger wallets, so this method will always return
   634  // an error.
   635  func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
   636  	return w.SignText(account, accounts.TextHash(text))
   637  }
   638  
   639  // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
   640  // transaction with the given account using passphrase as extra authentication.
   641  // Since USB wallets don't rely on passphrases, these are silently ignored.
   642  func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
   643  	return w.SignTx(account, tx, chainID)
   644  }