github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/usbwallet/wallet.go (about)

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