はじめに

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

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

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

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

概要

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

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

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

動機

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

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

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

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

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

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

仕様

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

基本動作

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

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

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などからお気軽に質問してください!

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