diff --git a/cmd/chantools/closepoolaccount.go b/cmd/chantools/closepoolaccount.go index 6a1c865..4321064 100644 --- a/cmd/chantools/closepoolaccount.go +++ b/cmd/chantools/closepoolaccount.go @@ -42,7 +42,7 @@ type closePoolAccountCommand struct { AuctioneerKey string Publish bool SweepAddr string - FeeRate uint16 + FeeRate uint32 MinExpiry uint32 MaxNumBlocks uint32 @@ -91,7 +91,7 @@ obtained by running 'pool accounts list' `, cc.cmd.Flags().StringVar( &cc.SweepAddr, "sweepaddr", "", "address to sweep the funds to", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) @@ -158,7 +158,7 @@ func (c *closePoolAccountCommand) Execute(_ *cobra.Command, _ []string) error { func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string, outpoint *wire.OutPoint, auctioneerKey *btcec.PublicKey, - sweepAddr string, publish bool, feeRate uint16, minExpiry, + sweepAddr string, publish bool, feeRate uint32, minExpiry, maxNumBlocks, maxNumAccounts, maxNumBatchKeys uint32) error { signer := &lnd.Signer{ diff --git a/cmd/chantools/doublespendinputs.go b/cmd/chantools/doublespendinputs.go new file mode 100644 index 0000000..2ad4ffd --- /dev/null +++ b/cmd/chantools/doublespendinputs.go @@ -0,0 +1,367 @@ +package main + +import ( + "bytes" + "encoding/hex" + "fmt" + "strconv" + + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/guggero/chantools/btc" + "github.com/guggero/chantools/lnd" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/spf13/cobra" +) + +type doubleSpendInputs struct { + APIURL string + InputOutpoints []string + Publish bool + SweepAddr string + FeeRate uint32 + RecoveryWindow uint32 + + rootKey *rootKey + cmd *cobra.Command +} + +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.", + Example: `chantools doublespendinputs \ + --inputoutpoints xxxxxxxxx:y,xxxxxxxxx:y \ + --sweepaddr bc1q..... \ + --feerate 10 \ + --publish`, + RunE: cc.Execute, + } + cc.cmd.Flags().StringVar( + &cc.APIURL, "apiurl", defaultAPIURL, "API URL to use (must "+ + "be esplora compatible)", + ) + cc.cmd.Flags().StringSliceVar( + &cc.InputOutpoints, "inputoutpoints", []string{}, + "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.cmd.Flags().Uint32Var( + &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ + "use for the sweep transaction in sat/vByte", + ) + cc.cmd.Flags().Uint32Var( + &cc.RecoveryWindow, "recoverywindow", defaultRecoveryWindow, + "number of keys to scan per internal/external branch; output "+ + "will consist of double this amount of keys", + ) + cc.cmd.Flags().BoolVar( + &cc.Publish, "publish", false, "publish replacement TX to "+ + "the chain API instead of just printing the TX", + ) + + cc.rootKey = newRootKey(cc.cmd, "deriving the input keys") + + return cc.cmd +} + +func (c *doubleSpendInputs) Execute(_ *cobra.Command, _ []string) error { + extendedKey, err := c.rootKey.read() + if err != nil { + return fmt.Errorf("error reading root key: %w", err) + } + + // Make sure sweep addr is set. + if c.SweepAddr == "" { + return fmt.Errorf("sweep addr is required") + } + + // 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} + + 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) + if err != nil { + return err + } + + addr, err := btcutil.DecodeAddress(addrString, chainParams) + if err != nil { + return err + } + + addresses = append(addresses, addr) + + txHash, err := chainhash.NewHashFromStr(input[:64]) + if err != nil { + return err + } + + vout, err := strconv.Atoi(input[65:]) + if err != nil { + return err + } + outpoint := wire.NewOutPoint(txHash, uint32(vout)) + + outpoints = append(outpoints, outpoint) + } + + // Create the paths for the addresses. + p2wkhPath, err := lnd.ParsePath(lnd.WalletDefaultDerivationPath) + if err != nil { + return err + } + + p2trPath, err := lnd.ParsePath(lnd.WalletBIP86DerivationPath) + if err != nil { + return err + } + + // Start with the txweight estimator. + estimator := input.TxWeightEstimator{} + + // Find the key for the given addresses and add their + // output weight to the tx estimator. + for _, addr := range addresses { + var key *hdkeychain.ExtendedKey + switch addr.(type) { + case *btcutil.AddressWitnessPubKeyHash: + key, err = iterateOverPath( + extendedKey, addr, p2wkhPath, c.RecoveryWindow, + ) + if err != nil { + return err + } + + estimator.AddP2WKHInput() + + case *btcutil.AddressTaproot: + key, err = iterateOverPath( + extendedKey, addr, p2trPath, c.RecoveryWindow, + ) + if err != nil { + return err + } + + estimator.AddTaprootKeySpendInput(txscript.SigHashDefault) + + default: + return fmt.Errorf("address type %T not supported", addr) + } + + // Get the private key. + privKey, err := key.ECPrivKey() + if err != nil { + return err + } + + privKeys = append(privKeys, privKey) + } + + // Now that we have the keys, we can create the transaction. + prevOuts := make(map[wire.OutPoint]*wire.TxOut) + + // Next get the full value of the inputs. + var totalInput btcutil.Amount + for _, input := range outpoints { + // Get the transaction. + tx, err := api.Transaction(input.Hash.String()) + if err != nil { + return err + } + + value := tx.Vout[input.Index].Value + + // Get the output index. + totalInput += btcutil.Amount(value) + + scriptPubkey, err := hex.DecodeString(tx.Vout[input.Index].ScriptPubkey) + if err != nil { + return err + } + + // Add the output to the map. + prevOuts[*input] = &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())) + + // Create the transaction. + 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 + } + + tx.AddTxOut(wire.NewTxOut(int64(totalInput-totalFee), sweepScript)) + + // Calculate the signature hash. + prevOutFetcher := txscript.NewMultiPrevOutFetcher(prevOuts) + sigHashes := txscript.NewTxSigHashes(tx, prevOutFetcher) + + // Sign the inputs depending on the address type. + for i, outpoint := range outpoints { + switch addresses[i].(type) { + case *btcutil.AddressWitnessPubKeyHash: + witness, err := txscript.WitnessSignature( + tx, sigHashes, i, prevOuts[*outpoint].Value, + prevOuts[*outpoint].PkScript, + txscript.SigHashAll, privKeys[i], true, + ) + if err != nil { + return err + } + + tx.TxIn[i].Witness = witness + + case *btcutil.AddressTaproot: + rawTxSig, err := txscript.RawTxInTaprootSignature( + tx, sigHashes, i, + prevOuts[*outpoint].Value, + prevOuts[*outpoint].PkScript, + []byte{}, txscript.SigHashDefault, privKeys[i], + ) + if err != nil { + return err + } + + tx.TxIn[i].Witness = wire.TxWitness{ + rawTxSig, + } + + default: + return fmt.Errorf("address type %T not supported", addresses[i]) + } + } + + // Serialize the transaction. + var txBuf bytes.Buffer + if err := tx.Serialize(&txBuf); err != nil { + return err + } + + // Print the transaction. + fmt.Printf("Sweeping transaction:\n%s\n", hex.EncodeToString(txBuf.Bytes())) + + // Publish the transaction. + if c.Publish { + txid, err := api.PublishTx(hex.EncodeToString(txBuf.Bytes())) + if err != nil { + return err + } + + fmt.Printf("Published transaction with txid %s\n", txid) + } + + return nil +} + +// iterateOverPath iterates over the given key path and tries to find the +// private key that corresponds to the given address. +func iterateOverPath(baseKey *hdkeychain.ExtendedKey, addr btcutil.Address, + path []uint32, maxTries uint32) (*hdkeychain.ExtendedKey, error) { + + for i := uint32(0); i < maxTries; i++ { + // Check for both the external and internal branch. + for _, branch := range []uint32{0, 1} { + // Create the path to derive the key. + addrPath := append(path, branch, i) //nolint:gocritic + + // Derive the key. + derivedKey, err := lnd.DeriveChildren(baseKey, addrPath) + if err != nil { + return nil, err + } + + var address btcutil.Address + switch addr.(type) { + case *btcutil.AddressWitnessPubKeyHash: + // Get the address for the derived key. + derivedAddr, err := derivedKey.Address(chainParams) + if err != nil { + return nil, err + } + + address, err = btcutil.NewAddressWitnessPubKeyHash( + derivedAddr.ScriptAddress(), chainParams, + ) + if err != nil { + return nil, err + } + + case *btcutil.AddressTaproot: + + pubkey, err := derivedKey.ECPubKey() + if err != nil { + return nil, err + } + + pubkey = txscript.ComputeTaprootKeyNoScript(pubkey) + + address, err = btcutil.NewAddressTaproot( + schnorr.SerializePubKey(pubkey), chainParams, + ) + if err != nil { + return nil, err + } + } + + // Compare the addresses. + if address.String() == addr.String() { + return derivedKey, nil + } + } + } + + return nil, fmt.Errorf("could not find key for address %s", addr.String()) +} diff --git a/cmd/chantools/recoverloopin.go b/cmd/chantools/recoverloopin.go index 831b0b7..211a8b3 100644 --- a/cmd/chantools/recoverloopin.go +++ b/cmd/chantools/recoverloopin.go @@ -25,7 +25,7 @@ type recoverLoopInCommand struct { Vout uint32 SwapHash string SweepAddr string - FeeRate uint16 + FeeRate uint32 StartKeyIndex int NumTries int @@ -73,7 +73,7 @@ func newRecoverLoopInCommand() *cobra.Command { &cc.SweepAddr, "sweep_addr", "", "address to recover "+ "the funds to", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", 0, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) diff --git a/cmd/chantools/rescuefunding.go b/cmd/chantools/rescuefunding.go index dedc92d..9cb17cb 100644 --- a/cmd/chantools/rescuefunding.go +++ b/cmd/chantools/rescuefunding.go @@ -45,7 +45,7 @@ type rescueFundingCommand struct { RemotePubKey string SweepAddr string - FeeRate uint16 + FeeRate uint32 APIURL string rootKey *rootKey @@ -115,7 +115,7 @@ chantools rescuefunding \ cc.cmd.Flags().StringVar( &cc.SweepAddr, "sweepaddr", "", "address to sweep the funds to", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) diff --git a/cmd/chantools/root.go b/cmd/chantools/root.go index 58e79ea..ec39d07 100644 --- a/cmd/chantools/root.go +++ b/cmd/chantools/root.go @@ -85,6 +85,7 @@ func main() { newCompactDBCommand(), newDeletePaymentsCommand(), newDeriveKeyCommand(), + newDoubleSpendInputsCommand(), newDropChannelGraphCommand(), newDumpBackupCommand(), newDumpChannelsCommand(), diff --git a/cmd/chantools/sweepremoteclosed.go b/cmd/chantools/sweepremoteclosed.go index cf11960..1220829 100644 --- a/cmd/chantools/sweepremoteclosed.go +++ b/cmd/chantools/sweepremoteclosed.go @@ -29,7 +29,7 @@ type sweepRemoteClosedCommand struct { APIURL string Publish bool SweepAddr string - FeeRate uint16 + FeeRate uint32 rootKey *rootKey cmd *cobra.Command @@ -77,7 +77,7 @@ Supported remote force-closed channel types are: cc.cmd.Flags().StringVar( &cc.SweepAddr, "sweepaddr", "", "address to sweep the funds to", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) @@ -122,7 +122,7 @@ type targetAddr struct { } func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, - sweepAddr string, recoveryWindow uint32, feeRate uint16, + sweepAddr string, recoveryWindow uint32, feeRate uint32, publish bool) error { var ( diff --git a/cmd/chantools/sweeptimelock.go b/cmd/chantools/sweeptimelock.go index c8f0ce2..74a29a4 100644 --- a/cmd/chantools/sweeptimelock.go +++ b/cmd/chantools/sweeptimelock.go @@ -29,7 +29,7 @@ type sweepTimeLockCommand struct { Publish bool SweepAddr string MaxCsvLimit uint16 - FeeRate uint16 + FeeRate uint32 rootKey *rootKey inputs *inputFlags @@ -71,7 +71,7 @@ parameter to 144.`, &cc.MaxCsvLimit, "maxcsvlimit", defaultCsvLimit, "maximum CSV "+ "limit to use", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) @@ -125,7 +125,7 @@ type sweepTarget struct { func sweepTimeLockFromSummary(extendedKey *hdkeychain.ExtendedKey, apiURL string, entries []*dataformat.SummaryEntry, sweepAddr string, - maxCsvTimeout uint16, publish bool, feeRate uint16) error { + maxCsvTimeout uint16, publish bool, feeRate uint32) error { targets := make([]*sweepTarget, 0, len(entries)) for _, entry := range entries { @@ -213,7 +213,7 @@ func sweepTimeLockFromSummary(extendedKey *hdkeychain.ExtendedKey, apiURL string func sweepTimeLock(extendedKey *hdkeychain.ExtendedKey, apiURL string, targets []*sweepTarget, sweepAddr string, maxCsvTimeout uint16, - publish bool, feeRate uint16) error { + publish bool, feeRate uint32) error { // Create signer and transaction template. signer := &lnd.Signer{ diff --git a/cmd/chantools/sweeptimelockmanual.go b/cmd/chantools/sweeptimelockmanual.go index 1a30401..25004c9 100644 --- a/cmd/chantools/sweeptimelockmanual.go +++ b/cmd/chantools/sweeptimelockmanual.go @@ -30,7 +30,7 @@ type sweepTimeLockManualCommand struct { Publish bool SweepAddr string MaxCsvLimit uint16 - FeeRate uint16 + FeeRate uint32 TimeLockAddr string RemoteRevocationBasePoint string @@ -92,7 +92,7 @@ address is always the one that's longer (because it's P2WSH and not P2PKH).`, "maximum number of channel updates to try, set to maximum "+ "number of times the channel was used", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) @@ -144,7 +144,7 @@ func (c *sweepTimeLockManualCommand) Execute(_ *cobra.Command, _ []string) error func sweepTimeLockManual(extendedKey *hdkeychain.ExtendedKey, apiURL string, sweepAddr, timeLockAddr string, remoteRevPoint *btcec.PublicKey, maxCsvTimeout, maxNumChannels uint16, maxNumChanUpdates uint64, - publish bool, feeRate uint16) error { + publish bool, feeRate uint32) error { // 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 diff --git a/cmd/chantools/zombierecovery_makeoffer.go b/cmd/chantools/zombierecovery_makeoffer.go index b479034..7464e25 100644 --- a/cmd/chantools/zombierecovery_makeoffer.go +++ b/cmd/chantools/zombierecovery_makeoffer.go @@ -26,7 +26,7 @@ import ( type zombieRecoveryMakeOfferCommand struct { Node1 string Node2 string - FeeRate uint16 + FeeRate uint32 rootKey *rootKey cmd *cobra.Command @@ -60,7 +60,7 @@ a counter offer.`, &cc.Node2, "node2_keys", "", "the JSON file generated in the"+ "previous step ('preparekeys') command of node 2", ) - cc.cmd.Flags().Uint16Var( + cc.cmd.Flags().Uint32Var( &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) diff --git a/doc/chantools.md b/doc/chantools.md index fe63648..00ccb3d 100644 --- a/doc/chantools.md +++ b/doc/chantools.md @@ -23,6 +23,7 @@ Complete documentation is available at https://github.com/guggero/chantools/. * [chantools compactdb](chantools_compactdb.md) - Create a copy of a channel.db file in safe/read-only mode * [chantools deletepayments](chantools_deletepayments.md) - Remove all (failed) payments from a channel DB * [chantools derivekey](chantools_derivekey.md) - Derive a key with a specific derivation path +* [chantools doublespendinputs](chantools_doublespendinputs.md) - 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. * [chantools dropchannelgraph](chantools_dropchannelgraph.md) - Remove all graph related data from a channel DB * [chantools dumpbackup](chantools_dumpbackup.md) - Dump the content of a channel.backup file * [chantools dumpchannels](chantools_dumpchannels.md) - Dump all channel information from an lnd channel database diff --git a/doc/chantools_closepoolaccount.md b/doc/chantools_closepoolaccount.md index e5b929b..7cf05d2 100644 --- a/doc/chantools_closepoolaccount.md +++ b/doc/chantools_closepoolaccount.md @@ -32,7 +32,7 @@ chantools closepoolaccount \ --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") --auctioneerkey string the auctioneer's static public key (default "028e87bdd134238f8347f845d9ecc827b843d0d1e27cdcb46da704d916613f4fce") --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) -h, --help help for closepoolaccount --maxnumaccounts uint32 the number of account indices to try at most (default 20) --maxnumbatchkeys uint32 the number of batch keys to try at most (default 500) diff --git a/doc/chantools_doublespendinputs.md b/doc/chantools_doublespendinputs.md new file mode 100644 index 0000000..9239688 --- /dev/null +++ b/doc/chantools_doublespendinputs.md @@ -0,0 +1,43 @@ +## chantools doublespendinputs + +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. + +``` +chantools doublespendinputs [flags] +``` + +### Examples + +``` +chantools doublespendinputs \ + --inputoutpoints xxxxxxxxx:y,xxxxxxxxx:y \ + --sweepaddr bc1q..... \ + --feerate 10 \ + --publish +``` + +### Options + +``` + --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") + --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) + -h, --help help for doublespendinputs + --inputoutpoints strings list of outpoints to double spend in the format txid:vout + --publish publish replacement TX to the chain API instead of just printing the TX + --recoverywindow uint32 number of keys to scan per internal/external branch; output will consist of double this amount of keys (default 2500) + --rootkey string BIP32 HD root key of the wallet to use for deriving the input keys; leave empty to prompt for lnd 24 word aezeed + --sweepaddr string address to sweep the funds to +``` + +### Options inherited from parent commands + +``` + -r, --regtest Indicates if regtest parameters should be used + -t, --testnet Indicates if testnet parameters should be used +``` + +### SEE ALSO + +* [chantools](chantools.md) - Chantools helps recover funds from lightning channels + diff --git a/doc/chantools_recoverloopin.md b/doc/chantools_recoverloopin.md index f983881..a6f1dd9 100644 --- a/doc/chantools_recoverloopin.md +++ b/doc/chantools_recoverloopin.md @@ -23,7 +23,7 @@ chantools recoverloopin \ ``` --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte -h, --help help for recoverloopin --loop_db_path string path to the loop database file --num_tries int number of tries to try to find the correct key index (default 1000) diff --git a/doc/chantools_rescuefunding.md b/doc/chantools_rescuefunding.md index 9345f50..69093fb 100644 --- a/doc/chantools_rescuefunding.md +++ b/doc/chantools_rescuefunding.md @@ -44,7 +44,7 @@ chantools rescuefunding \ --channeldb string lnd channel.db file to rescue a channel from; must contain the pending channel specified with --channelpoint --confirmedchannelpoint string channel outpoint that got confirmed on chain (:); normally this is the same as the --dbchannelpoint so it will be set to that value ifthis is left empty --dbchannelpoint string funding transaction outpoint of the channel to rescue (:) as it is recorded in the DB - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) -h, --help help for rescuefunding --localkeyindex uint32 in case a channel DB is not available (but perhaps a channel backup file), the derivation index of the local multisig public key can be specified manually --remotepubkey string in case a channel DB is not available (but perhaps a channel backup file), the remote multisig public key can be specified manually diff --git a/doc/chantools_sweepremoteclosed.md b/doc/chantools_sweepremoteclosed.md index ea457f1..02163b1 100644 --- a/doc/chantools_sweepremoteclosed.md +++ b/doc/chantools_sweepremoteclosed.md @@ -34,7 +34,7 @@ chantools sweepremoteclosed \ ``` --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) -h, --help help for sweepremoteclosed --publish publish sweep TX to the chain API instead of just printing the TX --recoverywindow uint32 number of keys to scan per derivation path (default 200) diff --git a/doc/chantools_sweeptimelock.md b/doc/chantools_sweeptimelock.md index 33f7a7b..0c8b429 100644 --- a/doc/chantools_sweeptimelock.md +++ b/doc/chantools_sweeptimelock.md @@ -31,7 +31,7 @@ chantools sweeptimelock \ ``` --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) --fromchanneldb string channel input is in the format of an lnd channel.db file --fromsummary string channel input is in the format of chantool's channel summary; specify '-' to read from stdin -h, --help help for sweeptimelock diff --git a/doc/chantools_sweeptimelockmanual.md b/doc/chantools_sweeptimelockmanual.md index d5813eb..cce3187 100644 --- a/doc/chantools_sweeptimelockmanual.md +++ b/doc/chantools_sweeptimelockmanual.md @@ -36,7 +36,7 @@ chantools sweeptimelockmanual \ ``` --apiurl string API URL to use (must be esplora compatible) (default "https://blockstream.info/api") --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) --fromchanneldb string channel input is in the format of an lnd channel.db file --fromsummary string channel input is in the format of chantool's channel summary; specify '-' to read from stdin -h, --help help for sweeptimelockmanual diff --git a/doc/chantools_zombierecovery_makeoffer.md b/doc/chantools_zombierecovery_makeoffer.md index a5367a2..3a3db26 100644 --- a/doc/chantools_zombierecovery_makeoffer.md +++ b/doc/chantools_zombierecovery_makeoffer.md @@ -29,7 +29,7 @@ chantools zombierecovery makeoffer \ ``` --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag - --feerate uint16 fee rate to use for the sweep transaction in sat/vByte (default 30) + --feerate uint32 fee rate to use for the sweep transaction in sat/vByte (default 30) -h, --help help for makeoffer --node1_keys string the JSON file generated in theprevious step ('preparekeys') command of node 1 --node2_keys string the JSON file generated in theprevious step ('preparekeys') command of node 2 diff --git a/lnd/signer.go b/lnd/signer.go index 86ac0e6..19fcb68 100644 --- a/lnd/signer.go +++ b/lnd/signer.go @@ -13,6 +13,7 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/wallet" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" ) @@ -27,8 +28,6 @@ type Signer struct { func (s *Signer) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescriptor) (input.Signature, error) { - witnessScript := signDesc.WitnessScript - // First attempt to fetch the private key which corresponds to the // specified public key. privKey, err := s.FetchPrivKey(&signDesc.KeyDesc) @@ -36,6 +35,14 @@ func (s *Signer) SignOutputRaw(tx *wire.MsgTx, return nil, err } + return s.SignOutputRawWithPrivkey(tx, signDesc, privKey) +} + +func (s *Signer) SignOutputRawWithPrivkey(tx *wire.MsgTx, + signDesc *input.SignDescriptor, + privKey *secp256k1.PrivateKey) (input.Signature, error) { + + witnessScript := signDesc.WitnessScript privKey = maybeTweakPrivKey(signDesc, privKey) sigHashes := txscript.NewTxSigHashes(tx, signDesc.PrevOutputFetcher) @@ -43,7 +50,11 @@ func (s *Signer) SignOutputRaw(tx *wire.MsgTx, // Are we spending a script path or the key path? The API is // slightly different, so we need to account for that to get the // raw signature. - var rawSig []byte + var ( + rawSig []byte + err error + ) + switch signDesc.SignMethod { case input.TaprootKeySpendBIP0086SignMethod, input.TaprootKeySpendSignMethod: @@ -105,7 +116,6 @@ func (s *Signer) SignOutputRaw(tx *wire.MsgTx, // Chop off the sighash flag at the end of the signature. return ecdsa.ParseDERSignature(sig[:len(sig)-1]) } - func (s *Signer) ComputeInputScript(_ *wire.MsgTx, _ *input.SignDescriptor) ( *input.Script, error) {