zpay32: add test case for P2TR fallback addresses
What changed, and why it matters
This commit only adds new test cases for handling Taproot (P2TR) fallback addresses in Lightning invoices. It does not change any production code, fix a bug, or alter security behavior. The change is purely to ensure the existing code correctly recognizes and processes a newer Bitcoin address type in tests.
No security action required. This is a test-only change. Reviewers may optionally verify that the production code already supports P2TR fallback addresses and that the new tests pass.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit modifies two test files in the zpay32 package. It replaces hardcoded fallback address version bytes with named constants (fallbackVersionPubkeyHash, fallbackVersionScriptHash, fallbackVersionWitness, fallbackVersionTaproot), adds a P2TR test address and test cases in TestParseFallbackAddr, and adds round-trip decode/encode test vectors for BOLT 11 invoices containing a P2TR fallback address. No production logic in invoice parsing or encoding is changed.
Changed components
zpay32/invoice_internal_test.gozpay32/invoice_test.goInspect captured patch +110 / −7
diff --git a/zpay32/invoice_internal_test.go b/zpay32/invoice_internal_test.go
index 0a41b4c..22434a9 100644
--- a/zpay32/invoice_internal_test.go
+++ b/zpay32/invoice_internal_test.go
@@ -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
@@ -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 {
diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go
index 4cb1a61..bfa1539 100644
--- a/zpay32/invoice_test.go
+++ b/zpay32/invoice_test.go
@@ -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)
@@ -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,
@@ -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.
Why this scored 15/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.