diff --git a/cmd/chantools/sweepremoteclosed.go b/cmd/chantools/sweepremoteclosed.go index a94cdad..5ec63f4 100644 --- a/cmd/chantools/sweepremoteclosed.go +++ b/cmd/chantools/sweepremoteclosed.go @@ -53,6 +53,7 @@ funds can be swept after the force-close transaction was confirmed. Supported remote force-closed channel types are: - STATIC_REMOTE_KEY (a.k.a. tweakless channels) - ANCHOR (a.k.a. anchor output channels) + - SIMPLE_TAPROOT (a.k.a. simple taproot channels) `, Example: `chantools sweepremoteclosed \ --recoverywindow 300 \ @@ -113,12 +114,13 @@ func (c *sweepRemoteClosedCommand) Execute(_ *cobra.Command, _ []string) error { } type targetAddr struct { - addr btcutil.Address - pubKey *btcec.PublicKey - path string - keyDesc *keychain.KeyDescriptor - vouts []*btc.Vout - script []byte + addr btcutil.Address + pubKey *btcec.PublicKey + path string + keyDesc *keychain.KeyDescriptor + vouts []*btc.Vout + script []byte + scriptTree *input.CommitScriptTree } func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, @@ -196,18 +198,6 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, err) } - sequence := wire.MaxTxInSequenceNum - switch target.addr.(type) { - case *btcutil.AddressWitnessPubKeyHash: - estimator.AddP2WKHInput() - - case *btcutil.AddressWitnessScriptHash: - estimator.AddWitnessInput( - input.ToRemoteConfirmedWitnessSize, - ) - sequence = 1 - } - prevOutPoint := wire.OutPoint{ Hash: *txHash, Index: uint32(vout.Outspend.Vin), @@ -217,18 +207,76 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, Value: int64(vout.Value), } prevOutFetcher.AddPrevOut(prevOutPoint, prevTxOut) - sweepTx.TxIn = append(sweepTx.TxIn, &wire.TxIn{ + txIn := &wire.TxIn{ PreviousOutPoint: prevOutPoint, - Sequence: sequence, - }) + Sequence: wire.MaxTxInSequenceNum, + } + sweepTx.TxIn = append(sweepTx.TxIn, txIn) + inputIndex := len(sweepTx.TxIn) - 1 - signDescs = append(signDescs, &input.SignDescriptor{ - KeyDesc: *target.keyDesc, - WitnessScript: target.script, - Output: prevTxOut, - HashType: txscript.SigHashAll, - PrevOutputFetcher: prevOutFetcher, - }) + var signDesc *input.SignDescriptor + switch target.addr.(type) { + case *btcutil.AddressWitnessPubKeyHash: + estimator.AddP2WKHInput() + + signDesc = &input.SignDescriptor{ + KeyDesc: *target.keyDesc, + WitnessScript: target.script, + Output: prevTxOut, + HashType: txscript.SigHashAll, + PrevOutputFetcher: prevOutFetcher, + InputIndex: inputIndex, + } + + case *btcutil.AddressWitnessScriptHash: + estimator.AddWitnessInput( + input.ToRemoteConfirmedWitnessSize, + ) + txIn.Sequence = 1 + + signDesc = &input.SignDescriptor{ + KeyDesc: *target.keyDesc, + WitnessScript: target.script, + Output: prevTxOut, + HashType: txscript.SigHashAll, + PrevOutputFetcher: prevOutFetcher, + InputIndex: inputIndex, + } + + case *btcutil.AddressTaproot: + estimator.AddWitnessInput( + input.TaprootToRemoteWitnessSize, + ) + txIn.Sequence = 1 + + tree := target.scriptTree + controlBlock, err := tree.CtrlBlockForPath( + input.ScriptPathSuccess, + ) + if err != nil { + return err + } + controlBlockBytes, err := controlBlock.ToBytes() + if err != nil { + return err + } + + script := tree.SettleLeaf.Script + signMethod := input.TaprootScriptSpendSignMethod + signDesc = &input.SignDescriptor{ + KeyDesc: *target.keyDesc, + WitnessScript: script, + Output: prevTxOut, + HashType: txscript.SigHashDefault, + PrevOutputFetcher: prevOutFetcher, + ControlBlock: controlBlockBytes, + InputIndex: inputIndex, + SignMethod: signMethod, + TapTweak: tree.TapscriptRoot, + } + } + + signDescs = append(signDescs, signDesc) } } @@ -270,7 +318,19 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, desc.SigHashes = sigHashes desc.InputIndex = idx - if len(desc.WitnessScript) > 0 { + switch { + // Simple Taproot Channels. + case desc.SignMethod == input.TaprootScriptSpendSignMethod: + witness, err := input.TaprootCommitSpendSuccess( + signer, desc, sweepTx, nil, + ) + if err != nil { + return err + } + sweepTx.TxIn[idx].Witness = witness + + // Anchor Channels. + case len(desc.WitnessScript) > 0: witness, err := input.CommitSpendToRemoteConfirmed( signer, desc, sweepTx, ) @@ -278,7 +338,9 @@ func sweepRemoteClosed(extendedKey *hdkeychain.ExtendedKey, apiURL, return err } sweepTx.TxIn[idx].Witness = witness - } else { + + // Static Remote Key Channels. + default: // The txscript library expects the witness script of a // P2WKH descriptor to be set to the pkScript of the // output... @@ -320,7 +382,9 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string, error) { var targets []*targetAddr - queryAddr := func(address btcutil.Address, script []byte) error { + queryAddr := func(address btcutil.Address, script []byte, + scriptTree *input.CommitScriptTree) error { + unspent, err := api.Unspent(address.EncodeAddress()) if err != nil { return fmt.Errorf("could not query unspent: %w", err) @@ -330,12 +394,13 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string, log.Infof("Found %d unspent outputs for address %v", len(unspent), address.EncodeAddress()) targets = append(targets, &targetAddr{ - addr: address, - pubKey: pubKey, - path: path, - keyDesc: keyDesc, - vouts: unspent, - script: script, + addr: address, + pubKey: pubKey, + path: path, + keyDesc: keyDesc, + vouts: unspent, + script: script, + scriptTree: scriptTree, }) } @@ -346,7 +411,7 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string, if err != nil { return nil, err } - if err := queryAddr(p2wkh, nil); err != nil { + if err := queryAddr(p2wkh, nil, nil); err != nil { return nil, err } @@ -354,7 +419,15 @@ func queryAddressBalances(pubKey *btcec.PublicKey, path string, if err != nil { return nil, err } - if err := queryAddr(p2anchor, script); err != nil { + if err := queryAddr(p2anchor, script, nil); err != nil { + return nil, err + } + + p2tr, scriptTree, err := lnd.P2TaprootStaticRemove(pubKey, chainParams) + if err != nil { + return nil, err + } + if err := queryAddr(p2tr, nil, scriptTree); err != nil { return nil, err } diff --git a/lnd/hdkeychain.go b/lnd/hdkeychain.go index d566674..838fa74 100644 --- a/lnd/hdkeychain.go +++ b/lnd/hdkeychain.go @@ -276,11 +276,7 @@ func GetWitnessAddrScript(addr btcutil.Address, chainParams.Name) } - builder := txscript.NewScriptBuilder() - builder.AddOp(txscript.OP_0) - builder.AddData(addr.ScriptAddress()) - - return builder.Script() + return txscript.PayToAddrScript(addr) } // GetP2WPKHScript creates a P2WKH output script from an address. If the address @@ -387,6 +383,21 @@ func P2AnchorStaticRemote(pubKey *btcec.PublicKey, return p2wsh, commitScript, err } +func P2TaprootStaticRemove(pubKey *btcec.PublicKey, + params *chaincfg.Params) (*btcutil.AddressTaproot, + *input.CommitScriptTree, error) { + + scriptTree, err := input.NewRemoteCommitScriptTree(pubKey) + if err != nil { + return nil, nil, fmt.Errorf("could not create script: %w", err) + } + + addr, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(scriptTree.TaprootKey), params, + ) + return addr, scriptTree, err +} + type HDKeyRing struct { ExtendedKey *hdkeychain.ExtendedKey ChainParams *chaincfg.Params