• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

58.04
/walletunlocker/service.go
1
package walletunlocker
2

3
import (
4
        "context"
5
        "crypto/rand"
6
        "errors"
7
        "fmt"
8
        "os"
9
        "time"
10

11
        "github.com/btcsuite/btcd/btcutil/hdkeychain"
12
        "github.com/btcsuite/btcd/chaincfg"
13
        "github.com/btcsuite/btcwallet/waddrmgr"
14
        "github.com/btcsuite/btcwallet/wallet"
15
        "github.com/lightningnetwork/lnd/aezeed"
16
        "github.com/lightningnetwork/lnd/chanbackup"
17
        "github.com/lightningnetwork/lnd/keychain"
18
        "github.com/lightningnetwork/lnd/kvdb"
19
        "github.com/lightningnetwork/lnd/lnrpc"
20
        "github.com/lightningnetwork/lnd/lnwallet"
21
        "github.com/lightningnetwork/lnd/lnwallet/btcwallet"
22
        "github.com/lightningnetwork/lnd/macaroons"
23
)
24

25
var (
26
        // ErrUnlockTimeout signals that we did not get the expected unlock
27
        // message before the timeout occurred.
28
        ErrUnlockTimeout = errors.New("got no unlock message before timeout")
29
)
30

31
// WalletUnlockParams holds the variables used to parameterize the unlocking of
32
// lnd's wallet after it has already been created.
33
type WalletUnlockParams struct {
34
        // Password is the public and private wallet passphrase.
35
        Password []byte
36

37
        // Birthday specifies the approximate time that this wallet was created.
38
        // This is used to bound any rescans on startup.
39
        Birthday time.Time
40

41
        // RecoveryWindow specifies the address lookahead when entering recovery
42
        // mode. A recovery will be attempted if this value is non-zero.
43
        RecoveryWindow uint32
44

45
        // Wallet is the loaded and unlocked Wallet. This is returned
46
        // from the unlocker service to avoid it being unlocked twice (once in
47
        // the unlocker service to check if the password is correct and again
48
        // later when lnd actually uses it). Because unlocking involves scrypt
49
        // which is resource intensive, we want to avoid doing it twice.
50
        Wallet *wallet.Wallet
51

52
        // ChansToRestore a set of static channel backups that should be
53
        // restored before the main server instance starts up.
54
        ChansToRestore ChannelsToRecover
55

56
        // UnloadWallet is a function for unloading the wallet, which should
57
        // be called on shutdown.
58
        UnloadWallet func() error
59

60
        // StatelessInit signals that the user requested the daemon to be
61
        // initialized stateless, which means no unencrypted macaroons should be
62
        // written to disk.
63
        StatelessInit bool
64

65
        // MacResponseChan is the channel for sending back the admin macaroon to
66
        // the WalletUnlocker service.
67
        MacResponseChan chan []byte
68

69
        // MacRootKey is the 32 byte macaroon root key specified by the user
70
        // during wallet initialization.
71
        MacRootKey []byte
72
}
73

74
// ChannelsToRecover wraps any set of packed (serialized+encrypted) channel
75
// back ups together. These can be passed in when unlocking the wallet, or
76
// creating a new wallet for the first time with an existing seed.
77
type ChannelsToRecover struct {
78
        // PackedMultiChanBackup is an encrypted and serialized multi-channel
79
        // backup.
80
        PackedMultiChanBackup chanbackup.PackedMulti
81

82
        // PackedSingleChanBackups is a series of encrypted and serialized
83
        // single-channel backup for one or more channels.
84
        PackedSingleChanBackups chanbackup.PackedSingles
85
}
86

87
// WalletInitMsg is a message sent by the UnlockerService when a user wishes to
88
// set up the internal wallet for the first time. The user MUST provide a
89
// passphrase, but is also able to provide their own source of entropy. If
90
// provided, then this source of entropy will be used to generate the wallet's
91
// HD seed. Otherwise, the wallet will generate one itself.
92
type WalletInitMsg struct {
93
        // Passphrase is the passphrase that will be used to encrypt the wallet
94
        // itself. This MUST be at least 8 characters.
95
        Passphrase []byte
96

97
        // WalletSeed is the deciphered cipher seed that the wallet should use
98
        // to initialize itself. The seed might be nil if the wallet should be
99
        // created from an extended master root key instead.
100
        WalletSeed *aezeed.CipherSeed
101

102
        // WalletExtendedKey is the wallet's extended master root key that
103
        // should be used instead of the seed, if non-nil. The extended key is
104
        // mutually exclusive to the wallet seed, but one of both is always set.
105
        WalletExtendedKey *hdkeychain.ExtendedKey
106

107
        // ExtendedKeyBirthday is the birthday of a wallet that's being restored
108
        // through an extended key instead of an aezeed.
109
        ExtendedKeyBirthday time.Time
110

111
        // WatchOnlyAccounts is a map of scoped account extended public keys
112
        // that should be imported to create a watch-only wallet.
113
        WatchOnlyAccounts map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey
114

115
        // WatchOnlyBirthday is the birthday of the master root key the above
116
        // watch-only account xpubs were derived from.
117
        WatchOnlyBirthday time.Time
118

119
        // WatchOnlyMasterFingerprint is the fingerprint of the master root key
120
        // the above watch-only account xpubs were derived from.
121
        WatchOnlyMasterFingerprint uint32
122

123
        // RecoveryWindow is the address look-ahead used when restoring a seed
124
        // with existing funds. A recovery window zero indicates that no
125
        // recovery should be attempted, such as after the wallet's initial
126
        // creation.
127
        RecoveryWindow uint32
128

129
        // ChanBackups a set of static channel backups that should be received
130
        // after the wallet has been initialized.
131
        ChanBackups ChannelsToRecover
132

133
        // StatelessInit signals that the user requested the daemon to be
134
        // initialized stateless, which means no unencrypted macaroons should be
135
        // written to disk.
136
        StatelessInit bool
137

138
        // MacRootKey is the 32 byte macaroon root key specified by the user
139
        // during wallet initialization.
140
        MacRootKey []byte
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
        // ChanBackups a set of static channel backups that should be received
166
        // after the wallet has been unlocked.
167
        ChanBackups ChannelsToRecover
168

169
        // UnloadWallet is a function for unloading the wallet, which should
170
        // be called on shutdown.
171
        UnloadWallet func() error
172

173
        // StatelessInit signals that the user requested the daemon to be
174
        // initialized stateless, which means no unencrypted macaroons should be
175
        // written to disk.
176
        StatelessInit bool
177
}
178

179
// UnlockerService implements the WalletUnlocker service used to provide lnd
180
// with a password for wallet encryption at startup. Additionally, during
181
// initial setup, users can provide their own source of entropy which will be
182
// used to generate the seed that's ultimately used within the wallet.
183
type UnlockerService struct {
184
        // Required by the grpc-gateway/v2 library for forward compatibility.
185
        lnrpc.UnimplementedWalletUnlockerServer
186

187
        // InitMsgs is a channel that carries all wallet init messages.
188
        InitMsgs chan *WalletInitMsg
189

190
        // UnlockMsgs is a channel where unlock parameters provided by the rpc
191
        // client to be used to unlock and decrypt an existing wallet will be
192
        // sent.
193
        UnlockMsgs chan *WalletUnlockMsg
194

195
        // MacResponseChan is the channel for sending back the admin macaroon to
196
        // the WalletUnlocker service.
197
        MacResponseChan chan []byte
198

199
        netParams *chaincfg.Params
200

201
        // macaroonFiles is the path to the three generated macaroons with
202
        // different access permissions. These might not exist in a stateless
203
        // initialization of lnd.
204
        macaroonFiles []string
205

206
        // resetWalletTransactions indicates that the wallet state should be
207
        // reset on unlock to force a full chain rescan.
208
        resetWalletTransactions bool
209

210
        // LoaderOpts holds the functional options for the wallet loader.
211
        loaderOpts []btcwallet.LoaderOption
212

213
        // macaroonDB is an instance of a database backend that stores all
214
        // macaroon root keys. This will be nil on initialization and must be
215
        // set using the SetMacaroonDB method as soon as it's available.
216
        macaroonDB kvdb.Backend
217
}
218

219
// New creates and returns a new UnlockerService.
220
func New(params *chaincfg.Params, macaroonFiles []string,
221
        resetWalletTransactions bool,
222
        loaderOpts []btcwallet.LoaderOption) *UnlockerService {
8✔
223

8✔
224
        return &UnlockerService{
8✔
225
                InitMsgs:   make(chan *WalletInitMsg, 1),
8✔
226
                UnlockMsgs: make(chan *WalletUnlockMsg, 1),
8✔
227

8✔
228
                // Make sure we buffer the channel is buffered so the main lnd
8✔
229
                // goroutine isn't blocking on writing to it.
8✔
230
                MacResponseChan:         make(chan []byte, 1),
8✔
231
                netParams:               params,
8✔
232
                macaroonFiles:           macaroonFiles,
8✔
233
                resetWalletTransactions: resetWalletTransactions,
8✔
234
                loaderOpts:              loaderOpts,
8✔
235
        }
8✔
236
}
8✔
237

238
// SetLoaderOpts can be used to inject wallet loader options after the unlocker
239
// service has been hooked to the main RPC server.
UNCOV
240
func (u *UnlockerService) SetLoaderOpts(loaderOpts []btcwallet.LoaderOption) {
×
UNCOV
241
        u.loaderOpts = loaderOpts
×
UNCOV
242
}
×
243

244
// SetMacaroonDB can be used to inject the macaroon database after the unlocker
245
// service has been hooked to the main RPC server.
246
func (u *UnlockerService) SetMacaroonDB(macaroonDB kvdb.Backend) {
2✔
247
        u.macaroonDB = macaroonDB
2✔
248
}
2✔
249

250
func (u *UnlockerService) newLoader(recoveryWindow uint32) (*wallet.Loader,
251
        error) {
17✔
252

17✔
253
        return btcwallet.NewWalletLoader(
17✔
254
                u.netParams, recoveryWindow, u.loaderOpts...,
17✔
255
        )
17✔
256
}
17✔
257

258
// WalletExists returns whether a wallet exists on the file path the
259
// UnlockerService is using.
UNCOV
260
func (u *UnlockerService) WalletExists() (bool, error) {
×
UNCOV
261
        loader, err := u.newLoader(0)
×
UNCOV
262
        if err != nil {
×
263
                return false, err
×
264
        }
×
UNCOV
265
        return loader.WalletExists()
×
266
}
267

268
// GenSeed is the first method that should be used to instantiate a new lnd
269
// instance. This method allows a caller to generate a new aezeed cipher seed
270
// given an optional passphrase. If provided, the passphrase will be necessary
271
// to decrypt the cipherseed to expose the internal wallet seed.
272
//
273
// Once the cipherseed is obtained and verified by the user, the InitWallet
274
// method should be used to commit the newly generated seed, and create the
275
// wallet.
276
func (u *UnlockerService) GenSeed(_ context.Context,
277
        in *lnrpc.GenSeedRequest) (*lnrpc.GenSeedResponse, error) {
4✔
278

4✔
279
        // Before we start, we'll ensure that the wallet hasn't already created
4✔
280
        // so we don't show a *new* seed to the user if one already exists.
4✔
281
        loader, err := u.newLoader(0)
4✔
282
        if err != nil {
4✔
283
                return nil, err
×
284
        }
×
285

286
        walletExists, err := loader.WalletExists()
4✔
287
        if err != nil {
4✔
288
                return nil, err
×
289
        }
×
290
        if walletExists {
5✔
291
                return nil, fmt.Errorf("wallet already exists")
1✔
292
        }
1✔
293

294
        var entropy [aezeed.EntropySize]byte
3✔
295

3✔
296
        switch {
3✔
297
        // If the user provided any entropy, then we'll make sure it's sized
298
        // properly.
299
        case len(in.SeedEntropy) != 0 && len(in.SeedEntropy) != aezeed.EntropySize:
1✔
300
                return nil, fmt.Errorf("incorrect entropy length: expected "+
1✔
301
                        "16 bytes, instead got %v bytes", len(in.SeedEntropy))
1✔
302

303
        // If the user provided the correct number of bytes, then we'll copy it
304
        // over into our buffer for usage.
305
        case len(in.SeedEntropy) == aezeed.EntropySize:
1✔
306
                copy(entropy[:], in.SeedEntropy[:])
1✔
307

308
        // Otherwise, we'll generate a fresh new set of bytes to use as entropy
309
        // to generate the seed.
310
        default:
1✔
311
                if _, err := rand.Read(entropy[:]); err != nil {
1✔
312
                        return nil, err
×
313
                }
×
314
        }
315

316
        // Now that we have our set of entropy, we'll create a new cipher seed
317
        // instance.
318
        //
319
        cipherSeed, err := aezeed.New(
2✔
320
                keychain.CurrentKeyDerivationVersion, &entropy, time.Now(),
2✔
321
        )
2✔
322
        if err != nil {
2✔
323
                return nil, err
×
324
        }
×
325

326
        // With our raw cipher seed obtained, we'll convert it into an encoded
327
        // mnemonic using the user specified pass phrase.
328
        mnemonic, err := cipherSeed.ToMnemonic(in.AezeedPassphrase)
2✔
329
        if err != nil {
2✔
330
                return nil, err
×
331
        }
×
332

333
        // Additionally, we'll also obtain the raw enciphered cipher seed as
334
        // well to return to the user.
335
        encipheredSeed, err := cipherSeed.Encipher(in.AezeedPassphrase)
2✔
336
        if err != nil {
2✔
337
                return nil, err
×
338
        }
×
339

340
        return &lnrpc.GenSeedResponse{
2✔
341
                CipherSeedMnemonic: mnemonic[:],
2✔
342
                EncipheredSeed:     encipheredSeed[:],
2✔
343
        }, nil
2✔
344
}
345

346
// extractChanBackups is a helper function that extracts the set of channel
347
// backups from the proto into a format that we'll pass to higher level
348
// sub-systems.
349
func extractChanBackups(chanBackups *lnrpc.ChanBackupSnapshot) *ChannelsToRecover {
2✔
350
        // If there aren't any populated channel backups, then we can exit
2✔
351
        // early as there's nothing to extract.
2✔
352
        if chanBackups == nil || (chanBackups.SingleChanBackups == nil &&
2✔
353
                chanBackups.MultiChanBackup == nil) {
4✔
354
                return nil
2✔
355
        }
2✔
356

357
        // Now that we know there's at least a single back up populated, we'll
358
        // extract the multi-chan backup (if it's there).
UNCOV
359
        var backups ChannelsToRecover
×
UNCOV
360
        if chanBackups.MultiChanBackup != nil {
×
UNCOV
361
                multiBackup := chanBackups.MultiChanBackup
×
UNCOV
362
                backups.PackedMultiChanBackup = multiBackup.MultiChanBackup
×
UNCOV
363
        }
×
364

UNCOV
365
        if chanBackups.SingleChanBackups == nil {
×
UNCOV
366
                return &backups
×
UNCOV
367
        }
×
368

369
        // Finally, we can extract all the single chan backups as well.
370
        for _, backup := range chanBackups.SingleChanBackups.ChanBackups {
×
371
                singleChanBackup := backup.ChanBackup
×
372

×
373
                backups.PackedSingleChanBackups = append(
×
374
                        backups.PackedSingleChanBackups, singleChanBackup,
×
375
                )
×
376
        }
×
377

378
        return &backups
×
379
}
380

381
// InitWallet is used when lnd is starting up for the first time to fully
382
// initialize the daemon and its internal wallet. At the very least a wallet
383
// password must be provided. This will be used to encrypt sensitive material
384
// on disk.
385
//
386
// In the case of a recovery scenario, the user can also specify their aezeed
387
// mnemonic and passphrase. If set, then the daemon will use this prior state
388
// to initialize its internal wallet.
389
//
390
// Alternatively, this can be used along with the GenSeed RPC to obtain a
391
// seed, then present it to the user. Once it has been verified by the user,
392
// the seed can be fed into this RPC in order to commit the new wallet.
393
func (u *UnlockerService) InitWallet(ctx context.Context,
394
        in *lnrpc.InitWalletRequest) (*lnrpc.InitWalletResponse, error) {
3✔
395

3✔
396
        // Make sure the password meets our constraints.
3✔
397
        password := in.WalletPassword
3✔
398
        if err := ValidatePassword(password); err != nil {
3✔
399
                return nil, err
×
400
        }
×
401

402
        // Require that the recovery window be non-negative.
403
        recoveryWindow := in.RecoveryWindow
3✔
404
        if recoveryWindow < 0 {
3✔
405
                return nil, fmt.Errorf("recovery window %d must be "+
×
406
                        "non-negative", recoveryWindow)
×
407
        }
×
408

409
        // Ensure that the macaroon root key is *exactly* 32-bytes.
410
        macaroonRootKey := in.MacaroonRootKey
3✔
411
        if len(macaroonRootKey) > 0 &&
3✔
412
                len(macaroonRootKey) != macaroons.RootKeyLen {
3✔
413

×
414
                return nil, fmt.Errorf("macaroon root key must be exactly "+
×
415
                        "%v bytes, is instead %v",
×
416
                        macaroons.RootKeyLen, len(macaroonRootKey),
×
417
                )
×
418
        }
×
419

420
        // We'll then open up the directory that will be used to store the
421
        // wallet's files so we can check if the wallet already exists.
422
        loader, err := u.newLoader(uint32(recoveryWindow))
3✔
423
        if err != nil {
3✔
424
                return nil, err
×
425
        }
×
426

427
        walletExists, err := loader.WalletExists()
3✔
428
        if err != nil {
3✔
429
                return nil, err
×
430
        }
×
431

432
        // If the wallet already exists, then we'll exit early as we can't
433
        // create the wallet if it already exists!
434
        if walletExists {
4✔
435
                return nil, fmt.Errorf("wallet already exists")
1✔
436
        }
1✔
437

438
        // At this point, we know the wallet doesn't already exist so we can
439
        // prepare the message that we'll send over the channel later.
440
        initMsg := &WalletInitMsg{
2✔
441
                Passphrase:     password,
2✔
442
                RecoveryWindow: uint32(recoveryWindow),
2✔
443
                StatelessInit:  in.StatelessInit,
2✔
444
                MacRootKey:     macaroonRootKey,
2✔
445
        }
2✔
446

2✔
447
        // There are two supported ways to initialize the wallet. Either from
2✔
448
        // the aezeed or the final extended master key directly.
2✔
449
        switch {
2✔
450
        // Don't allow the user to specify both as that would be ambiguous.
451
        case len(in.CipherSeedMnemonic) > 0 && len(in.ExtendedMasterKey) > 0:
×
452
                return nil, fmt.Errorf("cannot specify both the cipher " +
×
453
                        "seed mnemonic and the extended master key")
×
454

455
        // The aezeed is the preferred and default way of initializing a wallet.
456
        case len(in.CipherSeedMnemonic) > 0:
2✔
457
                // We'll map the user provided aezeed and passphrase into a
2✔
458
                // decoded cipher seed instance.
2✔
459
                var mnemonic aezeed.Mnemonic
2✔
460
                copy(mnemonic[:], in.CipherSeedMnemonic)
2✔
461

2✔
462
                // If we're unable to map it back into the ciphertext, then
2✔
463
                // either the mnemonic is wrong, or the passphrase is wrong.
2✔
464
                cipherSeed, err := mnemonic.ToCipherSeed(in.AezeedPassphrase)
2✔
465
                if err != nil {
3✔
466
                        return nil, err
1✔
467
                }
1✔
468

469
                initMsg.WalletSeed = cipherSeed
1✔
470

471
        // To support restoring a wallet where the seed isn't known or a wallet
472
        // created externally to lnd, we also allow the extended master key
473
        // (xprv) to be imported directly. This is what'll be stored in the
474
        // btcwallet database anyway.
UNCOV
475
        case len(in.ExtendedMasterKey) > 0:
×
UNCOV
476
                extendedKey, err := hdkeychain.NewKeyFromString(
×
UNCOV
477
                        in.ExtendedMasterKey,
×
UNCOV
478
                )
×
UNCOV
479
                if err != nil {
×
480
                        return nil, err
×
481
                }
×
482

483
                // The on-chain wallet of lnd is going to derive keys based on
484
                // the BIP49/84 key derivation paths from this root key. To make
485
                // sure we use default derivation paths, we want to avoid
486
                // deriving keys from something other than the master key (at
487
                // depth 0, denoted with "m/" in BIP32 notation).
UNCOV
488
                if extendedKey.Depth() != 0 {
×
489
                        return nil, fmt.Errorf("extended master key must " +
×
490
                                "be at depth 0 not a child key")
×
491
                }
×
492

493
                // Because we need the master key (at depth 0), it must be an
494
                // extended private key as the first levels of BIP49/84
495
                // derivation paths are hardened, which isn't possible with
496
                // extended public keys.
UNCOV
497
                if !extendedKey.IsPrivate() {
×
498
                        return nil, fmt.Errorf("extended master key must " +
×
499
                                "contain private keys")
×
500
                }
×
501

502
                // To avoid using the wrong master key, we check that it was
503
                // issued for the correct network. This will cause problems if
504
                // someone tries to import a "new" BIP84 zprv key because with
505
                // this we only support the "legacy" zprv prefix. But it is
506
                // trivial to convert between those formats, as long as the user
507
                // knows what they're doing.
UNCOV
508
                if !extendedKey.IsForNet(u.netParams) {
×
509
                        return nil, fmt.Errorf("extended master key must be "+
×
510
                                "for network %s", u.netParams.Name)
×
511
                }
×
512

513
                // When importing a wallet from its extended private key we
514
                // don't know the birthday as that information is not encoded in
515
                // that format. We therefore must set an arbitrary date to start
516
                // rescanning at if the user doesn't provide an explicit value
517
                // for it. Since lnd only uses SegWit addresses, we pick the
518
                // date of the first block that contained SegWit transactions
519
                // (481824).
UNCOV
520
                initMsg.ExtendedKeyBirthday = time.Date(
×
UNCOV
521
                        2017, time.August, 24, 1, 57, 37, 0, time.UTC,
×
UNCOV
522
                )
×
UNCOV
523
                if in.ExtendedMasterKeyBirthdayTimestamp != 0 {
×
524
                        initMsg.ExtendedKeyBirthday = time.Unix(
×
525
                                int64(in.ExtendedMasterKeyBirthdayTimestamp), 0,
×
526
                        )
×
527
                }
×
528

UNCOV
529
                initMsg.WalletExtendedKey = extendedKey
×
530

531
        // The third option for creating a wallet is the watch-only mode:
532
        // Instead of providing the master root key directly, each individual
533
        // account is passed as an extended public key only. Because of the
534
        // hardened derivation path up to the account (depth 3), it is not
535
        // possible to create a master root extended _public_ key. Therefore, an
536
        // xpub must be derived and passed into the unlocker for _every_ account
537
        // lnd expects.
UNCOV
538
        case in.WatchOnly != nil && len(in.WatchOnly.Accounts) > 0:
×
UNCOV
539
                initMsg.WatchOnlyAccounts = make(
×
UNCOV
540
                        map[waddrmgr.ScopedIndex]*hdkeychain.ExtendedKey,
×
UNCOV
541
                        len(in.WatchOnly.Accounts),
×
UNCOV
542
                )
×
UNCOV
543

×
UNCOV
544
                for _, acct := range in.WatchOnly.Accounts {
×
UNCOV
545
                        scopedIndex := waddrmgr.ScopedIndex{
×
UNCOV
546
                                Scope: waddrmgr.KeyScope{
×
UNCOV
547
                                        Purpose: acct.Purpose,
×
UNCOV
548
                                        Coin:    acct.CoinType,
×
UNCOV
549
                                },
×
UNCOV
550
                                Index: acct.Account,
×
UNCOV
551
                        }
×
UNCOV
552
                        acctKey, err := hdkeychain.NewKeyFromString(acct.Xpub)
×
UNCOV
553
                        if err != nil {
×
554
                                return nil, fmt.Errorf("error parsing xpub "+
×
555
                                        "%v: %v", acct.Xpub, err)
×
556
                        }
×
557

558
                        // Just to make sure the user is doing the right thing,
559
                        // we expect the public key to be at derivation depth
560
                        // three (which is the account level) and the key not to
561
                        // contain any private key material.
UNCOV
562
                        if acctKey.Depth() != 3 {
×
563
                                return nil, fmt.Errorf("xpub must be at " +
×
564
                                        "depth 3")
×
565
                        }
×
UNCOV
566
                        if acctKey.IsPrivate() {
×
567
                                return nil, fmt.Errorf("xpub is not really " +
×
568
                                        "an xpub, contains private key")
×
569
                        }
×
570

UNCOV
571
                        initMsg.WatchOnlyAccounts[scopedIndex] = acctKey
×
572
                }
573

574
                // When importing a wallet from its extended public keys we
575
                // don't know the birthday as that information is not encoded in
576
                // that format. We therefore must set an arbitrary date to start
577
                // rescanning at if the user doesn't provide an explicit value
578
                // for it. Since lnd only uses SegWit addresses, we pick the
579
                // date of the first block that contained SegWit transactions
580
                // (481824).
UNCOV
581
                initMsg.WatchOnlyBirthday = time.Date(
×
UNCOV
582
                        2017, time.August, 24, 1, 57, 37, 0, time.UTC,
×
UNCOV
583
                )
×
UNCOV
584
                if in.WatchOnly.MasterKeyBirthdayTimestamp != 0 {
×
585
                        initMsg.WatchOnlyBirthday = time.Unix(
×
586
                                int64(in.WatchOnly.MasterKeyBirthdayTimestamp),
×
587
                                0,
×
588
                        )
×
589
                }
×
590

591
        // No key material was set, no wallet can be created.
592
        default:
×
593
                return nil, fmt.Errorf("must either specify cipher seed " +
×
594
                        "mnemonic or the extended master key")
×
595
        }
596

597
        // Before we return the unlock payload, we'll check if we can extract
598
        // any channel backups to pass up to the higher level sub-system.
599
        chansToRestore := extractChanBackups(in.ChannelBackups)
1✔
600
        if chansToRestore != nil {
1✔
UNCOV
601
                initMsg.ChanBackups = *chansToRestore
×
UNCOV
602
        }
×
603

604
        // Deliver the initialization message back to the main daemon.
605
        select {
1✔
606
        case u.InitMsgs <- initMsg:
1✔
607
                // We need to read from the channel to let the daemon continue
1✔
608
                // its work and to get the admin macaroon. Once the response
1✔
609
                // arrives, we directly forward it to the client.
1✔
610
                select {
1✔
611
                case adminMac := <-u.MacResponseChan:
1✔
612
                        return &lnrpc.InitWalletResponse{
1✔
613
                                AdminMacaroon: adminMac,
1✔
614
                        }, nil
1✔
615

616
                case <-ctx.Done():
×
617
                        return nil, ErrUnlockTimeout
×
618
                }
619

620
        case <-ctx.Done():
×
621
                return nil, ErrUnlockTimeout
×
622
        }
623
}
624

625
// LoadAndUnlock creates a loader for the wallet and tries to unlock the wallet
626
// with the given password and recovery window. If the drop wallet transactions
627
// flag is set, the history state drop is performed before unlocking the wallet
628
// yet again.
629
func (u *UnlockerService) LoadAndUnlock(password []byte,
630
        recoveryWindow uint32) (*wallet.Wallet, func() error, error) {
3✔
631

3✔
632
        loader, err := u.newLoader(recoveryWindow)
3✔
633
        if err != nil {
3✔
634
                return nil, nil, err
×
635
        }
×
636

637
        // Check if wallet already exists.
638
        walletExists, err := loader.WalletExists()
3✔
639
        if err != nil {
3✔
640
                return nil, nil, err
×
641
        }
×
642

643
        if !walletExists {
4✔
644
                // Cannot unlock a wallet that does not exist!
1✔
645
                return nil, nil, fmt.Errorf("wallet not found")
1✔
646
        }
1✔
647

648
        // Try opening the existing wallet with the provided password.
649
        unlockedWallet, err := loader.OpenExistingWallet(password, false)
2✔
650
        if err != nil {
3✔
651
                // Could not open wallet, most likely this means that provided
1✔
652
                // password was incorrect.
1✔
653
                return nil, nil, err
1✔
654
        }
1✔
655

656
        // The user requested to drop their whole wallet transaction state to
657
        // force a full chain rescan for wallet addresses. Dropping the state
658
        // only properly takes effect after opening the wallet. That's why we
659
        // start, drop, stop and start again.
660
        if u.resetWalletTransactions {
2✔
661
                dropErr := wallet.DropTransactionHistory(
1✔
662
                        unlockedWallet.Database(), true,
1✔
663
                )
1✔
664

1✔
665
                // Even if dropping the history fails, we'll want to unload the
1✔
666
                // wallet. If unloading fails, that error is probably more
1✔
667
                // important to be returned to the user anyway.
1✔
668
                if err := loader.UnloadWallet(); err != nil {
1✔
669
                        return nil, nil, fmt.Errorf("could not unload "+
×
670
                                "wallet (tx history drop err: %v): %v", dropErr,
×
671
                                err)
×
672
                }
×
673

674
                // If dropping failed but unloading didn't, we'll still abort
675
                // and inform the user.
676
                if dropErr != nil {
1✔
677
                        return nil, nil, dropErr
×
678
                }
×
679

680
                // All looks good, let's now open the wallet again. The loader
681
                // was unloaded and might have removed its remote DB connection,
682
                // so let's re-create it as well.
683
                loader, err = u.newLoader(recoveryWindow)
1✔
684
                if err != nil {
1✔
685
                        return nil, nil, err
×
686
                }
×
687
                unlockedWallet, err = loader.OpenExistingWallet(password, false)
1✔
688
                if err != nil {
1✔
689
                        return nil, nil, err
×
690
                }
×
691
        }
692

693
        return unlockedWallet, loader.UnloadWallet, nil
1✔
694
}
695

696
// UnlockWallet sends the password provided by the incoming UnlockWalletRequest
697
// over the UnlockMsgs channel in case it successfully decrypts an existing
698
// wallet found in the chain's wallet database directory.
699
func (u *UnlockerService) UnlockWallet(ctx context.Context,
700
        in *lnrpc.UnlockWalletRequest) (*lnrpc.UnlockWalletResponse, error) {
3✔
701

3✔
702
        password := in.WalletPassword
3✔
703
        recoveryWindow := uint32(in.RecoveryWindow)
3✔
704

3✔
705
        unlockedWallet, unloadFn, err := u.LoadAndUnlock(
3✔
706
                password, recoveryWindow,
3✔
707
        )
3✔
708
        if err != nil {
5✔
709
                return nil, err
2✔
710
        }
2✔
711

712
        // We successfully opened the wallet and pass the instance back to
713
        // avoid it needing to be unlocked again.
714
        walletUnlockMsg := &WalletUnlockMsg{
1✔
715
                Passphrase:     password,
1✔
716
                RecoveryWindow: recoveryWindow,
1✔
717
                Wallet:         unlockedWallet,
1✔
718
                UnloadWallet:   unloadFn,
1✔
719
                StatelessInit:  in.StatelessInit,
1✔
720
        }
1✔
721

1✔
722
        // Before we return the unlock payload, we'll check if we can extract
1✔
723
        // any channel backups to pass up to the higher level sub-system.
1✔
724
        chansToRestore := extractChanBackups(in.ChannelBackups)
1✔
725
        if chansToRestore != nil {
1✔
UNCOV
726
                walletUnlockMsg.ChanBackups = *chansToRestore
×
UNCOV
727
        }
×
728

729
        // At this point we were able to open the existing wallet with the
730
        // provided password. We send the password over the UnlockMsgs
731
        // channel, such that it can be used by lnd to open the wallet.
732
        select {
1✔
733
        case u.UnlockMsgs <- walletUnlockMsg:
1✔
734
                // We need to read from the channel to let the daemon continue
1✔
735
                // its work. But we don't need the returned macaroon for this
1✔
736
                // operation, so we read it but then discard it.
1✔
737
                select {
1✔
738
                case <-u.MacResponseChan:
1✔
739
                        return &lnrpc.UnlockWalletResponse{}, nil
1✔
740

741
                case <-ctx.Done():
×
742
                        return nil, ErrUnlockTimeout
×
743
                }
744

745
        case <-ctx.Done():
×
746
                return nil, ErrUnlockTimeout
×
747
        }
748
}
749

750
// ChangePassword changes the password of the wallet and sends the new password
751
// across the UnlockPasswords channel to automatically unlock the wallet if
752
// successful.
753
func (u *UnlockerService) ChangePassword(ctx context.Context,
754
        in *lnrpc.ChangePasswordRequest) (*lnrpc.ChangePasswordResponse, error) {
6✔
755

6✔
756
        loader, err := u.newLoader(0)
6✔
757
        if err != nil {
6✔
758
                return nil, err
×
759
        }
×
760

761
        // First, we'll make sure the wallet exists for the specific chain and
762
        // network.
763
        walletExists, err := loader.WalletExists()
6✔
764
        if err != nil {
6✔
765
                return nil, err
×
766
        }
×
767

768
        if !walletExists {
7✔
769
                return nil, errors.New("wallet not found")
1✔
770
        }
1✔
771

772
        publicPw := in.CurrentPassword
5✔
773
        privatePw := in.CurrentPassword
5✔
774

5✔
775
        // If the current password is blank, we'll assume the user is coming
5✔
776
        // from a --noseedbackup state, so we'll use the default passwords.
5✔
777
        if len(in.CurrentPassword) == 0 {
7✔
778
                publicPw = lnwallet.DefaultPublicPassphrase
2✔
779
                privatePw = lnwallet.DefaultPrivatePassphrase
2✔
780
        }
2✔
781

782
        // Make sure the new password meets our constraints.
783
        if err := ValidatePassword(in.NewPassword); err != nil {
6✔
784
                return nil, err
1✔
785
        }
1✔
786

787
        // Load the existing wallet in order to proceed with the password change.
788
        w, err := loader.OpenExistingWallet(publicPw, false)
4✔
789
        if err != nil {
5✔
790
                return nil, err
1✔
791
        }
1✔
792

793
        // Now that we've opened the wallet, we need to close it in case of an
794
        // error. But not if we succeed, then the caller must close it.
795
        orderlyReturn := false
3✔
796
        defer func() {
6✔
797
                if !orderlyReturn {
4✔
798
                        _ = loader.UnloadWallet()
1✔
799
                }
1✔
800
        }()
801

802
        // Before we actually change the password, we need to check if all flags
803
        // were set correctly. The content of the previously generated macaroon
804
        // files will become invalid after we generate a new root key. So we try
805
        // to delete them here and they will be recreated during normal startup
806
        // later. If they are missing, this is only an error if the
807
        // stateless_init flag was not set.
808
        if in.NewMacaroonRootKey || in.StatelessInit {
6✔
809
                for _, file := range u.macaroonFiles {
10✔
810
                        err := os.Remove(file)
7✔
811
                        if err != nil && !in.StatelessInit {
8✔
812
                                return nil, fmt.Errorf("could not remove "+
1✔
813
                                        "macaroon file: %v. if the wallet "+
1✔
814
                                        "was initialized stateless please "+
1✔
815
                                        "add the --stateless_init "+
1✔
816
                                        "flag", err)
1✔
817
                        }
1✔
818
                }
819
        }
820

821
        // Attempt to change both the public and private passphrases for the
822
        // wallet. This will be done atomically in order to prevent one
823
        // passphrase change from being successful and not the other.
824
        err = w.ChangePassphrases(
2✔
825
                publicPw, in.NewPassword, privatePw, in.NewPassword,
2✔
826
        )
2✔
827
        if err != nil {
2✔
828
                return nil, fmt.Errorf("unable to change wallet passphrase: "+
×
829
                        "%v", err)
×
830
        }
×
831

832
        // The next step is to load the macaroon database, change the password
833
        // then close it again.
834
        // Attempt to open the macaroon DB, unlock it and then change
835
        // the passphrase.
836
        rootKeyStore, err := macaroons.NewRootKeyStorage(u.macaroonDB)
2✔
837
        if err != nil {
2✔
838
                return nil, err
×
839
        }
×
840
        macaroonService, err := macaroons.NewService(
2✔
841
                rootKeyStore, "lnd", in.StatelessInit,
2✔
842
        )
2✔
843
        if err != nil {
2✔
844
                return nil, err
×
845
        }
×
846

847
        err = macaroonService.CreateUnlock(&privatePw)
2✔
848
        if err != nil {
2✔
849
                closeErr := macaroonService.Close()
×
850
                if closeErr != nil {
×
851
                        return nil, fmt.Errorf("could not create unlock: %v "+
×
852
                                "--> follow-up error when closing: %v", err,
×
853
                                closeErr)
×
854
                }
×
855
                return nil, err
×
856
        }
857
        err = macaroonService.ChangePassword(privatePw, in.NewPassword)
2✔
858
        if err != nil {
2✔
859
                closeErr := macaroonService.Close()
×
860
                if closeErr != nil {
×
861
                        return nil, fmt.Errorf("could not change password: %v "+
×
862
                                "--> follow-up error when closing: %v", err,
×
863
                                closeErr)
×
864
                }
×
865
                return nil, err
×
866
        }
867

868
        // If requested by the user, attempt to replace the existing
869
        // macaroon root key with a new one.
870
        if in.NewMacaroonRootKey {
4✔
871
                err = macaroonService.GenerateNewRootKey()
2✔
872
                if err != nil {
2✔
873
                        closeErr := macaroonService.Close()
×
874
                        if closeErr != nil {
×
875
                                return nil, fmt.Errorf("could not generate "+
×
876
                                        "new root key: %v --> follow-up error "+
×
877
                                        "when closing: %v", err, closeErr)
×
878
                        }
×
879
                        return nil, err
×
880
                }
881
        }
882

883
        err = macaroonService.Close()
2✔
884
        if err != nil {
2✔
885
                return nil, fmt.Errorf("could not close macaroon service: %w",
×
886
                        err)
×
887
        }
×
888

889
        // Finally, send the new password across the UnlockPasswords channel to
890
        // automatically unlock the wallet.
891
        walletUnlockMsg := &WalletUnlockMsg{
2✔
892
                Passphrase:    in.NewPassword,
2✔
893
                Wallet:        w,
2✔
894
                StatelessInit: in.StatelessInit,
2✔
895
                UnloadWallet:  loader.UnloadWallet,
2✔
896
        }
2✔
897
        select {
2✔
898
        case u.UnlockMsgs <- walletUnlockMsg:
2✔
899
                // We need to read from the channel to let the daemon continue
2✔
900
                // its work and to get the admin macaroon. Once the response
2✔
901
                // arrives, we directly forward it to the client.
2✔
902
                orderlyReturn = true
2✔
903
                select {
2✔
904
                case adminMac := <-u.MacResponseChan:
2✔
905
                        return &lnrpc.ChangePasswordResponse{
2✔
906
                                AdminMacaroon: adminMac,
2✔
907
                        }, nil
2✔
908

909
                case <-ctx.Done():
×
910
                        return nil, ErrUnlockTimeout
×
911
                }
912

913
        case <-ctx.Done():
×
914
                return nil, ErrUnlockTimeout
×
915
        }
916
}
917

918
// ValidatePassword assures the password meets all of our constraints.
919
func ValidatePassword(password []byte) error {
8✔
920
        // Passwords should have a length of at least 8 characters.
8✔
921
        if len(password) < 8 {
9✔
922
                return errors.New("password must have at least 8 characters")
1✔
923
        }
1✔
924

925
        return nil
7✔
926
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc