mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
1aae47fd71
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.
1967 lines
45 KiB
Go
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)
|
|
})
|
|
}
|
|
}
|