@@ -2,12 +2,16 @@ package main
22
33import (
44 "bytes"
5+ _ "embed"
56 "encoding/hex"
7+ "encoding/json"
8+ "errors"
69 "fmt"
710
811 "github.com/btcsuite/btcd/btcec/v2"
912 "github.com/btcsuite/btcd/btcutil"
1013 "github.com/btcsuite/btcd/btcutil/hdkeychain"
14+ "github.com/btcsuite/btcd/chaincfg"
1115 "github.com/btcsuite/btcd/chaincfg/chainhash"
1216 "github.com/btcsuite/btcd/txscript"
1317 "github.com/btcsuite/btcd/wire"
@@ -19,6 +23,9 @@ import (
1923 "github.com/spf13/cobra"
2024)
2125
26+ //go:embed sweepremoteclosed_ancient.json
27+ var ancientChannelPoints []byte
28+
2229const (
2330 sweepRemoteClosedDefaultRecoveryWindow = 200
2431 sweepDustLimit = 600
@@ -121,9 +128,8 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error {
121128
122129type targetAddr struct {
123130 addr btcutil.Address
124- pubKey * btcec.PublicKey
125- path string
126131 keyDesc * keychain.KeyDescriptor
132+ tweak []byte
127133 vouts []* btc.Vout
128134 script []byte
129135 scriptTree * input.CommitScriptTree
@@ -154,9 +160,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
154160 return fmt .Errorf ("error parsing path: %w" , err )
155161 }
156162
157- hdKey , err := lnd .DeriveChildren (
158- extendedKey , parsedPath ,
159- )
163+ hdKey , err := lnd .DeriveChildren (extendedKey , parsedPath )
160164 if err != nil {
161165 return fmt .Errorf ("eror deriving children: %w" , err )
162166 }
@@ -168,7 +172,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
168172 }
169173
170174 foundTargets , err := queryAddressBalances (
171- privKey .PubKey (), path , & keychain.KeyDescriptor {
175+ privKey .PubKey (), & keychain.KeyDescriptor {
172176 PubKey : privKey .PubKey (),
173177 KeyLocator : keychain.KeyLocator {
174178 Family : keychain .KeyFamilyPaymentBase ,
@@ -183,6 +187,20 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
183187 targets = append (targets , foundTargets ... )
184188 }
185189
190+ // Also check if there are any funds in channels with the initial,
191+ // tweaked channel type that requires a channel point.
192+ ancientChannelTargets , err := checkAncientChannelPoints (
193+ api , recoveryWindow , extendedKey ,
194+ )
195+ if err != nil && ! errors .Is (err , errAddrNotFound ) {
196+ return fmt .Errorf ("could not check ancient channel points: %w" ,
197+ err )
198+ }
199+
200+ if len (ancientChannelTargets ) > 0 {
201+ targets = append (targets , ancientChannelTargets ... )
202+ }
203+
186204 // Create estimator and transaction template.
187205 var (
188206 signDescs []* input.SignDescriptor
@@ -235,6 +253,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
235253 signDesc = & input.SignDescriptor {
236254 KeyDesc : * target .keyDesc ,
237255 WitnessScript : target .script ,
256+ SingleTweak : target .tweak ,
238257 Output : prevTxOut ,
239258 HashType : txscript .SigHashAll ,
240259 PrevOutputFetcher : prevOutFetcher ,
@@ -383,7 +402,7 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
383402 return nil
384403}
385404
386- func queryAddressBalances (pubKey * btcec.PublicKey , path string ,
405+ func queryAddressBalances (pubKey * btcec.PublicKey ,
387406 keyDesc * keychain.KeyDescriptor , api * btc.ExplorerAPI ) ([]* targetAddr ,
388407 error ) {
389408
@@ -401,8 +420,6 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string,
401420 len (unspent ), address .EncodeAddress ())
402421 targets = append (targets , & targetAddr {
403422 addr : address ,
404- pubKey : pubKey ,
405- path : path ,
406423 keyDesc : keyDesc ,
407424 vouts : unspent ,
408425 script : script ,
@@ -439,3 +456,125 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string,
439456
440457 return targets , nil
441458}
459+
460+ type ancientChannel struct {
461+ OP string `json:"close_outpoint"`
462+ Addr string `json:"close_addr"`
463+ CP string `json:"commit_point"`
464+ }
465+
466+ func findAncientChannels (channels []ancientChannel , numKeys uint32 ,
467+ key * hdkeychain.ExtendedKey ) ([]ancientChannel , error ) {
468+
469+ if err := fillCache (numKeys , key ); err != nil {
470+ return nil , err
471+ }
472+
473+ var foundChannels []ancientChannel
474+ for _ , channel := range channels {
475+ // Decode the commit point.
476+ commitPointBytes , err := hex .DecodeString (channel .CP )
477+ if err != nil {
478+ return nil , fmt .Errorf ("unable to decode commit " +
479+ "point: %w" , err )
480+ }
481+ commitPoint , err := btcec .ParsePubKey (commitPointBytes )
482+ if err != nil {
483+ return nil , fmt .Errorf ("unable to parse commit " +
484+ "point: %w" , err )
485+ }
486+
487+ // Create the address for the commit key. The addresses in the
488+ // file are always for mainnet.
489+ targetPubKeyHash , _ , err := lnd .DecodeAddressHash (
490+ channel .Addr , & chaincfg .MainNetParams ,
491+ )
492+ if err != nil {
493+ return nil , fmt .Errorf ("error parsing addr: %w" , err )
494+ }
495+
496+ _ , _ , err = keyInCache (numKeys , targetPubKeyHash , commitPoint )
497+ switch {
498+ case err == nil :
499+ foundChannels = append (foundChannels , channel )
500+
501+ case errors .Is (err , errAddrNotFound ):
502+ // Try next address.
503+
504+ default :
505+ return nil , err
506+ }
507+ }
508+
509+ return foundChannels , nil
510+ }
511+
512+ func checkAncientChannelPoints (api * btc.ExplorerAPI , numKeys uint32 ,
513+ key * hdkeychain.ExtendedKey ) ([]* targetAddr , error ) {
514+
515+ var channels []ancientChannel
516+ err := json .Unmarshal (ancientChannelPoints , & channels )
517+ if err != nil {
518+ return nil , err
519+ }
520+
521+ ancientChannels , err := findAncientChannels (channels , numKeys , key )
522+ if err != nil {
523+ return nil , err
524+ }
525+
526+ targets := make ([]* targetAddr , 0 , len (ancientChannels ))
527+ for _ , ancientChannel := range ancientChannels {
528+ // Decode the commit point.
529+ commitPointBytes , err := hex .DecodeString (ancientChannel .CP )
530+ if err != nil {
531+ return nil , fmt .Errorf ("unable to decode commit " +
532+ "point: %w" , err )
533+ }
534+ commitPoint , err := btcec .ParsePubKey (commitPointBytes )
535+ if err != nil {
536+ return nil , fmt .Errorf ("unable to parse commit point: " +
537+ "%w" , err )
538+ }
539+
540+ // Create the address for the commit key. The addresses in the
541+ // file are always for mainnet.
542+ targetPubKeyHash , _ , err := lnd .DecodeAddressHash (
543+ ancientChannel .Addr , & chaincfg .MainNetParams ,
544+ )
545+ if err != nil {
546+ return nil , fmt .Errorf ("error parsing addr: %w" , err )
547+ }
548+ addr , err := lnd .ParseAddress (
549+ ancientChannel .Addr , & chaincfg .MainNetParams ,
550+ )
551+ if err != nil {
552+ return nil , fmt .Errorf ("error parsing addr: %w" , err )
553+ }
554+
555+ log .Infof ("Found private key for address %v in list of " +
556+ "ancient channels!" , addr )
557+
558+ unspent , err := api .Unspent (addr .EncodeAddress ())
559+ if err != nil {
560+ return nil , fmt .Errorf ("could not query unspent: %w" ,
561+ err )
562+ }
563+
564+ keyDesc , tweak , err := keyInCache (
565+ numKeys , targetPubKeyHash , commitPoint ,
566+ )
567+ if err != nil {
568+ return nil , err
569+ }
570+
571+ targets = append (targets , & targetAddr {
572+ addr : addr ,
573+ keyDesc : keyDesc ,
574+ tweak : tweak ,
575+ vouts : unspent ,
576+ })
577+ }
578+
579+ return targets , nil
580+ }
0 commit comments