Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support P2TR as sweep/change address everywhere #107

Merged
merged 4 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions btc/explorer_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,34 +196,47 @@ func (a *ExplorerAPI) PublishTx(rawTxHex string) (string, error) {
url := fmt.Sprintf("%s/tx", a.BaseURL)
resp, err := http.Post(url, "text/plain", strings.NewReader(rawTxHex))
if err != nil {
return "", err
return "", fmt.Errorf("error posting data to API '%s', "+
"server might be experiencing temporary issues, try "+
"again later; error details: %w", url, err)
}
defer resp.Body.Close()
body := new(bytes.Buffer)
_, err = body.ReadFrom(resp.Body)
if err != nil {
return "", err
return "", fmt.Errorf("error fetching data from API '%s', "+
"server might be experiencing temporary issues, try "+
"again later; error details: %w", url, err)
}
return body.String(), nil
}

func fetchJSON(url string, target interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
return fmt.Errorf("error fetching data from API '%s', "+
"server might be experiencing temporary issues, try "+
"again later; error details: %w", url, err)
}
defer resp.Body.Close()

body := new(bytes.Buffer)
_, err = body.ReadFrom(resp.Body)
if err != nil {
return err
return fmt.Errorf("error fetching data from API '%s', "+
"server might be experiencing temporary issues, try "+
"again later; error details: %w", url, err)
}
err = json.Unmarshal(body.Bytes(), target)
if err != nil {
if body.String() == "Transaction not found" {
return ErrTxNotFound
}

return fmt.Errorf("error decoding data from API '%s', "+
"server might be experiencing temporary issues, try "+
"again later; error details: %w", url, err)
}
return err

return nil
}
3 changes: 1 addition & 2 deletions btc/summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import (
"github.com/lightninglabs/chantools/dataformat"
)

func SummarizeChannels(apiURL string, channels []*dataformat.SummaryEntry,
func SummarizeChannels(api *ExplorerAPI, channels []*dataformat.SummaryEntry,
log btclog.Logger) (*dataformat.SummaryEntryFile, error) {

summaryFile := &dataformat.SummaryEntryFile{
Channels: channels,
}
api := &ExplorerAPI{BaseURL: apiURL}

for idx, channel := range channels {
tx, err := api.Transaction(channel.FundingTXID)
Expand Down
37 changes: 23 additions & 14 deletions cmd/chantools/closepoolaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/chantools/btc"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightninglabs/pool/account"
"github.com/lightninglabs/pool/poolscript"
Expand Down Expand Up @@ -89,7 +88,9 @@ obtained by running 'pool accounts list' `,
"API instead of just printing the TX",
)
cc.cmd.Flags().StringVar(
&cc.SweepAddr, "sweepaddr", "", "address to sweep the funds to",
&cc.SweepAddr, "sweepaddr", "", "address to recover the funds "+
"to; specify '"+lnd.AddressDeriveFromWallet+"' to "+
"derive a new address from the seed automatically",
)
cc.cmd.Flags().Uint32Var(
&cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+
Expand Down Expand Up @@ -125,8 +126,12 @@ func (c *closePoolAccountCommand) Execute(_ *cobra.Command, _ []string) error {
}

// Make sure sweep addr is set.
if c.SweepAddr == "" {
return fmt.Errorf("sweep addr is required")
err = lnd.CheckAddress(
c.SweepAddr, chainParams, true, "sweep", lnd.AddrTypeP2WKH,
lnd.AddrTypeP2TR,
)
if err != nil {
return err
}

// Parse account outpoint and auctioneer key.
Expand Down Expand Up @@ -161,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 := &btc.ExplorerAPI{BaseURL: apiURL}

tx, err := api.Transaction(outpoint.Hash.String())
if err != nil {
Expand Down Expand Up @@ -241,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,
)
Expand Down Expand Up @@ -277,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,
Expand Down
97 changes: 45 additions & 52 deletions cmd/chantools/doublespendinputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightninglabs/chantools/btc"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
Expand All @@ -35,16 +34,16 @@ type doubleSpendInputs struct {
func newDoubleSpendInputsCommand() *cobra.Command {
cc := &doubleSpendInputs{}
cc.cmd = &cobra.Command{
Use: "doublespendinputs",
Short: "Tries to double spend the given inputs by deriving the " +
"private for the address and sweeping the funds to the given " +
"address. This can only be used with inputs that belong to " +
"an lnd wallet.",
Use: "doublespendinputs",
Short: "Replace a transaction by double spending its input",
Long: `Tries to double spend the given inputs by deriving the
private for the address and sweeping the funds to the given address. This can
only be used with inputs that belong to an lnd wallet.`,
Example: `chantools doublespendinputs \
--inputoutpoints xxxxxxxxx:y,xxxxxxxxx:y \
--sweepaddr bc1q..... \
--feerate 10 \
--publish`,
--inputoutpoints xxxxxxxxx:y,xxxxxxxxx:y \
--sweepaddr bc1q..... \
--feerate 10 \
--publish`,
RunE: cc.Execute,
}
cc.cmd.Flags().StringVar(
Expand All @@ -56,7 +55,9 @@ func newDoubleSpendInputsCommand() *cobra.Command {
"list of outpoints to double spend in the format txid:vout",
)
cc.cmd.Flags().StringVar(
&cc.SweepAddr, "sweepaddr", "", "address to sweep the funds to",
&cc.SweepAddr, "sweepaddr", "", "address to recover the funds "+
"to; specify '"+lnd.AddressDeriveFromWallet+"' to "+
"derive a new address from the seed automatically",
)
cc.cmd.Flags().Uint32Var(
&cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+
Expand Down Expand Up @@ -84,24 +85,28 @@ func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error {
}

// Make sure sweep addr is set.
if c.SweepAddr == "" {
return fmt.Errorf("sweep addr is required")
err = lnd.CheckAddress(
c.SweepAddr, chainParams, true, "sweep", lnd.AddrTypeP2WKH,
lnd.AddrTypeP2TR,
)
if err != nil {
return err
}

// Make sure we have at least one input.
if len(c.InputOutpoints) == 0 {
return fmt.Errorf("inputoutpoints are required")
}

api := &btc.ExplorerAPI{BaseURL: c.APIURL}
api := newExplorerAPI(c.APIURL)

addresses := make([]btcutil.Address, 0, len(c.InputOutpoints))
outpoints := make([]*wire.OutPoint, 0, len(c.InputOutpoints))
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
}
Expand All @@ -113,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
}
Expand All @@ -139,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.
Expand All @@ -164,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)
Expand All @@ -184,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()))
Expand All @@ -233,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))
Expand Down Expand Up @@ -280,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])
}
}

Expand All @@ -291,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 {
Expand Down
3 changes: 1 addition & 2 deletions cmd/chantools/forceclose.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/btcsuite/btcd/btcutil/hdkeychain"
"github.com/btcsuite/btcd/txscript"
"github.com/lightninglabs/chantools/btc"
"github.com/lightninglabs/chantools/dataformat"
"github.com/lightninglabs/chantools/lnd"
"github.com/lightningnetwork/lnd/channeldb"
Expand Down Expand Up @@ -105,7 +104,7 @@ func forceCloseChannels(apiURL string, extendedKey *hdkeychain.ExtendedKey,
if err != nil {
return err
}
api := &btc.ExplorerAPI{BaseURL: apiURL}
api := newExplorerAPI(apiURL)
signer := &lnd.Signer{
ExtendedKey: extendedKey,
ChainParams: chainParams,
Expand Down
Loading
Loading