github.com/decred/dcrlnd@v0.7.6/walletunlocker/service.go (about) 1 package walletunlocker 2 3 import ( 4 "context" 5 "crypto/rand" 6 "errors" 7 "fmt" 8 "os" 9 "sync/atomic" 10 "time" 11 12 "github.com/decred/dcrd/chaincfg/v3" 13 "github.com/decred/dcrd/hdkeychain/v3" 14 "github.com/decred/dcrlnd/aezeed" 15 "github.com/decred/dcrlnd/chanbackup" 16 "github.com/decred/dcrlnd/channeldb" 17 "github.com/decred/dcrlnd/keychain" 18 "github.com/decred/dcrlnd/kvdb" 19 "github.com/decred/dcrlnd/lnrpc" 20 "github.com/decred/dcrlnd/lnwallet" 21 "github.com/decred/dcrlnd/macaroons" 22 23 "decred.org/dcrwallet/v4/wallet" 24 "github.com/decred/dcrlnd/lnwallet/dcrwallet" 25 walletloader "github.com/decred/dcrlnd/lnwallet/dcrwallet/loader" 26 "google.golang.org/grpc" 27 ) 28 29 var ( 30 // ErrUnlockTimeout signals that we did not get the expected unlock 31 // message before the timeout occurred. 32 ErrUnlockTimeout = errors.New("got no unlock message before timeout") 33 ) 34 35 // WalletUnlockParams holds the variables used to parameterize the unlocking of 36 // lnd's wallet after it has already been created. 37 type WalletUnlockParams struct { 38 // Password is the public and private wallet passphrase. 39 Password []byte 40 41 // Birthday specifies the approximate time that this wallet was created. 42 // This is used to bound any rescans on startup. 43 Birthday time.Time 44 45 // RecoveryWindow specifies the address lookahead when entering recovery 46 // mode. A recovery will be attempted if this value is non-zero. 47 RecoveryWindow uint32 48 49 // Wallet is the loaded and unlocked Wallet. This is returned 50 // from the unlocker service to avoid it being unlocked twice (once in 51 // the unlocker service to check if the password is correct and again 52 // later when lnd actually uses it). Because unlocking involves scrypt 53 // which is resource intensive, we want to avoid doing it twice. 54 Wallet *wallet.Wallet 55 56 // Loader is the wallet loader used to create or open the corresponding 57 // wallet. 58 Loader *walletloader.Loader 59 60 // Conn is the connection to the remote wallet when that is used 61 // instead of an embedded dcrwallet instance. 62 Conn *grpc.ClientConn 63 64 // ChansToRestore a set of static channel backups that should be 65 // restored before the main server instance starts up. 66 ChansToRestore ChannelsToRecover 67 68 // UnloadWallet is a function for unloading the wallet, which should 69 // be called on shutdown. 70 UnloadWallet func() error 71 72 // StatelessInit signals that the user requested the daemon to be 73 // initialized stateless, which means no unencrypted macaroons should be 74 // written to disk. 75 StatelessInit bool 76 77 // MacResponseChan is the channel for sending back the admin macaroon to 78 // the WalletUnlocker service. 79 MacResponseChan chan []byte 80 } 81 82 // ChannelsToRecover wraps any set of packed (serialized+encrypted) channel 83 // back ups together. These can be passed in when unlocking the wallet, or 84 // creating a new wallet for the first time with an existing seed. 85 type ChannelsToRecover struct { 86 // PackedMultiChanBackup is an encrypted and serialized multi-channel 87 // backup. 88 PackedMultiChanBackup chanbackup.PackedMulti 89 90 // PackedSingleChanBackups is a series of encrypted and serialized 91 // single-channel backup for one or more channels. 92 PackedSingleChanBackups chanbackup.PackedSingles 93 } 94 95 // WalletInitMsg is a message sent by the UnlockerService when a user wishes to 96 // set up the internal wallet for the first time. The user MUST provide a 97 // passphrase, but is also able to provide their own source of entropy. If 98 // provided, then this source of entropy will be used to generate the wallet's 99 // HD seed. Otherwise, the wallet will generate one itself. 100 type WalletInitMsg struct { 101 // Passphrase is the passphrase that will be used to encrypt the wallet 102 // itself. This MUST be at least 8 characters. 103 Passphrase []byte 104 105 // WalletSeed is the deciphered cipher seed that the wallet should use 106 // to initialize itself. The seed might be nil if the wallet should be 107 // created from an extended master root key instead. 108 WalletSeed *aezeed.CipherSeed 109 110 // WalletExtendedKey is the wallet's extended master root key that 111 // should be used instead of the seed, if non-nil. The extended key is 112 // mutually exclusive to the wallet seed, but one of both is always set. 113 WalletExtendedKey *hdkeychain.ExtendedKey 114 115 // ExtendedKeyBirthday is the birthday of a wallet that's being restored 116 // through an extended key instead of an aezeed. 117 ExtendedKeyBirthday time.Time 118 119 // WatchOnlyBirthday is the birthday of the master root key the above 120 // watch-only account xpubs were derived from. 121 WatchOnlyBirthday time.Time 122 123 // WatchOnlyMasterFingerprint is the fingerprint of the master root key 124 // the above watch-only account xpubs were derived from. 125 WatchOnlyMasterFingerprint uint32 126 127 // RecoveryWindow is the address look-ahead used when restoring a seed 128 // with existing funds. A recovery window zero indicates that no 129 // recovery should be attempted, such as after the wallet's initial 130 // creation. 131 RecoveryWindow uint32 132 133 // ChanBackups a set of static channel backups that should be received 134 // after the wallet has been initialized. 135 ChanBackups ChannelsToRecover 136 137 // StatelessInit signals that the user requested the daemon to be 138 // initialized stateless, which means no unencrypted macaroons should be 139 // written to disk. 140 StatelessInit bool 141 } 142 143 // WalletUnlockMsg is a message sent by the UnlockerService when a user wishes 144 // to unlock the internal wallet after initial setup. The user can optionally 145 // specify a recovery window, which will resume an interrupted rescan for used 146 // addresses. 147 type WalletUnlockMsg struct { 148 // Passphrase is the passphrase that will be used to encrypt the wallet 149 // itself. This MUST be at least 8 characters. 150 Passphrase []byte 151 152 // RecoveryWindow is the address look-ahead used when restoring a seed 153 // with existing funds. A recovery window zero indicates that no 154 // recovery should be attempted, such as after the wallet's initial 155 // creation, but before any addresses have been created. 156 RecoveryWindow uint32 157 158 // Wallet is the loaded and unlocked Wallet. This is returned through 159 // the channel to avoid it being unlocked twice (once to check if the 160 // password is correct, here in the WalletUnlocker and again later when 161 // lnd actually uses it). Because unlocking involves scrypt which is 162 // resource intensive, we want to avoid doing it twice. 163 Wallet *wallet.Wallet 164 165 Loader *walletloader.Loader 166 167 // Conn is the connection to a remote wallet when the daemon has been 168 // configured to connect to a wallet instead of using the embedded one. 169 Conn *grpc.ClientConn 170 171 // ChanBackups a set of static channel backups that should be received 172 // after the wallet has been unlocked. 173 ChanBackups ChannelsToRecover 174 175 // UnloadWallet is a function for unloading the wallet, which should 176 // be called on shutdown. 177 UnloadWallet func() error 178 179 // StatelessInit signals that the user requested the daemon to be 180 // initialized stateless, which means no unencrypted macaroons should be 181 // written to disk. 182 StatelessInit bool 183 } 184 185 // UnlockerService implements the WalletUnlocker service used to provide lnd 186 // with a password for wallet encryption at startup. Additionally, during 187 // initial setup, users can provide their own source of entropy which will be 188 // used to generate the seed that's ultimately used within the wallet. 189 type UnlockerService struct { 190 // InitMsgs is a channel that carries all wallet init messages. 191 InitMsgs chan *WalletInitMsg 192 193 // UnlockMsgs is a channel where unlock parameters provided by the rpc 194 // client to be used to unlock and decrypt an existing wallet will be 195 // sent. 196 UnlockMsgs chan *WalletUnlockMsg 197 198 // MacResponseChan is the channel for sending back the admin macaroon to 199 // the WalletUnlocker service. 200 MacResponseChan chan []byte 201 202 chainDir string 203 netParams *chaincfg.Params 204 205 // db is the db used for checking remote wallet unlocks. 206 db atomic.Pointer[channeldb.DB] 207 208 dcrwHost string 209 dcrwCert string 210 dcrwClientKey string 211 dcrwClientCert string 212 dcrwAccount int32 213 214 // macaroonFiles is the path to the three generated macaroons with 215 // different access permissions. These might not exist in a stateless 216 // initialization of lnd. 217 macaroonFiles []string 218 219 // resetWalletTransactions indicates that the wallet state should be 220 // reset on unlock to force a full chain rescan. 221 resetWalletTransactions bool 222 223 // LoaderOpts holds the functional options for the wallet loader. 224 loaderOpts []walletloader.LoaderOption 225 226 // macaroonDB is an instance of a database backend that stores all 227 // macaroon root keys. This will be nil on initialization and must be 228 // set using the SetMacaroonDB method as soon as it's available. 229 macaroonDB kvdb.Backend 230 } 231 232 // New creates and returns a new UnlockerService. 233 func New(chainDir string, params *chaincfg.Params, 234 macaroonFiles []string, dbTimeout time.Duration, 235 dcrwHost, dcrwCert, dcrwClientKey, 236 dcrwClientCert string, dcrwAccount int32) *UnlockerService { 237 238 return &UnlockerService{ 239 InitMsgs: make(chan *WalletInitMsg, 1), 240 UnlockMsgs: make(chan *WalletUnlockMsg, 1), 241 dcrwHost: dcrwHost, 242 dcrwCert: dcrwCert, 243 dcrwClientKey: dcrwClientKey, 244 dcrwClientCert: dcrwClientCert, 245 dcrwAccount: dcrwAccount, 246 247 // Make sure we buffer the channel is buffered so the main lnd 248 // goroutine isn't blocking on writing to it. 249 MacResponseChan: make(chan []byte, 1), 250 chainDir: chainDir, 251 netParams: params, 252 macaroonFiles: macaroonFiles, 253 resetWalletTransactions: false, 254 } 255 } 256 257 // SetDB sets the DB used for checking that unlocking remote wallets works. This 258 // must be called before the service is 259 func (u *UnlockerService) SetDB(db *channeldb.DB) { 260 u.db.Store(db) 261 } 262 263 // SetLoaderOpts can be used to inject wallet loader options after the unlocker 264 // service has been hooked to the main RPC server. 265 func (u *UnlockerService) SetLoaderOpts(loaderOpts []walletloader.LoaderOption) { 266 u.loaderOpts = loaderOpts 267 } 268 269 // SetMacaroonDB can be used to inject the macaroon database after the unlocker 270 // service has been hooked to the main RPC server. 271 func (u *UnlockerService) SetMacaroonDB(macaroonDB kvdb.Backend) { 272 u.macaroonDB = macaroonDB 273 } 274 275 func (u *UnlockerService) newLoader(recoveryWindow uint32) (*walletloader.Loader, 276 error) { 277 278 netDir := dcrwallet.NetworkDir(u.chainDir, u.netParams) 279 return walletloader.NewLoader( 280 u.netParams, netDir, recoveryWindow, 281 ), nil 282 } 283 284 // WalletExists returns whether a wallet exists on the file path the 285 // UnlockerService is using. 286 func (u *UnlockerService) WalletExists() (bool, error) { 287 loader, err := u.newLoader(wallet.DefaultGapLimit) 288 if err != nil { 289 return false, err 290 } 291 return loader.WalletExists() 292 } 293 294 // GenSeed is the first method that should be used to instantiate a new lnd 295 // instance. This method allows a caller to generate a new aezeed cipher seed 296 // given an optional passphrase. If provided, the passphrase will be necessary 297 // to decrypt the cipherseed to expose the internal wallet seed. 298 // 299 // Once the cipherseed is obtained and verified by the user, the InitWallet 300 // method should be used to commit the newly generated seed, and create the 301 // wallet. 302 func (u *UnlockerService) GenSeed(_ context.Context, 303 in *lnrpc.GenSeedRequest) (*lnrpc.GenSeedResponse, error) { 304 305 // Before we start, we'll ensure that the wallet hasn't already created 306 // so we don't show a *new* seed to the user if one already exists. 307 loader, err := u.newLoader(wallet.DefaultGapLimit) 308 if err != nil { 309 return nil, err 310 } 311 312 walletExists, err := loader.WalletExists() 313 if err != nil { 314 return nil, err 315 } 316 if walletExists { 317 return nil, fmt.Errorf("wallet already exists") 318 } 319 320 var entropy [aezeed.EntropySize]byte 321 322 switch { 323 // If the user provided any entropy, then we'll make sure it's sized 324 // properly. 325 case len(in.SeedEntropy) != 0 && len(in.SeedEntropy) != aezeed.EntropySize: 326 return nil, fmt.Errorf("incorrect entropy length: expected "+ 327 "16 bytes, instead got %v bytes", len(in.SeedEntropy)) 328 329 // If the user provided the correct number of bytes, then we'll copy it 330 // over into our buffer for usage. 331 case len(in.SeedEntropy) == aezeed.EntropySize: 332 copy(entropy[:], in.SeedEntropy) 333 334 // Otherwise, we'll generate a fresh new set of bytes to use as entropy 335 // to generate the seed. 336 default: 337 if _, err := rand.Read(entropy[:]); err != nil { 338 return nil, err 339 } 340 } 341 342 // Subtract 24h from the birthday, to ensure no reorgs could possibly 343 // make us miss some transactions. 344 birthday := time.Now().Add(-time.Hour * 24) 345 346 // Now that we have our set of entropy, we'll create a new cipher seed 347 // instance. 348 // 349 cipherSeed, err := aezeed.New( 350 keychain.KeyDerivationVersion, &entropy, birthday, 351 ) 352 if err != nil { 353 return nil, err 354 } 355 356 // With our raw cipher seed obtained, we'll convert it into an encoded 357 // mnemonic using the user specified pass phrase. 358 mnemonic, err := cipherSeed.ToMnemonic(in.AezeedPassphrase) 359 if err != nil { 360 return nil, err 361 } 362 363 // Additionally, we'll also obtain the raw enciphered cipher seed as 364 // well to return to the user. 365 encipheredSeed, err := cipherSeed.Encipher(in.AezeedPassphrase) 366 if err != nil { 367 return nil, err 368 } 369 370 return &lnrpc.GenSeedResponse{ 371 CipherSeedMnemonic: mnemonic[:], 372 EncipheredSeed: encipheredSeed[:], 373 }, nil 374 } 375 376 // extractChanBackups is a helper function that extracts the set of channel 377 // backups from the proto into a format that we'll pass to higher level 378 // sub-systems. 379 func extractChanBackups(chanBackups *lnrpc.ChanBackupSnapshot) *ChannelsToRecover { 380 // If there aren't any populated channel backups, then we can exit 381 // early as there's nothing to extract. 382 if chanBackups == nil || (chanBackups.SingleChanBackups == nil && 383 chanBackups.MultiChanBackup == nil) { 384 return nil 385 } 386 387 // Now that we know there's at least a single back up populated, we'll 388 // extract the multi-chan backup (if it's there). 389 var backups ChannelsToRecover 390 if chanBackups.MultiChanBackup != nil { 391 multiBackup := chanBackups.MultiChanBackup 392 backups.PackedMultiChanBackup = multiBackup.MultiChanBackup 393 } 394 395 if chanBackups.SingleChanBackups == nil { 396 return &backups 397 } 398 399 // Finally, we can extract all the single chan backups as well. 400 for _, backup := range chanBackups.SingleChanBackups.ChanBackups { 401 singleChanBackup := backup.ChanBackup 402 403 backups.PackedSingleChanBackups = append( 404 backups.PackedSingleChanBackups, singleChanBackup, 405 ) 406 } 407 408 return &backups 409 } 410 411 // InitWallet is used when lnd is starting up for the first time to fully 412 // initialize the daemon and its internal wallet. At the very least a wallet 413 // password must be provided. This will be used to encrypt sensitive material 414 // on disk. 415 // 416 // In the case of a recovery scenario, the user can also specify their aezeed 417 // mnemonic and passphrase. If set, then the daemon will use this prior state 418 // to initialize its internal wallet. 419 // 420 // Alternatively, this can be used along with the GenSeed RPC to obtain a 421 // seed, then present it to the user. Once it has been verified by the user, 422 // the seed can be fed into this RPC in order to commit the new wallet. 423 func (u *UnlockerService) InitWallet(ctx context.Context, 424 in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) { 425 426 // Make sure the password meets our constraints. 427 password := in.WalletPassword 428 if err := ValidatePassword(password); err != nil { 429 return nil, err 430 } 431 432 // Require that the recovery window be non-negative. 433 recoveryWindow := in.RecoveryWindow 434 if recoveryWindow < 0 { 435 return nil, fmt.Errorf("recovery window %d must be "+ 436 "non-negative", recoveryWindow) 437 } 438 if recoveryWindow == 0 { 439 recoveryWindow = int32(wallet.DefaultGapLimit) 440 } 441 442 // We'll then open up the directory that will be used to store the 443 // wallet's files so we can check if the wallet already exists. 444 loader, err := u.newLoader(uint32(recoveryWindow)) 445 if err != nil { 446 return nil, err 447 } 448 449 walletExists, err := loader.WalletExists() 450 if err != nil { 451 return nil, err 452 } 453 454 // If the wallet already exists, then we'll exit early as we can't 455 // create the wallet if it already exists! 456 if walletExists { 457 return nil, fmt.Errorf("wallet already exists") 458 } 459 460 // At this point, we know the wallet doesn't already exist so we can 461 // prepare the message that we'll send over the channel later. 462 initMsg := &WalletInitMsg{ 463 Passphrase: password, 464 RecoveryWindow: uint32(recoveryWindow), 465 StatelessInit: in.StatelessInit, 466 } 467 468 // There are two supported ways to initialize the wallet. Either from 469 // the aezeed or the final extended master key directly. 470 switch { 471 // Don't allow the user to specify both as that would be ambiguous. 472 case len(in.CipherSeedMnemonic) > 0 && len(in.ExtendedMasterKey) > 0: 473 return nil, fmt.Errorf("cannot specify both the cipher " + 474 "seed mnemonic and the extended master key") 475 476 // The aezeed is the preferred and default way of initializing a wallet. 477 case len(in.CipherSeedMnemonic) > 0: 478 // We'll map the user provided aezeed and passphrase into a 479 // decoded cipher seed instance. 480 var mnemonic aezeed.Mnemonic 481 copy(mnemonic[:], in.CipherSeedMnemonic) 482 483 // If we're unable to map it back into the ciphertext, then 484 // either the mnemonic is wrong, or the passphrase is wrong. 485 cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase) 486 if err != nil { 487 return nil, err 488 } 489 490 initMsg.WalletSeed = cipherSeed 491 492 // To support restoring a wallet where the seed isn't known or a wallet 493 // created externally to lnd, we also allow the extended master key 494 // (xprv) to be imported directly. This is what'll be stored in the 495 // dcrwallet database anyway. 496 case len(in.ExtendedMasterKey) > 0: 497 extendedKey, err := hdkeychain.NewKeyFromString( 498 in.ExtendedMasterKey, u.netParams, 499 ) 500 if err != nil { 501 return nil, err 502 } 503 504 // The on-chain wallet of lnd is going to derive keys based on 505 // the BIP49/84 key derivation paths from this root key. To make 506 // sure we use default derivation paths, we want to avoid 507 // deriving keys from something other than the master key (at 508 // depth 0, denoted with "m/" in BIP32 notation). 509 if extendedKey.Depth() != 0 { 510 return nil, fmt.Errorf("extended master key must " + 511 "be at depth 0 not a child key") 512 } 513 514 // Because we need the master key (at depth 0), it must be an 515 // extended private key as the first levels of BIP49/84 516 // derivation paths are hardened, which isn't possible with 517 // extended public keys. 518 if !extendedKey.IsPrivate() { 519 return nil, fmt.Errorf("extended master key must " + 520 "contain private keys") 521 } 522 523 // When importing a wallet from its extended private key we 524 // don't know the birthday as that information is not encoded in 525 // that format. We therefore must set an arbitrary date to start 526 // rescanning at if the user doesn't provide an explicit value 527 // for it. Since lnd only uses SegWit addresses, we pick the 528 // date of the first block that contained SegWit transactions 529 // (481824). 530 initMsg.ExtendedKeyBirthday = time.Date( 531 2017, time.August, 24, 1, 57, 37, 0, time.UTC, 532 ) 533 if in.ExtendedMasterKeyBirthdayTimestamp != 0 { 534 initMsg.ExtendedKeyBirthday = time.Unix( 535 int64(in.ExtendedMasterKeyBirthdayTimestamp), 0, 536 ) 537 } 538 539 initMsg.WalletExtendedKey = extendedKey 540 541 // The third option for creating a wallet is the watch-only mode: 542 // Instead of providing the master root key directly, each individual 543 // account is passed as an extended public key only. Because of the 544 // hardened derivation path up to the account (depth 3), it is not 545 // possible to create a master root extended _public_ key. Therefore, an 546 // xpub must be derived and passed into the unlocker for _every_ account 547 // lnd expects. 548 case in.WatchOnly != nil && len(in.WatchOnly.Accounts) > 0: 549 return nil, fmt.Errorf("watch-only wallets are not supported in dcrlnd") 550 551 // No key material was set, no wallet can be created. 552 default: 553 return nil, fmt.Errorf("must either specify cipher seed " + 554 "mnemonic or the extended master key") 555 } 556 557 // Before we return the unlock payload, we'll check if we can extract 558 // any channel backups to pass up to the higher level sub-system. 559 chansToRestore := extractChanBackups(in.ChannelBackups) 560 if chansToRestore != nil { 561 initMsg.ChanBackups = *chansToRestore 562 } 563 564 // Deliver the initialization message back to the main daemon. 565 select { 566 case u.InitMsgs <- initMsg: 567 // We need to read from the channel to let the daemon continue 568 // its work and to get the admin macaroon. Once the response 569 // arrives, we directly forward it to the client. 570 select { 571 case adminMac := <-u.MacResponseChan: 572 return &lnrpc.InitWalletResponse{ 573 AdminMacaroon: adminMac, 574 }, nil 575 576 case <-ctx.Done(): 577 return nil, ErrUnlockTimeout 578 } 579 580 case <-ctx.Done(): 581 return nil, ErrUnlockTimeout 582 } 583 } 584 585 // LoadAndUnlock creates a loader for the wallet and tries to unlock the wallet 586 // with the given password and recovery window. If the drop wallet transactions 587 // flag is set, the history state drop is performed before unlocking the wallet 588 // yet again. 589 func (u *UnlockerService) LoadAndUnlock(ctx context.Context, password []byte, 590 recoveryWindow uint32) (*wallet.Wallet, func() error, error) { 591 592 loader, err := u.newLoader(recoveryWindow) 593 if err != nil { 594 return nil, nil, err 595 } 596 597 // Check if wallet already exists. 598 walletExists, err := loader.WalletExists() 599 if err != nil { 600 return nil, nil, err 601 } 602 603 if !walletExists { 604 // Cannot unlock a wallet that does not exist! 605 return nil, nil, fmt.Errorf("wallet not found") 606 } 607 608 // Try opening the existing wallet with the provided password. 609 unlockedWallet, err := loader.OpenExistingWallet(ctx, password) 610 if err != nil { 611 // Could not open wallet, most likely this means that provided 612 // password was incorrect. 613 return nil, nil, err 614 } 615 616 // The user requested to drop their whole wallet transaction state to 617 // force a full chain rescan for wallet addresses. Dropping the state 618 // only properly takes effect after opening the wallet. That's why we 619 // start, drop, stop and start again. 620 if u.resetWalletTransactions { 621 return nil, nil, fmt.Errorf("dropping wallet txs is not supported in dcrlnd") 622 } 623 624 return unlockedWallet, loader.UnloadWallet, nil 625 } 626 627 // UnlockWallet sends the password provided by the incoming UnlockWalletRequest 628 // over the UnlockMsgs channel in case it successfully decrypts an existing 629 // wallet found in the chain's wallet database directory. 630 func (u *UnlockerService) UnlockWallet(ctx context.Context, 631 in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) { 632 633 if u.dcrwHost != "" && u.dcrwCert != "" { 634 // Using a remote wallet. 635 return u.unlockRemoteWallet(ctx, in) 636 } 637 638 password := in.WalletPassword 639 recoveryWindow := uint32(in.RecoveryWindow) 640 if recoveryWindow < wallet.DefaultGapLimit { 641 recoveryWindow = wallet.DefaultGapLimit 642 } 643 644 unlockedWallet, unloadFn, err := u.LoadAndUnlock( 645 ctx, password, recoveryWindow, 646 ) 647 if err != nil { 648 return nil, err 649 } 650 651 // We successfully opened the wallet and pass the instance back to 652 // avoid it needing to be unlocked again. 653 walletUnlockMsg := &WalletUnlockMsg{ 654 Passphrase: password, 655 RecoveryWindow: recoveryWindow, 656 Wallet: unlockedWallet, 657 UnloadWallet: unloadFn, 658 StatelessInit: in.StatelessInit, 659 } 660 661 // Before we return the unlock payload, we'll check if we can extract 662 // any channel backups to pass up to the higher level sub-system. 663 chansToRestore := extractChanBackups(in.ChannelBackups) 664 if chansToRestore != nil { 665 walletUnlockMsg.ChanBackups = *chansToRestore 666 } 667 668 // At this point we were able to open the existing wallet with the 669 // provided password. We send the password over the UnlockMsgs 670 // channel, such that it can be used by lnd to open the wallet. 671 select { 672 case u.UnlockMsgs <- walletUnlockMsg: 673 // We need to read from the channel to let the daemon continue 674 // its work. But we don't need the returned macaroon for this 675 // operation, so we read it but then discard it. 676 select { 677 case <-u.MacResponseChan: 678 return &lnrpc.UnlockWalletResponse{}, nil 679 680 case <-ctx.Done(): 681 return nil, ErrUnlockTimeout 682 } 683 684 case <-ctx.Done(): 685 return nil, ErrUnlockTimeout 686 } 687 } 688 689 // ChangePassword changes the password of the wallet and sends the new password 690 // across the UnlockPasswords channel to automatically unlock the wallet if 691 // successful. 692 func (u *UnlockerService) ChangePassword(ctx context.Context, 693 in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) { 694 695 loader, err := u.newLoader(wallet.DefaultGapLimit) 696 if err != nil { 697 return nil, err 698 } 699 700 // First, we'll make sure the wallet exists for the specific chain and 701 // network. 702 walletExists, err := loader.WalletExists() 703 if err != nil { 704 return nil, err 705 } 706 707 if !walletExists { 708 return nil, errors.New("wallet not found") 709 } 710 711 publicPw := in.CurrentPassword 712 privatePw := in.CurrentPassword 713 714 // If the current password is blank, we'll assume the user is coming 715 // from a --noseedbackup state, so we'll use the default passwords. 716 if len(in.CurrentPassword) == 0 { 717 publicPw = lnwallet.DefaultPublicPassphrase 718 privatePw = lnwallet.DefaultPrivatePassphrase 719 } 720 721 // Make sure the new password meets our constraints. 722 if err := ValidatePassword(in.NewPassword); err != nil { 723 return nil, err 724 } 725 726 // Load the existing wallet in order to proceed with the password change. 727 w, err := loader.OpenExistingWallet(ctx, publicPw) 728 if err != nil { 729 return nil, err 730 } 731 732 // Now that we've opened the wallet, we need to close it in case of an 733 // error. But not if we succeed, then the caller must close it. 734 orderlyReturn := false 735 defer func() { 736 if !orderlyReturn { 737 _ = loader.UnloadWallet() 738 } 739 }() 740 741 // Before we actually change the password, we need to check if all flags 742 // were set correctly. The content of the previously generated macaroon 743 // files will become invalid after we generate a new root key. So we try 744 // to delete them here and they will be recreated during normal startup 745 // later. If they are missing, this is only an error if the 746 // stateless_init flag was not set. 747 if in.NewMacaroonRootKey || in.StatelessInit { 748 for _, file := range u.macaroonFiles { 749 err := os.Remove(file) 750 if err != nil && !in.StatelessInit { 751 return nil, fmt.Errorf("could not remove "+ 752 "macaroon file: %v. if the wallet "+ 753 "was initialized stateless please "+ 754 "add the --stateless_init "+ 755 "flag", err) 756 } 757 } 758 } 759 760 // Attempt to change both the public and private passphrases for the 761 // wallet. This will be done atomically in order to prevent one 762 // passphrase change from being successful and not the other. 763 // 764 // TODO(decred) This is not an atomic operation. Discuss whether we want to 765 // actually use the public pssword. 766 err = w.ChangePrivatePassphrase(ctx, privatePw, in.NewPassword) 767 if err != nil { 768 return nil, fmt.Errorf("unable to change wallet private passphrase: "+ 769 "%v", err) 770 } 771 err = w.ChangePublicPassphrase(ctx, publicPw, in.NewPassword) 772 if err != nil { 773 return nil, fmt.Errorf("unable to change wallet public passphrase: "+ 774 "%v", err) 775 } 776 777 // The next step is to load the macaroon database, change the password 778 // then close it again. 779 // Attempt to open the macaroon DB, unlock it and then change 780 // the passphrase. 781 macaroonService, err := macaroons.NewService( 782 u.macaroonDB, "lnd", in.StatelessInit, 783 ) 784 if err != nil { 785 return nil, err 786 } 787 788 err = macaroonService.CreateUnlock(&privatePw) 789 if err != nil { 790 closeErr := macaroonService.Close() 791 if closeErr != nil { 792 return nil, fmt.Errorf("could not create unlock: %v "+ 793 "--> follow-up error when closing: %v", err, 794 closeErr) 795 } 796 return nil, err 797 } 798 err = macaroonService.ChangePassword(privatePw, in.NewPassword) 799 if err != nil { 800 closeErr := macaroonService.Close() 801 if closeErr != nil { 802 return nil, fmt.Errorf("could not change password: %v "+ 803 "--> follow-up error when closing: %v", err, 804 closeErr) 805 } 806 return nil, err 807 } 808 809 // If requested by the user, attempt to replace the existing 810 // macaroon root key with a new one. 811 if in.NewMacaroonRootKey { 812 err = macaroonService.GenerateNewRootKey() 813 if err != nil { 814 closeErr := macaroonService.Close() 815 if closeErr != nil { 816 return nil, fmt.Errorf("could not generate "+ 817 "new root key: %v --> follow-up error "+ 818 "when closing: %v", err, closeErr) 819 } 820 return nil, err 821 } 822 } 823 824 err = macaroonService.Close() 825 if err != nil { 826 return nil, fmt.Errorf("could not close macaroon service: %v", 827 err) 828 } 829 830 // Finally, send the new password across the UnlockPasswords channel to 831 // automatically unlock the wallet. 832 walletUnlockMsg := &WalletUnlockMsg{ 833 Passphrase: in.NewPassword, 834 Wallet: w, 835 StatelessInit: in.StatelessInit, 836 UnloadWallet: loader.UnloadWallet, 837 } 838 select { 839 case u.UnlockMsgs <- walletUnlockMsg: 840 // We need to read from the channel to let the daemon continue 841 // its work and to get the admin macaroon. Once the response 842 // arrives, we directly forward it to the client. 843 orderlyReturn = true 844 select { 845 case adminMac := <-u.MacResponseChan: 846 return &lnrpc.ChangePasswordResponse{ 847 AdminMacaroon: adminMac, 848 }, nil 849 850 case <-ctx.Done(): 851 return nil, ErrUnlockTimeout 852 } 853 854 case <-ctx.Done(): 855 return nil, ErrUnlockTimeout 856 } 857 } 858 859 // ValidatePassword assures the password meets all of our constraints. 860 func ValidatePassword(password []byte) error { 861 // Passwords should have a length of at least 8 characters. 862 if len(password) < 8 { 863 return errors.New("password must have at least 8 characters") 864 } 865 866 return nil 867 }