From 638973dce22955718fd606ae0328c9d1d29bc202 Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Tue, 29 Mar 2022 11:31:23 +0200 Subject: [PATCH] swap: add locking conditions to HtlcScript interface Use of the Script() function is problematic when we introduce taproot because our script will vary depending whether we use keyspend or a tapleaf spend path (and on the tapleaf spent). This has not previously been a problem for segwitv0 scripts, because they contain all of the logical branches for each of our spend conditions in a single script. This commit prepares for removal of the Script() function by moving our address/pkScript/sigScript generation (which need Script()) into each script's implementation of the HtlcScript interface so that they have access to the script directly. --- swap/htlc.go | 210 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 80 deletions(-) diff --git a/swap/htlc.go b/swap/htlc.go index 6f5a44d5..2c20e91f 100644 --- a/swap/htlc.go +++ b/swap/htlc.go @@ -65,6 +65,11 @@ type HtlcScript interface { // 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, + []byte, []byte, error) + // MaxSuccessWitnessSize returns the maximum witness size for the // success case witness. MaxSuccessWitnessSize() int @@ -190,86 +195,11 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, return nil, err } - var pkScript, sigScript []byte - var address btcutil.Address - - switch outputType { - case HtlcNP2WSH: - p2wshPkScript, err := input.WitnessScriptHash(htlc.Script()) - if err != nil { - return nil, err - } - - // Generate p2sh script for p2wsh (nested). - p2wshPkScriptHash := sha256.Sum256(p2wshPkScript) - hash160 := input.Ripemd160H(p2wshPkScriptHash[:]) - - builder := txscript.NewScriptBuilder() - - builder.AddOp(txscript.OP_HASH160) - builder.AddData(hash160) - builder.AddOp(txscript.OP_EQUAL) - - pkScript, err = builder.Script() - if err != nil { - return nil, err - } - - // Generate a valid sigScript that will allow us to spend the - // p2sh output. The sigScript will contain only a single push of - // the p2wsh witness program corresponding to the matching - // public key of this address. - sigScript, err = txscript.NewScriptBuilder(). - AddData(p2wshPkScript). - Script() - if err != nil { - return nil, err - } - - address, err = btcutil.NewAddressScriptHash( - p2wshPkScript, chainParams, - ) - if err != nil { - return nil, err - } - - case HtlcP2WSH: - pkScript, err = input.WitnessScriptHash(htlc.Script()) - if err != nil { - return nil, err - } - - address, err = btcutil.NewAddressWitnessScriptHash( - pkScript[2:], - chainParams, - ) - if err != nil { - return nil, err - } - - case HtlcP2TR: - // Confirm we have a v3 htlc. - trHtlc, ok := htlc.(*HtlcScriptV3) - if !ok { - return nil, ErrInvalidOutputSelected - } - - // Generate a tapscript address from our HTLC's taptree. - address, err = btcutil.NewAddressTaproot( - schnorr.SerializePubKey(trHtlc.TaprootKey), chainParams, - ) - if err != nil { - return nil, err - } - - // Generate locking script. - pkScript, err = txscript.PayToAddrScript(address) - if err != nil { - return nil, err - } - - default: - return nil, errors.New("unknown output type") + address, pkScript, sigScript, err := htlc.lockingConditions( + outputType, chainParams, + ) + if err != nil { + return nil, fmt.Errorf("could not get address: %w", err) } return &Htlc{ @@ -284,6 +214,79 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, }, nil } +// segwitV0LockingConditions provides the address, pkScript and sigScript (if +// required) for the segwit v0 script and output type provided. +func segwitV0LockingConditions(outputType HtlcOutputType, + chainParams *chaincfg.Params, script []byte) (btcutil.Address, + []byte, []byte, error) { + + switch outputType { + case HtlcNP2WSH: + p2wshPkScript, err := input.WitnessScriptHash(script) + if err != nil { + return nil, nil, nil, err + } + + // Generate p2sh script for p2wsh (nested). + p2wshPkScriptHash := sha256.Sum256(p2wshPkScript) + hash160 := input.Ripemd160H(p2wshPkScriptHash[:]) + + builder := txscript.NewScriptBuilder() + + builder.AddOp(txscript.OP_HASH160) + builder.AddData(hash160) + builder.AddOp(txscript.OP_EQUAL) + + pkScript, err := builder.Script() + if err != nil { + return nil, nil, nil, err + } + + // Generate a valid sigScript that will allow us to spend the + // p2sh output. The sigScript will contain only a single push of + // the p2wsh witness program corresponding to the matching + // public key of this address. + sigScript, err := txscript.NewScriptBuilder(). + AddData(p2wshPkScript). + Script() + if err != nil { + return nil, nil, nil, err + } + + address, err := btcutil.NewAddressScriptHash( + p2wshPkScript, chainParams, + ) + if err != nil { + return nil, nil, nil, err + } + + return address, pkScript, sigScript, nil + + case HtlcP2WSH: + pkScript, err := input.WitnessScriptHash(script) + if err != nil { + return nil, nil, nil, err + } + + address, err := btcutil.NewAddressWitnessScriptHash( + pkScript[2:], + chainParams, + ) + if err != nil { + return nil, nil, nil, err + } + + // Pay to witness script hash (segwit v0) does not need a + // sigScript (we provide it in the witness instead), so we + // return nil for our sigScript. + return address, pkScript, nil, nil + + default: + return nil, nil, nil, fmt.Errorf("unexpected output type: %d", + outputType) + } +} + // GenSuccessWitness returns the success script to spend this htlc with // the preimage. func (h *Htlc) GenSuccessWitness(receiverSig []byte, @@ -491,6 +494,14 @@ func (h *HtlcScriptV1) SuccessSequence() uint32 { return 0 } +// lockingConditions return the address, pkScript and sigScript (if +// required) for a htlc script. +func (h *HtlcScriptV1) lockingConditions(htlcOutputType HtlcOutputType, + params *chaincfg.Params) (btcutil.Address, []byte, []byte, error) { + + return segwitV0LockingConditions(htlcOutputType, params, h.script) +} + // HtlcScriptV2 encapsulates the htlc v2 script. type HtlcScriptV2 struct { script []byte @@ -625,6 +636,14 @@ func (h *HtlcScriptV2) SuccessSequence() uint32 { return 1 } +// lockingConditions return the address, pkScript and sigScript (if +// required) for a htlc script. +func (h *HtlcScriptV2) lockingConditions(htlcOutputType HtlcOutputType, + params *chaincfg.Params) (btcutil.Address, []byte, []byte, error) { + + return segwitV0LockingConditions(htlcOutputType, params, h.script) +} + // HtlcScriptV3 encapsulates the htlc v3 script. type HtlcScriptV3 struct { // The final locking script for the timeout path which is available to @@ -861,3 +880,34 @@ func (h *HtlcScriptV3) MaxTimeoutWitnessSize() int { func (h *HtlcScriptV3) SuccessSequence() uint32 { return 1 } + +// lockingConditions return the address, pkScript and sigScript (if required) +// for a htlc script. +func (h *HtlcScriptV3) lockingConditions(outputType HtlcOutputType, + chainParams *chaincfg.Params) (btcutil.Address, []byte, []byte, error) { + + // HtlcV3 can only have taproot output type, because we utilize + // tapscript claim paths. + if outputType != HtlcP2TR { + return nil, nil, nil, fmt.Errorf("htlc v3 only supports P2TR "+ + "outputs, got: %v", outputType) + } + + // Generate a tapscript address from our tree. + address, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(h.TaprootKey), chainParams, + ) + if err != nil { + return nil, nil, nil, err + } + + // Generate locking script. + pkScript, err := txscript.PayToAddrScript(address) + if err != nil { + return nil, nil, nil, err + } + + // Taproot (segwit v1) does not need a sigScript (we provide it in the + // witness instead), so we return nil for our sigScript. + return address, pkScript, nil, nil +}