Add fixoldbackup command

pull/3/head
Oliver Gugger 4 years ago
parent 543aacad35
commit 0a517d1a75
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -9,6 +9,7 @@
+ [dumpbackup](#dumpbackup)
+ [dumpchannels](#dumpchannels)
+ [filterbackup](#filterbackup)
+ [fixoldbackup](#fixoldbackup)
+ [forceclose](#forceclose)
+ [rescueclosed](#rescueclosed)
+ [showrootkey](#showrootkey)
@ -57,6 +58,7 @@ Available commands:
dumpbackup Dump the content of a channel.backup file.
dumpchannels Dump all channel information from lnd's channel database.
filterbackup Filter an lnd channel.backup file and remove certain channels.
fixoldbackup Fixes an old channel.backup file that is affected by the lnd issue #3881 (unable to derive shachain root key).
forceclose Force-close the last state that is in the channel.db provided.
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
@ -152,6 +154,30 @@ chantools filterbackup --rootkey xprvxxxxxxxxxx \
--discard 2abcdef2b2bffaaa...db0abadd:1,4abcdef2b2bffaaa...db8abadd:0
```
### fixoldbackup
```text
Usage:
chantools [OPTIONS] fixoldbackup [fixoldbackup-OPTIONS]
[fixoldbackup command options]
--rootkey= BIP32 HD root key of the wallet that was used to create the backup. Leave empty to prompt for lnd 24 word aezeed.
--multi_file= The lnd channel.backup file to fix.
```
Fixes an old channel.backup file that is affected by the lnd issue
[#3881](https://github.com/lightningnetwork/lnd/issues/3881) (<code>[lncli]
unable to restore chan backups: rpc error: code = Unknown desc = unable
to unpack chan backup: unable to derive shachain root key: unable to derive
private key</code>).
Example command:
```bash
chantools fixoldbackup --rootkey xprvxxxxxxxxxx \
--multi_file ~/.lnd/data/chain/bitcoin/mainnet/channel.backup
```
### forceclose
```text

@ -57,22 +57,22 @@ func ParsePath(path string) ([]uint32, error) {
return indices, nil
}
type ChannelBackupEncryptionRing struct {
type HDKeyRing struct {
ExtendedKey *hdkeychain.ExtendedKey
ChainParams *chaincfg.Params
}
func (r *ChannelBackupEncryptionRing) DeriveNextKey(_ keychain.KeyFamily) (
func (r *HDKeyRing) DeriveNextKey(_ keychain.KeyFamily) (
keychain.KeyDescriptor, error) {
return keychain.KeyDescriptor{}, nil
}
func (r *ChannelBackupEncryptionRing) DeriveKey(keyLoc keychain.KeyLocator) (
func (r *HDKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (
keychain.KeyDescriptor, error) {
var empty = keychain.KeyDescriptor{}
keyBackup, err := DeriveChildren(r.ExtendedKey, []uint32{
derivedKey, err := DeriveChildren(r.ExtendedKey, []uint32{
HardenedKeyStart + uint32(keychain.BIP0043Purpose),
HardenedKeyStart + r.ChainParams.HDCoinType,
HardenedKeyStart + uint32(keyLoc.Family),
@ -83,7 +83,7 @@ func (r *ChannelBackupEncryptionRing) DeriveKey(keyLoc keychain.KeyLocator) (
return empty, err
}
backupPubKey, err := keyBackup.ECPubKey()
derivedPubKey, err := derivedKey.ECPubKey()
if err != nil {
return empty, err
}
@ -92,6 +92,49 @@ func (r *ChannelBackupEncryptionRing) DeriveKey(keyLoc keychain.KeyLocator) (
Family: keyLoc.Family,
Index: keyLoc.Index,
},
PubKey: backupPubKey,
PubKey: derivedPubKey,
}, nil
}
// Check if a key descriptor is correct by making sure that we can derive the
// key that it describes.
func (r *HDKeyRing) CheckDescriptor(
keyDesc keychain.KeyDescriptor) error {
// A check doesn't make sense if there is no public key set.
if keyDesc.PubKey == nil {
return fmt.Errorf("no public key provided to check")
}
// Performance fix, derive static path only once.
familyKey, err := DeriveChildren(r.ExtendedKey, []uint32{
HardenedKeyStart + uint32(keychain.BIP0043Purpose),
HardenedKeyStart + r.ChainParams.HDCoinType,
HardenedKeyStart + uint32(keyDesc.Family),
0,
})
if err != nil {
return err
}
// Scan the same key range as lnd would do on channel restore.
for i := 0; i < keychain.MaxKeyRangeScan; i++ {
child, err := DeriveChildren(familyKey, []uint32{uint32(i)})
if err != nil {
return err
}
pubKey, err := child.ECPubKey()
if err != nil {
return err
}
if !pubKey.IsEqual(keyDesc.PubKey) {
continue
}
// If we found the key, we can abort and signal success.
return nil
}
// We scanned the max range and didn't find a key. It's very likely not
// derivable with the given information.
return keychain.ErrCannotDerivePrivKey
}

@ -41,7 +41,7 @@ func (c *dumpBackupCommand) Execute(_ []string) error {
return fmt.Errorf("backup file is required")
}
multiFile := chanbackup.NewMultiFile(c.MultiFile)
keyRing := &btc.ChannelBackupEncryptionRing{
keyRing := &btc.HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}

@ -46,7 +46,7 @@ func (c *filterBackupCommand) Execute(_ []string) error {
return fmt.Errorf("backup file is required")
}
multiFile := chanbackup.NewMultiFile(c.MultiFile)
keyRing := &btc.ChannelBackupEncryptionRing{
keyRing := &btc.HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}

@ -0,0 +1,108 @@
package main
import (
"fmt"
"os"
"time"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/guggero/chantools/btc"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/keychain"
)
type fixOldBackupCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root key of the wallet that was used to create the backup. Leave empty to prompt for lnd 24 word aezeed."`
MultiFile string `long:"multi_file" description:"The lnd channel.backup file to fix."`
}
func (c *fixOldBackupCommand) Execute(_ []string) error {
setupChainParams(cfg)
var (
extendedKey *hdkeychain.ExtendedKey
err error
)
// Check that root key is valid or fall back to console input.
switch {
case c.RootKey != "":
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default:
extendedKey, err = rootKeyFromConsole()
}
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
// Check that we have a backup file.
if c.MultiFile == "" {
return fmt.Errorf("backup file is required")
}
multiFile := chanbackup.NewMultiFile(c.MultiFile)
keyRing := &btc.HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
return fixOldChannelBackup(multiFile, keyRing)
}
func fixOldChannelBackup(multiFile *chanbackup.MultiFile,
ring *btc.HDKeyRing) error {
multi, err := multiFile.ExtractMulti(ring)
if err != nil {
return fmt.Errorf("could not extract multi file: %v", err)
}
log.Infof("Checking shachain root of %d channels, this might take a "+
"while.", len(multi.StaticBackups))
fixedChannels := 0
for _, single := range multi.StaticBackups {
err := ring.CheckDescriptor(single.ShaChainRootDesc)
switch err {
case nil:
continue
case keychain.ErrCannotDerivePrivKey:
// Fix the incorrect descriptor by deriving a default
// one and overwriting it in the backup.
log.Infof("The shachain root for channel %s could "+
"not be derived, must be in old format. "+
"Fixing...", single.FundingOutpoint.String())
baseKeyDesc, err := ring.DeriveKey(keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
Index: 0,
})
if err != nil {
return err
}
single.ShaChainRootDesc = baseKeyDesc
fixedChannels++
default:
return fmt.Errorf("could not check shachain root "+
"descriptor: %v", err)
}
}
if fixedChannels == 0 {
log.Info("No channels were affected by issue #3881, nothing " +
"to fix.")
return nil
}
log.Infof("Fixed shachain root of %d channels.", fixedChannels)
fileName := fmt.Sprintf("results/backup-fixed-%s.backup",
time.Now().Format("2006-01-02-15-04-05"))
log.Infof("Writing result to %s", fileName)
f, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
err = multi.PackToWriter(f, ring)
_ = f.Close()
if err != nil {
return err
}
return nil
}

@ -98,6 +98,10 @@ func runCommandParser() error {
"filterbackup", "Filter an lnd channel.backup file and " +
"remove certain channels.", "", &filterBackupCommand{},
)
_, _ = parser.AddCommand(
"fixoldbackup", "Fixes an old channel.backup file that is " +
"affected by the lnd issue #3881 (unable to derive " +
"shachain root key).", "", &fixOldBackupCommand{})
_, err := parser.Parse()
return err

Loading…
Cancel
Save