diff --git a/loopin.go b/loopin.go index 6eb0f4a..d4e7e57 100644 --- a/loopin.go +++ b/loopin.go @@ -945,14 +945,18 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context, return 0, err } + // Create a function that will assemble our timeout witness. witnessFunc := func(sig []byte) (wire.TxWitness, error) { return s.htlc.GenTimeoutWitness(sig) } + // Retrieve the full script required to unlock the output. + redeemScript := s.htlc.TimeoutScript() + sequence := uint32(0) timeoutTx, err := s.sweeper.CreateSweepTx( ctx, s.height, sequence, s.htlc, *htlcOutpoint, s.SenderKey, - witnessFunc, htlcValue, fee, s.timeoutAddr, + redeemScript, witnessFunc, htlcValue, fee, s.timeoutAddr, ) if err != nil { return 0, err diff --git a/loopout.go b/loopout.go index 97e8750..ecfcac1 100644 --- a/loopout.go +++ b/loopout.go @@ -1240,6 +1240,9 @@ func (s *loopOutSwap) sweep(ctx context.Context, return s.htlc.GenSuccessWitness(sig, s.Preimage) } + // Retrieve the full script required to unlock the output. + redeemScript := s.htlc.SuccessScript() + remainingBlocks := s.CltvExpiry - s.height blocksToLastReveal := remainingBlocks - MinLoopOutPreimageRevealDelta preimageRevealed := s.state == loopdb.StatePreimageRevealed @@ -1296,7 +1299,8 @@ func (s *loopOutSwap) sweep(ctx context.Context, // Create sweep tx. sweepTx, err := s.sweeper.CreateSweepTx( ctx, s.height, s.htlc.SuccessSequence(), s.htlc, htlcOutpoint, - s.ReceiverKey, witnessFunc, htlcValue, fee, s.DestAddr, + s.ReceiverKey, redeemScript, witnessFunc, htlcValue, fee, + s.DestAddr, ) if err != nil { return err diff --git a/swap/htlc.go b/swap/htlc.go index 5be9e35..56d9618 100644 --- a/swap/htlc.go +++ b/swap/htlc.go @@ -64,9 +64,6 @@ type HtlcScript interface { // redeeming the htlc. IsSuccessWitness(witness wire.TxWitness) bool - // Script returns the htlc script. - Script() []byte - // lockingConditions return the address, pkScript and sigScript (if // required) for a htlc script. lockingConditions(HtlcOutputType, *chaincfg.Params) (btcutil.Address, @@ -80,9 +77,21 @@ type HtlcScript interface { // timeout case witness. MaxTimeoutWitnessSize() int + // TimeoutScript returns the redeem script required to unlock the htlc + // after timeout. + TimeoutScript() []byte + + // SuccessScript returns the redeem script required to unlock the htlc + // using the preimage. + SuccessScript() []byte + // SuccessSequence returns the sequence to spend this htlc in the // success case. SuccessSequence() uint32 + + // SigHash is the signature hash to use for transactions spending from + // the htlc. + SigHash() txscript.SigHashType } // Htlc contains relevant htlc information from the receiver perspective. @@ -290,8 +299,8 @@ func (h *Htlc) AddSuccessToEstimator(estimator *input.TxWeightEstimator) error { if !ok { return ErrInvalidOutputSelected } - successLeaf := txscript.NewBaseTapLeaf(trHtlc.SuccessScript) - timeoutLeaf := txscript.NewBaseTapLeaf(trHtlc.TimeoutScript) + successLeaf := txscript.NewBaseTapLeaf(trHtlc.SuccessScript()) + timeoutLeaf := txscript.NewBaseTapLeaf(trHtlc.TimeoutScript()) timeoutLeafHash := timeoutLeaf.TapHash() tapscript := input.TapscriptPartialReveal( @@ -321,8 +330,8 @@ func (h *Htlc) AddTimeoutToEstimator(estimator *input.TxWeightEstimator) error { if !ok { return ErrInvalidOutputSelected } - successLeaf := txscript.NewBaseTapLeaf(trHtlc.SuccessScript) - timeoutLeaf := txscript.NewBaseTapLeaf(trHtlc.TimeoutScript) + successLeaf := txscript.NewBaseTapLeaf(trHtlc.SuccessScript()) + timeoutLeaf := txscript.NewBaseTapLeaf(trHtlc.TimeoutScript()) successLeafHash := successLeaf.TapHash() tapscript := input.TapscriptPartialReveal( @@ -436,8 +445,19 @@ func (h *HtlcScriptV1) IsSuccessWitness(witness wire.TxWitness) bool { return !isTimeoutTx } -// Script returns the htlc script. -func (h *HtlcScriptV1) Script() []byte { +// TimeoutScript returns the redeem script required to unlock the htlc after +// timeout. +// +// In the case of HtlcScriptV1, this is the full segwit v0 script. +func (h *HtlcScriptV1) TimeoutScript() []byte { + return h.script +} + +// SuccessScript returns the redeem script required to unlock the htlc using +// the preimage. +// +// In the case of HtlcScriptV1, this is the full segwit v0 script. +func (h *HtlcScriptV1) SuccessScript() []byte { return h.script } @@ -474,6 +494,11 @@ func (h *HtlcScriptV1) SuccessSequence() uint32 { return 0 } +// Sighash is the signature hash to use for transactions spending from the htlc. +func (h *HtlcScriptV1) SigHash() txscript.SigHashType { + return txscript.SigHashAll +} + // lockingConditions return the address, pkScript and sigScript (if // required) for a htlc script. func (h *HtlcScriptV1) lockingConditions(htlcOutputType HtlcOutputType, @@ -577,8 +602,19 @@ func (h *HtlcScriptV2) GenTimeoutWitness( return witnessStack, nil } -// Script returns the htlc script. -func (h *HtlcScriptV2) Script() []byte { +// TimeoutScript returns the redeem script required to unlock the htlc after +// timeout. +// +// In the case of HtlcScriptV2, this is the full segwit v0 script. +func (h *HtlcScriptV2) TimeoutScript() []byte { + return h.script +} + +// SuccessScript returns the redeem script required to unlock the htlc using +// the preimage. +// +// In the case of HtlcScriptV2, this is the full segwit v0 script. +func (h *HtlcScriptV2) SuccessScript() []byte { return h.script } @@ -616,6 +652,11 @@ func (h *HtlcScriptV2) SuccessSequence() uint32 { return 1 } +// Sighash is the signature hash to use for transactions spending from the htlc. +func (h *HtlcScriptV2) SigHash() txscript.SigHashType { + return txscript.SigHashAll +} + // lockingConditions return the address, pkScript and sigScript (if // required) for a htlc script. func (h *HtlcScriptV2) lockingConditions(htlcOutputType HtlcOutputType, @@ -626,13 +667,13 @@ func (h *HtlcScriptV2) lockingConditions(htlcOutputType HtlcOutputType, // HtlcScriptV3 encapsulates the htlc v3 script. type HtlcScriptV3 struct { - // TimeoutScript is the final locking script for the timeout path which + // timeoutScript is the final locking script for the timeout path which // is available to the sender after the set blockheight. - TimeoutScript []byte + timeoutScript []byte - // SuccessScript is the final locking script for the success path in + // successScript is the final locking script for the success path in // which the receiver reveals the preimage. - SuccessScript []byte + successScript []byte // InternalPubKey is the public key for the keyspend path which bypasses // the above two locking scripts. @@ -699,8 +740,8 @@ func newHTLCScriptV3(cltvExpiry int32, senderHtlcKey, receiverHtlcKey [33]byte, ) return &HtlcScriptV3{ - TimeoutScript: timeoutPathScript, - SuccessScript: successPathScript, + timeoutScript: timeoutPathScript, + successScript: successPathScript, InternalPubKey: aggregateKey.PreTweakedKey, TaprootKey: taprootKey, RootHash: rootHash, @@ -779,7 +820,7 @@ func (h *HtlcScriptV3) genControlBlock(leafScript []byte) ([]byte, error) { func (h *HtlcScriptV3) genSuccessWitness( receiverSig []byte, preimage lntypes.Preimage) (wire.TxWitness, error) { - controlBlockBytes, err := h.genControlBlock(h.TimeoutScript) + controlBlockBytes, err := h.genControlBlock(h.timeoutScript) if err != nil { return nil, err } @@ -787,7 +828,7 @@ func (h *HtlcScriptV3) genSuccessWitness( return wire.TxWitness{ preimage[:], receiverSig, - h.SuccessScript, + h.successScript, controlBlockBytes, }, nil } @@ -797,14 +838,14 @@ func (h *HtlcScriptV3) genSuccessWitness( func (h *HtlcScriptV3) GenTimeoutWitness( senderSig []byte) (wire.TxWitness, error) { - controlBlockBytes, err := h.genControlBlock(h.SuccessScript) + controlBlockBytes, err := h.genControlBlock(h.successScript) if err != nil { return nil, err } return wire.TxWitness{ senderSig, - h.TimeoutScript, + h.timeoutScript, controlBlockBytes, }, nil } @@ -815,9 +856,20 @@ func (h *HtlcScriptV3) IsSuccessWitness(witness wire.TxWitness) bool { return len(witness) == 4 } -// Script is not implemented, but necessary to conform to interface. -func (h *HtlcScriptV3) Script() []byte { - return nil +// TimeoutScript returns the redeem script required to unlock the htlc after +// timeout. +// +// In the case of HtlcScriptV3, this is the timeout tapleaf. +func (h *HtlcScriptV3) TimeoutScript() []byte { + return h.timeoutScript +} + +// SuccessScript returns the redeem script required to unlock the htlc using +// the preimage. +// +// In the case of HtlcScriptV3, this is the claim tapleaf. +func (h *HtlcScriptV3) SuccessScript() []byte { + return h.successScript } // MaxSuccessWitnessSize returns the maximum witness size for the @@ -864,6 +916,11 @@ func (h *HtlcScriptV3) SuccessSequence() uint32 { return 1 } +// Sighash is the signature hash to use for transactions spending from the htlc. +func (h *HtlcScriptV3) SigHash() txscript.SigHashType { + return txscript.SigHashDefault +} + // lockingConditions return the address, pkScript and sigScript (if required) // for a htlc script. func (h *HtlcScriptV3) lockingConditions(outputType HtlcOutputType, diff --git a/swap/htlc_test.go b/swap/htlc_test.go index 0bf9b9e..96b2f16 100644 --- a/swap/htlc_test.go +++ b/swap/htlc_test.go @@ -158,16 +158,17 @@ func TestHtlcV2(t *testing.T) { ) signTx := func(tx *wire.MsgTx, pubkey *btcec.PublicKey, - signer *input.MockSigner) (input.Signature, error) { + signer *input.MockSigner, witnessScript []byte) ( + input.Signature, error) { signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: pubkey, }, - WitnessScript: htlc.Script(), + WitnessScript: witnessScript, Output: htlcOutput, - HashType: txscript.SigHashAll, + HashType: htlc.SigHash(), SigHashes: txscript.NewTxSigHashes( tx, prevOutFetcher, ), @@ -189,6 +190,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.TxIn[0].Sequence = htlc.SuccessSequence() sweepSig, err := signTx( sweepTx, receiverPubKey, receiverSigner, + htlc.SuccessScript(), ) require.NoError(t, err) @@ -208,6 +210,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.TxIn[0].Sequence = 0 sweepSig, err := signTx( sweepTx, receiverPubKey, receiverSigner, + htlc.SuccessScript(), ) require.NoError(t, err) @@ -226,6 +229,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.LockTime = testCltvExpiry - 1 sweepSig, err := signTx( sweepTx, senderPubKey, senderSigner, + htlc.TimeoutScript(), ) require.NoError(t, err) @@ -244,6 +248,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.LockTime = testCltvExpiry sweepSig, err := signTx( sweepTx, senderPubKey, senderSigner, + htlc.TimeoutScript(), ) require.NoError(t, err) @@ -262,6 +267,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.LockTime = testCltvExpiry sweepSig, err := signTx( sweepTx, receiverPubKey, receiverSigner, + htlc.TimeoutScript(), ) require.NoError(t, err) @@ -297,6 +303,7 @@ func TestHtlcV2(t *testing.T) { sweepTx.LockTime = testCltvExpiry sweepSig, err := signTx( sweepTx, senderPubKey, senderSigner, + htlc.TimeoutScript(), ) require.NoError(t, err) @@ -390,7 +397,7 @@ func TestHtlcV3(t *testing.T) { sig, err := txscript.RawTxInTapscriptSignature( tx, hashCache, 0, value, p2trPkScript, leaf, - txscript.SigHashDefault, privateKey, + htlc.SigHash(), privateKey, ) require.NoError(t, err) @@ -415,7 +422,7 @@ func TestHtlcV3(t *testing.T) { sig := signTx( tx, receiverPrivKey, txscript.NewBaseTapLeaf( - trHtlc.SuccessScript, + trHtlc.SuccessScript(), ), ) witness, err := htlc.genSuccessWitness( @@ -439,7 +446,7 @@ func TestHtlcV3(t *testing.T) { sig := signTx( tx, receiverPrivKey, txscript.NewBaseTapLeaf( - trHtlc.SuccessScript, + trHtlc.SuccessScript(), ), ) witness, err := htlc.genSuccessWitness( @@ -463,7 +470,7 @@ func TestHtlcV3(t *testing.T) { sig := signTx( tx, senderPrivKey, txscript.NewBaseTapLeaf( - trHtlc.TimeoutScript, + trHtlc.TimeoutScript(), ), ) @@ -486,7 +493,7 @@ func TestHtlcV3(t *testing.T) { sig := signTx( tx, senderPrivKey, txscript.NewBaseTapLeaf( - trHtlc.TimeoutScript, + trHtlc.TimeoutScript(), ), ) @@ -509,7 +516,7 @@ func TestHtlcV3(t *testing.T) { sig := signTx( tx, receiverPrivKey, txscript.NewBaseTapLeaf( - trHtlc.TimeoutScript, + trHtlc.TimeoutScript(), ), ) diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 223e104..3e06e9b 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -23,7 +23,7 @@ type Sweeper struct { func (s *Sweeper) CreateSweepTx( globalCtx context.Context, height int32, sequence uint32, htlc *swap.Htlc, htlcOutpoint wire.OutPoint, - keyBytes [33]byte, + keyBytes [33]byte, witnessScript []byte, witnessFunc func(sig []byte) (wire.TxWitness, error), amount, fee btcutil.Amount, destAddr btcutil.Address) (*wire.MsgTx, error) { @@ -59,20 +59,30 @@ func (s *Sweeper) CreateSweepTx( } signDesc := lndclient.SignDescriptor{ - WitnessScript: htlc.Script(), + WitnessScript: witnessScript, Output: &wire.TxOut{ Value: int64(amount), PkScript: htlc.PkScript, }, - HashType: txscript.SigHashAll, + HashType: htlc.SigHash(), InputIndex: 0, KeyDesc: keychain.KeyDescriptor{ PubKey: key, }, } + // We need our previous outputs for taproot spends, and there's no + // harm including them for segwit v0, so we always include our prevOut. + prevOut := []*wire.TxOut{ + { + Value: int64(amount), + PkScript: htlc.PkScript, + }, + } + rawSigs, err := s.Lnd.Signer.SignOutputRaw( - globalCtx, sweepTx, []*lndclient.SignDescriptor{&signDesc}, nil, + globalCtx, sweepTx, []*lndclient.SignDescriptor{&signDesc}, + prevOut, ) if err != nil { return nil, fmt.Errorf("signing: %v", err)