diff --git a/input/script_utils.go b/input/script_utils.go index 1d0df7e3f..6e55539f1 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -492,6 +492,163 @@ func SenderHtlcSpendTimeout(receiverSig Signature, return witnessStack, nil } +// SenderHTLCTapLeafTimeout returns the full tapscript leaf for the timeout +// path of the sender HTLC. This is a small script that allows the sender to +// timeout the HTLC after a period of time: +// +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +func SenderHTLCTapLeafTimeout(senderHtlcKey, + receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + + timeoutLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(timeoutLeafScript), nil +} + +// SenderHTLCTapLeafSuccess returns the full tapscript leaf for the success +// path of the sender HTLC. This is a small script that allows the receiver to +// redeem the HTLC with a pre-image: +// +// OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 +// OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, + paymentHash []byte) (txscript.TapLeaf, error) { + + builder := txscript.NewScriptBuilder() + + // Check that the pre-image is 32 bytes as required. + builder.AddOp(txscript.OP_SIZE) + builder.AddInt64(32) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Check that the specified pre-images matches what we hard code into + // the script. + builder.AddOp(txscript.OP_HASH160) + builder.AddData(Ripemd160H(paymentHash)) + builder.AddOp(txscript.OP_EQUALVERIFY) + + // Verify the remote party's signature, then make them wait 1 block + // after confirmation to properly sweep. + builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) + builder.AddOp(txscript.OP_CHECKSIG) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + + successLeafScript, err := builder.Script() + if err != nil { + return txscript.TapLeaf{}, err + } + + return txscript.NewBaseTapLeaf(successLeafScript), nil +} + +// HtlcScriptTree... +type HtlcScriptTree struct { + // TaprootKey... + TaprootKey *btcec.PublicKey + + // SuccessTapLeaf... + SuccessTapLeaf txscript.TapLeaf + + // TimeoutTapLeaf... + TimeoutTapLeaf txscript.TapLeaf + + // TapscriptTree... + TapscriptTree *txscript.IndexedTapScriptTree +} + +// senderHtlcTapScriptTree builds the tapscript tree which is used to anchor +// the HTLC key for HTLCs on the sender's commitment. +func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // First, we'll obtain the tap leaves for both the success and timeout + // path. + successTapLeaf, err := SenderHTLCTapLeafSuccess( + receiverHtlcKey, payHash, + ) + if err != nil { + return nil, err + } + timeoutTapLeaf, err := SenderHTLCTapLeafTimeout( + senderHtlcKey, receiverHtlcKey, + ) + if err != nil { + return nil, err + } + + // With the two leaves obtained, we'll now make the tapscript tree, + // then obtain the root from that + tapscriptTree := txscript.AssembleTaprootScriptTree( + successTapLeaf, timeoutTapLeaf, + ) + + tapScriptRoot := tapscriptTree.RootNode.TapHash() + + // With the tapscript root obtained, we'll tweak the revocation key + // with this value to obtain the key that HTLCs will be sent to. + htlcKey := txscript.ComputeTaprootOutputKey( + revokeKey, tapScriptRoot[:], + ) + + return &HtlcScriptTree{ + TaprootKey: htlcKey, + SuccessTapLeaf: successTapLeaf, + TimeoutTapLeaf: timeoutTapLeaf, + TapscriptTree: tapscriptTree, + }, nil +} + +// SenderHTLCScriptTaproot constructs the taproot witness program (schnorr key) +// for an outgoing HTLC on the sender's version of the commitment transaction. +// This method returns the top level tweaked public key that commits to both +// the script paths. +// +// The returned key commits to a tapscript tree with two possible paths: +// +// - Timeout path: +// OP_CHECKSIGVERIFY +// OP_CHECKSIG +// +// - Success path: +// OP_SIZE 32 OP_EQUALVERIFY +// OP_HASH160 OP_EQUALVERIFY +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The timeout path can be spent with a witness of (sender timeout): +// +// +// +// The success path can be spent with a valid control block, and a witness of +// (receiver redeem): +// +// +// +// The top level keyspend key is the revocation key, which allows a defender to +// unilaterally spend the created output. +func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, + revokeKey *btcec.PublicKey, payHash []byte) (*HtlcScriptTree, error) { + + // Given all the necessary parameters, we'll return the HTLC script + // tree that includes the top level output script, as well as the two + // tap leaf paths. + return senderHtlcTapScriptTree( + senderHtlcKey, receiverHtlcKey, revokeKey, payHash, + ) +} // ReceiverHTLCScript constructs the public key script for an incoming HTLC // output payment for the receiver's version of the commitment transaction. The // possible execution paths from this script include: