mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
1882 lines
43 KiB
Go
1882 lines
43 KiB
Go
package input
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"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/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) *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[:], false,
|
|
)
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
// First, create a new test script tree.
|
|
htlcScriptTree := newTestSenderHtlcScriptTree(t)
|
|
|
|
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)
|
|
})
|
|
}
|
|
}
|
|
|
|
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) *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[:], false,
|
|
)
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
// 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)
|
|
|
|
// 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)
|
|
})
|
|
}
|
|
}
|
|
|
|
type testCommitScriptTree struct {
|
|
csvDelay uint32
|
|
|
|
selfKey *btcec.PrivateKey
|
|
|
|
revokeKey *btcec.PrivateKey
|
|
|
|
selfAmt btcutil.Amount
|
|
|
|
txOut *wire.TxOut
|
|
|
|
*CommitScriptTree
|
|
}
|
|
|
|
func newTestCommitScriptTree(local bool) (*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(),
|
|
)
|
|
} else {
|
|
commitScriptTree, err = NewRemoteCommitScriptTree(
|
|
selfKey.PubKey(),
|
|
)
|
|
}
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
|
|
commitScriptTree, err := newTestCommitScriptTree(true)
|
|
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)
|
|
})
|
|
}
|
|
}
|
|
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
// TestTaprootCommitScriptRemote tests that the remote party can properly sweep
|
|
// their output after force close.
|
|
func TestTaprootCommitScriptRemote(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
commitScriptTree, err := newTestCommitScriptTree(false)
|
|
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)
|
|
})
|
|
}
|
|
}
|
|
|
|
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() (*testSecondLevelHtlcTree, error) {
|
|
delayKey, err := btcec.NewPrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
revokeKey, err := btcec.NewPrivateKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
const csvDelay = 6
|
|
|
|
scriptTree, err := SecondLevelHtlcTapscriptTree(
|
|
delayKey.PubKey(), csvDelay,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tapScriptRoot := scriptTree.RootNode.TapHash()
|
|
|
|
htlcKey := txscript.ComputeTaprootOutputKey(
|
|
revokeKey.PubKey(), tapScriptRoot[:],
|
|
)
|
|
|
|
pkScript, err := PayToTaprootScript(htlcKey)
|
|
if err != nil {
|
|
return nil, 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[:],
|
|
}, nil
|
|
}
|
|
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
|
|
// TestTaprootSecondLevelHtlcScript tests that a channel peer can properly
|
|
// spend the second level HTLC script to resolve HTLCs.
|
|
func TestTaprootSecondLevelHtlcScript(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
htlcScriptTree, err := newTestSecondLevelHtlcTree()
|
|
require.NoError(t, err)
|
|
|
|
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)
|
|
})
|
|
}
|
|
}
|