mirror of
https://github.com/lightninglabs/loop
synced 2024-11-08 01:10:29 +00:00
multi: update sweeping to allow different sighashes and claim scripts
Taproot spends require a different sighash, so we update our HtlcScript interface to provide the appropriate sighash when sweeping. We also add distinct Timeout/Success Script functions to allow for tapleaf spends which have different locking scripts for different paths. Note that the timeout and success paths will be the same for segwit v0 htlcs, because it has a single branched script containing all spend paths. In future iterations, this differentiation of claim scripts can also be used to use musig2 to collaboratively keyspend P2TR htlcs with the server. This script can be expressed as PriorityScript (because we'll try to keyspend as a priority, and then fall back to a tap leaf spend). As we've done here, segwit v0 spends would just return their single script for PriorityScript, and the claim would be no different from our other claims.
This commit is contained in:
parent
9610becebd
commit
c7ef4297c0
@ -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
|
||||
|
@ -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
|
||||
|
105
swap/htlc.go
105
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,
|
||||
|
@ -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(),
|
||||
),
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user