はじめに
初めまして。
CryptoGamesというブロックチェーンゲーム企業でエンジニアをしている cardene(かるでね) です!
スマートコントラクトを書いたり、フロントエンド・バックエンド・インフラと幅広く触れています。
代表的なゲームはクリプトスペルズというブロックチェーンゲームです。
今回は、メソッドの動作拡張に使用される、動的サイズのbytes型引数の追加を提案している規格であるERC5750についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なERCについてまとめています。
概要
このEIPは、将来の拡張性を考慮して、関数への非構造化データの渡し方を統一する規格です。
コントラクト内で関数にデータを渡す方法を一元化し、将来の変更や新機能の追加を容易にすることを目指しています。
以下のERC721のsafeTransferFrom
関数などの引数末尾に追加されているdata
についての提案です。
既にopenzeppelinなどには組み込まれていますね。
動機
メソッドに追加データを組み込むことの目的は、既存のメソッドの機能を将来の拡張に対応させることです。
メソッドの拡張可能性は非常に有益です。
このEIPに準拠することで、transfer
やvote
などのメソッドは、文字列形式の理由(reasons)などの追加データを利用できます。
また、このEIPに従う既存のEIPのエクスポートされたメソッドは、追加データを使用してエンドースメントの証明、ソルト(solt)、ノンス(nonce)、またはリベル/コミット方式のコミットメントなど、新しい機能を追加できます。
最後に、データはコールバックに渡すためにも利用できます。
既存の関数を拡張する方法は、主に2つあり、それぞれ独自の課題が伴います。
新しいメソッドを追加する
- メソッド名は何になるか?
- どのようなパラメータが必要か?
- このメソッドのシグネチャがどのような使用ケースをサポートするか?
- オフチェーン署名をサポートするか?
既存のパラメータを利用するか、新たなパラメータを追加する
- 既存のパラメータを再利用するか、新しいパラメータを加えるべきか?
- どれくらいの数のパラメータを使うべきか?
- パラメータのサイズと型は何か?
メソッドの拡張方法を標準化することは、これらの課題に対する具体的なガイドラインを提供し、開発者にとって有用です。
このEIPは最大限の後方互換性と将来の互換性を確保しようとしています。
既にいくつかのEIP(例: EIP721やEIP1155)がこのEIPを部分的にサポートしており、新しい機能を追加する時に便益をもたらします。
他の実装者やEIPは、このEIPに依存することで、将来の新しい機能を実現できるメソッドインターフェースを提供できます。
ERC721については以下を参考にしてください。
ERC1155については以下を参考にしてください。
リベル/コミット方式のコミットメント
情報の秘匿性や信頼性を確保するための手法で、特に暗号学的なプロトコルやセキュリティ関連のアプリケーションで使用されます。
以下に、具体例を用いてリベル/コミット方式のコミットメントについて詳しく説明します。
背景情報
Alice(送信者)とBob(受信者)が秘密の情報をやり取りする場面を考えます。
Aliceはメッセージを送信し、Bobはそのメッセージを受け取ります。
しかし、Aliceはメッセージを公開したくない場合があります。
この場合、リベル/コミット方式のコミットメントが役立ちます。
リベル/コミット方式の手順
-
コミットメント(Commitment)の作成
- Aliceはメッセージを秘密のまま保持しながら、コミットメントを生成します。
- コミットメントは通常、ハッシュ関数を使用してメッセージから計算されます。
- 例:Aliceが「
Hello, Bob!
」というメッセージを送りたい場合、SHA-256ハッシュを計算し、コミットメント「2ef7bde608ce5404e97d5f042f95f89f1c232871
」を生成します。
-
コミットメントの送信
- AliceはコミットメントをBobに送信します。
- この段階ではまだメッセージの内容は公開されていません。
-
リベル(Reveal)
- Bobはコミットメントを受け取った後、Aliceからメッセージを要求します。
-
Aliceは秘密のメッセージ「
Hello, Bob!
」を公開します。
-
コミットメントの確認
- BobはAliceから受け取ったメッセージを元に、同じハッシュ関数を使用してコミットメントを再計算します。
- Bobの再計算結果が、Aliceが最初に送信したコミットメントと一致するか確認します。
-
メッセージの受け取り
- BobはAliceから受け取ったメッセージを解読し、内容を確認できます。
利点
- Aliceはメッセージを公開せずに、コミットメントを送信できます。
- Bobは、コミットメントが正しいかどうかを確認した後、メッセージを受け取ります。
- セキュリティが向上し、情報漏洩のリスクが低減します。
この例では、リベル/コミット方式のコミットメントが秘密情報のやり取りに使用されましたが、これは暗号学的なプロトコルやブロックチェーンにおいても信頼性の高い手法として広く利用されています。
仕様
このEIPでは、「bytes」という用語は、Solidityデータ型で使われる可変サイズのバイト配列を指します。
このEIPは、他の多くのERCとは異なり、コントラクト全体ではなく、コントラクト内のメソッドのレベルでの規格を定めています。
このEIPに従うと、最後のパラメータとして「bytes」を持つ任意のメソッドはこの規格に「準拠メソッド」と見なされます。
これらのメソッドは以下のように見えます。
function メソッド名(型1 値1, 型2 値2, ... bytes データ)
この規格に準拠するメソッドは、適格なメソッドである必要があり、さらにそのメソッドのパラメータの中で最後の「bytes」フィールドを動作を拡張するために指定する必要があります。
また、適格なメソッドには、同じ名前のオーバーロードされたメソッドが存在し、最後の「bytes」パラメータを持たない場合、かつ「bytes」が空の配列である場合、準拠メソッドの動作はそのオーバーロードされた同名のメソッドと同一である必要があります。
このEIPは、メソッドのパラメータと動作を特定の方法で標準化し、メソッドの拡張性を確保するための規格です。
準拠と非準拠の例
以下は、Fooコントラクト内の準拠メソッドmethodName1
です。
contract Foo {
// @dev This method allows extension behavior via `_data` field;
function methodName1(uint256 _param1, address _param2, bytes calldata _data);
function firstNonRelatedMethod(uint256 someValue);
function secondNonRelatedMethod(uint256 someValue);
}
以下は、別のmethodName2
にオーバーロードされたBarコントラクト内の準拠メソッドmethodName2
です。
contract Foo {
// @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);`
function methodName2(uint256 _param1, address _param2);
// @dev This method allows extension behavior via `_data` field;
// When passed in an empty array for `_data` field, this method
// MUST behave IDENTICAL to
// its overloaded sibling `methodName2(uint256 _param1, address _param2);`
function methodName2(uint256 _param1, address _param2, bytes calldata _data);
function firstNonRelatedMethod(uint256 someValue);
function secondNonRelatedMethod(uint256 someValue);
}
以下は、動作の拡張を許可しないため、非準拠なmethodName1
メソッドです。
contract Foo {
// @dev This method DO NOT allow extension behavior via `_data` field;
function methodName1(uint256 _param1, address _param2, bytes calldata _data);
function firstNonRelatedMethod(uint256 someValue);
function secondNonRelatedMethod(uint256 someValue);
}
以下は、空の配列である_data
が渡された場合に、オーバーロードされた同名のメソッドmethodName2(uint256 _param1, address _param2);
とは異なる動作をするため、非準拠なmethodName2(uint256 _param1, address _param2, bytes calldata _data);
です。
contract Foo {
// @dev This is a sibling method to `methodName2(uint256 _param1, address _param2, bytes calldata _data);`
function methodName2(uint256 _param1, address _param2);
// @dev This method allows extension behavior via `_data` field;
// When passed in an empty array for `_data` field, this method
// behave DIFFERENTLY to
// its overloaded sibling `methodName2(uint256 _param1, address _param2);`
function methodName2(uint256 _param1, address _param2, bytes calldata _data);
function firstNonRelatedMethod(uint256 someValue);
function secondNonRelatedMethod(uint256 someValue);
}
補足
「bytes」という可変サイズのバイト配列のデータ型を使用することは、さまざまな種類のデータを格納し、扱うための非常に柔軟な方法です。
このデータ型をメソッドの最後のパラメータとして指定することにより、このEIPはSolidityのデータ配置(calldata
レイアウト)と調和するよう設計されています。
このEIPは、異なる種類のデータを含むペイロード(情報の塊)を取り扱うために「bytes」という可変サイズのバイト配列を利用します。
そして、この「bytes」パラメータをメソッドのパラメータリストの最後に配置することで、Solidityのデータ配置に合わせることができます。
この仕組みにより、コントラクトやメソッドを呼び出す際に、異なるデータタイプや情報を柔軟にやり取りできるようになります。
これは、コントラクトやメソッドの使用範囲を広げ、さまざまなタイプのデータを取り扱うための強力なツールとなります。
後方互換性
多くの既存のEIP(Ethereum Improvement Proposal)は、既にその仕様の一部として準拠メソッドを持っています。
これらのEIPに準拠しているすべてのコントラクトは、このEIPに対して完全または部分的に準拠しています。
以下は、その一部のEIPの例です。
- EIP721では、次のメソッドが既にこの規格に準拠しています。
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
- EIP1155では、次のメソッドが既にこの規格に準拠しています。
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;
- EIP777では、次のメソッドが既にこの規格に準拠しています。
function burn(uint256 amount, bytes calldata data) external;
function send(address to, uint256 amount, bytes calldata data) external;
ただし、最後のパラメータとして「bytes」を持つすべての関数が準拠しているわけではありません。
以下の関数は、オーバーロードがない限り準拠していません。
なぜなら、その最後のパラメータが機能に関与しているからです。
- EIP2535では、次のメソッドが準拠していません。
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata) external;
- これらの準拠しないメソッドを準拠させるために、次のいずれかの方法が採用されることがあります。
-
新しいオーバーロードを作成する。
function diamondCut(FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata, bytes calldata _data) external;
- 上記のように、元のメソッドのすべてのパラメータの後に新しい「
_data
」を追加する。
- 上記のように、元のメソッドのすべてのパラメータの後に新しい「
-
「
bytes memory _calldata
」の使用を柔軟にする。- 「
bytes memory _calldata
」の使用方法を変更して、動作を拡張できるようにする。
- 「
- EIP1271では、次のメソッドが準拠していません。
function isValidSignature(bytes32 _hash, bytes memory _signature) public view returns (bytes4 magicValue);
これらの準拠しないメソッドを準拠させるために、次のいずれかの方法が採用されることがあります。
-
新しいオーバーロードを作成する
function isValidSignature(bytes32 _hash, bytes memory _signature, bytes calldata _data) public view returns (bytes4 magicValue);
- 上記のように、元のメソッドのすべてのパラメータの後に新しい「
_data
」を追加する。
- 上記のように、元のメソッドのすべてのパラメータの後に新しい「
-
「
bytes memory _signature
」の使用を柔軟にする。- 「
bytes memory _signature
」の使用方法を変更して、動作を拡張できるようにする。
- 「
これにより、これらのEIPに準拠しないメソッドも準拠させることができます。
セキュリティ考慮事項
追加データを使用して特定の拡張動作を行う場合、その拡張動作に関する最善の方法を実践することが重要です。
例えば、オンチェーンの検証に署名を提供したり、コミット-リベル方式でコミットメントを提供したりする場合、以下のポイントに留意する必要があります。
セキュリティとプライバシー
追加データを含める時には、セキュリティとプライバシーを最優先に考えるべきです。
データパラメータには、個人を特定できる情報や機密情報を含めないように注意が必要です。
リプレイ攻撃対策
データパラメータが公然と公開されることを考慮して、リプレイ攻撃に対する対策を講じる必要があります。
これには、トランザクションの一意性やトランザクションの順序に関する検討が含まれます。
トランザクション順序攻撃対策
データパラメータを含むトランザクションの順序が攻撃者に利用されないように対策を施す必要があります。
これにより、意図しない動作や不正な操作を防ぐことができます。
ベストプラクティスの遵守
特定の拡張動作に関連するベストプラクティスを遵守することは非常に重要です。
業界標準やセキュリティガイドラインに従い、セキュリティ強化措置を実施しましょう。
追加データの使用は慎重に行うべきであり、セキュリティとプライバシーを確保するために最適な方法を選択するべきです。
また、攻撃リスクに対する対策をしっかりと講じることが重要です。
引用
Zainan Victor Zhou (@xinbenlv), "ERC-5750: General Extensibility for Method Behaviors," Ethereum Improvement Proposals, no. 5750, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5750.
最後に
今回は「メソッドの動作拡張に使用される、動的サイズのbytes型引数の追加を提案している規格であるERC5750」についてまとめてきました!
いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!