@@ -9,8 +9,10 @@ import (
9
9
"os"
10
10
"time"
11
11
12
+ "github.com/btcsuite/btcd/btcec/v2"
12
13
"github.com/btcsuite/btcd/btcutil"
13
14
"github.com/btcsuite/btcd/wire"
15
+ "github.com/lightninglabs/chantools/cln"
14
16
"github.com/lightninglabs/chantools/lnd"
15
17
"github.com/spf13/cobra"
16
18
)
@@ -25,6 +27,8 @@ type zombieRecoveryPrepareKeysCommand struct {
25
27
26
28
NumKeys uint32
27
29
30
+ HsmSecret string
31
+
28
32
rootKey * rootKey
29
33
cmd * cobra.Command
30
34
}
@@ -58,6 +62,12 @@ correct ones for the matched channels.`,
58
62
& cc .NumKeys , "num_keys" , numMultisigKeys , "the number of " +
59
63
"multisig keys to derive" ,
60
64
)
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
+ )
61
71
62
72
cc .rootKey = newRootKey (cc .cmd , "deriving the multisig keys" )
63
73
@@ -67,12 +77,7 @@ correct ones for the matched channels.`,
67
77
func (c * zombieRecoveryPrepareKeysCommand ) Execute (_ * cobra.Command ,
68
78
_ []string ) error {
69
79
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 (
76
81
c .PayoutAddr , chainParams , false , "payout" , lnd .AddrTypeP2WKH ,
77
82
lnd .AddrTypeP2TR ,
78
83
)
@@ -98,19 +103,51 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
98
103
return errors .New ("invalid match file, node info missing" )
99
104
}
100
105
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
+
101
138
_ , pubKey , _ , err := lnd .DeriveKey (
102
139
extendedKey , lnd .IdentityPath (chainParams ), chainParams ,
103
140
)
104
141
if err != nil {
105
- return fmt .Errorf ("error deriving identity pubkey: %w" , err )
142
+ return "" , fmt .Errorf ("error deriving identity pubkey: %w" , err )
106
143
}
107
144
108
145
pubKeyStr := hex .EncodeToString (pubKey .SerializeCompressed ())
109
146
var nodeInfo * nodeInfo
110
147
switch {
111
148
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 ,
114
151
c .MatchFile )
115
152
116
153
case match .Node1 .PubKey == pubKeyStr :
@@ -126,7 +163,7 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
126
163
matchChannel := match .Channels [idx ]
127
164
addr , err := lnd .ParseAddress (matchChannel .Address , chainParams )
128
165
if err != nil {
129
- return fmt .Errorf ("error parsing channel funding " +
166
+ return "" , fmt .Errorf ("error parsing channel funding " +
130
167
"address '%s': %w" , matchChannel .Address , err )
131
168
}
132
169
@@ -136,23 +173,23 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
136
173
matchChannel .ChanPoint ,
137
174
)
138
175
if err != nil {
139
- return fmt .Errorf ("error parsing channel " +
176
+ return "" , fmt .Errorf ("error parsing channel " +
140
177
"point %s: %w" , matchChannel .ChanPoint ,
141
178
err )
142
179
}
143
180
144
181
var randomness [32 ]byte
145
182
if _ , err := rand .Read (randomness [:]); err != nil {
146
- return err
183
+ return "" , err
147
184
}
148
185
149
186
nonces , err := lnd .GenerateMuSig2Nonces (
150
187
extendedKey , randomness , chanPoint , chainParams ,
151
188
nil ,
152
189
)
153
190
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 )
156
193
}
157
194
158
195
matchChannel .MuSig2NonceRandomness = hex .EncodeToString (
@@ -171,8 +208,8 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
171
208
chainParams ,
172
209
)
173
210
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 )
176
213
}
177
214
178
215
nodeInfo .MultisigKeys = append (
@@ -182,14 +219,67 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
182
219
}
183
220
nodeInfo .PayoutAddr = c .PayoutAddr
184
221
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 )
187
229
if err != nil {
188
- return err
230
+ return "" , fmt . Errorf ( "error decoding HSM secret: %w" , err )
189
231
}
190
232
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
195
285
}
0 commit comments