This error occurs when verifying an ECDSA (Elliptic Curve Digital Signature Algorithm) signature fails, indicating the signature does not match the expected value for the given data and public key. Common causes include mismatched elliptic curves, incorrect signature encoding formats, or data corruption during transmission.
The ECDSA signature verification error indicates that a cryptographic signature validation has failed. ECDSA is an elliptic curve variant of the Digital Signature Algorithm used extensively in modern cryptography for signing data, JWTs (JSON Web Tokens), blockchain transactions, and secure communications. When Node.js attempts to verify a signature using the crypto module or libraries like elliptic, jsonwebtoken, or jsrsasign, it compares the provided signature against what it calculates from the data and public key. If they don't match, this error is thrown. The signature may have been generated with different parameters, corrupted during transmission, or the wrong key may be used for verification. This error is particularly common when working with JWT tokens using ES256, ES384, or ES512 algorithms, which rely on ECDSA. It can also occur in blockchain applications, certificate validation, and any system that uses elliptic curve cryptography for authentication or integrity verification.
Ensure the signing and verification operations use the same elliptic curve and hash algorithm:
const crypto = require('crypto');
// Signing
const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', {
namedCurve: 'prime256v1' // Also known as P-256 or secp256r1
});
const sign = crypto.createSign('SHA256');
sign.update('data to sign');
const signature = sign.sign(privateKey);
// Verification - must use same curve and algorithm
const verify = crypto.createVerify('SHA256');
verify.update('data to sign');
const isValid = verify.verify(publicKey, signature);
console.log('Signature valid:', isValid);Common curve names: prime256v1 (P-256), secp384r1 (P-384), secp521r1 (P-521). Make sure both sides use the same curve.
ECDSA signatures can be encoded in different formats. Ensure signing and verification use the same encoding:
const crypto = require('crypto');
// Option 1: DER encoding (default)
const signDER = crypto.createSign('SHA256');
signDER.update('data');
const derSignature = signDER.sign({
key: privateKey,
dsaEncoding: 'der' // Default
});
// Option 2: IEEE-P1363 encoding (r || s format)
const signIEEE = crypto.createSign('SHA256');
signIEEE.update('data');
const ieeeSignature = signIEEE.sign({
key: privateKey,
dsaEncoding: 'ieee-p1363'
});
// Verification must match encoding
const verify = crypto.createVerify('SHA256');
verify.update('data');
const isValid = verify.verify({
key: publicKey,
dsaEncoding: 'der' // Must match signing encoding
}, derSignature);If you receive signatures from external systems, check their documentation for the encoding format.
For JWT tokens, ensure the algorithm specified matches the key type:
const jwt = require('jsonwebtoken');
const fs = require('fs');
// Generate EC key pair for ES256
const { generateKeyPairSync } = require('crypto');
const { privateKey, publicKey } = generateKeyPairSync('ec', {
namedCurve: 'prime256v1', // P-256 for ES256
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// Sign with ES256
const token = jwt.sign({ data: 'payload' }, privateKey, {
algorithm: 'ES256'
});
// Verify with matching algorithm
try {
const decoded = jwt.verify(token, publicKey, {
algorithms: ['ES256'] // Must include the algorithm used to sign
});
console.log('Token valid:', decoded);
} catch (err) {
console.error('Verification failed:', err.message);
}ES256 uses P-256, ES384 uses P-384, ES512 uses P-521. The curve must match the algorithm.
Verify that the exact same data is being signed and verified (byte-for-byte):
const crypto = require('crypto');
const data = 'critical data';
// Sign
const sign = crypto.createSign('SHA256');
sign.update(data); // Data as string
const signature = sign.sign(privateKey);
// INCORRECT - different encoding
const verify1 = crypto.createVerify('SHA256');
verify1.update(Buffer.from(data, 'utf8')); // Explicitly encoded
console.log('Valid:', verify1.verify(publicKey, signature)); // May fail
// CORRECT - same format
const verify2 = crypto.createVerify('SHA256');
verify2.update(data); // Same as signing
console.log('Valid:', verify2.verify(publicKey, signature)); // Should work
// For complex objects, normalize before signing/verifying
function normalizeData(obj) {
return JSON.stringify(obj, Object.keys(obj).sort());
}
const payload = { b: 2, a: 1 };
const normalized = normalizeData(payload);
// Sign and verify using normalized stringPay special attention to whitespace, line endings, and character encoding.
Some ECDSA verification issues are caused by library bugs. Update to latest versions:
# Update to latest versions
npm update jsonwebtoken
npm update elliptic
npm update jsrsasign
# Or specify versions with known fixes
npm install elliptic@^6.5.8
npm install jsonwebtoken@^9.0.0Known issues to be aware of:
- elliptic 6.5.7 has a bug with signatures when hashes contain 4+ leading zero bytes
- Some versions of jsrsasign have signature format handling issues
- Older jsonwebtoken versions have EC key parsing problems
Check the changelog for your specific library for ECDSA-related fixes.
Inspect the signature components to identify format issues:
const crypto = require('crypto');
function inspectSignature(signature) {
// For IEEE-P1363 format (r || s)
const len = signature.length / 2;
const r = signature.slice(0, len);
const s = signature.slice(len);
console.log('Signature length:', signature.length);
console.log('r component:', r.toString('hex'));
console.log('s component:', s.toString('hex'));
// Check for valid ranges
console.log('r is zero:', r.every(b => b === 0));
console.log('s is zero:', s.every(b => b === 0));
}
// Test your signature
inspectSignature(signature);
// For DER format, use ASN.1 parser
const asn1 = require('asn1.js');
const ECSignature = asn1.define('ECSignature', function() {
this.seq().obj(
this.key('r').int(),
this.key('s').int()
);
});
try {
const decoded = ECSignature.decode(signature, 'der');
console.log('DER r:', decoded.r.toString(16));
console.log('DER s:', decoded.s.toString(16));
} catch (err) {
console.error('Not valid DER format:', err.message);
}This helps identify if the signature has valid components or is corrupted.
Understanding ECDSA Signature Components
An ECDSA signature consists of two integers (r, s) that prove the signer possessed the private key. The signature can be encoded in two main formats:
1. DER (Distinguished Encoding Rules): ASN.1 encoded structure used by OpenSSL and most certificate systems
2. IEEE-P1363: Raw concatenation of r and s as fixed-length integers
When interoperating between systems, format conversion may be necessary. Many JWT libraries expect IEEE-P1363 format internally but may accept DER.
Elliptic Package Bug with Leading Zeros
The elliptic package versions prior to 6.5.8 have a known issue where valid signatures fail verification if the message hash has 4 or more leading zero bytes. If you're experiencing random verification failures, check your hash values:
const hash = crypto.createHash('sha256').update(data).digest();
const leadingZeros = hash.findIndex(b => b !== 0);
console.log('Leading zero bytes:', leadingZeros);
// If >= 4, you may hit the elliptic bugCross-Library Compatibility
Different libraries may have subtle implementation differences:
// jsrsasign may produce signatures that fail in Node.js crypto
const jsrsasign = require('jsrsasign');
const crypto = require('crypto');
// Always test cross-library verification during development
// Consider using only Node.js native crypto for consistencyBlockchain Signature Verification
In blockchain contexts (Ethereum, Bitcoin), the signature often includes a recovery parameter (v) that must be 27 or 28 (or 0/1 in some implementations). Invalid v values cause verification failures:
function validateRecoveryParam(v) {
// Standard ECDSA
if (v !== 27 && v !== 28) {
throw new Error('Invalid recovery parameter: ' + v);
}
return v;
}Deterministic vs Non-Deterministic Signatures
ECDSA signatures are typically non-deterministic (RFC 6979 optional), meaning the same message produces different signatures each time. This is normal and expected. The signature should still verify correctly despite being different each time.
Performance Considerations
ECDSA verification is computationally expensive. For high-throughput systems:
- Cache verification results where appropriate
- Use signature batching for multiple verifications
- Consider using Ed25519 (faster) instead of ECDSA for new systems
- Profile your verification code to identify bottlenecks
Security Warnings
- Never disable signature verification in production even if experiencing errors
- Always validate the public key source before trusting verification results
- Use timing-safe comparison functions to prevent timing attacks
- Rotate keys regularly and maintain proper key management practices
Error: EMFILE: too many open files, watch
EMFILE: fs.watch() limit exceeded
Error: Middleware next() called multiple times (next() invoked twice)
Express middleware next() called multiple times
Error: Worker failed to initialize (worker startup error)
Worker failed to initialize in Node.js
Error: EMFILE: too many open files, open 'file.txt'
EMFILE: too many open files
Error: cluster.fork() failed (cannot create child process)
cluster.fork() failed - Cannot create child process