What changed, and why it matters
This commit adds a new helper function called VerifyLowS to the btcd Bitcoin library's ECDSA signature code. Its purpose is to detect signatures whose 'S' value is in the mathematically equivalent but non-standard 'high-S' form, which can make signatures malleable (one valid signature can be transformed into another valid one). The commit also adds unit tests. It does not by itself change any consensus or network validation rules; it only provides a reusable utility for callers to enforce low-S if they choose.
Review where VerifyLowS is intended to be called (future commits or PR context) to ensure high-S enforcement is applied consistently in transaction/script validation paths. No immediate action is required for this commit alone.
Security signals we found
New low-S signature validation helper added
High-S signatures rejected as non-canonical
Signature malleability mitigation utility
No existing validation paths modified in this commit
Test-only addition of require dependency
Evidence from the diff
The patch introduces VerifyLowS(sigStr []byte) error in btcec/ecdsa/signature.go. It parses a DER-encoded ECDSA signature and returns errHighS if sig.S() exceeds half the curve order (IsOverHalfOrder). It also refactors the existing parseSig header-magic error into a package-level exported-style variable errNoHeaderMagic and adds table-driven tests covering low-S, high-S, and malformed inputs. No existing callers are modified, and no consensus-critical enforcement is added in this commit.
Changed components
btcec/ecdsa/signature.gobtcec/ecdsa/signature_test.goInspect captured patch +50 / −1
diff --git a/btcec/ecdsa/signature.go b/btcec/ecdsa/signature.go
index a2574f8..7bae9f7 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("non-canonical signature: S value not in low-S form")
+ 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 7a457b1..ff14885 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)
+ }
+}
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.