input: switch to using new txscript.ScriptTemplate function

In this commit, we switch to using the new txscript.ScriptTemplate
function. This allows us to write the script in plain text, using some
hidden template operations to swap in items like keys or sigs.

This reduces in less code and boiler plate over all, the code that
defines the script now reads as if it was a comment.
This commit is contained in:
Olaoluwa Osuntokun
2025-03-24 18:29:40 -07:00
parent 30656178bc
commit 748d6e109f
2 changed files with 299 additions and 506 deletions

View File

@@ -20,6 +20,10 @@ import (
"golang.org/x/crypto/ripemd160" "golang.org/x/crypto/ripemd160"
) )
// TemplateParams is a type alias for the map[string]interface{} type used
// with txscript.ScriptTemplate to make code more readable.
type TemplateParams map[string]interface{}
var ( var (
// TODO(roasbeef): remove these and use the one's defined in txscript // TODO(roasbeef): remove these and use the one's defined in txscript
// within testnet-L. // within testnet-L.
@@ -83,69 +87,62 @@ func ParseSignature(rawSig []byte) (Signature, error) {
// WitnessScriptHash generates a pay-to-witness-script-hash public key script // WitnessScriptHash generates a pay-to-witness-script-hash public key script
// paying to a version 0 witness program paying to the passed redeem script. // paying to a version 0 witness program paying to the passed redeem script.
func WitnessScriptHash(witnessScript []byte) ([]byte, error) { func WitnessScriptHash(witnessScript []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WSHSize),
)
bldr.AddOp(txscript.OP_0)
scriptHash := sha256.Sum256(witnessScript) scriptHash := sha256.Sum256(witnessScript)
bldr.AddData(scriptHash[:]) return txscript.ScriptTemplate(
return bldr.Script() `OP_0 {{ hex .ScriptHash }}`,
txscript.WithScriptTemplateParams(TemplateParams{
"ScriptHash": scriptHash[:],
}),
)
} }
// WitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key script // WitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key script
// paying to a version 0 witness program containing the passed serialized // paying to a version 0 witness program containing the passed serialized
// public key. // public key.
func WitnessPubKeyHash(pubkey []byte) ([]byte, error) { func WitnessPubKeyHash(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder(
txscript.WithScriptAllocSize(P2WPKHSize),
)
bldr.AddOp(txscript.OP_0)
pkhash := btcutil.Hash160(pubkey) pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash) return txscript.ScriptTemplate(
return bldr.Script() `OP_0 {{ hex .PKHash }}`,
txscript.WithScriptTemplateParams(TemplateParams{
"PKHash": pkhash,
}),
)
} }
// GenerateP2SH generates a pay-to-script-hash public key script paying to the // GenerateP2SH generates a pay-to-script-hash public key script paying to the
// passed redeem script. // passed redeem script.
func GenerateP2SH(script []byte) ([]byte, error) { func GenerateP2SH(script []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder( scriptHash := btcutil.Hash160(script)
txscript.WithScriptAllocSize(NestedP2WPKHSize), return txscript.ScriptTemplate(
`OP_HASH160 {{ hex .ScriptHash }} OP_EQUAL`,
txscript.WithScriptTemplateParams(TemplateParams{
"ScriptHash": scriptHash,
}),
) )
bldr.AddOp(txscript.OP_HASH160)
scripthash := btcutil.Hash160(script)
bldr.AddData(scripthash)
bldr.AddOp(txscript.OP_EQUAL)
return bldr.Script()
} }
// GenerateP2PKH generates a pay-to-public-key-hash public key script paying to // GenerateP2PKH generates a pay-to-public-key-hash public key script paying to
// the passed serialized public key. // the passed serialized public key.
func GenerateP2PKH(pubkey []byte) ([]byte, error) { func GenerateP2PKH(pubkey []byte) ([]byte, error) {
bldr := txscript.NewScriptBuilder( pkHash := btcutil.Hash160(pubkey)
txscript.WithScriptAllocSize(P2PKHSize), return txscript.ScriptTemplate(
`OP_DUP OP_HASH160 {{ hex .pkh }} OP_EQUALVERIFY OP_CHECKSIG`,
txscript.WithScriptTemplateParams(TemplateParams{
"pkh": pkHash,
}),
) )
bldr.AddOp(txscript.OP_DUP)
bldr.AddOp(txscript.OP_HASH160)
pkhash := btcutil.Hash160(pubkey)
bldr.AddData(pkhash)
bldr.AddOp(txscript.OP_EQUALVERIFY)
bldr.AddOp(txscript.OP_CHECKSIG)
return bldr.Script()
} }
// GenerateUnknownWitness generates the maximum-sized witness public key script // GenerateUnknownWitness generates the maximum-sized witness public key script
// consisting of a version push and a 40-byte data push. // consisting of a version push and a 40-byte data push.
func GenerateUnknownWitness() ([]byte, error) { func GenerateUnknownWitness() ([]byte, error) {
bldr := txscript.NewScriptBuilder()
bldr.AddOp(txscript.OP_0)
witnessScript := make([]byte, 40) witnessScript := make([]byte, 40)
bldr.AddData(witnessScript) return txscript.ScriptTemplate(
return bldr.Script() `OP_0 {{ hex .WitnessScript }}`,
txscript.WithScriptTemplateParams(TemplateParams{
"WitnessScript": witnessScript,
}),
)
} }
// GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2 // GenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2
@@ -164,15 +161,13 @@ func GenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
aPub, bPub = bPub, aPub aPub, bPub = bPub, aPub
} }
bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( return txscript.ScriptTemplate(
MultiSigSize, `OP_2 {{ hex .pubA }} {{ hex .pubB }} OP_2 OP_CHECKMULTISIG`,
)) txscript.WithScriptTemplateParams(TemplateParams{
bldr.AddOp(txscript.OP_2) "pubA": aPub,
bldr.AddData(aPub) // Add both pubkeys (sorted). "pubB": bPub,
bldr.AddData(bPub) }),
bldr.AddOp(txscript.OP_2) )
bldr.AddOp(txscript.OP_CHECKMULTISIG)
return bldr.Script()
} }
// GenFundingPkScript creates a redeem script, and its matching p2wsh // GenFundingPkScript creates a redeem script, and its matching p2wsh
@@ -338,92 +333,40 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey,
revocationKey *btcec.PublicKey, paymentHash []byte, revocationKey *btcec.PublicKey, paymentHash []byte,
confirmedSpend bool) ([]byte, error) { confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( // Build the base script template
OfferedHtlcScriptSizeConfirmed, scriptTemplate := `
)) OP_DUP OP_HASH160 {{ hex .RevKeyHash }} OP_EQUAL
OP_IF
OP_CHECKSIG
OP_ELSE
{{ hex .ReceiverKey }}
OP_SWAP OP_SIZE 32 OP_EQUAL
OP_NOTIF
OP_DROP 2 OP_SWAP {{ hex .SenderKey }} 2 OP_CHECKMULTISIG
OP_ELSE
OP_HASH160 {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY
OP_CHECKSIG
OP_ENDIF
`
// The opening operations are used to determine if this is the receiver // Add 1 block CSV delay if a confirmation is required.
// of the HTLC attempting to sweep all the funds due to a contract
// breach. In this case, they'll place the revocation key at the top of
// the stack.
builder.AddOp(txscript.OP_DUP)
builder.AddOp(txscript.OP_HASH160)
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed()))
builder.AddOp(txscript.OP_EQUAL)
// If the hash matches, then this is the revocation clause. The output
// can be spent if the check sig operation passes.
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC claiming with
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the receiver's key on the top of
// the stack. This will be needed later if we decide that this is the
// sender activating the time out clause with the HTLC timeout
// transaction.
builder.AddData(receiverHtlcKey.SerializeCompressed())
// Atm, the top item of the stack is the receiverKey's so we use a swap
// to expose what is either the payment pre-image or a signature.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If it isn't then this might be the sender of the HTLC activating the
// time out clause.
builder.AddOp(txscript.OP_NOTIF)
// We'll drop the OP_IF return value off the top of the stack so we can
// reconstruct the multi-sig script used as an off-chain covenant. If
// two valid signatures are provided, then the output will be deemed as
// spendable.
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(senderHtlcKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, then the only other case is that this is the receiver of
// the HTLC sweeping it on-chain with the payment pre-image.
builder.AddOp(txscript.OP_ELSE)
// Hash the top item of the stack and compare it with the hash160 of
// the payment hash, which is already the sha256 of the payment
// pre-image. By using this little trick we're able to save space
// on-chain as the witness includes a 20-byte hash rather than a
// 32-byte hash.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// This checks the receiver's signature so that a third party with
// knowledge of the payment preimage still cannot steal the output.
builder.AddOp(txscript.OP_CHECKSIG)
// Close out the OP_IF statement above.
builder.AddOp(txscript.OP_ENDIF)
// Add 1 block CSV delay if a confirmation is required for the
// non-revocation clauses.
if confirmedSpend { if confirmedSpend {
builder.AddOp(txscript.OP_1) scriptTemplate += ` OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP`
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
} }
// Close out the OP_IF statement at the top of the script. // Close out the top level if statement.
builder.AddOp(txscript.OP_ENDIF) scriptTemplate += ` OP_ENDIF`
return builder.Script() // Use the ScriptTemplate function with the properly formatted template
return txscript.ScriptTemplate(
scriptTemplate,
txscript.WithScriptTemplateParams(TemplateParams{
"RevKeyHash": btcutil.Hash160(revocationKey.SerializeCompressed()), //nolint:ll
"ReceiverKey": receiverHtlcKey.SerializeCompressed(), //nolint:ll
"SenderKey": senderHtlcKey.SerializeCompressed(), //nolint:ll
"PaymentHashRipemd": Ripemd160H(paymentHash),
}),
)
} }
// SenderHtlcSpendRevokeWithKey constructs a valid witness allowing the receiver of an // SenderHtlcSpendRevokeWithKey constructs a valid witness allowing the receiver of an
@@ -559,14 +502,15 @@ func SenderHtlcSpendTimeout(receiverSig Signature,
func SenderHTLCTapLeafTimeout(senderHtlcKey, func SenderHTLCTapLeafTimeout(senderHtlcKey,
receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) { receiverHtlcKey *btcec.PublicKey) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder() timeoutLeafScript, err := txscript.ScriptTemplate(
`
builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) {{ hex .SenderKey }} OP_CHECKSIGVERIFY
builder.AddOp(txscript.OP_CHECKSIGVERIFY) {{ hex .ReceiverKey }} OP_CHECKSIG`,
builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_CHECKSIG) "SenderKey": schnorr.SerializePubKey(senderHtlcKey),
"ReceiverKey": schnorr.SerializePubKey(receiverHtlcKey),
timeoutLeafScript, err := builder.Script() }),
)
if err != nil { if err != nil {
return txscript.TapLeaf{}, err return txscript.TapLeaf{}, err
} }
@@ -585,28 +529,19 @@ func SenderHTLCTapLeafTimeout(senderHtlcKey,
func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
paymentHash []byte) (txscript.TapLeaf, error) { paymentHash []byte) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder() successLeafScript, err := txscript.ScriptTemplate(
`
// Check that the pre-image is 32 bytes as required. OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
builder.AddOp(txscript.OP_SIZE) {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY
builder.AddInt64(32) {{ hex .ReceiverKey }} OP_CHECKSIG
builder.AddOp(txscript.OP_EQUALVERIFY) OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP`,
txscript.WithScriptTemplateParams(TemplateParams{
// Check that the specified pre-image matches what we hard code into "PaymentHashRipemd": Ripemd160H(paymentHash),
// the script. "ReceiverKey": schnorr.SerializePubKey(
builder.AddOp(txscript.OP_HASH160) receiverHtlcKey,
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_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
successLeafScript, err := builder.Script()
if err != nil { if err != nil {
return txscript.TapLeaf{}, err return txscript.TapLeaf{}, err
} }
@@ -987,101 +922,44 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey,
receiverHtlcKey, revocationKey *btcec.PublicKey, receiverHtlcKey, revocationKey *btcec.PublicKey,
paymentHash []byte, confirmedSpend bool) ([]byte, error) { paymentHash []byte, confirmedSpend bool) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( scriptTemplate := `
AcceptedHtlcScriptSizeConfirmed, OP_DUP OP_HASH160 {{ hex .RevKeyHash }} OP_EQUAL
)) OP_IF
OP_CHECKSIG
// The opening operations are used to determine if this is the sender OP_ELSE
// of the HTLC attempting to sweep all the funds due to a contract {{ hex .SenderKey }}
// breach. In this case, they'll place the revocation key at the top of OP_SWAP OP_SIZE 32 OP_EQUAL
// the stack. OP_IF
builder.AddOp(txscript.OP_DUP) OP_HASH160 {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY
builder.AddOp(txscript.OP_HASH160) OP_2 OP_SWAP {{ hex .ReceiverKey }} OP_2 OP_CHECKMULTISIG
builder.AddData(btcutil.Hash160(revocationKey.SerializeCompressed())) OP_ELSE
builder.AddOp(txscript.OP_EQUAL) OP_DROP {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP
OP_CHECKSIG
// If the hash matches, then this is the revocation clause. The output OP_ENDIF
// can be spent if the check sig operation passes. `
builder.AddOp(txscript.OP_IF)
builder.AddOp(txscript.OP_CHECKSIG)
// Otherwise, this may either be the receiver of the HTLC starting the
// claiming process via the second level HTLC success transaction and
// the pre-image, or the sender of the HTLC sweeping the output after
// it has timed out.
builder.AddOp(txscript.OP_ELSE)
// We'll do a bit of set up by pushing the sender's key on the top of
// the stack. This will be needed later if we decide that this is the
// receiver transitioning the output to the claim state using their
// second-level HTLC success transaction.
builder.AddData(senderHtlcKey.SerializeCompressed())
// Atm, the top item of the stack is the sender's key so we use a swap
// to expose what is either the payment pre-image or something else.
builder.AddOp(txscript.OP_SWAP)
// With the top item swapped, check if it's 32 bytes. If so, then this
// *may* be the payment pre-image.
builder.AddOp(txscript.OP_SIZE)
builder.AddInt64(32)
builder.AddOp(txscript.OP_EQUAL)
// If the item on the top of the stack is 32-bytes, then it is the
// proper size, so this indicates that the receiver of the HTLC is
// attempting to claim the output on-chain by transitioning the state
// of the HTLC to delay+claim.
builder.AddOp(txscript.OP_IF)
// Next we'll hash the item on the top of the stack, if it matches the
// payment pre-image, then we'll continue. Otherwise, we'll end the
// script here as this is the invalid payment pre-image.
builder.AddOp(txscript.OP_HASH160)
builder.AddData(Ripemd160H(paymentHash))
builder.AddOp(txscript.OP_EQUALVERIFY)
// If the payment hash matches, then we'll also need to satisfy the
// multi-sig covenant by providing both signatures of the sender and
// receiver. If the convenient is met, then we'll allow the spending of
// this output, but only by the HTLC success transaction.
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_SWAP)
builder.AddData(receiverHtlcKey.SerializeCompressed())
builder.AddOp(txscript.OP_2)
builder.AddOp(txscript.OP_CHECKMULTISIG)
// Otherwise, this might be the sender of the HTLC attempting to sweep
// it on-chain after the timeout.
builder.AddOp(txscript.OP_ELSE)
// We'll drop the extra item (which is the output from evaluating the
// OP_EQUAL) above from the stack.
builder.AddOp(txscript.OP_DROP)
// With that item dropped off, we can now enforce the absolute
// lock-time required to timeout the HTLC. If the time has passed, then
// we'll proceed with a checksig to ensure that this is actually the
// sender of he original HTLC.
builder.AddInt64(int64(cltvExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddOp(txscript.OP_CHECKSIG)
// Close out the inner if statement.
builder.AddOp(txscript.OP_ENDIF)
// Add 1 block CSV delay for non-revocation clauses if confirmation is // Add 1 block CSV delay for non-revocation clauses if confirmation is
// required. // required.
if confirmedSpend { if confirmedSpend {
builder.AddOp(txscript.OP_1) scriptTemplate += ` OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP`
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
} }
// Close out the outer if statement. // Close out the outer if statement.
builder.AddOp(txscript.OP_ENDIF) scriptTemplate += ` OP_ENDIF`
return builder.Script() // Use the ScriptTemplate function with the properly formatted template
return txscript.ScriptTemplate(
scriptTemplate,
txscript.WithScriptTemplateParams(TemplateParams{
"RevKeyHash": btcutil.Hash160(
revocationKey.SerializeCompressed(),
),
"SenderKey": senderHtlcKey.SerializeCompressed(), //nolint:ll
"ReceiverKey": receiverHtlcKey.SerializeCompressed(), //nolint:ll
"PaymentHashRipemd": Ripemd160H(paymentHash),
"CltvExpiry": int64(cltvExpiry),
}),
)
} }
// ReceiverHtlcSpendRedeem constructs a valid witness allowing the receiver of // ReceiverHtlcSpendRedeem constructs a valid witness allowing the receiver of
@@ -1230,23 +1108,19 @@ func ReceiverHtlcSpendTimeout(signer Signer, signDesc *SignDescriptor,
func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey, func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey,
cltvExpiry uint32) (txscript.TapLeaf, error) { cltvExpiry uint32) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder() // The first part of the script will verify a signature from the sender
// authorizing the spend (the timeout). The second portion will ensure
// The first part of the script will verify a signature from the // that the CLTV expiry on the spending transaction is correct.
// sender authorizing the spend (the timeout). timeoutLeafScript, err := txscript.ScriptTemplate(
builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) `
builder.AddOp(txscript.OP_CHECKSIG) {{ hex .SenderKey }} OP_CHECKSIG
builder.AddOp(txscript.OP_1) OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) {{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP`,
builder.AddOp(txscript.OP_DROP) txscript.WithScriptTemplateParams(TemplateParams{
"SenderKey": schnorr.SerializePubKey(senderHtlcKey),
// The second portion will ensure that the CLTV expiry on the spending "CltvExpiry": int64(cltvExpiry),
// transaction is correct. }),
builder.AddInt64(int64(cltvExpiry)) )
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
timeoutLeafScript, err := builder.Script()
if err != nil { if err != nil {
return txscript.TapLeaf{}, err return txscript.TapLeaf{}, err
} }
@@ -1266,27 +1140,24 @@ func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
senderHtlcKey *btcec.PublicKey, senderHtlcKey *btcec.PublicKey,
paymentHash []byte) (txscript.TapLeaf, error) { paymentHash []byte) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder() // Check that the pre-image is 32 bytes as required. We also check that
// the specified pre-image matches what we hard code into the script.
// Check that the pre-image is 32 bytes as required. // Finally, verify the "2-of-2" multi-sig that requires both parties to
builder.AddOp(txscript.OP_SIZE) // sign off.
builder.AddInt64(32) successLeafScript, err := txscript.ScriptTemplate(
builder.AddOp(txscript.OP_EQUALVERIFY) `
OP_SIZE 32 OP_EQUALVERIFY OP_HASH160
// Check that the specified pre-image matches what we hard code into {{ hex .PaymentHashRipemd }} OP_EQUALVERIFY
// the script. {{ hex .ReceiverKey }} OP_CHECKSIGVERIFY
builder.AddOp(txscript.OP_HASH160) {{ hex .SenderKey }} OP_CHECKSIG`,
builder.AddData(Ripemd160H(paymentHash)) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_EQUALVERIFY) "PaymentHashRipemd": Ripemd160H(paymentHash),
"ReceiverKey": schnorr.SerializePubKey(
// Verify the "2-of-2" multi-sig that requires both parties to sign receiverHtlcKey,
// off. ),
builder.AddData(schnorr.SerializePubKey(receiverHtlcKey)) "SenderKey": schnorr.SerializePubKey(senderHtlcKey),
builder.AddOp(txscript.OP_CHECKSIGVERIFY) }),
builder.AddData(schnorr.SerializePubKey(senderHtlcKey)) )
builder.AddOp(txscript.OP_CHECKSIG)
successLeafScript, err := builder.Script()
if err != nil { if err != nil {
return txscript.TapLeaf{}, err return txscript.TapLeaf{}, err
} }
@@ -1547,43 +1418,34 @@ func ReceiverHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor,
func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay uint32) ([]byte, error) { csvDelay uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
ToLocalScriptSize,
))
// If this is the revocation clause for this script is to be executed, // If this is the revocation clause for this script is to be executed,
// the spender will push a 1, forcing us to hit the true clause of this // the spender will push a 1, forcing us to hit the true clause of this
// if statement. // if statement.
builder.AddOp(txscript.OP_IF)
// If this is the revocation case, then we'll push the revocation // If this is the revocation case, then we'll push the revocation
// public key on the stack. // public key on the stack.
builder.AddData(revocationKey.SerializeCompressed())
// Otherwise, this is either the sender or receiver of the HTLC // Otherwise, this is either the sender or receiver of the HTLC
// attempting to claim the HTLC output. // attempting to claim the HTLC output.
builder.AddOp(txscript.OP_ELSE)
// In order to give the other party time to execute the revocation // In order to give the other party time to execute the revocation
// clause above, we require a relative timeout to pass before the // clause above, we require a relative timeout to pass before the
// output can be spent. // output can be spent.
builder.AddInt64(int64(csvDelay))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
// If the relative timelock passes, then we'll add the delay key to the // If the relative timelock passes, then we'll add the delay key to the
// stack to ensure that we properly authenticate the spending party. // stack to ensure that we properly authenticate the spending party.
builder.AddData(delayKey.SerializeCompressed())
// Close out the if statement.
builder.AddOp(txscript.OP_ENDIF)
// In either case, we'll ensure that only either the party possessing // In either case, we'll ensure that only either the party possessing
// the revocation private key, or the delay private key is able to // the revocation private key, or the delay private key is able to
// spend this output. // spend this output.
builder.AddOp(txscript.OP_CHECKSIG) return txscript.ScriptTemplate(
`OP_IF
return builder.Script() {{ hex .RevokeKey }}
OP_ELSE
{{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP
{{ hex .DelayKey }}
OP_ENDIF OP_CHECKSIG`,
txscript.WithScriptTemplateParams(TemplateParams{
"RevokeKey": revocationKey.SerializeCompressed(),
"CsvDelay": int64(csvDelay),
"DelayKey": delayKey.SerializeCompressed(),
}),
)
} }
// TODO(roasbeef): move all taproot stuff to new file? // TODO(roasbeef): move all taproot stuff to new file?
@@ -1598,20 +1460,19 @@ func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey, func TaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey,
csvDelay uint32) (txscript.TapLeaf, error) { csvDelay uint32) (txscript.TapLeaf, error) {
builder := txscript.NewScriptBuilder()
// Ensure the proper party can sign for this output. // Ensure the proper party can sign for this output.
builder.AddData(schnorr.SerializePubKey(delayKey))
builder.AddOp(txscript.OP_CHECKSIG)
// Assuming the above passes, then we'll now ensure that the CSV delay // Assuming the above passes, then we'll now ensure that the CSV delay
// has been upheld, dropping the int we pushed on. If the sig above is // has been upheld, dropping the int we pushed on. If the sig above is
// valid, then a 1 will be left on the stack. // valid, then a 1 will be left on the stack.
builder.AddInt64(int64(csvDelay)) secondLevelLeafScript, err := txscript.ScriptTemplate(
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) `
builder.AddOp(txscript.OP_DROP) {{ hex .DelayKey }} OP_CHECKSIG
{{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP`,
secondLevelLeafScript, err := builder.Script() txscript.WithScriptTemplateParams(TemplateParams{
"DelayKey": schnorr.SerializePubKey(delayKey),
"CsvDelay": int64(csvDelay),
}),
)
if err != nil { if err != nil {
return txscript.TapLeaf{}, err return txscript.TapLeaf{}, err
} }
@@ -1880,50 +1741,25 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor,
func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey, func LeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay, cltvExpiry uint32) ([]byte, error) { csvDelay, cltvExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( // Build a script template with conditional paths for revocation and normal spending
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, // If this is the revocation clause, the spender will push a 1, forcing the first path
)) // Otherwise, this is either the sender or receiver of the HTLC attempting to claim
return txscript.ScriptTemplate(
// If this is the revocation clause for this script is to be executed, `
// the spender will push a 1, forcing us to hit the true clause of this OP_IF
// if statement. {{ hex .RevokeKey }}
builder.AddOp(txscript.OP_IF) OP_ELSE
{{ .CltvExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP
// If this this is the revocation case, then we'll push the revocation {{ .CsvDelay }} OP_CHECKSEQUENCEVERIFY OP_DROP
// public key on the stack. {{ hex .DelayKey }}
builder.AddData(revocationKey.SerializeCompressed()) OP_ENDIF OP_CHECKSIG`,
txscript.WithScriptTemplateParams(TemplateParams{
// Otherwise, this is either the sender or receiver of the HTLC "RevokeKey": revocationKey.SerializeCompressed(),
// attempting to claim the HTLC output. "CltvExpiry": int64(cltvExpiry),
builder.AddOp(txscript.OP_ELSE) "CsvDelay": int64(csvDelay),
"DelayKey": delayKey.SerializeCompressed(),
// The channel initiator always has the additional channel lease }),
// expiration constraint for outputs that pay to them which must be )
// satisfied.
builder.AddInt64(int64(cltvExpiry))
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
builder.AddOp(txscript.OP_DROP)
// In order to give the other party time to execute the revocation
// clause above, we require a relative timeout to pass before the
// output can be spent.
builder.AddInt64(int64(csvDelay))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
// If the relative timelock passes, then we'll add the delay key to the
// stack to ensure that we properly authenticate the spending party.
builder.AddData(delayKey.SerializeCompressed())
// Close out the if statement.
builder.AddOp(txscript.OP_ENDIF)
// In either case, we'll ensure that only either the party possessing
// the revocation private key, or the delay private key is able to
// spend this output.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
} }
// HtlcSpendSuccess spends a second-level HTLC output. This function is to be // HtlcSpendSuccess spends a second-level HTLC output. This function is to be
@@ -2065,32 +1901,21 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey)
// have divulged the revocation hash, allowing them to homomorphically // have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public // derive the proper private key which corresponds to the revoke public
// key. // key.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( return txscript.ScriptTemplate(
ToLocalScriptSize, `
)) OP_IF
{{ hex .RevokeKey }}
builder.AddOp(txscript.OP_IF) OP_ELSE
{{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP
// If a valid signature using the revocation key is presented, then {{ hex .SelfKey }}
// allow an immediate spend provided the proper signature. OP_ENDIF
builder.AddData(revokeKey.SerializeCompressed()) OP_CHECKSIG`,
txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_ELSE) "RevokeKey": revokeKey.SerializeCompressed(),
"CsvTimeout": int64(csvTimeout),
// Otherwise, we can re-claim our funds after a CSV delay of "SelfKey": selfKey.SerializeCompressed(),
// 'csvTimeout' timeout blocks, and a valid signature. }),
builder.AddInt64(int64(csvTimeout)) )
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
// Finally, we'll validate the signature against the public key that's
// left on the top of the stack.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
} }
// CommitScriptTree holds the taproot output key (in this case the revocation // CommitScriptTree holds the taproot output key (in this case the revocation
@@ -2230,14 +2055,15 @@ func NewLocalCommitScriptTree(csvTimeout uint32, selfKey,
func TaprootLocalCommitDelayScript(csvTimeout uint32, func TaprootLocalCommitDelayScript(csvTimeout uint32,
selfKey *btcec.PublicKey) ([]byte, error) { selfKey *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder() return txscript.ScriptTemplate(
builder.AddData(schnorr.SerializePubKey(selfKey)) `
builder.AddOp(txscript.OP_CHECKSIG) {{ hex .SelfKey }} OP_CHECKSIG
builder.AddInt64(int64(csvTimeout)) {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP`,
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_DROP) "SelfKey": schnorr.SerializePubKey(selfKey),
"CsvTimeout": int64(csvTimeout),
return builder.Script() }),
)
} }
// TaprootLocalCommitRevokeScript builds the tap leaf with the revocation path // TaprootLocalCommitRevokeScript builds the tap leaf with the revocation path
@@ -2245,13 +2071,13 @@ func TaprootLocalCommitDelayScript(csvTimeout uint32,
func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) ( func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) (
[]byte, error) { []byte, error) {
builder := txscript.NewScriptBuilder() return txscript.ScriptTemplate(
builder.AddData(schnorr.SerializePubKey(selfKey)) `{{ hex .SelfKey }} OP_DROP {{ hex .RevokeKey }} OP_CHECKSIG`,
builder.AddOp(txscript.OP_DROP) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddData(schnorr.SerializePubKey(revokeKey)) "SelfKey": schnorr.SerializePubKey(selfKey),
builder.AddOp(txscript.OP_CHECKSIG) "RevokeKey": schnorr.SerializePubKey(revokeKey),
}),
return builder.Script() )
} }
// TaprootCommitScriptToSelf creates the taproot witness program that commits // TaprootCommitScriptToSelf creates the taproot witness program that commits
@@ -2430,38 +2256,23 @@ func LeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey,
// have divulged the revocation hash, allowing them to homomorphically // have divulged the revocation hash, allowing them to homomorphically
// derive the proper private key which corresponds to the revoke public // derive the proper private key which corresponds to the revoke public
// key. // key.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( return txscript.ScriptTemplate(
ToLocalScriptSize + LeaseWitnessScriptSizeOverhead, `
)) OP_IF
{{ hex .RevokeKey }}
builder.AddOp(txscript.OP_IF) OP_ELSE
{{ .LeaseExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP
// If a valid signature using the revocation key is presented, then {{ .CsvTimeout }} OP_CHECKSEQUENCEVERIFY OP_DROP
// allow an immediate spend provided the proper signature. {{ hex .SelfKey }}
builder.AddData(revokeKey.SerializeCompressed()) OP_ENDIF
OP_CHECKSIG`,
builder.AddOp(txscript.OP_ELSE) txscript.WithScriptTemplateParams(TemplateParams{
"RevokeKey": revokeKey.SerializeCompressed(),
// Otherwise, we can re-claim our funds after once the CLTV lease "LeaseExpiry": int64(leaseExpiry),
// maturity has been met, along with the CSV delay of 'csvTimeout' "CsvTimeout": int64(csvTimeout),
// timeout blocks, and a valid signature. "SelfKey": selfKey.SerializeCompressed(),
builder.AddInt64(int64(leaseExpiry)) }),
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) )
builder.AddOp(txscript.OP_DROP)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)
builder.AddData(selfKey.SerializeCompressed())
builder.AddOp(txscript.OP_ENDIF)
// Finally, we'll validate the signature against the public key that's
// left on the top of the stack.
builder.AddOp(txscript.OP_CHECKSIG)
return builder.Script()
} }
// CommitSpendTimeout constructs a valid witness allowing the owner of a // CommitSpendTimeout constructs a valid witness allowing the owner of a
@@ -2577,13 +2388,12 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
// p2wkh output spendable immediately, requiring no contestation period. // p2wkh output spendable immediately, requiring no contestation period.
func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) { func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and is spendable immediately. // This script goes to the "other" party, and is spendable immediately.
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( return txscript.ScriptTemplate(
P2WPKHSize, `OP_0 {{ hex .PKHash }}`,
)) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_0) "PKHash": btcutil.Hash160(key.SerializeCompressed()),
builder.AddData(btcutil.Hash160(key.SerializeCompressed())) }),
)
return builder.Script()
} }
// CommitScriptToRemoteConfirmed constructs the script for the output on the // CommitScriptToRemoteConfirmed constructs the script for the output on the
@@ -2599,19 +2409,15 @@ func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// <key> OP_CHECKSIGVERIFY // <key> OP_CHECKSIGVERIFY
// 1 OP_CHECKSEQUENCEVERIFY // 1 OP_CHECKSEQUENCEVERIFY
func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( // Only the given key can spend the output after one confirmation.
ToRemoteConfirmedScriptSize, return txscript.ScriptTemplate(
)) `
{{ hex .Key }} OP_CHECKSIGVERIFY
// Only the given key can spend the output. OP_1 OP_CHECKSEQUENCEVERIFY`,
builder.AddData(key.SerializeCompressed()) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_CHECKSIGVERIFY) "Key": key.SerializeCompressed(),
}),
// Check that the it has one confirmation. )
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
} }
// NewRemoteCommitScriptTree constructs a new script tree for the remote party // NewRemoteCommitScriptTree constructs a new script tree for the remote party
@@ -2621,14 +2427,14 @@ func NewRemoteCommitScriptTree(remoteKey *btcec.PublicKey,
// First, construct the remote party's tapscript they'll use to sweep // First, construct the remote party's tapscript they'll use to sweep
// their outputs. // their outputs.
builder := txscript.NewScriptBuilder() remoteScript, err := txscript.ScriptTemplate(
builder.AddData(schnorr.SerializePubKey(remoteKey)) `
builder.AddOp(txscript.OP_CHECKSIG) {{ hex .RemoteKey }} OP_CHECKSIG
builder.AddOp(txscript.OP_1) OP_1 OP_CHECKSEQUENCEVERIFY OP_DROP`,
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddOp(txscript.OP_DROP) "RemoteKey": schnorr.SerializePubKey(remoteKey),
}),
remoteScript, err := builder.Script() )
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -2743,24 +2549,18 @@ func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor,
func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey, func LeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey,
leaseExpiry uint32) ([]byte, error) { leaseExpiry uint32) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45)) // This script adds lease expiration constraint in addition to the
// standard remote confirmed script requirements.
// Only the given key can spend the output. return txscript.ScriptTemplate(
builder.AddData(key.SerializeCompressed()) `
builder.AddOp(txscript.OP_CHECKSIGVERIFY) {{ hex .Key }} OP_CHECKSIGVERIFY
{{ .LeaseExpiry }} OP_CHECKLOCKTIMEVERIFY OP_DROP
// The channel initiator always has the additional channel lease OP_1 OP_CHECKSEQUENCEVERIFY`,
// expiration constraint for outputs that pay to them which must be txscript.WithScriptTemplateParams(TemplateParams{
// satisfied. "Key": key.SerializeCompressed(),
builder.AddInt64(int64(leaseExpiry)) "LeaseExpiry": int64(leaseExpiry),
builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) }),
builder.AddOp(txscript.OP_DROP) )
// Check that it has one confirmation.
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
} }
// CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to // CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to
@@ -2805,24 +2605,19 @@ func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor,
// OP_16 OP_CSV // OP_16 OP_CSV
// OP_ENDIF // OP_ENDIF
func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize( // Build the anchor script with two possible spending paths:
AnchorScriptSize, // 1. Spend immediately with key (the normal path)
)) // 2. Spend after 16 confirmations by anyone (the alternative path)
return txscript.ScriptTemplate(
// Spend immediately with key. `
builder.AddData(key.SerializeCompressed()) {{ hex .Key }} OP_CHECKSIG OP_IFDUP
builder.AddOp(txscript.OP_CHECKSIG) OP_NOTIF
OP_16 OP_CHECKSEQUENCEVERIFY
// Duplicate the value if true, since it will be consumed by the NOTIF. OP_ENDIF`,
builder.AddOp(txscript.OP_IFDUP) txscript.WithScriptTemplateParams(TemplateParams{
"Key": key.SerializeCompressed(),
// Otherwise spendable by anyone after 16 confirmations. }),
builder.AddOp(txscript.OP_NOTIF) )
builder.AddOp(txscript.OP_16)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
} }
// AnchorScriptTree holds all the contents needed to sweep a taproot anchor // AnchorScriptTree holds all the contents needed to sweep a taproot anchor
@@ -2841,11 +2636,9 @@ func NewAnchorScriptTree(
// The main script used is just a OP_16 CSV (anyone can sweep after 16 // The main script used is just a OP_16 CSV (anyone can sweep after 16
// blocks). // blocks).
builder := txscript.NewScriptBuilder() anchorScript, err := txscript.ScriptTemplate(
builder.AddOp(txscript.OP_16) `OP_16 OP_CHECKSEQUENCEVERIFY`,
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) )
anchorScript, err := builder.Script()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -169,10 +169,10 @@ func TapscriptFullKeyOnly(taprootKey *btcec.PublicKey) *waddrmgr.Tapscript {
// witness program. The passed public key will be serialized as an x-only key // witness program. The passed public key will be serialized as an x-only key
// to create the witness program. // to create the witness program.
func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) { func PayToTaprootScript(taprootKey *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder() return txscript.ScriptTemplate(
`OP_1 {{ hex .TaprootKey }}`,
builder.AddOp(txscript.OP_1) txscript.WithScriptTemplateParams(TemplateParams{
builder.AddData(schnorr.SerializePubKey(taprootKey)) "TaprootKey": schnorr.SerializePubKey(taprootKey),
}),
return builder.Script() )
} }