github.com/codingfuture/orig-energi3@v0.8.4/accounts/usbwallet/wallet.go (about)

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