From 22420b9acad56276f829a36dd46135c7aa602cc8 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sun, 5 Jan 2020 00:33:43 +0100 Subject: [PATCH] Add filterbackup command --- README.md | 25 ++++++++++ cmd/chantools/dumpbackup.go | 16 +++--- cmd/chantools/filterbackup.go | 92 +++++++++++++++++++++++++++++++++++ cmd/chantools/main.go | 4 ++ dataformat/input.go | 12 ++--- 5 files changed, 137 insertions(+), 12 deletions(-) create mode 100644 cmd/chantools/filterbackup.go diff --git a/README.md b/README.md index 4e207d9..fd4ae31 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ * [Commands](#commands) + [dumpbackup](#dumpbackup) + [dumpchannels](#dumpchannels) + + [filterbackup](#filterbackup) + [forceclose](#forceclose) + [rescueclosed](#rescueclosed) + [showrootkey](#showrootkey) @@ -54,6 +55,7 @@ Available commands: derivekey Derive a key with a specific derivation path from the BIP32 HD root key. 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. 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. @@ -126,6 +128,29 @@ Example command: chantools dumpchannels --channeldb ~/.lnd/data/graph/mainnet/channel.db ``` +### filterbackup + +```text +Usage: + chantools [OPTIONS] filterbackup [filterbackup-OPTIONS] + +[filterbackup 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 filter. + --discard= A comma separated list of channel funding outpoints (format :) to remove from the backup file. +``` + +Filter an lnd `channel.backup` file by removing certain channels (identified by +their funding transaction outpoints). + +Example command: + +```bash +chantools filterbackup --rootkey xprvxxxxxxxxxx \ + --multi_file ~/.lnd/data/chain/bitcoin/mainnet/channel.backup \ + --discard 2abcdef2b2bffaaa...db0abadd:1,4abcdef2b2bffaaa...db8abadd:0 +``` + ### forceclose ```text diff --git a/cmd/chantools/dumpbackup.go b/cmd/chantools/dumpbackup.go index ea505c7..34e5910 100644 --- a/cmd/chantools/dumpbackup.go +++ b/cmd/chantools/dumpbackup.go @@ -8,6 +8,7 @@ import ( "github.com/guggero/chantools/btc" "github.com/guggero/chantools/dump" "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/keychain" ) type dumpBackupCommand struct { @@ -40,17 +41,20 @@ func (c *dumpBackupCommand) Execute(_ []string) error { return fmt.Errorf("backup file is required") } multiFile := chanbackup.NewMultiFile(c.MultiFile) - multi, err := multiFile.ExtractMulti(&btc.ChannelBackupEncryptionRing{ + keyRing := &btc.ChannelBackupEncryptionRing{ ExtendedKey: extendedKey, ChainParams: chainParams, - }) - if err != nil { - return fmt.Errorf("could not extract multi file: %v", err) } - return dumpChannelBackup(multi) + return dumpChannelBackup(multiFile, keyRing) } -func dumpChannelBackup(multi *chanbackup.Multi) error { +func dumpChannelBackup(multiFile *chanbackup.MultiFile, + ring keychain.KeyRing) error { + + multi, err := multiFile.ExtractMulti(ring) + if err != nil { + return fmt.Errorf("could not extract multi file: %v", err) + } dumpSingles := make([]dump.BackupSingle, len(multi.StaticBackups)) for idx, single := range multi.StaticBackups { dumpSingles[idx] = dump.BackupSingle{ diff --git a/cmd/chantools/filterbackup.go b/cmd/chantools/filterbackup.go new file mode 100644 index 0000000..e4714eb --- /dev/null +++ b/cmd/chantools/filterbackup.go @@ -0,0 +1,92 @@ +package main + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/btcsuite/btcutil/hdkeychain" + "github.com/guggero/chantools/btc" + "github.com/lightningnetwork/lnd/chanbackup" + "github.com/lightningnetwork/lnd/keychain" +) + +type filterBackupCommand 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 filter."` + Discard string `long:"discard" description:"A comma separated list of channel funding outpoints (format :) to remove from the backup file."` +} + +func (c *filterBackupCommand) 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) + } + + // Parse discard filter. + discard := strings.Split(c.Discard, ",") + + // Check that we have a backup file. + if c.MultiFile == "" { + return fmt.Errorf("backup file is required") + } + multiFile := chanbackup.NewMultiFile(c.MultiFile) + keyRing := &btc.ChannelBackupEncryptionRing{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + return filterChannelBackup(multiFile, keyRing, discard) +} + +func filterChannelBackup(multiFile *chanbackup.MultiFile, ring keychain.KeyRing, + discard []string) error { + + multi, err := multiFile.ExtractMulti(ring) + if err != nil { + return fmt.Errorf("could not extract multi file: %v", err) + } + + keep := make([]chanbackup.Single, 0, len(multi.StaticBackups)) + for _, single := range multi.StaticBackups { + found := false + for _, discardChanPoint := range discard { + if single.FundingOutpoint.String() == discardChanPoint { + found = true + } + } + if found { + continue + } + keep = append(keep, single) + } + multi.StaticBackups = keep + + fileName := fmt.Sprintf("results/backup-filtered-%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 +} diff --git a/cmd/chantools/main.go b/cmd/chantools/main.go index 6466b8b..253f9af 100644 --- a/cmd/chantools/main.go +++ b/cmd/chantools/main.go @@ -93,6 +93,10 @@ func runCommandParser() error { "derivekey", "Derive a key with a specific derivation path "+ "from the BIP32 HD root key.", "", &deriveKeyCommand{}, ) + _, _ = parser.AddCommand( + "filterbackup", "Filter an lnd channel.backup file and " + + "remove certain channels.", "", &filterBackupCommand{}, + ) _, err := parser.Parse() return err diff --git a/dataformat/input.go b/dataformat/input.go index 05adfd3..8cc6be5 100644 --- a/dataformat/input.go +++ b/dataformat/input.go @@ -61,8 +61,8 @@ func (c *ListChannelsChannel) AsSummaryEntry() *SummaryEntry { return &SummaryEntry{ RemotePubkey: c.RemotePubkey, ChannelPoint: c.ChannelPoint, - FundingTXID: fundingTXID(c.ChannelPoint), - FundingTXIndex: fundingTXIndex(c.ChannelPoint), + FundingTXID: FundingTXID(c.ChannelPoint), + FundingTXIndex: FundingTXIndex(c.ChannelPoint), Capacity: uint64(c.Capacity), Initiator: c.Initiator, LocalBalance: uint64(c.LocalBalance), @@ -115,8 +115,8 @@ func (c *PendingChannelsChannel) AsSummaryEntry() *SummaryEntry { return &SummaryEntry{ RemotePubkey: c.Channel.RemotePubkey, ChannelPoint: c.Channel.ChannelPoint, - FundingTXID: fundingTXID(c.Channel.ChannelPoint), - FundingTXIndex: fundingTXIndex(c.Channel.ChannelPoint), + FundingTXID: FundingTXID(c.Channel.ChannelPoint), + FundingTXIndex: FundingTXIndex(c.Channel.ChannelPoint), Capacity: uint64(c.Channel.Capacity), Initiator: false, LocalBalance: uint64(c.Channel.LocalBalance), @@ -159,7 +159,7 @@ func (f *SummaryEntryFile) AsSummaryEntries() ([]*SummaryEntry, error) { return f.Channels, nil } -func fundingTXID(chanPoint string) string { +func FundingTXID(chanPoint string) string { parts := strings.Split(chanPoint, ":") if len(parts) != 2 { panic(fmt.Errorf("channel point not in format :: %s", @@ -168,7 +168,7 @@ func fundingTXID(chanPoint string) string { return parts[0] } -func fundingTXIndex(chanPoint string) uint32 { +func FundingTXIndex(chanPoint string) uint32 { parts := strings.Split(chanPoint, ":") if len(parts) != 2 { panic(fmt.Errorf("channel point %s not in format :",