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