77 "encoding/json"
88 "errors"
99 "fmt"
10+ "strings"
1011
1112 "github.com/btcsuite/btcd/btcec/v2"
1213 "github.com/btcsuite/btcd/btcutil"
@@ -16,6 +17,7 @@ import (
1617 "github.com/btcsuite/btcd/txscript"
1718 "github.com/btcsuite/btcd/wire"
1819 "github.com/lightninglabs/chantools/btc"
20+ "github.com/lightninglabs/chantools/cln"
1921 "github.com/lightninglabs/chantools/lnd"
2022 "github.com/lightningnetwork/lnd/input"
2123 "github.com/lightningnetwork/lnd/keychain"
@@ -38,6 +40,9 @@ type sweepRemoteClosedCommand struct {
3840 SweepAddr string
3941 FeeRate uint32
4042
43+ HsmSecret string
44+ PeerPubKeys string
45+
4146 rootKey * rootKey
4247 cmd * cobra.Command
4348}
@@ -92,19 +97,27 @@ Supported remote force-closed channel types are:
9297 "use for the sweep transaction in sat/vByte" ,
9398 )
9499
100+ cc .cmd .Flags ().StringVar (
101+ & cc .HsmSecret , "hsm_secret" , "" , "the hex encoded HSM secret " +
102+ "to use for deriving the multisig keys for a CLN " +
103+ "node; obtain by running 'xxd -p -c32 " +
104+ "~/.lightning/bitcoin/hsm_secret'" ,
105+ )
106+ cc .cmd .Flags ().StringVar (
107+ & cc .PeerPubKeys , "peers" , "" , "comma separated list of " +
108+ "hex encoded public keys of the remote peers " +
109+ "to recover funds from, only required when using " +
110+ "--hsm_secret to derive the keys" ,
111+ )
112+
95113 cc .rootKey = newRootKey (cc .cmd , "sweeping the wallet" )
96114
97115 return cc .cmd
98116}
99117
100118func (c * sweepRemoteClosedCommand ) Execute (_ * cobra.Command , _ []string ) error {
101- extendedKey , err := c .rootKey .read ()
102- if err != nil {
103- return fmt .Errorf ("error reading root key: %w" , err )
104- }
105-
106119 // Make sure sweep addr is set.
107- err = lnd .CheckAddress (
120+ err : = lnd .CheckAddress (
108121 c .SweepAddr , chainParams , true , "sweep" , lnd .AddrTypeP2WKH ,
109122 lnd .AddrTypeP2TR ,
110123 )
@@ -120,9 +133,93 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
120133 c .FeeRate = defaultFeeSatPerVByte
121134 }
122135
136+ var (
137+ signer lnd.ChannelSigner
138+ estimator input.TxWeightEstimator
139+ sweepScript []byte
140+ targets []* targetAddr
141+ )
142+ switch {
143+ case c .HsmSecret != "" :
144+ secretBytes , err := hex .DecodeString (c .HsmSecret )
145+ if err != nil {
146+ return fmt .Errorf ("error decoding HSM secret: %w" , err )
147+ }
148+
149+ var hsmSecret [32 ]byte
150+ copy (hsmSecret [:], secretBytes )
151+
152+ if c .PeerPubKeys == "" {
153+ return errors .New ("invalid peer public keys, must be " +
154+ "a comma separated list of hex encoded " +
155+ "public keys" )
156+ }
157+
158+ var pubKeys []* btcec.PublicKey
159+ for _ , pubKeyHex := range strings .Split (c .PeerPubKeys , "," ) {
160+ pkHex , err := hex .DecodeString (pubKeyHex )
161+ if err != nil {
162+ return fmt .Errorf ("error decoding peer " +
163+ "public key hex %s: %w" , pubKeyHex , err )
164+ }
165+
166+ pk , err := btcec .ParsePubKey (pkHex )
167+ if err != nil {
168+ return fmt .Errorf ("error parsing peer public " +
169+ "key hex %s: %w" , pubKeyHex , err )
170+ }
171+
172+ pubKeys = append (pubKeys , pk )
173+ }
174+
175+ signer = & cln.Signer {
176+ HsmSecret : hsmSecret ,
177+ }
178+
179+ targets , err = findTargetsCln (
180+ hsmSecret , pubKeys , c .APIURL , c .RecoveryWindow ,
181+ )
182+ if err != nil {
183+ return fmt .Errorf ("error finding targets: %w" , err )
184+ }
185+
186+ sweepScript , err = lnd .CheckAndEstimateAddress (
187+ c .SweepAddr , chainParams , & estimator , "sweep" ,
188+ )
189+ if err != nil {
190+ return err
191+ }
192+
193+ default :
194+ extendedKey , err := c .rootKey .read ()
195+ if err != nil {
196+ return fmt .Errorf ("error reading root key: %w" , err )
197+ }
198+
199+ signer = & lnd.Signer {
200+ ExtendedKey : extendedKey ,
201+ ChainParams : chainParams ,
202+ }
203+
204+ targets , err = findTargetsLnd (
205+ extendedKey , c .APIURL , c .RecoveryWindow ,
206+ )
207+ if err != nil {
208+ return fmt .Errorf ("error finding targets: %w" , err )
209+ }
210+
211+ sweepScript , err = lnd .PrepareWalletAddress (
212+ c .SweepAddr , chainParams , & estimator , extendedKey ,
213+ "sweep" ,
214+ )
215+ if err != nil {
216+ return err
217+ }
218+ }
219+
123220 return sweepRemoteClosed (
124- extendedKey , c . APIURL , c . SweepAddr , c . RecoveryWindow , c . FeeRate ,
125- c .Publish ,
221+ signer , & estimator , sweepScript , targets ,
222+ newExplorerAPI ( c . APIURL ), c . FeeRate , c .Publish ,
126223 )
127224}
128225
@@ -135,17 +232,8 @@ type targetAddr struct {
135232 scriptTree * input.CommitScriptTree
136233}
137234
138- func sweepRemoteClosed (extendedKey * hdkeychain.ExtendedKey , apiURL ,
139- sweepAddr string , recoveryWindow uint32 , feeRate uint32 ,
140- publish bool ) error {
141-
142- var estimator input.TxWeightEstimator
143- sweepScript , err := lnd .PrepareWalletAddress (
144- sweepAddr , chainParams , & estimator , extendedKey , "sweep" ,
145- )
146- if err != nil {
147- return err
148- }
235+ func findTargetsLnd (extendedKey * hdkeychain.ExtendedKey , apiURL string ,
236+ recoveryWindow uint32 ) ([]* targetAddr , error ) {
149237
150238 var (
151239 targets []* targetAddr
@@ -157,17 +245,18 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
157245 index )
158246 parsedPath , err := lnd .ParsePath (path )
159247 if err != nil {
160- return fmt .Errorf ("error parsing path: %w" , err )
248+ return nil , fmt .Errorf ("error parsing path: %w" , err )
161249 }
162250
163251 hdKey , err := lnd .DeriveChildren (extendedKey , parsedPath )
164252 if err != nil {
165- return fmt .Errorf ("eror deriving children: %w" , err )
253+ return nil , fmt .Errorf ("eror deriving children: %w" ,
254+ err )
166255 }
167256
168257 privKey , err := hdKey .ECPrivKey ()
169258 if err != nil {
170- return fmt .Errorf ("could not derive private " +
259+ return nil , fmt .Errorf ("could not derive private " +
171260 "key: %w" , err )
172261 }
173262
@@ -181,7 +270,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
181270 }, api ,
182271 )
183272 if err != nil {
184- return fmt .Errorf ("could not query API for " +
273+ return nil , fmt .Errorf ("could not query API for " +
185274 "addresses with funds: %w" , err )
186275 }
187276 targets = append (targets , foundTargets ... )
@@ -193,14 +282,60 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
193282 api , recoveryWindow , extendedKey ,
194283 )
195284 if err != nil && ! errors .Is (err , errAddrNotFound ) {
196- return fmt .Errorf ("could not check ancient channel points: %w" ,
197- err )
285+ return nil , fmt .Errorf ("could not check ancient channel " +
286+ "points: %w" , err )
198287 }
199288
200289 if len (ancientChannelTargets ) > 0 {
201290 targets = append (targets , ancientChannelTargets ... )
202291 }
203292
293+ return targets , nil
294+ }
295+
296+ func findTargetsCln (hsmSecret [32 ]byte , pubKeys []* btcec.PublicKey ,
297+ apiURL string , recoveryWindow uint32 ) ([]* targetAddr , error ) {
298+
299+ var (
300+ targets []* targetAddr
301+ api = newExplorerAPI (apiURL )
302+ )
303+ for _ , pubKey := range pubKeys {
304+ for index := range recoveryWindow {
305+ desc := & keychain.KeyDescriptor {
306+ PubKey : pubKey ,
307+ KeyLocator : keychain.KeyLocator {
308+ Family : keychain .KeyFamilyPaymentBase ,
309+ Index : index ,
310+ },
311+ }
312+ _ , privKey , err := cln .DeriveKeyPair (hsmSecret , desc )
313+ if err != nil {
314+ return nil , fmt .Errorf ("could not derive " +
315+ "private key: %w" , err )
316+ }
317+
318+ foundTargets , err := queryAddressBalances (
319+ privKey .PubKey (), desc , api ,
320+ )
321+ if err != nil {
322+ return nil , fmt .Errorf ("could not query API " +
323+ "for addresses with funds: %w" , err )
324+ }
325+ targets = append (targets , foundTargets ... )
326+ }
327+ }
328+
329+ log .Infof ("Found %d addresses with funds to sweep." , len (targets ))
330+
331+ return targets , nil
332+ }
333+
334+ func sweepRemoteClosed (signer lnd.ChannelSigner ,
335+ estimator * input.TxWeightEstimator , sweepScript []byte ,
336+ targets []* targetAddr , api * btc.ExplorerAPI , feeRate uint32 ,
337+ publish bool ) error {
338+
204339 // Create estimator and transaction template.
205340 var (
206341 signDescs []* input.SignDescriptor
@@ -332,13 +467,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
332467 }}
333468
334469 // Sign the transaction now.
335- var (
336- signer = & lnd.Signer {
337- ExtendedKey : extendedKey ,
338- ChainParams : chainParams ,
339- }
340- sigHashes = txscript .NewTxSigHashes (sweepTx , prevOutFetcher )
341- )
470+ var sigHashes = txscript .NewTxSigHashes (sweepTx , prevOutFetcher )
342471 for idx , desc := range signDescs {
343472 desc .SigHashes = sigHashes
344473 desc .InputIndex = idx
@@ -370,6 +499,13 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
370499 // P2WKH descriptor to be set to the pkScript of the
371500 // output...
372501 desc .WitnessScript = desc .Output .PkScript
502+
503+ // For CLN we need to activate a flag to make sure we
504+ // put the correct public key on the witness stack.
505+ if clnSigner , ok := signer .(* cln.Signer ); ok {
506+ clnSigner .SwapDescKeyAfterDerive = true
507+ }
508+
373509 witness , err := input .CommitSpendNoDelay (
374510 signer , desc , sweepTx ,
375511 len (desc .SingleTweak ) == 0 ,
@@ -382,7 +518,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
382518 }
383519
384520 var buf bytes.Buffer
385- err = sweepTx .Serialize (& buf )
521+ err : = sweepTx .Serialize (& buf )
386522 if err != nil {
387523 return err
388524 }
0 commit comments