Skip to content

Commit d5d5a91

Browse files
authored
Merge pull request #117 from lightninglabs/zombie-matching
zombierecovery: add --matchonly flag to makeoffer, --numkeys to preparekeys
2 parents 341d3af + 82a03a6 commit d5d5a91

File tree

4 files changed

+106
-52
lines changed

4 files changed

+106
-52
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Example (make sure you always use the latest version!):
3030

3131
```shell
3232
$ cd /tmp
33-
$ wget -O chantools.tar.gz https://github.com/lightninglabs/chantools/releases/download/v0.12.0/chantools-linux-amd64-v0.12.0.tar.gz
33+
$ wget -O chantools.tar.gz https://github.com/lightninglabs/chantools/releases/download/v0.12.2/chantools-linux-amd64-v0.12.2.tar.gz
3434
$ tar -zxvf chantools.tar.gz
3535
$ sudo mv chantools-*/chantools /usr/local/bin/
3636
```

cmd/chantools/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const (
3333
// version is the current version of the tool. It is set during build.
3434
// NOTE: When changing this, please also update the version in the
3535
// download link shown in the README.
36-
version = "0.12.1"
36+
version = "0.12.2"
3737
na = "n/a"
3838

3939
// lndVersion is the current version of lnd that we support. This is

cmd/chantools/zombierecovery_makeoffer.go

Lines changed: 92 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"fmt"
9-
"io/ioutil"
109
"os"
1110
"strconv"
1211
"strings"
@@ -28,6 +27,8 @@ type zombieRecoveryMakeOfferCommand struct {
2827
Node2 string
2928
FeeRate uint32
3029

30+
MatchOnly bool
31+
3132
rootKey *rootKey
3233
cmd *cobra.Command
3334
}
@@ -64,6 +65,10 @@ a counter offer.`,
6465
&cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+
6566
"use for the sweep transaction in sat/vByte",
6667
)
68+
cc.cmd.Flags().BoolVar(
69+
&cc.MatchOnly, "matchonly", false, "only match the keys, "+
70+
"don't create an offer",
71+
)
6772

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

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

85-
node1Bytes, err := ioutil.ReadFile(c.Node1)
90+
node1Bytes, err := os.ReadFile(c.Node1)
8691
if err != nil {
8792
return fmt.Errorf("error reading node1 key file %s: %w",
8893
c.Node1, err)
8994
}
90-
node2Bytes, err := ioutil.ReadFile(c.Node2)
95+
node2Bytes, err := os.ReadFile(c.Node2)
9196
if err != nil {
9297
return fmt.Errorf("error reading node2 key file %s: %w",
9398
c.Node2, err)
@@ -153,6 +158,22 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
153158
}
154159
}
155160

161+
// If we're only matching, we can stop here.
162+
if c.MatchOnly {
163+
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
164+
if err != nil {
165+
return fmt.Errorf("error parsing their keys: %w", err)
166+
}
167+
168+
theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
169+
if err != nil {
170+
return fmt.Errorf("error parsing our keys: %w", err)
171+
}
172+
return matchKeys(
173+
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
174+
)
175+
}
176+
156177
// Make sure one of the nodes is ours.
157178
_, pubKey, _, err := lnd.DeriveKey(
158179
extendedKey, lnd.IdentityPath(chainParams), chainParams,
@@ -206,52 +227,19 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
206227
return fmt.Errorf("payout address missing")
207228
}
208229

209-
ourPubKeys := make([]*btcec.PublicKey, len(ourKeys))
210-
theirPubKeys := make([]*btcec.PublicKey, len(theirKeys))
211-
for idx, pubKeyHex := range ourKeys {
212-
ourPubKeys[idx], err = pubKeyFromHex(pubKeyHex)
213-
if err != nil {
214-
return fmt.Errorf("error parsing our pubKey: %w", err)
215-
}
216-
}
217-
for idx, pubKeyHex := range theirKeys {
218-
theirPubKeys[idx], err = pubKeyFromHex(pubKeyHex)
219-
if err != nil {
220-
return fmt.Errorf("error parsing their pubKey: %w", err)
221-
}
230+
ourPubKeys, err := parseKeys(ourKeys)
231+
if err != nil {
232+
return fmt.Errorf("error parsing their keys: %w", err)
222233
}
223234

224-
// Loop through all channels and all keys now, this will definitely take
225-
// a while.
226-
channelLoop:
227-
for _, channel := range keys1.Channels {
228-
for ourKeyIndex, ourKey := range ourPubKeys {
229-
for _, theirKey := range theirPubKeys {
230-
match, witnessScript, err := matchScript(
231-
channel.Address, ourKey, theirKey,
232-
chainParams,
233-
)
234-
if err != nil {
235-
return fmt.Errorf("error matching "+
236-
"keys to script: %w", err)
237-
}
238-
239-
if match {
240-
channel.ourKeyIndex = uint32(ourKeyIndex)
241-
channel.ourKey = ourKey
242-
channel.theirKey = theirKey
243-
channel.witnessScript = witnessScript
244-
245-
log.Infof("Found keys for channel %s",
246-
channel.ChanPoint)
247-
248-
continue channelLoop
249-
}
250-
}
251-
}
235+
theirPubKeys, err := parseKeys(theirKeys)
236+
if err != nil {
237+
return fmt.Errorf("error parsing our keys: %w", err)
238+
}
252239

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

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

435+
// parseKeys parses a list of string keys into public keys.
436+
func parseKeys(keys []string) ([]*btcec.PublicKey, error) {
437+
pubKeys := make([]*btcec.PublicKey, 0, len(keys))
438+
for _, key := range keys {
439+
pubKey, err := pubKeyFromHex(key)
440+
if err != nil {
441+
return nil, err
442+
}
443+
pubKeys = append(pubKeys, pubKey)
444+
}
445+
446+
return pubKeys, nil
447+
}
448+
449+
// matchKeys tries to match the keys from the two nodes. It updates the channels
450+
// with the correct keys and witness scripts.
451+
func matchKeys(channels []*channel, ourPubKeys, theirPubKeys []*btcec.PublicKey,
452+
chainParams *chaincfg.Params) error {
453+
454+
// Loop through all channels and all keys now, this will definitely take
455+
// a while.
456+
channelLoop:
457+
for _, channel := range channels {
458+
for ourKeyIndex, ourKey := range ourPubKeys {
459+
for _, theirKey := range theirPubKeys {
460+
match, witnessScript, err := matchScript(
461+
channel.Address, ourKey, theirKey,
462+
chainParams,
463+
)
464+
if err != nil {
465+
return fmt.Errorf("error matching "+
466+
"keys to script: %w", err)
467+
}
468+
469+
if match {
470+
channel.ourKeyIndex = uint32(ourKeyIndex)
471+
channel.ourKey = ourKey
472+
channel.theirKey = theirKey
473+
channel.witnessScript = witnessScript
474+
475+
log.Infof("Found keys for channel %s: "+
476+
"our key %x, their key %x",
477+
channel.ChanPoint,
478+
ourKey.SerializeCompressed(),
479+
theirKey.SerializeCompressed())
480+
481+
continue channelLoop
482+
}
483+
}
484+
}
485+
486+
return fmt.Errorf("didn't find matching multisig keys for "+
487+
"channel %s", channel.ChanPoint)
488+
}
489+
490+
return nil
491+
}
492+
447493
func matchScript(address string, key1, key2 *btcec.PublicKey,
448494
params *chaincfg.Params) (bool, []byte, error) {
449495

cmd/chantools/zombierecovery_preparekeys.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"fmt"
88
"io/ioutil"
9+
"os"
910
"time"
1011

1112
"github.com/lightninglabs/chantools/lnd"
@@ -20,6 +21,8 @@ type zombieRecoveryPrepareKeysCommand struct {
2021
MatchFile string
2122
PayoutAddr string
2223

24+
NumKeys uint32
25+
2326
rootKey *rootKey
2427
cmd *cobra.Command
2528
}
@@ -47,7 +50,12 @@ correct ones for the matched channels.`,
4750
cc.cmd.Flags().StringVar(
4851
&cc.PayoutAddr, "payout_addr", "", "the address where this "+
4952
"node's rescued funds should be sent to, must be a "+
50-
"P2WPKH (native SegWit) address")
53+
"P2WPKH (native SegWit) address",
54+
)
55+
cc.cmd.Flags().Uint32Var(
56+
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
57+
"multisig keys to derive",
58+
)
5159

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

@@ -108,9 +116,9 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
108116
}
109117

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

0 commit comments

Comments
 (0)