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 }