zpay32: add support for P2TR fallback addresses
What changed, and why it matters
This commit adds support for a new type of Bitcoin fallback address (P2TR, also known as Taproot) when encoding and decoding Lightning Network payment invoices. Previously, LND could not include or read Taproot fallback addresses in invoices. This is a feature addition rather than a clear security fix, but missing support could have caused payment failures or forced users onto older address types when Taproot was preferred.
Treat as a routine feature addition. Reviewers should verify that NewAddressTaproot rejects invalid witness program lengths (32 bytes only) and that the witness version constant matches BOLT 11 spec. No urgent security action is indicated by the diff alone.
Security signals we found
Adds Taproot (P2TR) fallback address handling to BOLT 11 invoice codec
Replaces hardcoded fallback address version constants with named equivalents
No bounds checks or validation rules were relaxed in the diff
Feature gap in fallback address types is closed
Evidence from the diff
The change updates the zpay32 invoice codec to recognize txscript.TaprootWitnessVersion (version 1) as a valid fallback address version during decode, and to emit a Taproot fallback address during encode. It also replaces magic numbers (0, 17, 18) with named constants. The decode path now calls btcutil.NewAddressTaproot for version 1 witness programs, and the encode path selects the witness version for *btcutil.AddressTaproot. No validation logic for other versions was removed or weakened.
Changed components
zpay32/decode.gozpay32/encode.goBOLT 11 invoice fallback address parsing and serializationInspect captured patch +24 / −5
diff --git a/zpay32/decode.go b/zpay32/decode.go
index f97c778..577f6a6 100644
--- a/zpay32/decode.go
+++ b/zpay32/decode.go
@@ -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.
@@ -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
@@ -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
@@ -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
diff --git a/zpay32/encode.go b/zpay32/encode.go
index 43ccd5e..50f294e 100644
--- a/zpay32/encode.go
+++ b/zpay32/encode.go
@@ -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")
}
Why this scored 37/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.