本文へスキップ
ecdsa.com

P-256 と secp256k1 の比較|どちらも 256 ビット、異なる世界

NIST の曲線は Web、パスキー、セキュアハードウェアを支え、Koblitz 曲線は Bitcoin、Ethereum、Nostr を支えています。サイズも強度も同じですが、それぞれを取り巻くルールが異なります。

P-256 と secp256k1 は、自由に選べることがほとんどありません。通常、曲線はエコシステムとセットで決まるからです。どちらも位数が素数の 256 ビットのワイエルシュトラス曲線で、約 128 ビットセキュリティを持ち、鍵と署名のサイズも同じです。しかし、由来、対応するプラットフォーム、その上に重ねられた慣習が異なり、2 つの世界の間でデータをやり取りするところでバグが起こります。

比較表

P-256(secp256r1、prime256v1)secp256k1
規定NIST SP 800-186 §3.2.1.3、SEC 2 §2.4.2SEC 2 §2.4.1。SP 800-186 付録 H.2 がブロックチェーンでの利用を許可
方程式y² = x³ − 3x + b、法 p = 2²⁵⁶ − 2²²⁴ + 2¹⁹² + 2⁹⁶ − 1y² = x³ + 7、法 p = 2²⁵⁶ − 2³² − 977
パラメーターの由来由来が説明されていない SHA-1 のシードから b を導出固定された最小限の係数(Koblitz 型)
コファクター、セキュリティ1、約 128 ビット1、約 128 ビット
鍵と署名32 バイトの秘密鍵、33/65 バイトの公開鍵、生形式で 64 バイトの署名同じ
OID1.2.840.10045.3.1.71.3.132.0.10
JOSE / COSEES256 / −7ES256K / −47(RFC 8812)
Node.js 26(OpenSSL 3.6.5)での 1 秒あたりの署名数・検証数約 68,800 / 27,200約 6,500 / 7,200
@noble/curves 2.3.0 での 1 秒あたりの署名数・検証数約 3,800 / 870約 3,800 / 1,180
WebCrypto、TLS 1.3、WebAuthn対応非対応
Apple の Secure Enclave、PIV カード、大半の HSM対応まれ(AWS KMS は ECC_SECG_P256K1 を提供)
主な用途TLS 証明書、パスキー、JWT、デバイスの IDBitcoin、Ethereum、Nostr、大半の EVM チェーン

Apple M4 Max の 1 コアで測定しました。

由来と信頼性

P-256 は 1999年に NIST が公開して FIPS 186-2(2000年)で標準化し、SECG も secp256r1 として標準化しました。素数には高速な剰余演算を可能にする特殊な形があり、係数 b は、当時の「検証可能なランダム性」の手順が定めていたとおり、シードから SHA-1 を使って生成されました。シードの由来は一度も説明されておらず、Dual_EC_DRBG をめぐる暴露の後、一部の設計者が NIST 曲線を信用しなくなる原因になりました。そのシードに基づく P-256 への攻撃はこれまで見つかっておらず、P-256 は実際に使われている曲線の中で最も詳しく精査された曲線であり続けています。

secp256k1 は SEC 2(2000年)で定められました。a = 0、b = 7 なので隠せるものは何もなく、その特殊な構造によって効率的に計算できる自己準同型写像(GLV 法)が得られ、スカラー倍算が高速になります。サトシ・ナカモトは 2009年に Bitcoin のためにこの曲線を選びました。当時としては珍しい選択でしたが、Ethereum もこれに続きました。NIST SP 800-186(2023年)は、secp256k1 を「ブロックチェーン関連のアプリケーション向け」に明示的に許可しています。

どちらの曲線も位数が素数なので、コファクターにまつわる微妙な問題はありません。また、どちらも Shor のアルゴリズムによって同じように破られます。

プラットフォームとライブラリの対応状況

本当の違いはここにあります。P-256 は、鍵を管理してくれるあらゆるものに組み込まれています。すべてのブラウザーの WebCrypto、すべてのオペレーティングシステムの TLS スタック、Apple の Secure Enclave と CryptoKit、Android Keystore、TPM 2.0、PIV スマートカード、そしてすべてのクラウド KMS です。パスキーはデフォルトで P-256 を使い、CA/Browser Forum の Baseline Requirements は公的に信頼される証明書での使用を認めています。

secp256k1 は WebCrypto にも TLS 1.3 にもありません(TLS 1.2 のコードポイントは、すでに RFC 8422 で非推奨になっていました)。汎用ライブラリでも二の次の扱いです。そのためブロックチェーンのソフトウェアは専用の実装に頼っています。Bitcoin Core では定数時間実装の libsecp256k1、それ以外では @noble/curves、Rust の k256、Go の go-ethereum/crypto などです。ハードウェアウォレットは secp256k1 に対応していますが、エンタープライズ向けの HSM は製品によって異なります。AWS KMS は、署名専用の ECC_SECG_P256K1 鍵仕様として提供しています。

性能

サイズが同じなので、速度は曲線ではなく実装の質で決まります。OpenSSL には P-256 向けに手作業でチューニングしたアセンブリコードがある一方、secp256k1 は汎用コードで処理されます。そのため Node.js 26.10(OpenSSL 3.6.5)では、P-256 のほうが約 10 倍速く署名できます。Apple M4 Max の 1 コアで、毎秒約 68,800 回対 6,500 回です。純粋な JavaScript では、状況がやや逆転します。@noble/curves 2.3.0 はどちらの曲線でも同じ速度で署名し、自己準同型写像のおかげで secp256k1 の検証は約 3 分の 1 速くなります。libsecp256k1 はさらに高速です。教訓として、曲線ではなく、実際に組み込んで出荷するライブラリのベンチマークを取ってください。

異なる慣習

同じ ECDSA の数学でも、それぞれの世界では異なるルールとともに使われます。

  • ハッシュ:P-256 は SHA-256 と組み合わせます(ES256、ecdsa-with-SHA256)。Bitcoin は二重 SHA-256 のダイジェストに署名し、Ethereum は Keccak-256 のダイジェストに署名します。Keccak-256 は SHA3-256 とは異なります。
  • エンコーディング:X.509 と TLS は DER を使い、JOSE と WebCrypto は生形式の r‖s を使います。Ethereum はリカバリバイト v を付加して合計 65 バイトにし、Bitcoin の署名付きメッセージは先頭にヘッダーバイトを置きます。
  • 公開鍵の復元:Ethereum は公開鍵を送信しません。公開鍵の復元(ecrecover)によって署名から鍵を計算し、アドレスを比較します。P-256 のプロトコルでは、代わりに鍵を送信するか、証明書で証明します。
  • low-S:ECDSA の署名 (r, s) には、必ず対になる (r, n − s) が存在します。Bitcoin のリレーポリシーと Ethereum のトランザクションルール(EIP-2)は low-S の形式だけを受け入れますが、汎用の P-256 検証者は両方を受け入れます。
  • Schnorr:Bitcoin の Taproot は、secp256k1 上で 32 バイトの x-only 鍵を使う 64 バイトの BIP-340 Schnorr 署名を追加しました。これは Nostr でも使われています。P-256 には標準化された Schnorr 方式はありません。

この展性は、Node.js と、OpenSSL に基づくその secp256k1 対応を使って簡単に確かめられます。

Node.js 26:secp256k1 では (r, s) と (r, n − s) の両方が検証に通る
import { generateKeyPairSync, sign, verify } from "node:crypto";

// Group order of secp256k1 (SEC 2 §2.4.1).
const n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;

const { privateKey, publicKey } = generateKeyPairSync("ec", { namedCurve: "secp256k1" });
const message = Buffer.from("hello");
const raw = sign("sha256", message, { key: privateKey, dsaEncoding: "ieee-p1363" });

const r = raw.subarray(0, 32);
const s = BigInt("0x" + raw.subarray(32).toString("hex"));
const flipped = Buffer.concat([r, Buffer.from((n - s).toString(16).padStart(64, "0"), "hex")]);

const check = (sig) => verify("sha256", message, { key: publicKey, dsaEncoding: "ieee-p1363" }, sig);
console.log("(r, s) and (r, n - s) both verify:", check(raw) && check(flipped));
console.log("exactly one of them is low-S:", (s <= n / 2n) !== (n - s <= n / 2n));
出力
(r, s) and (r, n - s) both verify: true
exactly one of them is low-S: true

OpenSSL は low-S の署名を生成することも強制することもないため、この方法で作成した署名は、Bitcoin や Ethereum に受け入れられる前に正規化しなければなりません。Ethereum 署名検証ツールは high-S の値を警告します。

それぞれの用途

  • Web、TLS、パスキー、JWT、モバイルとデバイスの鍵:P-256 を使います。プラットフォームがハードウェアで提供しているのは P-256 だけだからです。
  • Bitcoin、Ethereum と EVM チェーン、Nostr:secp256k1 を使い、そのチェーンのハッシュ、エンコーディング、復元、low-S のルールに従います。
  • 2 つの世界をつなぐ場合(たとえば、パスキーで制御するスマートコントラクトウォレット):それぞれの曲線は、それぞれの領域の中だけで使います。Ethereum の Fusaka アップグレード(2025年12月)では、アドレス 0x100 に置かれた、コストが 6,900 ガスの P-256 検証用プリコンパイル EIP-7951 が追加されました。また、多くのロールアップが互換性のある RIP-7212 プリコンパイルを提供しているため、コントラクトはパスキーの署名を直接検証できます。
  • 自由に選べる新しいプロトコル:どちらでもありません。ソフトウェアの鍵には Ed25519、ハードウェアで保護された鍵には P-256 を検討してください。

移行時の注意点

鍵を曲線間で変換することはできません。曲線を変更すると鍵が新しくなり、ブロックチェーンではアドレスも新しくなります。

  1. 鍵の素材を曲線間で使い回さない:32 バイトの秘密の値はどちらの曲線でも有効な秘密鍵になりますが、得られる公開鍵は互いに無関係です。これは機能ではなく、事故のもとと考えてください。
  2. secp256k1 の署名を HSM や KMS に移す場合:その鍵タイプが存在することを確認し、出力は DER になるものと想定して、生形式の r‖s に変換し、low-S に正規化します。リカバリ ID は、両方のパリティを試して自分で計算します。
  3. EVM コントラクトで P-256 の署名を検証する場合:検証済みのライブラリを使うか、チェーンが提供していればプリコンパイルを使います。Solidity でモジュラー演算を自作してはいけません。
  4. JOSE の場合:ES256K から ES256 に切り替えると、alg、crv、鍵が変わります。移行期間中は、異なる kid 値を付けて両方の鍵を公開してください。

参考資料

すべての比較