ECDSA と EdDSA の比較といえば、通常は P-256 の ECDSA と Ed25519 の EdDSA の比較を指します。現在使われている楕円曲線署名のほぼすべてを、この 2 つの方式が占めています。強度もコンパクトさも同等なので、選択を左右するのは、一方では実装の安全性、もう一方ではエコシステムの対応状況です。
比較表
| ECDSA(P-256) | EdDSA(Ed25519) | |
|---|---|---|
| 標準 | FIPS 186-5 §6、SEC 1、ANSI X9.62 | RFC 8032、FIPS 186-5 §7(2023年から承認) |
| 曲線の形式 | 短縮ワイエルシュトラス形式、素数位数(コファクター 1) | ツイストエドワーズ形式、コファクター 8 |
| 安全性 | 約 128 ビット | 約 128 ビット |
| 秘密鍵 | 32 バイトのスカラー | 32 バイトのシード |
| 公開鍵 | 非圧縮形式で 65 バイト、圧縮形式で 33 バイト、SPKI DER で 91 バイト | 32 バイト、SPKI DER で 44 バイト |
| 署名 | 生の r‖s で 64 バイト、DER で 70〜72 バイト | R‖S で 64 バイト、エンコーディングは 1 種類のみ |
| nonce | 署名ごとにランダム、または RFC 6979 による決定的な生成 | 設計上、鍵とメッセージから導出 |
| 展性 | あり:(r, n − s) も検証に通る | なし(検証者が、S が位数未満であることを確認する場合) |
| 1 秒あたりの署名数・検証数(OpenSSL 3.6.5) | 約 87,000 / 29,000 | 約 60,000 / 25,000 |
| 1 秒あたりの署名数・検証数(@noble/curves 2.3) | 約 3,800 / 870 | 約 3,100 / 1,280 |
| JOSE / COSE | ES256 / −7 | Ed25519(RFC 9864)/ −19、旧来の EdDSA / −8 |
| TLS 1.3 の署名方式 | ecdsa_secp256r1_sha256(0x0403) | ed25519(0x0807) |
| 公的に信頼された TLS 証明書 | 可 | 不可(Baseline Requirements が認めるのは RSA と P-256/384/521 のみ) |
速度は、Apple M4 Max の 1 コアで OpenSSL 3.6.5(openssl speed)を使って計測しました。純粋な JavaScript の行は、Node.js 26.10 上の @noble/curves 2.3.0 で計測しています。
設計の違い
どちらも楕円曲線上の離散対数に基づく署名ですが、ECDSA は 1992年、EdDSA は 2011年と、設計された時期には約 20 年の開きがあり、それが随所に表れています。
nonce の扱い
ECDSA の署名は、署名ごとに秘密の nonce k を消費します。2 つのメッセージで k が重複したり、多数の署名にわたって数ビット偏っていたりするだけで、秘密鍵を計算できてしまいます。PlayStation 3 の署名鍵が漏洩したのも、多くの暗号資産ウォレットから資金が抜き取られたのも、この仕組みによるものです。FIPS 186-5 は、承認された乱数生成器によるランダムな k か、RFC 6979 による決定的な導出のどちらかを認めており、最近のライブラリの多くはデフォルトでそのどちらかを使います。それでも、この破綻のしかたは方式そのものに組み込まれています。
EdDSA はこの問題をなくしています。nonce は、秘密鍵の半分とメッセージをハッシュした値です(RFC 8032 §5.1.6)。同じメッセージに 2 回署名すれば同じ署名が得られ、異なるメッセージには必ず異なる nonce が使われます。乱数生成器はまったく関与しません。その代わりに問題となるのがフォールト攻撃です。同じメッセージに 2 回署名する間にグリッチを起こされると鍵が漏洩する可能性があるため、一部のハードウェアウォレットやポスト量子署名の標準である ML-DSA は、新しい乱数も混ぜ込むヘッジ付き署名を優先しています。
エンコーディングと展性
ECDSA には署名のエンコーディングが 2 つあります。生の r‖s(JOSE、WebCrypto、COSE、PKCS #11)と、ASN.1 DER の SEQUENCE(X.509、TLS、OpenSSL、Java、WebAuthn のアサーション)です。この 2 つの取り違えは「invalid signature」バグの最大の原因であり、詳しくは DER と生形式のガイドで解説しています。また ECDSA には展性があります。誰でも (r, s) を、同じく有効な (r, n − s) に変換できるため、Bitcoin と Ethereum は low-S を強制しています。
Ed25519 のエンコーディングは 64 バイトの 1 種類だけで、RFC 8032 §5.1.7 は S が位数未満であることを要求しています。これにより、明白な展性はなくなります。
検証のエッジケース
ECDSA の検証は明確に定義されています。r と s が 1…n−1 の範囲にあることを確認し、u₁·G + u₂·Q を計算して、その x 座標を r と比較します。範囲チェックを省くと致命的です。Java 15〜18 は、任意のメッセージに対してすべてゼロの署名を受け入れていました(CVE-2022-21449)。
EdDSA の検証には、もっと微妙な問題があります。Edwards25519 のコファクターは 8 なので、RFC 8032 はコファクター付きの検証 [8][S]B = [8]R + [8][k]A と、コファクターなしの検証 [S]B = R + [k]A のどちらも認めており、さらにライブラリごとに、位数の小さい点や正規形でないエンコーディングの扱いも異なります。正当に生成された署名なら結果は一致しますが、悪意のある入力では一致しません。ブロックチェーンのように、多数の参加者が有効性について合意しなければならないシステムは、1 つのルールセットに固定しています。よく選ばれるのは Zcash の ZIP 215 です。
それぞれの対応状況
ECDSA P-256 は、ハードウェアでも Web プラットフォームでも共通して使える、いわば最大公約数です。どのブラウザーの WebCrypto も対応しており、Apple の Secure Enclave や、大半の TPM、HSM、スマートカード、クラウド KMS の鍵タイプが(多くの場合はこれだけを)提供しています。パスキーもデフォルトでこれを使い(COSE −7)、CA/Browser Forum の Baseline Requirements(§6.1.5)は、公的に信頼された TLS 証明書に RSA と、P-256、P-384、P-521 上の ECDSA だけを認めています。
ソフトウェアで定義されるエコシステムでは、Ed25519 が主流です。OpenSSH はバージョン 9.5(2023年10月)以降、デフォルトで Ed25519 鍵を生成しています。Signal、WireGuard、Tor、age、そして新しく設計されるプロトコルの大半は 25519 系の曲線(鍵交換には X25519、署名には Ed25519)を基盤にしています。OpenPGP v6 の鍵はネイティブに対応しており(RFC 9580)、WebCrypto は Firefox 129、Safari 17、Chrome 137 以降で対応しています。JOSE では、RFC 9864 が多義的な EdDSA 識別子を、完全指定の Ed25519 に置き換えています。
FIPS 認証を受けた環境では、どちらも FIPS 186-5 で承認されています。ただし、認証済みの古いモジュールは EdDSA への対応以前のものである可能性があります。長期的には、どちらも量子コンピューターによって破られます。NIST IR 8547(まだドラフト)は、2035年以降に両方を禁止することを提案しています。
性能
Ed25519 は ECDSA よりはるかに速い、と一般に言われています。P-256 の実装が汎用的だった頃はそのとおりでしたが、手作業で最適化した P-256 のコードを搭載している現在の OpenSSL には当てはまりません。Apple M4 Max の 1 コアで、OpenSSL 3.6.5 は 1 秒あたり P-256 で約 87,000 回、Ed25519 で約 60,000 回の署名を行い、検証は約 29,000 回と 25,000 回でした。純粋な JavaScript(@noble/curves 2.3.0)では、Ed25519 の検証が約 50% 速い一方、署名は P-256 のほうがわずかに速くなります。いずれにせよコストはマイクロ秒単位で、これが決め手になることはまれです。重要なのは、どちらも RSA の署名より 1〜2 桁速いことです。
次のスクリプトは Node.js 26.10 で実行したもので、実用上の違いを数行で示します。
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));
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 の ECDSA 署名はランダムな nonce を使うため、同じメッセージに対する 2 つの署名は異なりますが、どちらも検証に通ります。署名検証ツールはどちらの方式も受け付け、検出したエンコーディングを表示します。
どちらを選ぶべきか
- SSH のユーザー鍵とホスト鍵:Ed25519、または FIDO セキュリティキーと組み合わせた
ed25519-skを使います。ECDSA は、Ed25519 に対応していない古いサーバーやハードウェアに限って使います。 - パスキーと WebAuthn:両方(COSE −7 と、−8 または −19)を受け入れます。ただし、プラットフォーム認証器からは ES256 が届くものと想定してください。
- 公的に信頼された TLS 証明書:ECDSA P-256(または P-384)を使います。Ed25519 は Baseline Requirements で認められていません。
- JWT と API トークン:互換性を最大限に確保する場合や、鍵をクラウド KMS に保管する場合は ES256 を使い、すべての受信側が RFC 8037 または RFC 9864 に対応している場合は Ed25519 を使います。詳しくは JWT における ES256 と EdDSA の比較のページで解説しています。
- Secure Enclave、TPM、スマートカード、大半の HSM の中にある鍵:ECDSA P-256 を使います。ハードウェアが提供しているのがこの方式だからです。
- ブロックチェーン:どちらを使うかは自分では選べません。チェーンの方式に従います(Bitcoin と Ethereum では secp256k1 の ECDSA または Schnorr、Solana では Ed25519)。
- 検証がコンセンサスに直結する新しいプロトコル:ZIP 215 のように検証ルールを明示的に規定したうえで、Ed25519 を使います。
移行時の注意点
P-256 鍵と Ed25519 鍵の間で変換する方法はありません。曲線も鍵の形式も異なるからです。一方から他方への移行は、鍵のローテーションになります。
- 新しい鍵は、それを使う場所で生成します。
openssl genpkey -algorithm ED25519やssh-keygen -t ed25519で生成するか、HSM が Ed25519 に対応していれば HSM の中で生成します。計画を立てる前に、ファームウェアの対応状況を確認してください。 - 移行期間中は両方の鍵を公開します。JWKS では各鍵に固有の
kidとalgを付け、authorized_keysでは古い行を削除する前に新しい行を追加します。証明書の場合は、新しい鍵に対して新しい証明書を申請します。 - 署名者より先に検証者を更新します。検証者は新しいアルゴリズム識別子(JOSE では
Ed25519またはEdDSA、X.509 では OID1.3.101.112、SSH ではssh-ed25519)を受け入れる必要があります。その際も許可リストによる固定は維持し、トークンやメッセージの側に選ばせてはいけません。 - 署名を切り替えてから古い鍵を廃止します。廃止するのは、古い鍵で署名したものがすべて期限切れになってからです。
- エンコーディングに注意します。ECDSA のコードでは DER と生形式の署名の変換がよく行われますが、Ed25519 のコードでは変換してはいけません。また、Ed25519 の「秘密鍵」のサイズは API によって異なります(32 バイトのシード、またはシードと公開鍵を合わせた 64 バイト)。
逆方向、つまり Ed25519 から ECDSA への移行は、主に鍵をハードウェアに移すときに起こります。決定的 nonce かヘッジ付き nonce を使い、署名を識別子として使う場合は low-S を強制し、DER と生形式の扱いは DER ⇄ 生形式の変換ツールでテストしてください。