Skip to content

Commit f8dfe21

Browse files
committed
ecdsa: add VerifyLowS helper and tests
Introduce a VerifyLowS helper to detect non-canonical high-S ECDSA signatures, along with unit tests.
1 parent 538fc9f commit f8dfe21

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

btcec/ecdsa/signature.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
var (
1919
errNegativeValue = errors.New("value may be interpreted as negative")
2020
errExcessivelyPaddedValue = errors.New("value is excessively padded")
21+
errHighS = errors.New("signature is not canonical due to unnecessarily high S value")
22+
errNoHeaderMagic = errors.New("malformed signature: no header magic")
2123
)
2224

2325
// Signature is a type representing an ecdsa signature.
@@ -90,7 +92,7 @@ func parseSig(sigStr []byte, der bool) (*Signature, error) {
9092
// 0x30
9193
index := 0
9294
if sigStr[index] != 0x30 {
93-
return nil, errors.New("malformed signature: no header magic")
95+
return nil, errNoHeaderMagic
9496
}
9597
index++
9698
// length of remaining message
@@ -254,3 +256,20 @@ func RecoverCompact(signature, hash []byte) (*btcec.PublicKey, bool, error) {
254256
func Sign(key *btcec.PrivateKey, hash []byte) *Signature {
255257
return secp_ecdsa.Sign(key, hash)
256258
}
259+
260+
// VerifyLowS verifies that the given ECDSA signature is strictly DER-encoded
261+
// and uses a canonical low-S value. It returns nil if the signature is valid;
262+
// otherwise it returns the encountered error.
263+
func VerifyLowS(sigStr []byte) error {
264+
sig, err := parseSig(sigStr, true)
265+
if err != nil {
266+
return err
267+
}
268+
sValue := sig.S()
269+
if sValue.IsOverHalfOrder() {
270+
// High-S, s > N/2.
271+
return errHighS
272+
}
273+
// Low-S, s <= N/2.
274+
return nil
275+
}

btcec/ecdsa/signature_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616

1717
"github.com/btcsuite/btcd/btcec/v2"
18+
"github.com/stretchr/testify/require"
1819
)
1920

2021
type signatureTest struct {
@@ -800,3 +801,32 @@ func TestPrivKeys(t *testing.T) {
800801
}
801802
}
802803
}
804+
805+
func TestVerifyLowS(t *testing.T) {
806+
signatureTests := []struct {
807+
name string
808+
sig []byte
809+
wantErr error
810+
}{
811+
{
812+
name: "Low S value",
813+
sig: hexToBytes("3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"),
814+
wantErr: nil,
815+
},
816+
{
817+
name: "High S value",
818+
sig: hexToBytes("304502200d309104bc47fecb3e23fadbabb26d3495ae1b48c1b14e8886b3f4f1c8ab122f02210085d04c97c30f69063b820a139cf17473d8e89ed587f7fa669e78175f798431fc"),
819+
wantErr: errHighS,
820+
},
821+
{
822+
name: "Invalid signature format",
823+
sig: hexToBytes("404502200d309104bc47fecb3e23fadbabb26d3495ae1b48c1b14e8886b3f4f1c8ab122f02210085d04c97c30f69063b820a139cf17473d8e89ed587f7fa669e78175f798431fc"),
824+
wantErr: errNoHeaderMagic,
825+
},
826+
}
827+
828+
for _, test := range signatureTests {
829+
err := VerifyLowS(test.sig)
830+
require.ErrorIs(t, err, test.wantErr)
831+
}
832+
}

0 commit comments

Comments
 (0)