diff --git a/cmd/chantools/sweeptimelock.go b/cmd/chantools/sweeptimelock.go index 0aeeb80..35e3d27 100644 --- a/cmd/chantools/sweeptimelock.go +++ b/cmd/chantools/sweeptimelock.go @@ -14,6 +14,7 @@ import ( "github.com/guggero/chantools/dataformat" "github.com/guggero/chantools/lnd" "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/spf13/cobra" ) @@ -104,28 +105,28 @@ func (c *sweepTimeLockCommand) Execute(_ *cobra.Command, _ []string) error { if c.FeeRate == 0 { c.FeeRate = defaultFeeSatPerVByte } - return sweepTimeLock( + return sweepTimeLockFromSummary( extendedKey, c.APIURL, entries, c.SweepAddr, c.MaxCsvLimit, c.Publish, c.FeeRate, ) } -func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, +type sweepTarget struct { + channelPoint string + txid chainhash.Hash + index uint32 + lockScript []byte + value int64 + commitPoint *btcec.PublicKey + revocationBasePoint *btcec.PublicKey + delayBasePointDesc *keychain.KeyDescriptor +} + +func sweepTimeLockFromSummary(extendedKey *hdkeychain.ExtendedKey, apiURL string, entries []*dataformat.SummaryEntry, sweepAddr string, maxCsvTimeout uint16, publish bool, feeRate uint16) error { - // Create signer and transaction template. - signer := &lnd.Signer{ - ExtendedKey: extendedKey, - ChainParams: chainParams, - } - api := &btc.ExplorerAPI{BaseURL: apiURL} - - sweepTx := wire.NewMsgTx(2) - totalOutputValue := int64(0) - signDescs := make([]*input.SignDescriptor, 0) - var estimator input.TxWeightEstimator - + targets := make([]*sweepTarget, 0, len(entries)) for _, entry := range entries { // Skip entries that can't be swept. if entry.ForceClose == nil || @@ -169,14 +170,14 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, } revBase, err := pubKeyFromHex(fc.RevocationBasePoint.PubKey) if err != nil { - return fmt.Errorf("error parsing commit point: %v", err) + return fmt.Errorf("error parsing revocation base "+ + "point: %v", err) } - delayDesc := fc.DelayBasePoint.Desc() - delayPrivKey, err := signer.FetchPrivKey(delayDesc) + delayDesc, err := fc.DelayBasePoint.Desc() if err != nil { - return fmt.Errorf("error getting private key: %v", err) + return fmt.Errorf("error parsing delay base point: %v", + err) } - delayBase := delayPrivKey.PubKey() lockScript, err := hex.DecodeString(fc.Outs[txindex].Script) if err != nil { @@ -184,29 +185,69 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, err) } + // Create the transaction input. + txHash, err := chainhash.NewHashFromStr(fc.TXID) + if err != nil { + return fmt.Errorf("error parsing tx hash: %v", err) + } + + targets = append(targets, &sweepTarget{ + channelPoint: entry.ChannelPoint, + txid: *txHash, + index: uint32(txindex), + lockScript: lockScript, + value: int64(fc.Outs[txindex].Value), + commitPoint: commitPoint, + revocationBasePoint: revBase, + delayBasePointDesc: delayDesc, + }) + } + + return sweepTimeLock( + extendedKey, apiURL, targets, sweepAddr, maxCsvTimeout, publish, + feeRate, + ) +} + +func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, + targets []*sweepTarget, sweepAddr string, maxCsvTimeout uint16, + publish bool, feeRate uint16) error { + + // Create signer and transaction template. + signer := &lnd.Signer{ + ExtendedKey: extendedKey, + ChainParams: chainParams, + } + api := &btc.ExplorerAPI{BaseURL: apiURL} + + sweepTx := wire.NewMsgTx(2) + totalOutputValue := int64(0) + signDescs := make([]*input.SignDescriptor, 0) + var estimator input.TxWeightEstimator + + for _, target := range targets { // We can't rely on the CSV delay of the channel DB to be // correct. But it doesn't cost us a lot to just brute force it. csvTimeout, script, scriptHash, err := bruteForceDelay( - input.TweakPubKey(delayBase, commitPoint), - input.DeriveRevocationPubkey(revBase, commitPoint), - lockScript, maxCsvTimeout, + input.TweakPubKey( + target.delayBasePointDesc.PubKey, + target.commitPoint, + ), input.DeriveRevocationPubkey( + target.revocationBasePoint, + target.commitPoint, + ), target.lockScript, maxCsvTimeout, ) if err != nil { log.Errorf("Could not create matching script for %s "+ - "or csv too high: %v", entry.ChannelPoint, - err) + "or csv too high: %v", target.channelPoint, err) continue } // Create the transaction input. - txHash, err := chainhash.NewHashFromStr(fc.TXID) - if err != nil { - return fmt.Errorf("error parsing tx hash: %v", err) - } sweepTx.TxIn = append(sweepTx.TxIn, &wire.TxIn{ PreviousOutPoint: wire.OutPoint{ - Hash: *txHash, - Index: uint32(txindex), + Hash: target.txid, + Index: target.index, }, Sequence: input.LockTimeToSequence( false, uint32(csvTimeout), @@ -215,18 +256,19 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, // Create the sign descriptor for the input. signDesc := &input.SignDescriptor{ - KeyDesc: *delayDesc, + KeyDesc: *target.delayBasePointDesc, SingleTweak: input.SingleTweakBytes( - commitPoint, delayBase, + target.commitPoint, + target.delayBasePointDesc.PubKey, ), WitnessScript: script, Output: &wire.TxOut{ PkScript: scriptHash, - Value: int64(fc.Outs[txindex].Value), + Value: target.value, }, HashType: txscript.SigHashAll, } - totalOutputValue += int64(fc.Outs[txindex].Value) + totalOutputValue += target.value signDescs = append(signDescs, signDesc) // Account for the input weight. diff --git a/dataformat/summary.go b/dataformat/summary.go index 1c4cd35..d69d885 100644 --- a/dataformat/summary.go +++ b/dataformat/summary.go @@ -1,6 +1,9 @@ package dataformat import ( + "encoding/hex" + "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/keychain" ) @@ -20,13 +23,25 @@ type BasePoint struct { PubKey string `json:"pubkey"` } -func (b *BasePoint) Desc() *keychain.KeyDescriptor { +func (b *BasePoint) Desc() (*keychain.KeyDescriptor, error) { + pubKeyHex, err := hex.DecodeString(b.PubKey) + if err != nil { + return nil, fmt.Errorf("error decoding base point pubkey: %v", + err) + } + pubKey, err := btcec.ParsePubKey(pubKeyHex, btcec.S256()) + if err != nil { + return nil, fmt.Errorf("error parsing base point pubkey: %v", + err) + } + return &keychain.KeyDescriptor{ KeyLocator: keychain.KeyLocator{ Family: keychain.KeyFamily(b.Family), Index: b.Index, }, - } + PubKey: pubKey, + }, nil } type Out struct {