@@ -34,8 +34,11 @@ type sweepTimeLockManualCommand struct {
3434 TimeLockAddr string
3535 RemoteRevocationBasePoint string
3636
37- MaxNumChansTotal uint16
38- MaxNumChanUpdates uint64
37+ MaxNumChannelsTotal uint16
38+ MaxNumChanUpdates uint64
39+
40+ ChannelBackup string
41+ ChannelPoint string
3942
4043 rootKey * rootKey
4144 inputs * inputFlags
@@ -56,6 +59,9 @@ and only the channel.backup file is available.
5659To get the value for --remoterevbasepoint you must use the dumpbackup command,
5760then look up the value for RemoteChanCfg -> RevocationBasePoint -> PubKey.
5861
62+ Alternatively you can directly use the --frombackup and --channelpoint flags to
63+ pull the required information from the given channel.backup file automatically.
64+
5965To get the value for --timelockaddr you must look up the channel's funding
6066output on chain, then follow it to the force close output. The time locked
6167address is always the one that's longer (because it's P2WSH and not P2PKH).` ,
@@ -64,6 +70,14 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`,
6470 --timelockaddr bc1q............ \
6571 --remoterevbasepoint 03xxxxxxx \
6672 --feerate 10 \
73+ --publish
74+
75+ chantools sweeptimelockmanual \
76+ --sweepaddr bc1q..... \
77+ --timelockaddr bc1q............ \
78+ --frombackup channel.backup \
79+ --channelpoint f39310xxxxxxxxxx:1 \
80+ --feerate 10 \
6781 --publish` ,
6882 RunE : cc .Execute ,
6983 }
@@ -83,7 +97,7 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`,
8397 "limit to use" ,
8498 )
8599 cc .cmd .Flags ().Uint16Var (
86- & cc .MaxNumChansTotal , "maxnumchanstotal" , maxKeys , "maximum " +
100+ & cc .MaxNumChannelsTotal , "maxnumchanstotal" , maxKeys , "maximum " +
87101 "number of keys to try, set to maximum number of " +
88102 "channels the local node potentially has or had" ,
89103 )
@@ -105,6 +119,16 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`,
105119 "remote node's revocation base point, can be found " +
106120 "in a channel.backup file" ,
107121 )
122+ cc .cmd .Flags ().StringVar (
123+ & cc .ChannelBackup , "frombackup" , "" , "channel backup file to " +
124+ "read the channel information from" ,
125+ )
126+ cc .cmd .Flags ().StringVar (
127+ & cc .ChannelPoint , "channelpoint" , "" , "channel point to use " +
128+ "for locating the channel in the channel backup file " +
129+ "specified in the --frombackup flag, " +
130+ "format: txid:index" ,
131+ )
108132
109133 cc .rootKey = newRootKey (cc .cmd , "deriving keys" )
110134 cc .inputs = newInputFlags (cc .cmd )
@@ -126,25 +150,93 @@ func (c *sweepTimeLockManualCommand) Execute(_ *cobra.Command, _ []string) error
126150 return fmt .Errorf ("time lock addr is required" )
127151 }
128152
153+ var (
154+ startCsvLimit uint16
155+ maxCsvLimit = c .MaxCsvLimit
156+ startNumChannelsTotal uint16
157+ maxNumChannelsTotal = c .MaxNumChannelsTotal
158+ remoteRevocationBasePoint = c .RemoteRevocationBasePoint
159+ )
160+
161+ // We either support specifying the remote revocation base point
162+ // manually, in which case the CSV limit and number of channels are not
163+ // known, or we can use the channel backup file to get the required
164+ // information from there directly.
165+ switch {
166+ case c .RemoteRevocationBasePoint != "" :
167+ // Nothing to do here but continue below with the info provided
168+ // by the user.
169+
170+ case c .ChannelBackup != "" :
171+ if c .ChannelPoint == "" {
172+ return fmt .Errorf ("channel point is required with " +
173+ "--frombackup" )
174+ }
175+
176+ backupChan , err := lnd .ExtractChannel (
177+ extendedKey , chainParams , c .ChannelBackup ,
178+ c .ChannelPoint ,
179+ )
180+ if err != nil {
181+ return fmt .Errorf ("error extracting channel: %w" , err )
182+ }
183+
184+ remoteCfg := backupChan .RemoteChanCfg
185+ remoteRevocationBasePoint = remoteCfg .RevocationBasePoint .PubKey
186+
187+ startCsvLimit = remoteCfg .CsvDelay
188+ maxCsvLimit = startCsvLimit + 1
189+
190+ delayPath , err := lnd .ParsePath (
191+ backupChan .LocalChanCfg .DelayBasePoint .Path ,
192+ )
193+ if err != nil {
194+ return fmt .Errorf ("error parsing delay path: %w" , err )
195+ }
196+ if len (delayPath ) != 5 {
197+ return fmt .Errorf ("invalid delay path '%v'" , delayPath )
198+ }
199+
200+ startNumChannelsTotal = uint16 (delayPath [4 ])
201+ maxNumChannelsTotal = startNumChannelsTotal + 1
202+
203+ case c .ChannelBackup != "" && c .RemoteRevocationBasePoint != "" :
204+ return fmt .Errorf ("cannot use both --frombackup and " +
205+ "--remoterevbasepoint at the same time" )
206+
207+ default :
208+ return fmt .Errorf ("either --frombackup or " +
209+ "--remoterevbasepoint is required" )
210+ }
211+
129212 // The remote revocation base point must also be set and a valid EC
130213 // point.
131- remoteRevPoint , err := pubKeyFromHex (c . RemoteRevocationBasePoint )
214+ remoteRevPoint , err := pubKeyFromHex (remoteRevocationBasePoint )
132215 if err != nil {
133216 return fmt .Errorf ("invalid remote revocation base point: %w" ,
134217 err )
135218 }
136219
137220 return sweepTimeLockManual (
138221 extendedKey , c .APIURL , c .SweepAddr , c .TimeLockAddr ,
139- remoteRevPoint , c .MaxCsvLimit , c .MaxNumChansTotal ,
222+ remoteRevPoint , startCsvLimit , maxCsvLimit ,
223+ startNumChannelsTotal , maxNumChannelsTotal ,
140224 c .MaxNumChanUpdates , c .Publish , c .FeeRate ,
141225 )
142226}
143227
144228func sweepTimeLockManual (extendedKey * hdkeychain.ExtendedKey , apiURL string ,
145229 sweepAddr , timeLockAddr string , remoteRevPoint * btcec.PublicKey ,
146- maxCsvTimeout , maxNumChannels uint16 , maxNumChanUpdates uint64 ,
147- publish bool , feeRate uint32 ) error {
230+ startCsvTimeout , maxCsvTimeout , startNumChannels , maxNumChannels uint16 ,
231+ maxNumChanUpdates uint64 , publish bool , feeRate uint32 ) error {
232+
233+ log .Debugf ("Starting to brute force the time lock script, using: " +
234+ "remote_rev_base_point=%x, start_csv_limit=%d, " +
235+ "max_csv_limit=%d, start_num_channels=%d, " +
236+ "max_num_channels=%d, max_num_chan_updates=%d" ,
237+ remoteRevPoint .SerializeCompressed (), startCsvTimeout ,
238+ maxCsvTimeout , startNumChannels , maxNumChannels ,
239+ maxNumChanUpdates )
148240
149241 // First of all, we need to parse the lock addr and make sure we can
150242 // brute force the script with the information we have. If not, we can't
@@ -179,10 +271,10 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
179271 delayDesc * keychain.KeyDescriptor
180272 commitPoint * btcec.PublicKey
181273 )
182- for i := uint16 ( 0 ) ; i < maxNumChannels ; i ++ {
274+ for i := startNumChannels ; i < maxNumChannels ; i ++ {
183275 csvTimeout , script , scriptHash , commitPoint , delayDesc , err = tryKey (
184- baseKey , remoteRevPoint , maxCsvTimeout , lockScript ,
185- uint32 (i ), maxNumChanUpdates ,
276+ baseKey , remoteRevPoint , startCsvTimeout , maxCsvTimeout ,
277+ lockScript , uint32 (i ), maxNumChanUpdates ,
186278 )
187279
188280 if err == nil {
@@ -305,7 +397,7 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
305397}
306398
307399func tryKey (baseKey * hdkeychain.ExtendedKey , remoteRevPoint * btcec.PublicKey ,
308- maxCsvTimeout uint16 , lockScript []byte , idx uint32 ,
400+ startCsvTimeout , maxCsvTimeout uint16 , lockScript []byte , idx uint32 ,
309401 maxNumChanUpdates uint64 ) (int32 , []byte , []byte , * btcec.PublicKey ,
310402 * keychain.KeyDescriptor , error ) {
311403
@@ -338,7 +430,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
338430 // points and CSV values.
339431 csvTimeout , script , scriptHash , commitPoint , err := bruteForceDelayPoint (
340432 delayPrivKey .PubKey (), remoteRevPoint , revRoot , lockScript ,
341- maxCsvTimeout , maxNumChanUpdates ,
433+ startCsvTimeout , maxCsvTimeout , maxNumChanUpdates ,
342434 )
343435 if err == nil {
344436 return csvTimeout , script , scriptHash , commitPoint ,
@@ -403,7 +495,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
403495
404496 csvTimeout , script , scriptHash , commitPoint , err = bruteForceDelayPoint (
405497 delayPrivKey .PubKey (), remoteRevPoint , revRoot2 , lockScript ,
406- maxCsvTimeout , maxNumChanUpdates ,
498+ startCsvTimeout , maxCsvTimeout , maxNumChanUpdates ,
407499 )
408500 if err == nil {
409501 return csvTimeout , script , scriptHash , commitPoint ,
@@ -444,7 +536,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
444536
445537 csvTimeout , script , scriptHash , commitPoint , err = bruteForceDelayPoint (
446538 delayPrivKey .PubKey (), remoteRevPoint , revRoot3 , lockScript ,
447- maxCsvTimeout , maxNumChanUpdates ,
539+ startCsvTimeout , maxCsvTimeout , maxNumChanUpdates ,
448540 )
449541 if err == nil {
450542 return csvTimeout , script , scriptHash , commitPoint ,
@@ -462,8 +554,8 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
462554
463555func bruteForceDelayPoint (delayBase , revBase * btcec.PublicKey ,
464556 revRoot * shachain.RevocationProducer , lockScript []byte ,
465- maxCsvTimeout uint16 , maxChanUpdates uint64 ) (int32 , [] byte , [] byte ,
466- * btcec.PublicKey , error ) {
557+ startCsvTimeout , maxCsvTimeout uint16 , maxChanUpdates uint64 ) (int32 ,
558+ [] byte , [] byte , * btcec.PublicKey , error ) {
467559
468560 for i := uint64 (0 ); i < maxChanUpdates ; i ++ {
469561 revPreimage , err := revRoot .AtIndex (i )
@@ -475,7 +567,7 @@ func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey,
475567 csvTimeout , script , scriptHash , err := bruteForceDelay (
476568 input .TweakPubKey (delayBase , commitPoint ),
477569 input .DeriveRevocationPubkey (revBase , commitPoint ),
478- lockScript , maxCsvTimeout ,
570+ lockScript , startCsvTimeout , maxCsvTimeout ,
479571 )
480572
481573 if err != nil {
0 commit comments