crypto.subtle is the standard cryptography interface of the web platform. The same calls — generateKey, importKey, sign, verify, exportKey — work in Chromium, Firefox and WebKit and in server runtimes such as Node.js, which makes it the natural choice for code that must run on both sides. It only exists in secure contexts: on a plain-http page crypto.subtle is undefined.
The specification fixes the ECDSA signature format to raw r‖s, 64 bytes on P-256, and the check found exactly that in all four engines. That matches JWS and JWT, so ES256 tokens need no conversion, but it does not match OpenSSL, Java's SHA256withECDSA, Go's SignASN1 or node:crypto, which all speak ASN.1 DER. Because a DER signature passed to verify() returns a plain false rather than an error, the mismatch looks like a forgery; convert with the DER ⇄ raw converter or check both forms in the signature verifier.
Keys are the other common source of friction. importKey("spki") takes DER bytes, never PEM text; "raw" takes the SEC1 point, and the compressed form is accepted by Node.js, Chromium and Firefox but refused by WebKit, so send the uncompressed point for portable code. Private keys come in as PKCS#8 DER or JWK only — the SEC1 EC PRIVATE KEY files OpenSSL writes by default have to be converted to PKCS#8 first, for example with the JWK converter.
The curve list ends at P-256, P-384 and P-521: secp256k1 and Brainpool raise NotSupportedError everywhere, so Bitcoin and Ethereum keys need a library such as @noble/curves. Ed25519 works in all four engines; Ed448 only in Node.js. Post-quantum support is uneven: Node.js 24.22 implements ML-DSA and ML-KEM behind an experimental warning, Chromium 156 accepts ML-DSA and ML-KEM-768/1024 but not ML-KEM-512, and Firefox 157 and WebKit 27.2 have neither — feature-detect before relying on them, and try the algorithms in the post-quantum signature playground.
The sample shows the portable pattern: name the hash in the algorithm object, import foreign PEM keys by decoding them to DER first, and remember that the 64-byte output has to be wrapped in DER before an OpenSSL-style verifier will read it.