From cd0a5f9f0699e2e20f9c2eb1e78b0e901f92e2cb Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 20 Jul 2020 18:16:28 +0200 Subject: [PATCH] swap: extend swap to be able to create the V2 htlc --- loopin.go | 2 +- swap/htlc.go | 315 +++++++++++++++++++++++++++++++++++++++-------- sweep/sweeper.go | 2 +- 3 files changed, 266 insertions(+), 53 deletions(-) diff --git a/loopin.go b/loopin.go index 89777d9..00f2eb0 100644 --- a/loopin.go +++ b/loopin.go @@ -756,7 +756,7 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context, } witnessFunc := func(sig []byte) (wire.TxWitness, error) { - return s.htlc.GenTimeoutWitness(sig) + return s.htlc.GenTimeoutWitness(sig), nil } timeoutTx, err := s.sweeper.CreateSweepTx( diff --git a/swap/htlc.go b/swap/htlc.go index 4cc6ca5..9982cc4 100644 --- a/swap/htlc.go +++ b/swap/htlc.go @@ -32,12 +32,46 @@ type ScriptVersion uint8 const ( // HtlcV1 refers to the original version of the HTLC script. HtlcV1 ScriptVersion = iota + + // HtlcV2 refers to the improved version of the HTLC script. + HtlcV2 ) +// htlcScript defines an interface for the different HTLC implementations. +type HtlcScript interface { + // genSuccessWitness returns the success script to spend this htlc with + // the preimage. + genSuccessWitness(receiverSig []byte, preimage lntypes.Preimage) wire.TxWitness + + // GenTimeoutWitness returns the timeout script to spend this htlc after + // timeout. + GenTimeoutWitness(senderSig []byte) wire.TxWitness + + // IsSuccessWitness checks whether the given stack is valid for + // redeeming the htlc. + IsSuccessWitness(witness wire.TxWitness) bool + + // Script returns the htlc script. + Script() []byte + + // MaxSuccessWitnessSize returns the maximum witness size for the + // success case witness. + MaxSuccessWitnessSize() int + + // MaxTimeoutWitnessSize returns the maximum witness size for the + // timeout case witness. + MaxTimeoutWitnessSize() int + + // SuccessSequence returns the sequence to spend this htlc in the + // success case. + SuccessSequence() uint32 +} + // Htlc contains relevant htlc information from the receiver perspective. type Htlc struct { + HtlcScript + Version ScriptVersion - Script []byte PkScript []byte Hash lntypes.Hash OutputType HtlcOutputType @@ -55,10 +89,12 @@ var ( // the maximum value for cltv expiry to get the maximum (worst case) // script size. QuoteHtlc, _ = NewHtlc( - HtlcV1, + HtlcV2, ^int32(0), quoteKey, quoteKey, quoteHash, HtlcP2WSH, &chaincfg.MainNetParams, ) + + ErrInvalidScriptVersion = fmt.Errorf("invalid script version") ) // String returns the string value of HtlcOutputType. @@ -82,25 +118,30 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, chainParams *chaincfg.Params) (*Htlc, error) { var ( - err error - script []byte + err error + htlc HtlcScript ) switch version { case HtlcV1: - script, err = swapHTLCScriptV1( + htlc, err = newHTLCScriptV1( + cltvExpiry, senderKey, receiverKey, hash, + ) + + case HtlcV2: + htlc, err = newHTLCScriptV2( cltvExpiry, senderKey, receiverKey, hash, ) default: - return nil, fmt.Errorf("unknown script version: %v", version) + return nil, ErrInvalidScriptVersion } if err != nil { return nil, err } - p2wshPkScript, err := input.WitnessScriptHash(script) + p2wshPkScript, err := input.WitnessScriptHash(htlc.Script()) if err != nil { return nil, err } @@ -158,9 +199,9 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, } return &Htlc{ + HtlcScript: htlc, Hash: hash, Version: version, - Script: script, PkScript: pkScript, OutputType: outputType, ChainParams: chainParams, @@ -169,7 +210,50 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, }, nil } -// SwapHTLCScriptV1 returns the on-chain HTLC witness script. +// GenSuccessWitness returns the success script to spend this htlc with +// the preimage. +func (h *Htlc) GenSuccessWitness(receiverSig []byte, + preimage lntypes.Preimage) (wire.TxWitness, error) { + + if h.Hash != preimage.Hash() { + return nil, errors.New("preimage doesn't match hash") + } + + return h.genSuccessWitness(receiverSig, preimage), nil +} + +// AddSuccessToEstimator adds a successful spend to a weight estimator. +func (h *Htlc) AddSuccessToEstimator(estimator *input.TxWeightEstimator) { + maxSuccessWitnessSize := h.MaxSuccessWitnessSize() + + switch h.OutputType { + case HtlcP2WSH: + estimator.AddWitnessInput(maxSuccessWitnessSize) + + case HtlcNP2WSH: + estimator.AddNestedP2WSHInput(maxSuccessWitnessSize) + } +} + +// AddTimeoutToEstimator adds a timeout spend to a weight estimator. +func (h *Htlc) AddTimeoutToEstimator(estimator *input.TxWeightEstimator) { + maxTimeoutWitnessSize := h.MaxTimeoutWitnessSize() + + switch h.OutputType { + case HtlcP2WSH: + estimator.AddWitnessInput(maxTimeoutWitnessSize) + + case HtlcNP2WSH: + estimator.AddNestedP2WSHInput(maxTimeoutWitnessSize) + } +} + +// HtlcScriptV1 encapsulates the htlc v1 script. +type HtlcScriptV1 struct { + script []byte +} + +// newHTLCScriptV1 constructs an HtlcScript with the HTLC V1 witness script. // // OP_SIZE 32 OP_EQUAL // OP_IF @@ -181,8 +265,8 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, // // OP_ENDIF // OP_CHECKSIG -func swapHTLCScriptV1(cltvExpiry int32, senderHtlcKey, - receiverHtlcKey [33]byte, swapHash lntypes.Hash) ([]byte, error) { +func newHTLCScriptV1(cltvExpiry int32, senderHtlcKey, + receiverHtlcKey [33]byte, swapHash lntypes.Hash) (*HtlcScriptV1, error) { builder := txscript.NewScriptBuilder() @@ -212,90 +296,219 @@ func swapHTLCScriptV1(cltvExpiry int32, senderHtlcKey, builder.AddOp(txscript.OP_CHECKSIG) - return builder.Script() -} + script, err := builder.Script() + if err != nil { + return nil, err + } -// GenSuccessWitness returns the success script to spend this htlc with the -// preimage. -func (h *Htlc) GenSuccessWitness(receiverSig []byte, - preimage lntypes.Preimage) (wire.TxWitness, error) { + return &HtlcScriptV1{ + script: script, + }, nil +} - if h.Hash != preimage.Hash() { - return nil, errors.New("preimage doesn't match hash") - } +// genSuccessWitness returns the success script to spend this htlc with +// the preimage. +func (h *HtlcScriptV1) genSuccessWitness(receiverSig []byte, + preimage lntypes.Preimage) wire.TxWitness { witnessStack := make(wire.TxWitness, 3) witnessStack[0] = append(receiverSig, byte(txscript.SigHashAll)) witnessStack[1] = preimage[:] - witnessStack[2] = h.Script + witnessStack[2] = h.script - return witnessStack, nil + return witnessStack +} + +// GenTimeoutWitness returns the timeout script to spend this htlc after +// timeout. +func (h *HtlcScriptV1) GenTimeoutWitness(senderSig []byte) wire.TxWitness { + + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = append(senderSig, byte(txscript.SigHashAll)) + witnessStack[1] = []byte{0} + witnessStack[2] = h.script + + return witnessStack } // IsSuccessWitness checks whether the given stack is valid for redeeming the // htlc. -func (h *Htlc) IsSuccessWitness(witness wire.TxWitness) bool { +func (h *HtlcScriptV1) IsSuccessWitness(witness wire.TxWitness) bool { if len(witness) != 3 { return false } isTimeoutTx := bytes.Equal([]byte{0}, witness[1]) + return !isTimeoutTx +} + +// Script returns the htlc script. +func (h *HtlcScriptV1) Script() []byte { + return h.script +} + +// MaxSuccessWitnessSize returns the maximum success witness size. +func (h *HtlcScriptV1) MaxSuccessWitnessSize() int { + // Calculate maximum success witness size + // + // - number_of_witness_elements: 1 byte + // - receiver_sig_length: 1 byte + // - receiver_sig: 73 bytes + // - preimage_length: 1 byte + // - preimage: 32 bytes + // - witness_script_length: 1 byte + // - witness_script: len(script) bytes + return 1 + 1 + 73 + 1 + 32 + 1 + len(h.script) +} + +// MaxTimeoutWitnessSize return the maximum timeout witness size. +func (h *HtlcScriptV1) MaxTimeoutWitnessSize() int { + // Calculate maximum timeout witness size + // + // - number_of_witness_elements: 1 byte + // - sender_sig_length: 1 byte + // - sender_sig: 73 bytes + // - zero_length: 1 byte + // - zero: 1 byte + // - witness_script_length: 1 byte + // - witness_script: len(script) bytes + return 1 + 1 + 73 + 1 + 1 + 1 + len(h.script) +} + +// SuccessSequence returns the sequence to spend this htlc in the success case. +func (h *HtlcScriptV1) SuccessSequence() uint32 { + return 0 +} + +// HtlcScriptV2 encapsulates the htlc v2 script. +type HtlcScriptV2 struct { + script []byte + senderKey [33]byte +} + +// newHTLCScriptV2 construct an HtlcScipt with the HTLC V2 witness script. +// +// OP_CHECKSIG OP_NOTIF +// OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIGVERIFY +// OP_CHECKLOCKTIMEVERIFY +// OP_ELSE +// OP_SIZE <20> OP_EQUALVERIFY OP_HASH160 OP_EQUALVERIFY 1 +// OP_CHECKSEQUENCEVERIFY +// OP_ENDIF +func newHTLCScriptV2(cltvExpiry int32, senderHtlcKey, + receiverHtlcKey [33]byte, swapHash lntypes.Hash) (*HtlcScriptV2, error) { + + builder := txscript.NewScriptBuilder() + builder.AddData(receiverHtlcKey[:]) + builder.AddOp(txscript.OP_CHECKSIG) + + builder.AddOp(txscript.OP_NOTIF) + + builder.AddOp(txscript.OP_DUP) + builder.AddOp(txscript.OP_HASH160) + senderHtlcKeyHash := sha256.Sum256(senderHtlcKey[:]) + builder.AddData(input.Ripemd160H(senderHtlcKeyHash[:])) + + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + builder.AddInt64(int64(cltvExpiry)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + + builder.AddOp(txscript.OP_ELSE) + + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(0x20) + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddOp(txscript.OP_HASH160) + builder.AddData(input.Ripemd160H(swapHash[:])) + builder.AddOp(txscript.OP_EQUALVERIFY) + builder.AddOp(txscript.OP_1) + + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + builder.AddOp(txscript.OP_ENDIF) + + script, err := builder.Script() + if err != nil { + return nil, err + } + + return &HtlcScriptV2{ + script: script, + senderKey: senderHtlcKey, + }, nil +} + +// genSuccessWitness returns the success script to spend this htlc with +// the preimage. +func (h *HtlcScriptV2) genSuccessWitness(receiverSig []byte, + preimage lntypes.Preimage) wire.TxWitness { + + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = preimage[:] + witnessStack[1] = append(receiverSig, byte(txscript.SigHashAll)) + witnessStack[2] = h.script + + return witnessStack +} + +// IsSuccessWitness checks whether the given stack is valid for redeeming the +// htlc. +func (h *HtlcScriptV2) IsSuccessWitness(witness wire.TxWitness) bool { + isTimeoutTx := len(witness) == 4 return !isTimeoutTx } // GenTimeoutWitness returns the timeout script to spend this htlc after // timeout. -func (h *Htlc) GenTimeoutWitness(senderSig []byte) (wire.TxWitness, error) { +func (h *HtlcScriptV2) GenTimeoutWitness(senderSig []byte) wire.TxWitness { - witnessStack := make(wire.TxWitness, 3) + witnessStack := make(wire.TxWitness, 4) witnessStack[0] = append(senderSig, byte(txscript.SigHashAll)) - witnessStack[1] = []byte{0} - witnessStack[2] = h.Script + witnessStack[1] = h.senderKey[:] + witnessStack[2] = []byte{} + witnessStack[3] = h.script - return witnessStack, nil + return witnessStack } -// AddSuccessToEstimator adds a successful spend to a weight estimator. -func (h *Htlc) AddSuccessToEstimator(estimator *input.TxWeightEstimator) { +// Script returns the htlc script. +func (h *HtlcScriptV2) Script() []byte { + return h.script +} + +// MaxSuccessWitnessSize returns maximum success witness size. +func (h *HtlcScriptV2) MaxSuccessWitnessSize() int { // Calculate maximum success witness size // // - number_of_witness_elements: 1 byte // - receiver_sig_length: 1 byte // - receiver_sig: 73 bytes // - preimage_length: 1 byte - // - preimage: 33 bytes + // - preimage: 32 bytes // - witness_script_length: 1 byte // - witness_script: len(script) bytes - maxSuccessWitnessSize := 1 + 1 + 73 + 1 + 33 + 1 + len(h.Script) - - switch h.OutputType { - case HtlcP2WSH: - estimator.AddWitnessInput(maxSuccessWitnessSize) - - case HtlcNP2WSH: - estimator.AddNestedP2WSHInput(maxSuccessWitnessSize) - } + return 1 + 1 + 73 + 1 + 32 + 1 + len(h.script) } -// AddTimeoutToEstimator adds a timeout spend to a weight estimator. -func (h *Htlc) AddTimeoutToEstimator(estimator *input.TxWeightEstimator) { +// MaxTimeoutWitnessSize returns maximum timeout witness size. +func (h *HtlcScriptV2) MaxTimeoutWitnessSize() int { // Calculate maximum timeout witness size // // - number_of_witness_elements: 1 byte // - sender_sig_length: 1 byte // - sender_sig: 73 bytes - // - zero_length: 1 byte + // - sender_key_length: 1 byte + // - sender_key: 33 bytes // - zero: 1 byte // - witness_script_length: 1 byte // - witness_script: len(script) bytes - maxTimeoutWitnessSize := 1 + 1 + 73 + 1 + 1 + 1 + len(h.Script) - - switch h.OutputType { - case HtlcP2WSH: - estimator.AddWitnessInput(maxTimeoutWitnessSize) + return 1 + 1 + 73 + 1 + 33 + 1 + 1 + len(h.script) +} - case HtlcNP2WSH: - estimator.AddNestedP2WSHInput(maxTimeoutWitnessSize) - } +// SuccessSequence returns the sequence to spend this htlc in the success case. +func (h *HtlcScriptV2) SuccessSequence() uint32 { + return 1 } diff --git a/sweep/sweeper.go b/sweep/sweeper.go index 22db0c5..2cd4884 100644 --- a/sweep/sweeper.go +++ b/sweep/sweeper.go @@ -58,7 +58,7 @@ func (s *Sweeper) CreateSweepTx( } signDesc := lndclient.SignDescriptor{ - WitnessScript: htlc.Script, + WitnessScript: htlc.Script(), Output: &wire.TxOut{ Value: int64(amount), },