Skip to content

Commit fd18186

Browse files
authored
Merge pull request #91 from lightninglabs/sweeptimelockmanual-backup-file
sweeptimelockmanual: allow specifying the backup file directly
2 parents c89cede + a13262f commit fd18186

File tree

4 files changed

+146
-22
lines changed

4 files changed

+146
-22
lines changed

cmd/chantools/sweeptimelock.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
239239
), input.DeriveRevocationPubkey(
240240
target.revocationBasePoint,
241241
target.commitPoint,
242-
), target.lockScript, maxCsvTimeout,
242+
), target.lockScript, 0, maxCsvTimeout,
243243
)
244244
if err != nil {
245245
log.Errorf("Could not create matching script for %s "+
@@ -346,14 +346,14 @@ func pubKeyFromHex(pubKeyHex string) (*btcec.PublicKey, error) {
346346
}
347347

348348
func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey,
349-
targetScript []byte, maxCsvTimeout uint16) (int32, []byte, []byte,
350-
error) {
349+
targetScript []byte, startCsvTimeout, maxCsvTimeout uint16) (int32,
350+
[]byte, []byte, error) {
351351

352352
if len(targetScript) != 34 {
353353
return 0, nil, nil, fmt.Errorf("invalid target script: %s",
354354
targetScript)
355355
}
356-
for i := uint16(0); i <= maxCsvTimeout; i++ {
356+
for i := startCsvTimeout; i <= maxCsvTimeout; i++ {
357357
s, err := input.CommitScriptToSelf(
358358
uint32(i), delayPubkey, revocationPubkey,
359359
)

cmd/chantools/sweeptimelockmanual.go

Lines changed: 109 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
5659
To get the value for --remoterevbasepoint you must use the dumpbackup command,
5760
then 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+
5965
To get the value for --timelockaddr you must look up the channel's funding
6066
output on chain, then follow it to the force close output. The time locked
6167
address 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

144228
func 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

307399
func 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

463555
func 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 {

cmd/chantools/sweeptimelockmanual_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ func TestSweepTimeLockManual(t *testing.T) {
8686
revPubKey, _ := btcec.ParsePubKey(revPubKeyBytes)
8787

8888
_, _, _, _, _, err = tryKey(
89-
baseKey, revPubKey, defaultCsvLimit, lockScript,
89+
baseKey, revPubKey, 0, defaultCsvLimit, lockScript,
9090
tc.keyIndex, 500,
9191
)
9292
require.NoError(t, err)

lnd/chanbackup.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import (
44
"bytes"
55
"fmt"
66

7+
"github.com/btcsuite/btcd/btcutil/hdkeychain"
8+
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/lightninglabs/chantools/dump"
710
"github.com/lightningnetwork/lnd/chanbackup"
811
"github.com/lightningnetwork/lnd/channeldb"
912
"github.com/lightningnetwork/lnd/keychain"
@@ -35,3 +38,32 @@ func CreateChannelBackup(db *channeldb.DB, multiFile *chanbackup.MultiFile,
3538
}
3639
return nil
3740
}
41+
42+
// ExtractChannel extracts a single channel from the given backup file and
43+
// returns it as a dump.BackupSingle struct.
44+
func ExtractChannel(extendedKey *hdkeychain.ExtendedKey,
45+
chainParams *chaincfg.Params, multiFilePath,
46+
channelPoint string) (*dump.BackupSingle, error) {
47+
48+
multiFile := chanbackup.NewMultiFile(multiFilePath)
49+
keyRing := &HDKeyRing{
50+
ExtendedKey: extendedKey,
51+
ChainParams: chainParams,
52+
}
53+
54+
multi, err := multiFile.ExtractMulti(keyRing)
55+
if err != nil {
56+
return nil, fmt.Errorf("could not extract multi file: %w", err)
57+
}
58+
59+
channels := dump.BackupDump(multi, chainParams)
60+
for _, channel := range channels {
61+
channel := channel
62+
63+
if channel.FundingOutpoint == channelPoint {
64+
return &channel, nil
65+
}
66+
}
67+
68+
return nil, fmt.Errorf("channel %s not found in backup", channelPoint)
69+
}

0 commit comments

Comments
 (0)