mirror of
https://github.com/guggero/chantools
synced 2024-11-07 03:20:43 +00:00
Add dumpbackup command
This commit is contained in:
parent
ffa06a0598
commit
8f2c8db310
55
README.md
55
README.md
@ -11,6 +11,15 @@ you use it for anything serious.
|
|||||||
**WARNING 2**: This tool will query public block explorer APIs, your privacy
|
**WARNING 2**: This tool will query public block explorer APIs, your privacy
|
||||||
might not be preserved. Use at your own risk.
|
might not be preserved. Use at your own risk.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
To install this tool, make sure you have `go 1.13.x` (or later) and `make`
|
||||||
|
installed and run the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@ -18,6 +27,7 @@ Usage:
|
|||||||
chantools [OPTIONS] <command>
|
chantools [OPTIONS] <command>
|
||||||
|
|
||||||
Application Options:
|
Application Options:
|
||||||
|
--testnet Set to true if testnet parameters should be used.
|
||||||
--apiurl= API URL to use (must be esplora compatible). (default: https://blockstream.info/api)
|
--apiurl= API URL to use (must be esplora compatible). (default: https://blockstream.info/api)
|
||||||
--listchannels= The channel input is in the format of lncli's listchannels format. Specify '-' to read from stdin.
|
--listchannels= The channel input is in the format of lncli's listchannels format. Specify '-' to read from stdin.
|
||||||
--pendingchannels= The channel input is in the format of lncli's pendingchannels format. Specify '-' to read from stdin.
|
--pendingchannels= The channel input is in the format of lncli's pendingchannels format. Specify '-' to read from stdin.
|
||||||
@ -28,11 +38,13 @@ Help Options:
|
|||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
|
|
||||||
Available commands:
|
Available commands:
|
||||||
dumpchannels Dump all channel information from lnd's channel database
|
dumpbackup Dump the content of a channel.backup file.
|
||||||
forceclose Force-close the last state that is in the channel.db provided
|
dumpchannels Dump all channel information from lnd's channel database.
|
||||||
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels
|
forceclose Force-close the last state that is in the channel.db provided.
|
||||||
summary Compile a summary about the current state of channels
|
rescueclosed Try finding the private keys for funds that are in outputs of remotely force-closed channels.
|
||||||
sweeptimelock Sweep the force-closed state after the time lock has expired
|
showrootkey Extract and show the BIP32 HD root key from the 24 word lnd aezeed.
|
||||||
|
summary Compile a summary about the current state of channels.
|
||||||
|
sweeptimelock Sweep the force-closed state after the time lock has expired.
|
||||||
```
|
```
|
||||||
|
|
||||||
## summary command
|
## summary command
|
||||||
@ -168,3 +180,36 @@ Example command:
|
|||||||
```bash
|
```bash
|
||||||
chantools dumpchannels --channeldb ~/.lnd/data/graph/mainnet/channel.db
|
chantools dumpchannels --channeldb ~/.lnd/data/graph/mainnet/channel.db
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## showrootkey command
|
||||||
|
|
||||||
|
This command converts the 24 word lnd aezeed phrase and password to the BIP32
|
||||||
|
HD root key that is used as the `rootkey` parameter in other commands of this
|
||||||
|
tool.
|
||||||
|
|
||||||
|
Example command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chantools showrootkey
|
||||||
|
```
|
||||||
|
|
||||||
|
## dumpbackup command
|
||||||
|
|
||||||
|
```text
|
||||||
|
Usage:
|
||||||
|
chantools [OPTIONS] dumpbackup [dumpbackup-OPTIONS]
|
||||||
|
|
||||||
|
[dumpbackup command options]
|
||||||
|
--rootkey= BIP32 HD root key of the wallet that was used to create the backup.
|
||||||
|
--multi_file= The lnd channel.backup file to dump.
|
||||||
|
```
|
||||||
|
|
||||||
|
This command dumps all information that is inside a `channel.backup` file in a
|
||||||
|
human readable format.
|
||||||
|
|
||||||
|
Example command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chantools dumpbackup --rootkey xprvxxxxxxxxxx \
|
||||||
|
--multi_file ~/.lnd/data/chain/bitcoin/mainnet/channel.backup
|
||||||
|
```
|
58
cmd_dumpbackup.go
Normal file
58
cmd_dumpbackup.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package chantools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/lightningnetwork/lnd/chanbackup"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dumpMulti struct {
|
||||||
|
Version chanbackup.MultiBackupVersion
|
||||||
|
StaticBackups []dumpSingle
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSingle is the information we want to dump from an lnd channel backup.
|
||||||
|
// See `chanbackup.Single` for information about the fields.
|
||||||
|
type dumpSingle struct {
|
||||||
|
Version chanbackup.SingleBackupVersion
|
||||||
|
IsInitiator bool
|
||||||
|
ChainHash string
|
||||||
|
FundingOutpoint string
|
||||||
|
ShortChannelID lnwire.ShortChannelID
|
||||||
|
RemoteNodePub string
|
||||||
|
Addresses []net.Addr
|
||||||
|
Capacity btcutil.Amount
|
||||||
|
LocalChanCfg dumpChanCfg
|
||||||
|
RemoteChanCfg dumpChanCfg
|
||||||
|
ShaChainRootDesc dumpDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpChannelBackup(multi *chanbackup.Multi) error {
|
||||||
|
dumpSingles := make([]dumpSingle, len(multi.StaticBackups))
|
||||||
|
for idx, single := range multi.StaticBackups {
|
||||||
|
dumpSingles[idx] = dumpSingle{
|
||||||
|
Version: single.Version,
|
||||||
|
IsInitiator: single.IsInitiator,
|
||||||
|
ChainHash: single.ChainHash.String(),
|
||||||
|
FundingOutpoint: single.FundingOutpoint.String(),
|
||||||
|
ShortChannelID: single.ShortChannelID,
|
||||||
|
RemoteNodePub: pubKeyToString(single.RemoteNodePub),
|
||||||
|
Addresses: single.Addresses,
|
||||||
|
Capacity: single.Capacity,
|
||||||
|
LocalChanCfg: toDumpChanCfg(single.LocalChanCfg),
|
||||||
|
RemoteChanCfg: toDumpChanCfg(single.RemoteChanCfg),
|
||||||
|
ShaChainRootDesc: toDumpDescriptor(
|
||||||
|
single.ShaChainRootDesc,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spew.Dump(dumpMulti{
|
||||||
|
Version: multi.Version,
|
||||||
|
StaticBackups: dumpSingles,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,22 +3,15 @@ package chantools
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
lndInternalDerivationPath = "m/1017'/0'/%d'/0/%d"
|
|
||||||
)
|
|
||||||
|
|
||||||
// dumpInfo is the information we want to dump from an open channel in lnd's
|
// dumpInfo is the information we want to dump from an open channel in lnd's
|
||||||
// channel DB. See `channeldb.OpenChannel` for information about the fields.
|
// channel DB. See `channeldb.OpenChannel` for information about the fields.
|
||||||
type dumpInfo struct {
|
type dumpInfo struct {
|
||||||
@ -48,22 +41,6 @@ type dumpInfo struct {
|
|||||||
RemoteShutdownScript lnwire.DeliveryAddress
|
RemoteShutdownScript lnwire.DeliveryAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
// dumpChanCfg is the information we want to dump from a channel configuration.
|
|
||||||
// See `channeldb.ChannelConfig` for more information about the fields.
|
|
||||||
type dumpChanCfg struct {
|
|
||||||
channeldb.ChannelConstraints
|
|
||||||
MultiSigKey dumpDescriptor
|
|
||||||
RevocationBasePoint dumpDescriptor
|
|
||||||
PaymentBasePoint dumpDescriptor
|
|
||||||
DelayBasePoint dumpDescriptor
|
|
||||||
HtlcBasePoint dumpDescriptor
|
|
||||||
}
|
|
||||||
|
|
||||||
type dumpDescriptor struct {
|
|
||||||
Path string
|
|
||||||
Pubkey string
|
|
||||||
}
|
|
||||||
|
|
||||||
func dumpChannelInfo(chanDb *channeldb.DB) error {
|
func dumpChannelInfo(chanDb *channeldb.DB) error {
|
||||||
channels, err := chanDb.FetchAllChannels()
|
channels, err := chanDb.FetchAllChannels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -124,27 +101,3 @@ func dumpChannelInfo(chanDb *channeldb.DB) error {
|
|||||||
spew.Dump(dumpChannels)
|
spew.Dump(dumpChannels)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDumpChanCfg(cfg channeldb.ChannelConfig) dumpChanCfg {
|
|
||||||
return dumpChanCfg{
|
|
||||||
ChannelConstraints: cfg.ChannelConstraints,
|
|
||||||
MultiSigKey: toDumpDescriptor(cfg.MultiSigKey),
|
|
||||||
RevocationBasePoint: toDumpDescriptor(cfg.RevocationBasePoint),
|
|
||||||
PaymentBasePoint: toDumpDescriptor(cfg.PaymentBasePoint),
|
|
||||||
DelayBasePoint: toDumpDescriptor(cfg.DelayBasePoint),
|
|
||||||
HtlcBasePoint: toDumpDescriptor(cfg.HtlcBasePoint),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toDumpDescriptor(desc keychain.KeyDescriptor) dumpDescriptor {
|
|
||||||
return dumpDescriptor{
|
|
||||||
Path: fmt.Sprintf(
|
|
||||||
lndInternalDerivationPath, desc.Family, desc.Index,
|
|
||||||
),
|
|
||||||
Pubkey: pubKeyToString(desc.PubKey),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func pubKeyToString(pubkey *btcec.PublicKey) string {
|
|
||||||
return hex.EncodeToString(pubkey.SerializeCompressed())
|
|
||||||
}
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/btcutil/hdkeychain"
|
"github.com/btcsuite/btcutil/hdkeychain"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -18,8 +17,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
hardenedKeyStart = uint32(hdkeychain.HardenedKeyStart)
|
|
||||||
chainParams = &chaincfg.MainNetParams
|
|
||||||
cacheSize = 2000
|
cacheSize = 2000
|
||||||
cache []*cacheEntry
|
cache []*cacheEntry
|
||||||
|
|
||||||
@ -184,22 +181,6 @@ func fillCache(extendedKey *hdkeychain.ExtendedKey) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deriveChildren(key *hdkeychain.ExtendedKey, path []uint32) (
|
|
||||||
*hdkeychain.ExtendedKey, error) {
|
|
||||||
|
|
||||||
var (
|
|
||||||
currentKey = key
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for _, pathPart := range path {
|
|
||||||
currentKey, err = currentKey.Child(pathPart)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return currentKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAddr(addr string) ([]byte, error) {
|
func parseAddr(addr string) ([]byte, error) {
|
||||||
// First parse address to get targetPubKeyHash from it later.
|
// First parse address to get targetPubKeyHash from it later.
|
||||||
targetAddr, err := btcutil.DecodeAddress(addr, chainParams)
|
targetAddr, err := btcutil.DecodeAddress(addr, chainParams)
|
||||||
|
57
dump.go
Normal file
57
dump.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package chantools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lndInternalDerivationPath = "m/1017'/0'/%d'/0/%d"
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpChanCfg is the information we want to dump from a channel configuration.
|
||||||
|
// See `channeldb.ChannelConfig` for more information about the fields.
|
||||||
|
type dumpChanCfg struct {
|
||||||
|
channeldb.ChannelConstraints
|
||||||
|
MultiSigKey dumpDescriptor
|
||||||
|
RevocationBasePoint dumpDescriptor
|
||||||
|
PaymentBasePoint dumpDescriptor
|
||||||
|
DelayBasePoint dumpDescriptor
|
||||||
|
HtlcBasePoint dumpDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
type dumpDescriptor struct {
|
||||||
|
Path string
|
||||||
|
Pubkey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDumpChanCfg(cfg channeldb.ChannelConfig) dumpChanCfg {
|
||||||
|
return dumpChanCfg{
|
||||||
|
ChannelConstraints: cfg.ChannelConstraints,
|
||||||
|
MultiSigKey: toDumpDescriptor(cfg.MultiSigKey),
|
||||||
|
RevocationBasePoint: toDumpDescriptor(cfg.RevocationBasePoint),
|
||||||
|
PaymentBasePoint: toDumpDescriptor(cfg.PaymentBasePoint),
|
||||||
|
DelayBasePoint: toDumpDescriptor(cfg.DelayBasePoint),
|
||||||
|
HtlcBasePoint: toDumpDescriptor(cfg.HtlcBasePoint),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func toDumpDescriptor(desc keychain.KeyDescriptor) dumpDescriptor {
|
||||||
|
return dumpDescriptor{
|
||||||
|
Path: fmt.Sprintf(
|
||||||
|
lndInternalDerivationPath, desc.Family, desc.Index,
|
||||||
|
),
|
||||||
|
Pubkey: pubKeyToString(desc.PubKey),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubKeyToString(pubkey *btcec.PublicKey) string {
|
||||||
|
if pubkey == nil {
|
||||||
|
return "<nil>"
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(pubkey.SerializeCompressed())
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -21,7 +21,7 @@ require (
|
|||||||
github.com/urfave/cli/v2 v2.0.0 // indirect
|
github.com/urfave/cli/v2 v2.0.0 // indirect
|
||||||
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 // indirect
|
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 // indirect
|
||||||
gitlab.com/NebulousLabs/go-upnp v0.0.0-20181011194642-3a71999ed0d3 // indirect
|
gitlab.com/NebulousLabs/go-upnp v0.0.0-20181011194642-3a71999ed0d3 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 // indirect
|
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876
|
||||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
|
||||||
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 // indirect
|
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 // indirect
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect
|
||||||
|
66
hdkeychain.go
Normal file
66
hdkeychain.go
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
package chantools
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcutil/hdkeychain"
|
||||||
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hardenedKeyStart = uint32(hdkeychain.HardenedKeyStart)
|
||||||
|
)
|
||||||
|
|
||||||
|
func deriveChildren(key *hdkeychain.ExtendedKey, path []uint32) (
|
||||||
|
*hdkeychain.ExtendedKey, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
currentKey = key
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for _, pathPart := range path {
|
||||||
|
currentKey, err = currentKey.Child(pathPart)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return currentKey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type channelBackupEncryptionRing struct {
|
||||||
|
extendedKey *hdkeychain.ExtendedKey
|
||||||
|
chainParams *chaincfg.Params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *channelBackupEncryptionRing) DeriveNextKey(_ keychain.KeyFamily) (
|
||||||
|
keychain.KeyDescriptor, error) {
|
||||||
|
|
||||||
|
return keychain.KeyDescriptor{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *channelBackupEncryptionRing) DeriveKey(keyLoc keychain.KeyLocator) (
|
||||||
|
keychain.KeyDescriptor, error) {
|
||||||
|
|
||||||
|
var empty = keychain.KeyDescriptor{}
|
||||||
|
keyBackup, err := deriveChildren(r.extendedKey, []uint32{
|
||||||
|
hardenedKeyStart + uint32(keychain.BIP0043Purpose),
|
||||||
|
hardenedKeyStart + r.chainParams.HDCoinType,
|
||||||
|
hardenedKeyStart + uint32(keyLoc.Family),
|
||||||
|
0,
|
||||||
|
keyLoc.Index,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return empty, err
|
||||||
|
}
|
||||||
|
|
||||||
|
backupPubKey, err := keyBackup.ECPubKey()
|
||||||
|
if err != nil {
|
||||||
|
return empty, err
|
||||||
|
}
|
||||||
|
return keychain.KeyDescriptor{
|
||||||
|
KeyLocator: keychain.KeyLocator{
|
||||||
|
Family: keyLoc.Family,
|
||||||
|
Index: keyLoc.Index,
|
||||||
|
},
|
||||||
|
PubKey: backupPubKey,
|
||||||
|
}, nil
|
||||||
|
}
|
124
main.go
124
main.go
@ -1,13 +1,21 @@
|
|||||||
package chantools
|
package chantools
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcutil/hdkeychain"
|
"github.com/btcsuite/btcutil/hdkeychain"
|
||||||
"github.com/jessevdk/go-flags"
|
"github.com/jessevdk/go-flags"
|
||||||
|
"github.com/lightningnetwork/lnd/aezeed"
|
||||||
"github.com/lightningnetwork/lnd/build"
|
"github.com/lightningnetwork/lnd/build"
|
||||||
|
"github.com/lightningnetwork/lnd/chanbackup"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -15,6 +23,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
|
Testnet bool `long:"testnet" description:"Set to true if testnet parameters should be used."`
|
||||||
ApiUrl string `long:"apiurl" description:"API URL to use (must be esplora compatible)."`
|
ApiUrl string `long:"apiurl" description:"API URL to use (must be esplora compatible)."`
|
||||||
ListChannels string `long:"listchannels" description:"The channel input is in the format of lncli's listchannels format. Specify '-' to read from stdin."`
|
ListChannels string `long:"listchannels" description:"The channel input is in the format of lncli's listchannels format. Specify '-' to read from stdin."`
|
||||||
PendingChannels string `long:"pendingchannels" description:"The channel input is in the format of lncli's pendingchannels format. Specify '-' to read from stdin."`
|
PendingChannels string `long:"pendingchannels" description:"The channel input is in the format of lncli's pendingchannels format. Specify '-' to read from stdin."`
|
||||||
@ -28,6 +37,7 @@ var (
|
|||||||
cfg = &config{
|
cfg = &config{
|
||||||
ApiUrl: defaultApiUrl,
|
ApiUrl: defaultApiUrl,
|
||||||
}
|
}
|
||||||
|
chainParams = &chaincfg.MainNetParams
|
||||||
)
|
)
|
||||||
|
|
||||||
func Main() error {
|
func Main() error {
|
||||||
@ -37,27 +47,32 @@ func Main() error {
|
|||||||
parser := flags.NewParser(cfg, flags.Default)
|
parser := flags.NewParser(cfg, flags.Default)
|
||||||
_, _ = parser.AddCommand(
|
_, _ = parser.AddCommand(
|
||||||
"summary", "Compile a summary about the current state of "+
|
"summary", "Compile a summary about the current state of "+
|
||||||
"channels", "", &summaryCommand{},
|
"channels.", "", &summaryCommand{},
|
||||||
)
|
)
|
||||||
_, _ = parser.AddCommand(
|
_, _ = parser.AddCommand(
|
||||||
"rescueclosed", "Try finding the private keys for funds that "+
|
"rescueclosed", "Try finding the private keys for funds that "+
|
||||||
"are in outputs of remotely force-closed channels", "",
|
"are in outputs of remotely force-closed channels.", "",
|
||||||
&rescueClosedCommand{},
|
&rescueClosedCommand{},
|
||||||
)
|
)
|
||||||
_, _ = parser.AddCommand(
|
_, _ = parser.AddCommand(
|
||||||
"forceclose", "Force-close the last state that is in the "+
|
"forceclose", "Force-close the last state that is in the "+
|
||||||
"channel.db provided", "",
|
"channel.db provided.", "", &forceCloseCommand{},
|
||||||
&forceCloseCommand{},
|
|
||||||
)
|
)
|
||||||
_, _ = parser.AddCommand(
|
_, _ = parser.AddCommand(
|
||||||
"sweeptimelock", "Sweep the force-closed state after the time "+
|
"sweeptimelock", "Sweep the force-closed state after the time "+
|
||||||
"lock has expired", "",
|
"lock has expired.", "", &sweepTimeLockCommand{},
|
||||||
&sweepTimeLockCommand{},
|
|
||||||
)
|
)
|
||||||
_, _ = parser.AddCommand(
|
_, _ = parser.AddCommand(
|
||||||
"dumpchannels", "Dump all channel information from lnd's "+
|
"dumpchannels", "Dump all channel information from lnd's "+
|
||||||
"channel database", "",
|
"channel database.", "", &dumpChannelsCommand{},
|
||||||
&dumpChannelsCommand{},
|
)
|
||||||
|
_, _ = parser.AddCommand(
|
||||||
|
"showrootkey", "Extract and show the BIP32 HD root key from "+
|
||||||
|
"the 24 word lnd aezeed.", "", &showRootKeyCommand{},
|
||||||
|
)
|
||||||
|
_, _ = parser.AddCommand(
|
||||||
|
"dumpbackup", "Dump the content of a channel.backup file.", "",
|
||||||
|
&dumpBackupCommand{},
|
||||||
)
|
)
|
||||||
|
|
||||||
_, err := parser.Parse()
|
_, err := parser.Parse()
|
||||||
@ -184,7 +199,7 @@ type dumpChannelsCommand struct {
|
|||||||
func (c *dumpChannelsCommand) Execute(_ []string) error {
|
func (c *dumpChannelsCommand) Execute(_ []string) error {
|
||||||
// Check that we have a channel DB.
|
// Check that we have a channel DB.
|
||||||
if c.ChannelDB == "" {
|
if c.ChannelDB == "" {
|
||||||
return fmt.Errorf("rescue DB is required")
|
return fmt.Errorf("channel DB is required")
|
||||||
}
|
}
|
||||||
db, err := channeldb.Open(path.Dir(c.ChannelDB))
|
db, err := channeldb.Open(path.Dir(c.ChannelDB))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -193,6 +208,97 @@ func (c *dumpChannelsCommand) Execute(_ []string) error {
|
|||||||
return dumpChannelInfo(db)
|
return dumpChannelInfo(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type showRootKeyCommand struct{}
|
||||||
|
|
||||||
|
func (c *showRootKeyCommand) Execute(_ []string) error {
|
||||||
|
// We'll now prompt the user to enter in their 24-word mnemonic.
|
||||||
|
fmt.Printf("Input your 24-word mnemonic separated by spaces: ")
|
||||||
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
mnemonicStr, err := reader.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We'll trim off extra spaces, and ensure the mnemonic is all
|
||||||
|
// lower case, then populate our request.
|
||||||
|
mnemonicStr = strings.TrimSpace(mnemonicStr)
|
||||||
|
mnemonicStr = strings.ToLower(mnemonicStr)
|
||||||
|
|
||||||
|
cipherSeedMnemonic := strings.Split(mnemonicStr, " ")
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
if len(cipherSeedMnemonic) != 24 {
|
||||||
|
return fmt.Errorf("wrong cipher seed mnemonic "+
|
||||||
|
"length: got %v words, expecting %v words",
|
||||||
|
len(cipherSeedMnemonic), 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additionally, the user may have a passphrase, that will also
|
||||||
|
// need to be provided so the daemon can properly decipher the
|
||||||
|
// cipher seed.
|
||||||
|
fmt.Printf("Input your cipher seed passphrase (press enter if " +
|
||||||
|
"your seed doesn't have a passphrase): ")
|
||||||
|
passphrase, err := terminal.ReadPassword(syscall.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var mnemonic aezeed.Mnemonic
|
||||||
|
copy(mnemonic[:], cipherSeedMnemonic[:])
|
||||||
|
|
||||||
|
// If we're unable to map it back into the ciphertext, then either the
|
||||||
|
// mnemonic is wrong, or the passphrase is wrong.
|
||||||
|
cipherSeed, err := mnemonic.ToCipherSeed(passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rootKey, err := hdkeychain.NewMaster(cipherSeed.Entropy[:], chainParams)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to derive master extended key")
|
||||||
|
}
|
||||||
|
fmt.Printf("\nYour BIP32 HD root key is: %s\n", rootKey.String())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dumpBackupCommand struct {
|
||||||
|
RootKey string `long:"rootkey" description:"BIP32 HD root key of the wallet that was used to create the backup."`
|
||||||
|
MultiFile string `long:"multi_file" description:"The lnd channel.backup file to dump."`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *dumpBackupCommand) Execute(_ []string) error {
|
||||||
|
setupChainParams(cfg)
|
||||||
|
|
||||||
|
// Check that root key is valid.
|
||||||
|
if c.RootKey == "" {
|
||||||
|
return fmt.Errorf("root key is required")
|
||||||
|
}
|
||||||
|
extendedKey, err := hdkeychain.NewKeyFromString(c.RootKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error parsing 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)
|
||||||
|
multi, err := multiFile.ExtractMulti(&channelBackupEncryptionRing{
|
||||||
|
extendedKey: extendedKey,
|
||||||
|
chainParams: chainParams,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not extract multi file: %v", err)
|
||||||
|
}
|
||||||
|
return dumpChannelBackup(multi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupChainParams(cfg *config) {
|
||||||
|
if cfg.Testnet {
|
||||||
|
chainParams = &chaincfg.TestNet3Params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setupLogging() {
|
func setupLogging() {
|
||||||
logWriter.RegisterSubLogger("CHAN", log)
|
logWriter.RegisterSubLogger("CHAN", log)
|
||||||
err := logWriter.InitLogRotator("./results/chantools.log", 10, 3)
|
err := logWriter.InitLogRotator("./results/chantools.log", 10, 3)
|
||||||
|
Loading…
Reference in New Issue
Block a user