From 538fc9fe55588b0329207eca2e2add2f64c51eb0 Mon Sep 17 00:00:00 2001 From: MPins Date: Tue, 18 Nov 2025 18:31:46 -0300 Subject: [PATCH 1/2] go.mod, go.sum: updated secp256k1 to v4.4.0 --- btcec/go.mod | 4 ++-- btcec/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/btcec/go.mod b/btcec/go.mod index 95e86dbc3a..c7b80352b7 100644 --- a/btcec/go.mod +++ b/btcec/go.mod @@ -5,12 +5,12 @@ go 1.22 require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/davecgh/go-spew v1.1.1 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 github.com/stretchr/testify v1.8.0 ) require ( - github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/crypto/blake256 v1.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/btcec/go.sum b/btcec/go.sum index 73b8f2b5ac..5b28b76ea2 100644 --- a/btcec/go.sum +++ b/btcec/go.sum @@ -3,10 +3,10 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/crypto/blake256 v1.1.0 h1:zPMNGQCm0g4QTY27fOCorQW7EryeQ/U0x++OzVrdms8= +github.com/decred/dcrd/crypto/blake256 v1.1.0/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= From f8dfe2105186b0ded208264fc77751e692bf2cf9 Mon Sep 17 00:00:00 2001 From: MPins Date: Tue, 18 Nov 2025 10:55:46 -0300 Subject: [PATCH 2/2] ecdsa: add VerifyLowS helper and tests Introduce a VerifyLowS helper to detect non-canonical high-S ECDSA signatures, along with unit tests. --- btcec/ecdsa/signature.go | 21 ++++++++++++++++++++- btcec/ecdsa/signature_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/btcec/ecdsa/signature.go b/btcec/ecdsa/signature.go index a2574f8794..cc0d82e8ce 100644 --- a/btcec/ecdsa/signature.go +++ b/btcec/ecdsa/signature.go @@ -18,6 +18,8 @@ import ( var ( errNegativeValue = errors.New("value may be interpreted as negative") errExcessivelyPaddedValue = errors.New("value is excessively padded") + errHighS = errors.New("signature is not canonical due to unnecessarily high S value") + errNoHeaderMagic = errors.New("malformed signature: no header magic") ) // Signature is a type representing an ecdsa signature. @@ -90,7 +92,7 @@ func parseSig(sigStr []byte, der bool) (*Signature, error) { // 0x30 index := 0 if sigStr[index] != 0x30 { - return nil, errors.New("malformed signature: no header magic") + return nil, errNoHeaderMagic } index++ // length of remaining message @@ -254,3 +256,20 @@ func RecoverCompact(signature, hash []byte) (*btcec.PublicKey, bool, error) { func Sign(key *btcec.PrivateKey, hash []byte) *Signature { return secp_ecdsa.Sign(key, hash) } + +// VerifyLowS verifies that the given ECDSA signature is strictly DER-encoded +// and uses a canonical low-S value. It returns nil if the signature is valid; +// otherwise it returns the encountered error. +func VerifyLowS(sigStr []byte) error { + sig, err := parseSig(sigStr, true) + if err != nil { + return err + } + sValue := sig.S() + if sValue.IsOverHalfOrder() { + // High-S, s > N/2. + return errHighS + } + // Low-S, s <= N/2. + return nil +} diff --git a/btcec/ecdsa/signature_test.go b/btcec/ecdsa/signature_test.go index 7a457b1e66..ff148856fd 100644 --- a/btcec/ecdsa/signature_test.go +++ b/btcec/ecdsa/signature_test.go @@ -15,6 +15,7 @@ import ( "testing" "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" ) type signatureTest struct { @@ -800,3 +801,32 @@ func TestPrivKeys(t *testing.T) { } } } + +func TestVerifyLowS(t *testing.T) { + signatureTests := []struct { + name string + sig []byte + wantErr error + }{ + { + name: "Low S value", + sig: hexToBytes("3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124"), + wantErr: nil, + }, + { + name: "High S value", + sig: hexToBytes("304502200d309104bc47fecb3e23fadbabb26d3495ae1b48c1b14e8886b3f4f1c8ab122f02210085d04c97c30f69063b820a139cf17473d8e89ed587f7fa669e78175f798431fc"), + wantErr: errHighS, + }, + { + name: "Invalid signature format", + sig: hexToBytes("404502200d309104bc47fecb3e23fadbabb26d3495ae1b48c1b14e8886b3f4f1c8ab122f02210085d04c97c30f69063b820a139cf17473d8e89ed587f7fa669e78175f798431fc"), + wantErr: errNoHeaderMagic, + }, + } + + for _, test := range signatureTests { + err := VerifyLowS(test.sig) + require.ErrorIs(t, err, test.wantErr) + } +}