github.com/cryptotooltop/go-ethereum@v0.0.0-20231103184714-151d1922f3e5/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 30 "github.com/scroll-tech/go-ethereum" 31 "github.com/scroll-tech/go-ethereum/accounts" 32 "github.com/scroll-tech/go-ethereum/common" 33 "github.com/scroll-tech/go-ethereum/core/types" 34 "github.com/scroll-tech/go-ethereum/crypto" 35 "github.com/scroll-tech/go-ethereum/log" 36 ) 37 38 // Maximum time between wallet health checks to detect USB unplugs. 39 const heartbeatCycle = time.Second 40 41 // Minimum time to wait between self derivation attempts, even it the user is 42 // requesting accounts like crazy. 43 const selfDeriveThrottling = time.Second 44 45 // driver defines the vendor specific functionality hardware wallets instances 46 // must implement to allow using them with the wallet lifecycle management. 47 type driver interface { 48 // Status returns a textual status to aid the user in the current state of the 49 // wallet. It also returns an error indicating any failure the wallet might have 50 // encountered. 51 Status() (string, error) 52 53 // Open initializes access to a wallet instance. The passphrase parameter may 54 // or may not be used by the implementation of a particular wallet instance. 55 Open(device io.ReadWriter, passphrase string) error 56 57 // Close releases any resources held by an open wallet instance. 58 Close() error 59 60 // Heartbeat performs a sanity check against the hardware wallet to see if it 61 // is still online and healthy. 62 Heartbeat() error 63 64 // Derive sends a derivation request to the USB device and returns the Ethereum 65 // address located on that path. 66 Derive(path accounts.DerivationPath) (common.Address, error) 67 68 // SignTx sends the transaction to the USB device and waits for the user to confirm 69 // or deny the transaction. 70 SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) 71 72 SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, 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 usb.DeviceInfo // Known USB device infos about the wallet 84 device usb.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 deriveNextPaths []accounts.DerivationPath // Next derivation paths for account auto-discovery (multiple bases supported) 90 deriveNextAddrs []common.Address // Next derived account addresses for auto-discovery (multiple bases supported) 91 deriveChain ethereum.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 exclusively 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 return w.driver.Close() 281 } 282 283 // Accounts implements accounts.Wallet, returning the list of accounts pinned to 284 // the USB hardware wallet. If self-derivation was enabled, the account list is 285 // periodically expanded based on current chain state. 286 func (w *wallet) Accounts() []accounts.Account { 287 // Attempt self-derivation if it's running 288 reqc := make(chan struct{}, 1) 289 select { 290 case w.deriveReq <- reqc: 291 // Self-derivation request accepted, wait for it 292 <-reqc 293 default: 294 // Self-derivation offline, throttled or busy, skip 295 } 296 // Return whatever account list we ended up with 297 w.stateLock.RLock() 298 defer w.stateLock.RUnlock() 299 300 cpy := make([]accounts.Account, len(w.accounts)) 301 copy(cpy, w.accounts) 302 return cpy 303 } 304 305 // selfDerive is an account derivation loop that upon request attempts to find 306 // new non-zero accounts. 307 func (w *wallet) selfDerive() { 308 w.log.Debug("USB wallet self-derivation started") 309 defer w.log.Debug("USB wallet self-derivation stopped") 310 311 // Execute self-derivations until termination or error 312 var ( 313 reqc chan struct{} 314 errc chan error 315 err error 316 ) 317 for errc == nil && err == nil { 318 // Wait until either derivation or termination is requested 319 select { 320 case errc = <-w.deriveQuit: 321 // Termination requested 322 continue 323 case reqc = <-w.deriveReq: 324 // Account discovery requested 325 } 326 // Derivation needs a chain and device access, skip if either unavailable 327 w.stateLock.RLock() 328 if w.device == nil || w.deriveChain == nil { 329 w.stateLock.RUnlock() 330 reqc <- struct{}{} 331 continue 332 } 333 select { 334 case <-w.commsLock: 335 default: 336 w.stateLock.RUnlock() 337 reqc <- struct{}{} 338 continue 339 } 340 // Device lock obtained, derive the next batch of accounts 341 var ( 342 accs []accounts.Account 343 paths []accounts.DerivationPath 344 345 nextPaths = append([]accounts.DerivationPath{}, w.deriveNextPaths...) 346 nextAddrs = append([]common.Address{}, w.deriveNextAddrs...) 347 348 context = context.Background() 349 ) 350 for i := 0; i < len(nextAddrs); i++ { 351 for empty := false; !empty; { 352 // Retrieve the next derived Ethereum account 353 if nextAddrs[i] == (common.Address{}) { 354 if nextAddrs[i], err = w.driver.Derive(nextPaths[i]); err != nil { 355 w.log.Warn("USB wallet account derivation failed", "err", err) 356 break 357 } 358 } 359 // Check the account's status against the current chain state 360 var ( 361 balance *big.Int 362 nonce uint64 363 ) 364 balance, err = w.deriveChain.BalanceAt(context, nextAddrs[i], nil) 365 if err != nil { 366 w.log.Warn("USB wallet balance retrieval failed", "err", err) 367 break 368 } 369 nonce, err = w.deriveChain.NonceAt(context, nextAddrs[i], nil) 370 if err != nil { 371 w.log.Warn("USB wallet nonce retrieval failed", "err", err) 372 break 373 } 374 // We've just self-derived a new account, start tracking it locally 375 // unless the account was empty. 376 path := make(accounts.DerivationPath, len(nextPaths[i])) 377 copy(path[:], nextPaths[i][:]) 378 if balance.Sign() == 0 && nonce == 0 { 379 empty = true 380 // If it indeed was empty, make a log output for it anyway. In the case 381 // of legacy-ledger, the first account on the legacy-path will 382 // be shown to the user, even if we don't actively track it 383 if i < len(nextAddrs)-1 { 384 w.log.Info("Skipping trakcking first account on legacy path, use personal.deriveAccount(<url>,<path>, false) to track", 385 "path", path, "address", nextAddrs[i]) 386 break 387 } 388 } 389 paths = append(paths, path) 390 account := accounts.Account{ 391 Address: nextAddrs[i], 392 URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, 393 } 394 accs = append(accs, account) 395 396 // Display a log message to the user for new (or previously empty accounts) 397 if _, known := w.paths[nextAddrs[i]]; !known || (!empty && nextAddrs[i] == w.deriveNextAddrs[i]) { 398 w.log.Info("USB wallet discovered new account", "address", nextAddrs[i], "path", path, "balance", balance, "nonce", nonce) 399 } 400 // Fetch the next potential account 401 if !empty { 402 nextAddrs[i] = common.Address{} 403 nextPaths[i][len(nextPaths[i])-1]++ 404 } 405 } 406 } 407 // Self derivation complete, release device lock 408 w.commsLock <- struct{}{} 409 w.stateLock.RUnlock() 410 411 // Insert any accounts successfully derived 412 w.stateLock.Lock() 413 for i := 0; i < len(accs); i++ { 414 if _, ok := w.paths[accs[i].Address]; !ok { 415 w.accounts = append(w.accounts, accs[i]) 416 w.paths[accs[i].Address] = paths[i] 417 } 418 } 419 // Shift the self-derivation forward 420 // TODO(karalabe): don't overwrite changes from wallet.SelfDerive 421 w.deriveNextAddrs = nextAddrs 422 w.deriveNextPaths = nextPaths 423 w.stateLock.Unlock() 424 425 // Notify the user of termination and loop after a bit of time (to avoid trashing) 426 reqc <- struct{}{} 427 if err == nil { 428 select { 429 case errc = <-w.deriveQuit: 430 // Termination requested, abort 431 case <-time.After(selfDeriveThrottling): 432 // Waited enough, willing to self-derive again 433 } 434 } 435 } 436 // In case of error, wait for termination 437 if err != nil { 438 w.log.Debug("USB wallet self-derivation failed", "err", err) 439 errc = <-w.deriveQuit 440 } 441 errc <- err 442 } 443 444 // Contains implements accounts.Wallet, returning whether a particular account is 445 // or is not pinned into this wallet instance. Although we could attempt to resolve 446 // unpinned accounts, that would be an non-negligible hardware operation. 447 func (w *wallet) Contains(account accounts.Account) bool { 448 w.stateLock.RLock() 449 defer w.stateLock.RUnlock() 450 451 _, exists := w.paths[account.Address] 452 return exists 453 } 454 455 // Derive implements accounts.Wallet, deriving a new account at the specific 456 // derivation path. If pin is set to true, the account will be added to the list 457 // of tracked accounts. 458 func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { 459 // Try to derive the actual account and update its URL if successful 460 w.stateLock.RLock() // Avoid device disappearing during derivation 461 462 if w.device == nil { 463 w.stateLock.RUnlock() 464 return accounts.Account{}, accounts.ErrWalletClosed 465 } 466 <-w.commsLock // Avoid concurrent hardware access 467 address, err := w.driver.Derive(path) 468 w.commsLock <- struct{}{} 469 470 w.stateLock.RUnlock() 471 472 // If an error occurred or no pinning was requested, return 473 if err != nil { 474 return accounts.Account{}, err 475 } 476 account := accounts.Account{ 477 Address: address, 478 URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)}, 479 } 480 if !pin { 481 return account, nil 482 } 483 // Pinning needs to modify the state 484 w.stateLock.Lock() 485 defer w.stateLock.Unlock() 486 487 if _, ok := w.paths[address]; !ok { 488 w.accounts = append(w.accounts, account) 489 w.paths[address] = make(accounts.DerivationPath, len(path)) 490 copy(w.paths[address], path) 491 } 492 return account, nil 493 } 494 495 // SelfDerive sets a base account derivation path from which the wallet attempts 496 // to discover non zero accounts and automatically add them to list of tracked 497 // accounts. 498 // 499 // Note, self derivation will increment the last component of the specified path 500 // opposed to decending into a child path to allow discovering accounts starting 501 // from non zero components. 502 // 503 // Some hardware wallets switched derivation paths through their evolution, so 504 // this method supports providing multiple bases to discover old user accounts 505 // too. Only the last base will be used to derive the next empty account. 506 // 507 // You can disable automatic account discovery by calling SelfDerive with a nil 508 // chain state reader. 509 func (w *wallet) SelfDerive(bases []accounts.DerivationPath, chain ethereum.ChainStateReader) { 510 w.stateLock.Lock() 511 defer w.stateLock.Unlock() 512 513 w.deriveNextPaths = make([]accounts.DerivationPath, len(bases)) 514 for i, base := range bases { 515 w.deriveNextPaths[i] = make(accounts.DerivationPath, len(base)) 516 copy(w.deriveNextPaths[i][:], base[:]) 517 } 518 w.deriveNextAddrs = make([]common.Address, len(bases)) 519 w.deriveChain = chain 520 } 521 522 // signHash implements accounts.Wallet, however signing arbitrary data is not 523 // supported for hardware wallets, so this method will always return an error. 524 func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error) { 525 return nil, accounts.ErrNotSupported 526 } 527 528 // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed 529 func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { 530 531 // Unless we are doing 712 signing, simply dispatch to signHash 532 if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) { 533 return w.signHash(account, crypto.Keccak256(data)) 534 } 535 536 // dispatch to 712 signing if the mimetype is TypedData and the format matches 537 w.stateLock.RLock() // Comms have own mutex, this is for the state fields 538 defer w.stateLock.RUnlock() 539 540 // If the wallet is closed, abort 541 if w.device == nil { 542 return nil, accounts.ErrWalletClosed 543 } 544 // Make sure the requested account is contained within 545 path, ok := w.paths[account.Address] 546 if !ok { 547 return nil, accounts.ErrUnknownAccount 548 } 549 // All infos gathered and metadata checks out, request signing 550 <-w.commsLock 551 defer func() { w.commsLock <- struct{}{} }() 552 553 // Ensure the device isn't screwed with while user confirmation is pending 554 // TODO(karalabe): remove if hotplug lands on Windows 555 w.hub.commsLock.Lock() 556 w.hub.commsPend++ 557 w.hub.commsLock.Unlock() 558 559 defer func() { 560 w.hub.commsLock.Lock() 561 w.hub.commsPend-- 562 w.hub.commsLock.Unlock() 563 }() 564 // Sign the transaction 565 signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66]) 566 if err != nil { 567 return nil, err 568 } 569 return signature, nil 570 } 571 572 // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given 573 // data with the given account using passphrase as extra authentication. 574 // Since USB wallets don't rely on passphrases, these are silently ignored. 575 func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mimeType string, data []byte) ([]byte, error) { 576 return w.SignData(account, mimeType, data) 577 } 578 579 func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { 580 return w.signHash(account, accounts.TextHash(text)) 581 } 582 583 // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger 584 // wallet to request a confirmation from the user. It returns either the signed 585 // transaction or a failure if the user denied the transaction. 586 // 587 // Note, if the version of the Ethereum application running on the Ledger wallet is 588 // too old to sign EIP-155 transactions, but such is requested nonetheless, an error 589 // will be returned opposed to silently signing in Homestead mode. 590 func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 591 w.stateLock.RLock() // Comms have own mutex, this is for the state fields 592 defer w.stateLock.RUnlock() 593 594 // If the wallet is closed, abort 595 if w.device == nil { 596 return nil, accounts.ErrWalletClosed 597 } 598 // Make sure the requested account is contained within 599 path, ok := w.paths[account.Address] 600 if !ok { 601 return nil, accounts.ErrUnknownAccount 602 } 603 // All infos gathered and metadata checks out, request signing 604 <-w.commsLock 605 defer func() { w.commsLock <- struct{}{} }() 606 607 // Ensure the device isn't screwed with while user confirmation is pending 608 // TODO(karalabe): remove if hotplug lands on Windows 609 w.hub.commsLock.Lock() 610 w.hub.commsPend++ 611 w.hub.commsLock.Unlock() 612 613 defer func() { 614 w.hub.commsLock.Lock() 615 w.hub.commsPend-- 616 w.hub.commsLock.Unlock() 617 }() 618 // Sign the transaction and verify the sender to avoid hardware fault surprises 619 sender, signed, err := w.driver.SignTx(path, tx, chainID) 620 if err != nil { 621 return nil, err 622 } 623 if sender != account.Address { 624 return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex()) 625 } 626 return signed, nil 627 } 628 629 // SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary 630 // data is not supported for Ledger wallets, so this method will always return 631 // an error. 632 func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { 633 return w.SignText(account, accounts.TextHash(text)) 634 } 635 636 // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given 637 // transaction with the given account using passphrase as extra authentication. 638 // Since USB wallets don't rely on passphrases, these are silently ignored. 639 func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { 640 return w.SignTx(account, tx, chainID) 641 }