# [EIP7951] secp256r1プリコンパイルでApple Secure EnclaveやPasskeyをEthereumに繋ぐ仕組みを理解しよう！

> EIP7951は、0x100番地にP256VERIFYというプリコンパイルを追加します。このプリコンパイルは、secp256r1（別名P-256またはprime256v1）楕円曲線上でのECDSA署名検証を6900ガスで実行します。

- URL: https://cardene.dev/articles/a55db75ffbc68801d968
- 公開日: 2026-04-04
- 更新日: 2026-04-04
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は「**EIP7951**」についてまとめていきます。

secp256r1（P-256）楕円曲線のECDSA署名検証を6900ガスのプリコンパイルとしてEthereumに追加し、Apple Secure EnclaveやPasskeyを直接Ethereumに繋げるようにする仕組みを提案している提案です。

https://eips.ethereum.org/EIPS/eip-7951

## 概要

**EIP7951**は、`0x100`番地に`P256VERIFY`というプリコンパイルを追加します。
このプリコンパイルは、secp256r1（別名P-256またはprime256v1）楕円曲線上でのECDSA署名検証を6900ガスで実行します。

![P256VERIFY プリコンパイル概要](/images/qiita-5185198a-5f86-46d3-82c6-9b614f5583ef.webp)

| 項目 | 内容 |
|------|------|
| プリコンパイルアドレス | `0x100` |
| 操作名 | `P256VERIFY` |
| ガスコスト | 6900 gas |
| 入力サイズ | 160バイト |
| 出力サイズ | 32バイト（成功時） / 0バイト（失敗時） |
| 対象曲線 | secp256r1（P-256 / prime256v1） |

:::message
secp256r1とsecp256k1の違い

Ethereumが既存の署名検証（`ecrecover`）で使っているのはsecp256k1曲線です。
一方、secp256r1（P-256）はNISTが標準化した別の楕円曲線で、Apple Secure Enclave、Android Keystore、FIDO2/WebAuthnなどのセキュアハードウェアで広く採用されています。
この2つは互換性がなく、これまでEthereumはsecp256r1の署名をオンチェーンで効率的に検証できませんでした。
:::

**EIP7951**はLayer 2ネットワークで先行実装された**RIP7212**と同じインターフェースを持ちながら、**RIP7212**に含まれていた2つのセキュリティ上の問題を修正したEthereumメインネット向けの仕様です。

## 動機

#### セキュアハードウェアとの統合

現代のスマートフォンやセキュリティキーに内蔵されているセキュアハードウェアは、secp256r1を使って秘密鍵を管理しています。
代表的な例を以下に示します。

| デバイス / 技術 | 利用場面 |
|---------------|---------|
| Apple Secure Enclave | Face ID / Touch IDでの署名 |
| Android Keystore | 指紋・顔認証での署名 |
| HSM（ハードウェアセキュリティモジュール） | 企業の鍵管理 |
| TEE（信頼実行環境） | セキュアな計算環境 |
| FIDO2 / WebAuthn | パスキー認証 |

これらのデバイスはすべてsecp256r1で署名を生成します。
Ethereumが**P256VERIFY**プリコンパイルを持つことで、ユーザーはiPhoneのFace IDやPasskeyを使ってEthereumのトランザクションを直接承認できるようになります。
専用のウォレットアプリをインストールしなくても、デバイスに組み込まれた認証機能がウォレットの署名に使えるため、一般ユーザーの参入障壁が大幅に下がります。

#### Layer 2での先行実装とEIP化の経緯

![RIP7212 から EIP7951 への改善](/images/qiita-ed727b3f-f48c-4b13-8c6f-6ce3d86b59f0.webp)

secp256r1のプリコンパイルはすでにLayer 2ネットワークで**RIP7212**として実装・運用されていました。
しかし、**RIP7212**には2つの重大なセキュリティ上の欠陥が発見されました。
**EIP7951**は**RIP7212**と同じインターフェースを維持しながら、これらの脆弱性を修正してEthereumメインネットへの導入を提案するものです。

#### **RIP7212**の脆弱性

**RIP7212**には以下の2つの問題がありました。

1つ目は「無限遠点チェックの欠如」です。
署名検証中に復元した点R'が無限遠点（楕円曲線の加法における単位元）になるケースを確認していませんでした。
この問題は実装によって動作が異なる非決定論的な動作を引き起こし、コンセンサス障害の原因になる可能性がありました。

![脆弱性① 無限遠点チェックの欠如](/images/qiita-0190af8d-82d6-4011-bb8f-931ede96ec0e.webp)

2つ目は「剰余比較の誤り」です。
復元したR'のx座標`r'`と署名の`r`を比較する際、`r' == r`という単純な等値比較を行っていました。
しかし、x座標が曲線の位数`n`を超えるケースでは、`r' ≡ r (mod n)`というモジュラー比較が必要です。
この誤りによって、数学的に正しいECDSA検証の結果と異なる結果を返す可能性がありました。

![脆弱性② 剰余比較の誤り](/images/qiita-4d6fcb35-a4b9-4881-89a8-737ea52301e8.webp)

:::message
無限遠点とは

楕円曲線の加法演算における特別な点で、通常の整数演算における「0」に相当します。
点Pと-Pを加算した結果がこの無限遠点になります。
ECDSA検証では、復元した点R'が無限遠点になった場合に検証失敗として扱う必要がありますが、**RIP7212**はこのチェックを省略していました。
:::

## 仕様

#### プリコンパイルアドレスとガスコスト

P256VERIFYは`0x100`番地に配置され、ガスコストは6900ガスです。
既存の`ECRECOVER`プリコンパイル（3000ガス）との実測ベンチマークを行った結果、secp256r1の検証処理がより重いことが確認されたため、6900ガスに設定されました。

エラーや検証失敗時も同じ6900ガスを消費します。
プリコンパイルはいかなる状況でもrevertしません。

#### secp256r1カーブパラメータ

secp256r1はNISTがSP 800-186で標準化した楕円曲線で、以下のパラメータで完全に定義されます。

| パラメータ | 値 |
|-----------|-----|
| 基底体の法 `p` | `0xffffffff00000001...ffffffff` |
| 曲線係数 `a` | `0xffffffff00000001...fffffffc` |
| 曲線係数 `b` | `0x5ac635d8aa3a93e7...2604b` |
| 基点Gのx座標 | `0x6b17d1f2e12c4247...8c296` |
| 基点Gのy座標 | `0x4fe342e2fe1a7f9b...51f5` |
| 部分群の位数 `n` | `0xffffffff00000000...632551` |
| 補因子 `h` | `0x1` |

曲線方程式は `y² = x³ + ax + b (mod p)` です。

#### エンコーディング

P256VERIFYの入力に登場するデータは、種類によってエンコード方法が異なります。

##### スカラーのエンコーディング

スカラー（署名コンポーネント`r`、`s`やメッセージハッシュ`h`）は32バイトのビッグエンディアン符号なし整数として表現します。
フィールド要素とは異なり、整数値は部分群の位数`n`以下である必要はありません。

##### フィールド要素のエンコーディング

有限体Fpの要素（フィールド要素）は32バイトのビッグエンディアン符号なし整数として表現します。
エンコードする整数は法`p`未満でなければなりません。`p`以上の値は不正入力として扱います。

##### 点（Point）のエンコーディング

secp256r1上の点は、x座標とy座標をそれぞれ32バイトでエンコードして連結した64バイトで表現します。

##### 無限遠点のエンコーディング

secp256r1では座標`(0, 0)`はFp上の正当な点ではないため（曲線方程式を満たさない）、64バイトのゼロ列を無限遠点の表現として使います。

:::message
まとめると、`h`はスカラー、`r`・`s`はスカラー、`qx`・`qy`はフィールド要素（Fp上の値）として扱われます。
スカラーは位数`n`未満でなくてよい（バリデーションでチェック）、フィールド要素は法`p`未満が必須（チェック③）という違いがあります。
:::

#### P256VERIFYの入出力

##### 入力形式

`P256VERIFY`は160バイトの入力を受け取ります。入力は以下の順で連結されたバイト列です。

![P256VERIFY 入力バイトレイアウト](/images/qiita-e5aeb6c1-a2b9-4196-9f09-d2a30fa684f0.webp)

| フィールド | サイズ | 説明 |
|-----------|--------|------|
| メッセージハッシュ `h` | 32バイト | 検証対象のハッシュ値 |
| 署名コンポーネント `r` | 32バイト | ECDSA署名のrコンポーネント |
| 署名コンポーネント `s` | 32バイト | ECDSA署名のsコンポーネント |
| 公開鍵 x座標 `qx` | 32バイト | secp256r1公開鍵のx座標 |
| 公開鍵 y座標 `qy` | 32バイト | secp256r1公開鍵のy座標 |

##### 出力形式

署名検証の成否に応じて異なるバイト列を返します。

| 結果 | 出力 |
|------|------|
| 検証成功 | `0x0000...0001`（32バイト） |
| 検証失敗 / 不正入力 | 空のバイト列（0バイト） |

##### 入力バリデーション

プリコンパイルは以下の5つのチェックを順番に実施します。いずれかが失敗した場合は、それ以降のチェックをスキップして空のバイト列を返します。

![P256VERIFY 入力バリデーションフロー](/images/qiita-3c127132-13c3-4be9-af2d-73ea1e6965b6.webp)

1. **入力長チェック**
   - 条件: 入力が正確に160バイトであること
   - 意味: 5つのフィールド（各32バイト）が揃っているかを確認します

2. **署名コンポーネント範囲チェック**
   - 条件: `0 < r < n` かつ `0 < s < n`
   - 意味: `r`と`s`が曲線の位数`n`の範囲内にあるかを確認します。0や`n`以上の値は無効です

3. **公開鍵座標範囲チェック**
   - 条件: `0 ≤ qx < p` かつ `0 ≤ qy < p`
   - 意味: 公開鍵の座標が有限体Fpの範囲内にあるかを確認します。`p`以上の値は体の要素ではありません

4. **点の曲線上チェック**
   - 条件: `qy² ≡ qx³ + a·qx + b (mod p)`
   - 意味: 公開鍵`(qx, qy)`がsecp256r1の曲線方程式を満たすかを確認します。曲線上にない点は不正な公開鍵です

5. **無限遠点チェック**
   - 条件: `(qx, qy) ≠ (0, 0)`
   - 意味: 公開鍵が無限遠点でないかを確認します。無限遠点は公開鍵として使えません

#### 検証アルゴリズム

バリデーションを通過した後、以下の手順で署名を検証します。

![P256VERIFY 署名検証アルゴリズム](/images/qiita-1c99e6d0-63aa-4029-ae37-512bf6745f35.webp)

**ステップ1: `s`の逆数を計算**

```text
s1 = s^(-1) (mod n)
```

署名時に使ったランダム数`k`を打ち消すための逆数`s1`を法`n`で計算します。

**ステップ2: 署名時の点R'を復元**

```text
R' = (h * s1) * G + (r * s1) * (qx, qy)
```

メッセージハッシュ`h`、逆数`s1`、公開鍵`(qx, qy)`、基点`G`を組み合わせて、署名時に使われたランダム点R'を復元します。

:::message
`G`は曲線の基点（Generator point）で、secp256r1のパラメータとして固定された点です。
楕円曲線暗号では `公開鍵 = 秘密鍵 × G` の関係が成り立ちます。
:::

**ステップ3: 無限遠点チェック（RIP7212修正箇所）**

```text
if R' == 無限遠点:
    return（失敗）
```

復元したR'が無限遠点になった場合は検証失敗です。**RIP7212**ではこのチェックが省略されていたため、非決定論的な動作が発生する可能性がありました。

**ステップ4: x座標のモジュラー比較（RIP7212修正箇所）**

```text
r' = R'.x

if r' ≡ r (mod n):
    return 0x0000...0001（成功）
else:
    return（失敗）
```

R'のx座標`r'`を取り出し、署名の`r`とモジュラー比較します。**RIP7212**では `r' == r` の単純な等値比較だったため、x座標が位数`n`を超えるエッジケースで誤った結果を返す可能性がありました。

## 補足

#### なぜRecoveryではなくVerificationを使うのか

Ethereumの既存プリコンパイル`ECRECOVER`（secp256k1用）は、署名からアドレスを「復元（Recovery）」する設計です。
一方、P256VERIFYは既に分かっている公開鍵に対して署名が正しいかを「検証（Verification）」する設計です。

この設計選択には以下の理由があります。

セキュアハードウェアのほとんどは秘密鍵をデバイス外に取り出せない設計になっており、署名の検証インターフェースを提供します。
また、NISTのFIPS 186-5標準はVerificationを規定しており、Recoveryは定義されていません。
さらに、secp256r1ではRecoveryよりVerificationの方が効率的です。

#### **RIP7212**との互換性

**EIP7951**は**RIP7212**と以下の互換性を保っています。

| 項目 | **RIP7212** | **EIP7951** |
|------|----------|----------|
| プリコンパイルアドレス | `0x100` | `0x100` |
| 入力形式 | 160バイト | 160バイト |
| 出力形式 | 32バイト / 0バイト | 32バイト / 0バイト |
| ガスコスト | 3450 gas | 6900 gas |
| 無限遠点チェック | なし | あり |
| モジュラー比較 | なし | あり |

**RIP7212**向けに書かれたコントラクトコードは、ガスコストの違いを除けば変更なしで動作します。
セキュリティ修正は正しく実装された呼び出し元には透過的です。修正が影響するのは、本来失敗すべきエッジケースのみです。

6900ガスというコストはECRECOVER（3000ガス）とのベンチマーク比較を根拠としており、実装ベンチマークの正確性を優先した結果です。
そのため、**RIP7212**の3450ガスとの互換性は意図的に維持していません。

## セキュリティ考慮事項

#### 署名の可鍛性（Malleability）

secp256k1の署名は**EIP2**でnon-malleable（可鍛性なし）が要求されていますが、secp256r1はNIST FIPS 186-5標準においてnon-malleabilityを要求していません。

:::message
**EIP2**はEthereumの初期のプロトコル改善提案です。secp256k1の署名における`s`値の範囲を制限し、署名の可鍛性を防いでいます。
:::

:::message
署名の可鍛性とは

同一のメッセージと秘密鍵から、異なるバイト列を持つ「別の有効な署名」を作り出せる性質を指します。
ビットコインやEthereumの初期には、この性質を悪用したトランザクションIDの改ざんが問題になりました。
Ethereumはsecp256k1の`s`値を上限以下に制限することで対策しています。
:::

secp256r1の署名では、同一メッセージに対して複数の有効な署名が存在する可能性があります。
non-malleabilityが必要なアプリケーション（例えば署名をIDとして使う場合）は、アプリケーション層で追加のチェックを実装してください。

#### 定数時間アルゴリズムの非必須化

**EIP7951**は、プリコンパイルの実装が定数時間アルゴリズムを使うことを要求しません。
サイドチャネル攻撃（処理時間の差異から秘密鍵を推測する攻撃）への耐性はプリコンパイルレベルでは保証されません。
秘密鍵の保護はデバイスのセキュアハードウェアが担う想定です。

#### 暗号強度

secp256r1は約128ビットのセキュリティ強度を提供します。
Ethereumで既に使われているsecp256k1と同等の強度であり、曲線パラメータはNIST SP 800-186で標準化されています。
また、**RIP7212**としてのL2デプロイを通じて広範な暗号解析の実績があります。

## 互換性

**EIP7951**が有効化されても既存のコントラクトには影響しません。
`0x100`番地にコントラクトをデプロイしているケースは現実的に存在しないため、互換性上の問題は発生しません。

Layer 2で**RIP7212**を使って開発したコントラクトは、ガスコストの違い（3450→6900ガス）を除いてそのまま動作します。

## 参考実装

secp256r1はハードウェアセキュリティデバイスや標準ライブラリで広く採用されているため、**EIP7951**は専用のリファレンス実装を提供していません。
実装の参考が必要な場合は、NISTのFIPS 186-5（Digital Signature Standard）を参照してください。

## 引用

https://eips.ethereum.org/EIPS/eip-7951

## 最後に

いかがだったでしょうか？

今回は「**EIP7951**」についてまとめてきました。

**EIP7951**は、Apple Secure EnclaveやPasskey、FIDO2デバイスで使われるsecp256r1楕円曲線の署名検証を、6900ガスのプリコンパイルとしてEthereumに追加する提案です。
Layer 2で先行実装された**RIP7212**の2つのセキュリティ問題（無限遠点チェックの欠如・モジュラー比較の誤り）を修正しながら、既存の実装との完全なインターフェース互換性を保っています。
このプリコンパイルにより、iPhoneやAndroidの生体認証をEthereumの署名に直接使えるアカウント抽象化パターンが現実的なコストで実現でき、一般ユーザーの参入障壁が大きく下がることが期待されます。

他でも色々記事を書いているのでぜひよろしければ読んでいってください！

もし記事に間違いなどがあれば、コメントやTwitterのDMなどで教えていただけると嬉しいです。
これからも学んだことを記事にしていきますので、よろしければフォローお願いします！

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/
