Skip to content

Commit bb57b0c

Browse files
committed
zpay32: reject invoices with high-S signatures when n is set
This change enforces low-S canonical signatures when `n` is present and adds the corresponding test vectors.
1 parent 841a291 commit bb57b0c

File tree

2 files changed

+41
-0
lines changed

2 files changed

+41
-0
lines changed

zpay32/decode.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/binary"
66
"errors"
77
"fmt"
8+
"math/big"
89
"strings"
910
"time"
1011
"unicode/utf8"
@@ -186,6 +187,13 @@ func Decode(invoice string, net *chaincfg.Params, opts ...DecodeOption) (
186187
return nil, fmt.Errorf("unable to deserialize "+
187188
"signature: %v", err)
188189
}
190+
// Ensure the signature is in canonical low-S form.
191+
sValue := new(big.Int).SetBytes(sigBase256[32:64])
192+
halfOrder := new(big.Int).Rsh(btcec.S256().N, 1)
193+
if sValue.Cmp(halfOrder) > 0 {
194+
return nil, fmt.Errorf("signature is non-canonical " +
195+
"(high-S)")
196+
}
189197
if !signature.Verify(hash, decodedInvoice.Destination) {
190198
return nil, fmt.Errorf("invalid invoice signature")
191199
}

zpay32/invoice_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/stretchr/testify/require"
2323
)
2424

25+
//nolint:ll
2526
var (
2627
testMillisat24BTC = lnwire.MilliSatoshi(2400000000000)
2728
testMillisat2500uBTC = lnwire.MilliSatoshi(250000000)
@@ -61,6 +62,9 @@ var (
6162
testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
6263
testPrivKey, testPubKey = btcec.PrivKeyFromBytes(testPrivKeyBytes)
6364

65+
testHighSPubKeyBytes, _ = hex.DecodeString("02d0139ce7427d6dfffd26a326c18be754ef1e64672b42694ba5b23ef6e6e7803d")
66+
testHighSPubKey, _ = btcec.ParsePubKey(testHighSPubKeyBytes)
67+
6468
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"))
6569

6670
testExpiry0 = time.Duration(0) * time.Second
@@ -898,6 +902,35 @@ func TestDecodeEncode(t *testing.T) {
898902
WithErrorOnUnknownFeatureBit(),
899903
},
900904
},
905+
{
906+
// Invoice with high-S signature and Public-key
907+
// recovery.
908+
encodedInvoice: "lnbc1pvjluezsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygspp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq9qrsgq357wnc5r2ueh7ck6q93dj32dlqnls087fxdwk8qakdyafkq3yap2r09nt4ndd0unm3z9u5t48y6ucv4r5sg7lk98c77ctvjczkspk5qprc90gx",
909+
valid: true,
910+
skipEncoding: true,
911+
decodedInvoice: func() *Invoice {
912+
return &Invoice{
913+
Net: &chaincfg.MainNetParams,
914+
Timestamp: time.Unix(1496314658, 0),
915+
PaymentHash: &testPaymentHash,
916+
PaymentAddr: fn.Some(specPaymentAddr),
917+
Description: &testPleaseConsider,
918+
Destination: testHighSPubKey,
919+
Features: lnwire.NewFeatureVector(
920+
lnwire.NewRawFeatureVector(
921+
8, 14,
922+
),
923+
lnwire.Features,
924+
),
925+
}
926+
},
927+
},
928+
{
929+
// Invoice with high-S signature and 'n' tagged field
930+
// for destination pubkey.
931+
encodedInvoice: "lnbc25m1p70xwfzpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66sp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygs9qrsgqsp5zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsp5cfzp9ugllvk03rltd6hvndxj26ux6gcxc5azyxk060rj9tzghct5zvjlps76gx8wpq5yuu79688k8gnm2c0al6v608s96l0xzrrlqqwnzxmu",
932+
valid: false,
933+
},
901934
}
902935

903936
for i, test := range tests {

0 commit comments

Comments
 (0)