package itest

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"testing"

	"github.com/btcsuite/btcd/blockchain"
	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/btcsuite/btcd/btcutil"
	"github.com/btcsuite/btcd/btcutil/psbt"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/lightningnetwork/lnd/funding"
	"github.com/lightningnetwork/lnd/input"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
	"github.com/lightningnetwork/lnd/lnrpc/signrpc"
	"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
	"github.com/lightningnetwork/lnd/lntest"
	"github.com/lightningnetwork/lnd/lntest/node"
	"github.com/lightningnetwork/lnd/lntypes"
	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
	"github.com/stretchr/testify/require"
)

const (
	testTaprootKeyFamily = 77
	testAmount           = 800_000
	signMethodBip86      = signrpc.SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND_BIP0086
	signMethodRootHash   = signrpc.SignMethod_SIGN_METHOD_TAPROOT_KEY_SPEND
	signMethodTapscript  = signrpc.SignMethod_SIGN_METHOD_TAPROOT_SCRIPT_SPEND
)

var (
	hexDecode = func(keyStr string) []byte {
		keyBytes, _ := hex.DecodeString(keyStr)
		return keyBytes
	}
	dummyInternalKey, _ = btcec.ParsePubKey(hexDecode(
		"03464805f5468e294d88cf15a3f06aef6c89d63ef1bd7b42db2e0c74c1ac" +
			"eb90fe",
	))
)

// testTaproot ensures that the daemon can send to and spend from taproot (p2tr)
// outputs.
func testTaproot(ht *lntest.HarnessTest) {
	testTaprootSendCoinsKeySpendBip86(ht, ht.Alice)
	testTaprootComputeInputScriptKeySpendBip86(ht, ht.Alice)
	testTaprootSignOutputRawScriptSpend(ht, ht.Alice)
	testTaprootSignOutputRawScriptSpend(
		ht, ht.Alice, txscript.SigHashSingle,
	)
	testTaprootSignOutputRawKeySpendBip86(ht, ht.Alice)
	testTaprootSignOutputRawKeySpendBip86(
		ht, ht.Alice, txscript.SigHashSingle,
	)
	testTaprootSignOutputRawKeySpendRootHash(ht, ht.Alice)

	muSig2Versions := []signrpc.MuSig2Version{
		signrpc.MuSig2Version_MUSIG2_VERSION_V040,
		signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2,
	}
	for _, version := range muSig2Versions {
		testTaprootMuSig2KeySpendBip86(ht, ht.Alice, version)
		testTaprootMuSig2KeySpendRootHash(ht, ht.Alice, version)
		testTaprootMuSig2ScriptSpend(ht, ht.Alice, version)
		testTaprootMuSig2CombinedLeafKeySpend(ht, ht.Alice, version)
		testMuSig2CombineKey(ht, ht.Alice, version)
	}

	testTaprootImportTapscriptFullTree(ht, ht.Alice)
	testTaprootImportTapscriptPartialReveal(ht, ht.Alice)
	testTaprootImportTapscriptRootHashOnly(ht, ht.Alice)
	testTaprootImportTapscriptFullKey(ht, ht.Alice)
}

// testTaprootSendCoinsKeySpendBip86 tests sending to and spending from
// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which
// internally uses the ComputeInputScript method for signing.
func testTaprootSendCoinsKeySpendBip86(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// We'll start the test by sending Alice some coins, which she'll use to
	// send to herself on a p2tr output.
	ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)

	// Let's create a p2tr address now.
	p2trResp := alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: AddrTypeTaprootPubkey,
	})

	// Assert this is a segwit v1 address that starts with bcrt1p.
	require.Contains(
		ht, p2trResp.Address, ht.Miner().ActiveNet.Bech32HRPSegwit+"1p",
	)

	// Send the coins from Alice's wallet to her own, but to the new p2tr
	// address.
	alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{
		Addr:       p2trResp.Address,
		Amount:     0.5 * btcutil.SatoshiPerBitcoin,
		TargetConf: 6,
	})

	txid := ht.AssertNumTxsInMempool(1)[0]

	// Wait until bob has seen the tx and considers it as owned.
	p2trOutputIndex := ht.GetOutputIndex(txid, p2trResp.Address)
	op := &lnrpc.OutPoint{
		TxidBytes:   txid[:],
		OutputIndex: uint32(p2trOutputIndex),
	}
	ht.AssertUTXOInWallet(alice, op, "")

	// Mine a block to clean up the mempool.
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// Let's sweep the whole wallet to a new p2tr address, making sure we
	// can sign transactions with v0 and v1 inputs.
	p2trResp = alice.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: lnrpc.AddressType_TAPROOT_PUBKEY,
	})

	alice.RPC.SendCoins(&lnrpc.SendCoinsRequest{
		Addr:       p2trResp.Address,
		SendAll:    true,
		TargetConf: 6,
	})

	// Make sure the coins sent to the address are confirmed correctly,
	// including the confirmation notification.
	confirmAddress(ht, alice, p2trResp.Address)
}

// testTaprootComputeInputScriptKeySpendBip86 tests sending to and spending from
// p2tr key spend only (BIP-0086) addresses through the SendCoins RPC which
// internally uses the ComputeInputScript method for signing.
func testTaprootComputeInputScriptKeySpendBip86(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// We'll start the test by sending Alice some coins, which she'll use
	// to send to herself on a p2tr output.
	ht.FundCoins(btcutil.SatoshiPerBitcoin, alice)

	// Let's create a p2tr address now.
	p2trAddr, p2trPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_TAPROOT_PUBKEY,
	)

	// Send the coins from Alice's wallet to her own, but to the new p2tr
	// address.
	req := &lnrpc.SendCoinsRequest{
		Addr:       p2trAddr.String(),
		Amount:     testAmount,
		TargetConf: 6,
	}
	alice.RPC.SendCoins(req)

	// Wait until bob has seen the tx and considers it as owned.
	txid := ht.AssertNumTxsInMempool(1)[0]
	p2trOutputIndex := ht.GetOutputIndex(txid, p2trAddr.String())
	op := &lnrpc.OutPoint{
		TxidBytes:   txid[:],
		OutputIndex: uint32(p2trOutputIndex),
	}
	ht.AssertUTXOInWallet(alice, op, "")

	p2trOutpoint := wire.OutPoint{
		Hash:  txid,
		Index: uint32(p2trOutputIndex),
	}

	// Mine a block to clean up the mempool.
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// We'll send the coins back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}
	signReq := &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:     utxoInfo[0],
			InputIndex: 0,
			Sighash:    uint32(txscript.SigHashDefault),
		}},
		PrevOutputs: utxoInfo,
	}
	signResp := alice.RPC.ComputeInputScript(signReq)

	tx.TxIn[0].Witness = signResp.InputScripts[0].Witness

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootSignOutputRawScriptSpend tests sending to and spending from p2tr
// script addresses using the script path with the SignOutputRaw RPC.
func testTaprootSignOutputRawScriptSpend(ht *lntest.HarnessTest,
	alice *node.HarnessNode, sigHashType ...txscript.SigHashType) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}
	keyDesc := alice.RPC.DeriveNextKey(req)

	leafSigningKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
	require.NoError(ht, err)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	// Let's add a second script output as well to test the partial reveal.
	leaf2 := testScriptSchnorrSig(ht.T, leafSigningKey)

	inclusionProof := leaf1.TapHash()
	tapscript := input.TapscriptPartialReveal(
		dummyInternalKey, leaf2, inclusionProof[:],
	)
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTapscriptInput(
		input.TaprootSignatureWitnessSize, tapscript,
	)
	estimator.AddP2WKHOutput()

	estimatedWeight := estimator.Weight()
	sigHash := txscript.SigHashDefault
	if len(sigHashType) != 0 {
		sigHash = sigHashType[0]

		// If a non-default sighash is used, then we'll need to add an
		// extra byte to account for the sighash that doesn't exist in
		// the default case.
		estimatedWeight++
	}

	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}

	// Before we actually sign, we want to make sure that we get an error
	// when we try to sign for a Taproot output without specifying all UTXO
	// information.
	signReq := &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:        utxoInfo[0],
			InputIndex:    0,
			KeyDesc:       keyDesc,
			Sighash:       uint32(sigHash),
			WitnessScript: leaf2.Script,
			SignMethod:    signMethodTapscript,
		}},
	}
	err = alice.RPC.SignOutputRawErr(signReq)
	require.Contains(
		ht, err.Error(), "error signing taproot output, transaction "+
			"input 0 is missing its previous outpoint information",
	)

	// We also want to make sure we get an error when we don't specify the
	// correct signing method.
	signReq = &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:        utxoInfo[0],
			InputIndex:    0,
			KeyDesc:       keyDesc,
			Sighash:       uint32(sigHash),
			WitnessScript: leaf2.Script,
		}},
		PrevOutputs: utxoInfo,
	}
	err = alice.RPC.SignOutputRawErr(signReq)
	require.Contains(
		ht, err.Error(), "selected sign method witness_v0 is not "+
			"compatible with given pk script 5120",
	)

	// Do the actual signing now.
	signReq = &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:        utxoInfo[0],
			InputIndex:    0,
			KeyDesc:       keyDesc,
			Sighash:       uint32(sigHash),
			WitnessScript: leaf2.Script,
			SignMethod:    signMethodTapscript,
		}},
		PrevOutputs: utxoInfo,
	}
	signResp := alice.RPC.SignOutputRaw(signReq)

	// We can now assemble the witness stack.
	controlBlockBytes, err := tapscript.ControlBlock.ToBytes()
	require.NoError(ht, err)

	sig := signResp.RawSigs[0]
	if len(sigHashType) != 0 {
		sig = append(sig, byte(sigHashType[0]))
	}
	tx.TxIn[0].Witness = wire.TxWitness{
		sig, leaf2.Script, controlBlockBytes,
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootSignOutputRawKeySpendBip86 tests that a tapscript address can
// also be spent using the key spend path through the SignOutputRaw RPC using a
// BIP0086 key spend only commitment.
func testTaprootSignOutputRawKeySpendBip86(ht *lntest.HarnessTest,
	alice *node.HarnessNode, sigHashType ...txscript.SigHashType) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}
	keyDesc := alice.RPC.DeriveNextKey(req)

	internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
	require.NoError(ht, err)

	// We want to make sure we can still use a tweaked key, even if it ends
	// up being essentially double tweaked because of the taproot root hash.
	dummyKeyTweak := sha256.Sum256([]byte("this is a key tweak"))
	internalKey = input.TweakPubKeyWithTweak(internalKey, dummyKeyTweak[:])

	// Our taproot key is a BIP0086 key spend only construction that just
	// commits to the internal key and no root hash.
	taprootKey := txscript.ComputeTaprootKeyNoScript(internalKey)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	sigHash := txscript.SigHashDefault
	if len(sigHashType) != 0 {
		sigHash = sigHashType[0]
	}

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTaprootKeySpendInput(sigHash)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}
	signReq := &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:      utxoInfo[0],
			InputIndex:  0,
			KeyDesc:     keyDesc,
			SingleTweak: dummyKeyTweak[:],
			Sighash:     uint32(sigHash),
			SignMethod:  signMethodBip86,
		}},
		PrevOutputs: utxoInfo,
	}
	signResp := alice.RPC.SignOutputRaw(signReq)

	sig := signResp.RawSigs[0]
	if len(sigHashType) != 0 {
		sig = append(sig, byte(sigHash))
	}
	tx.TxIn[0].Witness = wire.TxWitness{sig}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootSignOutputRawKeySpendRootHash tests that a tapscript address can
// also be spent using the key spend path through the SignOutputRaw RPC using a
// tapscript root hash.
func testTaprootSignOutputRawKeySpendRootHash(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}
	keyDesc := alice.RPC.DeriveNextKey(req)

	internalKey, err := btcec.ParsePubKey(keyDesc.RawKeyBytes)
	require.NoError(ht, err)

	// We want to make sure we can still use a tweaked key, even if it ends
	// up being essentially double tweaked because of the taproot root hash.
	dummyKeyTweak := sha256.Sum256([]byte("this is a key tweak"))
	internalKey = input.TweakPubKeyWithTweak(internalKey, dummyKeyTweak[:])

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	rootHash := leaf1.TapHash()
	taprootKey := txscript.ComputeTaprootOutputKey(internalKey, rootHash[:])

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}
	signReq := &signrpc.SignReq{
		RawTxBytes: buf.Bytes(),
		SignDescs: []*signrpc.SignDescriptor{{
			Output:      utxoInfo[0],
			InputIndex:  0,
			KeyDesc:     keyDesc,
			SingleTweak: dummyKeyTweak[:],
			Sighash:     uint32(txscript.SigHashDefault),
			TapTweak:    rootHash[:],
			SignMethod:  signMethodRootHash,
		}},
		PrevOutputs: utxoInfo,
	}
	signResp := alice.RPC.SignOutputRaw(signReq)
	tx.TxIn[0].Witness = wire.TxWitness{
		signResp.RawSigs[0],
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootMuSig2KeySpendBip86 tests that a combined MuSig2 key can also be
// used as a BIP-0086 key spend only key.
func testTaprootMuSig2KeySpendBip86(ht *lntest.HarnessTest,
	alice *node.HarnessNode, version signrpc.MuSig2Version) {

	// We're not going to commit to a script. So our taproot tweak will be
	// empty and just specify the necessary flag.
	taprootTweak := &signrpc.TaprootTweakDesc{
		KeySpendOnly: true,
	}

	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
		ht, alice, version,
	)
	_, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
		allPubKeys, version,
	)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}

	// We now need to create the raw sighash of the transaction, as that
	// will be the message we're signing collaboratively.
	prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
		utxoInfo[0].PkScript, utxoInfo[0].Value,
	)
	sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)

	sigHash, err := txscript.CalcTaprootSignatureHash(
		sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
	)
	require.NoError(ht, err)

	// Now that we have the transaction prepared, we need to start with the
	// signing. We simulate all three parties here, so we need to do
	// everything three times. But because we're going to use session 1 to
	// combine everything, we don't need its response, as it will store its
	// own signature.
	signReq := &signrpc.MuSig2SignRequest{
		SessionId:     sessResp1.SessionId,
		MessageDigest: sigHash,
	}
	alice.RPC.MuSig2Sign(signReq)

	signReq = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp2.SessionId,
		MessageDigest: sigHash,
		Cleanup:       true,
	}
	signResp2 := alice.RPC.MuSig2Sign(signReq)

	signReq = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp3.SessionId,
		MessageDigest: sigHash,
		Cleanup:       true,
	}
	signResp3 := alice.RPC.MuSig2Sign(signReq)

	// Luckily only one of the signers needs to combine the signature, so
	// let's do that now.
	combineReq := &signrpc.MuSig2CombineSigRequest{
		SessionId: sessResp1.SessionId,
		OtherPartialSignatures: [][]byte{
			signResp2.LocalPartialSignature,
			signResp3.LocalPartialSignature,
		},
	}
	combineResp := alice.RPC.MuSig2CombineSig(combineReq)
	require.Equal(ht, true, combineResp.HaveAllSignatures)
	require.NotEmpty(ht, combineResp.FinalSignature)

	sig, err := schnorr.ParseSignature(combineResp.FinalSignature)
	require.NoError(ht, err)
	require.True(ht, sig.Verify(sigHash, taprootKey))

	tx.TxIn[0].Witness = wire.TxWitness{
		combineResp.FinalSignature,
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootMuSig2KeySpendRootHash tests that a tapscript address can also be
// spent using a MuSig2 combined key.
func testTaprootMuSig2KeySpendRootHash(ht *lntest.HarnessTest,
	alice *node.HarnessNode, version signrpc.MuSig2Version) {

	// We're going to commit to a script as well. This is a hash lock with a
	// simple preimage of "foobar". We need to know this upfront so, we can
	// specify the taproot tweak with the root hash when creating the Musig2
	// signing session.
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))
	rootHash := leaf1.TapHash()
	taprootTweak := &signrpc.TaprootTweakDesc{
		ScriptRoot: rootHash[:],
	}

	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
		ht, alice, version,
	)
	_, taprootKey, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
		allPubKeys, version,
	)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTaprootKeySpendInput(txscript.SigHashDefault)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}

	// We now need to create the raw sighash of the transaction, as that
	// will be the message we're signing collaboratively.
	prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
		utxoInfo[0].PkScript, utxoInfo[0].Value,
	)
	sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)

	sigHash, err := txscript.CalcTaprootSignatureHash(
		sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
	)
	require.NoError(ht, err)

	// Now that we have the transaction prepared, we need to start with the
	// signing. We simulate all three parties here, so we need to do
	// everything three times. But because we're going to use session 1 to
	// combine everything, we don't need its response, as it will store its
	// own signature.
	req := &signrpc.MuSig2SignRequest{
		SessionId:     sessResp1.SessionId,
		MessageDigest: sigHash,
	}
	alice.RPC.MuSig2Sign(req)

	req = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp2.SessionId,
		MessageDigest: sigHash,
		Cleanup:       true,
	}
	signResp2 := alice.RPC.MuSig2Sign(req)

	req = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp3.SessionId,
		MessageDigest: sigHash,
		Cleanup:       true,
	}
	signResp3 := alice.RPC.MuSig2Sign(req)

	// Luckily only one of the signers needs to combine the signature, so
	// let's do that now.
	combineReq := &signrpc.MuSig2CombineSigRequest{
		SessionId: sessResp1.SessionId,
		OtherPartialSignatures: [][]byte{
			signResp2.LocalPartialSignature,
			signResp3.LocalPartialSignature,
		},
	}
	combineResp := alice.RPC.MuSig2CombineSig(combineReq)
	require.Equal(ht, true, combineResp.HaveAllSignatures)
	require.NotEmpty(ht, combineResp.FinalSignature)

	sig, err := schnorr.ParseSignature(combineResp.FinalSignature)
	require.NoError(ht, err)
	require.True(ht, sig.Verify(sigHash, taprootKey))

	tx.TxIn[0].Witness = wire.TxWitness{
		combineResp.FinalSignature,
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootMuSig2ScriptSpend tests that a tapscript address with an internal
// key that is a MuSig2 combined key can also be spent using the script path.
func testTaprootMuSig2ScriptSpend(ht *lntest.HarnessTest,
	alice *node.HarnessNode, version signrpc.MuSig2Version) {

	// We're going to commit to a script and spend the output using the
	// script. This is a hash lock with a simple preimage of "foobar". We
	// need to know this upfront so, we can specify the taproot tweak with
	// the root hash when creating the Musig2 signing session.
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))
	rootHash := leaf1.TapHash()
	taprootTweak := &signrpc.TaprootTweakDesc{
		ScriptRoot: rootHash[:],
	}

	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
		ht, alice, version,
	)
	internalKey, taprootKey, _, _, _ := createMuSigSessions(
		ht, alice, taprootTweak, keyDesc1, keyDesc2, keyDesc3,
		allPubKeys, version,
	)

	// Because we know the internal key and the script we want to spend, we
	// can now create the tapscript struct that's used for assembling the
	// control block and fee estimation.
	tapscript := input.TapscriptFullTree(internalKey, leaf1)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTapscriptInput(
		lntypes.WeightUnit(len([]byte("foobar"))+len(leaf1.Script)+1),
		tapscript,
	)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	// We can now assemble the witness stack.
	controlBlockBytes, err := tapscript.ControlBlock.ToBytes()
	require.NoError(ht, err)

	tx.TxIn[0].Witness = wire.TxWitness{
		[]byte("foobar"),
		leaf1.Script,
		controlBlockBytes,
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootMuSig2CombinedLeafKeySpend tests that a MuSig2 combined key can be
// used for an OP_CHECKSIG inside a tap script leaf spend.
func testTaprootMuSig2CombinedLeafKeySpend(ht *lntest.HarnessTest,
	alice *node.HarnessNode, version signrpc.MuSig2Version) {

	// We're using the combined MuSig2 key in a script leaf. So we need to
	// derive the combined key first, before we can build the script.
	keyDesc1, keyDesc2, keyDesc3, allPubKeys := deriveSigningKeys(
		ht, alice, version,
	)
	req := &signrpc.MuSig2CombineKeysRequest{
		AllSignerPubkeys: allPubKeys,
		Version:          version,
	}
	combineResp := alice.RPC.MuSig2CombineKeys(req)
	combinedPubKey, err := schnorr.ParsePubKey(combineResp.CombinedKey)
	require.NoError(ht, err)

	// We're going to commit to a script and spend the output using the
	// script. This is just an OP_CHECKSIG with the combined MuSig2 public
	// key.
	leaf := testScriptSchnorrSig(ht.T, combinedPubKey)
	tapscript := input.TapscriptPartialReveal(dummyInternalKey, leaf, nil)
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	// Spend the output again, this time back to a p2wkh address.
	p2wkhAddr, p2wkhPkScript := newAddrWithScript(
		ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	// Create fee estimation for a p2tr input and p2wkh output.
	feeRate := chainfee.SatPerKWeight(12500)
	estimator := input.TxWeightEstimator{}
	estimator.AddTapscriptInput(
		input.TaprootSignatureWitnessSize, tapscript,
	)
	estimator.AddP2WKHOutput()
	estimatedWeight := estimator.Weight()
	requiredFee := feeRate.FeeForWeight(estimatedWeight)

	tx := wire.NewMsgTx(2)
	tx.TxIn = []*wire.TxIn{{
		PreviousOutPoint: p2trOutpoint,
	}}
	value := int64(testAmount - requiredFee)
	tx.TxOut = []*wire.TxOut{{
		PkScript: p2wkhPkScript,
		Value:    value,
	}}

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	utxoInfo := []*signrpc.TxOut{{
		PkScript: p2trPkScript,
		Value:    testAmount,
	}}

	// Do the actual signing now.
	_, _, sessResp1, sessResp2, sessResp3 := createMuSigSessions(
		ht, alice, nil, keyDesc1, keyDesc2, keyDesc3, allPubKeys,
		version,
	)
	require.NoError(ht, err)

	// We now need to create the raw sighash of the transaction, as that
	// will be the message we're signing collaboratively.
	prevOutputFetcher := txscript.NewCannedPrevOutputFetcher(
		utxoInfo[0].PkScript, utxoInfo[0].Value,
	)
	sighashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)

	sigHash, err := txscript.CalcTapscriptSignaturehash(
		sighashes, txscript.SigHashDefault, tx, 0, prevOutputFetcher,
		leaf,
	)
	require.NoError(ht, err)

	// Now that we have the transaction prepared, we need to start with the
	// signing. We simulate all three parties here, so we need to do
	// everything three times. But because we're going to use session 1 to
	// combine everything, we don't need its response, as it will store its
	// own signature.
	signReq := &signrpc.MuSig2SignRequest{
		SessionId:     sessResp1.SessionId,
		MessageDigest: sigHash,
	}
	alice.RPC.MuSig2Sign(signReq)

	signReq = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp2.SessionId,
		MessageDigest: sigHash,
		Cleanup:       true,
	}
	signResp2 := alice.RPC.MuSig2Sign(signReq)

	// Before we have all partial signatures, we shouldn't get a final
	// signature back.
	combineReq := &signrpc.MuSig2CombineSigRequest{
		SessionId: sessResp1.SessionId,
		OtherPartialSignatures: [][]byte{
			signResp2.LocalPartialSignature,
		},
	}
	combineSigResp := alice.RPC.MuSig2CombineSig(combineReq)
	require.False(ht, combineSigResp.HaveAllSignatures)
	require.Empty(ht, combineSigResp.FinalSignature)

	signReq = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp3.SessionId,
		MessageDigest: sigHash,
	}
	signResp3 := alice.RPC.MuSig2Sign(signReq)

	// We manually clean up session 3, just to make sure that works as well.
	cleanReq := &signrpc.MuSig2CleanupRequest{
		SessionId: sessResp3.SessionId,
	}
	alice.RPC.MuSig2Cleanup(cleanReq)

	// A second call to that cleaned up session should now fail with a
	// specific error.
	signReq = &signrpc.MuSig2SignRequest{
		SessionId:     sessResp3.SessionId,
		MessageDigest: sigHash,
	}
	err = alice.RPC.MuSig2SignErr(signReq)
	require.Contains(ht, err.Error(), "not found")

	// Luckily only one of the signers needs to combine the signature, so
	// let's do that now.
	combineReq = &signrpc.MuSig2CombineSigRequest{
		SessionId: sessResp1.SessionId,
		OtherPartialSignatures: [][]byte{
			signResp3.LocalPartialSignature,
		},
	}
	combineResp1 := alice.RPC.MuSig2CombineSig(combineReq)
	require.Equal(ht, true, combineResp1.HaveAllSignatures)
	require.NotEmpty(ht, combineResp1.FinalSignature)

	sig, err := schnorr.ParseSignature(combineResp1.FinalSignature)
	require.NoError(ht, err)
	require.True(ht, sig.Verify(sigHash, combinedPubKey))

	// We can now assemble the witness stack.
	controlBlockBytes, err := tapscript.ControlBlock.ToBytes()
	require.NoError(ht, err)

	tx.TxIn[0].Witness = wire.TxWitness{
		combineResp1.FinalSignature,
		leaf.Script,
		controlBlockBytes,
	}

	// Serialize, weigh and publish the TX now, then make sure the
	// coins are sent and confirmed to the final sweep destination address.
	publishTxAndConfirmSweep(
		ht, alice, tx, estimatedWeight,
		&chainrpc.SpendRequest{
			Outpoint: &chainrpc.Outpoint{
				Hash:  p2trOutpoint.Hash[:],
				Index: p2trOutpoint.Index,
			},
			Script: p2trPkScript,
		},
		p2wkhAddr.String(),
	)
}

// testTaprootImportTapscriptScriptSpend tests importing p2tr script addresses
// using the script path with the full tree known.
func testTaprootImportTapscriptFullTree(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	_, internalKey, derivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	// Let's add a second script output as well to test the partial reveal.
	leaf2 := testScriptSchnorrSig(ht.T, internalKey)

	tapscript := input.TapscriptFullTree(internalKey, leaf1, leaf2)
	tree := txscript.AssembleTaprootScriptTree(leaf1, leaf2)
	rootHash := tree.RootNode.TapHash()
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Import the scripts and make sure we get the same address back as we
	// calculated ourselves.
	req := &walletrpc.ImportTapscriptRequest{
		InternalPublicKey: schnorr.SerializePubKey(internalKey),
		Script: &walletrpc.ImportTapscriptRequest_FullTree{
			FullTree: &walletrpc.TapscriptFullTree{
				AllLeaves: []*walletrpc.TapLeaf{{
					LeafVersion: uint32(
						leaf1.LeafVersion,
					),
					Script: leaf1.Script,
				}, {
					LeafVersion: uint32(
						leaf2.LeafVersion,
					),
					Script: leaf2.Script,
				}},
			},
		},
	}
	importResp := alice.RPC.ImportTapscript(req)

	calculatedAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)
	p2trOutputRPC := &lnrpc.OutPoint{
		TxidBytes:   p2trOutpoint.Hash[:],
		OutputIndex: p2trOutpoint.Index,
	}
	ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported")
	ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0)

	// Funding a PSBT from an imported script is not yet possible. So we
	// basically need to add all information manually for the wallet to be
	// able to sign for it.
	utxo := &wire.TxOut{
		Value:    testAmount,
		PkScript: p2trPkScript,
	}
	clearWalletImportedTapscriptBalance(
		ht, alice, utxo, p2trOutpoint, internalKey, derivationPath,
		rootHash[:],
	)
}

// testTaprootImportTapscriptPartialReveal tests importing p2tr script addresses
// for which we only know part of the tree.
func testTaprootImportTapscriptPartialReveal(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	_, internalKey, derivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	// Let's add a second script output as well to test the partial reveal.
	leaf2 := testScriptSchnorrSig(ht.T, internalKey)
	leaf2Hash := leaf2.TapHash()

	tapscript := input.TapscriptPartialReveal(
		internalKey, leaf1, leaf2Hash[:],
	)
	rootHash := tapscript.ControlBlock.RootHash(leaf1.Script)
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Import the scripts and make sure we get the same address back as we
	// calculated ourselves.
	req := &walletrpc.ImportTapscriptRequest{
		InternalPublicKey: schnorr.SerializePubKey(internalKey),
		Script: &walletrpc.ImportTapscriptRequest_PartialReveal{
			PartialReveal: &walletrpc.TapscriptPartialReveal{
				RevealedLeaf: &walletrpc.TapLeaf{
					LeafVersion: uint32(leaf1.LeafVersion),
					Script:      leaf1.Script,
				},
				FullInclusionProof: leaf2Hash[:],
			},
		},
	}
	importResp := alice.RPC.ImportTapscript(req)

	calculatedAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	p2trOutputRPC := &lnrpc.OutPoint{
		TxidBytes:   p2trOutpoint.Hash[:],
		OutputIndex: p2trOutpoint.Index,
	}
	ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported")
	ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0)

	// Funding a PSBT from an imported script is not yet possible. So we
	// basically need to add all information manually for the wallet to be
	// able to sign for it.
	utxo := &wire.TxOut{
		Value:    testAmount,
		PkScript: p2trPkScript,
	}
	clearWalletImportedTapscriptBalance(
		ht, alice, utxo, p2trOutpoint, internalKey, derivationPath,
		rootHash,
	)
}

// testTaprootImportTapscriptRootHashOnly tests importing p2tr script addresses
// for which we only know the root hash.
func testTaprootImportTapscriptRootHashOnly(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	_, internalKey, derivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))
	rootHash := leaf1.TapHash()

	tapscript := input.TapscriptRootHashOnly(internalKey, rootHash[:])
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Import the scripts and make sure we get the same address back as we
	// calculated ourselves.
	req := &walletrpc.ImportTapscriptRequest{
		InternalPublicKey: schnorr.SerializePubKey(internalKey),
		Script: &walletrpc.ImportTapscriptRequest_RootHashOnly{
			RootHashOnly: rootHash[:],
		},
	}
	importResp := alice.RPC.ImportTapscript(req)

	calculatedAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	p2trOutputRPC := &lnrpc.OutPoint{
		TxidBytes:   p2trOutpoint.Hash[:],
		OutputIndex: p2trOutpoint.Index,
	}
	ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported")
	ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0)

	// Funding a PSBT from an imported script is not yet possible. So we
	// basically need to add all information manually for the wallet to be
	// able to sign for it.
	utxo := &wire.TxOut{
		Value:    testAmount,
		PkScript: p2trPkScript,
	}
	clearWalletImportedTapscriptBalance(
		ht, alice, utxo, p2trOutpoint, internalKey, derivationPath,
		rootHash[:],
	)
}

// testTaprootImportTapscriptFullKey tests importing p2tr script addresses for
// which we only know the full Taproot key.
func testTaprootImportTapscriptFullKey(ht *lntest.HarnessTest,
	alice *node.HarnessNode) {

	// For the next step, we need a public key. Let's use a special family
	// for this.
	_, internalKey, derivationPath := deriveInternalKey(ht, alice)

	// Let's create a taproot script output now. This is a hash lock with a
	// simple preimage of "foobar".
	leaf1 := testScriptHashLock(ht.T, []byte("foobar"))

	tapscript := input.TapscriptFullTree(internalKey, leaf1)
	rootHash := leaf1.TapHash()
	taprootKey, err := tapscript.TaprootKey()
	require.NoError(ht, err)

	// Import the scripts and make sure we get the same address back as we
	// calculated ourselves.
	req := &walletrpc.ImportTapscriptRequest{
		InternalPublicKey: schnorr.SerializePubKey(taprootKey),
		Script: &walletrpc.ImportTapscriptRequest_FullKeyOnly{
			FullKeyOnly: true,
		},
	}
	importResp := alice.RPC.ImportTapscript(req)

	calculatedAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress)

	// Send some coins to the generated tapscript address.
	p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)

	p2trOutputRPC := &lnrpc.OutPoint{
		TxidBytes:   p2trOutpoint.Hash[:],
		OutputIndex: p2trOutpoint.Index,
	}
	ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported")
	ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0)

	// Funding a PSBT from an imported script is not yet possible. So we
	// basically need to add all information manually for the wallet to be
	// able to sign for it.
	utxo := &wire.TxOut{
		Value:    testAmount,
		PkScript: p2trPkScript,
	}
	clearWalletImportedTapscriptBalance(
		ht, alice, utxo, p2trOutpoint, internalKey, derivationPath,
		rootHash[:],
	)
}

// clearWalletImportedTapscriptBalance manually assembles and then attempts to
// sign a TX to sweep funds from an imported tapscript address.
func clearWalletImportedTapscriptBalance(ht *lntest.HarnessTest,
	hn *node.HarnessNode, utxo *wire.TxOut, outPoint wire.OutPoint,
	internalKey *btcec.PublicKey, derivationPath []uint32,
	rootHash []byte) {

	_, sweepPkScript := newAddrWithScript(
		ht, hn, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
	)

	output := &wire.TxOut{
		PkScript: sweepPkScript,
		Value:    utxo.Value - 1000,
	}
	packet, err := psbt.New(
		[]*wire.OutPoint{&outPoint}, []*wire.TxOut{output}, 2, 0,
		[]uint32{0},
	)
	require.NoError(ht, err)

	// We have everything we need to know to sign the PSBT.
	in := &packet.Inputs[0]
	in.Bip32Derivation = []*psbt.Bip32Derivation{{
		PubKey:    internalKey.SerializeCompressed(),
		Bip32Path: derivationPath,
	}}
	in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
		XOnlyPubKey: schnorr.SerializePubKey(internalKey),
		Bip32Path:   derivationPath,
	}}
	in.SighashType = txscript.SigHashDefault
	in.TaprootMerkleRoot = rootHash
	in.WitnessUtxo = utxo

	var buf bytes.Buffer
	require.NoError(ht, packet.Serialize(&buf))

	// Sign the manually funded PSBT now.
	signResp := hn.RPC.SignPsbt(&walletrpc.SignPsbtRequest{
		FundedPsbt: buf.Bytes(),
	})

	signedPacket, err := psbt.NewFromRawBytes(
		bytes.NewReader(signResp.SignedPsbt), false,
	)
	require.NoError(ht, err)

	// We should be able to finalize the PSBT and extract the sweep TX now.
	err = psbt.MaybeFinalizeAll(signedPacket)
	require.NoError(ht, err)

	sweepTx, err := psbt.Extract(signedPacket)
	require.NoError(ht, err)

	buf.Reset()
	err = sweepTx.Serialize(&buf)
	require.NoError(ht, err)

	// Publish the sweep transaction and then mine it as well.
	hn.RPC.PublishTransaction(&walletrpc.Transaction{
		TxHex: buf.Bytes(),
	})

	// Mine one block which should contain the sweep transaction.
	block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
	sweepTxHash := sweepTx.TxHash()
	ht.AssertTxInBlock(block, sweepTxHash)
}

// testScriptHashLock returns a simple bitcoin script that locks the funds to
// a hash lock of the given preimage.
func testScriptHashLock(t *testing.T, preimage []byte) txscript.TapLeaf {
	builder := txscript.NewScriptBuilder()
	builder.AddOp(txscript.OP_DUP)
	builder.AddOp(txscript.OP_HASH160)
	builder.AddData(btcutil.Hash160(preimage))
	builder.AddOp(txscript.OP_EQUALVERIFY)
	script1, err := builder.Script()
	require.NoError(t, err)
	return txscript.NewBaseTapLeaf(script1)
}

// testScriptSchnorrSig returns a simple bitcoin script that locks the funds to
// a Schnorr signature of the given public key.
func testScriptSchnorrSig(t *testing.T,
	pubKey *btcec.PublicKey) txscript.TapLeaf {

	builder := txscript.NewScriptBuilder()
	builder.AddData(schnorr.SerializePubKey(pubKey))
	builder.AddOp(txscript.OP_CHECKSIG)
	script2, err := builder.Script()
	require.NoError(t, err)
	return txscript.NewBaseTapLeaf(script2)
}

// newAddrWithScript returns a new address and its pkScript.
func newAddrWithScript(ht *lntest.HarnessTest, node *node.HarnessNode,
	addrType lnrpc.AddressType) (btcutil.Address, []byte) {

	p2wkhResp := node.RPC.NewAddress(&lnrpc.NewAddressRequest{
		Type: addrType,
	})
	p2wkhAddr, err := btcutil.DecodeAddress(
		p2wkhResp.Address, harnessNetParams,
	)
	require.NoError(ht, err)

	p2wkhPkScript, err := txscript.PayToAddrScript(p2wkhAddr)
	require.NoError(ht, err)

	return p2wkhAddr, p2wkhPkScript
}

// sendToTaprootOutput sends coins to a p2tr output of the given taproot key and
// mines a block to confirm the coins.
func sendToTaprootOutput(ht *lntest.HarnessTest, hn *node.HarnessNode,
	taprootKey *btcec.PublicKey) (wire.OutPoint, []byte) {

	tapScriptAddr, err := btcutil.NewAddressTaproot(
		schnorr.SerializePubKey(taprootKey), harnessNetParams,
	)
	require.NoError(ht, err)
	p2trPkScript, err := txscript.PayToAddrScript(tapScriptAddr)
	require.NoError(ht, err)

	// Send some coins to the generated tapscript address.
	req := &lnrpc.SendCoinsRequest{
		Addr:       tapScriptAddr.String(),
		Amount:     testAmount,
		TargetConf: 6,
	}
	hn.RPC.SendCoins(req)

	// Wait until the TX is found in the mempool.
	txid := ht.AssertNumTxsInMempool(1)[0]
	p2trOutputIndex := ht.GetOutputIndex(txid, tapScriptAddr.String())
	p2trOutpoint := wire.OutPoint{
		Hash:  txid,
		Index: uint32(p2trOutputIndex),
	}

	// Make sure the transaction is recognized by our wallet and has the
	// correct output type.
	var outputDetail *lnrpc.OutputDetail
	walletTxns := hn.RPC.GetTransactions(&lnrpc.GetTransactionsRequest{
		StartHeight: 0,
		EndHeight:   -1,
	})
	require.NotEmpty(ht, walletTxns.Transactions)
	for _, tx := range walletTxns.Transactions {
		if tx.TxHash != txid.String() {
			continue
		}

		for outputIdx, out := range tx.OutputDetails {
			if out.Address != tapScriptAddr.String() {
				continue
			}

			outputDetail = tx.OutputDetails[outputIdx]
			break
		}
	}
	require.NotNil(ht, outputDetail, "transaction not found in wallet")
	require.Equal(
		ht, lnrpc.OutputScriptType_SCRIPT_TYPE_WITNESS_V1_TAPROOT,
		outputDetail.OutputType,
	)

	// Clear the mempool.
	ht.MineBlocksAndAssertNumTxes(1, 1)

	return p2trOutpoint, p2trPkScript
}

// publishTxAndConfirmSweep is a helper function that publishes a transaction
// after checking its weight against an estimate. After asserting the given
// spend request, the given sweep address' balance is verified to be seen as
// funds belonging to the wallet.
func publishTxAndConfirmSweep(ht *lntest.HarnessTest, node *node.HarnessNode,
	tx *wire.MsgTx, estimatedWeight lntypes.WeightUnit,
	spendRequest *chainrpc.SpendRequest, sweepAddr string) {

	ht.Helper()

	// Before we publish the tx that spends the p2tr transaction, we want to
	// register a spend listener that we expect to fire after mining the
	// block.
	currentHeight := ht.CurrentHeight()

	// For a Taproot output we cannot leave the outpoint empty. Let's make
	// sure the API returns the correct error here.
	req := &chainrpc.SpendRequest{
		Script:     spendRequest.Script,
		HeightHint: currentHeight,
	}
	spendClient := node.RPC.RegisterSpendNtfn(req)

	// The error is only thrown when trying to read a message.
	_, err := spendClient.Recv()
	require.Contains(
		ht, err.Error(),
		"cannot register witness v1 spend request without outpoint",
	)

	// Now try again, this time with the outpoint set.
	req = &chainrpc.SpendRequest{
		Outpoint:   spendRequest.Outpoint,
		Script:     spendRequest.Script,
		HeightHint: currentHeight,
	}
	spendClient = node.RPC.RegisterSpendNtfn(req)

	var buf bytes.Buffer
	require.NoError(ht, tx.Serialize(&buf))

	// Since Schnorr signatures are fixed size, we must be able to estimate
	// the size of this transaction exactly.
	txWeight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
	require.EqualValues(ht, estimatedWeight, txWeight)

	txReq := &walletrpc.Transaction{
		TxHex: buf.Bytes(),
	}
	node.RPC.PublishTransaction(txReq)

	// Make sure the coins sent to the address are confirmed correctly,
	// including the confirmation notification.
	confirmAddress(ht, node, sweepAddr)

	// We now expect our spend event to go through.
	spendMsg, err := spendClient.Recv()
	require.NoError(ht, err)
	spend := spendMsg.GetSpend()
	require.NotNil(ht, spend)
	require.Equal(ht, spend.SpendingHeight, currentHeight+1)
}

// confirmAddress makes sure that a transaction in the mempool spends funds to
// the given address. It also checks that a confirmation notification for the
// address is triggered when the transaction is mined.
func confirmAddress(ht *lntest.HarnessTest, hn *node.HarnessNode,
	addrString string) {

	// Wait until the tx that sends to the address is found.
	txid := ht.AssertNumTxsInMempool(1)[0]

	// Wait until bob has seen the tx and considers it as owned.
	addrOutputIndex := ht.GetOutputIndex(txid, addrString)
	op := &lnrpc.OutPoint{
		TxidBytes:   txid[:],
		OutputIndex: uint32(addrOutputIndex),
	}
	ht.AssertUTXOInWallet(hn, op, "")

	// Before we confirm the transaction, let's register a confirmation
	// listener for it, which we expect to fire after mining a block.
	parsedAddr, err := btcutil.DecodeAddress(addrString, harnessNetParams)
	require.NoError(ht, err)
	addrPkScript, err := txscript.PayToAddrScript(parsedAddr)
	require.NoError(ht, err)

	currentHeight := ht.CurrentHeight()
	req := &chainrpc.ConfRequest{
		Script:       addrPkScript,
		Txid:         txid[:],
		HeightHint:   currentHeight,
		NumConfs:     1,
		IncludeBlock: true,
	}
	confClient := hn.RPC.RegisterConfirmationsNtfn(req)

	// Mine another block to clean up the mempool.
	ht.MineBlocksAndAssertNumTxes(1, 1)

	// We now expect our confirmation to go through, and also that the
	// block was specified.
	confMsg, err := confClient.Recv()
	require.NoError(ht, err)
	conf := confMsg.GetConf()
	require.NotNil(ht, conf)
	require.Equal(ht, conf.BlockHeight, currentHeight+1)
	require.NotNil(ht, conf.RawBlock)

	// We should also be able to decode the raw block.
	var blk wire.MsgBlock
	require.NoError(ht, blk.Deserialize(bytes.NewReader(conf.RawBlock)))
}

// deriveSigningKeys derives three signing keys and returns their descriptors,
// as well as the public keys in the Schnorr serialized format.
func deriveSigningKeys(ht *lntest.HarnessTest, node *node.HarnessNode,
	version signrpc.MuSig2Version) (*signrpc.KeyDescriptor,
	*signrpc.KeyDescriptor, *signrpc.KeyDescriptor, [][]byte) {

	// For muSig2 we need multiple keys. We derive three of them from the
	// same wallet, just so we know we can also sign for them again.
	req := &walletrpc.KeyReq{KeyFamily: testTaprootKeyFamily}
	keyDesc1 := node.RPC.DeriveNextKey(req)
	pubKey1, err := btcec.ParsePubKey(keyDesc1.RawKeyBytes)
	require.NoError(ht, err)

	keyDesc2 := node.RPC.DeriveNextKey(req)
	pubKey2, err := btcec.ParsePubKey(keyDesc2.RawKeyBytes)
	require.NoError(ht, err)

	keyDesc3 := node.RPC.DeriveNextKey(req)
	pubKey3, err := btcec.ParsePubKey(keyDesc3.RawKeyBytes)
	require.NoError(ht, err)

	// Now that we have all three keys we can create three sessions, one
	// for each of the signers. This would of course normally not happen on
	// the same node.
	var allPubKeys [][]byte
	switch version {
	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
		allPubKeys = [][]byte{
			schnorr.SerializePubKey(pubKey1),
			schnorr.SerializePubKey(pubKey2),
			schnorr.SerializePubKey(pubKey3),
		}

	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
		allPubKeys = [][]byte{
			pubKey1.SerializeCompressed(),
			pubKey2.SerializeCompressed(),
			pubKey3.SerializeCompressed(),
		}
	}

	return keyDesc1, keyDesc2, keyDesc3, allPubKeys
}

// createMuSigSessions creates a MuSig2 session with three keys that are
// combined into a single key. The same node is used for the three signing
// participants but a separate key is generated for each session. So the result
// should be the same as if it were three different nodes.
func createMuSigSessions(ht *lntest.HarnessTest, node *node.HarnessNode,
	taprootTweak *signrpc.TaprootTweakDesc,
	keyDesc1, keyDesc2, keyDesc3 *signrpc.KeyDescriptor,
	allPubKeys [][]byte, version signrpc.MuSig2Version) (*btcec.PublicKey,
	*btcec.PublicKey, *signrpc.MuSig2SessionResponse,
	*signrpc.MuSig2SessionResponse, *signrpc.MuSig2SessionResponse) {

	// Make sure that when not specifying a version we get an error, since
	// it is mandatory.
	err := node.RPC.MuSig2CreateSessionErr(&signrpc.MuSig2SessionRequest{})
	require.ErrorContains(ht, err, "unknown MuSig2 version")

	// Create the actual session with the version specified.
	sessResp1 := node.RPC.MuSig2CreateSession(&signrpc.MuSig2SessionRequest{
		KeyLoc:           keyDesc1.KeyLoc,
		AllSignerPubkeys: allPubKeys,
		TaprootTweak:     taprootTweak,
		Version:          version,
	})
	require.Equal(ht, version, sessResp1.Version)

	// Make sure the version is returned correctly.
	require.Equal(ht, version, sessResp1.Version)

	// Now that we have the three keys in a combined form, we want to make
	// sure the tweaking for the taproot key worked correctly. We first need
	// to parse the combined key without any tweaks applied to it. That will
	// be our internal key. Once we know that, we can tweak it with the
	// tapHash of the script root hash. We should arrive at the same result
	// as the API.
	combinedKey, err := schnorr.ParsePubKey(sessResp1.CombinedKey)
	require.NoError(ht, err)

	// When combining the key without creating a session, we expect the same
	// combined key to be created.
	expectedCombinedKey := combinedKey

	// Without a tweak, the internal key is equal to the combined key.
	internalKey := combinedKey

	// If there is a tweak, then there is the internal, pre-tweaked combined
	// key and the taproot key which is fully tweaked.
	if taprootTweak != nil {
		internalKey, err = schnorr.ParsePubKey(
			sessResp1.TaprootInternalKey,
		)
		require.NoError(ht, err)

		// We now know the taproot key. The session with the tweak
		// applied should produce the same key!
		expectedCombinedKey = txscript.ComputeTaprootOutputKey(
			internalKey, taprootTweak.ScriptRoot,
		)
		require.Equal(
			ht, schnorr.SerializePubKey(expectedCombinedKey),
			schnorr.SerializePubKey(combinedKey),
		)
	}

	// Same with the combine keys RPC, no version specified should give us
	// an error.
	err = node.RPC.MuSig2CombineKeysErr(&signrpc.MuSig2CombineKeysRequest{})
	require.ErrorContains(ht, err, "unknown MuSig2 version")

	// We should also get the same keys when just calling the
	// MuSig2CombineKeys RPC.
	combineReq := &signrpc.MuSig2CombineKeysRequest{
		AllSignerPubkeys: allPubKeys,
		TaprootTweak:     taprootTweak,
		Version:          version,
	}
	combineResp := node.RPC.MuSig2CombineKeys(combineReq)
	require.Equal(
		ht, schnorr.SerializePubKey(expectedCombinedKey),
		combineResp.CombinedKey,
	)
	require.Equal(
		ht, schnorr.SerializePubKey(internalKey),
		combineResp.TaprootInternalKey,
	)
	require.Equal(ht, version, combineResp.Version)

	// Everything is good so far, let's continue with creating the signing
	// session for the other two participants.
	req := &signrpc.MuSig2SessionRequest{
		KeyLoc:           keyDesc2.KeyLoc,
		AllSignerPubkeys: allPubKeys,
		OtherSignerPublicNonces: [][]byte{
			sessResp1.LocalPublicNonces,
		},
		TaprootTweak: taprootTweak,
		Version:      version,
	}
	sessResp2 := node.RPC.MuSig2CreateSession(req)
	require.Equal(ht, sessResp1.CombinedKey, sessResp2.CombinedKey)
	require.Equal(ht, version, sessResp2.Version)

	req = &signrpc.MuSig2SessionRequest{
		KeyLoc:           keyDesc3.KeyLoc,
		AllSignerPubkeys: allPubKeys,
		OtherSignerPublicNonces: [][]byte{
			sessResp1.LocalPublicNonces,
			sessResp2.LocalPublicNonces,
		},
		TaprootTweak: taprootTweak,
		Version:      version,
	}
	sessResp3 := node.RPC.MuSig2CreateSession(req)
	require.Equal(ht, sessResp2.CombinedKey, sessResp3.CombinedKey)
	require.Equal(ht, version, sessResp3.Version)
	require.Equal(ht, true, sessResp3.HaveAllNonces)

	// We need to distribute the rest of the nonces.
	nonceReq := &signrpc.MuSig2RegisterNoncesRequest{
		SessionId: sessResp1.SessionId,
		OtherSignerPublicNonces: [][]byte{
			sessResp2.LocalPublicNonces,
			sessResp3.LocalPublicNonces,
		},
	}
	nonceResp1 := node.RPC.MuSig2RegisterNonces(nonceReq)
	require.True(ht, nonceResp1.HaveAllNonces)

	nonceReq = &signrpc.MuSig2RegisterNoncesRequest{
		SessionId: sessResp2.SessionId,
		OtherSignerPublicNonces: [][]byte{
			sessResp3.LocalPublicNonces,
		},
	}
	nonceResp2 := node.RPC.MuSig2RegisterNonces(nonceReq)
	require.True(ht, nonceResp2.HaveAllNonces)

	return internalKey, combinedKey, sessResp1, sessResp2, sessResp3
}

// testTaprootCoopClose asserts that if both peers signal ShutdownAnySegwit,
// then a taproot closing addr is used. Otherwise, we shouldn't expect one to
// be used.
func testTaprootCoopClose(ht *lntest.HarnessTest) {
	// We'll start by making two new nodes, and funding a channel between
	// them.
	carol := ht.NewNode("Carol", nil)
	ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)

	dave := ht.NewNode("Dave", nil)
	ht.EnsureConnected(carol, dave)

	chanAmt := funding.MaxBtcFundingAmount
	pushAmt := btcutil.Amount(100000)
	satPerVbyte := btcutil.Amount(1)

	// We'll now open a channel between Carol and Dave.
	chanPoint := ht.OpenChannel(
		carol, dave, lntest.OpenChannelParams{
			Amt:         chanAmt,
			PushAmt:     pushAmt,
			SatPerVByte: satPerVbyte,
		},
	)

	// We'll now close out the channel and obtain the closing TXID.
	closingTxid := ht.CloseChannel(carol, chanPoint)

	// assertTaprootDeliveryUsed returns true if a Taproot addr was used in
	// the co-op close transaction.
	assertTaprootDeliveryUsed := func(closingTxid chainhash.Hash) bool {
		tx := ht.GetRawTransaction(closingTxid)
		for _, txOut := range tx.MsgTx().TxOut {
			if !txscript.IsPayToTaproot(txOut.PkScript) {
				return false
			}
		}

		return true
	}

	// We expect that the closing transaction only has P2TR addresses.
	require.True(ht, assertTaprootDeliveryUsed(closingTxid),
		"taproot addr not used!")

	// Now we'll bring Eve into the mix, Eve is running older software that
	// doesn't understand Taproot.
	eveArgs := []string{"--protocol.no-any-segwit"}
	eve := ht.NewNode("Eve", eveArgs)
	ht.EnsureConnected(carol, eve)

	// We'll now open up a chanel again between Carol and Eve.
	chanPoint = ht.OpenChannel(
		carol, eve, lntest.OpenChannelParams{
			Amt:         chanAmt,
			PushAmt:     pushAmt,
			SatPerVByte: satPerVbyte,
		},
	)

	// We'll now close out this channel and expect that no Taproot
	// addresses are used in the co-op close transaction.
	closingTxid = ht.CloseChannel(carol, chanPoint)
	require.False(ht, assertTaprootDeliveryUsed(closingTxid),
		"taproot addr shouldn't be used!")
}

// testMuSig2CombineKey makes sure that combining a key with MuSig2 returns the
// correct result according to the MuSig2 version specified.
func testMuSig2CombineKey(ht *lntest.HarnessTest, alice *node.HarnessNode,
	version signrpc.MuSig2Version) {

	testVector040Key1 := hexDecode(
		"F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BCE0" +
			"36F9",
	)
	testVector040Key2 := hexDecode(
		"DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502B" +
			"A659",
	)
	testVector040Key3 := hexDecode(
		"3590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D038" +
			"CA66",
	)

	testVector100Key1 := hexDecode(
		"02F9308A019258C31049344F85F89D5229B531C845836F99B08601F113BC" +
			"E036F9",
	)
	testVector100Key2 := hexDecode(
		"03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B50" +
			"2BA659",
	)
	testVector100Key3 := hexDecode(
		"023590A94E768F8E1815C2F24B4D80A8E3149316C3518CE7B7AD338368D0" +
			"38CA66",
	)

	var allPubKeys [][]byte
	switch version {
	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
		allPubKeys = [][]byte{
			testVector040Key1, testVector040Key2, testVector040Key3,
		}

	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
		allPubKeys = [][]byte{
			testVector100Key1, testVector100Key2, testVector100Key3,
		}
	}

	resp := alice.RPC.MuSig2CombineKeys(&signrpc.MuSig2CombineKeysRequest{
		AllSignerPubkeys: allPubKeys,
		TaprootTweak: &signrpc.TaprootTweakDesc{
			KeySpendOnly: true,
		},
		Version: version,
	})

	expectedFinalKey040 := hexDecode(
		"5b257b4e785d61157ef5303051f45184bd5cb47bc4b4069ed4dd453645" +
			"9cb83b",
	)
	expectedPreTweakKey040 := hexDecode(
		"d70cd69a2647f7390973df48cbfa2ccc407b8b2d60b08c5f1641185c79" +
			"98a290",
	)

	expectedFinalKey100 := hexDecode(
		"79e6c3e628c9bfbce91de6b7fb28e2aec7713d377cf260ab599dcbc40e54" +
			"2312",
	)
	expectedPreTweakKey100 := hexDecode(
		"789d937bade6673538f3e28d8368dda4d0512f94da44cf477a505716d26a" +
			"1575",
	)

	switch version {
	case signrpc.MuSig2Version_MUSIG2_VERSION_V040:
		require.Equal(ht, expectedFinalKey040, resp.CombinedKey)
		require.Equal(
			ht, expectedPreTweakKey040, resp.TaprootInternalKey,
		)

	case signrpc.MuSig2Version_MUSIG2_VERSION_V100RC2:
		require.Equal(ht, expectedFinalKey100, resp.CombinedKey)
		require.Equal(
			ht, expectedPreTweakKey100, resp.TaprootInternalKey,
		)
	}
}
