Merge pull request #117 from lightninglabs/zombie-matching

zombierecovery: add --matchonly flag to makeoffer, --numkeys to preparekeys
pull/118/head v0.12.2
Oliver Gugger 4 months ago committed by GitHub
commit d5d5a91430
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -30,7 +30,7 @@ Example (make sure you always use the latest version!):
```shell ```shell
$ cd /tmp $ 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 $ tar -zxvf chantools.tar.gz
$ sudo mv chantools-*/chantools /usr/local/bin/ $ sudo mv chantools-*/chantools /usr/local/bin/
``` ```

@ -33,7 +33,7 @@ const (
// version is the current version of the tool. It is set during build. // version is the current version of the tool. It is set during build.
// NOTE: When changing this, please also update the version in the // NOTE: When changing this, please also update the version in the
// download link shown in the README. // download link shown in the README.
version = "0.12.1" version = "0.12.2"
na = "n/a" na = "n/a"
// lndVersion is the current version of lnd that we support. This is // lndVersion is the current version of lnd that we support. This is

@ -6,7 +6,6 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"strconv" "strconv"
"strings" "strings"
@ -28,6 +27,8 @@ type zombieRecoveryMakeOfferCommand struct {
Node2 string Node2 string
FeeRate uint32 FeeRate uint32
MatchOnly bool
rootKey *rootKey rootKey *rootKey
cmd *cobra.Command cmd *cobra.Command
} }
@ -64,6 +65,10 @@ a counter offer.`,
&cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+
"use for the sweep transaction in sat/vByte", "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") cc.rootKey = newRootKey(cc.cmd, "signing the offer")
@ -82,12 +87,12 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
c.FeeRate = defaultFeeSatPerVByte c.FeeRate = defaultFeeSatPerVByte
} }
node1Bytes, err := ioutil.ReadFile(c.Node1) node1Bytes, err := os.ReadFile(c.Node1)
if err != nil { if err != nil {
return fmt.Errorf("error reading node1 key file %s: %w", return fmt.Errorf("error reading node1 key file %s: %w",
c.Node1, err) c.Node1, err)
} }
node2Bytes, err := ioutil.ReadFile(c.Node2) node2Bytes, err := os.ReadFile(c.Node2)
if err != nil { if err != nil {
return fmt.Errorf("error reading node2 key file %s: %w", return fmt.Errorf("error reading node2 key file %s: %w",
c.Node2, err) c.Node2, err)
@ -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. // Make sure one of the nodes is ours.
_, pubKey, _, err := lnd.DeriveKey( _, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.IdentityPath(chainParams), chainParams, extendedKey, lnd.IdentityPath(chainParams), chainParams,
@ -206,52 +227,19 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
return fmt.Errorf("payout address missing") return fmt.Errorf("payout address missing")
} }
ourPubKeys := make([]*btcec.PublicKey, len(ourKeys)) ourPubKeys, err := parseKeys(ourKeys)
theirPubKeys := make([]*btcec.PublicKey, len(theirKeys)) if err != nil {
for idx, pubKeyHex := range ourKeys { return fmt.Errorf("error parsing their keys: %w", err)
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)
}
} }
// Loop through all channels and all keys now, this will definitely take theirPubKeys, err := parseKeys(theirKeys)
// a while. if err != nil {
channelLoop: return fmt.Errorf("error parsing our keys: %w", err)
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
}
}
}
return fmt.Errorf("didn't find matching multisig keys for "+ err = matchKeys(keys1.Channels, ourPubKeys, theirPubKeys, chainParams)
"channel %s", channel.ChanPoint) if err != nil {
return err
} }
// Let's now sum up the tally of how much of the rescued funds should // Let's now sum up the tally of how much of the rescued funds should
@ -444,6 +432,64 @@ channelLoop:
return nil 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, func matchScript(address string, key1, key2 *btcec.PublicKey,
params *chaincfg.Params) (bool, []byte, error) { params *chaincfg.Params) (bool, []byte, error) {

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"time" "time"
"github.com/lightninglabs/chantools/lnd" "github.com/lightninglabs/chantools/lnd"
@ -20,6 +21,8 @@ type zombieRecoveryPrepareKeysCommand struct {
MatchFile string MatchFile string
PayoutAddr string PayoutAddr string
NumKeys uint32
rootKey *rootKey rootKey *rootKey
cmd *cobra.Command cmd *cobra.Command
} }
@ -47,7 +50,12 @@ correct ones for the matched channels.`,
cc.cmd.Flags().StringVar( cc.cmd.Flags().StringVar(
&cc.PayoutAddr, "payout_addr", "", "the address where this "+ &cc.PayoutAddr, "payout_addr", "", "the address where this "+
"node's rescued funds should be sent to, must be a "+ "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") cc.rootKey = newRootKey(cc.cmd, "deriving the multisig keys")
@ -108,9 +116,9 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
} }
// Derive all 2500 keys now, this might take a while. // 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( _, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.MultisigPath(chainParams, index), extendedKey, lnd.MultisigPath(chainParams, int(index)),
chainParams, chainParams,
) )
if err != nil { if err != nil {
@ -134,5 +142,5 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json", fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
time.Now().Format("2006-01-02"), pubKeyStr) time.Now().Format("2006-01-02"), pubKeyStr)
log.Infof("Writing result to %s", fileName) log.Infof("Writing result to %s", fileName)
return ioutil.WriteFile(fileName, matchBytes, 0644) return os.WriteFile(fileName, matchBytes, 0644)
} }

Loading…
Cancel
Save