Skip to content
ecdsa.com

ECDSA vs EdDSA: P-256 or Ed25519 for signatures

Both are elliptic-curve signatures with 64-byte outputs and 128-bit security. They differ in how safely they fail, where they are supported, and how strictly verifiers agree.

ECDSA vs EdDSA usually means ECDSA on P-256 versus EdDSA on Ed25519: the two signature schemes that cover almost every modern elliptic-curve deployment. They are equally strong and equally compact, so the choice is driven by implementation safety on one side and ecosystem support on the other.

At a glance

ECDSA (P-256)EdDSA (Ed25519)
StandardFIPS 186-5 §6, SEC 1, ANSI X9.62RFC 8032, FIPS 186-5 §7 (approved since 2023)
Curve formShort Weierstrass, prime order (cofactor 1)Twisted Edwards, cofactor 8
Securityabout 128 bitsabout 128 bits
Private key32-byte scalar32-byte seed
Public key65 bytes uncompressed, 33 compressed, 91 as SPKI DER32 bytes, 44 as SPKI DER
Signature64 bytes raw r‖s, 70–72 bytes DER64 bytes R‖S, one encoding
NonceRandom per signature, or RFC 6979 deterministicDerived from key and message by design
MalleableYes: (r, n − s) also verifiesNo, if the verifier checks S below the group order
Sign / verify per second, OpenSSL 3.6.5about 87,000 / 29,000about 60,000 / 25,000
Sign / verify per second, @noble/curves 2.3about 3,800 / 870about 3,100 / 1,280
JOSE / COSEES256 / −7Ed25519 (RFC 9864) / −19, older EdDSA / −8
TLS 1.3 schemeecdsa_secp256r1_sha256 (0x0403)ed25519 (0x0807)
Publicly trusted TLS certificatesYesNo (Baseline Requirements allow only RSA and P-256/384/521)

Speeds were measured on one core of an Apple M4 Max with OpenSSL 3.6.5 (openssl speed) and, for the pure JavaScript row, with @noble/curves 2.3.0 on Node.js 26.10.

How the designs differ

Both are discrete-logarithm signatures on elliptic curves, but they were designed about twenty years apart, ECDSA in 1992 and EdDSA in 2011, and it shows.

The nonce

An ECDSA signature consumes a secret per-signature nonce k. If k repeats for two messages, or is merely biased by a few bits across many signatures, the private key can be computed; that is how the PlayStation 3 signing key leaked and how many cryptocurrency wallets were drained. FIPS 186-5 allows either a random k from an approved generator or the deterministic derivation of RFC 6979, and most modern libraries default to one of those, but the failure mode is built into the scheme.

EdDSA removes it: the nonce is a hash of a secret key half and the message (RFC 8032 §5.1.6). Signing the same message twice gives the same signature, and different messages always get different nonces, with no random number generator involved. The trade-off is fault attacks: a glitch while signing the same message twice can leak the key, which is why some hardware wallets and the post-quantum ML-DSA standard prefer hedged signing that also mixes in fresh randomness.

Encoding and malleability

ECDSA has two signature encodings, raw r‖s (JOSE, WebCrypto, COSE, PKCS #11) and an ASN.1 DER SEQUENCE (X.509, TLS, OpenSSL, Java, WebAuthn assertions). Mixing them up is the single most common "invalid signature" bug, and the DER and raw guide covers it in detail. ECDSA is also malleable: anyone can turn (r, s) into the equally valid (r, n − s), which is why Bitcoin and Ethereum enforce low-S.

Ed25519 has one 64-byte encoding, and RFC 8032 §5.1.7 requires S to be below the group order, which removes the obvious malleability.

Verification edge cases

ECDSA verification is well defined: check that r and s are in 1…n−1, compute u₁·G + u₂·Q and compare its x-coordinate with r. Skipping the range check is fatal; Java 15 to 18 accepted an all-zero signature for any message (CVE-2022-21449).

EdDSA verification has a subtler issue. Because Edwards25519 has cofactor 8, RFC 8032 allows either a cofactored check [8][S]B = [8]R + [8][k]A or the cofactorless [S]B = R + [k]A, and libraries also differ on small-order points and non-canonical encodings. For honestly generated signatures the results agree; for adversarial inputs they do not. Systems where many parties must agree on validity, such as blockchains, pin one rule set; Zcash's ZIP 215 is the common choice.

Where each one is supported

ECDSA P-256 is the lowest common denominator of hardware and the web platform. Every browser's WebCrypto supports it, Apple's Secure Enclave and most TPMs, HSMs, smart cards and cloud KMS key types offer it (often exclusively), passkeys default to it (COSE −7), and the CA/Browser Forum Baseline Requirements (§6.1.5) allow only RSA and ECDSA on P-256, P-384 and P-521 for publicly trusted TLS certificates.

Ed25519 dominates software-defined ecosystems. OpenSSH has generated Ed25519 keys by default since version 9.5 (October 2023); Signal, WireGuard, Tor, age and most new protocol designs build on the 25519 curves (X25519 for key exchange, Ed25519 for signatures); OpenPGP v6 keys support it natively (RFC 9580); and WebCrypto supports it in Firefox 129, Safari 17 and Chrome 137 and later. In JOSE, RFC 9864 replaces the polymorphic EdDSA identifier with the fully specified Ed25519.

For FIPS-validated deployments both are approved under FIPS 186-5, though older validated modules may predate EdDSA support. For the long term, both fall to a quantum computer, and NIST IR 8547 (still a draft) proposes disallowing both after 2035.

Performance

The folklore says Ed25519 is much faster than ECDSA. That was true when P-256 implementations were generic; it is not true of OpenSSL today, which ships hand-optimized P-256 code. On one Apple M4 Max core, OpenSSL 3.6.5 produced about 87,000 P-256 signatures per second against about 60,000 for Ed25519, and verified about 29,000 against 25,000. In pure JavaScript (@noble/curves 2.3.0) Ed25519 verifies about 50% faster, while P-256 signs slightly faster. Either way the cost is microseconds and rarely decides anything; what matters is that both are one to two orders of magnitude faster than RSA signing.

The script below, run with Node.js 26.10, shows the practical differences in a few lines.

Node.js: sizes and determinism of Ed25519 and ECDSA P-256
import { generateKeyPairSync, sign, verify } from "node:crypto";

const message = Buffer.from("transfer 100 to alice");

const ed = generateKeyPairSync("ed25519");
const ec = generateKeyPairSync("ec", { namedCurve: "P-256" });

// Ed25519: the algorithm argument is null because the hash is fixed (SHA-512).
const ed1 = sign(null, message, ed.privateKey);
const ed2 = sign(null, message, ed.privateKey);

// ECDSA P-256 with SHA-256, raw r||s output as used by JOSE and WebCrypto.
const ec1 = sign("sha256", message, { key: ec.privateKey, dsaEncoding: "ieee-p1363" });
const ec2 = sign("sha256", message, { key: ec.privateKey, dsaEncoding: "ieee-p1363" });

console.log("Ed25519 signature bytes:", ed1.length, "deterministic:", ed1.equals(ed2));
console.log("ECDSA P-256 signature bytes:", ec1.length, "deterministic:", ec1.equals(ec2));
console.log("Ed25519 public key (SPKI DER) bytes:", ed.publicKey.export({ type: "spki", format: "der" }).length);
console.log("P-256 public key (SPKI DER) bytes:", ec.publicKey.export({ type: "spki", format: "der" }).length);
console.log("both verify:", verify(null, message, ed.publicKey, ed1) && verify("sha256", message, { key: ec.publicKey, dsaEncoding: "ieee-p1363" }, ec1));
Output
Ed25519 signature bytes: 64 deterministic: true
ECDSA P-256 signature bytes: 64 deterministic: false
Ed25519 public key (SPKI DER) bytes: 44
P-256 public key (SPKI DER) bytes: 91
both verify: true

Node's ECDSA signer uses random nonces, so two signatures over the same message differ; both still verify. The signature verifier accepts either scheme and shows which encoding it detected.

When to choose which

  • SSH user and host keys: Ed25519, or ed25519-sk with a FIDO security key. Use ECDSA only for old servers or hardware that lacks it.
  • Passkeys and WebAuthn: accept both (COSE −7 and −8 or −19), but expect ES256 from platform authenticators.
  • Publicly trusted TLS certificates: ECDSA P-256 (or P-384); Ed25519 is not permitted by the Baseline Requirements.
  • JWT and API tokens: ES256 for the widest compatibility and for keys held in a cloud KMS; Ed25519 where every consumer supports RFC 8037 or RFC 9864. The ES256 vs EdDSA for JWT page goes deeper.
  • Keys inside Secure Enclave, TPMs, smart cards or most HSMs: ECDSA P-256, because that is what the hardware offers.
  • Blockchains: neither choice is yours; follow the chain (secp256k1 ECDSA or Schnorr for Bitcoin and Ethereum, Ed25519 for Solana).
  • New protocols with consensus-critical verification: Ed25519 with an explicitly specified verification rule such as ZIP 215.

Migration notes

There is no conversion between a P-256 key and an Ed25519 key: different curves, different key formats. Moving from one to the other is a key rotation.

  1. Generate new keys where they will live. With openssl genpkey -algorithm ED25519 or ssh-keygen -t ed25519, or inside the HSM if it supports Ed25519. Check firmware support before planning.
  2. Publish both keys during the overlap. In a JWKS give each key its own kid and alg; in authorized_keys add the new line before removing the old; for certificates, request a new one for the new key.
  3. Update verifiers before signers. Verifiers must accept the new algorithm identifier (Ed25519 or EdDSA in JOSE, OID 1.3.101.112 in X.509, ssh-ed25519 in SSH) while still pinning an allowlist, never letting the token or message choose.
  4. Switch signing, then retire the old key once everything signed with it has expired.
  5. Watch encodings. ECDSA code often converts between DER and raw signatures; Ed25519 code must not, and Ed25519 "private key" sizes differ by API (32-byte seed or 64 bytes of seed plus public key).

Going the other way, from Ed25519 to ECDSA, happens mostly when keys move into hardware. Use deterministic or hedged nonces, enforce low-S if signatures act as identifiers, and test DER and raw handling with the DER and raw converter.

References

All comparisons