From 83d081a54e2b3636afb43c91d2777d0ce5b66717 Mon Sep 17 00:00:00 2001 From: Lrifton92 Date: Wed, 1 Jul 2026 02:37:34 +0200 Subject: [PATCH] btcec/schnorr: reject s >= group order in ParseSignature ParseSignature documents that it enforces the BIP-340 requirement that the s component lie in the range [0, n-1], and the r component is already checked against the field prime accordingly. However, the overflow return value of s.SetByteSlice was discarded, so an s value greater than or equal to the group order n was silently reduced modulo n and accepted instead of being rejected. This restores parity with the reference implementation in decred/dcrd/dcrec/secp256k1/schnorr (of which this code is a port) and with Bitcoin Core, both of which reject such encodings at parse time. The ErrSigSTooBig error kind is already defined in the imported schnorr package but was previously unused here. A regression test covering the s == n and s > n encodings is added; it fails before this change (the signatures are silently accepted) and passes after. Signed-off-by: Lrifton92 --- btcec/schnorr/signature.go | 5 ++++- btcec/schnorr/signature_test.go | 38 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/btcec/schnorr/signature.go b/btcec/schnorr/signature.go index fbfe1615..05bc808f 100644 --- a/btcec/schnorr/signature.go +++ b/btcec/schnorr/signature.go @@ -91,7 +91,10 @@ func ParseSignature(sig []byte) (*Signature, error) { return nil, signatureError(ecdsa_schnorr.ErrSigRTooBig, str) } var s btcec.ModNScalar - s.SetByteSlice(sig[32:64]) + if overflow := s.SetByteSlice(sig[32:64]); overflow { + str := "invalid signature: s >= group order" + return nil, signatureError(ecdsa_schnorr.ErrSigSTooBig, str) + } // Return the signature. return NewSignature(&r, &s), nil diff --git a/btcec/schnorr/signature_test.go b/btcec/schnorr/signature_test.go index 9e99bbe2..409c1f3d 100644 --- a/btcec/schnorr/signature_test.go +++ b/btcec/schnorr/signature_test.go @@ -291,3 +291,41 @@ func TestSchnorrSignNoMutate(t *testing.T) { t.Fatalf("private key modified: %v", err) } } + +// TestParseSignatureComponentRange ensures ParseSignature enforces the BIP-340 +// range restrictions on both the r and s components, in particular that an s +// value greater than or equal to the group order n is rejected rather than +// silently reduced modulo n. +func TestParseSignatureComponentRange(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + sig string + err error + }{ + { + name: "r == p", + sig: "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d09", + err: ecdsa_schnorr.ErrSigRTooBig, + }, + { + name: "s == n", + sig: "4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + err: ecdsa_schnorr.ErrSigSTooBig, + }, + { + name: "s > n", + sig: "4e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd41fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364142", + err: ecdsa_schnorr.ErrSigSTooBig, + }, + } + + for _, test := range tests { + _, err := ParseSignature(decodeHex(test.sig)) + if !errors.Is(err, test.err) { + t.Errorf("%s: mismatched err -- got %v, want %v", + test.name, err, test.err) + } + } +}