You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
chantools/cmd/chantools/rescuefunding.go

215 lines
6.4 KiB
Go

package main
import (
"bytes"
"fmt"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcutil/psbt"
"github.com/guggero/chantools/lnd"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"path"
)
const (
MaxChannelLookup = 5000
// MultiSigWitnessSize 222 bytes
// - NumberOfWitnessElements: 1 byte
// - NilLength: 1 byte
// - sigAliceLength: 1 byte
// - sigAlice: 73 bytes
// - sigBobLength: 1 byte
// - sigBob: 73 bytes
// - WitnessScriptLength: 1 byte
// - WitnessScript (MultiSig)
MultiSigWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + input.MultiSigSize
)
var (
PsbtKeyTypeOutputMissingSigPubkey = []byte{0xcc}
)
type rescueFundingCommand struct {
RootKey string `long:"rootkey" description:"BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed."`
ChannelDB string `long:"channeldb" description:"The lnd channel.db file to rescue a channel from. Must contain the pending channel specified with --channelpoint."`
ChannelPoint string `long:"channelpoint" description:"The funding transaction outpoint of the channel to rescue (<txid>:<txindex>) as it is recorded in the DB."`
ConfirmedOutPoint string `long:"confirmedchannelpoint" description:"The channel outpoint that got confirmed on chain (<txid>:<txindex>). Normally this is the same as the --channelpoint so it will be set to that value if this is left empty."`
SweepAddr string `long:"sweepaddr" description:"The address to sweep the rescued funds to."`
SatPerByte int64 `long:"satperbyte" description:"The fee rate to use in satoshis/vByte."`
}
func (c *rescueFundingCommand) Execute(_ []string) error {
setupChainParams(cfg)
var (
extendedKey *hdkeychain.ExtendedKey
chainOp *wire.OutPoint
err error
)
// Check that root key is valid or fall back to console input.
switch {
case c.RootKey != "":
extendedKey, err = hdkeychain.NewKeyFromString(c.RootKey)
default:
extendedKey, _, err = lnd.ReadAezeedFromTerminal(
chainParams,
)
}
if err != nil {
return fmt.Errorf("error reading root key: %v", err)
}
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
// Check that we have a channel DB.
if c.ChannelDB == "" {
return fmt.Errorf("channel DB is required")
}
db, err := channeldb.Open(
path.Dir(c.ChannelDB), path.Base(c.ChannelDB),
channeldb.OptionSetSyncFreelist(true),
channeldb.OptionReadOnly(true),
)
if err != nil {
return fmt.Errorf("error opening rescue DB: %v", err)
}
// Parse channel point of channel to rescue as known to the DB.
dbOp, err := lnd.ParseOutpoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing channel point: %v", err)
}
// Parse channel point of channel to rescue as confirmed on chain (if
// different).
if len(c.ConfirmedOutPoint) == 0 {
chainOp = dbOp
} else {
chainOp, err = lnd.ParseOutpoint(c.ChannelPoint)
if err != nil {
return fmt.Errorf("error parsing confirmed channel "+
"point: %v", err)
}
}
// Make sure the sweep addr is a P2WKH address so we can do accurate
// fee estimation.
sweepScript, err := lnd.GetP2WPKHScript(c.SweepAddr, chainParams)
if err != nil {
return fmt.Errorf("error parsing sweep addr: %v", err)
}
if c.SatPerByte < 0 {
return fmt.Errorf("satperbyte must be greater than 0")
}
return rescueFunding(
db, signer, dbOp, chainOp, sweepScript,
btcutil.Amount(c.SatPerByte),
)
}
func rescueFunding(db *channeldb.DB, signer *lnd.Signer, dbFundingPoint,
chainPoint *wire.OutPoint, sweepPKScript []byte,
feeRate btcutil.Amount) error {
// First of all make sure the channel can be found in the DB.
pendingChan, err := db.FetchChannel(*dbFundingPoint)
if err != nil {
return fmt.Errorf("error loading pending channel %s from DB: "+
"%v", dbFundingPoint, err)
}
// Prepare the wire part of the PSBT.
txIn := &wire.TxIn{
PreviousOutPoint: *chainPoint,
Sequence: 0,
}
txOut := &wire.TxOut{
PkScript: sweepPKScript,
}
// Locate the output in the funding TX.
utxo := pendingChan.FundingTxn.TxOut[dbFundingPoint.Index]
// We should also be able to create the funding script from the two
// multisig keys.
localKey := pendingChan.LocalChanCfg.MultiSigKey.PubKey
remoteKey := pendingChan.RemoteChanCfg.MultiSigKey.PubKey
witnessScript, fundingTxOut, err := input.GenFundingPkScript(
localKey.SerializeCompressed(), remoteKey.SerializeCompressed(),
utxo.Value,
)
if err != nil {
return fmt.Errorf("could not derive funding script: %v", err)
}
// Some last sanity check that we're working with the correct data.
if !bytes.Equal(fundingTxOut.PkScript, utxo.PkScript) {
return fmt.Errorf("funding output script does not match UTXO")
}
// Now the rest of the known data for the PSBT.
pIn := psbt.PInput{
WitnessUtxo: utxo,
WitnessScript: witnessScript,
Unknowns: []*psbt.Unknown{{
// We add the public key the other party needs to sign
// with as a proprietary field so we can easily read it
// out with the signrescuefunding command.
Key: PsbtKeyTypeOutputMissingSigPubkey,
Value: remoteKey.SerializeCompressed(),
}},
}
// Estimate the transaction weight so we can do the fee estimation.
var estimator input.TxWeightEstimator
estimator.AddWitnessInput(MultiSigWitnessSize)
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
txOut.Value = utxo.Value - int64(totalFee)
// Let's now create the PSBT as we have everything we need so far.
wireTx := &wire.MsgTx{
Version: 2,
TxIn: []*wire.TxIn{txIn},
TxOut: []*wire.TxOut{txOut},
}
packet, err := psbt.NewFromUnsignedTx(wireTx)
if err != nil {
return fmt.Errorf("error creating PSBT: %v", err)
}
packet.Inputs[0] = pIn
// Now we add our partial signature.
err = signer.AddPartialSignature(
packet, pendingChan.LocalChanCfg.MultiSigKey, utxo,
witnessScript, 0,
)
if err != nil {
return fmt.Errorf("error adding partial signature: %v", err)
}
// We're done, we can now output the finished PSBT.
base64, err := packet.B64Encode()
if err != nil {
return fmt.Errorf("error encoding PSBT: %v", err)
}
fmt.Printf("Partially signed transaction created. Send this to the "+
"other peer \nand ask them to run the 'chantools "+
"signrescuefunding' command: \n\n%s\n\n", base64)
return nil
}