7
7
"encoding/json"
8
8
"errors"
9
9
"fmt"
10
+ "os"
11
+ "regexp"
12
+ "slices"
10
13
"strings"
11
14
12
15
"github.com/btcsuite/btcd/btcec/v2"
@@ -19,8 +22,11 @@ import (
19
22
"github.com/lightninglabs/chantools/btc"
20
23
"github.com/lightninglabs/chantools/cln"
21
24
"github.com/lightninglabs/chantools/lnd"
25
+ "github.com/lightningnetwork/lnd/fn/v2"
22
26
"github.com/lightningnetwork/lnd/input"
23
27
"github.com/lightningnetwork/lnd/keychain"
28
+ "github.com/lightningnetwork/lnd/lncfg"
29
+ "github.com/lightningnetwork/lnd/lnrpc"
24
30
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
25
31
"github.com/spf13/cobra"
26
32
)
@@ -40,8 +46,9 @@ type sweepRemoteClosedCommand struct {
40
46
SweepAddr string
41
47
FeeRate uint32
42
48
43
- HsmSecret string
44
- PeerPubKeys string
49
+ HsmSecret string
50
+ PeerPubKeys string
51
+ KnownOutputs string
45
52
46
53
rootKey * rootKey
47
54
cmd * cobra.Command
@@ -107,7 +114,16 @@ Supported remote force-closed channel types are:
107
114
& cc .PeerPubKeys , "peers" , "" , "comma separated list of " +
108
115
"hex encoded public keys of the remote peers " +
109
116
"to recover funds from, only required when using " +
110
- "--hsm_secret to derive the keys" ,
117
+ "--hsm_secret to derive the keys; can also be a file " +
118
+ "name to a file that contains the public keys, one " +
119
+ "per line" ,
120
+ )
121
+ cc .cmd .Flags ().StringVar (
122
+ & cc .KnownOutputs , "known_outputs" , "" , "a comma separated " +
123
+ "list of known output addresses to use for matching " +
124
+ "against, instead of querying the API; can also be " +
125
+ "a file name to a file that contains the known " +
126
+ "outputs, one per line" ,
111
127
)
112
128
113
129
cc .rootKey = newRootKey (cc .cmd , "sweeping the wallet" )
@@ -134,11 +150,32 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
134
150
}
135
151
136
152
var (
137
- signer lnd.ChannelSigner
138
- estimator input.TxWeightEstimator
139
- sweepScript []byte
140
- targets []* targetAddr
153
+ signer lnd.ChannelSigner
154
+ estimator input.TxWeightEstimator
155
+ knownOutputs []string
156
+ sweepScript []byte
157
+ targets []* targetAddr
141
158
)
159
+
160
+ if c .KnownOutputs != "" {
161
+ knownOutputs , err = listOrFile (c .KnownOutputs )
162
+ if err != nil {
163
+ return fmt .Errorf ("error reading known outputs: %w" ,
164
+ err )
165
+ }
166
+
167
+ for _ , output := range knownOutputs {
168
+ _ , err = lnd .ParseAddress (output , chainParams )
169
+ if err != nil {
170
+ return fmt .Errorf ("error parsing known output " +
171
+ "address %s: %w" , output , err )
172
+ }
173
+ }
174
+
175
+ log .Infof ("Using %d known outputs for matching." ,
176
+ len (knownOutputs ))
177
+ }
178
+
142
179
switch {
143
180
case c .HsmSecret != "" :
144
181
secretBytes , err := hex .DecodeString (c .HsmSecret )
@@ -152,11 +189,16 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
152
189
if c .PeerPubKeys == "" {
153
190
return fmt .Errorf ("invalid peer public keys, must be " +
154
191
"a comma separated list of hex encoded " +
155
- "public keys" )
192
+ "public keys or a file name " )
156
193
}
157
194
158
195
var pubKeys []* btcec.PublicKey
159
- for _ , pubKeyHex := range strings .Split (c .PeerPubKeys , "," ) {
196
+ hexPubKeys , err := listOrFile (c .PeerPubKeys )
197
+ if err != nil {
198
+ return fmt .Errorf ("error reading peer public keys: %w" ,
199
+ err )
200
+ }
201
+ for _ , pubKeyHex := range hexPubKeys {
160
202
pkHex , err := hex .DecodeString (pubKeyHex )
161
203
if err != nil {
162
204
return fmt .Errorf ("error decoding peer " +
@@ -172,12 +214,16 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
172
214
pubKeys = append (pubKeys , pk )
173
215
}
174
216
217
+ log .Infof ("Using %d peer public keys for recovery." ,
218
+ len (pubKeys ))
219
+
175
220
signer = & cln.Signer {
176
221
HsmSecret : hsmSecret ,
177
222
}
178
223
179
224
targets , err = findTargetsCln (
180
225
hsmSecret , pubKeys , c .APIURL , c .RecoveryWindow ,
226
+ knownOutputs ,
181
227
)
182
228
if err != nil {
183
229
return fmt .Errorf ("error finding targets: %w" , err )
@@ -202,7 +248,7 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
202
248
}
203
249
204
250
targets , err = findTargetsLnd (
205
- extendedKey , c .APIURL , c .RecoveryWindow ,
251
+ extendedKey , c .APIURL , c .RecoveryWindow , knownOutputs ,
206
252
)
207
253
if err != nil {
208
254
return fmt .Errorf ("error finding targets: %w" , err )
@@ -233,7 +279,7 @@ type targetAddr struct {
233
279
}
234
280
235
281
func findTargetsLnd (extendedKey * hdkeychain.ExtendedKey , apiURL string ,
236
- recoveryWindow uint32 ) ([]* targetAddr , error ) {
282
+ recoveryWindow uint32 , knownOutputs [] string ) ([]* targetAddr , error ) {
237
283
238
284
var (
239
285
targets []* targetAddr
@@ -267,7 +313,7 @@ func findTargetsLnd(extendedKey *hdkeychain.ExtendedKey, apiURL string,
267
313
Family : keychain .KeyFamilyPaymentBase ,
268
314
Index : index ,
269
315
},
270
- }, api ,
316
+ }, api , knownOutputs ,
271
317
)
272
318
if err != nil {
273
319
return nil , fmt .Errorf ("could not query API for " +
@@ -294,7 +340,8 @@ func findTargetsLnd(extendedKey *hdkeychain.ExtendedKey, apiURL string,
294
340
}
295
341
296
342
func findTargetsCln (hsmSecret [32 ]byte , pubKeys []* btcec.PublicKey ,
297
- apiURL string , recoveryWindow uint32 ) ([]* targetAddr , error ) {
343
+ apiURL string , recoveryWindow uint32 ,
344
+ knownOutputs []string ) ([]* targetAddr , error ) {
298
345
299
346
var (
300
347
targets []* targetAddr
@@ -316,7 +363,7 @@ func findTargetsCln(hsmSecret [32]byte, pubKeys []*btcec.PublicKey,
316
363
}
317
364
318
365
foundTargets , err := queryAddressBalances (
319
- privKey .PubKey (), desc , api ,
366
+ privKey .PubKey (), desc , api , knownOutputs ,
320
367
)
321
368
if err != nil {
322
369
return nil , fmt .Errorf ("could not query API " +
@@ -540,13 +587,19 @@ func sweepRemoteClosed(signer lnd.ChannelSigner,
540
587
}
541
588
542
589
func queryAddressBalances (pubKey * btcec.PublicKey ,
543
- keyDesc * keychain.KeyDescriptor , api * btc.ExplorerAPI ) ([] * targetAddr ,
544
- error ) {
590
+ keyDesc * keychain.KeyDescriptor , api * btc.ExplorerAPI ,
591
+ knownOutputs [] string ) ([] * targetAddr , error ) {
545
592
546
593
var targets []* targetAddr
547
594
queryAddr := func (address btcutil.Address , script []byte ,
548
595
scriptTree * input.CommitScriptTree ) error {
549
596
597
+ if len (knownOutputs ) > 0 {
598
+ if ! slices .Contains (knownOutputs , address .String ()) {
599
+ return nil
600
+ }
601
+ }
602
+
550
603
unspent , err := api .Unspent (address .EncodeAddress ())
551
604
if err != nil {
552
605
return fmt .Errorf ("could not query unspent: %w" , err )
@@ -716,3 +769,21 @@ func checkAncientChannelPoints(api *btc.ExplorerAPI, numKeys uint32,
716
769
717
770
return targets , nil
718
771
}
772
+
773
+ func listOrFile (listOrPath string ) ([]string , error ) {
774
+ if lnrpc .FileExists (lncfg .CleanAndExpandPath (listOrPath )) {
775
+ contents , err := os .ReadFile (listOrPath )
776
+ if err != nil {
777
+ return nil , fmt .Errorf ("error reading file %s: %w" ,
778
+ listOrPath , err )
779
+ }
780
+
781
+ re := regexp .MustCompile (`[,\s]+` )
782
+ parts := re .Split (string (contents ), - 1 )
783
+ return fn .Filter (parts , func (s string ) bool {
784
+ return len (strings .TrimSpace (s )) > 0
785
+ }), nil
786
+ }
787
+
788
+ return strings .Split (listOrPath , "," ), nil
789
+ }
0 commit comments