diff --git a/swap/htlc.go b/swap/htlc.go index 6f5a44d..2c20e91 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,14 +195,36 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, return nil, err } - var pkScript, sigScript []byte - var address btcutil.Address + address, pkScript, sigScript, err := htlc.lockingConditions( + outputType, chainParams, + ) + if err != nil { + return nil, fmt.Errorf("could not get address: %w", err) + } + + return &Htlc{ + HtlcScript: htlc, + Hash: hash, + Version: version, + PkScript: pkScript, + OutputType: outputType, + ChainParams: chainParams, + Address: address, + SigScript: sigScript, + }, 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(htlc.Script()) + p2wshPkScript, err := input.WitnessScriptHash(script) if err != nil { - return nil, err + return nil, nil, nil, err } // Generate p2sh script for p2wsh (nested). @@ -210,78 +237,54 @@ func NewHtlc(version ScriptVersion, cltvExpiry int32, builder.AddData(hash160) builder.AddOp(txscript.OP_EQUAL) - pkScript, err = builder.Script() + pkScript, err := builder.Script() if err != nil { - return nil, err + 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(). + sigScript, err := txscript.NewScriptBuilder(). AddData(p2wshPkScript). Script() if err != nil { - return nil, err + return nil, nil, nil, err } - address, err = btcutil.NewAddressScriptHash( + address, err := btcutil.NewAddressScriptHash( p2wshPkScript, chainParams, ) if err != nil { - return nil, err + return nil, nil, nil, err } + return address, pkScript, sigScript, nil + case HtlcP2WSH: - pkScript, err = input.WitnessScriptHash(htlc.Script()) + pkScript, err := input.WitnessScriptHash(script) if err != nil { - return nil, err + return nil, nil, nil, err } - address, err = btcutil.NewAddressWitnessScriptHash( + 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 + return nil, nil, nil, err } - // Generate locking script. - pkScript, err = txscript.PayToAddrScript(address) - if err != nil { - return 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, errors.New("unknown output type") + return nil, nil, nil, fmt.Errorf("unexpected output type: %d", + outputType) } - - return &Htlc{ - HtlcScript: htlc, - Hash: hash, - Version: version, - PkScript: pkScript, - OutputType: outputType, - ChainParams: chainParams, - Address: address, - SigScript: sigScript, - }, nil } // GenSuccessWitness returns the success script to spend this htlc with @@ -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 +}