はじめに(Introduction)
以下のMeta Transactionsの仕様をGoogle翻訳で翻訳してみます。
ERC-2771: ネイティブメタトランザクション用の安全なプロトコル(Secure Protocol for Native Meta Transactions)
信頼できるフォワーダーを通じてメタトランザクションを受信するためのコントラクトインターフェイス(A contract interface for receiving meta transactions through a trusted forwarder)
Authors Ronan Sandford (@wighawag),Liraz Siri (@lirazsiri), Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Hadrien Croubois (@Amxx), Sachin Tomar (@tomarsachin2271), Patrick McCorry (@stonecoldpat), Nicolas Venturo (@nventuro), Fabian Vogelsteller (@frozeman), Gavin John (@Pandapip1)
Created 2020-07-01
要旨(Abstract)
この EIP は、信頼できる Forwarder(転送者)コントラクトを通じてメタトランザクションを受け入れるための Recipient(受信者)コントラクトのコントラクトレベルのプロトコルを定義します。
プロトコルの変更は行われません。
Recipient(受信者)コントラクトには、追加の呼び出しデータを追加することによって、有効な msg.sender( _msgSender() と呼ばれる)と msg.data( _msgData() と呼ばれる)が送信されます。
動悸(Motivation)
ガス代を支払うための ETH を持たない外部所有のアカウントからの呼び出しをイーサリアムコントラクトで受け入れられるようにすることへの関心が高まっています。
第三者がガス代を支払うことを可能にするソリューションは、メタトランザクションと呼ばれます。
この EIP の目的では、メタ トランザクションとは、トランザクション署名者によって承認され、ガスの料金を支払う信頼できない第三者 (ガス リレー) によって中継されるトランザクションです。
仕様(Specification)
この文書内のキーワード「しなければならない(MUST)」、「してはならない(MUST NOT)」、「必須(REQUIRED)」、「しなければならない(SHALL)」、「してはならない(SHALL NOT)」、「すべきである(SHOULD)」、「すべきではない(SHOULD NOT)」、「推奨(RECOMMENDED)」、「してもよい(MAY)」、「任意(OPTIONAL)」は次のとおりです。 RFC 2119 に記載されているように解釈されます。
定義(Definitions)
トランザクション署名者(Transaction Signer): トランザクションに署名してガスリレー(Gas Relay)に送信します
ガスリレー(Gas Relay): トランザクション署名者からオフチェーンで署名されたリクエストを受信し、信頼できる転送者(Trusted Forwarder)を経由する有効なトランザクションに変換するためにガスを支払います。
信頼できる転送者(Trusted Forwarder): トランザクション署名者(Transaction Signers)からのリクエストを転送する前に、署名とナンスを正しく検証するためにRecipient(受信者)によって信頼されるコントラクト
受信者(Recipient): 信頼できる転送者(Trusted Forwarder)を通じてメタトランザクションを受け入れるコントラクト
フロー例
例 :
(bool success, bytes memory returnData) = to.call.value(value)(abi.encodePacked(data, from));
受信者(Recipient) コントラクトは、次の 3 つの操作を実行して トランザクション署名者(Transaction Signer) のアドレスを抽出できます。
-
転送者(Forwarder) が信頼できることを確認してください。
これがどのように実装されるかについては、この提案の範囲外です。 - 呼び出しデータの最後の 20 バイトから トランザクション署名者(Transaction Signer) のアドレスを抽出し、それをトランザクションの元の
senderとして (msg.senderの代わりに) 使用します。 -
msg.senderが信頼できるフォワーダーではない場合 (またはmsg.dataが 20 バイト未満の場合)、元のmsg.senderをそのまま返します。
受信者(Recipient) は、信頼されていないコントラクトから追加されたアドレス データを抽出することを防ぐために、フォワーダーを信頼していることを確認しなければなりません (MUST)。
これにより、アドレスが偽造される可能性があります。
プロトコルサポート検出メカニズム(Protocol Support Discovery Mechanism)
受信者(Recipient) コントラクトが、このコントラクトがネイティブ メタ トランザクションをサポートしていることを認識している特定のフロントエンドで使用されていない限り、コントラクトと対話するためにメタトランザクションを使用するという選択肢をユーザーに提供することはできません。
したがって、受信者(Recipient) がメタ トランザクションをサポートしていることを世界に知らせることができるメカニズムが必要です。
これは、メタ トランザクションを Web3 ウォレット レベルでサポートする場合に特に重要です。
このようなウォレットは、ユーザーがやり取りしたい 受信者(Recipient) コントラクトについて必ずしも何も知っているとは限りません。
受信者(Recipient) はさまざまなインターフェースや機能(トランザクションのバッチ処理、さまざまなメッセージ署名形式など)を持つ転送者を信頼できるため、ウォレットがどの転送者(Forwarder)が信頼できるかを検出できるようにする必要があります。
この検出メカニズムを提供するには、受信者(Recipient) コントラクトはこの機能を実装する必要があります。
function isTrustedForwarder(address forwarder) external view returns(bool);
isTrustedForwarder は、転送者が受信者によって信頼されている場合は true を返さなければなりません。そうでない場合は false を返さなければなりません(MUST)。
isTrustedForwarder を元に戻してはなりません(MUST NOT)。
内部的には、受信者は転送者からのリクエストを受け入れなければなりません (MUST)。
isTrustedForwarder 関数はオンチェーンで呼び出すことができ、そのためガス制限を導入しなければなりません (MUST)。
50,000 を超えるガスを消費してはなりません(SHOULD NOT)。
理論的根拠(Rationale)
- 最もシンプルで実行可能なコントラクト インターフェイスを標準化することで、コントラクト開発者がメタ トランザクションのサポートを簡単に追加できるようにします。
- 受信者コントラクトでメタ トランザクションがサポートされていない場合、外部所有のアカウントはメタ トランザクションを使用して受信者コントラクトと対話できません。
- 標準のコントラクト インターフェイスがなければ、受信者がメタ トランザクションをサポートしているかどうかをクライアントが検出する標準的な方法はありません。
- 標準のコントラクト インターフェイスがなければ、メタ トランザクションを受信者に送信する標準的な方法はありません。
- 信頼できる送信者を利用できない場合、すべての受信者コントラクトは、メタ トランザクションを安全に受け入れるために必要なロジックを内部で実装する必要があります。
- 検出プロトコルがなければ、受信者が特定の送信者をサポートしているかどうかをクライアントが検出するメカニズムはありません。
- コントラクト インターフェイスを信頼できる送信者の内部実装の詳細に依存しないようにすることで、受信者コントラクトがコードを変更せずに複数の送信者をサポートできるようになります。
-
msg.senderは、誰がトランザクションに署名したかを判断するためにコントラクトによって検査できるトランザクション パラメーターです。
このパラメータの整合性はイーサリアム EVM によって保証されていますが、メタ トランザクションの場合はmsg.senderを保護するだけでは不十分です。- 問題は、メタ トランザクションをネイティブに認識していないコントラクトの場合、トランザクションの
msg.senderによって、トランザクション署名者(Transaction Signer) ではなく ガスリレー(Gas Relay) から送信されているように見えることです。
メタトランザクションを受け入れるためのコントラクトの安全なプロトコルでは、ガスリレー(Gas Relay) が トランザクション署名者(Transaction Signer) によるリクエストを偽造、変更、または複製することを防ぐ必要があります。
- 問題は、メタ トランザクションをネイティブに認識していないコントラクトの場合、トランザクションの
リファレンス実装(Reference Implementation)
受信者の例(Recipient Example)
contract RecipientExample {
function purchaseItem(uint256 itemId) external {
address sender = _msgSender();
// ... perform the purchase for sender
}
address immutable _trustedForwarder;
constructor(address trustedForwarder) internal {
_trustedForwarder = trustedForwarder;
}
function isTrustedForwarder(address forwarder) public returns(bool) {
return forwarder == _trustedForwarder;
}
function _msgSender() internal view returns (address payable signer) {
signer = msg.sender;
if (msg.data.length>=20 && isTrustedForwarder(signer)) {
assembly {
signer := shr(96,calldataload(sub(calldatasize(),20)))
}
}
}
}
セキュリティに関する考慮事項(Security Considerations)
悪意のある送信者は、_msgSender() の値を偽造し、任意のアドレスから事実上トランザクションを送信する可能性があります。
したがって、Recipient(受信者)コントラクトは送信者を信頼する際に細心の注意を払う必要があります。
送信者ーがアップグレード可能な場合は、コントラクトが悪意のあるアップグレードを実行しないことも信頼する必要があります。
さらに、攻撃者が自分のアドレスを「信頼(trust)」してトランザクションを転送し、トランザクションを偽造できる可能性があるため、どの送信者を信頼するかを変更することも制限する必要があります。
信頼できる送信者のリストを不変にすることをお勧めします。これが不可能な場合は、信頼できるコントラクト所有者のみがリストを変更できるようにする必要があります。
著作権(Copyright)
著作権および関連する権利は CC0 を通じて放棄されます。
引用(Citation)
この文書を次のように引用してください。
Ronan Sandford (@wighawag), Liraz Siri (@lirazsiri), Dror Tirosh (@drortirosh), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Hadrien Croubois (@Amxx), Sachin Tomar (@tomarsachin2271), Patrick McCorry (@stonecoldpat), Nicolas Venturo (@nventuro), Fabian Vogelsteller (@frozeman), Gavin John (@Pandapip1), "ERC-2771: Secure Protocol for Native Meta Transactions," Ethereum Improvement Proposals, no. 2771, July 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2771.
まとめ(Conclusion)
要するにGas代を保持していない トランザクション署名者(Transaction Signer)のコントラクト実行をガスリレー(Gas Relay)がGas代を肩代わりして実行するのだが、コントラクトを直接実行するのではなく信頼できる転送者(Trusted Forwarder)を経由して受信者(Recipient)コントラクトを実行するということらしいです。
ガスリレー(Gas Relay)が直接受信者(Recipient)コントラクトを実行すると、実行者がガスリレー(Gas Relay)になってしまうので、信頼できる転送者(Trusted Forwarder)を経由を利用します。
受信者(Recipient)コントラクトでは、msg.senderではなく_msgSender()を利用することで、通常にコントラクトを実行した場合でも問題なく動作するようになっています。
重要な事として、受信者(Recipient)コントラクトが許しちゃうと勝手に他人になりすましが出来てしまうので、信頼できる転送者(Trusted Forwarder)を指定する必要があります。
また、信頼できる転送者(Trusted Forwarder)はトランザクション署名者(Transaction Signer)を検証する機能を持っている必要があります。
(このあたりの実装については、このERCにはあまり言及されていません。)
