From 26cd100cf290de3b765e93ee391a1d8b3c59a122 Mon Sep 17 00:00:00 2001 From: MPins Date: Fri, 14 Nov 2025 12:00:03 -0300 Subject: [PATCH 1/2] zpay32: enforce low-S signature when `n` is defined Enforce low-S canonical signatures when n is defined and include test vectors to validate the new behavior. --- zpay32/decode.go | 4 ++++ zpay32/invoice_test.go | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/zpay32/decode.go b/zpay32/decode.go index 577f6a6d13d..dd96646f91b 100644 --- a/zpay32/decode.go +++ b/zpay32/decode.go @@ -186,6 +186,10 @@ func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) ( return nil, fmt.Errorf("unable to deserialize "+ "signature: %v", err) } + // Ensure the signature is in canonical low-S form. + if err = ecdsa.VerifyLowS(sig.ToSignatureBytes()); err != nil { + return nil, err + } if !signature.Verify(hash, decodedInvoice.Destination) { return nil, fmt.Errorf("invalid invoice signature") } diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go index bfa1539f3ec..7d871690008 100644 --- a/zpay32/invoice_test.go +++ b/zpay32/invoice_test.go @@ -22,6 +22,7 @@ import ( "github.com/stretchr/testify/require" ) +//nolint:ll var ( testMillisat24BTC = lnwire.MilliSatoshi(2400000000000) testMillisat2500uBTC = lnwire.MilliSatoshi(250000000) @@ -61,6 +62,9 @@ var ( testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734") testPrivKey, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes) + testHighSPubKeyBytes, _ = hex.DecodeString("02d0139ce7427d6dfffd26a326c18be754ef1e64672b42694ba5b23ef6e6e7803d") + testHighSPubKey, _ = btcec.ParsePubKey(testHighSPubKeyBytes) + testDescriptionHashSlice = chainhash.HashB([]byte("One piece of chocolate cake, one icecream cone, one pickle, one slice of swiss cheese, one slice of salami, one lollypop, one piece of cherry pie, one sausage, one cupcake, and one slice of watermelon")) testExpiry0 = time.Duration(0) * time.Second @@ -898,6 +902,35 @@ func TestDecodeEncode(t *testing.T) { WithErrorOnUnknownFeatureBit(), }, }, + { + // Invoice with high-S signature and Public-key + // recovery. + encodedInvoice: "lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap2r09nt4ndd0unm3z9u5t48y6ucv4r5sg7lk98c77ctvjczkspk5qprc90gx", + valid: true, + skipEncoding: true, + decodedInvoice: func() *Invoice { + return &Invoice{ + Net: &chaincfg.MainNetParams, + Timestamp: time.Unix(1496314658, 0), + PaymentHash: &testPaymentHash, + PaymentAddr: fn.Some(specPaymentAddr), + Description: &testPleaseConsider, + Destination: testHighSPubKey, + Features: lnwire.NewFeatureVector( + lnwire.NewRawFeatureVector( + 8, 14, + ), + lnwire.Features, + ), + } + }, + }, + { + // Invoice with high-S signature and 'n' tagged field + // for destination pubkey. + encodedInvoice: "lnbc25m1p70xwfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsp5cfzp9ugllvk03rltd6hvndxj26ux6gcxc5azyxk060rj9tzghct5zvjlps76gx8wpq5yuu79688k8gnm2c0al6v608s96l0xzrrlqqwnzxmu", + valid: false, + }, } for i, test := range tests { From a118638bbf5b1ce7dad6f5755fd78fed49fd7f1e Mon Sep 17 00:00:00 2001 From: MPins Date: Fri, 14 Nov 2025 12:11:47 -0300 Subject: [PATCH 2/2] docs: release-notes-0.21.0 --- docs/release-notes/release-notes-0.21.0.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes/release-notes-0.21.0.md b/docs/release-notes/release-notes-0.21.0.md index 754fedc20a4..e3d8f8a435b 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -303,6 +303,9 @@ # Technical and Architectural Updates ## BOLT Spec Updates +LND now [enforces](https://github.com/lightning/bolts/pull/1284) low-S canonical +signatures when `n` field is present in a BOLT11 invoice. + ## Testing * [Added unit tests for TLV length validation across multiple packages](https://github.com/lightningnetwork/lnd/pull/10249). @@ -435,3 +438,4 @@ * Pins * Suheb * Ziggie +* Pins