diff --git a/walletunlocker/service.go b/walletunlocker/service.go index 747a9e54..3fe007cb 100644 --- a/walletunlocker/service.go +++ b/walletunlocker/service.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/wallet" "github.com/lightningnetwork/lnd/aezeed" + "github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnwallet" @@ -17,6 +18,19 @@ import ( "golang.org/x/net/context" ) +// ChannelsToRecover wraps any set of packed (serialized+encrypted) channel +// back ups together. These can be passed in when unlocking the wallet, or +// creating a new wallet for the first time with an existing seed. +type ChannelsToRecover struct { + // PackedMultiChanBackup is an encrypted and serialized multi-channel + // backup. + PackedMultiChanBackup chanbackup.PackedMulti + + // PackedSingleChanBackups is a series of encrypted and serialized + // single-channel backup for one or more channels. + PackedSingleChanBackups chanbackup.PackedSingles +} + // WalletInitMsg is a message sent by the UnlockerService when a user wishes to // set up the internal wallet for the first time. The user MUST provide a // passphrase, but is also able to provide their own source of entropy. If @@ -36,6 +50,10 @@ type WalletInitMsg struct { // recovery should be attempted, such as after the wallet's initial // creation. RecoveryWindow uint32 + + // ChanBackups a set of static channel backups that should be received + // after the wallet has been initialized. + ChanBackups ChannelsToRecover } // WalletUnlockMsg is a message sent by the UnlockerService when a user wishes @@ -53,12 +71,16 @@ type WalletUnlockMsg struct { // creation, but before any addresses have been created. RecoveryWindow uint32 - // Wallet is the loaded and unlocked Wallet. This is returned - // through the channel to avoid it being unlocked twice (once to check - // if the password is correct, here in the WalletUnlocker and again - // later when lnd actually uses it). Because unlocking involves scrypt - // which is resource intensive, we want to avoid doing it twice. + // Wallet is the loaded and unlocked Wallet. This is returned through + // the channel to avoid it being unlocked twice (once to check if the + // password is correct, here in the WalletUnlocker and again later when + // lnd actually uses it). Because unlocking involves scrypt which is + // resource intensive, we want to avoid doing it twice. Wallet *wallet.Wallet + + // ChanBackups a set of static channel backups that should be received + // after the wallet has been unlocked. + ChanBackups ChannelsToRecover } // UnlockerService implements the WalletUnlocker service used to provide lnd @@ -167,6 +189,43 @@ func (u *UnlockerService) GenSeed(ctx context.Context, }, nil } +// extractChanBackups is a helper function that extracts the set of channel +// backups from the proto into a format that we'll pass to higher level +// sub-systems. +func extractChanBackups(chanBackups *lnrpc.ChanBackupSnapshot) *ChannelsToRecover { + // If there aren't any populated channel backups, then we can exit + // early as there's nothing to extract. + if chanBackups == nil || (chanBackups.SingleChanBackups == nil && + chanBackups.MultiChanBackup == nil) { + return nil + } + + // Now that we know there's at least a single back up populated, we'll + // extract the multi-chan backup (if it's there). + var backups ChannelsToRecover + if chanBackups.MultiChanBackup != nil { + multiBackup := chanBackups.MultiChanBackup + backups.PackedMultiChanBackup = chanbackup.PackedMulti( + multiBackup.MultiChanBackup, + ) + } + + if chanBackups.SingleChanBackups == nil { + return &backups + } + + // Finally, we can extract all the single chan backups as well. + for _, backup := range chanBackups.SingleChanBackups.ChanBackups { + singleChanBackup := backup.ChanBackup + + backups.PackedSingleChanBackups = append( + backups.PackedSingleChanBackups, singleChanBackup, + ) + } + + return &backups +} + // InitWallet is used when lnd is starting up for the first time to fully // initialize the daemon and its internal wallet. At the very least a wallet // password must be provided. This will be used to encrypt sensitive material @@ -233,6 +292,13 @@ func (u *UnlockerService) InitWallet(ctx context.Context, RecoveryWindow: uint32(recoveryWindow), } + // Before we return the unlock payload, we'll check if we can extract + // any channel backups to pass up to the higher level sub-system. + chansToRestore := extractChanBackups(in.ChannelBackups) + if chansToRestore != nil { + initMsg.ChanBackups = *chansToRestore + } + u.InitMsgs <- initMsg return &lnrpc.InitWalletResponse{}, nil @@ -277,6 +343,13 @@ func (u *UnlockerService) UnlockWallet(ctx context.Context, Wallet: unlockedWallet, } + // Before we return the unlock payload, we'll check if we can extract + // any channel backups to pass up to the higher level sub-system. + chansToRestore := extractChanBackups(in.ChannelBackups) + if chansToRestore != nil { + walletUnlockMsg.ChanBackups = *chansToRestore + } + // At this point we was able to open the existing wallet with the // provided password. We send the password over the UnlockMsgs // channel, such that it can be used by lnd to open the wallet.