multi: standardize sweep/change addr support

pull/107/head
Oliver Gugger 4 months ago
parent 7e3ea44fd4
commit a05962e03e
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -166,11 +166,21 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string,
sweepAddr string, publish bool, feeRate uint32, minExpiry,
maxNumBlocks, maxNumAccounts, maxNumBatchKeys uint32) error {
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
var (
estimator input.TxWeightEstimator
signer = &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
api = newExplorerAPI(apiURL)
)
sweepScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
api := newExplorerAPI(apiURL)
tx, err := api.Transaction(outpoint.Hash.String())
if err != nil {
@ -246,7 +256,6 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string,
// Calculate the fee based on the given fee rate and our weight
// estimation.
var (
estimator input.TxWeightEstimator
prevOutFetcher = txscript.NewCannedPrevOutputFetcher(
pkScript, sweepValue,
)
@ -282,15 +291,10 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string,
signDesc.HashType = txscript.SigHashDefault
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
}
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
// Add our sweep destination output.
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
sweepTx.TxOut = []*wire.TxOut{{
Value: sweepValue - int64(totalFee),
PkScript: sweepScript,

@ -105,8 +105,8 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
privKeys := make([]*secp256k1.PrivateKey, 0, len(c.InputOutpoints))
// Get the addresses for the inputs.
for _, input := range c.InputOutpoints {
addrString, err := api.Address(input)
for _, inputOutpoint := range c.InputOutpoints {
addrString, err := api.Address(inputOutpoint)
if err != nil {
return err
}
@ -118,12 +118,12 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
addresses = append(addresses, addr)
txHash, err := chainhash.NewHashFromStr(input[:64])
txHash, err := chainhash.NewHashFromStr(inputOutpoint[:64])
if err != nil {
return err
}
vout, err := strconv.Atoi(input[65:])
vout, err := strconv.Atoi(inputOutpoint[65:])
if err != nil {
return err
}
@ -144,7 +144,13 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
}
// Start with the txweight estimator.
estimator := input.TxWeightEstimator{}
var estimator input.TxWeightEstimator
sweepScript, err := lnd.PrepareWalletAddress(
c.SweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
// Find the key for the given addresses and add their
// output weight to the tx estimator.
@ -169,7 +175,9 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
return err
}
estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
estimator.AddTaprootKeySpendInput(
txscript.SigHashDefault,
)
default:
return fmt.Errorf("address type %T not supported", addr)
@ -189,47 +197,32 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
// Next get the full value of the inputs.
var totalInput btcutil.Amount
for _, input := range outpoints {
for _, outpoint := range outpoints {
// Get the transaction.
tx, err := api.Transaction(input.Hash.String())
tx, err := api.Transaction(outpoint.Hash.String())
if err != nil {
return err
}
value := tx.Vout[input.Index].Value
value := tx.Vout[outpoint.Index].Value
// Get the output index.
totalInput += btcutil.Amount(value)
scriptPubkey, err := hex.DecodeString(tx.Vout[input.Index].ScriptPubkey)
scriptPubkey, err := hex.DecodeString(
tx.Vout[outpoint.Index].ScriptPubkey,
)
if err != nil {
return err
}
// Add the output to the map.
prevOuts[*input] = &wire.TxOut{
prevOuts[*outpoint] = &wire.TxOut{
Value: int64(value),
PkScript: scriptPubkey,
}
}
// Calculate the fee.
sweepAddr, err := btcutil.DecodeAddress(c.SweepAddr, chainParams)
if err != nil {
return err
}
switch sweepAddr.(type) {
case *btcutil.AddressWitnessPubKeyHash:
estimator.AddP2WKHOutput()
case *btcutil.AddressTaproot:
estimator.AddP2TROutput()
default:
return fmt.Errorf("address type %T not supported", sweepAddr)
}
// Calculate the fee.
feeRateKWeight := chainfee.SatPerKVByte(1000 * c.FeeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
@ -238,14 +231,8 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
tx := wire.NewMsgTx(2)
// Add the inputs.
for _, input := range outpoints {
tx.AddTxIn(wire.NewTxIn(input, nil, nil))
}
// Add the output.
sweepScript, err := txscript.PayToAddrScript(sweepAddr)
if err != nil {
return err
for _, outpoint := range outpoints {
tx.AddTxIn(wire.NewTxIn(outpoint, nil, nil))
}
tx.AddTxOut(wire.NewTxOut(int64(totalInput-totalFee), sweepScript))
@ -285,7 +272,8 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
}
default:
return fmt.Errorf("address type %T not supported", addresses[i])
return fmt.Errorf("address type %T not supported",
addresses[i])
}
}
@ -296,7 +284,7 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
}
// Print the transaction.
fmt.Printf("Sweeping transaction:\n%s\n", hex.EncodeToString(txBuf.Bytes()))
fmt.Printf("Sweeping transaction:\n%x\n", txBuf.Bytes())
// Publish the transaction.
if c.Publish {

@ -115,17 +115,12 @@ func (c *pullAnchorCommand) Execute(_ *cobra.Command, _ []string) error {
err)
}
changeScript, err := lnd.GetP2WPKHScript(c.ChangeAddr, chainParams)
if err != nil {
return fmt.Errorf("error parsing change addr: %w", err)
}
// Set default values.
if c.FeeRate == 0 {
c.FeeRate = defaultFeeSatPerVByte
}
return createPullTransactionTemplate(
extendedKey, c.APIURL, outpoint, c.AnchorAddrs, changeScript,
extendedKey, c.APIURL, outpoint, c.AnchorAddrs, c.ChangeAddr,
c.FeeRate,
)
}
@ -141,14 +136,23 @@ type targetAnchor struct {
func createPullTransactionTemplate(rootKey *hdkeychain.ExtendedKey,
apiURL string, sponsorOutpoint *wire.OutPoint, anchorAddrs []string,
changeScript []byte, feeRate uint32) error {
changeAddr string, feeRate uint32) error {
signer := &lnd.Signer{
ExtendedKey: rootKey,
ChainParams: chainParams,
var (
signer = &lnd.Signer{
ExtendedKey: rootKey,
ChainParams: chainParams,
}
api = newExplorerAPI(apiURL)
estimator input.TxWeightEstimator
)
changeScript, err := lnd.PrepareWalletAddress(
changeAddr, chainParams, &estimator, rootKey, "change",
)
if err != nil {
return err
}
api := newExplorerAPI(apiURL)
estimator := input.TxWeightEstimator{}
// Make sure the sponsor input is a P2WPKH or P2TR input and is known
// to the block explorer, so we can fetch the witness utxo.
@ -209,7 +213,6 @@ func createPullTransactionTemplate(rootKey *hdkeychain.ExtendedKey,
}
// Now we can calculate the fee and add the change output.
estimator.AddP2WKHOutput()
anchorAmt := uint64(len(anchorAddrs)) * 330
totalOutputValue := btcutil.Amount(sponsorTxOut.Value + anchorAmt)
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()

@ -5,7 +5,6 @@ import (
"encoding/hex"
"fmt"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
@ -165,29 +164,20 @@ func (c *recoverLoopInCommand) Execute(_ *cobra.Command, _ []string) error {
}
// Get the destination address.
sweepAddr, err := btcutil.DecodeAddress(c.SweepAddr, chainParams)
var estimator input.TxWeightEstimator
sweepScript, err := lnd.PrepareWalletAddress(
c.SweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
// Calculate the sweep fee.
estimator := &input.TxWeightEstimator{}
err = htlc.AddTimeoutToEstimator(estimator)
err = htlc.AddTimeoutToEstimator(&estimator)
if err != nil {
return err
}
switch sweepAddr.(type) {
case *btcutil.AddressWitnessPubKeyHash:
estimator.AddP2WKHOutput()
case *btcutil.AddressTaproot:
estimator.AddP2TROutput()
default:
return fmt.Errorf("unsupported address type")
}
feeRateKWeight := chainfee.SatPerKVByte(
1000 * c.FeeRate,
).FeePerKWeight()
@ -216,13 +206,8 @@ func (c *recoverLoopInCommand) Execute(_ *cobra.Command, _ []string) error {
})
// Add output for the destination address.
sweepPkScript, err := txscript.PayToAddrScript(sweepAddr)
if err != nil {
return err
}
sweepTx.AddTxOut(&wire.TxOut{
PkScript: sweepPkScript,
PkScript: sweepScript,
Value: int64(loopIn.Contract.AmountRequested) - int64(fee),
})

@ -236,35 +236,38 @@ func (c *rescueFundingCommand) Execute(_ *cobra.Command, _ []string) error {
return 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: %w", err)
}
return rescueFunding(
localKeyDesc, remotePubKey, signer, chainOp,
sweepScript, btcutil.Amount(c.FeeRate), c.APIURL,
localKeyDesc, remotePubKey, signer, chainOp, c.SweepAddr,
btcutil.Amount(c.FeeRate), c.APIURL,
)
}
func rescueFunding(localKeyDesc *keychain.KeyDescriptor,
remoteKey *btcec.PublicKey, signer *lnd.Signer,
chainPoint *wire.OutPoint, sweepPKScript []byte, feeRate btcutil.Amount,
chainPoint *wire.OutPoint, sweepAddr string, feeRate btcutil.Amount,
apiURL string) error {
var (
estimator input.TxWeightEstimator
api = newExplorerAPI(apiURL)
)
sweepScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, &estimator, signer.ExtendedKey, "sweep",
)
if err != nil {
return err
}
// Prepare the wire part of the PSBT.
txIn := &wire.TxIn{
PreviousOutPoint: *chainPoint,
Sequence: 0,
}
txOut := &wire.TxOut{
PkScript: sweepPKScript,
PkScript: sweepScript,
}
// Locate the output in the funding TX.
api := newExplorerAPI(apiURL)
tx, err := api.Transaction(chainPoint.Hash.String())
if err != nil {
return fmt.Errorf("error fetching UTXO info for outpoint %s: "+
@ -303,17 +306,15 @@ func rescueFunding(localKeyDesc *keychain.KeyDescriptor,
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
// 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
// Estimate the transaction weight, so we can do the fee estimation.
estimator.AddWitnessInput(MultiSigWitnessSize)
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
txOut.Value = utxo.Value - int64(totalFee)

@ -133,6 +133,14 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
sweepAddr string, recoveryWindow uint32, feeRate uint32,
publish bool) error {
var estimator input.TxWeightEstimator
sweepScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
var (
targets []*targetAddr
api = newExplorerAPI(apiURL)
@ -177,7 +185,6 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
// Create estimator and transaction template.
var (
estimator input.TxWeightEstimator
signDescs []*input.SignDescriptor
sweepTx = wire.NewMsgTx(2)
totalOutputValue = uint64(0)
@ -292,13 +299,6 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL,
len(targets), totalOutputValue, sweepDustLimit)
}
// Add our sweep destination output.
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
estimator.AddP2WKHOutput()
// Calculate the fee based on the given fee rate and our weight
// estimation.
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()

@ -221,18 +221,26 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
publish bool, feeRate uint32) error {
// Create signer and transaction template.
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
var (
estimator input.TxWeightEstimator
signer = &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
api = newExplorerAPI(apiURL)
)
sweepScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
api := newExplorerAPI(apiURL)
var (
sweepTx = wire.NewMsgTx(2)
totalOutputValue = int64(0)
signDescs = make([]*input.SignDescriptor, 0)
prevOutFetcher = txscript.NewMultiPrevOutFetcher(nil)
estimator input.TxWeightEstimator
)
for _, target := range targets {
// We can't rely on the CSV delay of the channel DB to be
@ -247,8 +255,8 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
), target.lockScript, 0, maxCsvTimeout,
)
if err != nil {
log.Errorf("Could not create matching script for %s "+
"or csv too high: %w", target.channelPoint, err)
log.Errorf("could not create matching script for %s "+
"or csv too high: %v", target.channelPoint, err)
continue
}
@ -288,13 +296,6 @@ func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string,
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
}
// Add our sweep destination output.
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
estimator.AddP2WKHOutput()
// Calculate the fee based on the given fee rate and our weight
// estimation.
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()

@ -248,12 +248,30 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
maxCsvTimeout, startNumChannels, maxNumChannels,
maxNumChanUpdates)
// Create signer and transaction template.
var (
estimator input.TxWeightEstimator
signer = &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
api = newExplorerAPI(apiURL)
)
// 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
// continue anyway.
lockScript, err := lnd.GetP2WSHScript(timeLockAddr, chainParams)
lockScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, nil, extendedKey, "time lock",
)
if err != nil {
return fmt.Errorf("invalid time lock addr: %w", err)
return err
}
sweepScript, err := lnd.PrepareWalletAddress(
sweepAddr, chainParams, &estimator, extendedKey, "sweep",
)
if err != nil {
return err
}
// We need to go through a lot of our keys so it makes sense to
@ -303,13 +321,6 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
return fmt.Errorf("target script not derived")
}
// Create signer and transaction template.
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
}
api := newExplorerAPI(apiURL)
// We now know everything we need to construct the sweep transaction,
// except for what outpoint to sweep. We'll ask the chain API to give
// us this information.
@ -339,17 +350,11 @@ func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string,
// Calculate the fee based on the given fee rate and our weight
// estimation.
var estimator input.TxWeightEstimator
estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize)
estimator.AddP2WKHOutput()
feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight()
totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight()))
// Add our sweep destination output.
sweepScript, err := lnd.GetP2WPKHScript(sweepAddr, chainParams)
if err != nil {
return err
}
sweepTx.TxOut = []*wire.TxOut{{
Value: sweepValue - int64(totalFee),
PkScript: sweepScript,

@ -13,6 +13,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/shachain"
@ -437,6 +438,67 @@ func CheckAddress(addr string, chainParams *chaincfg.Params, allowDerive bool,
return nil
}
func PrepareWalletAddress(addr string, chainParams *chaincfg.Params,
estimator *input.TxWeightEstimator, rootKey *hdkeychain.ExtendedKey,
hint string) ([]byte, error) {
// We already checked if deriving a new address is allowed in a previous
// step, so we can just go ahead and do it now if requested.
if addr == AddressDeriveFromWallet {
// To maximize compatibility and recoverability, we always
// derive the very first P2WKH address from the wallet.
// This corresponds to the derivation path: m/84'/0'/0'/0/0.
derivedKey, err := DeriveChildren(rootKey, []uint32{
HardenedKeyStart + waddrmgr.KeyScopeBIP0084.Purpose,
HardenedKeyStart + chainParams.HDCoinType,
HardenedKeyStart + 0, 0, 0,
})
if err != nil {
return nil, err
}
derivedPubKey, err := derivedKey.ECPubKey()
if err != nil {
return nil, err
}
p2wkhAddr, err := P2WKHAddr(derivedPubKey, chainParams)
if err != nil {
return nil, err
}
return txscript.PayToAddrScript(p2wkhAddr)
}
parsedAddr, err := ParseAddress(addr, chainParams)
if err != nil {
return nil, fmt.Errorf("%s address is invalid: %w", hint, err)
}
// Exit early if we don't need to estimate the weight.
if estimator == nil {
return txscript.PayToAddrScript(parsedAddr)
}
// These are the three address types that we support in general. We
// should have checked that we get the correct type in a previous step.
switch parsedAddr.(type) {
case *btcutil.AddressWitnessPubKeyHash:
estimator.AddP2WKHOutput()
case *btcutil.AddressWitnessScriptHash:
estimator.AddP2WSHOutput()
case *btcutil.AddressTaproot:
estimator.AddP2TROutput()
default:
return nil, fmt.Errorf("%s address is of wrong type", hint)
}
return txscript.PayToAddrScript(parsedAddr)
}
func matchAddrType(addr btcutil.Address, allowedTypes ...AddrType) bool {
contains := func(allowedTypes []AddrType, addrType AddrType) bool {
for _, allowedType := range allowedTypes {

Loading…
Cancel
Save