Merge pull request #91 from lightninglabs/sweeptimelockmanual-backup-file

sweeptimelockmanual: allow specifying the backup file directly
pull/109/head
Oliver Gugger 5 months ago committed by GitHub
commit fd18186a82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -239,7 +239,7 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
), input.DeriveRevocationPubkey( ), input.DeriveRevocationPubkey(
target.revocationBasePoint, target.revocationBasePoint,
target.commitPoint, target.commitPoint,
), target.lockScript, maxCsvTimeout, ), target.lockScript, 0, maxCsvTimeout,
) )
if err != nil { if err != nil {
log.Errorf("Could not create matching script for %s "+ log.Errorf("Could not create matching script for %s "+
@ -346,14 +346,14 @@ func pubKeyFromHex(pubKeyHex string) (*btcec.PublicKey, error) {
} }
func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey, func bruteForceDelay(delayPubkey, revocationPubkey *btcec.PublicKey,
targetScript []byte, maxCsvTimeout uint16) (int32, []byte, []byte, targetScript []byte, startCsvTimeout, maxCsvTimeout uint16) (int32,
error) { []byte, []byte, error) {
if len(targetScript) != 34 { if len(targetScript) != 34 {
return 0, nil, nil, fmt.Errorf("invalid target script: %s", return 0, nil, nil, fmt.Errorf("invalid target script: %s",
targetScript) targetScript)
} }
for i := uint16(0); i <= maxCsvTimeout; i++ { for i := startCsvTimeout; i <= maxCsvTimeout; i++ {
s, err := input.CommitScriptToSelf( s, err := input.CommitScriptToSelf(
uint32(i), delayPubkey, revocationPubkey, uint32(i), delayPubkey, revocationPubkey,
) )

@ -34,8 +34,11 @@ type sweepTimeLockManualCommand struct {
TimeLockAddr string TimeLockAddr string
RemoteRevocationBasePoint string RemoteRevocationBasePoint string
MaxNumChansTotal uint16 MaxNumChannelsTotal uint16
MaxNumChanUpdates uint64 MaxNumChanUpdates uint64
ChannelBackup string
ChannelPoint string
rootKey *rootKey rootKey *rootKey
inputs *inputFlags inputs *inputFlags
@ -56,6 +59,9 @@ and only the channel.backup file is available.
To get the value for --remoterevbasepoint you must use the dumpbackup command, To get the value for --remoterevbasepoint you must use the dumpbackup command,
then look up the value for RemoteChanCfg -> RevocationBasePoint -> PubKey. then look up the value for RemoteChanCfg -> RevocationBasePoint -> PubKey.
Alternatively you can directly use the --frombackup and --channelpoint flags to
pull the required information from the given channel.backup file automatically.
To get the value for --timelockaddr you must look up the channel's funding To get the value for --timelockaddr you must look up the channel's funding
output on chain, then follow it to the force close output. The time locked output on chain, then follow it to the force close output. The time locked
address is always the one that's longer (because it's P2WSH and not P2PKH).`, 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).`,
--timelockaddr bc1q............ \ --timelockaddr bc1q............ \
--remoterevbasepoint 03xxxxxxx \ --remoterevbasepoint 03xxxxxxx \
--feerate 10 \ --feerate 10 \
--publish
chantools sweeptimelockmanual \
--sweepaddr bc1q..... \
--timelockaddr bc1q............ \
--frombackup channel.backup \
--channelpoint f39310xxxxxxxxxx:1 \
--feerate 10 \
--publish`, --publish`,
RunE: cc.Execute, RunE: cc.Execute,
} }
@ -83,7 +97,7 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`,
"limit to use", "limit to use",
) )
cc.cmd.Flags().Uint16Var( cc.cmd.Flags().Uint16Var(
&cc.MaxNumChansTotal, "maxnumchanstotal", maxKeys, "maximum "+ &cc.MaxNumChannelsTotal, "maxnumchanstotal", maxKeys, "maximum "+
"number of keys to try, set to maximum number of "+ "number of keys to try, set to maximum number of "+
"channels the local node potentially has or had", "channels the local node potentially has or had",
) )
@ -105,6 +119,16 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`,
"remote node's revocation base point, can be found "+ "remote node's revocation base point, can be found "+
"in a channel.backup file", "in a channel.backup file",
) )
cc.cmd.Flags().StringVar(
&cc.ChannelBackup, "frombackup", "", "channel backup file to "+
"read the channel information from",
)
cc.cmd.Flags().StringVar(
&cc.ChannelPoint, "channelpoint", "", "channel point to use "+
"for locating the channel in the channel backup file "+
"specified in the --frombackup flag, "+
"format: txid:index",
)
cc.rootKey = newRootKey(cc.cmd, "deriving keys") cc.rootKey = newRootKey(cc.cmd, "deriving keys")
cc.inputs = newInputFlags(cc.cmd) cc.inputs = newInputFlags(cc.cmd)
@ -126,9 +150,68 @@ func (c *sweepTimeLockManualCommand) Execute(_ *cobra.Command, _ []string) error
return fmt.Errorf("time lock addr is required") return fmt.Errorf("time lock addr is required")
} }
var (
startCsvLimit uint16
maxCsvLimit = c.MaxCsvLimit
startNumChannelsTotal uint16
maxNumChannelsTotal = c.MaxNumChannelsTotal
remoteRevocationBasePoint = c.RemoteRevocationBasePoint
)
// We either support specifying the remote revocation base point
// manually, in which case the CSV limit and number of channels are not
// known, or we can use the channel backup file to get the required
// information from there directly.
switch {
case c.RemoteRevocationBasePoint != "":
// Nothing to do here but continue below with the info provided
// by the user.
case c.ChannelBackup != "":
if c.ChannelPoint == "" {
return fmt.Errorf("channel point is required with " +
"--frombackup")
}
backupChan, err := lnd.ExtractChannel(
extendedKey, chainParams, c.ChannelBackup,
c.ChannelPoint,
)
if err != nil {
return fmt.Errorf("error extracting channel: %w", err)
}
remoteCfg := backupChan.RemoteChanCfg
remoteRevocationBasePoint = remoteCfg.RevocationBasePoint.PubKey
startCsvLimit = remoteCfg.CsvDelay
maxCsvLimit = startCsvLimit + 1
delayPath, err := lnd.ParsePath(
backupChan.LocalChanCfg.DelayBasePoint.Path,
)
if err != nil {
return fmt.Errorf("error parsing delay path: %w", err)
}
if len(delayPath) != 5 {
return fmt.Errorf("invalid delay path '%v'", delayPath)
}
startNumChannelsTotal = uint16(delayPath[4])
maxNumChannelsTotal = startNumChannelsTotal + 1
case c.ChannelBackup != "" && c.RemoteRevocationBasePoint != "":
return fmt.Errorf("cannot use both --frombackup and " +
"--remoterevbasepoint at the same time")
default:
return fmt.Errorf("either --frombackup or " +
"--remoterevbasepoint is required")
}
// The remote revocation base point must also be set and a valid EC // The remote revocation base point must also be set and a valid EC
// point. // point.
remoteRevPoint, err := pubKeyFromHex(c.RemoteRevocationBasePoint) remoteRevPoint, err := pubKeyFromHex(remoteRevocationBasePoint)
if err != nil { if err != nil {
return fmt.Errorf("invalid remote revocation base point: %w", return fmt.Errorf("invalid remote revocation base point: %w",
err) err)
@ -136,15 +219,24 @@ func (c *sweepTimeLockManualCommand) Execute(_ *cobra.Command, _ []string) error
return sweepTimeLockManual( return sweepTimeLockManual(
extendedKey, c.APIURL, c.SweepAddr, c.TimeLockAddr, extendedKey, c.APIURL, c.SweepAddr, c.TimeLockAddr,
remoteRevPoint, c.MaxCsvLimit, c.MaxNumChansTotal, remoteRevPoint, startCsvLimit, maxCsvLimit,
startNumChannelsTotal, maxNumChannelsTotal,
c.MaxNumChanUpdates, c.Publish, c.FeeRate, c.MaxNumChanUpdates, c.Publish, c.FeeRate,
) )
} }
func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string, func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
sweepAddr, timeLockAddr string, remoteRevPoint *btcec.PublicKey, sweepAddr, timeLockAddr string, remoteRevPoint *btcec.PublicKey,
maxCsvTimeout, maxNumChannels uint16, maxNumChanUpdates uint64, startCsvTimeout, maxCsvTimeout, startNumChannels, maxNumChannels uint16,
publish bool, feeRate uint32) error { maxNumChanUpdates uint64, publish bool, feeRate uint32) error {
log.Debugf("Starting to brute force the time lock script, using: "+
"remote_rev_base_point=%x, start_csv_limit=%d, "+
"max_csv_limit=%d, start_num_channels=%d, "+
"max_num_channels=%d, max_num_chan_updates=%d",
remoteRevPoint.SerializeCompressed(), startCsvTimeout,
maxCsvTimeout, startNumChannels, maxNumChannels,
maxNumChanUpdates)
// First of all, we need to parse the lock addr and make sure we can // First of all, we need to parse the lock addr and make sure we can
// brute force the script with the information we have. If not, we can't // 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,
delayDesc *keychain.KeyDescriptor delayDesc *keychain.KeyDescriptor
commitPoint *btcec.PublicKey commitPoint *btcec.PublicKey
) )
for i := uint16(0); i < maxNumChannels; i++ { for i := startNumChannels; i < maxNumChannels; i++ {
csvTimeout, script, scriptHash, commitPoint, delayDesc, err = tryKey( csvTimeout, script, scriptHash, commitPoint, delayDesc, err = tryKey(
baseKey, remoteRevPoint, maxCsvTimeout, lockScript, baseKey, remoteRevPoint, startCsvTimeout, maxCsvTimeout,
uint32(i), maxNumChanUpdates, lockScript, uint32(i), maxNumChanUpdates,
) )
if err == nil { if err == nil {
@ -305,7 +397,7 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
} }
func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey, func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
maxCsvTimeout uint16, lockScript []byte, idx uint32, startCsvTimeout, maxCsvTimeout uint16, lockScript []byte, idx uint32,
maxNumChanUpdates uint64) (int32, []byte, []byte, *btcec.PublicKey, maxNumChanUpdates uint64) (int32, []byte, []byte, *btcec.PublicKey,
*keychain.KeyDescriptor, error) { *keychain.KeyDescriptor, error) {
@ -338,7 +430,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
// points and CSV values. // points and CSV values.
csvTimeout, script, scriptHash, commitPoint, err := bruteForceDelayPoint( csvTimeout, script, scriptHash, commitPoint, err := bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot, lockScript, delayPrivKey.PubKey(), remoteRevPoint, revRoot, lockScript,
maxCsvTimeout, maxNumChanUpdates, startCsvTimeout, maxCsvTimeout, maxNumChanUpdates,
) )
if err == nil { if err == nil {
return csvTimeout, script, scriptHash, commitPoint, return csvTimeout, script, scriptHash, commitPoint,
@ -403,7 +495,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint( csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot2, lockScript, delayPrivKey.PubKey(), remoteRevPoint, revRoot2, lockScript,
maxCsvTimeout, maxNumChanUpdates, startCsvTimeout, maxCsvTimeout, maxNumChanUpdates,
) )
if err == nil { if err == nil {
return csvTimeout, script, scriptHash, commitPoint, return csvTimeout, script, scriptHash, commitPoint,
@ -444,7 +536,7 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint( csvTimeout, script, scriptHash, commitPoint, err = bruteForceDelayPoint(
delayPrivKey.PubKey(), remoteRevPoint, revRoot3, lockScript, delayPrivKey.PubKey(), remoteRevPoint, revRoot3, lockScript,
maxCsvTimeout, maxNumChanUpdates, startCsvTimeout, maxCsvTimeout, maxNumChanUpdates,
) )
if err == nil { if err == nil {
return csvTimeout, script, scriptHash, commitPoint, return csvTimeout, script, scriptHash, commitPoint,
@ -462,8 +554,8 @@ func tryKey(baseKey *hdkeychain.ExtendedKey, remoteRevPoint *btcec.PublicKey,
func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey, func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey,
revRoot *shachain.RevocationProducer, lockScript []byte, revRoot *shachain.RevocationProducer, lockScript []byte,
maxCsvTimeout uint16, maxChanUpdates uint64) (int32, []byte, []byte, startCsvTimeout, maxCsvTimeout uint16, maxChanUpdates uint64) (int32,
*btcec.PublicKey, error) { []byte, []byte, *btcec.PublicKey, error) {
for i := uint64(0); i < maxChanUpdates; i++ { for i := uint64(0); i < maxChanUpdates; i++ {
revPreimage, err := revRoot.AtIndex(i) revPreimage, err := revRoot.AtIndex(i)
@ -475,7 +567,7 @@ func bruteForceDelayPoint(delayBase, revBase *btcec.PublicKey,
csvTimeout, script, scriptHash, err := bruteForceDelay( csvTimeout, script, scriptHash, err := bruteForceDelay(
input.TweakPubKey(delayBase, commitPoint), input.TweakPubKey(delayBase, commitPoint),
input.DeriveRevocationPubkey(revBase, commitPoint), input.DeriveRevocationPubkey(revBase, commitPoint),
lockScript, maxCsvTimeout, lockScript, startCsvTimeout, maxCsvTimeout,
) )
if err != nil { if err != nil {

@ -86,7 +86,7 @@ func TestSweepTimeLockManual(t *testing.T) {
revPubKey, _ := btcec.ParsePubKey(revPubKeyBytes) revPubKey, _ := btcec.ParsePubKey(revPubKeyBytes)
_, _, _, _, _, err = tryKey( _, _, _, _, _, err = tryKey(
baseKey, revPubKey, defaultCsvLimit, lockScript, baseKey, revPubKey, 0, defaultCsvLimit, lockScript,
tc.keyIndex, 500, tc.keyIndex, 500,
) )
require.NoError(t, err) require.NoError(t, err)

@ -4,6 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/chantools/dump"
"github.com/lightningnetwork/lnd/chanbackup" "github.com/lightningnetwork/lnd/chanbackup"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/keychain"
@ -35,3 +38,32 @@ func CreateChannelBackup(db *channeldb.DB, multiFile *chanbackup.MultiFile,
} }
return nil return nil
} }
// ExtractChannel extracts a single channel from the given backup file and
// returns it as a dump.BackupSingle struct.
func ExtractChannel(extendedKey *hdkeychain.ExtendedKey,
chainParams *chaincfg.Params, multiFilePath,
channelPoint string) (*dump.BackupSingle, error) {
multiFile := chanbackup.NewMultiFile(multiFilePath)
keyRing := &HDKeyRing{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
multi, err := multiFile.ExtractMulti(keyRing)
if err != nil {
return nil, fmt.Errorf("could not extract multi file: %w", err)
}
channels := dump.BackupDump(multi, chainParams)
for _, channel := range channels {
channel := channel
if channel.FundingOutpoint == channelPoint {
return &channel, nil
}
}
return nil, fmt.Errorf("channel %s not found in backup", channelPoint)
}

Loading…
Cancel
Save