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

sweepremoteclosed: add support for simple taproot channels #106

Merged
merged 1 commit into from
Dec 28, 2023
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
151 changes: 112 additions & 39 deletions cmd/chantools/sweepremoteclosed.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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),
Expand All @@ -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)
}
}

Expand Down Expand Up @@ -270,15 +318,29 @@ 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,
)
if err != nil {
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...
Expand Down Expand Up @@ -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)
Expand All @@ -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,
})
}

Expand All @@ -346,15 +411,23 @@ 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
}

p2anchor, script, err := lnd.P2AnchorStaticRemote(pubKey, chainParams)
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
}

Expand Down
21 changes: 16 additions & 5 deletions lnd/hdkeychain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading