lnd/input/taproot_test.go
Olaoluwa Osuntokun 1aae47fd71
input+lnwallet: update taproot scripts to accept optional aux leaf
In this commit, we update all the taproot scripts to also accept an
optional aux leaf. This aux leaf can be used to add more redemption
paths for advanced channels, or just as an extra commitment space.
2024-08-28 11:52:31 +02:00

1967 lines
45 KiB
Go

package input
import (
"bytes"
"crypto/rand"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
)
type testSenderHtlcScriptTree struct {
preImage lntypes.Preimage
senderKey *btcec.PrivateKey
receiverKey *btcec.PrivateKey
revokeKey *btcec.PrivateKey
htlcTxOut *wire.TxOut
*HtlcScriptTree
rootHash []byte
htlcAmt int64
}
func newTestSenderHtlcScriptTree(t *testing.T,
auxLeaf AuxTapLeaf) *testSenderHtlcScriptTree {
var preImage lntypes.Preimage
_, err := rand.Read(preImage[:])
require.NoError(t, err)
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
payHash := preImage.Hash()
htlcScriptTree, err := SenderHTLCScriptTaproot(
senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(),
payHash[:], lntypes.Remote, auxLeaf,
)
require.NoError(t, err)
const htlcAmt = 100
pkScript, err := PayToTaprootScript(htlcScriptTree.TaprootKey)
require.NoError(t, err)
targetTxOut := &wire.TxOut{
Value: htlcAmt,
PkScript: pkScript,
}
return &testSenderHtlcScriptTree{
preImage: preImage,
senderKey: senderKey,
receiverKey: receiverKey,
revokeKey: revokeKey,
htlcTxOut: targetTxOut,
htlcAmt: htlcAmt,
rootHash: htlcScriptTree.TapscriptRoot,
HtlcScriptTree: htlcScriptTree,
}
}
type witnessGen func(*wire.MsgTx, *txscript.TxSigHashes,
txscript.PrevOutputFetcher) (wire.TxWitness, error)
func htlcSenderRedeemValidWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testSenderHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
receiverKey := htlcScriptTree.receiverKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
receiverKey,
},
}
successLeaf := htlcScriptTree.SuccessTapLeaf
scriptTree := htlcScriptTree.HtlcScriptTree
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: receiverKey.PubKey(),
},
WitnessScript: successLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return SenderHTLCScriptTaprootRedeem(
signer, signDesc, spendTx,
htlcScriptTree.preImage[:],
htlcScriptTree.revokeKey.PubKey(),
scriptTree.TapscriptTree,
)
}
}
func htlcSenderRevocationWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testSenderHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
revokeKey := htlcScriptTree.revokeKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
revokeKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootKeySpendSignMethod,
TapTweak: htlcScriptTree.TapscriptRoot,
PrevOutputFetcher: prevOuts,
}
return SenderHTLCScriptTaprootRevoke(
signer, signDesc, spendTx,
)
}
}
func htlcSenderTimeoutWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testSenderHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
timeoutLeaf := htlcScriptTree.TimeoutTapLeaf
scriptTree := htlcScriptTree.HtlcScriptTree
receiverSigner := &MockSigner{
Privkeys: []*btcec.PrivateKey{
htlcScriptTree.receiverKey,
},
}
receiverDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: htlcScriptTree.receiverKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
receiverSig, err := receiverSigner.SignOutputRaw(
spendTx, receiverDesc,
)
if err != nil {
return nil, err
}
senderKey := htlcScriptTree.senderKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
senderKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: senderKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return SenderHTLCScriptTaprootTimeout(
receiverSig, sigHash, signer, signDesc, spendTx,
htlcScriptTree.revokeKey.PubKey(),
scriptTree.TapscriptTree,
)
}
}
func testTaprootSenderHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
// First, create a new test script tree.
htlcScriptTree := newTestSenderHtlcScriptTree(t, auxLeaf)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: htlcScriptTree.htlcAmt,
})
testCases := []struct {
name string
// TODO(roasbeef): use sighash slice
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
valid bool
}{
// Valid redeem with the pre-image, and the spending
// transaction set to CSV 1 to enforce the required delay.
{
name: "redeem success valid sighash all",
witnessGen: htlcSenderRedeemValidWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Valid with pre-image, using sighash default.
{
name: "redeem success valid sighash default",
witnessGen: htlcSenderRedeemValidWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Valid with pre-image, using sighash single+anyonecanpay.
{
name: "redeem success valid sighash " +
"single|anyonecanpay",
witnessGen: htlcSenderRedeemValidWitnessGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Invalid spend, the witness is correct, but the spending tx
// doesn't have a sequence of 1 set. This uses the CSV 0 trick:
// 0 > 0 -> false.
{
name: "redeem success invalid wrong sequence",
witnessGen: htlcSenderRedeemValidWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: false,
},
// Valid spend with the revocation key, sighash all.
{
name: "revocation spend valid sighash all",
witnessGen: htlcSenderRevocationWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
// Valid spend with the revocation key, sighash default.
{
name: "revocation spend valid sighash default",
witnessGen: htlcSenderRevocationWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
valid: true,
},
// Valid spend with the revocation key, sighash single+anyone
// can pay.
{
name: "revocation spend valid sighash " +
"single|anyonecanpay",
witnessGen: htlcSenderRevocationWitnessGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
valid: true,
},
// Invalid spend with the revocation key. The witness mutator
// modifies the sig.
{
name: "revocation spend invalid",
witnessGen: htlcSenderRevocationWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
// Valid spend of the timeout path, sighash default.
{
name: "timeout spend valid",
witnessGen: htlcSenderTimeoutWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
valid: true,
},
// Valid spend of the timeout path, sighash all.
{
name: "timeout spend valid sighash all",
witnessGen: htlcSenderTimeoutWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
// Valid spend of the timeout path, sighash single.
{
name: "timeout spend valid sighash single",
witnessGen: htlcSenderTimeoutWitnessGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
valid: true,
},
// Invalid spend of timeout path, invalid receiver sig.
{
name: "timeout spend invalid receiver sig",
witnessGen: htlcSenderTimeoutWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
// Invalid spend of timeout path, invalid sender sig.
{
name: "timeout spend invalid sender sig",
witnessGen: htlcSenderTimeoutWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[1][0] ^= 1
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
htlcScriptTree.htlcTxOut.PkScript,
htlcScriptTree.htlcAmt,
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
htlcScriptTree.htlcTxOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags,
nil, hashCache, htlcScriptTree.htlcAmt,
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
// TestTaprootSenderHtlcSpend tests that all the positive and negative paths
// for the sender HTLC tapscript tree work as expected.
func TestTaprootSenderHtlcSpend(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
t.Run(name, func(t *testing.T) {
var auxLeaf AuxTapLeaf
if hasAuxLeaf {
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
bytes.Repeat([]byte{0x01}, 32),
))
}
testTaprootSenderHtlcSpend(t, auxLeaf)
})
}
}
type testReceiverHtlcScriptTree struct {
preImage lntypes.Preimage
senderKey *btcec.PrivateKey
receiverKey *btcec.PrivateKey
revokeKey btcec.PrivateKey
htlcTxOut *wire.TxOut
*HtlcScriptTree
rootHash []byte
htlcAmt int64
lockTime int32
}
func newTestReceiverHtlcScriptTree(t *testing.T,
auxLeaf AuxTapLeaf) *testReceiverHtlcScriptTree {
var preImage lntypes.Preimage
_, err := rand.Read(preImage[:])
require.NoError(t, err)
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
const cltvExpiry = 144
payHash := preImage.Hash()
htlcScriptTree, err := ReceiverHTLCScriptTaproot(
cltvExpiry, senderKey.PubKey(), receiverKey.PubKey(),
revokeKey.PubKey(), payHash[:], lntypes.Remote, auxLeaf,
)
require.NoError(t, err)
const htlcAmt = 100
pkScript, err := PayToTaprootScript(htlcScriptTree.TaprootKey)
require.NoError(t, err)
targetTxOut := &wire.TxOut{
Value: htlcAmt,
PkScript: pkScript,
}
return &testReceiverHtlcScriptTree{
preImage: preImage,
senderKey: senderKey,
receiverKey: receiverKey,
revokeKey: *revokeKey,
htlcTxOut: targetTxOut,
htlcAmt: htlcAmt,
rootHash: htlcScriptTree.TapscriptRoot,
lockTime: cltvExpiry,
HtlcScriptTree: htlcScriptTree,
}
}
func htlcReceiverTimeoutWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testReceiverHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
senderKey := htlcScriptTree.senderKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
senderKey,
},
}
timeoutLeaf := htlcScriptTree.TimeoutTapLeaf
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: senderKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
// With the lock time in place, we can now generate the timeout
// witness.
return ReceiverHTLCScriptTaprootTimeout(
signer, signDesc, spendTx, htlcScriptTree.lockTime,
htlcScriptTree.revokeKey.PubKey(),
htlcScriptTree.TapscriptTree,
)
}
}
func htlcReceiverRevocationWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testReceiverHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
revokeKey := htlcScriptTree.revokeKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
&revokeKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootKeySpendSignMethod,
TapTweak: htlcScriptTree.TapscriptRoot,
PrevOutputFetcher: prevOuts,
}
return ReceiverHTLCScriptTaprootRevoke(
signer, signDesc, spendTx,
)
}
}
func htlcReceiverSuccessWitnessGen(sigHash txscript.SigHashType,
htlcScriptTree *testReceiverHtlcScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
successsLeaf := htlcScriptTree.SuccessTapLeaf
scriptTree := htlcScriptTree.HtlcScriptTree
senderSigner := &MockSigner{
Privkeys: []*btcec.PrivateKey{
htlcScriptTree.senderKey,
},
}
senderDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: htlcScriptTree.senderKey.PubKey(),
},
WitnessScript: successsLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
senderSig, err := senderSigner.SignOutputRaw(
spendTx, senderDesc,
)
if err != nil {
return nil, err
}
receiverKey := htlcScriptTree.receiverKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
receiverKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: receiverKey.PubKey(),
},
WitnessScript: successsLeaf.Script,
Output: htlcScriptTree.htlcTxOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return ReceiverHTLCScriptTaprootRedeem(
senderSig, sigHash, htlcScriptTree.preImage[:],
signer, signDesc, spendTx,
htlcScriptTree.revokeKey.PubKey(),
scriptTree.TapscriptTree,
)
}
}
func testTaprootReceiverHtlcSpend(t *testing.T, auxLeaf AuxTapLeaf) {
// 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)
// TODO(roasbeef): issue with revoke key??? ctrl block even/odd
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: htlcScriptTree.htlcAmt,
})
testCases := []struct {
name string
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
txMutator func(tx *wire.MsgTx)
valid bool
}{
// Valid timeout by the sender after the timeout period has
// passed. We also use a sequence of 1 as the sender must wait
// a single block before being able to sweep the HTLC.
{
name: "timeout valid sig hash all",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Valid timeout like above, but sighash default.
{
name: "timeout valid sig hash default",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Valid timeout like above, but sighash single.
{
name: "timeout valid sig hash single",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
// Invalid timeout case, the sequence of the spending
// transaction isn't set to 1.
{
name: "timeout invalid wrong sequence",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: false,
},
// Invalid timeout case, the lock time is set to the wrong
// value.
{
name: "timeout invalid wrong lock time",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
txMutator: func(tx *wire.MsgTx) {
tx.LockTime = 0
},
valid: false,
},
// Invalid timeout case, the signature is invalid.
{
name: "timeout invalid wrong sig",
witnessGen: htlcReceiverTimeoutWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
// Valid spend of the revocation path.
{
name: "revocation spend valid",
witnessGen: htlcReceiverRevocationWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
// Invalid spend of the revocation path.
{
name: "revocation spend valid",
witnessGen: htlcReceiverRevocationWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
// Valid success spend w/ pre-image and sender sig.
{
name: "success spend valid",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
// Valid succcess spend sighash default.
{
name: "success spend valid sighash default",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
// Valid succcess spend sighash default.
{
name: "success spend valid sig hash default",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashDefault, htlcScriptTree,
),
valid: true,
},
// Valid succcess spend sighash single.
{
name: "success spend valid sighash single",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
valid: true,
},
// Invalid success spend, wrong pre-image.
{
name: "success spend invalid preimage",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
// The pre-image is the 3rd item (starting from
// the "bottom") of the witness stack).
wit[2][0] ^= 1
},
valid: false,
},
// Invalid success spend, invalid sender sig.
{
name: "success spend invalid sender sig",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
// Flip a bit in the sender sig which is the
// first element of the witness stack.
wit[0][0] ^= 1
},
valid: false,
},
// Invalid success spend, invalid receiver sig.
{
name: "success spend invalid receiver sig",
witnessGen: htlcReceiverSuccessWitnessGen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
// Flip a bit in the receiver sig which is the
// second element of the witness stack.
wit[1][0] ^= 1
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
// TODO(roasbeef): consolidate w/ above
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
htlcScriptTree.htlcTxOut.PkScript,
htlcScriptTree.htlcAmt,
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.txMutator != nil {
testCase.txMutator(spendTxCopy)
}
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
htlcScriptTree.htlcTxOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags,
nil, hashCache, htlcScriptTree.htlcAmt,
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
// TestTaprootReceiverHtlcSpend tests that all possible paths for redeeming an
// accepted HTLC (on the commitment transaction) of the receiver work properly.
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),
),
)
}
testTaprootReceiverHtlcSpend(t, auxLeaf)
})
}
}
type testCommitScriptTree struct {
csvDelay uint32
selfKey *btcec.PrivateKey
revokeKey *btcec.PrivateKey
selfAmt btcutil.Amount
txOut *wire.TxOut
*CommitScriptTree
}
func newTestCommitScriptTree(local bool,
auxLeaf AuxTapLeaf) (*testCommitScriptTree, error) {
selfKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
revokeKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
const (
csvDelay = 6
selfAmt = 1000
)
var commitScriptTree *CommitScriptTree
if local {
commitScriptTree, err = NewLocalCommitScriptTree(
csvDelay, selfKey.PubKey(), revokeKey.PubKey(),
auxLeaf,
)
} else {
commitScriptTree, err = NewRemoteCommitScriptTree(
selfKey.PubKey(), auxLeaf,
)
}
if err != nil {
return nil, err
}
pkScript, err := PayToTaprootScript(commitScriptTree.TaprootKey)
if err != nil {
return nil, err
}
return &testCommitScriptTree{
csvDelay: csvDelay,
selfKey: selfKey,
revokeKey: revokeKey,
selfAmt: selfAmt,
txOut: &wire.TxOut{
PkScript: pkScript,
Value: selfAmt,
},
CommitScriptTree: commitScriptTree,
}, nil
}
func localCommitSweepWitGen(sigHash txscript.SigHashType,
commitScriptTree *testCommitScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
selfKey := commitScriptTree.selfKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
selfKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: selfKey.PubKey(),
},
WitnessScript: commitScriptTree.SettleLeaf.Script,
Output: commitScriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return TaprootCommitSpendSuccess(
signer, signDesc, spendTx,
commitScriptTree.TapscriptTree,
)
}
}
func localCommitRevokeWitGen(sigHash txscript.SigHashType,
commitScriptTree *testCommitScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
revokeKey := commitScriptTree.revokeKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
revokeKey,
},
}
revScript := commitScriptTree.RevocationLeaf.Script
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
WitnessScript: revScript,
Output: commitScriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return TaprootCommitSpendRevoke(
signer, signDesc, spendTx,
commitScriptTree.TapscriptTree,
)
}
}
func testTaprootCommitScriptToSelf(t *testing.T, auxLeaf AuxTapLeaf) {
commitScriptTree, err := newTestCommitScriptTree(true, auxLeaf)
require.NoError(t, err)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: int64(commitScriptTree.selfAmt),
})
testCases := []struct {
name string
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
valid bool
}{
{
name: "valid sweep to self",
witnessGen: localCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = commitScriptTree.csvDelay
},
valid: true,
},
{
name: "valid sweep to self sighash default",
witnessGen: localCommitSweepWitGen(
txscript.SigHashDefault, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = commitScriptTree.csvDelay
},
valid: true,
},
{
name: "valid sweep to self sighash single",
witnessGen: localCommitSweepWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = commitScriptTree.csvDelay
},
valid: true,
},
{
name: "invalid sweep to self wrong sequence",
witnessGen: localCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: false,
},
{
name: "invalid sweep to self bad sig",
witnessGen: localCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
{
name: "valid revocation sweep",
witnessGen: localCommitRevokeWitGen(
txscript.SigHashAll, commitScriptTree,
),
valid: true,
},
{
name: "valid revocation sweep sighash default",
witnessGen: localCommitRevokeWitGen(
txscript.SigHashDefault, commitScriptTree,
),
valid: true,
},
{
name: "valid revocation sweep sighash single",
witnessGen: localCommitRevokeWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
commitScriptTree,
),
valid: true,
},
{
name: "invalid revocation sweep bad sig",
witnessGen: localCommitRevokeWitGen(
txscript.SigHashAll, commitScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
commitScriptTree.txOut.PkScript,
int64(commitScriptTree.selfAmt),
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
commitScriptTree.txOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags, nil,
hashCache,
int64(commitScriptTree.selfAmt),
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
// TestTaprootCommitScriptToSelf tests that the taproot script for redeeming
// one's output after a force close behaves as expected.
func TestTaprootCommitScriptToSelf(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
t.Run(name, func(t *testing.T) {
var auxLeaf AuxTapLeaf
if hasAuxLeaf {
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
bytes.Repeat([]byte{0x01}, 32),
))
}
testTaprootCommitScriptToSelf(t, auxLeaf)
})
}
}
func remoteCommitSweepWitGen(sigHash txscript.SigHashType,
commitScriptTree *testCommitScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
selfKey := commitScriptTree.selfKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
selfKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: selfKey.PubKey(),
},
WitnessScript: commitScriptTree.SettleLeaf.Script,
Output: commitScriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return TaprootCommitRemoteSpend(
signer, signDesc, spendTx,
commitScriptTree.TapscriptTree,
)
}
}
func testTaprootCommitScriptRemote(t *testing.T, auxLeaf AuxTapLeaf) {
commitScriptTree, err := newTestCommitScriptTree(false, auxLeaf)
require.NoError(t, err)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: int64(commitScriptTree.selfAmt),
})
testCases := []struct {
name string
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
valid bool
}{
{
name: "valid remote sweep",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
{
name: "valid remote sweep sighash default",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashDefault, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
{
name: "valid remote sweep sighash single",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
valid: true,
},
{
name: "invalid remote sweep wrong sequence",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 0
},
valid: false,
},
{
name: "invalid bad sig",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
{
name: "invalid bad sig right sequence",
witnessGen: remoteCommitSweepWitGen(
txscript.SigHashAll, commitScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
witnessMutator: func(wit wire.TxWitness) {
wit[0][0] ^= 1
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
commitScriptTree.txOut.PkScript,
int64(commitScriptTree.selfAmt),
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
commitScriptTree.txOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags, nil,
hashCache,
int64(commitScriptTree.selfAmt),
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
// TestTaprootCommitScriptRemote tests that the remote party can properly sweep
// their output after force close.
func TestTaprootCommitScriptRemote(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
t.Run(name, func(t *testing.T) {
var auxLeaf AuxTapLeaf
if hasAuxLeaf {
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
bytes.Repeat([]byte{0x01}, 32),
))
}
testTaprootCommitScriptRemote(t, auxLeaf)
})
}
}
type testAnchorScriptTree struct {
sweepKey *btcec.PrivateKey
amt btcutil.Amount
txOut *wire.TxOut
*AnchorScriptTree
}
func newTestAnchorScripTree() (*testAnchorScriptTree, error) {
sweepKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
anchorScriptTree, err := NewAnchorScriptTree(sweepKey.PubKey())
if err != nil {
return nil, err
}
const amt = 1_000
pkScript, err := PayToTaprootScript(anchorScriptTree.TaprootKey)
if err != nil {
return nil, err
}
return &testAnchorScriptTree{
sweepKey: sweepKey,
amt: amt,
txOut: &wire.TxOut{
PkScript: pkScript,
Value: amt,
},
AnchorScriptTree: anchorScriptTree,
}, nil
}
func anchorSweepWitGen(sigHash txscript.SigHashType,
anchorScriptTree *testAnchorScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
sweepKey := anchorScriptTree.sweepKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
sweepKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: sweepKey.PubKey(),
},
Output: anchorScriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootKeySpendSignMethod,
TapTweak: anchorScriptTree.TapscriptRoot,
PrevOutputFetcher: prevOuts,
}
return TaprootAnchorSpend(
signer, signDesc, spendTx,
)
}
}
func anchorAnySweepWitGen(sigHash txscript.SigHashType,
anchorScriptTree *testAnchorScriptTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
return TaprootAnchorSpendAny(
anchorScriptTree.sweepKey.PubKey(),
)
}
}
// TestTaprootCommitScript tests that a channel peer can properly spend the
// anchor, and that anyone can spend it after 16 blocks.
func TestTaprootAnchorScript(t *testing.T) {
t.Parallel()
anchorScriptTree, err := newTestAnchorScripTree()
require.NoError(t, err)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: int64(anchorScriptTree.amt),
})
testCases := []struct {
name string
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
valid bool
}{
{
name: "valid anchor sweep",
witnessGen: anchorSweepWitGen(
txscript.SigHashAll, anchorScriptTree,
),
valid: true,
},
{
name: "valid anchor sweep sighash default",
witnessGen: anchorSweepWitGen(
txscript.SigHashDefault, anchorScriptTree,
),
valid: true,
},
{
name: "valid anchor sweep single",
witnessGen: anchorSweepWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
anchorScriptTree,
),
valid: true,
},
{
name: "invalid anchor sweep bad sig",
witnessGen: anchorSweepWitGen(
txscript.SigHashAll, anchorScriptTree,
),
witnessMutator: func(witness wire.TxWitness) {
witness[0][0] ^= 1
},
valid: false,
},
{
name: "valid 3rd party sweep",
witnessGen: anchorAnySweepWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
anchorScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 16
},
valid: true,
},
{
name: "invalid 3rd party sweep",
witnessGen: anchorAnySweepWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
anchorScriptTree,
),
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 2
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
anchorScriptTree.txOut.PkScript,
int64(anchorScriptTree.amt),
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
anchorScriptTree.txOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags,
nil, hashCache,
int64(anchorScriptTree.amt),
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
type testSecondLevelHtlcTree struct {
delayKey *btcec.PrivateKey
revokeKey *btcec.PrivateKey
csvDelay uint32
amt btcutil.Amount
txOut *wire.TxOut
scriptTree *txscript.IndexedTapScriptTree
tapScriptRoot []byte
}
func newTestSecondLevelHtlcTree(t *testing.T,
auxLeaf AuxTapLeaf) *testSecondLevelHtlcTree {
delayKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
const csvDelay = 6
scriptTree, err := SecondLevelHtlcTapscriptTree(
delayKey.PubKey(), csvDelay, auxLeaf,
)
require.NoError(t, err)
tapScriptRoot := scriptTree.RootNode.TapHash()
htlcKey := txscript.ComputeTaprootOutputKey(
revokeKey.PubKey(), tapScriptRoot[:],
)
pkScript, err := PayToTaprootScript(htlcKey)
require.NoError(t, err)
const amt = 100
return &testSecondLevelHtlcTree{
delayKey: delayKey,
revokeKey: revokeKey,
csvDelay: csvDelay,
txOut: &wire.TxOut{
PkScript: pkScript,
Value: amt,
},
amt: amt,
scriptTree: scriptTree,
tapScriptRoot: tapScriptRoot[:],
}
}
func secondLevelHtlcSuccessWitGen(sigHash txscript.SigHashType,
scriptTree *testSecondLevelHtlcTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
selfKey := scriptTree.delayKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
selfKey,
},
}
tapLeaf := scriptTree.scriptTree.LeafMerkleProofs[0].TapLeaf
witnessScript := tapLeaf.Script
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: selfKey.PubKey(),
},
WitnessScript: witnessScript,
Output: scriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootScriptSpendSignMethod,
PrevOutputFetcher: prevOuts,
}
return TaprootHtlcSpendSuccess(
signer, signDesc, spendTx,
scriptTree.revokeKey.PubKey(), scriptTree.scriptTree,
)
}
}
func secondLevelHtlcRevokeWitnessgen(sigHash txscript.SigHashType,
scriptTree *testSecondLevelHtlcTree) witnessGen {
return func(spendTx *wire.MsgTx, hashCache *txscript.TxSigHashes,
prevOuts txscript.PrevOutputFetcher) (wire.TxWitness, error) {
revokeKey := scriptTree.revokeKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
revokeKey,
},
}
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
Output: scriptTree.txOut,
HashType: sigHash,
InputIndex: 0,
SigHashes: hashCache,
SignMethod: TaprootKeySpendSignMethod,
TapTweak: scriptTree.tapScriptRoot,
PrevOutputFetcher: prevOuts,
}
return TaprootHtlcSpendRevoke(
signer, signDesc, spendTx,
)
}
}
func testTaprootSecondLevelHtlcScript(t *testing.T, auxLeaf AuxTapLeaf) {
htlcScriptTree := newTestSecondLevelHtlcTree(t, auxLeaf)
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{
Value: int64(htlcScriptTree.amt),
})
testCases := []struct {
name string
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
witnessMutator func(witness wire.TxWitness)
valid bool
}{
{
name: "valid success sweep",
witnessGen: secondLevelHtlcSuccessWitGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = htlcScriptTree.csvDelay
},
},
{
name: "valid success sweep sighash default",
witnessGen: secondLevelHtlcSuccessWitGen(
txscript.SigHashDefault, htlcScriptTree,
),
valid: true,
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = htlcScriptTree.csvDelay
},
},
{
name: "valid success sweep sighash single",
witnessGen: secondLevelHtlcSuccessWitGen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
valid: true,
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = htlcScriptTree.csvDelay
},
},
{
name: "invalid success sweep bad sig",
witnessGen: secondLevelHtlcSuccessWitGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: false,
witnessMutator: func(witness wire.TxWitness) {
witness[0][0] ^= 0x01
},
},
{
name: "invalid success sweep bad sequence",
witnessGen: secondLevelHtlcSuccessWitGen(
txscript.SigHashAll, htlcScriptTree,
),
valid: false,
txInMutator: func(txIn *wire.TxIn) {
txIn.Sequence = 1
},
},
{
name: "valid revocation sweep",
witnessGen: secondLevelHtlcRevokeWitnessgen(
txscript.SigHashAll, htlcScriptTree,
),
valid: true,
},
{
name: "valid revocation sweep sig hash default",
witnessGen: secondLevelHtlcRevokeWitnessgen(
txscript.SigHashDefault, htlcScriptTree,
),
valid: true,
},
{
name: "valid revocation sweep single",
witnessGen: secondLevelHtlcRevokeWitnessgen(
txscript.SigHashSingle|
txscript.SigHashAnyOneCanPay,
htlcScriptTree,
),
valid: true,
},
{
name: "invalid revocation sweep",
witnessGen: secondLevelHtlcRevokeWitnessgen(
txscript.SigHashAll, htlcScriptTree,
),
witnessMutator: func(witness wire.TxWitness) {
witness[0][0] ^= 0x01
},
valid: false,
},
}
for i, testCase := range testCases {
i := i
testCase := testCase
spendTxCopy := spendTx.Copy()
t.Run(testCase.name, func(t *testing.T) {
if testCase.txInMutator != nil {
testCase.txInMutator(spendTxCopy.TxIn[0])
}
prevOuts := txscript.NewCannedPrevOutputFetcher(
htlcScriptTree.txOut.PkScript,
int64(htlcScriptTree.amt),
)
hashCache := txscript.NewTxSigHashes(
spendTxCopy, prevOuts,
)
var err error
spendTxCopy.TxIn[0].Witness, err = testCase.witnessGen(
spendTxCopy, hashCache, prevOuts,
)
require.NoError(t, err)
if testCase.witnessMutator != nil {
testCase.witnessMutator(
spendTxCopy.TxIn[0].Witness,
)
}
// With the witness generated, we'll now check for
// script validity.
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(
htlcScriptTree.txOut.PkScript,
spendTxCopy, 0,
txscript.StandardVerifyFlags,
nil, hashCache,
int64(htlcScriptTree.amt),
prevOuts,
)
}
assertEngineExecution(t, i, testCase.valid, newEngine)
})
}
}
// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly
// spend the second level HTLC script to resolve HTLCs.
func TestTaprootSecondLevelHtlcScript(t *testing.T) {
t.Parallel()
for _, hasAuxLeaf := range []bool{true, false} {
name := fmt.Sprintf("aux_leaf=%v", hasAuxLeaf)
t.Run(name, func(t *testing.T) {
var auxLeaf AuxTapLeaf
if hasAuxLeaf {
auxLeaf = fn.Some(txscript.NewBaseTapLeaf(
bytes.Repeat([]byte{0x01}, 32),
))
}
testTaprootSecondLevelHtlcScript(t, auxLeaf)
})
}
}