Skip to content

Commit 9aa70e4

Browse files
committed
Merge branch '0-19-3-branch-rc1-10119' into 0-19-3-branch-rc1
2 parents f275fd6 + 100feb7 commit 9aa70e4

File tree

6 files changed

+173
-3
lines changed

6 files changed

+173
-3
lines changed

docs/release-notes/release-notes-0.19.3.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@
3333
can cause contract resolvers to be stuck at marking the channel force close as
3434
being complete.
3535

36+
- [Fixed a bug in `btcwallet` that caused issues with Tapscript addresses being
37+
imported in a watch-only (e.g. remote-signing)
38+
setup](https://github.com/lightningnetwork/lnd/pull/10119).
39+
3640
# New Features
3741

3842
## Functional Enhancements
@@ -84,4 +88,5 @@
8488

8589
* Elle Mouton
8690
* Olaoluwa Osuntokun
91+
* Oliver Gugger
8792
* Yong Yu

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ require (
1111
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
1212
github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
1313
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b
14-
github.com/btcsuite/btcwallet v0.16.14
14+
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3
1515
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
1616
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
1717
github.com/btcsuite/btcwallet/walletdb v1.5.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
6262
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b h1:MQ+Q6sDy37V1wP1Yu79A5KqJutolqUGwA99UZWQDWZM=
6363
github.com/btcsuite/btclog/v2 v2.0.1-0.20250728225537-6090e87c6c5b/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
6464
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
65-
github.com/btcsuite/btcwallet v0.16.14 h1:CofysgmI1ednkLsXontAdBoXJkbiim7unXnFKhLLjnE=
66-
github.com/btcsuite/btcwallet v0.16.14/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
65+
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3 h1:MAjNRpj3XhCOrhchq4wq0qI34TIBX/DCnT6OLWejx68=
66+
github.com/btcsuite/btcwallet v0.16.15-0.20250805011126-a3632ae48ab3/go.mod h1:H6dfoZcWPonM2wbVsR2ZBY0PKNZKdQyLAmnX8vL9JFA=
6767
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
6868
github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
6969
github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=

itest/lnd_psbt_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,14 @@ func testFundPsbt(ht *lntest.HarnessTest) {
10601060
alice := ht.NewNodeWithCoins("Alice", nil)
10611061
bob := ht.NewNodeWithCoins("Bob", nil)
10621062

1063+
runFundPsbt(ht, alice, bob)
1064+
}
1065+
1066+
// runFundPsbt tests the FundPsbt RPC use case where we want to fund a PSBT
1067+
// that already has an input specified. This is a pay-join scenario where Bob
1068+
// wants to send Alice some coins, but he wants to do so in a way that doesn't
1069+
// reveal the full amount he is sending.
1070+
func runFundPsbt(ht *lntest.HarnessTest, alice, bob *node.HarnessNode) {
10631071
// We test a pay-join between Alice and Bob. Bob wants to send Alice
10641072
// 5 million Satoshis in a non-obvious way. So Bob selects a UTXO that's
10651073
// bigger than 5 million Satoshis and expects the change minus the send

itest/lnd_remote_signer_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ var remoteSignerTestCases = []*lntest.TestCase{
2727
Name: "account import",
2828
TestFunc: testRemoteSignerAccountImport,
2929
},
30+
{
31+
Name: "tapscript import",
32+
TestFunc: testRemoteSignerTapscriptImport,
33+
},
3034
{
3135
Name: "channel open",
3236
TestFunc: testRemoteSignerChannelOpen,
@@ -228,6 +232,24 @@ func testRemoteSignerAccountImport(ht *lntest.HarnessTest) {
228232
tc.fn(ht, watchOnly, carol)
229233
}
230234

235+
func testRemoteSignerTapscriptImport(ht *lntest.HarnessTest) {
236+
tc := remoteSignerTestCase{
237+
name: "tapscript import",
238+
sendCoins: true,
239+
fn: func(tt *lntest.HarnessTest, wo, carol *node.HarnessNode) {
240+
testTaprootImportTapscriptFullTree(ht, wo)
241+
testTaprootImportTapscriptPartialReveal(ht, wo)
242+
testTaprootImportTapscriptRootHashOnly(ht, wo)
243+
testTaprootImportTapscriptFullKey(ht, wo)
244+
245+
testTaprootImportTapscriptFullKeyFundPsbt(ht, wo)
246+
},
247+
}
248+
249+
_, watchOnly, carol := prepareRemoteSignerTest(ht, tc)
250+
tc.fn(ht, watchOnly, carol)
251+
}
252+
231253
func testRemoteSignerChannelOpen(ht *lntest.HarnessTest) {
232254
tc := remoteSignerTestCase{
233255
name: "basic channel open close",
@@ -328,6 +350,11 @@ func testRemoteSignerPSBT(ht *lntest.HarnessTest) {
328350
// that aren't in the wallet. But we also want to make
329351
// sure we can fund and then sign PSBTs from our wallet.
330352
runFundAndSignPsbt(ht, wo)
353+
354+
// We also have a more specific funding test that does
355+
// a pay-join payment with Carol.
356+
ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
357+
runFundPsbt(ht, wo, carol)
331358
},
332359
}
333360

itest/lnd_taproot_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ func testTaprootImportScripts(ht *lntest.HarnessTest) {
9090
testTaprootImportTapscriptPartialReveal(ht, alice)
9191
testTaprootImportTapscriptRootHashOnly(ht, alice)
9292
testTaprootImportTapscriptFullKey(ht, alice)
93+
94+
testTaprootImportTapscriptFullKeyFundPsbt(ht, alice)
9395
}
9496

9597
// testTaprootSendCoinsKeySpendBip86 tests sending to and spending from
@@ -1359,6 +1361,134 @@ func testTaprootImportTapscriptFullKey(ht *lntest.HarnessTest,
13591361
)
13601362
}
13611363

1364+
// testTaprootImportTapscriptFullKeyFundPsbt tests importing p2tr script
1365+
// addresses for which we only know the full Taproot key. We also test that we
1366+
// can use such an imported script to fund a PSBT.
1367+
func testTaprootImportTapscriptFullKeyFundPsbt(ht *lntest.HarnessTest,
1368+
alice *node.HarnessNode) {
1369+
1370+
// For the next step, we need a public key. Let's use a special family
1371+
// for this.
1372+
_, internalKey, derivationPath := deriveInternalKey(ht, alice)
1373+
1374+
// Let's create a taproot script output now. This is a hash lock with a
1375+
// simple preimage of "foobar".
1376+
leaf1 := testScriptHashLock(ht.T, []byte("foobar"))
1377+
1378+
tapscript := input.TapscriptFullTree(internalKey, leaf1)
1379+
rootHash := leaf1.TapHash()
1380+
taprootKey, err := tapscript.TaprootKey()
1381+
require.NoError(ht, err)
1382+
1383+
// Import the scripts and make sure we get the same address back as we
1384+
// calculated ourselves.
1385+
req := &walletrpc.ImportTapscriptRequest{
1386+
InternalPublicKey: schnorr.SerializePubKey(taprootKey),
1387+
Script: &walletrpc.ImportTapscriptRequest_FullKeyOnly{
1388+
FullKeyOnly: true,
1389+
},
1390+
}
1391+
importResp := alice.RPC.ImportTapscript(req)
1392+
1393+
calculatedAddr, err := btcutil.NewAddressTaproot(
1394+
schnorr.SerializePubKey(taprootKey), harnessNetParams,
1395+
)
1396+
require.NoError(ht, err)
1397+
require.Equal(ht, calculatedAddr.String(), importResp.P2TrAddress)
1398+
1399+
// Send some coins to the generated tapscript address.
1400+
p2trOutpoint, p2trPkScript := sendToTaprootOutput(ht, alice, taprootKey)
1401+
1402+
p2trOutputRPC := &lnrpc.OutPoint{
1403+
TxidBytes: p2trOutpoint.Hash[:],
1404+
OutputIndex: p2trOutpoint.Index,
1405+
}
1406+
ht.AssertUTXOInWallet(alice, p2trOutputRPC, "imported")
1407+
ht.AssertWalletAccountBalance(alice, "imported", testAmount, 0)
1408+
1409+
// We now fund a PSBT that spends the imported tapscript address.
1410+
utxo := &wire.TxOut{
1411+
Value: testAmount,
1412+
PkScript: p2trPkScript,
1413+
}
1414+
_, sweepPkScript := newAddrWithScript(
1415+
ht, alice, lnrpc.AddressType_WITNESS_PUBKEY_HASH,
1416+
)
1417+
1418+
output := &wire.TxOut{
1419+
PkScript: sweepPkScript,
1420+
Value: 1,
1421+
}
1422+
packet, err := psbt.New(
1423+
[]*wire.OutPoint{&p2trOutpoint}, []*wire.TxOut{output}, 2, 0,
1424+
[]uint32{0},
1425+
)
1426+
require.NoError(ht, err)
1427+
1428+
// We have everything we need to know to sign the PSBT.
1429+
in := &packet.Inputs[0]
1430+
in.Bip32Derivation = []*psbt.Bip32Derivation{{
1431+
PubKey: internalKey.SerializeCompressed(),
1432+
Bip32Path: derivationPath,
1433+
}}
1434+
in.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{{
1435+
XOnlyPubKey: schnorr.SerializePubKey(internalKey),
1436+
Bip32Path: derivationPath,
1437+
}}
1438+
in.SighashType = txscript.SigHashDefault
1439+
in.TaprootMerkleRoot = rootHash[:]
1440+
in.WitnessUtxo = utxo
1441+
1442+
var buf bytes.Buffer
1443+
require.NoError(ht, packet.Serialize(&buf))
1444+
1445+
change := &walletrpc.PsbtCoinSelect_ExistingOutputIndex{
1446+
ExistingOutputIndex: 0,
1447+
}
1448+
fundResp := alice.RPC.FundPsbt(&walletrpc.FundPsbtRequest{
1449+
Template: &walletrpc.FundPsbtRequest_CoinSelect{
1450+
CoinSelect: &walletrpc.PsbtCoinSelect{
1451+
Psbt: buf.Bytes(),
1452+
ChangeOutput: change,
1453+
},
1454+
},
1455+
Fees: &walletrpc.FundPsbtRequest_SatPerVbyte{
1456+
SatPerVbyte: 1,
1457+
},
1458+
})
1459+
1460+
// Sign the manually funded PSBT now.
1461+
signResp := alice.RPC.SignPsbt(&walletrpc.SignPsbtRequest{
1462+
FundedPsbt: fundResp.FundedPsbt,
1463+
})
1464+
1465+
signedPacket, err := psbt.NewFromRawBytes(
1466+
bytes.NewReader(signResp.SignedPsbt), false,
1467+
)
1468+
require.NoError(ht, err)
1469+
1470+
// We should be able to finalize the PSBT and extract the sweep TX now.
1471+
err = psbt.MaybeFinalizeAll(signedPacket)
1472+
require.NoError(ht, err)
1473+
1474+
sweepTx, err := psbt.Extract(signedPacket)
1475+
require.NoError(ht, err)
1476+
1477+
buf.Reset()
1478+
err = sweepTx.Serialize(&buf)
1479+
require.NoError(ht, err)
1480+
1481+
// Publish the sweep transaction and then mine it as well.
1482+
alice.RPC.PublishTransaction(&walletrpc.Transaction{
1483+
TxHex: buf.Bytes(),
1484+
})
1485+
1486+
// Mine one block which should contain the sweep transaction.
1487+
block := ht.MineBlocksAndAssertNumTxes(1, 1)[0]
1488+
sweepTxHash := sweepTx.TxHash()
1489+
ht.AssertTxInBlock(block, sweepTxHash)
1490+
}
1491+
13621492
// clearWalletImportedTapscriptBalance manually assembles and then attempts to
13631493
// sign a TX to sweep funds from an imported tapscript address.
13641494
func clearWalletImportedTapscriptBalance(ht *lntest.HarnessTest,

0 commit comments

Comments
 (0)