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