[ERC7741] EIP712を使用してメタトランザクションを実行する仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、**EIP712**を活用してガスレスでトランザクションを実行してもらう仕組みを提案している**ERC7741**についてまとめていきます！

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

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

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

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

# 概要

**ERC7741**は、**EIP712**に基づく署名を活用し、オペレーター（代理人）を管理するためのメタトランザクションやアトミックなコントラクト実行を可能にする一連の関数を提案しています。
**EIP712**は、ブロックチェーン上の署名データに明確な型情報を付与することでセキュリティと利便性を向上させる規格です。
この仕組みを利用することで、ユーザーはブロックチェーン上で直接トランザクションを実行することなく（特定のアドレスにガス代を負担しつつ処理を実行してもらう）、オペレーターの権限を設定・変更することができます。

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

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

# 動機

**ERC7741**の主な目的は、オペレーター管理の柔軟性、安全性、効率性を向上させることです。
特に、**EIP712**の署名方式を採用することで、ユーザーはガス代（トランザクション手数料）を支払わずにオペレーターを認証・管理できるようになり、エンドユーザーの利便性が大きく向上します。  

また、この標準が解決しようとする課題は以下の通りです。  

## メタトランザクションの実現  

メタトランザクションとは、ユーザー自身が直接ブロックチェーン上でトランザクションを送信するのではなく、第三者（リレイヤーやオペレーター）が代理で実行する仕組みです。
**EIP712**署名を利用すれば、ユーザーはオフチェーン（ブロックチェーン外）でオペレーターに権限を与えることができ、実際のトランザクションはオペレーターが送信します。
このため、ユーザーはネイティブトークン（ETHやMATICなど）を持っていなくても、ブロックチェーン上で何らかの処理の実行が可能になります。
特に、頻繁なオペレーター変更が求められるシナリオでは、トランザクションコストの削減と利便性の向上に貢献します。  

## セキュリティの向上  

**EIP712**の署名方式を採用することで、より安全なメッセージ署名が可能になります。
従来の単純なハッシュデータに署名する方式（**EIP191**など）では、メッセージの意味が分かりにくく、フィッシング攻撃のリスクが高まる問題がありました。
**EIP712**では、署名対象のデータに型情報を付与して構造化されたメッセージを作成するため、ユーザーは何に署名しているのかを明確に認識できます。
これにより、誤った操作を防ぎ、安全性が向上します。

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

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

## 相互運用性

この標準は、オペレーター管理のための統一されたインターフェースを提供します。
これにより、異なるコントラクトやプロトコル間でのオペレーター管理が統一されて開発者の負担が軽減されます。
特に、さまざまな**Vaultプロトコル**において、オペレーターが資産管理を行うケースでは、この標準を採用することで互換性が確保され、異なるプラットフォーム間での統合が容易になります。  

:::note info
Vaultとは、ブロックチェーンにおいて資産を管理・運用するためのコントラクトのことを指します。
特に、DeFi（分散型金融）においてよく使用されており、ユーザーがトークンを預けてステーキング、利回りの最適化、資産の自動運用などを行う仕組みを提供します。
:::

## クロスチェーン環境での運用の簡素化  

複数のブロックチェーンをまたいでオペレーターを管理する場合、一貫した権限管理を維持することは大きな課題となります。
この標準を導入することで、オフチェーン署名によるオペレーターの設定や権限移行が容易になり、異なるチェーン間での操作の一貫性を保つことができます。
特に、クロスチェーンでの資産管理を行うプロジェクトにとっては、運用の負担を大幅に軽減できます。  

# 仕様

**ERC7741**は、オペレーター（代理人）を管理するための**EIP712**署名方式に基づく承認スキームを定義しています。
これにより、**EIP6909**や**EIP7540**といった既存のオペレーター管理プロトコルと互換性を持ち、コントラクトが一貫した方法でオペレーターの設定や権限管理を行えるようになります。  

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

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

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

https://qiita.com/cardene/items/4aa8cf3f1806f966ce4a

## オペレーター対応コントラクト  

この承認スキームを適用できるのは、**IOperatorインターフェース**を実装したコントラクトです。  
このインターフェースには、以下のような機能が含まれています。  

### `OperatorSet`

`owner`が特定のオペレーター（operator）に権限を付与または取り消した時に発行されるイベントです。
これにより、オフチェーンでオペレーターの変更履歴を追跡できます。  

### `setOperator関数` 

オペレーターの権限を設定または解除する関数です。
外部からの呼び出しが可能であり、指定したオペレーターに対して`approved`（承認または非承認）を設定します。  

### `isOperator関数` 

指定された`owner`とオペレーターの組み合わせが現在承認されているかどうかを確認する関数です。
オペレーターが権限を持っているかどうかをブール値（`true`または`false`）で返します。  

**EIP6909**では、オペレーター（operator）の代わりに「spender」という名称を使用するなど、引数の命名には若干の違いがありますが機能自体は同じであるため、この規格と互換性を持たせることが可能です。  


## 関数

### `authorizeOperator`

**EIP712**署名を使用して、`owner`がオペレーターに対して特定の権限を付与または取り消す関数。
メタトランザクションを活用することで、`owner`自身が直接トランザクションを送信することなく、オフチェーンで署名するだけでオペレーターの管理が可能になります。

関数の動作として、以下の条件を満たす必要があります。  

- **期限（deadline）が過ぎていた場合、トランザクションを失敗させる**  

署名の有効期限が過ぎた場合、トランザクションが実行されないようにすることで、古い署名が悪用されることを防ぎます。  

- **署名の`nonce`を無効にする**  

`nonce`とは、一度しか使用できない一意の値のことです。
これをトランザクションごとに無効化することで、リプレイ攻撃（過去の署名を再利用して不正にトランザクションを実行する攻撃）を防ぎます。  

- **EIP712署名が正しいことを検証し、不正な署名の場合はトランザクションを失敗させる**  

 った署名や不正な署名が使われた場合、関数の実行を拒否します。  

- **オペレーターのステータスを`approved`の値に設定する**  

`approved`が`true`の場合、オペレーターを有効化し、`false`の場合は権限を取り消します。  

- **OperatorSetイベントを発行する**  

ブロックチェーン上にオペレーターの設定変更を記録し、外部のアプリケーションが履歴を追跡できるようにします。  

この関数は、実行が成功すると`true`を返します。  

---

### `invalidateNonce`

`owner`（`msg.sender`）が特定の`nonce`を無効化する関数。  

**EIP712**署名を使用する場合、悪意のある第三者が過去の署名を不正に再利用するリスクがあります。
そのため、`owner`は不要になった`nonce`を明示的に無効にすることで、より高いセキュリティを確保できます。  

この関数を実行すると、指定した`nonce`は二度と使用できなくなります。  

---

### `authorizations`

特定の`controller`と`nonce`の組み合わせがすでに使用されたか確認する関数。  

- **controller（管理者アドレス）**
    - 署名を検証するコントラクトまたはエンティティのアドレス。  
- **nonce**
    - 一意の値で、既に使用されていれば`true`、未使用なら`false`を返す。 

この機能により、`owner`は過去に使用した署名が再利用されるリスクを管理できます。  

---

### `DOMAIN_SEPARATOR` 

**EIP712**で定義されたドメインセパレーターを取得する関数。  

ドメインセパレーターとは、異なるブロックチェーンやコントラクト間でのリプレイ攻撃を防ぐために設計された一意の識別子です。  

具体的には、以下の情報を組み合わせた値になります。  
- **コントラクトのアドレス**  
- **チェーンID（EthereumやPolygonなどのネットワーク識別子）**  
- **特定のメッセージスキーマ**  

このセパレーターを使用することで、攻撃者が別のブロックチェーン上で同じ署名を流用して不正なトランザクションを実行することを防げます。  

---

## ERC165のサポート  

**ERC7741**を実装するコントラクトは、**ERC165**の`supportsInterface`関数を実装する必要があります。

**ERC165**は、コントラクトがどのインターフェースを実装しているかを識別するための規格です。  

具体的には、`supportsInterface`関数に`0xa9e50872`という定数を渡した場合、`true`を返す必要があります。
これにより、外部のアプリケーションやコントラクトが、この標準を正しく実装しているかを判別できます。  

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

https://qiita.com/cardene/items/541184977a5cba51b759

# 補足

## ERC2612との類似性

**ERC7741**は、**ERC2612（Permit）**と意図的に似た仕様になっています。
その目的は、既に**ERC2612**を実装しているプロジェクトがスムーズにこの規格を導入できるようにすることです。

**ERC2612**は、**EIP712**署名を利用して、トークンの承認をオフチェーンで行う規格です。
本来、**ERC20**トークンの`transfer`を第三者（たとえばDeFiプロトコル）に許可するには、`approve`関数を使ってトランザクションを送信する必要があります。
しかし、**ERC2612**を使うと、トランザクションを発行せずにオフチェーンで署名を作成し、それをオペレーターに渡すことで承認が完了します。
これにより、ユーザーはガス代を削減しつつ、よりスムーズなトランザクション処理が可能になります。

**ERC7741**も同じく、**EIP712**署名を利用してオペレーターを承認する仕組みを提供しており、その基本的な設計思想は**ERC2612**と一致しています。

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

https://qiita.com/cardene/items/6809173b2ac428b1ccd0

## ERC2612との主な違い  

**ERC7741**と**ERC2612**の大きな違いは、`nonce`を`bytes32`型にしている点です。  

**ERC2612**では、`nonce`は`uint256`型を使用しており、基本的に自動でインクリメントされる値です。
つまり、`nonce`は「0 → 1 → 2 → 3…」というように、順番に使用されることが前提になっています。
この仕組みは単純で理解しやすいですが、以下のような制約が生じます。  

- **未使用の`nonce`を飛ばすことができない**  

**ERC2612**の`uint256`型の`nonce`は、順番通りに消費しなければならないため、未使用の`nonce`を飛ばすとエラーになります。
例えば、`nonce`が「`5`」の状態で「`7`」の`nonce`を使おうとすると、`nonce`「`6`」が飛ばされるため、承認されません。  

- **複数の署名を並列で処理しにくい**  

ユーザーが並行して異なるトランザクションを処理しようとすると、どの`nonce`が次に使われるかを正確に管理する必要があり、並列処理が難しくなります。  

一方、**ERC7741**では`nonce`を`bytes32`型にすることで、順序に依存しない`nonce`管理を可能にしています。 

- **`nonce`の順番を気にせず使用できる**  

`bytes32`型の`nonce`は、単調増加する必要がなく、ランダムな値を使用できます。
例えば、あるオペレーターが「`0xabc123`」、別のオペレーターが「`0xdef456`」といった異なる`nonce`を自由に使用できます。  

- **並列処理が容易になる**  

`nonce`の順番を厳密に管理する必要がないため、ユーザーは複数の異なるオペレーターに対して、並行して異なるトランザクションを発行できます。
これにより、マルチスレッド環境や分散処理を行うコントラクトとの相性が良くなります。  

# 参考実装

```js
// This code snippet is incomplete pseudocode used for example only and is no way intended to be used in production or guaranteed to be secure

bytes32 public constant AUTHORIZE_OPERATOR_TYPEHASH =
    keccak256("AuthorizeOperator(address controller,address operator,bool approved,bytes32 nonce,uint256 deadline)");

mapping(address owner => mapping(bytes32 nonce => bool used)) authorizations;

function DOMAIN_SEPARATOR() public view returns (bytes32) {
  // EIP-712 implementation 
}

function isValidSignature(address signer, bytes32 digest, bytes memory signature) internal view returns (bool valid) {
  // ERC-1271 implementation 
}

function authorizeOperator(
    address controller,
    address operator,
    bool approved,
    bytes32 nonce,
    uint256 deadline,
    bytes memory signature
) external returns (bool success) {
    require(block.timestamp <= deadline, "ERC7540Vault/expired");
    require(controller != address(0), "ERC7540Vault/invalid-controller");
    require(!authorizations[controller][nonce], "ERC7540Vault/authorization-used");

    authorizations[controller][nonce] = true;

    bytes32 digest = keccak256(
        abi.encodePacked(
            "\x19\x01",
            DOMAIN_SEPARATOR(),
            keccak256(abi.encode(AUTHORIZE_OPERATOR_TYPEHASH, controller, operator, approved, nonce, deadline))
        )
    );

    require(SignatureLib.isValidSignature(controller, digest, signature), "ERC7540Vault/invalid-authorization");

    isOperator[controller][operator] = approved;
    emit OperatorSet(controller, operator, approved);

    success = true;
}

function invalidateNonce(bytes32 nonce) external {
    authorizations[msg.sender][nonce] = true;
}
```

# セキュリティ

## オペレーターの管理と署名のリスク

オペレーターは、ユーザーの資産やリソースを管理する権限を持つため影響力が大きいです。
**EIP712**署名を使用したオペレーター承認では、ユーザーが一度署名したメッセージが悪用される可能性があるため慎重な管理が必要になります。  

署名が何らかの理由で漏洩して第三者に不正に利用されるなどリスクがあります。
このように、オペレーターが正しく管理されなかった場合、ユーザーの資産が予期せぬ方法で移動されたり、制御不能な状態に陥る可能性があります。  

---

## 署名の有効期限を短くするべき理由

**EIP712**による署名は、一度作成されると第三者に渡らない限り、ブロックチェーン上で変更・キャンセルすることができません。
そのため、未使用の署名が流出すると攻撃者が悪用するリスクが生じます。

例えば、ユーザーがオペレーターを承認する署名を作成し、その署名が何らかの形で漏洩した場合オペレーターが意図しない操作を行う可能性があります。
この問題を最小限に抑えるために、署名の有効期限を可能な限り短く設定することが重要です。  

有効期限が長すぎると、以下のようなリスクが高まります。  

- **漏洩した署名の悪用リスクが増大する**  

もし署名が流出した場合、攻撃者は期限が切れるまでそれを利用できるため、被害が発生する可能性が高くなります。  

- **ユーザーが意図しない時点でオペレーターが操作を実行する可能性がある**  
例えば、ユーザーがオペレーターの権限を与えたものの、そのオペレーターがすぐに実行しなかった場合、後になってユーザーの意図と異なる状況で操作される可能性があります。  

- **署名をキャンセルする手段が限られる**  

一度発行された署名は、オフチェーンで管理されるため、ブロックチェーン上で直接取り消すことはできません。
短い有効期限を設定することで、署名が未使用のまま残るリスクを低減できます。 

---

## 短い有効期限の設定がもたらす安全性の向上  

署名の有効期限を短くすることで、オペレーター管理における安全性を大幅に向上させることができます。
具体的には、次のようなメリットがあります。  

- 署名が漏洩した場合でも、短期間で無効化されるため悪用される可能性が低くなる。
- ユーザーが意図したタイミングでのみオペレーターが操作を実行できるようになる。
- 署名の取り消しができなくても、期限切れによって自然に無効化されるため不要なリスクを最小限に抑えられる。

特に、クロスチェーン環境や頻繁にオペレーターを変更するケースでは、長期間有効な署名を使用するとリスクが高まるため、できるだけ短い有効期限を設定することが望ましいです。  

# 引用

Jeroen Offerijns (@hieronx), João Martins (@0xTimepunk), "ERC-7741: Authorize Operator [DRAFT]," Ethereum Improvement Proposals, no. 7741, June 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7741.

# 最後に

今回は「**EIP712**を活用してガスレスでトランザクションを実行してもらう仕組みを提案している**ERC7741**」についてまとめてきました！
いかがだったでしょうか？

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