github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/accounts/usbwallet/wallet.go (about)

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