ecdsa.com

Verify a signature · WebCrypto

How to verify an ECDSA signature in WebCrypto

The browser's built-in WebCrypto API (crypto.subtle) verifies ECDSA signatures without any libraries — and Node.js ships the same API. Two constraints drive the whole recipe: keys are imported from binary SPKI, and signatures must be raw r‖s, never DER.

Tested against the WebCrypto API in Node.js 26 — the same crypto.subtle interface browsers implement.

Same recipe in:Node.jsPythonGoOpenSSLWebCrypto
PEM → bytes helper (WebCrypto imports binary, not PEM)
function pemToBytes(pem) {
const b64 = pem.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
return Uint8Array.from(atob(b64), (c) => c.charCodeAt(0));
}
import the public key and verify a raw signature
const key = await crypto.subtle.importKey(
"spki", // the body of "BEGIN PUBLIC KEY"
pemToBytes(publicKeyPem),
{ name: "ECDSA", namedCurve: "P-256" },
false,
["verify"],
);
 
const ok = await crypto.subtle.verify(
{ name: "ECDSA", hash: "SHA-256" },
key,
rawSignature, // exactly 64 bytes: r‖s
new TextEncoder().encode(message),
);
console.log(ok ? "valid" : "INVALID");

How it works

  1. The curve is declared at import time and must match the key — importKey rejects an SPKI whose curve differs from namedCurve.
  2. The hash is declared at verify time. Conventional pairings: P-256 with SHA-256, P-384 with SHA-384, P-521 with SHA-512.
  3. verify resolves to a boolean for a wrong signature; a rejected promise means the inputs themselves are malformed.

Gotchas

  • WebCrypto accepts only raw r‖s signatures. A DER signature from OpenSSL or Node's default sign quietly returns false — no error. Convert DER → raw first.
  • secp256k1 is not in WebCrypto: namedCurve supports P-256, P-384 and P-521 only. For Bitcoin/Ethereum keys use a library such as @noble/curves.
  • crypto.subtle exists only in secure contexts (HTTPS or localhost) — on plain HTTP it is undefined and every call fails before cryptography even starts.

Related recipes