diff --git a/input/script_utils.go b/input/script_utils.go index ed410bac2..9a2db2927 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -554,19 +554,24 @@ func SenderHTLCTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, return txscript.NewBaseTapLeaf(successLeafScript), nil } -// HtlcScriptTree... +// HtlcScriptTree holds the taproot output key, as well as the two script path +// leaves that every taproot HTLC script depends on. type HtlcScriptTree struct { - // TaprootKey... + // TaprootKey is the key that will be used to generate the taproot output. TaprootKey *btcec.PublicKey - // SuccessTapLeaf... + // SuccessTapLeaf is the tapleaf for the redemption path. SuccessTapLeaf txscript.TapLeaf - // TimeoutTapLeaf... + // TimeoutTapLeaf is the tapleaf for the timeout path. TimeoutTapLeaf txscript.TapLeaf - // TapscriptTree... + // TapscriptTree is the full tapscript tree that also includes the + // control block needed to spend each of the leaves. TapscriptTree *txscript.IndexedTapScriptTree + + // TapscriptTreeRoot is the root hash of the tapscript tree. + TapscriptRoot []byte } // senderHtlcTapScriptTree builds the tapscript tree which is used to anchor @@ -608,6 +613,7 @@ func senderHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, TapscriptTree: tapscriptTree, + TapscriptRoot: tapScriptRoot[:], }, nil } @@ -654,7 +660,7 @@ func SenderHTLCScriptTaproot(senderHtlcKey, receiverHtlcKey, // the sighash type isn't sighash default. func maybeAppendSighash(sig Signature, sigHash txscript.SigHashType) []byte { sigBytes := sig.Serialize() - if sigHash != txscript.SigHashDefault { + if sigHash == txscript.SigHashDefault { return sigBytes } @@ -1066,8 +1072,8 @@ func ReceiverHtlcTapLeafTimeout(senderHtlcKey *btcec.PublicKey, // // OP_SIZE 32 OP_EQUALVERIFY OP_HASH160 // OP_EQUALVERIFY -// OP_CHECKSIGVERIFY -// OP_CHECKSIG +// OP_CHECKSIGVERIFY +// OP_CHECKSIG func ReceiverHtlcTapLeafSuccess(receiverHtlcKey *btcec.PublicKey, senderHtlcKey *btcec.PublicKey, paymentHash []byte) (txscript.TapLeaf, error) { @@ -1115,7 +1121,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, return nil, err } timeoutTapLeaf, err := ReceiverHtlcTapLeafTimeout( - receiverHtlcKey, cltvExpiry, + senderHtlcKey, cltvExpiry, ) if err != nil { return nil, err @@ -1140,6 +1146,7 @@ func receiverHtlcTapScriptTree(senderHtlcKey, receiverHtlcKey, SuccessTapLeaf: successTapLeaf, TimeoutTapLeaf: timeoutTapLeaf, TapscriptTree: tapscriptTree, + TapscriptRoot: tapScriptRoot[:], }, nil } @@ -1213,8 +1220,8 @@ func ReceiverHTLCScriptTaprootRedeem(senderSig Signature, // The final witness stack is: // * witnessStack := wire.TxWitness(make([][]byte, 5)) - witnessStack[0] = append(senderSig.Serialize(), byte(senderSigHash)) - witnessStack[1] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[0] = maybeAppendSighash(senderSig, senderSigHash) + witnessStack[1] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[2] = paymentPreimage witnessStack[3] = signDesc.WitnessScript witnessStack[4], err = successControlBlock.ToBytes() @@ -1266,7 +1273,7 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, // The final witness is pretty simple, we just need to present a valid // signature for the script, and then provide the control block. witnessStack := make(wire.TxWitness, 3) - witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript witnessStack[2], err = timeoutControlBlock.ToBytes() if err != nil { @@ -1276,6 +1283,25 @@ func ReceiverHTLCScriptTaprootTimeout(signer Signer, signDesc *SignDescriptor, return witnessStack, nil } +// ReceiverHTLCScriptTaprootRevoke creates a valid witness needed to spend the +// revocation path of the HTLC from the PoV of the sender (offerer) of the +// HTLC. This uses a plain keyspend using the specified revocation key. +func ReceiverHTLCScriptTaprootRevoke(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx) (wire.TxWitness, error) { + + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) + + return witnessStack, nil +} + // SecondLevelHtlcScript is the uniform script that's used as the output for // the second-level HTLC transactions. The second level transaction act as a // sort of covenant, ensuring that a 2-of-2 multi-sig output can only be @@ -1457,7 +1483,7 @@ func TaprootHtlcSpendRevoke(signer Signer, signDesc *SignDescriptor, // The witness stack in this case is pretty simple: we only need to // specify the signature generated. witnessStack := make(wire.TxWitness, 1) - witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) return witnessStack, nil } @@ -1494,7 +1520,7 @@ func TaprootHtlcSpendSuccess(signer Signer, signDesc *SignDescriptor, // // witnessStack := make(wire.TxWitness, 3) - witnessStack[0] = append(sweepSig.Serialize(), byte(signDesc.HashType)) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) witnessStack[1] = signDesc.WitnessScript witnessStack[2], err = redeemControlBlock.ToBytes() if err != nil { @@ -1748,30 +1774,29 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey) return builder.Script() } -// TaprootCommitScriptToSelf creates the taproot witness program that commits -// to the revocation (keyspend) and delay path (script path) in a single -// taproot output key. -// -// For the delay path we have the following tapscript leaf script: -// -// OP_CHECKSIG -// OP_CHECKSEQUENCEVERIFY OP_DROP -// -// This can then be spent with just: -// -// -// -// Where the to_delay_script is listed above, and the delay_control_block -// computed as: -// -// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey -// -// The revocation key spend path will simply present a valid signature with the -// witness being just: -// -// -func TaprootCommitScriptToSelf(csvTimeout uint32, - selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { +// CommitScript holds the taproot output key (in this case the revocation key, +// or a NUMs point for the remote output) along with the tapscript leaf that +// can spend the output after a delay. +type CommitScriptTree struct { + // TaprootKey is the key that will be used to generate the taproot + // output. + TaprootKey *btcec.PublicKey + + // SettleLeaf is the leaf used to settle the output after the delay. + SettleLeaf txscript.TapLeaf + + // TapscriptTree is the full tapscript tree that also includes the + // control block needed to spend each of the leaves. + TapscriptTree *txscript.IndexedTapScriptTree + + // TapscriptTreeRoot is the root hash of the tapscript tree. + TapscriptRoot []byte +} + +// NewLocalCommitScriptTree returns a new CommitScript tree that can be used to +// create and spend the commitment output for the local party. +func NewLocalCommitScriptTree(csvTimeout uint32, + selfKey, revokeKey *btcec.PublicKey) (*CommitScriptTree, error) { // First, we'll need to construct the tapLeaf that'll be our delay CSV // clause. @@ -1801,7 +1826,104 @@ func TaprootCommitScriptToSelf(csvTimeout uint32, revokeKey, tapScriptRoot[:], ) - return toLocalOutputKey, nil + return &CommitScriptTree{ + SettleLeaf: tapLeaf, + TaprootKey: toLocalOutputKey, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + }, nil +} + +// TaprootCommitScriptToSelf creates the taproot witness program that commits +// to the revocation (keyspend) and delay path (script path) in a single +// taproot output key. +// +// For the delay path we have the following tapscript leaf script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY OP_DROP +// +// This can then be spent with just: +// +// +// +// Where the to_delay_script is listed above, and the delay_control_block +// computed as: +// +// delay_control_block = (output_key_y_parity | 0xc0) || revocationpubkey +// +// The revocation key spend path will simply present a valid signature with the +// witness being just: +// +// +func TaprootCommitScriptToSelf(csvTimeout uint32, + selfKey, revokeKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + commitScriptTree, err := NewLocalCommitScriptTree( + csvTimeout, selfKey, revokeKey, + ) + if err != nil { + return nil, err + } + + return commitScriptTree.TaprootKey, nil +} + +// TaprootCommitSpendSuccess constructs a valid witness allowing a node to +// sweep the settled taproot output after the delay has passed for a force +// close. +func TaprootCommitSpendSuccess(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, revokeKey *btcec.PublicKey, + scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll need to construct a valid control block to execute the + // leaf script for sweep settlement. + settleTapleafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] + settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] + settleControlBlock := settleMerkleProof.ToControlBlock(revokeKey) + + // With the control block created, we'll now generate the signature we + // need to authorize the spend. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The final witness stack will be: + // + // + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) + witnessStack[1] = signDesc.WitnessScript + witnessStack[2], err = settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil +} + +// TaprootCommitSpendRevoke constructs a valid witness allowing a node to sweep +// the revoked taproot output of a malicious peer. +func TaprootCommitSpendRevoke(signer Signer, signDesc *SignDescriptor, + revokeTx *wire.MsgTx) (wire.TxWitness, error) { + + // For this spend type, we only need a single signature which'll be a + // keyspend using the revoke private key. + sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) + + return witnessStack, nil } // LeaseCommitScriptToSelf constructs the public key script for the output on the @@ -2018,26 +2140,10 @@ func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } -// TaprootCommitScriptToRemote constructs a taproot witness program for the -// output on the commitment transaction for the remote party. For the top level -// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a -// sort of practical NUMs point (the local party would never sign for this). We -// then commit to a single tapscript leaf that holds the normal CSV 1 delay -// script. -// -// Our single tapleaf will use the following script: -// -// OP_CHECKSIG -// OP_CHECKSEQUENCEVERIFY -// -// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it -// succeeds, which then enforces our 1 CSV. The true will remain on the stack, -// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on -// the stack. -// -// TODO(roasbeef): double check here can't pass additional stack elements? -func TaprootCommitScriptToRemote(combinedFundingKey, - remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) { +// NewRemoteCommitScriptTree constructs a new script tree for the remote party +// to sweep their funds after a hard coded 1 block delay. +func NewRemoteCommitScriptTree(numsKey, + remoteKey *btcec.PublicKey) (*CommitScriptTree, error) { // First, construct the remote party's tapscript they'll use to sweep their // outputs. @@ -2060,10 +2166,82 @@ func TaprootCommitScriptToRemote(combinedFundingKey, // Now that we have our root, we can arrive at the final output script // by tweaking the internal key with this root. toRemoteOutputKey := txscript.ComputeTaprootOutputKey( - combinedFundingKey, tapScriptRoot[:], + numsKey, tapScriptRoot[:], ) - return toRemoteOutputKey, nil + return &CommitScriptTree{ + TaprootKey: toRemoteOutputKey, + SettleLeaf: tapLeaf, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + }, nil +} + +// TaprootCommitScriptToRemote constructs a taproot witness program for the +// output on the commitment transaction for the remote party. For the top level +// key spend, we'll use the combined funding key (musig2.KeyAgg(k1, k2)), as a +// sort of practical NUMs point (the local party would never sign for this). We +// then commit to a single tapscript leaf that holds the normal CSV 1 delay +// script. +// +// Our single tapleaf will use the following script: +// +// OP_CHECKSIG +// OP_CHECKSEQUENCEVERIFY +// +// The CSV clause is a bit subtle, but OP_CHECKSIG will return true if it +// succeeds, which then enforces our 1 CSV. The true will remain on the stack, +// causing the script to pass. If the CHECKSIG fails, then a 0 will remain on +// the stack. +// +// TODO(roasbeef): double check here can't pass additional stack elements? +func TaprootCommitScriptToRemote(combinedFundingKey, + remoteKey *btcec.PublicKey) (*btcec.PublicKey, error) { + + commitScriptTree, err := NewRemoteCommitScriptTree( + combinedFundingKey, remoteKey, + ) + if err != nil { + return nil, err + } + + return commitScriptTree.TaprootKey, nil +} + +// TaprootCommitRemoteSpend allows the remote party to sweep their output into +// their wallet after an enforced 1 block delay. +func TaprootCommitRemoteSpend(signer Signer, signDesc *SignDescriptor, + sweepTx *wire.MsgTx, numsKey *btcec.PublicKey, + scriptTree *txscript.IndexedTapScriptTree) (wire.TxWitness, error) { + + // First, we'll need to construct a valid control block to execute the + // leaf script for sweep settlement. + settleTapleafHash := txscript.NewBaseTapLeaf( + signDesc.WitnessScript, + ).TapHash() + settleIdx := scriptTree.LeafProofIndex[settleTapleafHash] + settleMerkleProof := scriptTree.LeafMerkleProofs[settleIdx] + settleControlBlock := settleMerkleProof.ToControlBlock(numsKey) + + // With the control block created, we'll now generate the signature we + // need to authorize the spend. + sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc) + if err != nil { + return nil, err + } + + // The final witness stack will be: + // + // + witnessStack := make(wire.TxWitness, 3) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) + witnessStack[1] = signDesc.WitnessScript + witnessStack[2], err = settleControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil } // LeaseCommitScriptToRemoteConfirmed constructs the script for the output on @@ -2164,15 +2342,29 @@ func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) { return builder.Script() } -// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that -// encodes the anchor output spending conditions: the passed key can be used -// for keyspend, with the OP_CSV 16 clause living within an internal tapscript -// leaf. +// AnchorScriptTree holds all the contents needed to to sweep a taproot anchor +// output on chain. // -// Spend paths: -// - Key spend: -// - Script spend: OP_16 CSV -func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { +// TODO(roasbeef): refactor trees to reduce dedup +type AnchorScriptTree struct { + // TaprootKey is the key that will be used to generate the taproot + // output. + TaprootKey *btcec.PublicKey + + // SweepLeaf is the leaf used to settle the output after the delay. + SweepLeaf txscript.TapLeaf + + // TapscriptTree is the full tapscript tree that also includes the + // control block needed to spend each of the leaves. + TapscriptTree *txscript.IndexedTapScriptTree + + // TapscriptTreeRoot is the root hash of the tapscript tree. + TapscriptRoot []byte +} + +// NewAnchorScriptTree makes a new script tree for an anchor output with the +// passed anchor key. +func NewAnchorScriptTree(anchorKey *btcec.PublicKey) (*AnchorScriptTree, error) { // The main script used is just a OP_16 CSV (anyone can sweep after 16 // blocks). builder := txscript.NewScriptBuilder() @@ -2192,11 +2384,83 @@ func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { // Now that we have our root, we can arrive at the final output script // by tweaking the internal key with this root. - anchorKey := txscript.ComputeTaprootOutputKey( - key, tapScriptRoot[:], + anchorOutputKey := txscript.ComputeTaprootOutputKey( + anchorKey, tapScriptRoot[:], ) - return anchorKey, nil + return &AnchorScriptTree{ + TaprootKey: anchorOutputKey, + SweepLeaf: tapLeaf, + TapscriptTree: tapScriptTree, + TapscriptRoot: tapScriptRoot[:], + }, nil +} + +// TaprootOutputKeyAnchor returns the segwit v1 (taproot) witness program that +// encodes the anchor output spending conditions: the passed key can be used +// for keyspend, with the OP_CSV 16 clause living within an internal tapscript +// leaf. +// +// Spend paths: +// - Key spend: +// - Script spend: OP_16 CSV +func TaprootOutputKeyAnchor(key *btcec.PublicKey) (*btcec.PublicKey, error) { + anchorScriptTree, err := NewAnchorScriptTree(key) + if err != nil { + return nil, err + } + + return anchorScriptTree.TaprootKey, nil +} + +// TaprootAnchorSpend constructs a valid witness allowing a node to sweep their +// anchor output. +func TaprootAnchorSpend(signer Signer, signDesc *SignDescriptor, + revokeTx *wire.MsgTx) (wire.TxWitness, error) { + + // For this spend type, we only need a single signature which'll be a + // keyspend using the revoke private key. + sweepSig, err := signer.SignOutputRaw(revokeTx, signDesc) + if err != nil { + return nil, err + } + + // The witness stack in this case is pretty simple: we only need to + // specify the signature generated. + witnessStack := make(wire.TxWitness, 1) + witnessStack[0] = maybeAppendSighash(sweepSig, signDesc.HashType) + + return witnessStack, nil +} + +// TaprootAnchorSpendAny constructs a valid witness allowing anyone to sweep +// the anchor output after 16 blocks. +func TaprootAnchorSpendAny(anchorKey *btcec.PublicKey) (wire.TxWitness, error) { + anchorScriptTree, err := NewAnchorScriptTree(anchorKey) + if err != nil { + return nil, err + } + + // For this spend, the only thing we need to do is create a valid + // control block. Other than that, there're no restrictions to how the + // output can be spent. + scriptTree := anchorScriptTree.TapscriptTree + sweepLeaf := anchorScriptTree.SweepLeaf + sweepIdx := scriptTree.LeafProofIndex[sweepLeaf.TapHash()] + sweepMerkleProof := scriptTree.LeafMerkleProofs[sweepIdx] + sweepControlBlock := sweepMerkleProof.ToControlBlock(anchorKey) + + // The final witness stack will be: + // + // + witnessStack := make(wire.TxWitness, 2) + witnessStack[0] = sweepLeaf.Script + witnessStack[1], err = sweepControlBlock.ToBytes() + if err != nil { + return nil, err + } + + return witnessStack, nil } // CommitSpendAnchor constructs a valid witness allowing a node to spend their diff --git a/input/script_utils_test.go b/input/script_utils_test.go index 05bc6b5d1..92eb80699 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -52,7 +52,7 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool, if err != nil { t.Fatalf("stepping (%v)\n", err) } - debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis)) + debugBuf.WriteString(fmt.Sprintf("Stepping %v\n", dis)) done, err = vm.Step() if err != nil && valid { @@ -65,8 +65,11 @@ func assertEngineExecution(t *testing.T, testNum int, valid bool, "should be invalid: %v", testNum, err) } - debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack())) - debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack())) + debugBuf.WriteString(fmt.Sprintf("Stack: %v\n", + vm.GetStack())) + debugBuf.WriteString(fmt.Sprintf("AltStack: %v\n", + vm.GetAltStack())) + debugBuf.WriteString("-----\n") } // If we get to this point the unexpected case was not reached diff --git a/input/taproot_test.go b/input/taproot_test.go new file mode 100644 index 000000000..74ec1db03 --- /dev/null +++ b/input/taproot_test.go @@ -0,0 +1,1877 @@ +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 + + lockTime int32 +} + +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[:], + ) + 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 + + 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 vaild sighash all", + witnessGen: htlcSenderRevocationWitnessGen( + txscript.SigHashAll, htlcScriptTree, + ), + valid: true, + }, + + // Valid spend with the revocation key, sighash default. + { + name: "revocation spend vaild sighash default", + witnessGen: htlcSenderRevocationWitnessGen( + txscript.SigHashDefault, htlcScriptTree, + ), + valid: true, + }, + + // Valid spend with the revocation key, sighash single+anyone + // can pay. + { + name: "revocation spend vaild 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 reciever 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 { + 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, + txscript.NewCannedPrevOutputFetcher( + htlcScriptTree.htlcTxOut.PkScript, + htlcScriptTree.htlcAmt, + ), + ) + } + 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[:], + ) + 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) + + 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 { + 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, + txscript.NewCannedPrevOutputFetcher( + htlcScriptTree.htlcTxOut.PkScript, + htlcScriptTree.htlcAmt, + ), + ) + } + 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( + revokeKey.PubKey(), selfKey.PubKey(), + ) + } + + 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.revokeKey.PubKey(), + 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, + }, + } + + signDesc := &SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + PubKey: revokeKey.PubKey(), + }, + Output: commitScriptTree.txOut, + HashType: sigHash, + InputIndex: 0, + SigHashes: hashCache, + SignMethod: TaprootKeySpendSignMethod, + TapTweak: commitScriptTree.TapscriptRoot, + PrevOutputFetcher: prevOuts, + } + + return TaprootCommitSpendRevoke( + signer, signDesc, spendTx, + ) + } +} + +// 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 { + 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), + txscript.NewCannedPrevOutputFetcher( + commitScriptTree.txOut.PkScript, + int64(commitScriptTree.selfAmt), + ), + ) + } + 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.revokeKey.PubKey(), + 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 { + 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), + txscript.NewCannedPrevOutputFetcher( + commitScriptTree.txOut.PkScript, + int64(commitScriptTree.selfAmt), + ), + ) + } + 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 { + 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), + txscript.NewCannedPrevOutputFetcher( + anchorScriptTree.txOut.PkScript, + int64(anchorScriptTree.amt), + ), + ) + } + 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, scriptTree.revokeKey.PubKey(), + spendTx, 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 deafult", + 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 { + 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), + txscript.NewCannedPrevOutputFetcher( + htlcScriptTree.txOut.PkScript, + int64(htlcScriptTree.amt), + ), + ) + } + assertEngineExecution(t, i, testCase.valid, newEngine) + }) + } +} diff --git a/input/test_utils.go b/input/test_utils.go index c7e26f9e7..35f372c43 100644 --- a/input/test_utils.go +++ b/input/test_utils.go @@ -72,9 +72,70 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, return nil, fmt.Errorf("mock signer does not have key") } - sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes, - signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript, - signDesc.HashType, privKey) + // In case of a taproot output any signature is always a Schnorr + // signature, based on the new tapscript sighash algorithm. + // + // TODO(roasbeef): should conslidate with btcwallet/signer.go + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + sigHashes := txscript.NewTxSigHashes( + tx, signDesc.PrevOutputFetcher, + ) + + witnessScript := signDesc.WitnessScript + + // Are we spending a script path or the key path? The API is + // slightly different, so we need to account for that to get the + // raw signature. + var ( + rawSig []byte + err error + ) + switch signDesc.SignMethod { + case TaprootKeySpendBIP0086SignMethod, + TaprootKeySpendSignMethod: + + // This function tweaks the private key using the tap + // root key supplied as the tweak. + rawSig, err = txscript.RawTxInTaprootSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + signDesc.TapTweak, signDesc.HashType, + privKey, + ) + if err != nil { + return nil, err + } + + case TaprootScriptSpendSignMethod: + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: witnessScript, + } + rawSig, err = txscript.RawTxInTapscriptSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + leaf, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + } + + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) + if err != nil { + return nil, err + } + + return sig, nil + } + + sig, err := txscript.RawTxInWitnessSignature( + tx, signDesc.SigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.WitnessScript, + signDesc.HashType, privKey, + ) if err != nil { return nil, err }