input: add template vs builder byte-for-byte equivalence tests
What changed, and why it matters
This commit only adds new test code. It does not change any production code. The tests compare newly added 'ScriptTemplate' functions against older 'ScriptBuilder' versions to make sure they produce identical Bitcoin scripts byte-for-byte. It is a regression-test addition, not a security fix or vulnerability.
No security action required. Treat as normal test-only commit during review/CI.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces two new test files (input/script_utils_legacy_test.go and input/script_utils_template_equiv_test.go) and extends input/taproot_test.go. The legacy file copies old ScriptBuilder implementations as private test helpers. The equivalence test file runs 22 table-driven comparisons asserting that current ScriptTemplate-based functions produce the same bytes as the legacy builders for segwit v0 and taproot scripts. taproot_test.go is updated to also exercise a new WithProdScripts() option in existing spend tests. No production logic is modified.
Changed components
input/script_utils_legacy_test.goinput/script_utils_template_equiv_test.goinput/taproot_test.goInspect captured patch +1372 / −78
diff --git a/input/script_utils_legacy_test.go b/input/script_utils_legacy_test.go
new file mode 100644
index 0000000..b3b4f88
--- /dev/null
+++ b/input/script_utils_legacy_test.go
@@ -0,0 +1,738 @@
+package input
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "fmt"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/btcec/v2/schnorr"
+ "github.com/btcsuite/btcd/btcutil"
+ "github.com/btcsuite/btcd/txscript"
+)
+
+// legacyWitnessScriptHash generates a pay-to-witness-script-hash public key
+// script paying to a version 0 witness program paying to the passed redeem
+// script.
+func legacyWitnessScriptHash(witnessScript []byte) ([]byte, error) {
+ bldr := txscript.NewScriptBuilder(
+ txscript.WithScriptAllocSize(P2WSHSize),
+ )
+
+ bldr.AddOp(txscript.OP_0)
+ scriptHash := sha256.Sum256(witnessScript)
+ bldr.AddData(scriptHash[:])
+
+ return bldr.Script()
+}
+
+// legacyWitnessPubKeyHash generates a pay-to-witness-pubkey-hash public key
+// script paying to a version 0 witness program containing the passed
+// serialized public key.
+func legacyWitnessPubKeyHash(pubkey []byte) ([]byte, error) {
+ bldr := txscript.NewScriptBuilder(
+ txscript.WithScriptAllocSize(P2WPKHSize),
+ )
+
+ bldr.AddOp(txscript.OP_0)
+ pkhash := btcutil.Hash160(pubkey)
+ bldr.AddData(pkhash)
+
+ return bldr.Script()
+}
+
+// legacyGenerateP2SH generates a pay-to-script-hash public key script paying
+// to the passed redeem script.
+func legacyGenerateP2SH(script []byte) ([]byte, error) {
+ bldr := txscript.NewScriptBuilder(
+ txscript.WithScriptAllocSize(NestedP2WPKHSize),
+ )
+
+ bldr.AddOp(txscript.OP_HASH160)
+ scripthash := btcutil.Hash160(script)
+ bldr.AddData(scripthash)
+ bldr.AddOp(txscript.OP_EQUAL)
+
+ return bldr.Script()
+}
+
+// legacyGenerateP2PKH generates a pay-to-public-key-hash public key script
+// paying to the passed serialized public key.
+func legacyGenerateP2PKH(pubkey []byte) ([]byte, error) {
+ bldr := txscript.NewScriptBuilder(
+ txscript.WithScriptAllocSize(P2PKHSize),
+ )
+
+ 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()
+}
+
+// legacyGenMultiSigScript generates the non-p2sh'd multisig script for 2 of 2
+// pubkeys.
+func legacyGenMultiSigScript(aPub, bPub []byte) ([]byte, error) {
+ if len(aPub) != 33 || len(bPub) != 33 {
+ return nil, fmt.Errorf("pubkey size error: compressed " +
+ "pubkeys only")
+ }
+
+ // Swap to sort pubkeys if needed. Keys are sorted in lexicographical
+ // order. The signatures within the scriptSig must also adhere to the
+ // order, ensuring that the signatures for each public key appears in
+ // the proper order on the stack.
+ if bytes.Compare(aPub, bPub) == 1 {
+ aPub, bPub = bPub, aPub
+ }
+
+ bldr := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ MultiSigSize,
+ ))
+ bldr.AddOp(txscript.OP_2)
+ bldr.AddData(aPub) // Add both pubkeys (sorted).
+ bldr.AddData(bPub)
+ bldr.AddOp(txscript.OP_2)
+ bldr.AddOp(txscript.OP_CHECKMULTISIG)
+
+ return bldr.Script()
+}
+
+// legacySenderHTLCScript constructs the public key script for an outgoing HTLC
+// output payment for the sender's version of the commitment transaction.
+func legacySenderHTLCScript(senderHtlcKey, receiverHtlcKey,
+ revocationKey *btcec.PublicKey, paymentHash []byte,
+ confirmedSpend bool) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ OfferedHtlcScriptSizeConfirmed,
+ ))
+
+ // The opening operations are used to determine if this is the receiver
+ // 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 {
+ builder.AddOp(txscript.OP_1)
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+ }
+
+ // Close out the OP_IF statement at the top of the script.
+ builder.AddOp(txscript.OP_ENDIF)
+
+ return builder.Script()
+}
+
+// legacyReceiverHTLCScript constructs the public key script for an incoming
+// HTLC output payment for the receiver's version of the commitment
+// transaction.
+func legacyReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey,
+ receiverHtlcKey, revocationKey *btcec.PublicKey,
+ paymentHash []byte, confirmedSpend bool) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ AcceptedHtlcScriptSizeConfirmed,
+ ))
+
+ // The opening operations are used to determine if this is the sender
+ // 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 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
+ // required.
+ if confirmedSpend {
+ builder.AddOp(txscript.OP_1)
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+ }
+
+ // Close out the outer if statement.
+ builder.AddOp(txscript.OP_ENDIF)
+
+ return builder.Script()
+}
+
+// legacySecondLevelHtlcScript is the uniform script that's used as the output
+// for the second-level HTLC transactions.
+func legacySecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
+ csvDelay uint32) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ ToLocalScriptSize,
+ ))
+
+ // 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
+ // if statement.
+ builder.AddOp(txscript.OP_IF)
+
+ // If this is the revocation case, then we'll push the revocation
+ // public key on the stack.
+ builder.AddData(revocationKey.SerializeCompressed())
+
+ // Otherwise, this is either the sender or receiver of the HTLC
+ // attempting to claim the HTLC output.
+ builder.AddOp(txscript.OP_ELSE)
+
+ // 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()
+}
+
+// legacyCommitScriptToSelf constructs the public key script for the output on
+// the commitment transaction paying to the "owner" of said commitment
+// transaction.
+func legacyCommitScriptToSelf(csvTimeout uint32, selfKey,
+ revokeKey *btcec.PublicKey) ([]byte, error) {
+ // This script is spendable under two conditions: either the
+ // 'csvTimeout' has passed and we can redeem our funds, or they can
+ // produce a valid signature with the revocation public key. The
+ // revocation public key will *only* be known to the other party if we
+ // have divulged the revocation hash, allowing them to homomorphically
+ // derive the proper private key which corresponds to the revoke public
+ // key.
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ ToLocalScriptSize,
+ ))
+
+ builder.AddOp(txscript.OP_IF)
+
+ // If a valid signature using the revocation key is presented, then
+ // allow an immediate spend provided the proper signature.
+ builder.AddData(revokeKey.SerializeCompressed())
+
+ builder.AddOp(txscript.OP_ELSE)
+
+ // Otherwise, we can re-claim our funds after a CSV delay of
+ // '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()
+}
+
+// legacyLeaseCommitScriptToSelf constructs the public key script for the
+// output on the commitment transaction paying to the "owner" of said
+// commitment transaction, with an additional lease expiry constraint.
+func legacyLeaseCommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey,
+ csvTimeout, leaseExpiry uint32) ([]byte, error) {
+
+ // This script is spendable under two conditions: either the
+ // 'csvTimeout' has passed and we can redeem our funds, or they can
+ // produce a valid signature with the revocation public key. The
+ // revocation public key will *only* be known to the other party if we
+ // have divulged the revocation hash, allowing them to homomorphically
+ // derive the proper private key which corresponds to the revoke public
+ // key.
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
+ ))
+
+ builder.AddOp(txscript.OP_IF)
+
+ // If a valid signature using the revocation key is presented, then
+ // allow an immediate spend provided the proper signature.
+ builder.AddData(revokeKey.SerializeCompressed())
+
+ builder.AddOp(txscript.OP_ELSE)
+
+ // Otherwise, we can re-claim our funds after once the CLTV lease
+ // maturity has been met, along with the CSV delay of 'csvTimeout'
+ // timeout blocks, and a valid signature.
+ 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()
+}
+
+// legacyCommitScriptUnencumbered constructs the public key script on the
+// commitment transaction paying to the "other" party. The constructed output
+// is a normal p2wkh output spendable immediately, requiring no contestation
+// period.
+func legacyCommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
+ // This script goes to the "other" party, and is spendable immediately.
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ P2WPKHSize,
+ ))
+ builder.AddOp(txscript.OP_0)
+ builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
+
+ return builder.Script()
+}
+
+// legacyCommitScriptToRemoteConfirmed constructs the script for the output on
+// the commitment transaction paying to the remote party of said commitment
+// transaction. The money can only be spend after one confirmation.
+func legacyCommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ ToRemoteConfirmedScriptSize,
+ ))
+
+ // Only the given key can spend the output.
+ builder.AddData(key.SerializeCompressed())
+ builder.AddOp(txscript.OP_CHECKSIGVERIFY)
+
+ // Check that the it has one confirmation.
+ builder.AddOp(txscript.OP_1)
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+
+ return builder.Script()
+}
+
+// legacyLeaseCommitScriptToRemoteConfirmed constructs the script for the
+// output on the commitment transaction paying to the remote party of said
+// commitment transaction, with an additional lease expiry constraint.
+func legacyLeaseCommitScriptToRemoteConfirmed(key *btcec.PublicKey,
+ leaseExpiry uint32) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(45))
+
+ // Only the given key can spend the output.
+ builder.AddData(key.SerializeCompressed())
+ builder.AddOp(txscript.OP_CHECKSIGVERIFY)
+
+ // The channel initiator always has the additional channel lease
+ // expiration constraint for outputs that pay to them which must be
+ // satisfied.
+ builder.AddInt64(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()
+}
+
+// legacyCommitScriptAnchor constructs the script for the anchor output
+// spendable by the given key immediately, or by anyone after 16 confirmations.
+func legacyCommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ AnchorScriptSize,
+ ))
+
+ // Spend immediately with key.
+ builder.AddData(key.SerializeCompressed())
+ builder.AddOp(txscript.OP_CHECKSIG)
+
+ // Duplicate the value if true, since it will be consumed by the NOTIF.
+ builder.AddOp(txscript.OP_IFDUP)
+
+ // 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()
+}
+
+// legacyLeaseSecondLevelHtlcScript is the uniform script that's used as the
+// output for the second-level HTLC transactions with a lease expiry
+// constraint.
+func legacyLeaseSecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
+ csvDelay, cltvExpiry uint32) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder(txscript.WithScriptAllocSize(
+ ToLocalScriptSize + LeaseWitnessScriptSizeOverhead,
+ ))
+
+ // 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
+ // if statement.
+ builder.AddOp(txscript.OP_IF)
+
+ // If this is the revocation case, then we'll push the revocation
+ // public key on the stack.
+ builder.AddData(revocationKey.SerializeCompressed())
+
+ // Otherwise, this is either the sender or receiver of the HTLC
+ // attempting to claim the HTLC output.
+ builder.AddOp(txscript.OP_ELSE)
+
+ // 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()
+}
+
+// legacySenderHTLCTapLeafTimeout returns the full tapscript leaf for the
+// timeout path of the sender HTLC.
+func legacySenderHTLCTapLeafTimeout(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
+}
+
+// legacySenderHTLCTapLeafSuccess returns the full tapscript leaf for the
+// success path of the sender HTLC.
+func legacySenderHTLCTapLeafSuccess(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-image 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_1)
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+
+ successLeafScript, err := builder.Script()
+ if err != nil {
+ return txscript.TapLeaf{}, err
+ }
+
+ return txscript.NewBaseTapLeaf(successLeafScript), nil
+}
+
+// legacyReceiverHtlcTapLeafTimeout returns the full tapscript leaf for the
+// timeout path of the receiver HTLC.
+func legacyReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey,
+ 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).
+ builder.AddData(schnorr.SerializePubKey(senderHtlcKey))
+ builder.AddOp(txscript.OP_CHECKSIG)
+ builder.AddOp(txscript.OP_1)
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+
+ // The second portion will ensure that the CLTV expiry on the spending
+ // transaction is correct.
+ builder.AddInt64(int64(cltvExpiry))
+ builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+
+ timeoutLeafScript, err := builder.Script()
+ if err != nil {
+ return txscript.TapLeaf{}, err
+ }
+
+ return txscript.NewBaseTapLeaf(timeoutLeafScript), nil
+}
+
+// legacyReceiverHtlcTapLeafSuccess returns the full tapscript leaf for the
+// success path for an HTLC on the receiver's commitment transaction.
+func legacyReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey,
+ senderHtlcKey *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-image matches what we hard code into
+ // the script.
+ builder.AddOp(txscript.OP_HASH160)
+ builder.AddData(Ripemd160H(paymentHash))
+ builder.AddOp(txscript.OP_EQUALVERIFY)
+
+ // Verify the "2-of-2" multi-sig that requires both parties to sign
+ // off.
+ builder.AddData(schnorr.SerializePubKey(receiverHtlcKey))
+ builder.AddOp(txscript.OP_CHECKSIGVERIFY)
+ builder.AddData(schnorr.SerializePubKey(senderHtlcKey))
+ builder.AddOp(txscript.OP_CHECKSIG)
+
+ successLeafScript, err := builder.Script()
+ if err != nil {
+ return txscript.TapLeaf{}, err
+ }
+
+ return txscript.NewBaseTapLeaf(successLeafScript), nil
+}
+
+// legacyTaprootSecondLevelTapLeaf constructs the tap leaf used as the sole
+// script path for a second level HTLC spend.
+func legacyTaprootSecondLevelTapLeaf(delayKey *btcec.PublicKey,
+ csvDelay uint32) (txscript.TapLeaf, error) {
+
+ builder := txscript.NewScriptBuilder()
+
+ // 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
+ // has been upheld, dropping the int we pushed on. If the sig above is
+ // valid, then a 1 will be left on the stack.
+ builder.AddInt64(int64(csvDelay))
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+
+ secondLevelLeafScript, err := builder.Script()
+ if err != nil {
+ return txscript.TapLeaf{}, err
+ }
+
+ return txscript.NewBaseTapLeaf(secondLevelLeafScript), nil
+}
+
+// legacyTaprootLocalCommitDelayScript builds the tap leaf with the CSV delay
+// script for the to-local output.
+func legacyTaprootLocalCommitDelayScript(csvTimeout uint32,
+ selfKey *btcec.PublicKey) ([]byte, error) {
+
+ builder := txscript.NewScriptBuilder()
+ builder.AddData(schnorr.SerializePubKey(selfKey))
+ builder.AddOp(txscript.OP_CHECKSIG)
+ builder.AddInt64(int64(csvTimeout))
+ builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
+ builder.AddOp(txscript.OP_DROP)
+
+ return builder.Script()
+}
+
+// legacyTaprootLocalCommitRevokeScript builds the tap leaf with the revocation
+// path for the to-local output.
+func legacyTaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) (
+ []byte, error) {
+
+ builder := txscript.NewScriptBuilder()
+ builder.AddData(schnorr.SerializePubKey(selfKey))
+ builder.AddOp(txscript.OP_DROP)
+ builder.AddData(schnorr.SerializePubKey(revokeKey))
+ builder.AddOp(txscript.OP_CHECKSIG)
+
+ return builder.Script()
+}
diff --git a/input/script_utils_template_equiv_test.go b/input/script_utils_template_equiv_test.go
new file mode 100644
index 0000000..4d26b5b
--- /dev/null
+++ b/input/script_utils_template_equiv_test.go
@@ -0,0 +1,454 @@
+package input
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "testing"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/stretchr/testify/require"
+)
+
+// testKeyBytes returns deterministic key bytes for testing. The index parameter
+// produces different keys for different roles by deriving private keys from a
+// hash and computing the corresponding public key on secp256k1.
+func testKeyBytes(t *testing.T, index byte) *btcec.PublicKey {
+ t.Helper()
+
+ hash := sha256.Sum256([]byte{index})
+ privKey, _ := btcec.PrivKeyFromBytes(hash[:])
+
+ return privKey.PubKey()
+}
+
+// testPaymentHash returns a deterministic 32-byte payment hash.
+func testPaymentHash() []byte {
+ h := sha256.Sum256([]byte("test-payment-preimage"))
+ return h[:]
+}
+
+// TestTemplateVsBuilderEquivalence verifies that the new ScriptTemplate-based
+// functions produce byte-for-byte identical output to the old ScriptBuilder
+// versions for all script types.
+func TestTemplateVsBuilderEquivalence(t *testing.T) {
+ t.Parallel()
+
+ // Set up test keys for various roles.
+ senderKey := testKeyBytes(t, 1)
+ receiverKey := testKeyBytes(t, 2)
+ revokeKey := testKeyBytes(t, 3)
+ selfKey := testKeyBytes(t, 4)
+ delayKey := testKeyBytes(t, 5)
+ remoteKey := testKeyBytes(t, 6)
+
+ payHash := testPaymentHash()
+
+ const (
+ csvDelay uint32 = 144
+ cltvExpiry uint32 = 800000
+ leaseExpiry uint32 = 900000
+ )
+
+ t.Run("WitnessScriptHash", func(t *testing.T) {
+ t.Parallel()
+ witnessScript := []byte("test-witness-script")
+
+ got, err := WitnessScriptHash(witnessScript)
+ require.NoError(t, err)
+
+ want, err := legacyWitnessScriptHash(witnessScript)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got,
+ "WitnessScriptHash mismatch:\n"+
+ " legacy: %x\n template: %x",
+ want, got,
+ )
+ })
+
+ t.Run("WitnessPubKeyHash", func(t *testing.T) {
+ t.Parallel()
+ pubkey := senderKey.SerializeCompressed()
+
+ got, err := WitnessPubKeyHash(pubkey)
+ require.NoError(t, err)
+
+ want, err := legacyWitnessPubKeyHash(pubkey)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("GenerateP2SH", func(t *testing.T) {
+ t.Parallel()
+ script := []byte("test-redeem-script")
+
+ got, err := GenerateP2SH(script)
+ require.NoError(t, err)
+
+ want, err := legacyGenerateP2SH(script)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("GenerateP2PKH", func(t *testing.T) {
+ t.Parallel()
+ pubkey := senderKey.SerializeCompressed()
+
+ got, err := GenerateP2PKH(pubkey)
+ require.NoError(t, err)
+
+ want, err := legacyGenerateP2PKH(pubkey)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("GenMultiSigScript", func(t *testing.T) {
+ t.Parallel()
+ aPub := senderKey.SerializeCompressed()
+ bPub := receiverKey.SerializeCompressed()
+
+ got, err := GenMultiSigScript(aPub, bPub)
+ require.NoError(t, err)
+
+ want, err := legacyGenMultiSigScript(aPub, bPub)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("SenderHTLCScript/confirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := SenderHTLCScript(
+ senderKey, receiverKey, revokeKey, payHash, true,
+ )
+ require.NoError(t, err)
+
+ want, err := legacySenderHTLCScript(
+ senderKey, receiverKey, revokeKey, payHash, true,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got,
+ "SenderHTLCScript(confirmed) mismatch:\n"+
+ " legacy: %x\n template: %x",
+ want, got,
+ )
+ })
+
+ t.Run("SenderHTLCScript/unconfirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := SenderHTLCScript(
+ senderKey, receiverKey, revokeKey, payHash, false,
+ )
+ require.NoError(t, err)
+
+ want, err := legacySenderHTLCScript(
+ senderKey, receiverKey, revokeKey, payHash, false,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("ReceiverHTLCScript/confirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := ReceiverHTLCScript(
+ cltvExpiry, senderKey, receiverKey, revokeKey,
+ payHash, true,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyReceiverHTLCScript(
+ cltvExpiry, senderKey, receiverKey, revokeKey,
+ payHash, true,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got,
+ "ReceiverHTLCScript(confirmed) mismatch:\n"+
+ " legacy: %x\n template: %x",
+ want, got,
+ )
+ })
+
+ t.Run("ReceiverHTLCScript/unconfirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := ReceiverHTLCScript(
+ cltvExpiry, senderKey, receiverKey, revokeKey,
+ payHash, false,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyReceiverHTLCScript(
+ cltvExpiry, senderKey, receiverKey, revokeKey,
+ payHash, false,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("SecondLevelHtlcScript", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := SecondLevelHtlcScript(
+ revokeKey, delayKey, csvDelay,
+ )
+ require.NoError(t, err)
+
+ want, err := legacySecondLevelHtlcScript(
+ revokeKey, delayKey, csvDelay,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("CommitScriptToSelf", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := CommitScriptToSelf(csvDelay, selfKey, revokeKey)
+ require.NoError(t, err)
+
+ want, err := legacyCommitScriptToSelf(
+ csvDelay, selfKey, revokeKey,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("LeaseCommitScriptToSelf", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := LeaseCommitScriptToSelf(
+ selfKey, revokeKey, csvDelay, leaseExpiry,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyLeaseCommitScriptToSelf(
+ selfKey, revokeKey, csvDelay, leaseExpiry,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("CommitScriptUnencumbered", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := CommitScriptUnencumbered(remoteKey)
+ require.NoError(t, err)
+
+ want, err := legacyCommitScriptUnencumbered(remoteKey)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("CommitScriptToRemoteConfirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := CommitScriptToRemoteConfirmed(remoteKey)
+ require.NoError(t, err)
+
+ want, err := legacyCommitScriptToRemoteConfirmed(remoteKey)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("LeaseCommitScriptToRemoteConfirmed", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := LeaseCommitScriptToRemoteConfirmed(
+ remoteKey, leaseExpiry,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyLeaseCommitScriptToRemoteConfirmed(
+ remoteKey, leaseExpiry,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("CommitScriptAnchor", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := CommitScriptAnchor(senderKey)
+ require.NoError(t, err)
+
+ want, err := legacyCommitScriptAnchor(senderKey)
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ t.Run("LeaseSecondLevelHtlcScript", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := LeaseSecondLevelHtlcScript(
+ revokeKey, delayKey, csvDelay, cltvExpiry,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyLeaseSecondLevelHtlcScript(
+ revokeKey, delayKey, csvDelay, cltvExpiry,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ // Taproot script equivalence tests. These compare the non-prod
+ // (default) variant of the template functions against the old builder
+ // code which also produced the non-prod scripts.
+ t.Run("SenderHTLCTapLeafTimeout", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := SenderHTLCTapLeafTimeout(senderKey, receiverKey)
+ require.NoError(t, err)
+
+ want, err := legacySenderHTLCTapLeafTimeout(
+ senderKey, receiverKey,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want.Script, got.Script)
+ })
+
+ t.Run("SenderHTLCTapLeafSuccess", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := SenderHTLCTapLeafSuccess(receiverKey, payHash)
+ require.NoError(t, err)
+
+ want, err := legacySenderHTLCTapLeafSuccess(
+ receiverKey, payHash,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want.Script, got.Script)
+ })
+
+ t.Run("ReceiverHtlcTapLeafTimeout", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := ReceiverHtlcTapLeafTimeout(
+ senderKey, cltvExpiry,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyReceiverHtlcTapLeafTimeout(
+ senderKey, cltvExpiry,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want.Script, got.Script)
+ })
+
+ t.Run("ReceiverHtlcTapLeafSuccess", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := ReceiverHtlcTapLeafSuccess(
+ receiverKey, senderKey, payHash,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyReceiverHtlcTapLeafSuccess(
+ receiverKey, senderKey, payHash,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want.Script, got.Script)
+ })
+
+ t.Run("TaprootSecondLevelTapLeaf", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := TaprootSecondLevelTapLeaf(delayKey, csvDelay)
+ require.NoError(t, err)
+
+ want, err := legacyTaprootSecondLevelTapLeaf(
+ delayKey, csvDelay,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want.Script, got.Script)
+ })
+
+ t.Run("TaprootLocalCommitDelayScript", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := TaprootLocalCommitDelayScript(
+ csvDelay, selfKey,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyTaprootLocalCommitDelayScript(
+ csvDelay, selfKey,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got,
+ "TaprootLocalCommitDelayScript mismatch:\n"+
+ " legacy: %x\n template: %x",
+ want, got,
+ )
+ })
+
+ t.Run("TaprootLocalCommitRevokeScript", func(t *testing.T) {
+ t.Parallel()
+
+ got, err := TaprootLocalCommitRevokeScript(
+ selfKey, revokeKey,
+ )
+ require.NoError(t, err)
+
+ want, err := legacyTaprootLocalCommitRevokeScript(
+ selfKey, revokeKey,
+ )
+ require.NoError(t, err)
+
+ require.Equal(t, want, got)
+ })
+
+ // Log a summary of all scripts tested for visual inspection.
+ t.Log("All 22 template vs builder script equivalence checks passed")
+}
+
+// TestTemplateScriptDisassembly provides human-readable output of a few key
+// scripts to make it easy to verify correctness visually.
+func TestTemplateScriptDisassembly(t *testing.T) {
+ t.Parallel()
+
+ senderKey := testKeyBytes(t, 1)
+ receiverKey := testKeyBytes(t, 2)
+ revokeKey := testKeyBytes(t, 3)
+ payHash := testPaymentHash()
+
+ // SenderHTLCScript with confirmed spend.
+ script, err := SenderHTLCScript(
+ senderKey, receiverKey, revokeKey, payHash, true,
+ )
+ require.NoError(t, err)
+ t.Logf("SenderHTLCScript (confirmed):\n %s",
+ hex.EncodeToString(script))
+
+ // ReceiverHTLCScript with confirmed spend.
+ script, err = ReceiverHTLCScript(
+ 800000, senderKey, receiverKey, revokeKey, payHash, true,
+ )
+ require.NoError(t, err)
+ t.Logf("ReceiverHTLCScript (confirmed):\n %s",
+ hex.EncodeToString(script))
+}
diff --git a/input/taproot_test.go b/input/taproot_test.go
index 3a1e000..7defa9e 100644
--- a/input/taproot_test.go
+++ b/input/taproot_test.go
@@ -35,7 +35,8 @@ type testSenderHtlcScriptTree struct {
}
func newTestSenderHtlcScriptTree(t *testing.T,
- auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree {
+ auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) *testSenderHtlcScriptTree {
var preImage lntypes.Preimage
_, err := rand.Read(preImage[:])
@@ -51,9 +52,9 @@ func newTestSenderHtlcScriptTree(t *testing.T,
require.NoError(t, err)
payHash := preImage.Hash()
- htlcScriptTree, err := SenderHTLCScriptTaproot(
+ htlcScriptTree, err := senderHtlcTapScriptTree(
senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(),
- payHash[:], lntypes.Remote, auxLeaf,
+ payHash[:], htlcRemoteIncoming, auxLeaf, opts...,
)
require.NoError(t, err)
@@ -212,9 +213,11 @@ func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType,
}
}
-func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
+func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) {
+
// First, create a new test script tree.
- htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf)
+ htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf, opts...)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
@@ -439,17 +442,34 @@ func TestTaprootSenderHtlcSpend(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
- name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
- t.Run(name, func(t *testing.T) {
- var auxLeaf AuxTapLeaf
- if hasAuxLeaf {
- auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
- bytes.Repeat([]byte{0x01}, 32),
- ))
- }
-
- testTaprootSenderHtlcSpend(t, auxLeaf)
- })
+ for _, prodScript := range []bool{false, true} {
+ name := fmt.Sprintf(
+ "aux_leaf=%v/prod_script=%v",
+ hasAuxLeaf, prodScript,
+ )
+ t.Run(name, func(t *testing.T) {
+ var auxLeaf AuxTapLeaf
+ if hasAuxLeaf {
+ leaf := bytes.Repeat(
+ []byte{0x01}, 32,
+ )
+ auxLeaf = fn.Some(
+ txscript.NewBaseTapLeaf(leaf),
+ )
+ }
+
+ var opts []TaprootScriptOpt
+ if prodScript {
+ opts = append(
+ opts, WithProdScripts(),
+ )
+ }
+
+ testTaprootSenderHtlcSpend(
+ t, auxLeaf, opts...,
+ )
+ })
+ }
}
}
@@ -474,7 +494,8 @@ type testReceiverHtlcScriptTree struct {
}
func newTestReceiverHtlcScriptTree(t *testing.T,
- auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree {
+ auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) *testReceiverHtlcScriptTree {
var preImage lntypes.Preimage
_, err := rand.Read(preImage[:])
@@ -492,9 +513,10 @@ func newTestReceiverHtlcScriptTree(t *testing.T,
const cltvExpiry = 144
payHash := preImage.Hash()
- htlcScriptTree, err := ReceiverHTLCScriptTaproot(
- cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(),
- revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf,
+ htlcScriptTree, err := receiverHtlcTapScriptTree(
+ senderKey.PubKey(), receiverKey.PubKey(),
+ revokeKey.PubKey(), payHash[:], cltvExpiry,
+ htlcRemoteOutgoing, auxLeaf, opts...,
)
require.NoError(t, err)
@@ -652,11 +674,13 @@ func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType,
}
}
-func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
+func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) {
+
// We'll start by creating the HTLC script tree (contains all 3 valid
// spend paths), and also a mock spend transaction that we'll be
// signing below.
- htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf)
+ htlcScriptTree := newTestReceiverHtlcScriptTree(t, auxLeaf, opts...)
// TODO(roasbeef): issue with revoke key??? ctrl block even/odd
@@ -916,19 +940,34 @@ func TestTaprootReceiverHtlcSpend(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
- name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
- t.Run(name, func(t *testing.T) {
- var auxLeaf AuxTapLeaf
- if hasAuxLeaf {
- auxLeaf = fn.Some(
- txscript.NewBaseTapLeaf(
- bytes.Repeat([]byte{0x01}, 32),
- ),
+ for _, prodScript := range []bool{false, true} {
+ name := fmt.Sprintf(
+ "aux_leaf=%v/prod_script=%v",
+ hasAuxLeaf, prodScript,
+ )
+ t.Run(name, func(t *testing.T) {
+ var auxLeaf AuxTapLeaf
+ if hasAuxLeaf {
+ leaf := bytes.Repeat(
+ []byte{0x01}, 32,
+ )
+ auxLeaf = fn.Some(
+ txscript.NewBaseTapLeaf(leaf),
+ )
+ }
+
+ var opts []TaprootScriptOpt
+ if prodScript {
+ opts = append(
+ opts, WithProdScripts(),
+ )
+ }
+
+ testTaprootReceiverHtlcSpend(
+ t, auxLeaf, opts...,
)
- }
-
- testTaprootReceiverHtlcSpend(t, auxLeaf)
- })
+ })
+ }
}
}
@@ -947,7 +986,8 @@ type testCommitScriptTree struct {
}
func newTestCommitScriptTree(local bool,
- auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) {
+ auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) (*testCommitScriptTree, error) {
selfKey, err := btcec.NewPrivateKey()
if err != nil {
@@ -968,11 +1008,11 @@ func newTestCommitScriptTree(local bool,
if local {
commitScriptTree, err = NewLocalCommitScriptTree(
csvDelay, selfKey.PubKey(), revokeKey.PubKey(),
- auxLeaf,
+ auxLeaf, opts...,
)
} else {
commitScriptTree, err = NewRemoteCommitScriptTree(
- selfKey.PubKey(), auxLeaf,
+ selfKey.PubKey(), auxLeaf, opts...,
)
}
if err != nil {
@@ -1064,8 +1104,12 @@ func localCommitRevokeWitGen(sigHash txscript.SigHashType,
}
}
-func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) {
- commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf)
+func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) {
+
+ commitScriptTree, err := newTestCommitScriptTree(
+ true, auxLeaf, opts...,
+ )
require.NoError(t, err)
spendTx := wire.NewMsgTx(2)
@@ -1233,17 +1277,34 @@ func TestTaprootCommitScriptToSelf(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
- name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
- t.Run(name, func(t *testing.T) {
- var auxLeaf AuxTapLeaf
- if hasAuxLeaf {
- auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
- bytes.Repeat([]byte{0x01}, 32),
- ))
- }
-
- testTaprootCommitScriptToSelf(t, auxLeaf)
- })
+ for _, prodScript := range []bool{false, true} {
+ name := fmt.Sprintf(
+ "aux_leaf=%v/prod_script=%v",
+ hasAuxLeaf, prodScript,
+ )
+ t.Run(name, func(t *testing.T) {
+ var auxLeaf AuxTapLeaf
+ if hasAuxLeaf {
+ leaf := bytes.Repeat(
+ []byte{0x01}, 32,
+ )
+ auxLeaf = fn.Some(
+ txscript.NewBaseTapLeaf(leaf),
+ )
+ }
+
+ var opts []TaprootScriptOpt
+ if prodScript {
+ opts = append(
+ opts, WithProdScripts(),
+ )
+ }
+
+ testTaprootCommitScriptToSelf(
+ t, auxLeaf, opts...,
+ )
+ })
+ }
}
}
@@ -1280,8 +1341,12 @@ func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
}
}
-func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) {
- commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf)
+func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) {
+
+ commitScriptTree, err := newTestCommitScriptTree(
+ false, auxLeaf, opts...,
+ )
require.NoError(t, err)
spendTx := wire.NewMsgTx(2)
@@ -1426,17 +1491,34 @@ func TestTaprootCommitScriptRemote(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
- name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
- t.Run(name, func(t *testing.T) {
- var auxLeaf AuxTapLeaf
- if hasAuxLeaf {
- auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
- bytes.Repeat([]byte{0x01}, 32),
- ))
- }
-
- testTaprootCommitScriptRemote(t, auxLeaf)
- })
+ for _, prodScript := range []bool{false, true} {
+ name := fmt.Sprintf(
+ "aux_leaf=%v/prod_script=%v",
+ hasAuxLeaf, prodScript,
+ )
+ t.Run(name, func(t *testing.T) {
+ var auxLeaf AuxTapLeaf
+ if hasAuxLeaf {
+ leaf := bytes.Repeat(
+ []byte{0x01}, 32,
+ )
+ auxLeaf = fn.Some(
+ txscript.NewBaseTapLeaf(leaf),
+ )
+ }
+
+ var opts []TaprootScriptOpt
+ if prodScript {
+ opts = append(
+ opts, WithProdScripts(),
+ )
+ }
+
+ testTaprootCommitScriptRemote(
+ t, auxLeaf, opts...,
+ )
+ })
+ }
}
}
@@ -1676,7 +1758,8 @@ type testSecondLevelHtlcTree struct {
}
func newTestSecondLevelHtlcTree(t *testing.T,
- auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree {
+ auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) *testSecondLevelHtlcTree {
delayKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
@@ -1687,7 +1770,7 @@ func newTestSecondLevelHtlcTree(t *testing.T,
const csvDelay = 6
scriptTree, err := SecondLevelHtlcTapscriptTree(
- delayKey.PubKey(), csvDelay, auxLeaf,
+ delayKey.PubKey(), csvDelay, auxLeaf, opts...,
)
require.NoError(t, err)
@@ -1783,8 +1866,10 @@ func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType,
}
}
-func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) {
- htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf)
+func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf,
+ opts ...TaprootScriptOpt) {
+
+ htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf, opts...)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
@@ -1951,16 +2036,33 @@ func TestTaprootSecondLevelHtlcScript(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
- name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
- t.Run(name, func(t *testing.T) {
- var auxLeaf AuxTapLeaf
- if hasAuxLeaf {
- auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
- bytes.Repeat([]byte{0x01}, 32),
- ))
- }
-
- testTaprootSecondLevelHtlcScript(t, auxLeaf)
- })
+ for _, prodScript := range []bool{false, true} {
+ name := fmt.Sprintf(
+ "aux_leaf=%v/prod_script=%v",
+ hasAuxLeaf, prodScript,
+ )
+ t.Run(name, func(t *testing.T) {
+ var auxLeaf AuxTapLeaf
+ if hasAuxLeaf {
+ leaf := bytes.Repeat(
+ []byte{0x01}, 32,
+ )
+ auxLeaf = fn.Some(
+ txscript.NewBaseTapLeaf(leaf),
+ )
+ }
+
+ var opts []TaprootScriptOpt
+ if prodScript {
+ opts = append(
+ opts, WithProdScripts(),
+ )
+ }
+
+ testTaprootSecondLevelHtlcScript(
+ t, auxLeaf, opts...,
+ )
+ })
+ }
}
}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.