Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/release-notes/release-notes-0.19.3.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
messages simultaneously. The fix ensures only a single goroutine processes the
backlog at any given time using an atomic flag.

- [Fixed a bug in `btcwallet` that caused issues with Tapscript addresses being
imported in a watch-only (e.g. remote-signing)
setup](https://github.com/lightningnetwork/lnd/pull/10119).

# New Features

## Functional Enhancements
Expand Down Expand Up @@ -71,4 +75,5 @@
# Contributors (Alphabetical Order)

* Olaoluwa Osuntokun
* Oliver Gugger
* Yong Yu
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b
github.com/btcsuite/btcwallet v0.16.14
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
github.com/btcsuite/btcwallet/walletdb v1.5.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM=
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.16.14 h1:CofysgmI1ednkLsXontAdBoXJkbiim7unXnFKhLLjnE=
github.com/btcsuite/btcwallet v0.16.14/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3 h1:MAjNRpj3XhCOrhchq4wq0qI34TIBX/DCnT6OLWejx68=
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
Expand Down
8 changes: 8 additions & 0 deletions itest/lnd_psbt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,14 @@ func testFundPsbt(ht *lntest.HarnessTest) {
alice := ht.NewNodeWithCoins("Alice", nil)
bob := ht.NewNodeWithCoins("Bob", nil)

runFundPsbt(ht, alice, bob)
}

// runFundPsbt tests the FundPsbt RPC use case where we want to fund a PSBT
// that already has an input specified. This is a pay-join scenario where Bob
// wants to send Alice some coins, but he wants to do so in a way that doesn't
// reveal the full amount he is sending.
func runFundPsbt(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) {
// We test a pay-join between Alice and Bob. Bob wants to send Alice
// 5 million Satoshis in a non-obvious way. So Bob selects a UTXO that's
// bigger than 5 million Satoshis and expects the change minus the send
Expand Down
27 changes: 27 additions & 0 deletions itest/lnd_remote_signer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var remoteSignerTestCases = []*lntest.TestCase{
Name: "account import",
TestFunc: testRemoteSignerAccountImport,
},
{
Name: "tapscript import",
TestFunc: testRemoteSignerTapscriptImport,
},
{
Name: "channel open",
TestFunc: testRemoteSignerChannelOpen,
Expand Down Expand Up @@ -228,6 +232,24 @@ func testRemoteSignerAccountImport(ht *lntest.HarnessTest) {
tc.fn(ht, watchOnly, carol)
}

func testRemoteSignerTapscriptImport(ht *lntest.HarnessTest) {
tc := remoteSignerTestCase{
name: "tapscript import",
sendCoins: true,
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
testTaprootImportTapscriptFullTree(ht, wo)
testTaprootImportTapscriptPartialReveal(ht, wo)
testTaprootImportTapscriptRootHashOnly(ht, wo)
testTaprootImportTapscriptFullKey(ht, wo)

testTaprootImportTapscriptFullKeyFundPsbt(ht, wo)
},
}

_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
tc.fn(ht, watchOnly, carol)
}

func testRemoteSignerChannelOpen(ht *lntest.HarnessTest) {
tc := remoteSignerTestCase{
name: "basic channel open close",
Expand Down Expand Up @@ -328,6 +350,11 @@ func testRemoteSignerPSBT(ht *lntest.HarnessTest) {
// that aren't in the wallet. But we also want to make
// sure we can fund and then sign PSBTs from our wallet.
runFundAndSignPsbt(ht, wo)

// We also have a more specific funding test that does
// a pay-join payment with Carol.
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
runFundPsbt(ht, wo, carol)
},
}

Expand Down
130 changes: 130 additions & 0 deletions itest/lnd_taproot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ func testTaprootImportScripts(ht *lntest.HarnessTest) {
testTaprootImportTapscriptPartialReveal(ht, alice)
testTaprootImportTapscriptRootHashOnly(ht, alice)
testTaprootImportTapscriptFullKey(ht, alice)

testTaprootImportTapscriptFullKeyFundPsbt(ht, alice)
}

// testTaprootSendCoinsKeySpendBip86 tests sending to and spending from
Expand Down Expand Up @@ -1359,6 +1361,134 @@ func testTaprootImportTapscriptFullKey(ht *lntest.HarnessTest,
)
}

// testTaprootImportTapscriptFullKeyFundPsbt tests importing p2tr script
// addresses for which we only know the full Taproot key. We also test that we
// can use such an imported script to fund a PSBT.
func testTaprootImportTapscriptFullKeyFundPsbt(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)

// We now fund a PSBT that spends the imported tapscript address.
utxo := &wire.TxOut{
Value: testAmount,
PkScript: p2trPkScript,
}
_, sweepPkScript := newAddrWithScript(
ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
)

output := &wire.TxOut{
PkScript: sweepPkScript,
Value: 1,
}
packet, err := psbt.New(
[]*wire.OutPoint{&p2trOutpoint}, []*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))

change := &walletrpc.PsbtCoinSelect_ExistingOutputIndex{
ExistingOutputIndex: 0,
}
fundResp := alice.RPC.FundPsbt(&walletrpc.FundPsbtRequest{
Template: &walletrpc.FundPsbtRequest_CoinSelect{
CoinSelect: &walletrpc.PsbtCoinSelect{
Psbt: buf.Bytes(),
ChangeOutput: change,
},
},
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
SatPerVbyte: 1,
},
})

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

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.
alice.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)
}

// clearWalletImportedTapscriptBalance manually assembles and then attempts to
// sign a TX to sweep funds from an imported tapscript address.
func clearWalletImportedTapscriptBalance(ht *lntest.HarnessTest,
Expand Down
Loading