fakechanbackup: create fake backup from graph data

pull/20/head
Oliver Gugger 3 years ago
parent 5aa69d38ab
commit 53f886cf43
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -97,6 +97,12 @@ func reportOutspend(api *ExplorerAPI,
entry.ClosingTX.AllOutsSpent = false
summaryFile.ChannelsWithUnspent++
for _, o := range utxo {
if o.ScriptPubkeyType == "v0_p2wpkh" {
entry.ClosingTX.ToRemoteAddr = o.ScriptPubkeyAddr
}
}
if couldBeOurs(entry, utxo) {
summaryFile.ChannelsWithPotential++
summaryFile.FundsForceClose += utxo[0].Value

@ -4,17 +4,22 @@ import (
"bytes"
"encoding/hex"
"fmt"
"github.com/lightningnetwork/lnd/tor"
"io/ioutil"
"net"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/gogo/protobuf/jsonpb"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/spf13/cobra"
)
@ -23,9 +28,11 @@ type fakeChanBackupCommand struct {
NodeAddr string
ChannelPoint string
ShortChanID string
Initiator bool
Capacity uint64
MultiFile string
FromChannelGraph string
MultiFile string
rootKey *rootKey
cmd *cobra.Command
@ -47,13 +54,27 @@ output. But to initiate DLP, we would need to have a channel.backup file.
Fortunately, if we have enough information about the channel, we can create a
faked/skeleton channel.backup file that at least lets us talk to the other node
and ask them to do their part. Then we can later brute-force the private key for
the transaction output of our part of the funds (see rescueclosed command).`,
the transaction output of our part of the funds (see rescueclosed command).
There are two versions of this command: The first one is to create a fake
backup for a single channel where all flags (except --from_channel_graph) need
to be set. This is the easiest to use since it only relies on data that is
publicly available (for example on 1ml.com) but involves more manual work.
The second version of the command only takes the --from_channel_graph and
--multi_file flags and tries to assemble all channels found in the public
network graph (must be provided in the JSON format that the
'lncli describegraph' command returns) into a fake backup file. This is the
most convenient way to use this command but requires one to have a fully synced
lnd node.`,
Example: `chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
--capacity 123456 \
--channelpoint f39310xxxxxxxxxx:1 \
--initiator \
--remote_node_addr 022c260xxxxxxxx@213.174.150.1:9735 \
--short_channel_id 566222x300x1 \
--multi_file fake.backup
chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
--from_channel_graph lncli_describegraph.json \
--multi_file fake.backup`,
RunE: cc.Execute,
}
@ -76,9 +97,10 @@ the transaction output of our part of the funds (see rescueclosed command).`,
&cc.Capacity, "capacity", 0, "the channel's capacity in "+
"satoshis",
)
cc.cmd.Flags().BoolVar(
&cc.Initiator, "initiator", false, "whether our node was the "+
"initiator (funder) of the channel",
cc.cmd.Flags().StringVar(
&cc.FromChannelGraph, "from_channel_graph", "", "the full "+
"LN channel graph in the JSON format that the "+
"'lncli describegraph' returns",
)
multiFileName := fmt.Sprintf("results/fake-%s.backup",
time.Now().Format("2006-01-02-15-04-05"))
@ -104,6 +126,21 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
ChainParams: chainParams,
}
if c.FromChannelGraph != "" {
graphBytes, err := ioutil.ReadFile(c.FromChannelGraph)
if err != nil {
return fmt.Errorf("error reading graph JSON file %s: "+
"%v", c.FromChannelGraph, err)
}
graph := &lnrpc.ChannelGraph{}
err = jsonpb.UnmarshalString(string(graphBytes), graph)
if err != nil {
return fmt.Errorf("error parsing graph JSON: %v", err)
}
return backupFromGraph(graph, keyRing, multiFile)
}
// Parse channel point of channel to fake.
chanOp, err := lnd.ParseOutpoint(c.ChannelPoint)
if err != nil {
@ -160,8 +197,134 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
"be equal to index on --channelpoint")
}
// Create some fake channel config.
chanCfg := channeldb.ChannelConfig{
singles := []chanbackup.Single{newSingle(
*chanOp, shortChanID, nodePubkey, []net.Addr{addr},
btcutil.Amount(c.Capacity),
)}
return writeBackups(singles, keyRing, multiFile)
}
func backupFromGraph(graph *lnrpc.ChannelGraph, keyRing *lnd.HDKeyRing,
multiFile *chanbackup.MultiFile) error {
// Since we have the master root key, we can find out our local node's
// identity pubkey by just deriving it.
nodePubKey, err := keyRing.NodePubKey()
if err != nil {
return fmt.Errorf("error deriving node pubkey: %v", err)
}
nodePubKeyStr := hex.EncodeToString(nodePubKey.SerializeCompressed())
// Let's now find all channels in the graph that our node is part of.
channels := lnd.AllNodeChannels(graph, nodePubKeyStr)
// Let's create a single backup entry for each channel.
singles := make([]chanbackup.Single, len(channels))
for idx, channel := range channels {
var peerPubKeyStr string
if channel.Node1Pub == nodePubKeyStr {
peerPubKeyStr = channel.Node2Pub
} else {
peerPubKeyStr = channel.Node1Pub
}
peerPubKeyBytes, err := hex.DecodeString(peerPubKeyStr)
if err != nil {
return fmt.Errorf("error parsing hex: %v", err)
}
peerPubKey, err := btcec.ParsePubKey(
peerPubKeyBytes, btcec.S256(),
)
if err != nil {
return fmt.Errorf("error parsing pubkey: %v", err)
}
peer, err := lnd.FindNode(graph, peerPubKeyStr)
if err != nil {
return err
}
peerAddresses := make([]net.Addr, len(peer.Addresses))
for idx, peerAddr := range peer.Addresses {
var err error
if strings.Contains(peerAddr.Addr, ".onion") {
peerAddresses[idx], err = tor.ParseAddr(
peerAddr.Addr, "",
)
if err != nil {
return fmt.Errorf("error parsing "+
"tor address: %v", err)
}
continue
}
peerAddresses[idx], err = net.ResolveTCPAddr(
"tcp", peerAddr.Addr,
)
if err != nil {
return fmt.Errorf("could not parse addr: %s",
err)
}
}
shortChanID := lnwire.NewShortChanIDFromInt(channel.ChannelId)
chanOp, err := lnd.ParseOutpoint(channel.ChanPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %v",
err)
}
singles[idx] = newSingle(
*chanOp, shortChanID, peerPubKey, peerAddresses,
btcutil.Amount(channel.Capacity),
)
}
return writeBackups(singles, keyRing, multiFile)
}
func writeBackups(singles []chanbackup.Single, keyRing keychain.KeyRing,
multiFile *chanbackup.MultiFile) error {
newMulti := chanbackup.Multi{
Version: chanbackup.DefaultMultiVersion,
StaticBackups: singles,
}
var packed bytes.Buffer
err := newMulti.PackToWriter(&packed, keyRing)
if err != nil {
return fmt.Errorf("unable to multi-pack backups: %v", err)
}
return multiFile.UpdateAndSwap(packed.Bytes())
}
func newSingle(fundingOutPoint wire.OutPoint, shortChanID lnwire.ShortChannelID,
nodePubKey *btcec.PublicKey, addrs []net.Addr,
capacity btcutil.Amount) chanbackup.Single {
return chanbackup.Single{
Version: chanbackup.DefaultSingleVersion,
IsInitiator: true,
ChainHash: *chainParams.GenesisHash,
FundingOutpoint: fundingOutPoint,
ShortChannelID: shortChanID,
RemoteNodePub: nodePubKey,
Addresses: addrs,
Capacity: capacity,
LocalChanCfg: fakeChanCfg(nodePubKey),
RemoteChanCfg: fakeChanCfg(nodePubKey),
ShaChainRootDesc: keychain.KeyDescriptor{
PubKey: nodePubKey,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
Index: 1,
},
},
}
}
func fakeChanCfg(nodePubkey *btcec.PublicKey) channeldb.ChannelConfig {
return channeldb.ChannelConfig{
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: 500,
ChanReserve: 5000,
@ -206,34 +369,4 @@ func (c *fakeChanBackupCommand) Execute(_ *cobra.Command, _ []string) error {
},
},
}
newMulti := chanbackup.Multi{
Version: chanbackup.DefaultMultiVersion,
StaticBackups: []chanbackup.Single{{
Version: chanbackup.DefaultSingleVersion,
IsInitiator: c.Initiator,
ChainHash: *chainParams.GenesisHash,
FundingOutpoint: *chanOp,
ShortChannelID: shortChanID,
RemoteNodePub: nodePubkey,
Addresses: []net.Addr{addr},
Capacity: btcutil.Amount(c.Capacity),
LocalChanCfg: chanCfg,
RemoteChanCfg: chanCfg,
ShaChainRootDesc: keychain.KeyDescriptor{
PubKey: nodePubkey,
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamilyRevocationRoot,
Index: 1,
},
},
}},
}
var packed bytes.Buffer
err = newMulti.PackToWriter(&packed, keyRing)
if err != nil {
return fmt.Errorf("unable to multi-pack backups: %v", err)
}
return multiFile.UpdateAndSwap(packed.Bytes())
}

@ -9,6 +9,7 @@ type ClosingTX struct {
ForceClose bool `json:"force_close"`
AllOutsSpent bool `json:"all_outputs_spent"`
OurAddr string `json:"our_addr"`
ToRemoteAddr string `json:"to_remote_addr"`
SweepPrivkey string `json:"sweep_privkey"`
ConfHeight uint32 `json:"conf_height"`
}

@ -17,6 +17,17 @@ faked/skeleton channel.backup file that at least lets us talk to the other node
and ask them to do their part. Then we can later brute-force the private key for
the transaction output of our part of the funds (see rescueclosed command).
There are two versions of this command: The first one is to create a fake
backup for a single channel where all flags (except --from_channel_graph) need
to be set. This is the easiest to use since it only relies on data that is
publicly available (for example on 1ml.com) but involves more manual work.
The second version of the command only takes the --from_channel_graph and
--multi_file flags and tries to assemble all channels found in the public
network graph (must be provided in the JSON format that the
'lncli describegraph' command returns) into a fake backup file. This is the
most convenient way to use this command but requires one to have a fully synced
lnd node.
```
chantools fakechanbackup [flags]
```
@ -27,24 +38,27 @@ chantools fakechanbackup [flags]
chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
--capacity 123456 \
--channelpoint f39310xxxxxxxxxx:1 \
--initiator \
--remote_node_addr 022c260xxxxxxxx@213.174.150.1:9735 \
--short_channel_id 566222x300x1 \
--multi_file fake.backup
chantools fakechanbackup --rootkey xprvxxxxxxxxxx \
--from_channel_graph lncli_describegraph.json \
--multi_file fake.backup
```
### Options
```
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
--capacity uint the channel's capacity in satoshis
--channelpoint string funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is displayed on 1ml.com
-h, --help help for fakechanbackup
--initiator whether our node was the initiator (funder) of the channel
--multi_file string the fake channel backup file to create (default "results/fake-2021-03-01-10-12-23.backup")
--remote_node_addr string the remote node connection information in the format pubkey@host:port
--rootkey string BIP32 HD root key of the wallet to use for encrypting the backup; leave empty to prompt for lnd 24 word aezeed
--short_channel_id string the short channel ID in the format <blockheight>x<transactionindex>x<outputindex>
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
--capacity uint the channel's capacity in satoshis
--channelpoint string funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is displayed on 1ml.com
--from_channel_graph string the full LN channel graph in the JSON format that the 'lncli describegraph' returns
-h, --help help for fakechanbackup
--multi_file string the fake channel backup file to create (default "results/fake-2021-05-01-22-08-48.backup")
--remote_node_addr string the remote node connection information in the format pubkey@host:port
--rootkey string BIP32 HD root key of the wallet to use for encrypting the backup; leave empty to prompt for lnd 24 word aezeed
--short_channel_id string the short channel ID in the format <blockheight>x<transactionindex>x<outputindex>
```
### Options inherited from parent commands

@ -11,6 +11,7 @@ require (
github.com/btcsuite/btcwallet/walletdb v1.3.4
github.com/coreos/bbolt v1.3.3
github.com/davecgh/go-spew v1.1.1
github.com/gogo/protobuf v1.2.1
github.com/gohugoio/hugo v0.79.1 // indirect
github.com/jessevdk/go-flags v1.4.0 // indirect
github.com/lightningnetwork/lnd v0.11.1-beta

@ -0,0 +1,34 @@
package lnd
import (
"fmt"
"github.com/lightningnetwork/lnd/lnrpc"
)
func AllNodeChannels(graph *lnrpc.ChannelGraph,
nodePubKey string) []*lnrpc.ChannelEdge {
var result []*lnrpc.ChannelEdge
for _, edge := range graph.Edges {
if edge.Node1Pub != nodePubKey && edge.Node2Pub != nodePubKey {
continue
}
result = append(result, edge)
}
return result
}
func FindNode(graph *lnrpc.ChannelGraph,
nodePubKey string) (*lnrpc.LightningNode, error) {
for _, node := range graph.Nodes {
if node.PubKey == nodePubKey {
return node, nil
}
}
return nil, fmt.Errorf("node %s not found in graph", nodePubKey)
}

@ -293,8 +293,8 @@ func (r *HDKeyRing) DeriveKey(keyLoc keychain.KeyLocator) (
}, nil
}
// Check if a key descriptor is correct by making sure that we can derive the
// key that it describes.
// CheckDescriptor checks 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 {
@ -335,3 +335,17 @@ func (r *HDKeyRing) CheckDescriptor(
// derivable with the given information.
return keychain.ErrCannotDerivePrivKey
}
// NodePubKey returns the public key that represents an lnd node's public
// network identity.
func (r *HDKeyRing) NodePubKey() (*btcec.PublicKey, error) {
keyDesc, err := r.DeriveKey(keychain.KeyLocator{
Family: keychain.KeyFamilyNodeKey,
Index: 0,
})
if err != nil {
return nil, err
}
return keyDesc.PubKey, nil
}

Loading…
Cancel
Save