From d31fe5213dee08e4331c0c2f6d9c2c3d4d826bdd 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 43efbd928b5c9c021db0e0ca82c98f5bcc1b0e52 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 04b4290e26f..e099c03010d 100644 --- a/docs/release-notes/release-notes-0.21.0.md +++ b/docs/release-notes/release-notes-0.21.0.md @@ -61,6 +61,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 ## Database @@ -78,3 +81,4 @@ * Boris Nagaev * Elle Mouton * Nishant Bansal +* Pins