Skip to content

Commit d5902bf

Browse files
committed
zombierecovery: make preparekeys CLN compatible
1 parent 26a20dd commit d5902bf

File tree

3 files changed

+150
-57
lines changed

3 files changed

+150
-57
lines changed

cmd/chantools/zombierecovery_makeoffer.go

+5-16
Original file line numberDiff line numberDiff line change
@@ -183,22 +183,6 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
183183
}
184184
}
185185

186-
// If we're only matching, we can stop here.
187-
if c.MatchOnly {
188-
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
189-
if err != nil {
190-
return fmt.Errorf("error parsing their keys: %w", err)
191-
}
192-
193-
theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
194-
if err != nil {
195-
return fmt.Errorf("error parsing our keys: %w", err)
196-
}
197-
return matchKeys(
198-
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
199-
)
200-
}
201-
202186
// Make sure one of the nodes is ours.
203187
_, pubKey, _, err := lnd.DeriveKey(
204188
extendedKey, lnd.IdentityPath(chainParams), chainParams,
@@ -277,6 +261,11 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
277261
return err
278262
}
279263

264+
// If we're only matching, we can stop here.
265+
if c.MatchOnly {
266+
return nil
267+
}
268+
280269
// Let's prepare the PSBT.
281270
packet, err := psbt.NewFromUnsignedTx(wire.NewMsgTx(2))
282271
if err != nil {

cmd/chantools/zombierecovery_makeoffer_test.go

+32-18
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

+113-23
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
"os"
1010
"time"
1111

12+
"github.com/btcsuite/btcd/btcec/v2"
1213
"github.com/btcsuite/btcd/btcutil"
1314
"github.com/btcsuite/btcd/wire"
15+
"github.com/lightninglabs/chantools/cln"
1416
"github.com/lightninglabs/chantools/lnd"
1517
"github.com/spf13/cobra"
1618
)
@@ -25,6 +27,8 @@ type zombieRecoveryPrepareKeysCommand struct {
2527

2628
NumKeys uint32
2729

30+
HsmSecret string
31+
2832
rootKey *rootKey
2933
cmd *cobra.Command
3034
}
@@ -58,6 +62,12 @@ correct ones for the matched channels.`,
5862
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
5963
"multisig keys to derive",
6064
)
65+
cc.cmd.Flags().StringVar(
66+
&cc.HsmSecret, "hsm_secret", "", "the hex encoded HSM secret "+
67+
"to use for deriving the multisig keys for a CLN "+
68+
"node; obtain by running 'xxd -p -c32 "+
69+
"~/.lightning/bitcoin/hsm_secret'",
70+
)
6171

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

@@ -67,12 +77,7 @@ correct ones for the matched channels.`,
6777
func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
6878
_ []string) error {
6979

70-
extendedKey, err := c.rootKey.read()
71-
if err != nil {
72-
return fmt.Errorf("error reading root key: %w", err)
73-
}
74-
75-
err = lnd.CheckAddress(
80+
err := lnd.CheckAddress(
7681
c.PayoutAddr, chainParams, false, "payout", lnd.AddrTypeP2WKH,
7782
lnd.AddrTypeP2TR,
7883
)
@@ -98,19 +103,51 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
98103
return errors.New("invalid match file, node info missing")
99104
}
100105

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

108145
pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed())
109146
var nodeInfo *nodeInfo
110147
switch {
111148
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
112-
return fmt.Errorf("derived pubkey %s from seed but that key "+
113-
"was not found in the match file %s", pubKeyStr,
149+
return "", fmt.Errorf("derived pubkey %s from seed but that "+
150+
"key was not found in the match file %s", pubKeyStr,
114151
c.MatchFile)
115152

116153
case match.Node1.PubKey == pubKeyStr:
@@ -126,7 +163,7 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
126163
matchChannel := match.Channels[idx]
127164
addr, err := lnd.ParseAddress(matchChannel.Address, chainParams)
128165
if err != nil {
129-
return fmt.Errorf("error parsing channel funding "+
166+
return "", fmt.Errorf("error parsing channel funding "+
130167
"address '%s': %w", matchChannel.Address, err)
131168
}
132169

@@ -136,23 +173,23 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
136173
matchChannel.ChanPoint,
137174
)
138175
if err != nil {
139-
return fmt.Errorf("error parsing channel "+
176+
return "", fmt.Errorf("error parsing channel "+
140177
"point %s: %w", matchChannel.ChanPoint,
141178
err)
142179
}
143180

144181
var randomness [32]byte
145182
if _, err := rand.Read(randomness[:]); err != nil {
146-
return err
183+
return "", err
147184
}
148185

149186
nonces, err := lnd.GenerateMuSig2Nonces(
150187
extendedKey, randomness, chanPoint, chainParams,
151188
nil,
152189
)
153190
if err != nil {
154-
return fmt.Errorf("error generating MuSig2 "+
155-
"nonces: %w", err)
191+
return "", fmt.Errorf("error generating "+
192+
"MuSig2 nonces: %w", err)
156193
}
157194

158195
matchChannel.MuSig2NonceRandomness = hex.EncodeToString(
@@ -171,8 +208,8 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
171208
chainParams,
172209
)
173210
if err != nil {
174-
return fmt.Errorf("error deriving multisig pubkey: %w",
175-
err)
211+
return "", fmt.Errorf("error deriving multisig "+
212+
"pubkey: %w", err)
176213
}
177214

178215
nodeInfo.MultisigKeys = append(
@@ -182,14 +219,67 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
182219
}
183220
nodeInfo.PayoutAddr = c.PayoutAddr
184221

185-
// Write the result back into a new file.
186-
matchBytes, err := json.MarshalIndent(match, "", " ")
222+
return pubKeyStr, nil
223+
}
224+
225+
func (c *zombieRecoveryPrepareKeysCommand) clnDeriveKeys(match *match) (string,
226+
error) {
227+
228+
secretBytes, err := hex.DecodeString(c.HsmSecret)
187229
if err != nil {
188-
return err
230+
return "", fmt.Errorf("error decoding HSM secret: %w", err)
189231
}
190232

191-
fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
192-
time.Now().Format("2006-01-02"), pubKeyStr)
193-
log.Infof("Writing result to %s", fileName)
194-
return os.WriteFile(fileName, matchBytes, 0644)
233+
var hsmSecret [32]byte
234+
copy(hsmSecret[:], secretBytes)
235+
236+
nodePubKey, err := cln.NodeKey(hsmSecret)
237+
if err != nil {
238+
return "", fmt.Errorf("error deriving node pubkey: %w", err)
239+
}
240+
241+
pubKeyStr := hex.EncodeToString(nodePubKey.SerializeCompressed())
242+
var ourNodeInfo, theirNodeInfo *nodeInfo
243+
switch {
244+
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
245+
return "", fmt.Errorf("derived pubkey %s from seed but that "+
246+
"key was not found in the match file %s", pubKeyStr,
247+
c.MatchFile)
248+
249+
case match.Node1.PubKey == pubKeyStr:
250+
ourNodeInfo = match.Node1
251+
theirNodeInfo = match.Node2
252+
253+
default:
254+
ourNodeInfo = match.Node2
255+
theirNodeInfo = match.Node1
256+
}
257+
258+
theirNodeKeyBytes, err := hex.DecodeString(theirNodeInfo.PubKey)
259+
if err != nil {
260+
return "", fmt.Errorf("error decoding peer pubkey: %w", err)
261+
}
262+
theirNodeKey, err := btcec.ParsePubKey(theirNodeKeyBytes)
263+
if err != nil {
264+
return "", fmt.Errorf("error parsing peer pubkey: %w", err)
265+
}
266+
267+
// Derive all 2500 keys now, this might take a while.
268+
for index := range c.NumKeys {
269+
pubKey, err := cln.FundingKey(
270+
hsmSecret, theirNodeKey, uint64(index),
271+
)
272+
if err != nil {
273+
return "", fmt.Errorf("error deriving multisig "+
274+
"pubkey: %w", err)
275+
}
276+
277+
ourNodeInfo.MultisigKeys = append(
278+
ourNodeInfo.MultisigKeys,
279+
hex.EncodeToString(pubKey.SerializeCompressed()),
280+
)
281+
}
282+
ourNodeInfo.PayoutAddr = c.PayoutAddr
283+
284+
return pubKeyStr, nil
195285
}

0 commit comments

Comments
 (0)