Skip to content

Commit

Permalink
Merge pull request #117 from lightninglabs/zombie-matching
Browse files Browse the repository at this point in the history
zombierecovery: add --matchonly flag to makeoffer, --numkeys to preparekeys
  • Loading branch information
guggero authored Jan 23, 2024
2 parents 341d3af + 82a03a6 commit d5d5a91
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 52 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Example (make sure you always use the latest version!):

```shell
$ cd /tmp
$ wget -O chantools.tar.gz https://github.com/lightninglabs/chantools/releases/download/v0.12.0/chantools-linux-amd64-v0.12.0.tar.gz
$ wget -O chantools.tar.gz https://github.com/lightninglabs/chantools/releases/download/v0.12.2/chantools-linux-amd64-v0.12.2.tar.gz
$ tar -zxvf chantools.tar.gz
$ sudo mv chantools-*/chantools /usr/local/bin/
```
Expand Down
2 changes: 1 addition & 1 deletion cmd/chantools/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const (
// version is the current version of the tool. It is set during build.
// NOTE: When changing this, please also update the version in the
// download link shown in the README.
version = "0.12.1"
version = "0.12.2"
na = "n/a"

// lndVersion is the current version of lnd that we support. This is
Expand Down
138 changes: 92 additions & 46 deletions cmd/chantools/zombierecovery_makeoffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
Expand All @@ -28,6 +27,8 @@ type zombieRecoveryMakeOfferCommand struct {
Node2 string
FeeRate uint32

MatchOnly bool

rootKey *rootKey
cmd *cobra.Command
}
Expand Down Expand Up @@ -64,6 +65,10 @@ a counter offer.`,
&cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+
"use for the sweep transaction in sat/vByte",
)
cc.cmd.Flags().BoolVar(
&cc.MatchOnly, "matchonly", false, "only match the keys, "+
"don't create an offer",
)

cc.rootKey = newRootKey(cc.cmd, "signing the offer")

Expand All @@ -82,12 +87,12 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
c.FeeRate = defaultFeeSatPerVByte
}

node1Bytes, err := ioutil.ReadFile(c.Node1)
node1Bytes, err := os.ReadFile(c.Node1)
if err != nil {
return fmt.Errorf("error reading node1 key file %s: %w",
c.Node1, err)
}
node2Bytes, err := ioutil.ReadFile(c.Node2)
node2Bytes, err := os.ReadFile(c.Node2)
if err != nil {
return fmt.Errorf("error reading node2 key file %s: %w",
c.Node2, err)
Expand Down Expand Up @@ -153,6 +158,22 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
}
}

// If we're only matching, we can stop here.
if c.MatchOnly {
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
if err != nil {
return fmt.Errorf("error parsing their keys: %w", err)
}

theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
if err != nil {
return fmt.Errorf("error parsing our keys: %w", err)
}
return matchKeys(
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
)
}

// Make sure one of the nodes is ours.
_, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.IdentityPath(chainParams), chainParams,
Expand Down Expand Up @@ -206,52 +227,19 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
return fmt.Errorf("payout address missing")
}

ourPubKeys := make([]*btcec.PublicKey, len(ourKeys))
theirPubKeys := make([]*btcec.PublicKey, len(theirKeys))
for idx, pubKeyHex := range ourKeys {
ourPubKeys[idx], err = pubKeyFromHex(pubKeyHex)
if err != nil {
return fmt.Errorf("error parsing our pubKey: %w", err)
}
}
for idx, pubKeyHex := range theirKeys {
theirPubKeys[idx], err = pubKeyFromHex(pubKeyHex)
if err != nil {
return fmt.Errorf("error parsing their pubKey: %w", err)
}
ourPubKeys, err := parseKeys(ourKeys)
if err != nil {
return fmt.Errorf("error parsing their keys: %w", err)
}

// Loop through all channels and all keys now, this will definitely take
// a while.
channelLoop:
for _, channel := range keys1.Channels {
for ourKeyIndex, ourKey := range ourPubKeys {
for _, theirKey := range theirPubKeys {
match, witnessScript, err := matchScript(
channel.Address, ourKey, theirKey,
chainParams,
)
if err != nil {
return fmt.Errorf("error matching "+
"keys to script: %w", err)
}

if match {
channel.ourKeyIndex = uint32(ourKeyIndex)
channel.ourKey = ourKey
channel.theirKey = theirKey
channel.witnessScript = witnessScript

log.Infof("Found keys for channel %s",
channel.ChanPoint)

continue channelLoop
}
}
}
theirPubKeys, err := parseKeys(theirKeys)
if err != nil {
return fmt.Errorf("error parsing our keys: %w", err)
}

return fmt.Errorf("didn't find matching multisig keys for "+
"channel %s", channel.ChanPoint)
err = matchKeys(keys1.Channels, ourPubKeys, theirPubKeys, chainParams)
if err != nil {
return err
}

// Let's now sum up the tally of how much of the rescued funds should
Expand Down Expand Up @@ -444,6 +432,64 @@ channelLoop:
return nil
}

// parseKeys parses a list of string keys into public keys.
func parseKeys(keys []string) ([]*btcec.PublicKey, error) {
pubKeys := make([]*btcec.PublicKey, 0, len(keys))
for _, key := range keys {
pubKey, err := pubKeyFromHex(key)
if err != nil {
return nil, err
}
pubKeys = append(pubKeys, pubKey)
}

return pubKeys, nil
}

// matchKeys tries to match the keys from the two nodes. It updates the channels
// with the correct keys and witness scripts.
func matchKeys(channels []*channel, ourPubKeys, theirPubKeys []*btcec.PublicKey,
chainParams *chaincfg.Params) error {

// Loop through all channels and all keys now, this will definitely take
// a while.
channelLoop:
for _, channel := range channels {
for ourKeyIndex, ourKey := range ourPubKeys {
for _, theirKey := range theirPubKeys {
match, witnessScript, err := matchScript(
channel.Address, ourKey, theirKey,
chainParams,
)
if err != nil {
return fmt.Errorf("error matching "+
"keys to script: %w", err)
}

if match {
channel.ourKeyIndex = uint32(ourKeyIndex)
channel.ourKey = ourKey
channel.theirKey = theirKey
channel.witnessScript = witnessScript

log.Infof("Found keys for channel %s: "+
"our key %x, their key %x",
channel.ChanPoint,
ourKey.SerializeCompressed(),
theirKey.SerializeCompressed())

continue channelLoop
}
}
}

return fmt.Errorf("didn't find matching multisig keys for "+
"channel %s", channel.ChanPoint)
}

return nil
}

func matchScript(address string, key1, key2 *btcec.PublicKey,
params *chaincfg.Params) (bool, []byte, error) {

Expand Down
16 changes: 12 additions & 4 deletions cmd/chantools/zombierecovery_preparekeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"time"

"github.com/lightninglabs/chantools/lnd"
Expand All @@ -20,6 +21,8 @@ type zombieRecoveryPrepareKeysCommand struct {
MatchFile string
PayoutAddr string

NumKeys uint32

rootKey *rootKey
cmd *cobra.Command
}
Expand Down Expand Up @@ -47,7 +50,12 @@ correct ones for the matched channels.`,
cc.cmd.Flags().StringVar(
&cc.PayoutAddr, "payout_addr", "", "the address where this "+
"node's rescued funds should be sent to, must be a "+
"P2WPKH (native SegWit) address")
"P2WPKH (native SegWit) address",
)
cc.cmd.Flags().Uint32Var(
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
"multisig keys to derive",
)

cc.rootKey = newRootKey(cc.cmd, "deriving the multisig keys")

Expand Down Expand Up @@ -108,9 +116,9 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
}

// Derive all 2500 keys now, this might take a while.
for index := 0; index < numMultisigKeys; index++ {
for index := uint32(0); index < c.NumKeys; index++ {
_, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.MultisigPath(chainParams, index),
extendedKey, lnd.MultisigPath(chainParams, int(index)),
chainParams,
)
if err != nil {
Expand All @@ -134,5 +142,5 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
time.Now().Format("2006-01-02"), pubKeyStr)
log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, matchBytes, 0644)
return os.WriteFile(fileName, matchBytes, 0644)
}

0 comments on commit d5d5a91

Please sign in to comment.