Add summary and rescueclosed commands

pull/3/head
Oliver Gugger 5 years ago
parent 2774bcee41
commit 6949457997
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

3
.gitignore vendored

@ -1 +1,2 @@
/chansummary
/chantools
results

@ -1,4 +1,4 @@
PKG := github.com/guggero/chansummary
PKG := github.com/guggero/chantools
GOTEST := GO111MODULE=on go test -v
@ -33,12 +33,12 @@ unit:
$(UNIT)
build:
@$(call print, "Building chansummary.")
$(GOBUILD) $(PKG)/cmd/chansummary
@$(call print, "Building chantools.")
$(GOBUILD) $(PKG)/cmd/chantools
install:
@$(call print, "Installing chansummary.")
$(GOINSTALL) $(PKG)/cmd/chansummary
@$(call print, "Installing chantools.")
$(GOINSTALL) $(PKG)/cmd/chantools
fmt:
@$(call print, "Formatting source.")

@ -1,4 +1,4 @@
# Channel summary
# Channel tools
This tool works with the output of lnd's `listchannels` command and creates
a summary of the on-chain state of these channels.

@ -1,12 +1,17 @@
package chansummary
package chantools
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
)
var (
ErrTxNotFound = errors.New("transaction not found")
)
type chainApi struct {
baseUrl string
}
@ -17,17 +22,18 @@ type transaction struct {
}
type vin struct {
Tixid string `json:"txid"`
Vout int `json:"vout"`
Prevout *vout `json:"prevout"`
Tixid string `json:"txid"`
Vout int `json:"vout"`
Prevout *vout `json:"prevout"`
Sequence uint32 `json:"sequence"`
}
type vout struct {
ScriptPubkey string `json:"scriptpubkey"`
ScriptPubkeyAsm string `json:"scriptpubkey_asm"`
ScriptPubkeyType string `json:"scriptpubkey_type"`
ScriptPubkeyAddr string `json:"scriptpubkey_addr"`
Value uint64 `json:"value"`
ScriptPubkeyAddr string `json:"scriptpubkey_address"`
Value uint64 `json:"value"`
outspend *outspend
}
@ -76,5 +82,11 @@ func Fetch(url string, target interface{}) error {
if err != nil {
return err
}
return json.Unmarshal(body.Bytes(), target)
err = json.Unmarshal(body.Bytes(), target)
if err != nil {
if string(body.Bytes()) == "Transaction not found" {
return ErrTxNotFound
}
}
return err
}

@ -0,0 +1,236 @@
package chantools
import (
"crypto/subtle"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
var (
hardenedKeyStart = uint32(hdkeychain.HardenedKeyStart)
chainParams = &chaincfg.MainNetParams
cacheSize = 2000
cache []*cacheEntry
errAddrNotFound = errors.New("addr not found")
)
type cacheEntry struct {
privKey *btcec.PrivateKey
pubKey *btcec.PublicKey
}
func bruteForceChannels(cfg *config, entries []*SummaryEntry,
chanDb *channeldb.DB) error {
err := fillCache(cfg.RootKey)
if err != nil {
return err
}
channels, err := chanDb.FetchAllChannels()
if err != nil {
return err
}
// Try naive/lucky guess with information from channel DB.
for _, channel := range channels {
channelPoint := channel.FundingOutpoint.String()
var channelEntry *SummaryEntry
for _, entry := range entries {
if entry.ChannelPoint == channelPoint {
channelEntry = entry
}
}
// Don't try anything with open channels, fully closed channels
// or channels where we already have the private key.
if channelEntry == nil || channelEntry.ClosingTX == nil ||
channelEntry.ClosingTX.AllOutsSpent ||
channelEntry.ClosingTX.OurAddr == "" ||
channelEntry.ClosingTX.SweepPrivkey != "" {
continue
}
if channel.RemoteNextRevocation != nil {
wif, err := addrInCache(
channelEntry.ClosingTX.OurAddr,
channel.RemoteNextRevocation,
)
switch {
case err == nil:
channelEntry.ClosingTX.SweepPrivkey = wif
case err == errAddrNotFound:
default:
return err
}
}
if channel.RemoteCurrentRevocation != nil {
wif, err := addrInCache(
channelEntry.ClosingTX.OurAddr,
channel.RemoteCurrentRevocation,
)
switch {
case err == nil:
channelEntry.ClosingTX.SweepPrivkey = wif
case err == errAddrNotFound:
default:
return err
}
}
}
summaryBytes, err := json.MarshalIndent(&SummaryEntryFile{
Channels: entries,
}, "", " ")
if err != nil {
return err
}
fileName := fmt.Sprintf("results/bruteforce-%s.json",
time.Now().Format("2006-01-02-15-04-05"))
log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, summaryBytes, 0644)
}
func addrInCache(addr string, perCommitPoint *btcec.PublicKey) (string, error) {
// First parse address to get targetPubKeyHash from it later.
targetAddr, err := btcutil.DecodeAddress(addr, chainParams)
if err != nil {
return "", err
}
var targetPubKeyHash []byte
// Make the check on the decoded address according to the active
// network (testnet or mainnet only).
if !targetAddr.IsForNet(chainParams) {
return "", fmt.Errorf(
"address: %v is not valid for this network: %v",
targetAddr.String(), chainParams.Name,
)
}
// Must be a bech32 native SegWit address.
switch targetAddr.(type) {
case *btcutil.AddressWitnessPubKeyHash:
targetPubKeyHash = targetAddr.ScriptAddress()
default:
return "", fmt.Errorf("address: must be a bech32 P2WPKH address")
}
// Loop through all cached payment base point keys, tweak each of it
// with the per_commit_point and see if the hashed public key
// corresponds to the target pubKeyHash of the given address.
for i := 0; i < cacheSize; i++ {
cacheEntry := cache[i]
basePoint := cacheEntry.pubKey
tweakedPubKey := input.TweakPubKey(basePoint, perCommitPoint)
tweakBytes := input.SingleTweakBytes(perCommitPoint, basePoint)
tweakedPrivKey := input.TweakPrivKey(
cacheEntry.privKey, tweakBytes,
)
hashedPubKey := btcutil.Hash160(
tweakedPubKey.SerializeCompressed(),
)
equal := subtle.ConstantTimeCompare(
targetPubKeyHash[:], hashedPubKey[:],
)
if equal == 1 {
wif, err := btcutil.NewWIF(
tweakedPrivKey, chainParams, true,
)
if err != nil {
return "", err
}
log.Infof("The private key for addr %s found after "+
"%d tries: %s", addr, i, wif.String(),
)
return wif.String(), nil
}
}
return "", errAddrNotFound
}
func fillCache(rootKey string) error {
extendedKey, err := hdkeychain.NewKeyFromString(rootKey)
if err != nil {
return err
}
cache = make([]*cacheEntry, cacheSize)
for i := 0; i < cacheSize; i++ {
key, err := deriveChildren(extendedKey, []uint32{
hardenedKeyStart + uint32(keychain.BIP0043Purpose),
hardenedKeyStart + chainParams.HDCoinType,
hardenedKeyStart + uint32(keychain.KeyFamilyPaymentBase),
0,
uint32(i),
})
if err != nil {
return err
}
privKey, err := key.ECPrivKey()
if err != nil {
return err
}
pubKey, err := key.ECPubKey()
if err != nil {
return err
}
cache[i] = &cacheEntry{
privKey: privKey,
pubKey: pubKey,
}
if i > 0 && i%10000 == 0 {
fmt.Printf("Filled cache with %d of %d keys.\n",
i, cacheSize)
}
}
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 addrFromDesc(desc *keychain.KeyDescriptor) (string, error) {
hash160 := btcutil.Hash160(desc.PubKey.SerializeCompressed())
addr, err := btcutil.NewAddressWitnessPubKeyHash(
hash160, chainParams,
)
if err != nil {
return "", err
}
return addr.String(), nil
}

@ -1,108 +1,117 @@
package chansummary
package chantools
import (
"encoding/json"
"fmt"
"strconv"
"strings"
"io/ioutil"
"time"
)
type channel struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
Capacity string `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance string `json:"local_balance"`
RemoteBalance string `json:"remote_balance"`
}
func (c *channel) FundingTXID() string {
parts := strings.Split(c.ChannelPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>"))
func collectChanSummary(cfg *config, channels []*SummaryEntry) error {
summaryFile := &SummaryEntryFile{
Channels: channels,
}
return parts[0]
}
func (c *channel) FundingTXIndex() int {
parts := strings.Split(c.ChannelPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>"))
}
return parseInt(parts[1])
}
func (c *channel) localBalance() uint64 {
return uint64(parseInt(c.LocalBalance))
}
func (c *channel) remoteBalance() uint64 {
return uint64(parseInt(c.RemoteBalance))
}
func collectChanSummary(cfg *config, channels []*channel) error {
var (
chansClosed = 0
chansOpen = 0
valueUnspent = uint64(0)
valueSalvage = uint64(0)
valueSafe = uint64(0)
)
chainApi := &chainApi{baseUrl: cfg.ApiUrl}
for idx, channel := range channels {
tx, err := chainApi.Transaction(channel.FundingTXID())
tx, err := chainApi.Transaction(channel.FundingTXID)
if err == ErrTxNotFound {
log.Errorf("Funding TX %s not found. Ignoring.",
channel.FundingTXID)
continue
}
if err != nil {
log.Errorf("Problem with channel %d (%s): %v.",
idx, channel.FundingTXID, err)
return err
}
outspend := tx.Vout[channel.FundingTXIndex()].outspend
outspend := tx.Vout[channel.FundingTXIndex].outspend
if outspend.Spent {
chansClosed++
summaryFile.ClosedChannels++
channel.ClosingTX = &ClosingTX{
TXID: outspend.Txid,
}
s, f, err := reportOutspend(chainApi, channel, outspend)
err := reportOutspend(
chainApi, summaryFile, channel, outspend,
)
if err != nil {
log.Errorf("Problem with channel %d (%s): %v.",
idx, channel.FundingTXID, err)
return err
}
valueSalvage += s
valueSafe += f
} else {
chansOpen++
valueUnspent += channel.localBalance()
summaryFile.OpenChannels++
summaryFile.FundsOpenChannels += channel.LocalBalance
channel.ClosingTX = nil
}
if idx%50 == 0 {
fmt.Printf("Queried channel %d of %d.\n", idx,
log.Infof("Queried channel %d of %d.", idx,
len(channels))
}
}
fmt.Printf("Finished scanning.\nClosed channels: %d\nOpen channels: "+
"%d\nSats in open channels: %d\nSats that can possibly be "+
"salvaged: %d\nSats in co-op close channels: %d\n", chansClosed,
chansOpen, valueUnspent, valueSalvage, valueSafe)
return nil
log.Info("Finished scanning.")
log.Infof("Open channels: %d", summaryFile.OpenChannels)
log.Infof("Sats in open channels: %d", summaryFile.FundsOpenChannels)
log.Infof("Closed channels: %d", summaryFile.ClosedChannels)
log.Infof(" --> force closed channels: %d",
summaryFile.ForceClosedChannels)
log.Infof(" --> coop closed channels: %d",
summaryFile.CoopClosedChannels)
log.Infof(" --> closed channels with all outputs spent: %d",
summaryFile.FullySpentChannels)
log.Infof(" --> closed channels with unspent outputs: %d",
summaryFile.ChannelsWithPotential)
log.Infof("Sats in closed channels: %d", summaryFile.FundsClosedChannels)
log.Infof(" --> closed channel sats that have been swept/spent: %d",
summaryFile.FundsClosedSpent)
log.Infof(" --> closed channel sats that are in force-close outputs: %d",
summaryFile.FundsForceClose)
log.Infof(" --> closed channel sats that are in coop close outputs: %d",
summaryFile.FundsCoopClose)
summaryBytes, err := json.MarshalIndent(summaryFile, "", " ")
if err != nil {
return err
}
fileName := fmt.Sprintf("results/summary-%s.json",
time.Now().Format("2006-01-02-15-04-05"))
log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, summaryBytes, 0644)
}
func reportOutspend(api *chainApi, ch *channel, os *outspend) (uint64, uint64,
error) {
func reportOutspend(api *chainApi, summaryFile *SummaryEntryFile,
entry *SummaryEntry, os *outspend) error {
spendTx, err := api.Transaction(os.Txid)
if err != nil {
return 0, 0, err
return err
}
summaryFile.FundsClosedChannels += entry.LocalBalance
if isCoopClose(spendTx) {
summaryFile.CoopClosedChannels++
summaryFile.FundsCoopClose += entry.LocalBalance
entry.ClosingTX.ForceClose = false
return nil
}
summaryFile.ForceClosedChannels++
entry.ClosingTX.ForceClose = true
numSpent := 0
salvageBalance := uint64(0)
safeBalance := uint64(0)
for _, vout := range spendTx.Vout {
if vout.outspend.Spent {
numSpent++
}
}
if numSpent != len(spendTx.Vout) {
fmt.Printf("Channel %s spent by %s:%d which has %d outputs of "+
"which %d are spent:\n", ch.ChannelPoint, os.Txid,
log.Debugf("Channel %s spent by %s:%d which has %d outputs of "+
"which %d are spent.", entry.ChannelPoint, os.Txid,
os.Vin, len(spendTx.Vout), numSpent)
var utxo []*vout
for _, vout := range spendTx.Vout {
@ -110,9 +119,11 @@ func reportOutspend(api *chainApi, ch *channel, os *outspend) (uint64, uint64,
utxo = append(utxo, vout)
}
}
entry.ClosingTX.AllOutsSpent = false
summaryFile.ChannelsWithPotential++
if salvageable(ch, utxo) {
salvageBalance += utxo[0].Value
if couldBeOurs(entry, utxo) {
summaryFile.FundsForceClose += utxo[0].Value
outs := spendTx.Vout
@ -121,42 +132,34 @@ func reportOutspend(api *chainApi, ch *channel, os *outspend) (uint64, uint64,
outs[0].ScriptPubkeyType == "v0_p2wpkh" &&
outs[0].outspend.Spent == false:
safeBalance += utxo[0].Value
case len(outs) == 2 &&
outs[0].ScriptPubkeyType == "v0_p2wpkh" &&
outs[1].ScriptPubkeyType == "v0_p2wpkh":
safeBalance += utxo[0].Value
entry.ClosingTX.OurAddr = outs[0].ScriptPubkeyAddr
}
} else {
for idx, vout := range spendTx.Vout {
if !vout.outspend.Spent {
fmt.Printf("UTXO %d of type %s with "+
"value %d\n", idx,
log.Debugf("UTXO %d of type %s with "+
"value %d", idx,
vout.ScriptPubkeyType,
vout.Value)
}
}
fmt.Printf("Local balance: %s\n", ch.LocalBalance)
fmt.Printf("Remote balance: %s\n", ch.RemoteBalance)
fmt.Printf("Initiator: %v\n", ch.Initiator)
log.Debugf("Local balance: %d", entry.LocalBalance)
log.Debugf("Remote balance: %d", entry.RemoteBalance)
log.Debugf("Initiator: %v", entry.Initiator)
}
} else {
entry.ClosingTX.AllOutsSpent = true
summaryFile.FundsClosedSpent += entry.LocalBalance
summaryFile.FullySpentChannels++
}
return salvageBalance, safeBalance, nil
return nil
}
func salvageable(ch *channel, utxo []*vout) bool {
return ch.localBalance() == utxo[0].Value ||
ch.remoteBalance() == 0
func couldBeOurs(entry *SummaryEntry, utxo []*vout) bool {
return utxo[0].ScriptPubkeyType == "v0_p2wpkh" && entry.LocalBalance != 0
}
func parseInt(str string) int {
index, err := strconv.Atoi(str)
if err != nil {
panic(fmt.Errorf("error parsing '%s' as int: %v", str, err))
}
return index
func isCoopClose(tx *transaction) bool {
return tx.Vin[0].Sequence == 0xffffffff
}

@ -1,16 +0,0 @@
package main
import (
"fmt"
"os"
"github.com/guggero/chansummary"
)
func main() {
if err := chansummary.Main(); err != nil {
fmt.Printf("Error running chansummary: %v\n", err)
}
os.Exit(0)
}

@ -0,0 +1,16 @@
package main
import (
"fmt"
"os"
"github.com/guggero/chantools"
)
func main() {
if err := chantools.Main(); err != nil {
fmt.Printf("Error running chantools: %v\n", err)
}
os.Exit(0)
}

@ -0,0 +1,36 @@
package chantools
type ClosingTX struct {
TXID string `json:"txid"`
ForceClose bool `json:"force_close"`
AllOutsSpent bool `json:"all_outputs_spent"`
OurAddr string `json:"our_addr"`
SweepPrivkey string `json:"sweep_privkey"`
}
type SummaryEntry struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
FundingTXID string `json:"funding_txid"`
FundingTXIndex uint32 `json:"funding_tx_index"`
Capacity uint64 `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalance uint64 `json:"local_balance"`
RemoteBalance uint64 `json:"remote_balance"`
ClosingTX *ClosingTX `json:"closing_tx,omitempty"`
}
type SummaryEntryFile struct {
Channels []*SummaryEntry `json:"channels"`
OpenChannels uint32 `json:"open_channels"`
ClosedChannels uint32 `json:"closed_channels"`
ForceClosedChannels uint32 `json:"force_closed_channels"`
CoopClosedChannels uint32 `json:"coop_closed_channels"`
FullySpentChannels uint32 `json:"fully_spent_channels"`
ChannelsWithPotential uint32 `json:"channels_with_potential_funds"`
FundsOpenChannels uint64 `json:"funds_open_channels"`
FundsClosedChannels uint64 `json:"funds_closed_channels"`
FundsClosedSpent uint64 `json:"funds_closed_channels_spent"`
FundsForceClose uint64 `json:"funds_force_closed_maybe_ours"`
FundsCoopClose uint64 `json:"funds_coop_closed_maybe_ours"`
}

@ -1,5 +1,15 @@
module github.com/guggero/chansummary
module github.com/guggero/chantools
require github.com/jessevdk/go-flags v1.4.0
require (
github.com/btcsuite/btcd v0.20.1-beta
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/davecgh/go-spew v1.1.1
github.com/jessevdk/go-flags v1.4.0
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191025122959-1a0ab538d53c
github.com/prometheus/common v0.4.0
github.com/urfave/cli/v2 v2.0.0 // indirect
golang.org/x/crypto v0.0.0-20191111213947-16651526fdb4 // indirect
)
go 1.13

211
go.sum

@ -1,2 +1,213 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.schwanenlied.me/yawning/bsaes.git v0.0.0-20180720073208-c0276d75487e/go.mod h1:BWqTsj8PgcPriQJGl7el20J/7TuT1d/hSyFDXMEpoEo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e/go.mod h1:Bdzq+51GR4/0DIhaICZEOm+OHvXGwwB2trKZ8B4Y6eQ=
github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82/go.mod h1:GbuBk21JqF+driLX3XtJYNZjGa45YDoa9IqCTzNSfEc=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Yawning/aez v0.0.0-20180114000226-4dad034d9db2/go.mod h1:9pIqrY6SXNL8vjRQE5Hd/OL5GyK/9MrGUWs87z/eFfk=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.0-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.10.0 h1:fFZncfYJ7VByePTGttzJc3qfCyDzU95ucZYk0M912lU=
github.com/btcsuite/btcwallet v0.10.0/go.mod h1:4TqBEuceheGNdeLNrelliLHJzmXauMM2vtWfuy1pFiM=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0/go.mod h1:UwQE78yCerZ313EXZwEiu3jNAtfXj2n2+c8RWiE/WNA=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf40SmmMkSQkoE8vCT+s=
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.1.0 h1:JHAL7wZ8pX4SULabeAv/wPO9sseRWMGzE80lfVmRw6Y=
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/goleveldb v1.0.0 h1:Tvd0BfvqX9o823q1j2UZ/epQo09eJh6dTcRp79ilIN4=
github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/snappy-go v1.0.0 h1:ZxaA6lo2EpxGddsA8JwWOcxlzRybb444sgmeJQMJGQE=
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/frankban/quicktest v1.2.2/go.mod h1:Qh/WofXFeiAFII1aEBu529AtJo6Zg2VHscnEsbBnJ20=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v0.0.0-20170724004829-f2862b476edc/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/retry v0.0.0-20180821225755-9058e192b216/go.mod h1:OohPQGsr4pnxwD5YljhQ+TZnuVRYpa5irjugL1Yuif4=
github.com/juju/testing v0.0.0-20190723135506-ce30eb24acd2/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/juju/utils v0.0.0-20180820210520-bf9cc5bdd62d/go.mod h1:6/KLg8Wz/y2KVGWEpkK9vMNGkOnu4k/cqs8Z1fKjTOk=
github.com/juju/version v0.0.0-20180108022336-b64dbd566305/go.mod h1:kE8gK5X0CImdr7qpSKl3xB2PmpySSmfj7zVbkZFs81U=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs=
github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d h1:tt8hwvxl6fksSfchjBGaWu+pnWJQfG1OWiCM20qOSAE=
github.com/lightninglabs/gozmq v0.0.0-20190710231225-cea2a031735d/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/neutrino v0.10.0 h1:yWVy2cOCCXbKFdpYCE9vD1fWRJDd9FtGXhUws4l9RkU=
github.com/lightninglabs/neutrino v0.10.0/go.mod h1:C3KhCMk1Mcx3j8v0qRVWM1Ow6rIJSvSPnUAq00ZNAfk=
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a h1:GoWPN4i4jTKRxhVNh9a2vvBBO1Y2seiJB+SopUYoKyo=
github.com/lightningnetwork/lightning-onion v0.0.0-20190909101754-850081b08b6a/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191025122959-1a0ab538d53c h1:eZcbiUop12hTTVIjicfm85do4kftmJqAwGVWYPh6+Xo=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20191025122959-1a0ab538d53c/go.mod h1:nq06y2BDv7vwWeMmwgB7P3pT7/Uj7sGf5FzHISVD6t4=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
github.com/urfave/cli v1.18.0 h1:m9MfmZWX7bwr9kUcs/Asr95j0IVXzGNNc+/5ku2m26Q=
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli/v2 v2.0.0 h1:+HU9SCbu8GnEUFtIBfuUNXN39ofWViIEJIp6SURMpCg=
github.com/urfave/cli/v2 v2.0.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191111213947-16651526fdb4 h1:AGVXd+IAyeAb3FuQvYDYQ9+WR2JHm0+C0oYJaU1C4rs=
golang.org/x/crypto v0.0.0-20191111213947-16651526fdb4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v1 v1.0.1/go.mod h1:3NjfXwocQRYAPTq4/fzX+CwUhPRcR/azYRhj8G+LqMo=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/macaroon-bakery.v2 v2.0.1/go.mod h1:B4/T17l+ZWGwxFSZQmlBwp25x+og7OkhETfr3S9MbIA=
gopkg.in/macaroon.v2 v2.0.0/go.mod h1:+I6LnTMkm/uV5ew/0nsulNjL16SK4+C8yDmRUzHR17I=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

@ -0,0 +1,221 @@
package chantools
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/lightningnetwork/lnd/channeldb"
"io/ioutil"
"os"
"strconv"
"strings"
)
type InputFile interface {
AsSummaryEntries() ([]*SummaryEntry, error)
}
type Input interface {
AsSummaryEntry() *SummaryEntry
}
func ParseInput(cfg *config) ([]*SummaryEntry, error) {
var (
content []byte
err error
target InputFile
)
switch {
case cfg.ListChannels != "":
content, err = readInput(cfg.ListChannels)
target = &listChannelsFile{}
case cfg.PendingChannels != "":
content, err = readInput(cfg.PendingChannels)
target = &pendingChannelsFile{}
case cfg.FromSummary != "":
content, err = readInput(cfg.FromSummary)
target = &SummaryEntryFile{}
case cfg.FromChannelDB != "":
db, err := channeldb.Open(cfg.FromChannelDB)
if err != nil {
return nil, fmt.Errorf("error opening channel DB: %v",
err)
}
target = &channelDBFile{db: db}
return target.AsSummaryEntries()
default:
return nil, fmt.Errorf("an input file must be specified")
}
if err != nil {
return nil, err
}
decoder := json.NewDecoder(bytes.NewReader(content))
err = decoder.Decode(&target)
if err != nil {
return nil, err
}
return target.AsSummaryEntries()
}
func readInput(input string) ([]byte, error) {
if strings.TrimSpace(input) == "-" {
return ioutil.ReadAll(os.Stdin)
}
return ioutil.ReadFile(input)
}
type listChannelsFile struct {
Channels []*listChannelsChannel `json:"channels"`
}
func (f *listChannelsFile) AsSummaryEntries() ([]*SummaryEntry, error) {
result := make([]*SummaryEntry, len(f.Channels))
for idx, entry := range f.Channels {
result[idx] = entry.AsSummaryEntry()
}
return result, nil
}
type listChannelsChannel struct {
RemotePubkey string `json:"remote_pubkey"`
ChannelPoint string `json:"channel_point"`
CapacityStr string `json:"capacity"`
Initiator bool `json:"initiator"`
LocalBalanceStr string `json:"local_balance"`
RemoteBalanceStr string `json:"remote_balance"`
}
func (c *listChannelsChannel) AsSummaryEntry() *SummaryEntry {
return &SummaryEntry{
RemotePubkey: c.RemotePubkey,
ChannelPoint: c.ChannelPoint,
FundingTXID: fundingTXID(c.ChannelPoint),
FundingTXIndex: fundingTXIndex(c.ChannelPoint),
Capacity: uint64(parseInt(c.CapacityStr)),
Initiator: c.Initiator,
LocalBalance: uint64(parseInt(c.LocalBalanceStr)),
RemoteBalance: uint64(parseInt(c.RemoteBalanceStr)),
}
}
type pendingChannelsFile struct {
PendingOpen []*pendingChannelsChannel `json:"pending_open_channels"`
PendingClosing []*pendingChannelsChannel `json:"pending_closing_channels"`
PendingForceClosing []*pendingChannelsChannel `json:"pending_force_closing_channels"`
WaitingClose []*pendingChannelsChannel `json:"waiting_close_channels"`
}
func (f *pendingChannelsFile) AsSummaryEntries() ([]*SummaryEntry, error) {
numChannels := len(f.PendingOpen) + len(f.PendingClosing) +
len(f.PendingForceClosing) + len(f.WaitingClose)
result := make([]*SummaryEntry, numChannels)
idx := 0
for _, entry := range f.PendingOpen {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.PendingClosing {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.PendingForceClosing {
result[idx] = entry.AsSummaryEntry()
idx++
}
for _, entry := range f.WaitingClose {
result[idx] = entry.AsSummaryEntry()
idx++
}
return result, nil
}
type pendingChannelsChannel struct {
Channel struct {
RemotePubkey string `json:"remote_node_pub"`
ChannelPoint string `json:"channel_point"`
CapacityStr string `json:"capacity"`
LocalBalanceStr string `json:"local_balance"`
RemoteBalanceStr string `json:"remote_balance"`
} `json:"channel"`
}
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),
Capacity: uint64(parseInt(c.Channel.CapacityStr)),
Initiator: false,
LocalBalance: uint64(parseInt(c.Channel.LocalBalanceStr)),
RemoteBalance: uint64(parseInt(c.Channel.RemoteBalanceStr)),
}
}
type channelDBFile struct {
db *channeldb.DB
}
func (c *channelDBFile) AsSummaryEntries() ([]*SummaryEntry, error) {
channels, err := c.db.FetchAllChannels()
if err != nil {
return nil, fmt.Errorf("error fetching channels: %v", err)
}
result := make([]*SummaryEntry, len(channels))
for idx, channel := range channels {
result[idx] = &SummaryEntry{
RemotePubkey: hex.EncodeToString(
channel.IdentityPub.SerializeCompressed(),
),
ChannelPoint: channel.FundingOutpoint.String(),
FundingTXID: channel.FundingOutpoint.Hash.String(),
FundingTXIndex: channel.FundingOutpoint.Index,
Capacity: uint64(channel.Capacity),
Initiator: channel.IsInitiator,
LocalBalance: uint64(
channel.LocalCommitment.LocalBalance.ToSatoshis(),
),
RemoteBalance: uint64(
channel.LocalCommitment.RemoteBalance.ToSatoshis(),
),
}
}
return result, nil
}
func (f *SummaryEntryFile) AsSummaryEntries() ([]*SummaryEntry, error) {
return f.Channels, nil
}
func fundingTXID(chanPoint string) string {
parts := strings.Split(chanPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>: %s",
chanPoint))
}
return parts[0]
}
func fundingTXIndex(chanPoint string) uint32 {
parts := strings.Split(chanPoint, ":")
if len(parts) != 2 {
panic(fmt.Errorf("channel point not in format <txid>:<idx>",
chanPoint))
}
return uint32(parseInt(parts[1]))
}
func parseInt(str string) int {
index, err := strconv.Atoi(str)
if err != nil {
panic(fmt.Errorf("error parsing '%s' as int: %v", str, err))
}
return index
}

@ -1,11 +1,12 @@
package chansummary
package chantools
import (
"bytes"
"encoding/json"
"fmt"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/jessevdk/go-flags"
"io/ioutil"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/channeldb"
)
const (
@ -13,42 +14,90 @@ const (
)
type config struct {
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)."`
RootKey string `long:"rootkey" description:"BIP32 HD root key to use."`
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."`
FromSummary string `long:"fromsummary" description:"The channel input is in the format of this tool's channel summary. Specify '-' to read from stdin."`
FromChannelDB string `long:"fromchanneldb" description:"The channel input is in the format of an lnd channel.db file. Specify '-' to read from stdin."`
RescueDB string `long:"rescuedb" description:"The lnd channel.db file to use for rescuing remote force-closed channels."`
}
type fileContent struct {
Channels []*channel `json:"channels"`
}
var (
logWriter = build.NewRotatingLogWriter()
log = build.NewSubLogger("CHAN", logWriter.GenSubLogger)
cfg = &config{
ApiUrl: defaultApiUrl,
}
)
func Main() error {
var (
err error
args []string
)
setupLogging()
// Parse command line.
config := &config{
ApiUrl: defaultApiUrl,
}
if args, err = flags.Parse(config); err != nil {
parser := flags.NewParser(cfg, flags.Default)
_, _ = parser.AddCommand(
"summary", "Compile a summary about the current state of "+
"channels.", "", &summaryCommand{},
)
_, _ = parser.AddCommand(
"rescueclosed", "Try finding the private keys for funds that "+
"are in outputs of remotely force-closed channels", "",
&rescueClosedCommand{},
)
_, err := parser.Parse()
return err
}
type summaryCommand struct{}
func (c *summaryCommand) Execute(args []string) error {
// Parse channel entries from any of the possible input files.
entries, err := ParseInput(cfg)
if err != nil {
return err
}
if len(args) != 1 {
return fmt.Errorf("exactly one file argument needed")
return collectChanSummary(cfg, entries)
}
type rescueClosedCommand struct{}
func (c *rescueClosedCommand) Execute(args []string) error {
// Check that root key is valid.
if cfg.RootKey == "" {
return fmt.Errorf("root key is required")
}
file := args[0]
// Read file and parse into channel.
content, err := ioutil.ReadFile(file)
_, err := hdkeychain.NewKeyFromString(cfg.RootKey)
if err != nil {
return err
return fmt.Errorf("error parsing root key: %v", err)
}
// Check that we have a rescue DB.
if cfg.RescueDB == "" {
return fmt.Errorf("rescue DB is required")
}
decoder := json.NewDecoder(bytes.NewReader(content))
channels := fileContent{}
err = decoder.Decode(&channels)
db, err := channeldb.Open(cfg.RescueDB)
if err != nil {
return fmt.Errorf("error opening rescue DB: %v", err)
}
// Parse channel entries from any of the possible input files.
entries, err := ParseInput(cfg)
if err != nil {
return err
}
return bruteForceChannels(cfg, entries, db)
}
return collectChanSummary(config, channels.Channels)
func setupLogging() {
logWriter.RegisterSubLogger("CHAN", log)
err := logWriter.InitLogRotator("./results/chantools.log", 10, 3)
if err != nil {
panic(err)
}
err = build.ParseAndSetDebugLevels("trace", logWriter)
if err != nil {
panic(err)
}
}

Loading…
Cancel
Save