はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、Dappとウォレットの間でやり取りされるRPCリクエストに対して、ドメインに紐づく公開鍵と電子署名を用いて改ざん検知と正当性検証を行う仕組みを提案しているERC7754についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ERC7754は、ウォレットとDapp(分散型アプリケーション)の間の通信を改ざんできない形で行うための仕組みを定義しています。
正式名称は「Tamperproof Web Immutable Secure Transaction(TWIST)」で、ウォレットに新しいRPCメソッドである wallet_signedRequest の実装を求めています。
この仕組みの中心は「署名付きリクエスト」です。
Dappは自分のドメイン(DNSレコード)に公開鍵を登録し、その公開鍵に対応する秘密鍵で、ウォレットへ送るリクエストの内容に電子署名を行います。
ウォレットは、そのDappのDNSに登録されている公開鍵を取得し、送られてきたリクエストの署名を検証することで、以下を確認できます。
- そのリクエストが本当にそのDappから送られたものであること
- 通信途中で内容が書き換えられていないこと
構造を簡単に表すと以下の関係になります。
これはWebにおけるHTTPSと同じ考え方です。
HTTPSではサーバー証明書により通信相手の正当性と改ざん防止を保証しますが、ERC7754では「Dappの公開鍵をDNSに結びつけ、署名で検証する」ことで、Dappとウォレットの間に信頼の鎖を作ります。
動機
ERC7754が必要とされる理由は、現在のDappとウォレット間の通信が「中間者攻撃(MITM: Man In The Middle)」に非常に弱い構造になっているためです。
現在の問題点
Dappとウォレットは通常、ブラウザ上のJavaScriptとRPC通信でやり取りします。
この通信経路には以下のような脆弱性があります。
- XSS(クロスサイトスクリプティング)によってページ内のJavaScriptが書き換えられる
- 悪意あるブラウザ拡張機能がRPC通信をフックする
この結果、攻撃者はウォレットに送られるRPCリクエストを途中で盗み見たり、内容を書き換えたりできます。具体的な被害例は以下です。
| 攻撃内容 | 何が起こるか |
|---|---|
| calldataの改ざん | 送金先や金額を差し替え、資金を盗むトランザクションに変えられる |
| EIP712署名パラメータの変更 | ユーザーが意図しないメッセージ内容に署名させられる |
| 再利用可能な署名の取得 | 後で何度も悪用できる署名を盗まれる |
EIP712については以下の記事を参考にして下さい。
ユーザーとDapp双方のリスク
この状況では、たとえDapp自体が誠実に作られていても、通信途中で改ざんされる可能性があります。
その結果として以下の問題が発生します。
| 立場 | 問題点 |
|---|---|
| ユーザー | 正規のDappを使っているのに資産を失う可能性がある |
| Dapp開発者 | 攻撃の責任は自分にないのに、サービスの信用を失う |
つまり、「Dappとウォレットの間に信頼の連鎖が存在しない」こと自体が、エコシステム全体の安全性を下げています。
ERC7754による解決
ERC7754は、wallet_signedRequest というRPCメソッドを通じて、Dapp側が以下の流れを明示的に行えるようにします。
- リクエスト内容に電子署名を付与する
- ウォレット側がDNSに登録された公開鍵でその署名を検証する
これによりウォレットは、以下を厳密に検証できます。
- そのリクエストが本当にそのドメインのDappから来たものか
- 途中で内容が書き換えられていないか
これまでのように、ビット詰めや独自の引数エンコードといった「暗黙的で壊れやすい防御」に頼る必要がなくなり、Dappとウォレットの間に明確な暗号学的な信頼関係が構築されます。
結果として、ERC7754は「WebにおけるHTTPSと同等の安全性」を、Dappとウォレットの通信にもたらすための標準です。
仕様
全体構成
ERC7754は、Dappのドメイン証明書を信頼の起点(Root of Trust)として、ウォレットまで暗号学的な信頼の連鎖を構築します。
流れは以下です。
- ブラウザがHTTPS証明書を検証し、ドメインが正当であることを確認します。
- そのドメインのDNSに、TWIST用のTXTレコードを設定します。
- TXTレコードには、TWISTマニフェストのURLが記載されます。
- マニフェストは通常
/.well-known/twist.jsonに配置されます。 - マニフェストには、公開鍵の一覧(ID、署名アルゴリズム、公開鍵本体)がJSON形式で定義されます。
- Dappはバックエンドなど安全な環境でリクエスト内容に署名します。
- 元のRPCペイロード、署名、鍵IDを
wallet_signedRequestでウォレットに送ります。 - ウォレットはマニフェストの公開鍵で署名を検証し、正しければ通常処理を行います。
これにより「そのドメインの運営者が署名したRPCだけを正当なものとして扱う」という信頼モデルが成立します。
ウォレット連携
鍵の発見
信頼の連鎖を成立させるためには、ウォレットが「どの公開鍵がそのDappに属するのか」を正しく知る必要があります。
ERC7754では、DNSにTXTレコードを追加し、そこからTWISTマニフェストの場所を取得する方式を採用します。
考え方はメール署名で使われるDKIM(RFC 6376)と似ていますが、鍵を直接DNSに置くのではなく、JSONマニフェスト経由にすることで将来の拡張性と複数鍵・複数アルゴリズム対応を可能にしています。
鍵のキャッシュについては、EIP7519(JWTの運用モデル)と同様に、ウォレットが事前取得して保持してもよいですが、失効機構がないため長時間のキャッシュは危険です。
そのため、公開鍵のキャッシュは最大2時間までとすることが強く推奨されています。
DNSとマニフェストの例
DNSレコード例です。
TXT: TWIST=/.well-known/twist.json
対応するマニフェスト例です。
{
"publicKeys": [
{ "id": "1", "alg": "ES256", "publicKey": "0xaf34..." },
{ "id": "2", "alg": "PS256", "publicKey": "0x98ab..." }
]
}
各フィールドの意味は以下です。
| 項目 | 説明 |
|---|---|
| id | 鍵の識別子 |
| alg | 署名アルゴリズム(RFC 7518準拠) |
| publicKey | X.509 SPKI形式(DER)を16進文字列化した公開鍵 |
必須サポートアルゴリズムは以下です。
| 種別 | 内容 |
|---|---|
| MUST | ES256, EdDSA |
| SHOULD | PS256, RS256, ES384, ES512, PS384, PS512, RS384, RS512 |
暗号処理はブラウザ標準のSubtleCrypto APIの利用が推奨されています。
マニフェスト構造
TWISTマニフェストは以下のJSONスキーマを持ちます。
{
"title": "TWIST manifest",
"type": "object",
"properties": {
"publicKeys": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"alg": { "type": "string" },
"publicKey": { "type": "string" }
}
}
}
}
}
非常に単純な構造で、将来の拡張を阻害しない設計です。
RPCメソッド
wallet_signedRequest の引数構造はTypeScriptで以下のように定義されます。
type RequestPayload<Params> = { method: string; params: Params };
type SignedRequestParameters<Params> = [
requestPayload: RequestPayload<Params>,
signature: `0x${string}`,
keyId: string,
];
呼び出し例です。
const keyId = '1';
const requestPayload = {
method: 'eth_sendTransaction',
params: [{ /* ... */ }],
};
const signature = await getSignature(requestPayload, keyId);
const result = await ethereum.request({
method: 'wallet_signedRequest',
params: [requestPayload, signature, keyId],
});
署名検証フロー
ウォレットは以下の検証手順を必ず行います。
- 送信元タブのドメインを取得します。
- HTTPSであることを強制します(DNSスプーフィング対策)。
- DNSのTWISTレコード、または
/.well-known/twist.jsonからマニフェストを取得します。 - リダイレクトは禁止します(オープンリダイレクト対策)。
- Content-Typeが
application/jsonであることを確認します。 - keyIdに対応する公開鍵とアルゴリズムを取得します。
-
crypto.verify(alg, key, signature, requestPayload)で署名検証します。
検証結果に応じた挙動は以下です。
| 状態 | ウォレットの動作 |
|---|---|
| TWIST未設定 | 従来通り処理 |
| TWIST設定ありだが通常RPC使用 | 警告を表示し、ユーザーが許可すれば続行 |
| 署名不正 | 警告を表示し、続行か中止を選ばせる |
| 署名正当 | そのまま処理 |
実装例(ウォレット側)
async function signedRequest(
requestPayload,
signature,
keyId,
) {
const domain = getDappDomain();
const doh = require('dohjs');
const resolver = new doh.DohResolver('<doh-endpoint>');
let manifestPath = '';
const dnsResp = await resolver.query(domain, 'TXT');
for (const record of dnsResp.answers) {
if (!record.data.startsWith('TWIST=')) continue;
manifestPath = record.data.substring(5);
break;
}
const manifestUrl = `https://${domain}${manifestPath}`;
const manifestReq = await fetch(manifestUrl, { redirect: 'error' });
const contentType = (manifestReq.headers.get('content-type') || '').toLowerCase();
if (!contentType.startsWith('application/json')) {
throw new Error('The manifest is not a proper JSON file');
}
const manifest = await manifestReq.json();
const keyData = manifest.publicKeys.find((x) => x.id === keyId);
if (!keyData) {
throw new Error('Could not find the signing key');
}
const valid = await crypto.verify(keyData.alg, keyData.publicKey, signature, requestPayload);
if (!valid) {
throw new Error('The data was tampered with');
}
return await processRequest(requestPayload);
}
ウォレットUIの提案(Wallet UX)
ERC7754はUX面も重視しています。
HTTPSの鍵アイコンと同様に、TWISTが有効なDappでは「この通信は署名検証済みである」ことを視覚的に示す表示を推奨しています。
また、署名が無い、もしくは不正な場合でも即ブロックはせず、明確で理解しやすい警告を出し、ユーザー自身が続行か中止かを選べる設計とされています。
これにより、利便性を損なわずに安全性と透明性を高めることができます。
補足
ERC7754は、既存のEIP712やEIP1193の仕組みを変更するものではなく、それらの上に「署名付きRPC」という機能を追加する形で設計されています。
そのため、従来のDappやウォレットの動作を壊すことなく導入できます。
EIP1193については以下の記事を参考にして下さい。
設計思想は、すでに実運用で実績のある仕組みを参考にしています。
代表例がメールの送信ドメイン認証で使われるDKIMです。
DKIMも、DNSに公開鍵を置き、送信者が署名し、受信側が検証することで改ざんやなりすましを防ぎます。
ERC7754も同様に、DNSと公開鍵、署名検証によって信頼の連鎖を構築します。
このように、まったく新しい暗号技術を導入するのではなく、既存の標準技術と運用モデルを組み合わせているため、以下の特徴があります。
- ウォレット側もDapp側も実装が比較的容易です。
- 既存のインフラ(DNS、HTTPS、公開鍵暗号、WebCrypto)をそのまま利用できます。
- 想定される攻撃モデルが明確で、セキュリティ分析がしやすい構造です。
その結果、エコシステムに大きな混乱を与えず、段階的に導入できる現実的な標準になっています。
セキュリティ
リプレイ攻撃への対応
リクエストに署名を付けることで「改ざんされていないこと」は保証できますが、それだけでは「同じ署名付きリクエストを何度も使い回されること(リプレイ攻撃)」までは防げません。
具体的には以下の問題が残ります。
- 同一チェーン上で同じ署名付きリクエストを何度も送信される可能性があります。
- 異なるチェーンに対して同じ署名を再利用される可能性があります。
1については、トランザクションの場合は通常「nonce(ノンス)」があるため、同じトランザクションは一度しか実行できません。
2については、EIP712の signTypedData でチェーンIDを含めて署名することで、チェーン間リプレイを防げます。
ただし、ステートを変更しない読み取り専用メソッド(balanceOf などの readonly RPC)には nonce もチェーン固有の文脈も無い場合があります。
この場合、理論上はリプレイが可能ですが、資金移動や権限変更ができないため、攻撃価値は非常に低いと評価されています。
以上の理由から、ERC7754では現時点で統一的なリプレイ防止機構は必須とはしていません。
ただし、将来必要になった場合には、TWISTマニフェストを拡張し、JWTのような有効期限付きトークンやリプレイ防止用カウンタを組み込む余地が残されています。
悪意あるマニフェストへの対策
TWISTの信頼モデルは「正しい公開鍵が正しいドメインから取得される」ことに依存します。
したがって、マニフェスト自体が改ざんされた場合、仕組み全体が無効化される危険があります。
想定されている攻撃と対策は以下です。
| 攻撃シナリオ | 内容 | 対策 |
|---|---|---|
| DNSなりすまし | 攻撃者が偽のDNS応答を返し、偽のマニフェストを指させる | ウォレットは必ず https://${sender.tab.url} 配下のみから取得する |
| オープンリダイレクト悪用 | 正規ドメイン上のリダイレクト機能を使い、外部の偽マニフェストへ誘導 | ウォレットはリダイレクトを一切許可しない |
| ドメイン内への不正ファイル設置 | 脆弱なアップロード機能などを悪用して偽JSONを置く | Content-Type が application/json であることを厳密に確認する |
特に重要なのは、以下の2点です。
- マニフェスト取得時にリダイレクトを絶対に追跡しないことです。
これにより、正規ドメインから外部の悪意あるサイトへ誘導される攻撃を防ぎます。 - HTTPではなく必ずHTTPSを使い、かつ取得元ドメインを送信元タブのドメインと完全一致させます。
これにより、DNSスプーフィングや中間者攻撃で別のマニフェストを差し込まれるリスクを下げます。
このように、ERC7754は「マニフェスト自体が攻撃対象になる」ことを前提に設計されており、取得経路と形式の厳格な検証を必須要件として定めています。
引用
Erik Marks (@remarks), Guillaume Grosbois (@uni-guillaume), "ERC-7754: Tamperproof Extension Wallets API (TWIST) [DRAFT]," Ethereum Improvement Proposals, no. 7754, July 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7754.
最後に
今回は「Dappとウォレットの間でやり取りされるRPCリクエストに対して、ドメインに紐づく公開鍵と電子署名を用いて改ざん検知と正当性検証を行う仕組みを提案しているERC7754」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!