From 8a5d241a4acacc330c57e5079a959488be2d3dad Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 4 Mar 2026 18:12:34 -0800 Subject: [PATCH] input: add template vs builder byte-for-byte equivalence tests Add regression tests that compare every ScriptTemplate-based function against the original ScriptBuilder implementations extracted from git history. This ensures the template migration produces identical script bytes for all 22 script types (segwit v0 + taproot). The legacy builder functions are kept as private test helpers in script_utils_legacy_test.go, extracted verbatim from the pre-template commit. --- input/script_utils_legacy_test.go | 738 ++++++++++++++++++++++ input/script_utils_template_equiv_test.go | 454 +++++++++++++ input/taproot_test.go | 250 +++++--- 3 files changed, 1368 insertions(+), 74 deletions(-) create mode 100644 input/script_utils_legacy_test.go create mode 100644 input/script_utils_template_equiv_test.go diff --git a/input/script_utils_legacy_test.go b/input/script_utils_legacy_test.go new file mode 100644 index 000000000..b3b4f88e7 --- /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 000000000..4d26b5b7d --- /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 3a1e00037..7defa9eb1 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), - )) - } + 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), + ) + } - testTaprootSenderHtlcSpend(t, auxLeaf) - }) + 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), + ) + } - testTaprootReceiverHtlcSpend(t, auxLeaf) - }) + var opts []TaprootScriptOpt + if prodScript { + opts = append( + opts, WithProdScripts(), + ) + } + + testTaprootReceiverHtlcSpend( + t, auxLeaf, opts..., + ) + }) + } } } @@ -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), - )) - } + 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), + ) + } - testTaprootCommitScriptToSelf(t, auxLeaf) - }) + 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), - )) - } + 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), + ) + } - testTaprootCommitScriptRemote(t, auxLeaf) - }) + 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), - )) - } + 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), + ) + } - testTaprootSecondLevelHtlcScript(t, auxLeaf) - }) + var opts []TaprootScriptOpt + if prodScript { + opts = append( + opts, WithProdScripts(), + ) + } + + testTaprootSecondLevelHtlcScript( + t, auxLeaf, opts..., + ) + }) + } } }