要点:secp256k1 は、素数 p = 2²⁵⁶ − 2³² − 977 の素体上の楕円曲線 y² = x³ + 7 で、2000年に SEC 2 で定義されました。群の点の個数は素数(コファクター 1)で、約 128 ビットセキュリティが得られます。係数はシードから生成されたものではなく、速度を重視して選ばれました。a = 0 によって計算式が簡単になり、さらにこの曲線には効率的に計算できる自己準同型写像があるため、スカラー倍算を高速化できます。Bitcoin は 2009年の最初のコミットからこの曲線を使い、Ethereum がそれに続き、現在では Nostr や大半の EVM チェーンも使っています。WebCrypto、TLS 1.3、大半のセキュアハードウェアには含まれていません。ブロックチェーンのプロトコル以外では、P-256 か Ed25519 を選ぶことを推奨します。この曲線のエコシステムは、通常の ECDSA に独自のルールを加えています。Bitcoin のリレーポリシーと Ethereum のトランザクションでは low-S 署名だけを認めること、鍵を送信する代わりに公開鍵の復元を使うこと、そしてハッシュに Keccak-256 または二重 SHA-256 を使うことです。
パラメーター
SEC 2 v2.0 §2.4.1 は、この曲線を 6 つの数で定義しています(SEC 2)。
| パラメーター | 値 | 意味 |
|---|---|---|
| p | 2²⁵⁶ − 2³² − 977(0xffff…fffefffffc2f) | 有限体の素数。この形のおかげで p を法とする剰余演算が軽量 |
| a, b | 0, 7 | 曲線 y² = x³ + 7 |
| G | x = 0x79be667e…16f81798、y = 0x483ada77…fb10d4b8 | ベースポイント |
| n | 0xffff…fffebaaedce6af48a03bbfd25e8cd0364141 | G の位数(素数) |
| h | 1 | コファクター。無限遠点以外のすべての点が群全体を生成 |
ここには信用するしかない値はなく、由来が説明されていないシードから得られた値もありません。次のスクリプトは、Python の普通の整数だけで定数を確かめます。
G = (Gx, Gy)
print("p == 2^256 - 2^32 - 977:", hex(p) == "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f")
print("G is on y^2 = x^3 + 7: ", (Gy * Gy - Gx**3 - b) % p == 0)
print("n is a probable prime: ", probable_prime(n))
print("n * G is infinity: ", mul(n, G) is None)
t = p + 1 - n
print("trace t = p + 1 - n: ", t)
print("Hasse bound |t| <= 2*sqrt(p):", t * t <= 4 * p)
print("n < p, p - n has", (p - n).bit_length(), "bits: the group has n points, cofactor 1")
p == 2^256 - 2^32 - 977: True
G is on y^2 = x^3 + 7: True
n is a probable prime: True
n * G is infinity: True
trace t = p + 1 - n: 432420386565659656852420866390673177327
Hasse bound |t| <= 2*sqrt(p): True
n < p, p - n has 129 bits: the group has n points, cofactor 1
add と mul は、教科書どおりのアフィン座標による点の加算とバイナリ法(double-and-add)です(定数の確認には十分ですが、定数時間ではないため、秘密の値の処理には使えません)。トレースは SafeCurves が公開している値と一致します。n は素数で、点の個数に等しいため、小さな部分群は存在しません。Curve25519 を使うプロトコルを複雑にしているコファクターの問題は、この曲線にはありません。
パラメーターの形が高速化を生む理由
これらの数の次の 3 つの性質は、意図して選ばれたものです。
- 素数は 2²⁵⁶ から小さな数を引いた値なので、512 ビットの積の p を法とする剰余は、一般的な除算ではなく、2³² + 977 による数回の乗算で求められます。
- a = 0 により、点の 2 倍算のたびに乗算が 1 回不要になります。
- 自己準同型写像:p ≡ 1 (mod 3) なので、p を法とする 1 の 3 乗根 β が存在し、写像 (x, y) → (βx, y) はすべての点を曲線上の別の点に移します。この写像は、n を法とする 1 の 3 乗根 λ を掛けることと同じです。そのため、スカラー k を約 128 ビットずつの 2 つの値に k = k₁ + k₂λ と分割し、kP を k₁P + k₂(λP) として計算できます。ここで λP のコストは有限体の乗算 1 回です。これが GLV 法(Gallant、Lambert、Vanstone、2001年)です。
同じスクリプトで β と λ を求め、この恒等式を確かめます。
print("p % 3 == 1 (cube roots of unity exist mod p):", p % 3 == 1)
beta = pow(2, (p - 1) // 3, p)
lam = pow(3, (n - 1) // 3, n)
# Pick the lambda that matches this beta (there are two non-trivial cube roots of unity mod n).
if mul(lam, G) != (beta * Gx % p, Gy):
lam = lam * lam % n
print("beta^3 == 1 mod p, lambda^3 == 1 mod n:", pow(beta, 3, p) == 1, pow(lam, 3, n) == 1)
print("lambda * G == (beta * Gx, Gy): ", mul(lam, G) == (beta * Gx % p, Gy))
print("beta =", hex(beta))
print("lambda =", hex(lam))
p % 3 == 1 (cube roots of unity exist mod p): True
beta^3 == 1 mod p, lambda^3 == 1 mod n: True True
lambda * G == (beta * Gx, Gy): True
beta = 0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee
lambda = 0x5363ad4cc05c30e0a5261c028812645a122e22ea20816678df02967c1b23bd72
これらは libsecp256k1 が使っている定数です。Hal Finney は 2011年2月の bitcointalk のスレッドで Bitcoin 向けにこの手法を説明し、検証が 20〜25% 速くなると見込みました。彼のコードは、Pieter Wuille による libsecp256k1 の出発点になりました。Bitcoin Core は PR #20147(Bitcoin Core 0.21、2021年)での libsecp256k1 の更新とともに GLV 最適化をデフォルトで有効にし、その PR では、署名の検証を有効にした状態で初期同期が約 16% 速くなったと測定されています。これがどの程度効くかはライブラリによって異なります。OpenSSL には secp256k1 専用のコードがなく、P-256 のほうがはるかに高速です(数値は P-256 と secp256k1 の比較にあります)。
Bitcoin と Ethereum が採用している理由
Bitcoin の最初のコミット(2009年8月)は、OpenSSL の EC_KEY_new_by_curve_name(NID_secp256k1) で鍵を作成しています。サトシ・ナカモトは、この選択の理由を一度も説明していません。よく挙げられる理由は、利用者本人の発言ではなく、曲線そのものの性質です。Bitcoin wiki には、この曲線が「特に効率的な計算を可能にする、ランダムではない特殊な方法で構成された」こと、そして「広く使われている NIST 曲線とは異なり、secp256k1 の定数は予測可能な方法で選ばれた」ことが記されています(Bitcoin wiki)。Ethereum は Bitcoin の曲線をそのまま使い、ハッシュ関数を置き換えました。トランザクションとメッセージは Keccak-256 でハッシュされ、アドレスも Keccak-256 を使って鍵から導出されます。
標準化団体の対応は後からでした。RFC 8812(2020年)は JOSE と COSE 向けに ES256K を登録し、NIST SP 800-186(2023年)§H.2 は、この曲線について「ブロックチェーン関連のアプリケーションでの使用が認められる」と述べています(一般用途に推奨される曲線には含まれていません)。
安全性の水準
この群の離散対数問題に対して知られている最良の攻撃は Pollard の rho 法で、SafeCurves によれば約 0.886·√n ≈ 2¹²⁷·⁸ 回の加算が必要です。これは P-256 と同じ数値で、いわゆる「128 ビットセキュリティ」に当たります。(この曲線では、位数 3 の自己準同型写像を使って rho 法をさらに少し、約 √3 倍高速化できますが、全体の見通しは変わりません。)SafeCurves は secp256k1 を「安全」な曲線に分類していませんが、その理由は離散対数ではなく実装にかかわるものです。
| SafeCurves の基準 | secp256k1 | 実務上の意味 |
|---|---|---|
| rho 法、転送攻撃(transfer)、剛性(rigidity) | 合格 | 離散対数は困難で、定数の由来も説明されている |
| CM 判別式 | 不合格(D = −3) | 判別式が小さいことが自己準同型写像を生む要因。そこから導かれる攻撃はない |
| ツイスト安全性 | 合格、2¹⁰⁹·⁵ | 曲線本体より低い。点の検証を省くコードでのみ問題になる |
| ラダー、完全な加算公式 | 不合格 | 定数時間で例外のないコードを書くには、Curve25519 より注意が必要 |
| 識別不能性 | 不合格 | 点がランダムな文字列に見えない。検閲耐性のあるプロトコルでは問題になるが、署名には無関係 |
実務上の教訓は P-256 の場合と同じです。この曲線のために書かれたライブラリ(libsecp256k1、@noble/curves、Rust の k256、go-ethereum のバインディング)を使い、入力された公開鍵を検証してください。秘密の値を扱うスカラー倍算は、決して自作してはいけません。点として受け取った鍵は、使う前に y² = x³ + 7 上にあることを確認しなければなりません。これを省くと典型的な無効曲線攻撃が成立し、ツイストの数値が関係してくるのはこの場面です。また、すべての ECDSA 曲線と同様に、nonce が繰り返されたり偏っていたりすると鍵が漏洩します(ECDSA の nonce 再利用)。
鍵とアドレス
秘密鍵は [1, n − 1] の範囲の数 d で、公開鍵は点 Q = dG です。同じ Q にも複数の表記方法があり、それらを取り違えることが「アドレスが違う」というバグのよくある原因になっています。次の例では d = 1 を使います。その公開鍵は G そのものです(誰もが知っている鍵なので、決して使ってはいけません)。
compressed (33 bytes): 0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
uncompressed (65 bytes): 0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8
x-only, BIP-340 (32 bytes): 79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Ethereum address, keccak256(x || y)[12:]: 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
Bitcoin P2PKH from the compressed key: 1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH
Bitcoin P2PKH from the uncompressed key: 1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm
- 圧縮形式(SEC 1 §2.3.3):y が偶数なら
02、奇数なら03を先頭に置き、続けて x を置きます。Bitcoin は 10 年以上前から、デフォルトで圧縮形式の鍵を使っています。展開には p を法とする平方根が必要ですが、この p(p ≡ 3 mod 4)では 1 回のべき乗 y = (x³ + 7)^((p+1)/4) で求められます。 - 非圧縮形式:
04 ‖ x ‖ yです。この形式では、同じ鍵から別の Bitcoin アドレスが得られます。非圧縮形式の鍵を使っていた古いウォレットが、同じ秘密鍵に対して別のアドレスを表示するのはこのためです。 - x-only(32 バイト):Taproot と Nostr は BIP-340 の Schnorr 署名を使います。その鍵は y 座標を省き、y が偶数であることを前提とします。
- Ethereum アドレス:
04プレフィックスを除いた 64 バイトのx ‖ yに対する Keccak-256 の末尾 20 バイトです。Keccak-256 は SHA3-256 とは異なります。パディングが違うため、hashlib.sha3_256を使うと誤ったアドレスになります。
low-S:1 つの署名に 2 つの有効な形式
ECDSA 署名 (r, s) には、同じように検証に通る対の署名 (r, n − s) があります。nonce の点の符号を反転しても r は変わらないからです。TLS で P-256 を使う場合は、誰も気にしません。しかし Bitcoin では問題になりました。対の署名からは同じトランザクションに対して別のトランザクション ID が生じ、ID で支払いを追跡していたサービスが欺かれるおそれがあったのです。Bitcoin Core 0.11.1(2015年)では、ノードが「リレーやマイニングの際に、ECDSA 署名に正規の『low-s』エンコーディングを要求する」ようになりました(リリースノート)。これはコンセンサスルールではなく、リレーポリシーです。Ethereum は EIP-2(Homestead、2016年)でさらに踏み込み、「s の値が secp256k1n/2 より大きいトランザクション署名は、今後すべて無効とみなされる」と定めました。ただし、「ECDSA の recover プリコンパイル済みコントラクトは変更されず、引き続き high-S の値を受け入れる」ともしています。
汎用ライブラリはこれらのルールに従いませんが、ブロックチェーン向けのライブラリは従います。実験では、Node.js の OpenSSL で 1,000 件のランダムなメッセージに署名し、そのうち 1 つの high-S 署名と、それと対になる low-S 署名を 3 つのライブラリで検証しました。
const { msg, sig, s } = sample;
const low = Buffer.concat([sig.subarray(0, 32), Buffer.from((n - s).toString(16).padStart(64, "0"), "hex")]);
const opensslOk = (x) => verify("sha256", msg, { key: publicKey, dsaEncoding: "ieee-p1363" }, x);
console.log(`node:crypto verify high-S: ${opensslOk(sig)} low-S twin: ${opensslOk(low)}`);
console.log(`noble default high-S: ${secp256k1.verify(sig, msg, pub)} low-S twin: ${secp256k1.verify(low, msg, pub)}`);
console.log(`noble lowS: false high-S: ${secp256k1.verify(sig, msg, pub, { lowS: false })}`);
OpenSSL 3.5.8: 494 of 1000 signatures have s > n/2
node:crypto verify high-S: true low-S twin: true
noble default high-S: false low-S twin: true
noble lowS: false high-S: true
ethers 6.17.0 v=27 high-S: non-canonical s; use ._s
OpenSSL の署名の約半分は high-S で、OpenSSL は両方の形式を受け入れます。@noble/curves は、特に指定しない限り secp256k1 の high-S 署名を拒否し、ethers は high-S 署名から署名オブジェクトを作成することさえ拒否します。汎用ライブラリや HSM で署名してブロックチェーンに送信するシステムは、正規化しなければなりません。s > n/2 であれば s を n − s に置き換え、リカバリビットを反転します。Google Cloud KMS は、secp256k1 鍵が low-S 署名だけを返すことをドキュメントに明記しています。一方、AWS KMS の ECC_SECG_P256K1 のドキュメントにはそのような保証がないため、出力を自分で正規化してください。これらの署名をやり取りする際の DER 形式と生形式のエンコーディングについては、ECDSA 署名の形式で解説しています。
公開鍵の復元
署名、メッセージのハッシュ値、そして 1〜2 ビットの追加情報があれば、署名者の公開鍵を計算できます。そのため Ethereum のトランザクションには公開鍵がまったく含まれず、Bitcoin の署名付きメッセージはアドレスに対して検証されます。追加のビットはリカバリ ID です(v、yParity、または Bitcoin のメッセージ署名のヘッダーバイト)。復元の仕組み、復元で起こりうる問題、ecrecover の挙動については、ECDSA 公開鍵の復元で解説しています。
対応状況
| プラットフォーム | secp256k1 |
|---|---|
WebCrypto(ブラウザー、Deno、Node の crypto.subtle) | 非対応。@noble/curves か、libsecp256k1 の WASM ビルドを使用 |
OpenSSL と Node.js の crypto | 対応(汎用の、より低速なコード) |
| BoringSSL | 非対応:組み込まれているのは P-224、P-256、P-384、P-521 のみ |
| Java(SunEC) | JDK 16 で削除:Curve not supported: secp256k1。Bouncy Castle を使用 |
| Apple CryptoKit、Secure Enclave、Android Keystore、TPM 2.0、PIV カード | 非対応 |
| クラウド KMS | AWS KMS の ECC_SECG_P256K1、Google Cloud KMS の EC_SIGN_SECP256K1_SHA256(HSM による保護のみ)、Azure Key Vault の P-256K(ES256K で使用) |
| TLS 1.3、Web PKI の X.509、WebAuthn | 非対応 |
スマートフォンやノートパソコンのセキュアハードウェアが対応していないため、モバイルウォレットは secp256k1 鍵をソフトウェアで保持する(ハードウェア内にある別の鍵で保護する)か、別体のハードウェアウォレットに頼っています。
よくある間違い
- Keccak-256 ではなく SHA3-256 でハッシュする:Ethereum での誤りです。Bitcoin で、メッセージとトランザクションに使われる二重 SHA-256 の代わりに単一の SHA-256 を使うのも同じ誤りです。署名は別のメッセージに対して検証され、復元すると、有効に見える別のアドレスが得られます。
- high-S 署名を送信する:OpenSSL、HSM、クラウド KMS で作成した high-S 署名を、そのまま Ethereum や Bitcoin のネットワークに送信する誤りです。先に正規化してください。
- 圧縮形式と非圧縮形式の鍵を混同する:Bitcoin アドレスを導出する際に起こる誤りです。
- 生形式のバイト長:64 バイトの
r ‖ sと 65 バイトのr ‖ s ‖ vは別の形式です。ライブラリはこれをはっきりとエラーで知らせます。ethers の invalid raw signature length がその例です。 - プラットフォームの対応を当然と考える:Java 11 で動作していたコードは Java 17 で失敗します。WebCrypto には、この曲線が一度も含まれたことがありません。
- 必要のない場面で secp256k1 を使う:トークン、TLS、デバイスの ID、パスキーでは利点がなく、ハードウェアの対応も失われます。
出典
- SEC 2 v2.0:推奨される楕円曲線ドメインパラメーター、§2.4.1。SEC 1 v2.0、§2.3.3(点のエンコーディング)
- NIST SP 800-186、付録 H.2
- SafeCurves:rho、判別式、ツイストの各表
- Gallant、Lambert、Vanstone『Faster Point Multiplication on Elliptic Curves with Efficient Endomorphisms』、CRYPTO 2001。Hal Finney による署名検証の高速化に関するスレッド(2011年)。Bitcoin Core PR #20147
- Bitcoin Core 0.11.1 のリリースノート(low-S のリレーポリシー)。EIP-2
- RFC 8812(
ES256K) - 鍵管理サービス:AWS KMS の鍵仕様、Google Cloud KMS のアルゴリズム、Azure Key Vault の鍵の種類