[ERC2770] メタトランザクションでの署名検証とトランザクションの中継を行う仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、メタトランザクションを実行するために、署名の検証とトランザクションの中継を行う仕組みを提案している**ERC2770**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

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

# 概要

**ERC2770**は、メタトランザクション（meta-transaction）の処理を実行するための「**拡張可能なフォワーダー（Forwarder）**」を提案しています。
フォワーダーの主な役割は、署名されたリクエストの検証をオンチェーンで行い、元の署名者のアドレスを最終的に呼び出されるコントラクトに提供することです。
この仕組みは、**ERC712**に準拠しており、構造体を拡張可能にすることで、後から追加された型情報もウォレットにとって読みやすい形式で表示できるようになります。

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

https://qiita.com/cardene/items/f54bc85cb66de5a54257

このフォワーダーコントラクトは、様々なメタトランザクションの利用形態に対応可能な、汎用的で再利用性の高い仕組みを提供することを目的としています。

# 動機

近年、ETHを保有していないユーザーでもスマートコントラクトとやり取りできるようにするためのニーズが高まっています。
このニーズを実現する手段が、メタトランザクションです。

メタトランザクションでは、ユーザー（EOA）がトランザクションの内容に署名し、そのデータを他の誰か（リレイヤー）が実際のEthereumトランザクションとして送信します。
ただしこの場合、コントラクトで `msg.sender` を使って署名者を特定することはできません。
なぜなら `msg.sender` はリレイヤーのアドレスになるためです。

そこで、署名を復元し、元の署名者アドレス（signer）を信頼できる形で提供する必要があります。

このフォワーダーコントラクトを使えば、複数のGas RelayやRelay Recipientが、1つの信頼できる署名検証コードを共有できます。
その結果、以下の利点が得られます。

- オンチェーンの重複コードが削減される
- セキュリティと信頼性が向上する
- メタトランザクションのフレームワーク全体の一貫性が保たれる

このように、メタトランザクションをより安全かつ効率的に利用できる基盤を提供することが**ERC2770**の目的です。

# 仕様

## フォワーダー仕様の技術解説

### 基本動作

このコントラクトは、署名済みの型付きデータ（Typed Data）とその**ERC712**署名を受け取り、署名の正当性を検証したうえで呼び出し先のコントラクトにトランザクションを転送する機能を提供します。
トランザクションのデータには署名者のアドレスも追加され、呼び出し先ではこの情報を `msg.data` の末尾から読み取ることで、実際の署名者を知ることができます（**ERC2771** に準拠）。

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

https://qiita.com/cardene/items/e84f53471b7a79240f33

### ForwardRequest構造体の登録

フォワーダーが扱うトランザクション構造体 `ForwardRequest` は以下のような固定形式を持ちます。

### `ForwardRequest`

```javascript
struct ForwardRequest {
   address from;
   address to;
   uint256 value;
   uint256 gas;
   uint256 nonce;
   bytes data;
   uint256 validUntil;
}
```

メタトランザクションの実行に必要な基本情報を格納する構造体。
署名者、呼び出し先、送信額、ガス上限、有効期限などの情報をまとめて管理します。

**パラメータ**

* `from`
    * リクエストを作成した外部アカウントのアドレス。
* `to`
    * 呼び出し対象のコントラクトアドレス。
* `value`
    * 呼び出し時に送信するEtherの量。
* `gas`
    * 呼び出しに使用するガス上限。
* `nonce`
    * 署名リプレイ防止のために使用される一意の値。
* `data`
    * コントラクト呼び出しに渡す実際のデータ。
* `validUntil`
    * このリクエストが有効とされる最終ブロック番号。
    * 0の場合は無期限。

### `registerRequestType`

```javascript
function registerRequestType(string typeName, string typeSuffix)
```

拡張構造体をフォワーダーに事前登録する関数。
カスタム型付き構造体（例：拡張されたリクエスト）を登録し、署名検証に使用できるようにします。
`typeSuffix` は **ERC712** に準拠した構造体定義です。

**引数**

* `typeName`
    * 登録する構造体の名前。
* `typeSuffix`
    * 拡張構造体の型記述（**ERC712**互換）。

### `verify`

```javascript
function verify(
   ForwardRequest forwardRequest,
   bytes32 domainSeparator,
   bytes32 requestTypeHash,
   bytes suffixData,
   bytes signature
) view;
```

署名が有効かどうかを検証する関数。
`ForwardRequest` とその拡張フィールドに基づいた署名が、実際にリクエストの署名者によるものであるかを確認します。
署名の構造は **ERC712** に準拠しています。

**引数**

* `forwardRequest`
    * 検証対象のリクエスト構造体。
* `domainSeparator`
    * 他アプリとの署名混同を防ぐためのドメイン識別子（**ERC712**仕様）。
* `requestTypeHash`
    * 登録済み構造体の型ハッシュ。
* `suffixData`
    * 拡張フィールドの署名対象データ。
* `signature`
    * 検証対象の署名。

### `execute`

```javascript
function execute(
   ForwardRequest forwardRequest,
   bytes32 domainSeparator,
   bytes32 requestTypeHash,
   bytes suffixData,
   bytes signature
)
public
payable
returns (
   bool success,
   bytes memory ret
)
```

検証後にトランザクションを実行する関数。
内部で `verify` を実行し、署名が有効な場合のみ、指定の宛先へコールを行います。
その際、`msg.data` の末尾に署名者のアドレス（`forwardRequest.from`）を追加して送信します。
実行の成否にかかわらず `nonce` は更新され、リプレイ攻撃を防ぎます。

**引数**

* `forwardRequest`
    * 実行対象のリクエスト構造体。
* `domainSeparator`
    * ドメイン分離用の識別子。
* `requestTypeHash`
    * 登録済み構造体のハッシュ。
* `suffixData`
    * 拡張フィールドの署名対象データ。
* `signature`
    * 検証対象の署名。

**戻り値**

* `success`
    * 呼び出し先での処理が成功したかどうか。
* `ret`
    * 呼び出し先からの戻りデータ。

## suffixDataの生成

拡張構造体において署名対象とする追加データは `suffixData` として与えられます。
以下はその一例です。

### `calculateSuffixData`

```javascript
function calculateSuffixData(ExtendedRequest request) internal pure returns (bytes) {
    return abi.encode(request.x, keccak256(request.z), hashExtraData(request.extraData));
}
```

拡張リクエストにおける追加署名データ（suffixData）を生成する関数。
構造体の `z` や `extraData` をハッシュ化し、追加項目 `x` とともにエンコードして返します。

**引数**

* `request`
    * `ExtendedRequest` 型の構造体。

**戻り値**

* `bytes`
    * 署名に使用する追加データ。

### `hashExtraData`

```javascript
function hashExtraData(ExtraData extraData) internal pure returns (bytes32) {
    return keccak256(abi.encode(
        keccak256("ExtraData(uint256 a,uint256 b,uint256 c)"),
        extraData.a,
        extraData.b,
        extraData.c
    ));
}
```

`ExtraData` 構造体のハッシュを計算する関数。
署名に使用される `ExtraData` 部分を **ERC712** のルールに従ってハッシュ化します。

**引数**

* `extraData`
    * `ExtraData` 型の構造体。

**戻り値**

* `bytes32`
    * ハッシュ値。

### `ExtraData`

```javascript
struct ExtraData {
   uint256 a;
   uint256 b;
   uint256 c;
}
```

拡張構造体 `ExtendedRequest` の内部にネストされる構造体。
追加の任意フィールドを3つの整数で定義し、署名対象に含めるために使用します。

**パラメータ**

* `a`
    * 拡張データのフィールド1。
* `b`
    * 拡張データのフィールド2。
* `c`
    * 拡張データのフィールド3。

## 署名者アドレスの受け取り方法

リレー先のコントラクト（Recipient）は、呼び出し元（`msg.sender`）ではなく、`msg.data` の末尾に付加された署名者アドレスから、実際の送信者を抽出する必要があります。
これは **ERC2771** に準拠した設計です。

# 補足

従来のように `msg.sender` を使ってエンドユーザーを識別する方法では、EthereumのDAppエコシステムの将来は行き詰まってしまいます。
なぜなら、ユーザーがスマートコントラクトとやり取りするためには事前にETHを保有している必要があるという制約があるためです。

この制約により、スマートコントラクトの多くのユースケースは現実的に実行できず、ユーザーの参入障壁が高いままとなります。
その結果、DAppの普及も進まず、ETHを持っていないから使えない → ユースケースが増えない → 普及しない、という悪循環が続きます。

こうした課題を解消するのが、ETHを必要としないメタトランザクションです。
また、リクエストの有効期限を指定する `validUntil` フィールドについては、タイムスタンプではなくブロック番号で指定する設計が採用されています。
これは、EVMで一般的に使用されるブロックベースのタイマーとの連携がしやすく、より正確な制御が可能になるという利点があります。

# セキュリティ

このフォワーダー機構に対応するすべてのコントラクトは、外部からのフォワーディングリクエストを信頼して処理することになります。
つまり、フォワーダーコントラクトは任意のアカウントを代理して任意の操作を実行できる権限を持つことになるため、極めて重要な信頼の根幹となります。

そのため、このコントラクト自体に次のような問題が一切存在してはならないとされています。

- 脆弱性

コントラクトにバグやセキュリティホールがあると、悪意のあるリクエストで不正操作が可能になります。

- 中央集権性

署名の検証やリクエストの構造が一部の管理者によって改ざん可能であれば、システム全体の信頼性が損なわれます。

このように、Forwarderコントラクトの堅牢性と非中央集権性は、メタトランザクション全体の安全性と信頼性を保証する鍵となります。

# 引用

Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), "ERC-2770: Meta-Transactions Forwarder Contract [DRAFT]," Ethereum Improvement Proposals, no. 2770, July 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2770.

# 最後に

今回は「メタトランザクションを実行するために、署名の検証とトランザクションの中継を行う仕組みを提案している**ERC2770**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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