0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ERC7582] コントラクトアカウントの検証処理を外部コントラクトに委譲する仕組みを理解しよう!

Posted at

はじめに

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

今回は、既存のERC4337インターフェースを変更せず、検証ロジックをモジュール化できる新しいアカウント拡張の仕組みを提案しているERC7582についてまとめていきます!

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

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

概要

ERC4337は、アカウント(ウォレット)をスマートコントラクトとして実装するための仕組みを提案している規格です。
ERC7582は、ERC4337系のスマートコントラクトアカウントに対して、「プラグイン」と呼ばれる拡張モジュールを安全かつ標準的な方法で追加できるようにするものです。

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

ここでいうプラグインとは、アカウント本体とは別コントラクトとして実装される拡張ロジックのことで、アカウントから呼び出されて特殊な権限で実行されるコードのことを指します。
さらに「コンポーザブルなロジック」という表現は、複数のプラグインや検証ロジックを組み合わせて利用できるようにする、という意味です。

この仕様では、新しいインターフェースを増やすのではなく、すでにERC4337が定義している仕組みを活用してプラグイン連携を実現します。
具体的には以下の要素を利用します。

要素名 役割の説明
Entry Point ERC4337で全てのユーザー操作を受け付けるルーター的なコントラクトです。
NonceManager Entry Pointに紐づいた「nonce(使い捨てカウンター)」を一元管理する仕組みで、リプレイ攻撃(同じ操作の二重実行)を防ぐために使われます。
UserOperationEvent Entry Pointがユーザー操作を処理したときに発行されるイベントで、誰がどのような操作を行ったかをログとして残します。
IAccount.validateUserOp ERC4337のスマートコントラクトアカウントが実装すべき唯一の関数で、ユーザー操作の署名検証や残高チェックなどを行います。

ERC7582では、上記のような既存の仕組みを「プラグインにも適用できるように整理し直す」ことで、アカウントがプラグインをどのように呼び出し、どのプラグインにどのような実行権限を与えるかを明確に扱えるようにします。

特に重要なのは、validateUserOp を通じて「どの検証ロジック(ここではプラグイン側のバリデータ)を使うか」を切り出せるようにする点です。これにより、アカウントは以下のようなことができるようになります。

  • どのプラグインを呼ぶかを、ユーザー操作ごとに柔軟に決める
  • プラグインに対して、通常の外部コントラクトとは異なる特別な実行権限を与える

ERC4337は、IAccountインターフェースとして validateUserOp だけを要求するミニマルな設計になっていますが、この仕様はそのミニマルさを壊しません。
IAccountに新しい関数を増やさずに、既存の validateUserOp とEntry Point周りの情報だけを使ってプラグイン連携を実現しようとしている点が特徴です。
そのため、開発者にとっても扱いやすく、既存のアカウント実装や他のアカウント抽象化(アカウントアブストラクション)の標準と衝突しづらいアプローチになっています。

動機

スマートコントラクトアカウント(コントラクトアカウント)は、秘密鍵だけを持つ従来型のウォレットとは違い、ユーザーの資産管理やトランザクションの実行方法をコードで自由にカスタマイズできるアカウントです。
ユーザー行動を事前にプログラムしておけるため、表現力が高く強力な仕組みです。

一方で、実運用レベルで使いやすい機能を実現しようとすると、まだ設計が標準化されていない部分が多く、機能や柔軟性に制約が出やすいという課題があります。
特に「安全に抽象化(アブストラクション)するための設計」が十分に合意されていないと、ウォレットごとにバラバラな実装になり、エコシステム全体としての再利用性や互換性が低くなってしまいます。

ERC4337は、この問題の一部を解決するために、以下のようなコアな関心事を共通化・簡素化することに成功しています。

  • トランザクション手数料の支払い方法の抽象化(ユーザーが直接ETHを持っていなくてもよい、など)
  • Entry Pointを中心とした共通ルートを通じて、アカウントの処理フローを統一すること

ただし、ユーザーの意図をよりリッチに表現し、日常的なユースケースに近づけるためには、さらに「アカウントの権限や検証ロジックを別コントラクトに委譲する」ための、シンプルで予測しやすい仕組みがあると便利です。

例えば、以下のような機能は典型例です。

  • ソーシャルリカバリー
    • ウォレットを復旧するときに、複数の信頼できる人やデバイスの承認で鍵を復元する仕組みです。
  • 支払いスケジュール(定期支払い)
    • サブスクリプションのように、決まったタイミングで自動的に支払いが行われる仕組みです。
  • カストディアル(管理者付き)サービスに近い体験
    • 取引所や専門サービスのように、特定の権限を持つ第三者や専用アプリが代わりに実行してくれる体験に近づけることです。

これらを自然に実現するには、アカウントが特定のプラグインに対して「実行権限」や「検証権限」を委譲でき、なおかつその挙動が予測しやすく、標準化されていることが重要です。
そうすることで、ユーザー体験を保ったまま、より高度な機能を組み合わせて使うことができます。

ERC4337IAccountインターフェース自体は、validateUserOp という関数だけを定義しており、プラグインやカスタム検証ロジックを追加するための専用APIは用意していません。
そのため、従来はアカウントコントラクト自体をアップグレードしたり、まるごと移行(マイグレーション)したりしないと、こうした拡張が難しい状況でした。

しかし、IAccountとEntry Point周りの仕組みをよく見ると、次のような情報がすでに揃っています。

  • Entry PointとNonceManager による一意なナンス管理(シングルトンなナンス追跡)
  • validateUserOp の中でユーザー操作の検証を行うというフックポイント
  • UserOperationEventによる実行ログ

ERC4337は、これらの情報をうまく利用することで、追加のインターフェースを定義しなくても、スマートコントラクトアカウントにプラグインやその他の組み合わせ可能な検証ロジックを後付けできると整理しています。

まとめると、ERC4337は以下のような狙いを持っています。

  • 既存のIAccount/Entry Point/NonceManagerの枠組みを前提にしつつ
  • アカウントがプラグインに権限・検証ロジックを委譲できる
  • しかも、新しい必須関数を増やさないミニマルな方法で

これによって、アカウントアブストラクションの利点を保ちながら、ソーシャルリカバリーや定期支払いなどの高度な機能を、より開発しやすく、ユーザーにとっても扱いやすい形で提供できるようにしようとしています。

仕様

MADVの基本アイデア

ERC4337では、UserOperation(ユーザー操作)ごとに「nonce(ナンス)」という使い捨て番号を必ず含めます。
ERC7582では、このナンスを単なる連番として使うのではなく、ナンスの上位192bitを「バリデータ(validator)識別子」として利用するという新しい方法を導入します。

ここでいうバリデータとは、アカウントの署名検証や追加ロジックを担当するコントラクトのことです。

MADV(Modular Account Delegated Validation)アカウントは、ナンスのこの部分を読み取ることで、次の処理を行います。

  1. どのバリデータを使うかを判定する。
  2. UserOperationの全データ(calldata)をそのバリデータに渡す。
  3. バリデータが計算した validationData をEntryPointに返す。

これによって、アカウント本体には複雑な検証ロジックを置かず、検証ロジックを自由に差し替えられるプラグイン構造が成立します。

MADV のフロー

image.png
https://eips.ethereum.org/EIPS/eip-7582

  1. ユーザーがUserOperationを作成し、nonce の上位192bitに「validator の識別子」を埋め込む。
  2. EntryPointが validateUserOp をMADVアカウントへ呼び出す。
  3. MADVアカウントは nonce のupper 192bitを読み取り、そこからバリデータコントラクトのアドレスを取得する。
  4. UserOperation全体を引数として、対応するバリデータへ委譲する(delegation)。
  5. バリデータはERC4337標準の validationData を返す。
  6. MADVアカウントはその値をEntryPointに返す。
  7. EntryPointは validationData をもとに実行可否を判断する。

この流れの特徴は、以下の点にあります。

特徴 内容
nonce を実質的な「プラグイン識別子」として再利用 追加のデータ領域や signature の拡張が不要。
EntryPointの getNonce がそのまま使える ERC4337標準のナンス管理の利点をそのまま保持。
UserOperationEventnonce が必ず含まれる プラグインが何を使ったか分析しやすい。

nonce を識別子として使う理由

同じ結果を得る方法としては、signature フィールドにプラグイン識別子を詰め込む方法があります。
しかし、MADVでは以下の理由で nonce を採用しています。

理由 内容
calldata コストの削減 nonce は必須フィールドなので追加コストがかからない。
NonceManagerの自動管理をそのまま利用できる キーごとに連番が管理されるため、プラグイン単位の実行も安全に処理される。
UserOperationEventnonce を露出している signature フィールドはイベントで露出しないため、追跡しにくい。

また、nonce の下位64bitは従来通り連番として利用されるため、ERC4337との互換性が保たれます。

MADVアカウントが守るべきルール

MADV アカウント(送信者コントラクト)が必ず行うべき

動作 内容
validator の識別子抽出 nonce の上位192bitを取り出してバリデータを特定する。
バリデータへの転送 userOpcalldata をそのままバリデータに転送する。
バリデータの結果を返す バリデータが返した validationData をEntryPointに返す。

バリデータコントラクトが必ず守る

動作 内容
ERC4337の慣習を守る 特に validationData の形式を満たす。
validateUserOp 委譲に対応 MADVから受け取った userOp 全体を元に検証を行う。

ERC4337の関連コードの意味

PackedUserOperation

UserOperation のデータ構造をまとめたもの。
特に nonce の「上位192bitが validator key」として使われる点が重要。

IAccount.validateUserOp

EntryPointがアカウントに対して行う唯一の検証フック。
MADVではここでバリデータ識別子を抽出し、バリデータに委譲する。

NonceManager.getNonce

nonce の上位192bit(= key)ごとに、連番のナンスを返す。
MADVのプラグイン識別子に最適。

UserOperationEvent

UserOperation の処理結果をログとして残すイベント。
nonce が露出する点が、プラグイン動作の分析を容易にする。

インターフェース

PackedUserOperation interface

/**
 * User Operation struct
 * @param sender                - The sender account of this request.
 * @param nonce                 - Unique value the sender uses to verify it is not a replay. In MADV, the validator identifier is encoded in the high 192 bit (`key`) of the nonce value
 * @param initCode              - If set, the account contract will be created by this constructor/
 * @param callData              - The method call to execute on this account.
 * @param accountGasLimits      - Packed gas limits for validateUserOp and gas limit passed to the callData method call.
 * @param preVerificationGas    - Gas not calculated by the handleOps method, but added to the gas paid.
 *                                Covers batch overhead.
 * @param gasFees               - packed gas fields maxPriorityFeePerGas and maxFeePerGas - Same as EIP-1559 gas parameters.
 * @param paymasterAndData      - If set, this field holds the paymaster address, verification gas limit, postOp gas limit and paymaster-specific extra data
 *                                The paymaster will pay for the transaction instead of the sender.
 * @param signature             - Sender-verified signature over the entire request, the EntryPoint address and the chain ID.
 */
struct PackedUserOperation {
    address sender;
    uint256 nonce;
    bytes initCode;
    bytes callData;
    bytes32 accountGasLimits;
    uint256 preVerificationGas;
    bytes32 gasFees;
    bytes paymasterAndData;
    bytes signature;
}

IAccount interface

interface IAccount {
    /**
     * Validate user's signature and nonce
     * the entryPoint will make the call to the recipient only if this validation call returns successfully.
     * signature failure should be reported by returning SIG_VALIDATION_FAILED (1).
     * This allows making a "simulation call" without a valid signature
     * Other failures (e.g. nonce mismatch, or invalid signature format) should still revert to signal failure.
     *
     * @dev Must validate caller is the entryPoint.
     *      Must validate the signature and nonce
     * @param userOp              - The operation that is about to be executed.
     * @param userOpHash          - Hash of the user's request data. can be used as the basis for signature.
     * @param missingAccountFunds - Missing funds on the account's deposit in the entrypoint.
     *                              This is the minimum amount to transfer to the sender(entryPoint) to be
     *                              able to make the call. The excess is left as a deposit in the entrypoint
     *                              for future calls. Can be withdrawn anytime using "entryPoint.withdrawTo()".
     *                              In case there is a paymaster in the request (or the current deposit is high
     *                              enough), this value will be zero.
     * @return validationData       - Packaged ValidationData structure. use `_packValidationData` and
     *                              `_unpackValidationData` to encode and decode.
     *                              <20-byte> sigAuthorizer - 0 for valid signature, 1 to mark signature failure,
     *                                 otherwise, an address of an "authorizer" contract.
     *                              <6-byte> validUntil - Last timestamp this operation is valid. 0 for "indefinite"
     *                              <6-byte> validAfter - First timestamp this operation is valid
     *                                                    If an account doesn't use time-range, it is enough to
     *                                                    return SIG_VALIDATION_FAILED value (1) for signature failure.
     *                              Note that the validation code cannot use block.timestamp (or block.number) directly.
     */
    function validateUserOp(
        PackedUserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external returns (uint256 validationData);
}

NonceManager interface

 /**
     * Return the next nonce for this sender.
     * Within a given key, the nonce values are sequenced (starting with zero, and incremented by one on each userop)
     * But UserOp with different keys can come with arbitrary order.
     *
     * @param sender the account address
     * @param key the high 192 bit of the nonce, in MADV the validator identifier is encoded here 
     * @return nonce a full nonce to pass for next UserOp with this sender.
     */
    function getNonce(address sender, uint192 key)
    external view returns (uint256 nonce);

UserOperationEvent

/***
     * An event emitted after each successful request
     * @param userOpHash - unique identifier for the request (hash its entire content, except signature).
     * @param sender - the account that generates this request.
     * @param paymaster - if non-null, the paymaster that pays for this request.
     * @param nonce - the nonce value from the request.
     * @param success - true if the sender transaction succeeded, false if reverted.
     * @param actualGasCost - actual amount paid (by account or paymaster) for this UserOperation.
     * @param actualGasUsed - total gas used by this UserOperation (including preVerification, creation, validation and execution).
     */
    event UserOperationEvent(bytes32 indexed userOpHash, address indexed sender, address indexed paymaster, uint256 nonce, bool success, uint256 actualGasCost, uint256 actualGasUsed);

構造体

PackedUserOperation

struct PackedUserOperation {
    address sender;
    uint256 nonce;
    bytes initCode;
    bytes callData;
    bytes32 accountGasLimits;
    uint256 preVerificationGas;
    bytes32 gasFees;
    bytes paymasterAndData;
    bytes signature;
}

ユーザー操作(UserOperation)の内容をまとめた構造体。
ERC4337が EntryPointに処理させるユーザー操作をひとつのデータ構造にまとめたものです。
送信者アドレス、nonce、コントラクト生成のためのコード、アカウントに対して実行するメソッドの呼び出し内容、ガスに関連する設定値、支払いに利用されるペイマスター情報、署名など、操作に必要な値がすべて含まれています。
MADVでは nonce の上位192bitをバリデータ識別子として利用するため、この構造体の nonce フィールドが特に重要になります。

  • パラメータ
フィールド名 内容
sender UserOperationを送信するアカウントのアドレス。
nonce リプレイ攻撃防止のための番号。MADV では上位192bitにバリデータ識別子を含める。
initCode アカウントが未作成の場合に、そのアカウントを生成するためのコード。
callData アカウントに対して実行するメソッドの内容。
accountGasLimits validateUserOpcallData 実行時に使われるガス上限をまとめた値。
preVerificationGas handleOps 全体の処理に必要な追加ガス量。
gasFees maxPriorityFeePerGasmaxFeePerGas のパラメータをまとめた値。
paymasterAndData Paymasterのアドレスと追加データをまとめた値。
signature UserOperation全体、EntryPointアドレス、チェーンIDに対する署名。

イベント

UserOperationEvent

event UserOperationEvent(
    bytes32 indexed userOpHash,
    address indexed sender,
    address indexed paymaster,
    uint256 nonce,
    bool success,
    uint256 actualGasCost,
    uint256 actualGasUsed
);

UserOperationが正常に処理されたときに発行されるイベント。
EntryPointがUserOperationを実行し終えたあとに必ず発行されるイベントです。
操作のハッシュ、送信者、利用されたペイマスター、nonce、成功したかどうか、実際に消費したガス量などが含まれます。
MADVでは nonce にバリデータ識別子が含まれるため、このイベントを読み取ることでどのプラグインを使ったかを外部から把握できるという利点があります。

  • パラメータ
パラメータ名 説明
userOpHash UserOperation 全体(署名除く)から作られる識別ハッシュ。
sender UserOperation を実行したアカウント。
paymaster 手数料を支払った Paymaster のアドレス。
nonce UserOperation に含まれていた nonce。MADV では識別子を含む。
success 実行が成功したかどうか。
actualGasCost 最終的に支払われたガスコスト。
actualGasUsed 検証・実行を含め、UserOperation 全体で使用した総ガス量。

関数

validateUserOp(IAccount)

function validateUserOp(
    PackedUserOperation calldata userOp,
    bytes32 userOpHash,
    uint256 missingAccountFunds
) external returns (uint256 validationData);

EntryPointがアカウントに対して署名とナンスを検証させる関数。
ERC4337のアカウントが必ず実装しなければならない関数です。
EntryPointのみが呼び出すことができます。ユーザー操作の署名確認、nonce 確認、必要であればEntryPointへの不足資金送金などを行います。
MADVでは、ここでバリデータ識別子の抽出・バリデータコントラクトへの委譲を行い、そのバリデータが返す validationData をそのままEntryPointに返します。
validationData は署名の正当性や有効期限などを含む特殊なフォーマットの値です。

  • 引数
引数名 説明
userOp 実行されるUserOperation。
userOpHash UserOperationのハッシュ。署名検証に利用される。
missingAccountFunds EntryPointに入金すべき不足金額。
  • 戻り値
戻り値名 説明
validationData 検証結果として返す値。署名の妥当性や有効期限をまとめたデータ。

getNonce(NonceManager)

function getNonce(address sender, uint192 key)
    external view returns (uint256 nonce);

指定した senderkey の組み合わせから、次に使用する nonce を返す関数。
ERC4337NonceManagerが提供する関数で、アカウントごと、さらに key ごとにナンスを管理します。
MADVでは key = nonce の上位192bit = バリデータ識別子として扱われるため、プラグインごとに別のナンス列が自動で付与されます。
これにより、異なるバリデータを使うUserOperationが独立したナンス管理を持てるようになり、安全性が向上します。

  • 引数
引数名 説明
sender アカウントのアドレス。
key nonce の上位192bitを表す値。MADVではバリデータ識別子。
  • 戻り値
戻り値名 説明
nonce 次に利用すべき nonce。

補足

目的と設計思想

ERC4337はアカウントアブストラクションを実現するための枠組みですが、インターフェースとしては最小限の validateUserOp のみが定義されています。
MADVは、このミニマルな設計を壊すことなく、追加機能を柔軟に拡張できるようにすることを目的としています。
特に、既存のインターフェースへ新しい関数を追加する必要がないため、アカウント開発者・バリデータ開発者双方にとって扱いやすく、導入しやすいという利点があります。

さらに、MADVは既存フィールドである nonce を「バリデータ識別子として流用する」という方法を採用しています。
この流用によって、検証プラグインの識別のために専用フィールドを追加する必要がなく、結果として次のメリットがあります。

観点 内容
calldata コストの削減 nonce は必須フィールドのため、追加バイトを増やさず識別情報を載せられる。
getNonce の仕組みの活用 NonceManagerkey 単位管理がそのままプラグイン識別に使える。
UserOperationEvent の利用 nonce がイベントに露出するため、プラグイン呼び出しを追加イベントなしで追跡可能。

この点は、signature フィールドに情報を詰める方法よりも自然で透明性が高く、他の署名スキームと衝突しないという良い特徴を持っています。

バリデータアドレス抽出の柔軟性

MADVではアカウントが「nonce の上位192bitからどのようにバリデータアドレスを取得するか」を自由に定義できます。

この柔軟性により、以下のような使い分けができます。

抽出方式 内容
そのままアドレスとして扱う nonce 上位ビットを直接バリデータアドレスとみなす。導入が簡単。
ストレージ参照として扱う mappingや配列等を使って、より構造化されたプラグイン管理を実現。

この仕組みにより、以下の両方をサポートできます。

  • 必要なときだけ読み込む「オンデマンド型」のプラグイン
  • 何度も利用されるプラグインをストレージに保持する方式

MADVが満たすべき最小要件

MADVが必ず守るべき要件は、とてもシンプルです。

要件 内容
nonce に識別子を載せる nonce の上位192bitにバリデータ identifier を含める。
validationData を返す 抽出したバリデータが算出した validationData を EntryPoint に返す。

つまり、MADVの本質は以下の2点に集約されます。

  • nonce をプラグイン識別子として利用すること
  • プラグイン(validator)が出した検証結果だけを返すこと

この最小構成でプラグインの仕組みを提供できることが、MADVの大きな利点です。

セキュリティ

MADVは新しい関数や EntryPoint側の仕様変更を必要としない非常に小さな拡張であり、意図的に攻撃対象面(attack surface)を小さく保っています。
ただし、アカウント開発者がどのようにバリデータの抽出処理や承認ロジックを設計するかは各実装によって異なるため、以下のような観点での注意が必要です。

MADV自体が持つ性質

MADVが追加するのは「nonce に識別子を入れ、その識別子を基にバリデータに処理を委譲する」というフックだけです。
この仕様自体は新たな攻撃経路を作るものではありません。
ただし、バリデータの実装次第では次のようなリスクが考えられます。

リスク領域 内容
バリデータの署名検証方法 独自方式を採用すると signature forgery(署名偽造)リスクが増える可能性がある。
バリデータのストレージ管理 誤ったmapping管理により意図しないプラグインが選択される可能性がある。
バリデータへの権限移譲 プラグインが不正なロジックを実行できるような設計は避けるべき。

MADVはあくまで「委譲の仕組み」であり、その安全性は委譲先となるバリデータの実装品質に大きく依存します。

ERC4337の検証フローとの整合性

すべてのバリデータはERC4337の検証ルールに従う必要があります。
特に以下は重要です。

項目 説明
validationData の形式 authorizervalidUntil、validAfter を含む既定の構造を正しく返す必要がある。
block.timestampblock.number を直接利用できない ルールに反すると検証の前提が崩れ、意図しない拒否や承認につながる可能性がある。
EntryPointのみが正しく検証を呼び出せることの保証 バリデータ側にも caller チェックなどの防御が必要になる場合がある。

これらを正しく実装しなければ、バリデータの安全性が損なわれ使用者の資産に影響が出る可能性があります。

参考実装

引用

Shivanshi Tyagi (@nerderlyne), Ross Campbell (@z0r0z), "ERC-7582: Modular Accounts with Delegated Validation [DRAFT]," Ethereum Improvement Proposals, no. 7582, December 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7582.

最後に

今回は「既存のERC4337インターフェースを変更せず、検証ロジックをモジュール化できる新しいアカウント拡張の仕組みを提案しているERC7582」についてまとめてきました!
いかがだったでしょうか?

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!

Twitter @cardene777

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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?