Skip to content

Commit 4694e5e

Browse files
committed
zombierecovery: make preparekeys CLN compatible
1 parent 221b751 commit 4694e5e

File tree

3 files changed

+145
-52
lines changed

3 files changed

+145
-52
lines changed

cmd/chantools/zombierecovery_makeoffer.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -159,22 +159,6 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
159159
}
160160
}
161161

162-
// If we're only matching, we can stop here.
163-
if c.MatchOnly {
164-
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
165-
if err != nil {
166-
return fmt.Errorf("error parsing their keys: %w", err)
167-
}
168-
169-
theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
170-
if err != nil {
171-
return fmt.Errorf("error parsing our keys: %w", err)
172-
}
173-
return matchKeys(
174-
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
175-
)
176-
}
177-
178162
// Make sure one of the nodes is ours.
179163
_, pubKey, _, err := lnd.DeriveKey(
180164
extendedKey, lnd.IdentityPath(chainParams), chainParams,
@@ -243,6 +227,11 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
243227
return err
244228
}
245229

230+
// If we're only matching, we can stop here.
231+
if c.MatchOnly {
232+
return nil
233+
}
234+
246235
// Let's now sum up the tally of how much of the rescued funds should
247236
// go to which party.
248237
var (

cmd/chantools/zombierecovery_makeoffer_test.go

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
var (
13-
key1Bytes, _ = hex.DecodeString(
14-
"0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564" +
15-
"ea0ab6",
16-
)
17-
key1, _ = btcec.ParsePubKey(key1Bytes)
18-
key2Bytes, _ = hex.DecodeString(
19-
"038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcb" +
20-
"debd5b",
21-
)
22-
key2, _ = btcec.ParsePubKey(key2Bytes)
23-
addr = "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05" +
24-
"ul63"
25-
)
26-
2712
func TestMatchScript(t *testing.T) {
28-
ok, _, err := matchScript(addr, key1, key2, &chaincfg.MainNetParams)
29-
require.NoError(t, err)
30-
require.True(t, ok)
13+
testCases := []struct {
14+
key1 string
15+
key2 string
16+
addr string
17+
params *chaincfg.Params
18+
}{{
19+
key1: "0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564ea0ab6",
20+
key2: "038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcbdebd5b",
21+
addr: "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05ul63",
22+
params: &chaincfg.MainNetParams,
23+
}, {
24+
key1: "03585d8e760bd0925da67d9c22a69dcad9f51f90a39f9a681971268555975ea30d",
25+
key2: "0326a2171c97673cc8cd7a04a043f0224c59591fc8c9de320a48f7c9b68ab0ae2b",
26+
addr: "bcrt1qhcn39q6jc0krkh9va230y2z6q96zadt8fhxw3erv92fzlrw83cyq40nwek",
27+
params: &chaincfg.RegressionNetParams,
28+
}}
29+
30+
for _, tc := range testCases {
31+
key1Bytes, err := hex.DecodeString(tc.key1)
32+
require.NoError(t, err)
33+
key1, err := btcec.ParsePubKey(key1Bytes)
34+
require.NoError(t, err)
35+
36+
key2Bytes, err := hex.DecodeString(tc.key2)
37+
require.NoError(t, err)
38+
key2, err := btcec.ParsePubKey(key2Bytes)
39+
require.NoError(t, err)
40+
41+
ok, _, err := matchScript(tc.addr, key1, key2, tc.params)
42+
require.NoError(t, err)
43+
require.True(t, ok)
44+
}
3145
}

cmd/chantools/zombierecovery_preparekeys.go

Lines changed: 108 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"time"
1111

12+
"github.com/btcsuite/btcd/btcec/v2"
13+
"github.com/lightninglabs/chantools/cln"
1214
"github.com/lightninglabs/chantools/lnd"
1315
"github.com/spf13/cobra"
1416
)
@@ -23,6 +25,8 @@ type zombieRecoveryPrepareKeysCommand struct {
2325

2426
NumKeys uint32
2527

28+
HsmSecret string
29+
2630
rootKey *rootKey
2731
cmd *cobra.Command
2832
}
@@ -56,6 +60,12 @@ correct ones for the matched channels.`,
5660
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
5761
"multisig keys to derive",
5862
)
63+
cc.cmd.Flags().StringVar(
64+
&cc.HsmSecret, "hsm_secret", "", "the hex encoded HSM secret "+
65+
"to use for deriving the multisig keys for a CLN "+
66+
"node; obtain by running 'xxd -p -c32 "+
67+
"~/.lightning/bitcoin/hsm_secret'",
68+
)
5969

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

@@ -65,12 +75,7 @@ correct ones for the matched channels.`,
6575
func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
6676
_ []string) error {
6777

68-
extendedKey, err := c.rootKey.read()
69-
if err != nil {
70-
return fmt.Errorf("error reading root key: %w", err)
71-
}
72-
73-
_, err = lnd.GetP2WPKHScript(c.PayoutAddr, chainParams)
78+
_, err := lnd.GetP2WPKHScript(c.PayoutAddr, chainParams)
7479
if err != nil {
7580
return errors.New("invalid payout address, must be P2WPKH")
7681
}
@@ -93,19 +98,51 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
9398
return errors.New("invalid match file, node info missing")
9499
}
95100

101+
// Derive the keys for the node type, depending on the input flags.
102+
var pubKeyStr string
103+
switch {
104+
case c.HsmSecret != "":
105+
pubKeyStr, err = c.clnDeriveKeys(match)
106+
default:
107+
pubKeyStr, err = c.lndDeriveKeys(match)
108+
}
109+
if err != nil {
110+
return err
111+
}
112+
113+
// Write the result back into a new file.
114+
matchBytes, err := json.MarshalIndent(match, "", " ")
115+
if err != nil {
116+
return err
117+
}
118+
119+
fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
120+
time.Now().Format("2006-01-02"), pubKeyStr)
121+
log.Infof("Writing result to %s", fileName)
122+
return os.WriteFile(fileName, matchBytes, 0644)
123+
}
124+
125+
func (c *zombieRecoveryPrepareKeysCommand) lndDeriveKeys(match *match) (string,
126+
error) {
127+
128+
extendedKey, err := c.rootKey.read()
129+
if err != nil {
130+
return "", fmt.Errorf("error reading root key: %w", err)
131+
}
132+
96133
_, pubKey, _, err := lnd.DeriveKey(
97134
extendedKey, lnd.IdentityPath(chainParams), chainParams,
98135
)
99136
if err != nil {
100-
return fmt.Errorf("error deriving identity pubkey: %w", err)
137+
return "", fmt.Errorf("error deriving identity pubkey: %w", err)
101138
}
102139

103140
pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed())
104141
var nodeInfo *nodeInfo
105142
switch {
106143
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
107-
return fmt.Errorf("derived pubkey %s from seed but that key "+
108-
"was not found in the match file %s", pubKeyStr,
144+
return "", fmt.Errorf("derived pubkey %s from seed but that "+
145+
"key was not found in the match file %s", pubKeyStr,
109146
c.MatchFile)
110147

111148
case match.Node1.PubKey == pubKeyStr:
@@ -122,8 +159,8 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
122159
chainParams,
123160
)
124161
if err != nil {
125-
return fmt.Errorf("error deriving multisig pubkey: %w",
126-
err)
162+
return "", fmt.Errorf("error deriving multisig "+
163+
"pubkey: %w", err)
127164
}
128165

129166
nodeInfo.MultisigKeys = append(
@@ -133,14 +170,67 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
133170
}
134171
nodeInfo.PayoutAddr = c.PayoutAddr
135172

136-
// Write the result back into a new file.
137-
matchBytes, err := json.MarshalIndent(match, "", " ")
173+
return pubKeyStr, nil
174+
}
175+
176+
func (c *zombieRecoveryPrepareKeysCommand) clnDeriveKeys(match *match) (string,
177+
error) {
178+
179+
secretBytes, err := hex.DecodeString(c.HsmSecret)
138180
if err != nil {
139-
return err
181+
return "", fmt.Errorf("error decoding HSM secret: %w", err)
140182
}
141183

142-
fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
143-
time.Now().Format("2006-01-02"), pubKeyStr)
144-
log.Infof("Writing result to %s", fileName)
145-
return os.WriteFile(fileName, matchBytes, 0644)
184+
var hsmSecret [32]byte
185+
copy(hsmSecret[:], secretBytes)
186+
187+
nodePubKey, err := cln.NodeKey(hsmSecret)
188+
if err != nil {
189+
return "", fmt.Errorf("error deriving node pubkey: %w", err)
190+
}
191+
192+
pubKeyStr := hex.EncodeToString(nodePubKey.SerializeCompressed())
193+
var ourNodeInfo, theirNodeInfo *nodeInfo
194+
switch {
195+
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
196+
return "", fmt.Errorf("derived pubkey %s from seed but that "+
197+
"key was not found in the match file %s", pubKeyStr,
198+
c.MatchFile)
199+
200+
case match.Node1.PubKey == pubKeyStr:
201+
ourNodeInfo = match.Node1
202+
theirNodeInfo = match.Node2
203+
204+
default:
205+
ourNodeInfo = match.Node2
206+
theirNodeInfo = match.Node1
207+
}
208+
209+
theirNodeKeyBytes, err := hex.DecodeString(theirNodeInfo.PubKey)
210+
if err != nil {
211+
return "", fmt.Errorf("error decoding peer pubkey: %w", err)
212+
}
213+
theirNodeKey, err := btcec.ParsePubKey(theirNodeKeyBytes)
214+
if err != nil {
215+
return "", fmt.Errorf("error parsing peer pubkey: %w", err)
216+
}
217+
218+
// Derive all 2500 keys now, this might take a while.
219+
for index := range c.NumKeys {
220+
pubKey, err := cln.FundingKey(
221+
hsmSecret, theirNodeKey, uint64(index),
222+
)
223+
if err != nil {
224+
return "", fmt.Errorf("error deriving multisig "+
225+
"pubkey: %w", err)
226+
}
227+
228+
ourNodeInfo.MultisigKeys = append(
229+
ourNodeInfo.MultisigKeys,
230+
hex.EncodeToString(pubKey.SerializeCompressed()),
231+
)
232+
}
233+
ourNodeInfo.PayoutAddr = c.PayoutAddr
234+
235+
return pubKeyStr, nil
146236
}

0 commit comments

Comments
 (0)