mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-11-10 14:17:56 +01:00
Merge pull request #9975 from MPins/issue-9591
Add Support for P2TR Fallback Addresses in BOLT-11
This commit is contained in:
@@ -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
|
reflect their state on the channel commitment without having to send or receive
|
||||||
a certain amount of msats.
|
a certain amount of msats.
|
||||||
|
|
||||||
|
- Added support for [P2TR Fallback Addresses](
|
||||||
|
https://github.com/lightningnetwork/lnd/pull/9975) in BOLT-11 invoices.
|
||||||
|
|
||||||
## Functional Enhancements
|
## Functional Enhancements
|
||||||
* [Add](https://github.com/lightningnetwork/lnd/pull/9677)
|
* [Add](https://github.com/lightningnetwork/lnd/pull/9677)
|
||||||
`ConfirmationsUntilActive` and `ConfirmationHeight` field to the
|
`ConfirmationsUntilActive` and `ConfirmationHeight` field to the
|
||||||
|
|||||||
@@ -15,10 +15,18 @@ import (
|
|||||||
"github.com/btcsuite/btcd/btcutil/bech32"
|
"github.com/btcsuite/btcd/btcutil/bech32"
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/lightningnetwork/lnd/fn/v2"
|
"github.com/lightningnetwork/lnd/fn/v2"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
fallbackVersionWitness = txscript.BaseSegwitWitnessVersion
|
||||||
|
fallbackVersionTaproot = txscript.TaprootWitnessVersion
|
||||||
|
fallbackVersionPubkeyHash = 17
|
||||||
|
fallbackVersionScriptHash = 18
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrInvalidUTF8Description is returned if the invoice description is
|
// ErrInvalidUTF8Description is returned if the invoice description is
|
||||||
// not valid UTF-8.
|
// not valid UTF-8.
|
||||||
@@ -529,7 +537,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro
|
|||||||
|
|
||||||
version := data[0]
|
version := data[0]
|
||||||
switch version {
|
switch version {
|
||||||
case 0:
|
case fallbackVersionWitness:
|
||||||
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
witness, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -548,7 +556,16 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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)
|
pubKeyHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -558,7 +575,7 @@ func parseFallbackAddr(data []byte, net *chaincfg.Params) (btcutil.Address, erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case 18:
|
case fallbackVersionScriptHash:
|
||||||
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
scriptHash, err := bech32.ConvertBits(data[1:], 5, 8, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -202,13 +202,15 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
|
|||||||
var version byte
|
var version byte
|
||||||
switch addr := invoice.FallbackAddr.(type) {
|
switch addr := invoice.FallbackAddr.(type) {
|
||||||
case *btcutil.AddressPubKeyHash:
|
case *btcutil.AddressPubKeyHash:
|
||||||
version = 17
|
version = fallbackVersionPubkeyHash
|
||||||
case *btcutil.AddressScriptHash:
|
case *btcutil.AddressScriptHash:
|
||||||
version = 18
|
version = fallbackVersionScriptHash
|
||||||
case *btcutil.AddressWitnessPubKeyHash:
|
case *btcutil.AddressWitnessPubKeyHash:
|
||||||
version = addr.WitnessVersion()
|
version = addr.WitnessVersion()
|
||||||
case *btcutil.AddressWitnessScriptHash:
|
case *btcutil.AddressWitnessScriptHash:
|
||||||
version = addr.WitnessVersion()
|
version = addr.WitnessVersion()
|
||||||
|
case *btcutil.AddressTaproot:
|
||||||
|
version = addr.WitnessVersion()
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown fallback address type")
|
return fmt.Errorf("unknown fallback address type")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -593,19 +593,34 @@ func TestParseFallbackAddr(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
testAddrTestnetData, _ := bech32.ConvertBits(testAddrTestnet.ScriptAddress(), 8, 5, true)
|
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)
|
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)
|
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)
|
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)
|
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 {
|
tests := []struct {
|
||||||
data []byte
|
data []byte
|
||||||
@@ -651,6 +666,17 @@ func TestParseFallbackAddr(t *testing.T) {
|
|||||||
valid: true,
|
valid: true,
|
||||||
result: testAddrMainnetP2WSH,
|
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 {
|
for i, test := range tests {
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ var (
|
|||||||
testAddrMainnetP2SH, _ = btcutil.DecodeAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", &chaincfg.MainNetParams)
|
testAddrMainnetP2SH, _ = btcutil.DecodeAddress("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX", &chaincfg.MainNetParams)
|
||||||
testAddrMainnetP2WPKH, _ = btcutil.DecodeAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", &chaincfg.MainNetParams)
|
testAddrMainnetP2WPKH, _ = btcutil.DecodeAddress("bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4", &chaincfg.MainNetParams)
|
||||||
testAddrMainnetP2WSH, _ = btcutil.DecodeAddress("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", &chaincfg.MainNetParams)
|
testAddrMainnetP2WSH, _ = btcutil.DecodeAddress("bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", &chaincfg.MainNetParams)
|
||||||
|
testAddrMainnetP2TR, _ = btcutil.DecodeAddress("bc1pptdvg0d2nj99568"+
|
||||||
|
"qn6ssdy4cygnwuxgw2ukmnwgwz7jpqjz2kszse2s3lm",
|
||||||
|
&chaincfg.MainNetParams)
|
||||||
|
|
||||||
testHopHintPubkeyBytes1, _ = hex.DecodeString("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255")
|
testHopHintPubkeyBytes1, _ = hex.DecodeString("029e03a901b85534ff1e92c43c74431f7ce72046060fcf7a95c37e148f78c77255")
|
||||||
testHopHintPubkey1, _ = btcec.ParsePubKey(testHopHintPubkeyBytes1)
|
testHopHintPubkey1, _ = btcec.ParsePubKey(testHopHintPubkeyBytes1)
|
||||||
@@ -299,8 +302,14 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Ignore unknown witness version in fallback address.
|
// Ignore unknown witness version in fallback address.
|
||||||
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsfpppw508d6qejxtdg4y5r3zarvary0c5xw7k8txqv6x0a75xuzp0zsdzk5hq6tmfgweltvs6jk5nhtyd9uqksvr48zga9mw08667w8264gkspluu66jhtcmct36nx363km6cquhhv2cpc6q43r",
|
encodedInvoice: "lnbc20m1pvjluezpp5qqqsyqcyq5rqwzqfqq" +
|
||||||
valid: true,
|
"qsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan" +
|
||||||
|
"79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrq" +
|
||||||
|
"sfp4z6yn92zrp97a6q5hhh8swys7uf4hm9tr8a0xylnk" +
|
||||||
|
"26fvkg3jx0sdsxvma0zvf2h0pycyyzdrmjncq6lzrfuw" +
|
||||||
|
"xfhv6gzz4q5303n3up6as4ghe5qthg7x20z7vae8w5rq" +
|
||||||
|
"u6de3g4jl7kvuap3qedprqsqqmgqqm6s8sl",
|
||||||
|
valid: true,
|
||||||
decodedInvoice: func() *Invoice {
|
decodedInvoice: func() *Invoice {
|
||||||
return &Invoice{
|
return &Invoice{
|
||||||
Net: &chaincfg.MainNetParams,
|
Net: &chaincfg.MainNetParams,
|
||||||
@@ -649,6 +658,74 @@ func TestDecodeEncode(t *testing.T) {
|
|||||||
i.Destination = nil
|
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
|
// Send 2500uBTC for a cup of coffee with a custom CLTV
|
||||||
// expiry value.
|
// expiry value.
|
||||||
|
|||||||
Reference in New Issue
Block a user