gitlab.com/aquachain/aquachain@v1.17.16-rc3.0.20221018032414-e3ddf1e1c055/aqua/accounts/usbwallet/wallet.go (about)

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