# [ERC5630] Ethereumウォレットで暗号化と復号化を行う仕様を理解しよう！

> ERC5630は、Ethereumの鍵（アカウントが持つ秘密鍵/公開鍵の組）を使って暗号化と復号を行う新しい方法を提案しています。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、Ethereumウォレットの鍵を使って安全にメッセージを暗号化・復号できる共通ルールを定めた提案している**ERC5630**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC5630**は、Ethereumの鍵（アカウントが持つ秘密鍵/公開鍵の組）を使って**暗号化**と**復号**を行う新しい方法を提案しています。
ここで使う楕円曲線（公開鍵暗号で使う数学的な土台）は、Ethereumの署名で採用されている**secp256k1**のみです。
提案の中心は2つの新しいRPCメソッドです。
RPCは「**リモート手続き呼び出し**」のことで、ウォレットやノードに対して決められた関数を呼ぶための標準的なインターフェースです。

* `eth_getEncryptionPublicKey`
    暗号化に使う公開鍵を取得します。
    これにより、送信者は受信者の鍵に合わせた暗号化ができます。
* `eth_performECDH`
    ウォレットが**ECDH**（楕円曲線版ディフィー–ヘルマン鍵共有。二者間で共通の秘密を安全に作る手順）だけを実行します。
    これで共有秘密が得られ、復号処理に進めます。

この2つを組み合わせると、ユーザーは暗号化されたデータを受け取れるようになり、受け取った側は自分のウォレットで復号できるようになります。
重要な方針として、ウォレットはECDHという核心部分の計算のみを担当し、その後の**ECIES**（楕円曲線統合暗号方式。共有秘密から実際の暗号文を作ったり復号したりする一連の手順）は実装者に任せます（推奨の標準形は示しますが、固定はしません）。

従来の一部のEIPでは、署名と暗号化で**同じ秘密鍵**を使い、しかも2つの異なる曲線（secp256k1 と ec25519）にまたがって運用し、さらにECIESの方式を固定していました。
**ERC5630**は、曲線をsecp256k1に統一し、ウォレット側は最小限のECDHだけに絞るという設計により、役割分担を明確にします。

## 動機

まず、動機はEthereumアドレス宛ての直接暗号化です。
**ERC5630**を使うと、送信者は事前に相手との直接チャネル（個別の通信路）を用意していなくても、オンチェーンで特定の受信者に暗号化メッセージを届けることができます。
ここで標準化するのは**暗号文（ciphertext）の作り方**だけであり、**オンチェーンでどう送るか**までは規定しません。
理想的にはこの用途のためのスマートコントラクト基盤が整備されるべきですが、もしそれがなければ、各トランザクションにある生データ欄（raw data field）を使って暗号文を載せることもできます。

次に、よくある設計パターンとして、dAppがユーザーのために新しい秘密（例：新規鍵や共有秘密）を生成するケースがあります。
ユーザーにその新しい秘密を別途保管・バックアップさせるのは負担が大きく、紛失の原因にもなります。
そこで**ERC5630**では、その新しい秘密をユーザーがコントロールする公開鍵（対応する秘密鍵はユーザーの**HDウォレット階層**の中にある、という意味です。
HDウォレットは**階層的決定性ウォレット**で、1つの**シードフレーズ**から多数の鍵を体系的に導出できる仕組み）に対して暗号化し、暗号文だけを安全な場所（例：オンチェーン）に保存しておくことができます。
こうすると、新しい秘密の安全性をユーザーが既に守っているシードフレーズの安全性に乗せ替えることができます。
ユーザーは新たな鍵素材を別管理する必要がなく、ユーザー体験（UX）の負担が大幅に下がるという利点があります。
このデザインパターンは、**Tornado Cash**をはじめとする複数のdAppで既に使われています。

## 仕様

#### 楕円曲線と暗号方式の選択

**ERC5630**では、Ethereumの署名と同じ「**`secp256k1`**」という楕円曲線を、暗号化にもそのまま使います。
`secp256k1` は、楕円曲線暗号（ECC）の一種で、EthereumやBitcoinで馴染みのある曲線です。

暗号化の方式としては「**ECIES**」を使います。
ECIES は、楕円曲線暗号を使ったハイブリッド暗号方式で、ざっくりと以下のような構成です。

* 楕円曲線ディフィー–ヘルマン（ECDH）で「**共有秘密**」を作る
* その共有秘密から鍵導出関数（KDF）で共通鍵を作る
* 共通鍵でAESのような共通鍵暗号を使って実際のデータを暗号化する
* 必要に応じてHMACのようなメッセージ認証方式で改ざん検知も行う

**ERC5630**が重要視しているのは、ウォレットに「**できるだけシンプルで、安全性の高い役割だけをさせる**」という点です。
具体的には、ウォレットには「**ECDH（楕円曲線ディフィー–ヘルマン）のコア計算**」だけを担当させます。
ECDHとは、自分の秘密鍵と相手の公開鍵から、2者にだけわかる共通の秘密値を計算する手順のことです。

ウォレットはこの「**ECDHの秘密値を計算する部分**」だけを行い、それより周りの処理、つまり、以下の部分はdAppやライブラリなどの実装者に任せる形にしています。

* ECIESの構成（どのKDF・HMAC・AESモードを使うか）
* 暗号文の構造
* 復号処理の組み立て

これにより、ウォレット側の実装を軽くしつつ、アプリ側は必要に応じてECIESのバリアントを選べる、という柔軟な設計になっています。

#### データ表現とシリアライズ方式

**ERC5630**では、通信やRPCのパラメータとして扱うデータ形式を、かなり厳密に指定しています。
すべてのバイナリデータは「`0x`」で始まる16進文字列としてやり取りします。

代表的なものは以下です。

* バイナリデータ全般
    0xプレフィックス付きの16進文字列
* 秘密鍵・公開鍵
    同じく0xプレフィックス付きの16進文字列で表現
* 公開鍵の形式
    圧縮形式（compressed form）で表現
* Ethereumアカウント
    通常どおり、`0x` から始まる20バイトの16進文字列（アドレス）

ここで出てくる「**公開鍵の圧縮形式**」とは、楕円曲線上の点（x, y）のうち、x座標と「**y座標が上側か下側か（偶数か奇数か）**」だけを符号化する方式です。
これにより、公開鍵のサイズが33バイト程度に圧縮されます。

楕円曲線の点をシリアライズ（バイト列に変換）したり、逆にデシリアライズ（バイト列から点を復元）したりする方法は、「**SEC 1: Elliptic Curve Cryptography**」という標準仕様（Daniel R. L. Brownによる文書）のセクション2.3.3に従うことが義務付けられています。

このとき、デシリアライズに関しては以下の条件が必須です。

- 入力のバイト列の長さは、⌈log₂q / 8⌉ + 1 = 33 バイトでなければなりません。
   ここでqは楕円曲線の位数ですが、secp256k1ではこの式の結果が33バイトになります。
- 最初の1バイト目は、`0x02` または `0x03` である必要があります。
   これは圧縮形式の公開鍵を表すプレフィックスで、0x02はy座標が偶数、0x03は奇数といった区別に使われます。
- 残りの32バイトは整数とみなされ、その値が `0` 以上 `p−1` 以下の範囲に入っている必要があります。
   `p` はsecp256k1で使われる素数で、この範囲に入らない値は無効です。
- さらに、そのx座標が、楕円曲線の方程式 $X³ + 7（mod p）$ に対して「**平方剰余（quadratic residue）**」になる必要があります。
   平方剰余というのは、「**何かの値を2乗した結果として得られる値かどうか**」という性質で、これを満たすことでちゃんと楕円曲線上に実在する点であることを確認します。

このようにして、圧縮形式の公開鍵が正しい形式かどうか、ウォレットやライブラリがしっかり検証することが求められています。

#### 推奨されるECIESバリアント

ECIES自体は枠組みだけが決まっていて、中でどのKDF・HMAC・暗号方式を使うかはバリアントによって異なります。
**ERC5630**では、「**特に理由がなければ、この組み合わせを使ってください**」という推奨構成を示しています。

推奨されている構成は以下のとおりです。

* KDF（鍵導出関数）：ANSI-X9.63-KDF + SHA-512
    * ANSI-X9.63-KDF は、楕円曲線暗号と一緒に使うことを想定した鍵導出関数の標準仕様です。
    * SHA-512はハッシュ関数で、512ビットの出力を持つSHA-2系の一種です。
* HMAC：HMAC–SHA-256–256、キー長32オクテット（256ビット）
    * HMAC は「**メッセージ認証コード**」の一種で、改ざん検知のために使われます。
    * SHA-256を内部で使い、出力長256ビット、キー長256ビットで構成することを意味しています。

* 共通鍵暗号：AES–256 in CBC mode
    * AES–256 は、鍵長256ビットの共通鍵暗号（ブロック暗号）です。
    * CBCモードは、ブロック暗号に連鎖を持たせて暗号化する運用モードで、初期化ベクトル（IV）を用いて安全性を高めます。

また、暗号文（ciphertext）の具体的なシリアライズ形式については、以下のようにしています。

* ECIESの暗号文は、「**バイナリデータをそのまま連結した形式**」を使う
* このとき、暗号文内に含める楕円曲線の点（例えば送信側の一時公開鍵）は、圧縮形式で表現する

つまり、暗号文は「**圧縮形式の公開鍵**」や「**実際の暗号化データ**」、「**タグ（HMACなど）**」をバイナリのまま順番に並べ、その全体を0xプレフィックス付き16進文字列として扱うという方針です。

#### `eth_getEncryptionPublicKey`

このメソッドは、「**特定のEthereumアドレスが持つ秘密鍵に対応する暗号化用公開鍵を取得する**」ためのRPCメソッドです。
実際には署名用と同じ鍵ですが、それを「**暗号化のためにも使う**」と明示的に取り出すイメージです。

呼び出し例は以下のようになります。

```js
request({
  method: 'eth_getEncryptionPublicKey',
  params: [account]
})
```

ここで `account` は、0xで始まる20バイトの16進文字列（通常のEthereumアドレス）です。

クライアント（ウォレット）は、このリクエストを受け取ると、以下の手順で処理します。

1. `account` に対応する秘密鍵 `sk` を探します。
    そのアカウントに対応する秘密鍵がウォレット内に存在しない場合は、エラーを返します。
2. 見つかった秘密鍵 `sk` から、secp256k1曲線上の公開鍵を計算します。
    これは通常のEthereum署名で使っている計算と同じです。
3. 得られた公開鍵を、SEC 1のセクション2.3.3に従って「**圧縮形式の楕円曲線点**」としてシリアライズします。
    その上で、0xプレフィックス付きの16進文字列として返します。

このメソッドの返り値は、「**暗号化に使える公開鍵**」であり、これを使ってdAppや他のユーザーがそのアドレス宛てにECIES暗号文を作れるようになります。

#### `eth_performECDH`

もう1つのメソッド `eth_performECDH` は、「**ウォレットに対してECDHの秘密値計算だけを依頼する**」ためのRPCメソッドです。
ECDHは、自分の秘密鍵と相手の公開鍵から共有秘密（2者しか知らない値）を計算する仕組みです。

呼び出し例は以下のようになります。

```javascript
request({
  method: 'eth_performECDH',
  params: [account, ephemeralKey]
})
```

ここで `account` は先ほどと同じEthereumアドレス、`ephemeralKey` は「**一時的に使われる公開鍵**」を表す楕円曲線の点です。
`ephemeralKey` は、先ほどの仕様どおり、圧縮形式の楕円曲線点を0xプレフィックス付きの16進文字列で表したものです。

クライアント（ウォレット）は、以下の手順で処理します。

1. `account` に対応する秘密鍵 `sk` を探します。
    対応する秘密鍵が存在しなければ、エラーを返します。
2. `ephemeralKey` を、SEC 1のセクション2.3.3に従って楕円曲線の点としてデシリアライズします。
    ここで、圧縮形式であることが必須であり、形式が不正であればエラーを投げます。
    先ほど説明した長さ・先頭バイト・範囲チェック・平方剰余チェックなどが行われます。
3. SEC 1のセクション3.3.1に従って、ECDHの共有秘密を計算します。
    これは、「**秘密鍵 `sk` と公開鍵 `ephemeralKey` のスカラー倍**」を計算し、その結果から共有秘密となる値（フィールド要素）を取り出す処理です。
4. 得られた共有秘密を、SEC 1のセクション2.3.5に従ってフィールド要素としてシリアライズします。
    その上で、0xプレフィックス付き16進文字列として返します。
    大きさは32バイト固定です。

このメソッドの返り値は、「**ECDHで得られた共有秘密**」を表す32バイトの値です。
アプリ側は、この値を入力としてKDF（前述のANSI-X9.63-KDF + SHA-512など）を実行し、そこから共通鍵を導出し、暗号化・復号に使うことができます。

ウォレットは「**秘密鍵を外部に出さない**」、「**ECDH部分だけを安全に実行する**」という役割に徹し、それ以外のECIESロジックはすべてアプリ側に任される構成になっています。

#### スマートコントラクトへの暗号化

ここまでは**EOA**、つまり人間が持つウォレットアドレス宛ての暗号化を想定していました。
しかし、最近では**EIP4337**に代表されるアカウントアブストラクションや、スマートコントラクトウォレットの普及により、「**アドレスがスマートコントラクト**」であるケースも増えています。

:::message
**ERC4337**については以下の記事を参考にしてください。
:::

[\[ERC4337\] プロトコルの変更をせずにAAを実装する仕組みを理解しよう！](/articles/8c97ca7c93c4557ebfc2)

そこで**ERC5630**では、「**スマートコントラクトが自分宛ての暗号化方法を広告できる仕組み**」も提案しています。
これは、署名検証の標準インターフェイスである**EIP1271**の「**暗号化版**」のような位置づけです。
EIP-1271は「このコントラクトにとって有効な署名かどうか」をコントラクト自身に判断させるための標準ですが、ここでは「**このコントラクト宛ての暗号文をどう作ればよいか**」を表現します。

:::message
**EIP1271**については以下の記事を参考にしてください。
:::

[\[ERC1271\] コントラクトで署名を検証する仕組みを理解しよう！](/articles/da237b1b8cc185491512)

提案されているインターフェイスは、以下のようなSolidityコントラクトです。

```js
pragma solidity ^0.8.0;

contract ERC5630 {
  /**
   * @dev Should return an encryption of the provided plaintext, using the provided randomness.
   * @param plaintext      Plaintext to be encrypted
   * @param randomness     Entropy to be used during encryption
   */
  function encryptTo(bytes memory plaintext, bytes32 randomness)
    public
    view
    returns (bytes memory ciphertext);
}
```

* コントラクトは `encryptTo` という関数を実装することができます。
    これは「**与えられた平文 `plaintext` を、与えられた乱数 `randomness` を使って暗号化し、その結果 `ciphertext` を返す**」関数です。
* `plaintext` は、暗号化したい元データを任意のバイト列として渡します。
* `randomness` は暗号化に使う乱数や追加のエントロピー（予測不能な値）で、暗号の安全性を高めるために使われます。
    例えば、ECIESで使う一時鍵の生成やIVの生成などに利用できるイメージです。
* 各コントラクトは、この `encryptTo` を好きな方式で実装して問題ありません。
    ただし、特に理由がない限り、**ERC5630**で推奨しているECIESバリアント（ANSI-X9.63-KDF + SHA-512、HMAC–SHA-256–256、AES-256-CBC、圧縮公開鍵＋連結シリアライズ）を使うことが望ましいとされています。

この仕組みにより、スマートコントラクト側は「**このコントラクト宛ての暗号文は、こういう形で作ってね**」というルールを自分で定義し、それを `encryptTo` という形で外部に公開できる

送信側のアプリケーションは、コントラクトを呼び出して `encryptTo` を実行することで、「**コントラクトが期待する形式の暗号文**」を直接得ることができ、暗号方式の食い違いを防げます。

## 補足

#### 曲線と鍵の再利用に関する安全性

**ERC5630**は、署名と暗号化の両方に `secp256k1` を使い、さらにそれらで同じ秘密鍵を利用します。
これに対し、過去のEIPの一部では、以下の構成が採られていました。

* 署名：`secp256k1`
* 暗号化：`ec25519`
* さらに両方で同じ秘密鍵を使用

しかし、この方式には大きな問題があります。
「**異なる楕円曲線で同じ秘密鍵を使う暗号方式**」には安全性の証明が存在しないため、攻撃が知られていないとしても、リスクの評価自体が難しい状態になります。

**ERC5630**が採用する「**同じ曲線（secp256k1）上で、署名（ECDSA）と暗号化（ECIES）を行い、同じ秘密鍵を使う方式**」は、1つの曲線上で統一されている点で状況が異なります。
この構成は過去の研究で既に扱われており、例えば以下の研究で安全性が示されています。

* **Degabriele, Lehmann, Paterson, Smart, Strefler (2011)**
    「**EMVの暗号方式における、署名と暗号化を同じ鍵で行う構成の安全性&&」を分析した研究です。
    この中で、ECDSAとECIESを同じ曲線上で扱う方式は「**一般群モデルにおいて安全**」と評価されています。

さらに、この方式は実際に**EMV決済システム**でも使われています。
つまり、理論面と実運用の両面から、同一曲線・同一鍵で署名と暗号化を行う構成が実績を持っていると言えます。

#### オンチェーンでの公開鍵取得について

**ERC5630**の大きな利点として、「**アカウントが一度でもトランザクションに署名していれば、その公開鍵をオンチェーンデータから復元できる**」という点があります。

Ethereumの署名（ECDSA）は、署名値から公開鍵を復元できる仕組みになっています。
これは、署名アルゴリズムの特性で、署名に含まれる情報から公開鍵を逆算できるためです。

つまり、データが以下の状態なら暗号化が可能になります。

* アカウントが過去に1度でもトランザクションを送信している
* その署名をブロックチェーンから読み取れる
* 署名から `secp256k1` の公開鍵を復元できる

これにより、送信者は「**相手の公開鍵を知るための特別なチャネル**」を必要としません。

#### ECDHのみをウォレット側で担当する理由

**ERC5630**では、ウォレットが行う処理をECDHの秘密値計算部分だけに絞っています。
これには以下の2つの理由があります。

##### 柔軟性

ECIESは「**構造の枠組み**」だけが規定されており、内部で使う KDF・HMAC・暗号方式などは複数の選択肢があります。
ウォレット側にECIES全体を固定してしまうと、実装者が別のバリアントを選べなくなり、アップデートも難しくなります。
ECDHだけをウォレットに任せる設計なら、実装者はアプリ側で自由にECIESバリアントを選べます。

##### 軽量なデータ通信

ウォレットが扱うデータ量が32バイト程度に収まり、以下のような状況で大きな利点となります。

* 平文や暗号文が大きい
* クライアントとウォレットの間がネットワーク越しで、通信量を減らしたい

例えば、スマホウォレットやブラウザ拡張など、通信量が小さいほうが扱いやすい環境で恩恵があります。

#### `secp256k1` に関する「ツイスト攻撃」について

Christian Lundkvist 氏のGitHub投稿で、`secp256k1` を使う時の「**ツイスト攻撃**」の懸念が指摘されています。
ツイスト攻撃とは、曲線の「**ツイスト（捻れ）と呼ばれる別の曲線**」を悪用して、相手の秘密値の情報を引き出す攻撃です。

**ERC5630**は、この攻撃が成立しない理由を次のように説明しています。

##### ツイスト攻撃は「古典的ECDH」のみが対象でありECIESには当てはまらない

ツイスト攻撃が成立する条件は以下のとおりです。

* 攻撃者が細工した点を被害者に渡す
* 被害者がその点に対して秘密鍵を使ったスカラー倍（“秘密値の乗算”）を行う
* 被害者がその結果の点を攻撃者に返す

この一連の流れが成立するのは古典的Diffie–Hellmanのみです。

一方、ECIESの動作は大きく違います。

* 暗号化側
    使う秘密鍵は「**一時鍵（ephemeral key）**」であり、攻撃者が知っても問題がありません。
* 復号側
    被害者はECDHの結果を攻撃者に返しません。
    内部でAES復号のために使用するだけです。

つまり、ツイスト攻撃に必要な「**攻撃者に計算結果を返す**」という条件が成立しません。

##### ERC5630が扱う公開鍵は「圧縮形式」のため無効な点を弾ける

ツイスト攻撃を成立させるためには、「**攻撃者が曲線上にない点を送りつける**」必要があります。
しかし、**ERC5630**の公開鍵表現は「**圧縮形式の点**」に限定されています。

圧縮形式の場合、以下の性質があるため、「**意図的に曲線外の点を作る**」ことができません。

* 最初の1バイトが 0x02 or 0x03
* 残りの32バイトが $x$ 座標
* デコード時に $y$ 座標が一意に決まる
* 曲線方程式を満たさない場合は復元不能

攻撃の前提が崩れるため、ツイスト攻撃が成立しません。

##### 点が曲線上にあるかどうかの検証を必ず行うため問題が起きない

**ERC5630**は、公開鍵のデシリアライズ時に「**SEC 1 の基準で曲線上の点であることを検証する**」ことを要求しています。

* 形式のチェック
* $x$ の範囲チェック
* 曲線方程式に対する平方剰余チェック

これらによって、曲線上にない点を確実に排除できます。

圧縮形式を使用しているため、そもそも攻撃に使える「**不正な点**」を作れませんが、その上で厳格な検証が必須になっています。

## 互換性

まず、RPCメソッドの互換性について整理します。

**ERC5630**は、新しいRPCメソッドとして `eth_performECDH` を導入しています。
`eth_performECDH` 自体はこれまで存在していなかったメソッドなので、過去のウォレットやdAppとの間で「**仕様が変わって挙動がおかしくなる**」といった種類の互換性問題は発生しません。

一方で、`eth_getEncryptionPublicKey` については少し事情があります。
過去の別の提案では、`eth_getEncryptionPublicKey` と、今回とは無関係な `eth_decrypt` というメソッドがセットで提案されていました。
**ERC5630**は、このうち `eth_getEncryptionPublicKey` の挙動を上書きする形になります。

ただし、ここで重要なのは以下の点です。

* 暗号化用の公開鍵は、暗号化を行う「**その時点**」で取得し直せばよい
* 新しい仕様に従って取得した公開鍵で暗号化されたデータ（新しい暗号文）は、**ERC5630**方式のECIESで復号される
* 一方で、過去の提案に基づいて作られた暗号文は、依然としてその提案に対応した `eth_decrypt` によって復号できます

つまり、新旧方式で暗号文の生成ルールは変わりますが、「**過去に作成済みの暗号文が読めなくなる**」という問題は起こりません。
さらに、過去の提案はそもそも正式な標準にはなっておらず、非推奨ではない形で本番運用に組み込まれている実装も、少なくとも著者らの認識では存在していません。

このため、**ERC5630**が `eth_getEncryptionPublicKey` の動作を上書きしたとしても、実務的に大きな後方互換性の問題が生じる可能性は低いと判断されています。

## テストケース

**ERC5630**で想定されている一連の処理がどのように動くかを、具体例を使って確認します。
ここでは、秘密鍵から公開鍵を導出し、`eth_getEncryptionPublicKey` を呼び出して暗号化用公開鍵を取得し、実際にECIESで暗号化・復号する流れを示しています。

#### 秘密鍵・アドレス・公開鍵

まず、以下の秘密署名鍵（secret signing key）が与えられます。

```text
0x439047a312c8502d7dd276540e89fe6639d39da1d8466f79be390579d7eaa3b2
```

この秘密鍵に対応するEthereumアドレスは次のとおりです。

```text
0x72682F2A3c160947696ac3c9CC48d290aa89549c
```

この秘密鍵から `secp256k1` 上の公開鍵を計算すると、圧縮形式で以下のようになります。

```text
0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010
```

ここまでで、「**あるアカウントの秘密鍵・アドレス・圧縮公開鍵**」が一式そろっています。

#### `eth_getEncryptionPublicKey` の挙動

次に、クライアントが `eth_getEncryptionPublicKey` を使って、このアドレスに対応する暗号化用公開鍵を取得する例です。

```javascript
request({
  method: 'eth_getEncryptionPublicKey',
  params: ["0x72682F2A3c160947696ac3c9CC48d290aa89549c"]
})
```

**ERC5630**に従ったウォレットは、以下のように動きます。

1. パラメータとして渡されたアドレスに対応する秘密鍵を探索する
2. 見つかった秘密鍵から `secp256k1` 公開鍵を計算する
3. その公開鍵を圧縮形式でシリアライズし、`0x` 付き16進文字列として返す

この結果として、上記リクエストに対するレスポンスは以下のようになります。

```text
"0x03ff5763a2d3113229f2eda8305fae5cc1729e89037532a42df357437532770010"
```

これは、先ほど計算した公開鍵と一致しており、`eth_getEncryptionPublicKey` が期待どおりに動いていることを示しています。

#### 公開鍵での暗号化（ECIES）

次に、この公開鍵を使って実際にメッセージを暗号化する例です。
暗号化するメッセージは、以下の英語の文章になっています。

```text
I use Firn Protocol to gain privacy on Ethereum.
```

暗号化を行う側（encryptor）は、**ERC5630**で推奨されているECIESバリアント（ANSI-X9.63-KDF + SHA-512、HMAC–SHA-256–256、AES-256-CBC、圧縮公開鍵＋連結シリアライズ）を使って暗号化処理を行います。
その結果として、以下のような暗号文（ciphertext）の一例が得られます。

```text
"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf850e6c2af8fb38e3e31d679deac82bd12148332fa0e34aecb31981bd4fe8f7ac1b74866ce65cbe848ee7a9d39093e0de0bd8523a615af8d6a83bbd8541bf174f47b1ea2bd57396b4a950a0a2eb77af09e36bd5832b8841848a8b302bd816c41ce"
```

この暗号文は、
先頭に ECIES で使われた「**一時公開鍵（ephemeral public key）**」が圧縮形式で入っており、その後ろに暗号化された本体や認証タグなどが連結されている構造になっています。

#### 復号側による一時公開鍵の抽出

復号を行う側（decryptor）は、まず暗号文から一時公開鍵部分を取り出します。
上の暗号文の場合、先頭33バイト（0xを含めて66文字＋プレフィックス2文字で合計68文字分の16進文字列）が、一時公開鍵に相当します。

抽出される一時公開鍵は、以下のようになります。

```text
"0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"
```

この値は、ECIESにおいて送信者が生成した一時鍵ペアの「**公開鍵**」に当たる部分で、復号側では、これと自分の秘密鍵を合わせてECDHを行い、共有秘密を計算します。

#### `eth_performECDH` の呼び出し

復号側は、ECDHの計算をウォレットに任せるために `eth_performECDH` を呼び出します。
リクエストは以下のようになります。

```javascript
request({
  method: 'eth_performECDH',
  params: [
    "0x72682F2A3c160947696ac3c9CC48d290aa89549c",
    "0x036f06f9355b0e3f7d2971da61834513d5870413d28a16d7d68ce05dc78744daf8"
  ]
})
```

* 最初のパラメータは、秘密鍵を持っているアカウントのアドレス
* 二番目のパラメータは、暗号文から抽出した一時公開鍵

ウォレットは、`eth_performECDH` の仕様に従って以下の処理を行います。

1. アドレスに対応する秘密鍵を探す
2. 一時公開鍵を圧縮形式の楕円曲線点としてデシリアライズする
3. ECDHを実行し、共有秘密（Diffie–Hellman 秘密）を計算する
4. その結果を32バイトのフィールド要素としてシリアライズし、0x付き16進文字列で返す

この例では、返り値は次のようになります。

```text
"0x4ad782e7409702101abe6d0279f242a2c545c46dd50a6704a4b9e3ae2730522e"
```

これが、ECDHで得られた共有秘密に相当します。

#### ECIES での復号と平文の取得

復号側は、この共有秘密を入力としてKDFを実行し、そこからAES-256-CBCなどに利用する共通鍵を導出します。
さらに、暗号文の残りの部分（実データと認証タグ）を使い、**ERC5630**で推奨されているECIESバリアントに従って復号を行います。

その結果、最初に暗号化した平文である

```text
I use Firn Protocol to gain privacy on Ethereum.
```

が復元されます。

この一連のテストケースによって、`eth_getEncryptionPublicKey` と `eth_performECDH` が仕様どおり連携し、ECIESによる暗号化・復号が問題なく動作することが確認できます。

## セキュリティ

**ERC5630**は、暗号技術として特定の独自方式や実験的アルゴリズムを使うのではなく、すでに広く標準化され、実運用の中で十分に検証されているアルゴリズム群を前提としています。

具体的には、以下のような点が挙げられます。

* 楕円曲線：secp256k1（ビットコイン・イーサリアムで実績のある曲線）
* 署名：ECDSA
* 鍵共有：ECDH
* ハッシュ・KDF・HMAC：SHA-2系・ANSI-X9.63-KDF・HMAC-SHA-256
* 共通鍵暗号：AES-256-CBC

これらはいずれも、暗号コミュニティで広く使われている標準的なアルゴリズムであり、既存のベストプラクティスに従った構成になっています。

また、ウォレット側の責任範囲を「**ECDHのコア演算のみに限定する**」ことで、実装の複雑さを抑えつつ、機密情報（秘密鍵）を外部に出さない設計になっている点も重要です。
検証すべき入力（圧縮公開鍵の形式チェックや曲線上の点であるかの確認）も仕様として明示されており、「**よくある実装ミス**」に起因する攻撃の余地を減らすよう配慮されています。

このように、**ERC5630**は、既存の標準と実績のあるアルゴリズムを組み合わせ、その上で最小限かつ安全なインターフェイスだけを定義することで、堅牢な暗号基盤を提供することを意図しています。

## 引用

Firn Protocol (@firnprotocol), Fried L. Trout, Weiji Guo (@weijiguo), "ERC-5630: New approach for encryption / decryption [DRAFT]," Ethereum Improvement Proposals, no. 5630, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5630.

## 最後に

今回は「」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
