2.8 KiB
ECDSA algorithms (ES256 / ES384 / ES512)
Purpose
RFC 7518 §3.4 ECDSA signing and verification over NIST P-256, P-384, and P-521. The signing input is hashed with the curve-matched SHA-2 variant, signed via @suaveplan/crypto-curve's curve primitives, and serialised as the fixed-length concatenation R || S of the two coordinates. DER-encoded signatures in the JOSE position are rejected with JWS.SIG_FORMAT_INVALID — silently parsing them would hide protocol drift between producers, which is the source of multiple JOSE interop CVEs.
Features
ES256→ P-256 + SHA-256, 64-byteR||S.ES384→ P-384 + SHA-384, 96-byteR||S.ES512→ P-521 + SHA-512, 132-byteR||S(R and S are 66 bytes each).- DER-encoded signature in JOSE position →
JWS.SIG_FORMAT_INVALID. - Key shape: raw uncompressed SEC1 public-key bytes (
0x04 || X || Y) for verify; raw scalar for sign.
Usage: Basic Example
import { p256 } from "@suaveplan/crypto-curve";
import { compactSign, compactVerify } from "@suaveplan/jws";
const priv = p256.randomPrivateKey();
const pub = p256.getPublicKey(priv);
const token = await compactSign("hello", priv, "ES256");
await compactVerify(token, pub, { algorithms: ["ES256"] });
Usage: Advanced Example
import { es256, JwsError } from "@suaveplan/jws";
// Reject DER-encoded signature in JOSE position:
const derSig = new Uint8Array([0x30, 0x46, /* ... */]);
try {
await es256.verify(signingInput, derSig, pub);
} catch (err) {
if (err instanceof JwsError && err.code === "JWS.SIG_FORMAT_INVALID") {
// DER detected by 0x30 lead byte + length mismatch.
}
}
// Wrong-length raw R||S also rejected:
try {
await es256.verify(signingInput, new Uint8Array(63), pub);
} catch (err) {
if (err instanceof JwsError && err.code === "JWS.SIG_FORMAT_INVALID") {
// Generic length mismatch.
}
}
API Reference
es256: AlgorithmImpl<Uint8Array>— verify(input, sig: 64 bytes, pub), sign(input, priv: 32 bytes).es384: AlgorithmImpl<Uint8Array>— sig 96 bytes, priv 48 bytes.es512: AlgorithmImpl<Uint8Array>— sig 132 bytes, priv 66 bytes.
Implementation Notes
Public keys MUST be uncompressed SEC1 (0x04 || X || Y of the relevant coordinate length). The verifier delegates to @suaveplan/crypto-curve's p256.verify / p384.verify / p521.verify, which use the Noble curves library with constant-time scalar multiplication. Signing uses RFC 6979 deterministic k (no per-signature randomness required), which makes ECDSA JWS production deterministic for a given (privateKey, payload) pair — this is a feature for reproducible-build test vectors but means consumers should NOT reuse k across messages outside this library. The 132-byte length for ES512 surprises producers expecting 128: P-521 coordinates are 66 bytes each, not 64.