123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971 |
- 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/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.Miner.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.Miner.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 := int64(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 := int64(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 := int64(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 := int64(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 := int64(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 := int64(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(
- len([]byte("foobar"))+len(leaf1.Script)+1, tapscript,
- )
- estimator.AddP2WKHOutput()
- estimatedWeight := int64(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 := int64(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.Miner.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.Miner.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 int64,
- 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.Miner.GetBestBlock()
- // 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: uint32(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: uint32(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.Equal(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, uint32(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.Miner.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.Miner.GetBestBlock()
- req := &chainrpc.ConfRequest{
- Script: addrPkScript,
- Txid: txid[:],
- HeightHint: uint32(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, uint32(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.Miner.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,
- )
- }
- }
|