mirror of
https://github.com/lightningnetwork/lnd.git
synced 2026-08-16 13:00:19 +02:00
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.
This commit is contained in:
parent
833f17fe6b
commit
8a5d241a4a
3 changed files with 1368 additions and 74 deletions
738
input/script_utils_legacy_test.go
Normal file
738
input/script_utils_legacy_test.go
Normal file
|
|
@ -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()
|
||||
}
|
||||
454
input/script_utils_template_equiv_test.go
Normal file
454
input/script_utils_template_equiv_test.go
Normal file
|
|
@ -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))
|
||||
}
|
||||
|
|
@ -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...,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue