@@ -37,6 +37,9 @@ type sweepTimeLockManualCommand struct {
3737 MaxNumChannelsTotal uint16
3838 MaxNumChanUpdates uint64
3939
40+ ChannelBackup string
41+ ChannelPoint string
42+
4043 rootKey * rootKey
4144 inputs * inputFlags
4245 cmd * cobra.Command
@@ -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 }
@@ -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,17 +150,77 @@ 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 , 0 , c .MaxCsvLimit , 0 , c .MaxNumChannelsTotal ,
222+ remoteRevPoint , startCsvLimit , maxCsvLimit ,
223+ startNumChannelsTotal , maxNumChannelsTotal ,
140224 c .MaxNumChanUpdates , c .Publish , c .FeeRate ,
141225 )
142226}
@@ -146,6 +230,14 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
146230 startCsvTimeout , maxCsvTimeout , startNumChannels , maxNumChannels uint16 ,
147231 maxNumChanUpdates uint64 , publish bool , feeRate uint32 ) error {
148232
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 )
240+
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
151243 // continue anyway.
0 commit comments