github.com/mprishchepo/go-ethereum@v1.9.7-0.20191031044858-21506be82b68/accounts/usbwallet/wallet.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package usbwallet implements support for USB hardware wallets. 18 package usbwallet 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "math/big" 25 "sync" 26 "time" 27 28 ethereum "github.com/Fantom-foundation/go-ethereum" 29 "github.com/Fantom-foundation/go-ethereum/accounts" 30 "github.com/Fantom-foundation/go-ethereum/common" 31 "github.com/Fantom-foundation/go-ethereum/core/types" 32 "github.com/Fantom-foundation/go-ethereum/crypto" 33 "github.com/Fantom-foundation/go-ethereum/log" 34 "github.com/karalabe/usb" 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 usb.DeviceInfo // Known USB device infos about the wallet 81 device usb.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 deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) 87 deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) 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 return w.driver.Close() 278 } 279 280 // Accounts implements accounts.Wallet, returning the list of accounts pinned to 281 // the USB hardware wallet. If self-derivation was enabled, the account list is 282 // periodically expanded based on current chain state. 283 func (w *wallet) Accounts() []accounts.Account { 284 // Attempt self-derivation if it's running 285 reqc := make(chan struct{}, 1) 286 select { 287 case w.deriveReq <- reqc: 288 // Self-derivation request accepted, wait for it 289 <-reqc 290 default: 291 // Self-derivation offline, throttled or busy, skip 292 } 293 // Return whatever account list we ended up with 294 w.stateLock.RLock() 295 defer w.stateLock.RUnlock() 296 297 cpy := make([]accounts.Account, len(w.accounts)) 298 copy(cpy, w.accounts) 299 return cpy 300 } 301 302 // selfDerive is an account derivation loop that upon request attempts to find 303 // new non-zero accounts. 304 func (w *wallet) selfDerive() { 305 w.log.Debug("USB wallet self-derivation started") 306 defer w.log.Debug("USB wallet self-derivation stopped") 307 308 // Execute self-derivations until termination or error 309 var ( 310 reqc chan struct{} 311 errc chan error 312 err error 313 ) 314 for errc == nil && err == nil { 315 // Wait until either derivation or termination is requested 316 select { 317 case errc = <-w.deriveQuit: 318 // Termination requested 319 continue 320 case reqc = <-w.deriveReq: 321 // Account discovery requested 322 } 323 // Derivation needs a chain and device access, skip if either unavailable 324 w.stateLock.RLock() 325 if w.device == nil || w.deriveChain == nil { 326 w.stateLock.RUnlock() 327 reqc <- struct{}{} 328 continue 329 } 330 select { 331 case <-w.commsLock: 332 default: 333 w.stateLock.RUnlock() 334 reqc <- struct{}{} 335 continue 336 } 337 // Device lock obtained, derive the next batch of accounts 338 var ( 339 accs []accounts.Account 340 paths []accounts.DerivationPath 341 342 nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) 343 nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) 344 345 context = context.Background() 346 ) 347 for i := 0; i < len(nextAddrs); i++ { 348 for empty := false; !empty; { 349 // Retrieve the next derived Ethereum account 350 if nextAddrs[i] == (common.Address{}) { 351 if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil { 352 w.log.Warn("USB wallet account derivation failed", "err", err) 353 break 354 } 355 } 356 // Check the account's status against the current chain state 357 var ( 358 balance *big.Int 359 nonce uint64 360 ) 361 balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) 362 if err != nil { 363 w.log.Warn("USB wallet balance retrieval failed", "err", err) 364 break 365 } 366 nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) 367 if err != nil { 368 w.log.Warn("USB wallet nonce retrieval failed", "err", err) 369 break 370 } 371 // If the next account is empty, stop self-derivation, but add for the last base path 372 if balance.Sign() == 0 && nonce == 0 { 373 empty = true 374 if i < len(nextAddrs)-1 { 375 break 376 } 377 } 378 // We've just self-derived a new account, start tracking it locally 379 path := make(accounts.DerivationPath, len(nextPaths[i])) 380 copy(path[:], nextPaths[i][:]) 381 paths = append(paths, path) 382 383 account := accounts.Account{ 384 Address: nextAddrs[i], 385 URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, 386 } 387 accs = append(accs, account) 388 389 // Display a log message to the user for new (or previously empty accounts) 390 if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) { 391 w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) 392 } 393 // Fetch the next potential account 394 if !empty { 395 nextAddrs[i] = common.Address{} 396 nextPaths[i][len(nextPaths[i])-1]++ 397 } 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.deriveNextAddrs = nextAddrs 415 w.deriveNextPaths = nextPaths 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] = make(accounts.DerivationPath, len(path)) 483 copy(w.paths[address], path) 484 } 485 return account, nil 486 } 487 488 // SelfDerive sets a base account derivation path from which the wallet attempts 489 // to discover non zero accounts and automatically add them to list of tracked 490 // accounts. 491 // 492 // Note, self derivaton will increment the last component of the specified path 493 // opposed to decending into a child path to allow discovering accounts starting 494 // from non zero components. 495 // 496 // Some hardware wallets switched derivation paths through their evolution, so 497 // this method supports providing multiple bases to discover old user accounts 498 // too. Only the last base will be used to derive the next empty account. 499 // 500 // You can disable automatic account discovery by calling SelfDerive with a nil 501 // chain state reader. 502 func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { 503 w.stateLock.Lock() 504 defer w.stateLock.Unlock() 505 506 w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) 507 for i, base := range bases { 508 w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) 509 copy(w.deriveNextPaths[i][:], base[:]) 510 } 511 w.deriveNextAddrs = make([]common.Address, len(bases)) 512 w.deriveChain = chain 513 } 514 515 // signHash implements accounts.Wallet, however signing arbitrary data is not 516 // supported for hardware wallets, so this method will always return an error. 517 func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { 518 return nil, accounts.ErrNotSupported 519 } 520 521 // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 522 func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 523 return w.signHash(account, crypto.Keccak256(data)) 524 } 525 526 // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given 527 // data with the given account using passphrase as extra authentication. 528 // Since USB wallets don't rely on passphrases, these are silently ignored. 529 func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 530 return w.SignData(account, mimeType, data) 531 } 532 533 func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { 534 return w.signHash(account, accounts.TextHash(text)) 535 } 536 537 // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger 538 // wallet to request a confirmation from the user. It returns either the signed 539 // transaction or a failure if the user denied the transaction. 540 // 541 // Note, if the version of the Ethereum application running on the Ledger wallet is 542 // too old to sign EIP-155 transactions, but such is requested nonetheless, an error 543 // will be returned opposed to silently signing in Homestead mode. 544 func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 545 w.stateLock.RLock() // Comms have own mutex, this is for the state fields 546 defer w.stateLock.RUnlock() 547 548 // If the wallet is closed, abort 549 if w.device == nil { 550 return nil, accounts.ErrWalletClosed 551 } 552 // Make sure the requested account is contained within 553 path, ok := w.paths[account.Address] 554 if !ok { 555 return nil, accounts.ErrUnknownAccount 556 } 557 // All infos gathered and metadata checks out, request signing 558 <-w.commsLock 559 defer func() { w.commsLock <- struct{}{} }() 560 561 // Ensure the device isn't screwed with while user confirmation is pending 562 // TODO(karalabe): remove if hotplug lands on Windows 563 w.hub.commsLock.Lock() 564 w.hub.commsPend++ 565 w.hub.commsLock.Unlock() 566 567 defer func() { 568 w.hub.commsLock.Lock() 569 w.hub.commsPend-- 570 w.hub.commsLock.Unlock() 571 }() 572 // Sign the transaction and verify the sender to avoid hardware fault surprises 573 sender, signed, err := w.driver.SignTx(path, tx, chainID) 574 if err != nil { 575 return nil, err 576 } 577 if sender != account.Address { 578 return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 579 } 580 return signed, nil 581 } 582 583 // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary 584 // data is not supported for Ledger wallets, so this method will always return 585 // an error. 586 func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 587 return w.SignText(account, accounts.TextHash(text)) 588 } 589 590 // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given 591 // transaction with the given account using passphrase as extra authentication. 592 // Since USB wallets don't rely on passphrases, these are silently ignored. 593 func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 594 return w.SignTx(account, tx, chainID) 595 }