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
3 changes: 3 additions & 0 deletions docs/release-notes/release-notes-0.20.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ when the appropriate TLV flag is set. This allows for HTLCs carrying metadata to
reflect their state on the channel commitment without having to send or receive
a certain amount of msats.

- Added support for [P2TR Fallback Addresses](
https://github.com/lightningnetwork/lnd/pull/9975) in BOLT-11 invoices.

## Functional Enhancements
* [Add](https://github.com/lightningnetwork/lnd/pull/9677)
`ConfirmationsUntilActive` and `ConfirmationHeight` field to the
Expand Down
23 changes: 20 additions & 3 deletions zpay32/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ import (
"github.com/btcsuite/btcd/btcutil/bech32"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/lightningnetwork/lnd/fn/v2"
"github.com/lightningnetwork/lnd/lnwire"
)

const (
fallbackVersionWitness = txscript.BaseSegwitWitnessVersion
fallbackVersionTaproot = txscript.TaprootWitnessVersion
fallbackVersionPubkeyHash = 17
fallbackVersionScriptHash = 18
)

var (
// ErrInvalidUTF8Description is returned if the invoice description is
// not valid UTF-8.
Expand Down Expand Up @@ -529,7 +537,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro

version := data[0]
switch version {
case 0:
case fallbackVersionWitness:
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
Expand All @@ -548,7 +556,16 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro
if err != nil {
return nil, err
}
case 17:
case fallbackVersionTaproot:
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
}
addr, err = btcutil.NewAddressTaproot(witness, net)
if err != nil {
return nil, err
}
case fallbackVersionPubkeyHash:
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
Expand All @@ -558,7 +575,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro
if err != nil {
return nil, err
}
case 18:
case fallbackVersionScriptHash:
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
if err != nil {
return nil, err
Expand Down
6 changes: 4 additions & 2 deletions zpay32/encode.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,15 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
var version byte
switch addr := invoice.FallbackAddr.(type) {
case *btcutil.AddressPubKeyHash:
version = 17
version = fallbackVersionPubkeyHash
case *btcutil.AddressScriptHash:
version = 18
version = fallbackVersionScriptHash
case *btcutil.AddressWitnessPubKeyHash:
version = addr.WitnessVersion()
case *btcutil.AddressWitnessScriptHash:
version = addr.WitnessVersion()
case *btcutil.AddressTaproot:
version = addr.WitnessVersion()
default:
return fmt.Errorf("unknown fallback address type")
}
Expand Down
36 changes: 31 additions & 5 deletions zpay32/invoice_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -593,19 +593,34 @@ func TestParseFallbackAddr(t *testing.T) {
t.Parallel()

testAddrTestnetData, _ := bech32.ConvertBits(testAddrTestnet.ScriptAddress(), 8, 5, true)
testAddrTestnetDataWithVersion := append([]byte{17}, testAddrTestnetData...)
testAddrTestnetDataWithVersion := append(
[]byte{fallbackVersionPubkeyHash}, testAddrTestnetData...,
)

testRustyAddrData, _ := bech32.ConvertBits(testRustyAddr.ScriptAddress(), 8, 5, true)
testRustyAddrDataWithVersion := append([]byte{17}, testRustyAddrData...)
testRustyAddrDataWithVersion := append(
[]byte{fallbackVersionPubkeyHash}, testRustyAddrData...,
)

testAddrMainnetP2SHData, _ := bech32.ConvertBits(testAddrMainnetP2SH.ScriptAddress(), 8, 5, true)
testAddrMainnetP2SHDataWithVersion := append([]byte{18}, testAddrMainnetP2SHData...)
testAddrMainnetP2SHDataWithVersion := append(
[]byte{fallbackVersionScriptHash}, testAddrMainnetP2SHData...,
)

testAddrMainnetP2WPKHData, _ := bech32.ConvertBits(testAddrMainnetP2WPKH.ScriptAddress(), 8, 5, true)
testAddrMainnetP2WPKHDataWithVersion := append([]byte{0}, testAddrMainnetP2WPKHData...)
testAddrMainnetP2WPKHDataWithVersion := append(
[]byte{fallbackVersionWitness}, testAddrMainnetP2WPKHData...,
)

testAddrMainnetP2WSHData, _ := bech32.ConvertBits(testAddrMainnetP2WSH.ScriptAddress(), 8, 5, true)
testAddrMainnetP2WSHDataWithVersion := append([]byte{0}, testAddrMainnetP2WSHData...)
testAddrMainnetP2WSHDataWithVersion := append(
[]byte{fallbackVersionWitness}, testAddrMainnetP2WSHData...,
)

testAddrMainnetP2TRData, _ := bech32.ConvertBits(
testAddrMainnetP2TR.ScriptAddress(), 8, 5, true)
testAddrMainnetP2TRDataWithVersion := append(
[]byte{fallbackVersionTaproot}, testAddrMainnetP2TRData...)

tests := []struct {
data []byte
Expand Down Expand Up @@ -651,6 +666,17 @@ func TestParseFallbackAddr(t *testing.T) {
valid: true,
result: testAddrMainnetP2WSH,
},
{
data: testAddrMainnetP2TRDataWithVersion,
net: &chaincfg.MainNetParams,
valid: true,
result: testAddrMainnetP2TR,
},
{
data: testAddrMainnetP2TRDataWithVersion[:10],
net: &chaincfg.MainNetParams,
valid: false, // data too short for P2TR address
},
}

for i, test := range tests {
Expand Down
81 changes: 79 additions & 2 deletions zpay32/invoice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ var (
testAddrMainnetP2SH, _ = btcutil.DecodeAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", &chaincfg.MainNetParams)
testAddrMainnetP2WPKH, _ = btcutil.DecodeAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", &chaincfg.MainNetParams)
testAddrMainnetP2WSH, _ = btcutil.DecodeAddress("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", &chaincfg.MainNetParams)
testAddrMainnetP2TR, _ = btcutil.DecodeAddress("bc1pptdvg0d2nj99568"+
"qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm",
&chaincfg.MainNetParams)

testHopHintPubkeyBytes1, _ = hex.DecodeString("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255")
testHopHintPubkey1, _ = btcec.ParsePubKey(testHopHintPubkeyBytes1)
Expand Down Expand Up @@ -299,8 +302,14 @@ func TestDecodeEncode(t *testing.T) {
},
{
// Ignore unknown witness version in fallback address.
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpppw508d6qejxtdg4y5r3zarvary0c5xw7k8txqv6x0a75xuzp0zsdzk5hq6tmfgweltvs6jk5nhtyd9uqksvr48zga9mw08667w8264gkspluu66jhtcmct36nx363km6cquhhv2cpc6q43r",
valid: true,
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqq" +
"qsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" +
"79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrq" +
"sfp4z6yn92zrp97a6q5hhh8swys7uf4hm9tr8a0xylnk" +
"26fvkg3jx0sdsxvma0zvf2h0pycyyzdrmjncq6lzrfuw" +
"xfhv6gzz4q5303n3up6as4ghe5qthg7x20z7vae8w5rq" +
"u6de3g4jl7kvuap3qedprqsqqmgqqm6s8sl",
valid: true,
decodedInvoice: func() *Invoice {
return &Invoice{
Net: &chaincfg.MainNetParams,
Expand Down Expand Up @@ -649,6 +658,74 @@ func TestDecodeEncode(t *testing.T) {
i.Destination = nil
},
},
{
// On mainnet, with fallback (p2tr) address "bc1pptdvg0d
// 2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm"
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqq" +
"qsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" +
"79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrq" +
"sfp4pptdvg0d2nj99568qn6ssdy4cygnwuxgw2ukmnwg" +
"wz7jpqjz2kszs9zs3tmcpgulwc0ruwc2cm97udy6sdfe" +
"nwvha8qlkfwx49sgk40kze4kwsh706rae3uc30ltpwpw" +
"mjyhc3uan4ljz56wksg5gsnhrrhcqsrq93d",

valid: true,
decodedInvoice: func() *Invoice {
return &Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2TR,
Features: emptyFeatures,
}
},
beforeEncoding: func(i *Invoice) {
// Since this destination pubkey was recovered
// from the signature, we must set it nil before
// encoding to get back the same invoice string.
i.Destination = nil
},
},
{
// On mainnet, with fallback (p2tr) address "bc1pptdvg0d
// 2nj99568qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm"
// using the test vector payment from BOLT 11
encodedInvoice: "lnbc20m1pvjluezsp5zyg3zyg3zyg3zyg3zy" +
"g3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqc" +
"yq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqyp" +
"qhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98kly" +
"sy043l2ahrqsfp4pptdvg0d2nj99568qn6ssdy4cygnw" +
"uxgw2ukmnwgwz7jpqjz2kszs9qrsgqy606dznq28exny" +
"dt2r4c29y56xjtn3sk4mhgjtl4pg2y4ar3249rq4ajlm" +
"j9jy8zvlzw7cr8mggqzm842xfr0v72rswzq9xvr4hknf" +
"sqwmn6xd",

valid: true,
decodedInvoice: func() *Invoice {
return &Invoice{
Net: &chaincfg.MainNetParams,
MilliSat: &testMillisat20mBTC,
Timestamp: time.Unix(1496314658, 0),
PaymentHash: &testPaymentHash,
DescriptionHash: &testDescriptionHash,
Destination: testPubKey,
FallbackAddr: testAddrMainnetP2TR,
Features: lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(
8, 14,
),
lnwire.Features,
),
PaymentAddr: fn.Some(specPaymentAddr),
}
},
// Skip encoding since LND encode the tagged fields
// in a different order.
skipEncoding: true,
},
{
// Send 2500uBTC for a cup of coffee with a custom CLTV
// expiry value.
Expand Down
Loading