はじめに

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

今回は、リカバリーポリシーを自由にカスタマイズできるソーシャルリカバリーの仕組みを提案しているERC7093についてまとめていきます!

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

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

概要

ERC7093は、スマートコントラクトアカウントの「ソーシャルリカバリー」(本人が鍵を失った時、信頼できる人たちの協力でアカウントを復旧する仕組み)を標準化するためのインターフェイスを定義します。 従来の仕組みでは、リカバリーに使う条件や身元確認の仕組みがアカウントのスマートコントラクトに強く結びついていました。ERC7093はこれを切り離し、ユーザーが自由にリカバリーポリシーを選んだり拡張したりできるようにします。

ここで重要になるのが「Guardian(ガーディアン)」です。 ガーディアンとは、アカウントを復旧する際に協力する存在を指し、ブロックチェーン上のアカウントに限らず、オフチェーンの人物認証やNFTなども含めて幅広く設定できるようになります。

これにより、同じスマートコントラクトアカウントでも、後からリカバリーポリシーを柔軟に変更でき、ユーザーは自分に合った方法でアカウントの安全性を高めることができます。

主な特徴をまとめると以下のようになります。

機能・特徴 説明
身元確認とリカバリー処理の分離 認証方法とリカバリー手順が独立し、より柔軟に構成できるようになります。
ガーディアンの幅広い定義 友人・家族などブロックチェーンアカウントを持たない人や、NFT・SBT(譲渡不可トークン)などもガーディアンに設定できます。
リカバリーポリシーのカスタマイズ 要求する署名の数や種類など、ユーザーが柔軟に決定できます。
スマートコントラクトを変更せず機能拡張が可能 アカウント側のコントラクトをアップグレードする必要がなく、新しいポリシーに対応できます。
複数のリカバリーメカニズムを併用可能 1つの方法に依存せず、より安全な構成ができます。

動機

ソーシャルリカバリーという考え方は、Ethereum共同創設者のVitalik Buterin氏が以前から提唱してきた考え方です。 ブロックチェーンの利用者が秘密鍵を失うリスクは常に存在し、特に暗号技術に詳しくない人にとっては致命的です。 そこで、信頼できる人やサービスの協力によってアカウントを取り戻す仕組みが求められてきました。

ソーシャルリカバリーの基本的な動作は以下の流れです。

  1. ユーザー本人とガーディアンの身元を確認 ここでいうガーディアンとは、アカウントのリカバリーを助ける対象です。 ブロックチェーンアカウントだけでなく、本人確認できる外部サービスやトークンなども含みます。
  2. ガーディアンから署名(承認)を集める 複数のガーディンが署名した場合、一定数(閾値)に達するとアカウントを復旧できます。
  3. 署名の種類や組み合わせに基づいて判断します。 例えば、特定のガーディアンの署名は必須とする、一部のグループから複数人の署名が必要など、複雑なルールも設定できます。

従来の仕組みでは、これらのルールはアカウント側のスマートコントラクトに組み込まれていました。 このため、新しい認証手段に対応したい場合はコントラクトをアップデートする必要があり、柔軟性に欠けていました。

ERC7093はこの問題を解決するために以下の考え方を採用しています。

  • 身元確認(ガーディアンとして認めるか)
  • リカバリーポリシー(署名のルールや閾値の決め方)
  • 実際のリカバリー手順

これらを独立させ、ガーディアンの種類やポリシーを自由に拡張できるようにします。 その結果、ユーザーはより幅広い選択肢の中から安全性と利便性のバランスを取ることができます。

例えば、以下のような使い方が可能になります。

  • 友人や家族がブロックチェーンアカウントを持っていなくても、ガーディアンとして設定できる。
  • アカウント所有者だけが保有できるNFT(またはSBT)を用いて身元を証明する。
  • 特定の相手の署名は必須にしつつ、それ以外のガーディアンは複数人の署名で代替可能にする。
  • 新しい認証方式が登場しても、アカウント自体のスマートコントラクトを変更せずに対応できる。

このように ERC7093は、従来のソーシャルリカバリー構造の限界を超え、より柔軟で拡張性の高いリカバリー機構を提供することを目的にしています。

仕様

4つの概念

ERC7093は、ソーシャルリカバリーを実現するために必要となる4つの主要コンポーネントを定義しています。 それぞれの役割は以下のとおりです。

コンポーネント名 説明
Identity ガーディアンの身元を表現する仕組み。アカウント型だけでなく、署名や証明をオンチェーンで検証可能なあらゆる形式を含みます。
PermissionVerifier ガーディアンが提出した署名や証明を検証する仕組み。オンチェーン・オフチェーンどちらのガーディアンにも対応します。
RecoveryPolicyVerifier リカバリー条件(ポリシー)の検証を担う仕組み。署名数のしきい値やロック期間など柔軟に設定できます。
RecoveryAccount / RecoveryModule 実際にアカウントのリカバリー処理を実行する中心的なコントラクト。構成に応じてモジュール化も可能です。

Identityに対応する対象は幅広く、以下の例が含まれます。

  • EOA / SCA(通常のブロックチェーンアカウント)
  • WebAuthn / Passkey
  • メールドメイン署名(DKIM)
  • OpenID トークン
  • ゼロ知識証明(ZKP)
  • NFT / SBT(譲渡不可トークン)
  • 将来的な方式も拡張可能

この設計により、ソーシャルリカバリーを将来にわたって柔軟に拡張できるようになります。

DataTypes

構造体

Identity
javascript
struct Identity {
    address guardianVerifier;
    bytes signer;
}

ガーディアンの身元情報を表す構造体。 Identity はガーディアンがどのように認証されるかを定義します。 signer が空の場合はEOAまたはSCAとして扱われ、署名検証には安全なECDSAまたは ERC1271 の検証手段を利用します。 signer が空でない場合は、アカウント以外の識別方式となり、guardianVerifier に指定されたコントラクトへ IPermissionVerifier を通じて検証を委任します。

パラメータ

  • guardianVerifier
    • 署名や証明を検証するコントラクトのアドレス。
  • signer
    • ガーディアンの識別情報。空の場合はEOA/SCAを意味します。
GuardianInfo
javascript
struct GuardianInfo {
    Identity guardian;
    uint64 property;
}

ガーディアンの属性情報を表す構造体。 ガーディアンが持つ属性を管理します。 属性は RecoveryPolicyVerifier が定義し、重み値や割合、ロールに応じた重みづけなど柔軟に利用できます。

パラメータ

  • guardian
    • ガーディアンの身元を表す Identity。
  • property
    • ガーディアンが持つ属性値。
    • 例として重みや割合などを設定できます。
ThresholdConfig
javascript
struct ThresholdConfig {
    uint64 threshold;
    int48 lockPeriod;
}

しきい値設定を表す構造体。 リカバリー要求に必要な条件を定義します。 threshold は必要となる属性値の合計、lockPeriod はリカバリー実行までの待機時間を意味します。

パラメータ

  • threshold
    • リカバリーに必要な合計値。
  • lockPeriod
    • リカバリー実行までのロック期間。
RecoveryConfigArg
javascript
struct RecoveryConfigArg {
    address policyVerifier;
    GuardianInfo[] guardianInfos;
    ThresholdConfig[] thresholdConfigs;
}

リカバリー設定を表す構造体。 複数のリカバリー設定を持つことができ、ガーディアン一覧と複数のしきい値設定を組み合わせて構成します。 policyVerifier は任意で、不要な場合は省略できます。

パラメータ

  • policyVerifier
    • ポリシー検証を行うコントラクト。
  • guardianInfos
    • ガーディアン情報の配列。
  • thresholdConfigs
    • しきい値設定の配列。
Permission
javascript
struct Permission {
    Identity guardian;
    bytes signature;
}

ガーディアンの署名情報を表す構造体。 リカバリー要求に参加するガーディアンが生成した署名を保持します。 startRecovery で検証対象となり、すべての署名が有効である必要があります。

パラメータ

  • guardian
    • 署名者となるガーディアン。
  • signature
    • ガーディアンの署名データ。

IPermissionVerifier

非アカウント型ガーディアンの署名や証明を検証するためのインターフェイス。

Identity の signer が空でない場合に利用されます。 署名の形式確認や複数署名の検証が可能であり、ガーディン方式に依存しない汎用的な拡張性を持ちます。

javascript
/**
 * @dev Interface for no-account type identity signature/proof verification
 */
interface IPermissionVerifier {
    /**
     * @dev Check if the signer key format is correct
     */
    function isValidSigners(bytes[] signers) external returns (bool);

    /**
     * @dev Validate permission
     */
    function isValidPermission(
        bytes32 hash,
        bytes signer,
        bytes signature
    ) external returns (bool);

    /**
     * @dev Validate permissions
     */
    function isValidPermissions(
        bytes32 hash,
        bytes[] signers,
        bytes[] signatures
    ) external returns (bool);

    /**
     * @dev Return supported signer key information, format, signature format, hash algorithm, etc.
     * MAY TODO:using ERC-3668: ccip-read
     */
    function getGuardianVerifierInfo() public view returns (bytes memory);
}

IRecoveryPolicyVerifier

リカバリーポリシーを検証するインターフェイス。 しきい値や属性の確認を行い、条件に従ってリカバリーの成否と累積値を返します。 ガーディアンが一覧に存在するか、重複がないかなどもチェック対象となります。

javascript
/**
 * @dev Interface for recovery policy verification
 */
interface IRecoveryPolicyVerifier {
    /**
     * @dev Verify recovery policy and return verification success and lock period
     * Verification includes checking if guardians exist in the Guardians List
     */
    function verifyRecoveryPolicy( Permission[] memory permissions, uint64[] memory properties)
        external
        view
        returns (bool succ, uint64 weight);

    /**
     * @dev Returns supported policy settings and accompanying property definitions for Guardian.
     */
    function getPolicyVerifierInfo() public view returns (bytes memory);
}

IRecoveryAccount

javascript
interface IRecoveryAccount {
    modifier onlySelf() {
        require(msg.sender == address(this), "onlySelf: NOT_AUTHORIZED");
        _;
    }

    modifier InRecovering(address policyVerifyAddress) {
        (bool isRecovering, ) = getRecoveryStatus(policyVerifierAddress);
        require(isRecovering, "InRecovering: no ongoing recovery");
        _;
    }

    /**
     * @dev Events for updating guardians, starting for recovery, executing recovery, and canceling recovery
     */
    event RecoveryStarted(bytes newOwners, uint256 nonce, uint48 expiryTime);
    event RecoveryExecuted(bytes newOwners, uint256 nonce);
    event RecoveryCanceled(uint256 nonce);

    /**
     * @dev Return the domain separator name and version for signatures
     * Also return the domainSeparator for EIP-712 signature
     */

    /// @notice             Domain separator name for signatures
    function DOMAIN_SEPARATOR_NAME() external view returns (string memory);

    /// @notice             Domain separator version for signatures
    function DOMAIN_SEPARATOR_VERSION() external view returns (string memory);

    /// @notice             returns the domainSeparator for EIP-712 signature
    /// @return             the bytes32 domainSeparator for EIP-712 signature
    function domainSeparatorV4() external view returns (bytes32);

    /**
     * @dev Update /replace guardians and recovery policies
     * Multiple recovery policies can be set using an array of RecoveryConfigArg
     */
    function updateGuardians(RecoveryConfigArg[] recoveryConfigArgs) external onlySelf;

    // Generate EIP-712 message hash,
    // Iterate over signatures for verification,
    // Verify recovery policy,
    // Store temporary state or recover immediately based on the result returned by verifyRecoveryPolicy.
    function startRecovery(
        uint256 configIndex,
        bytes newOwner,
        Permission[] permissions
    ) external;

    /**
     * @dev Execute recovery
     * temporary state -> ownerKey rotation
     */
    function executeRecovery(uint256 configIndex) external;

    function cancelRecovery(uint256 configIndex) external onlySelf InRecovering(policyVerifier);

    function cancelRecoveryByGuardians(uint256 configIndex, Permission[] permissions)
        external
        InRecovering(policyVerifier);

    /**
     * @dev Get wallet recovery config, check if an identity is a guardian, get the nonce of social recovery, and get the recovery status of the wallet
     */
    function isGuardian(uint256 configIndex, identity guardian) public view returns (bool);

    function getRecoveryConfigs() public view returns (RecoveryConfigArg[] recoveryConfigArgs);

    function getRecoveryNonce() public view returns (uint256 nonce);

    function getRecoveryStatus(address policyVerifier) public view returns (bool isRecovering, uint48 expiryTime);
}

修飾子

onlySelf
javascript
modifier onlySelf() {
    require(msg.sender == address(this), "onlySelf: NOT_AUTHORIZED");
    _;
}

コントラクト自身のみが実行できる制御を行うmodifier。 外部からの呼び出しを防ぎ、意図しない更新処理を防止します。

InRecovering
javascript
modifier InRecovering(address policyVerifyAddress) {
    (bool isRecovering, ) = getRecoveryStatus(policyVerifierAddress);
    require(isRecovering, "InRecovering: no ongoing recovery");
    _;
}

リカバリー処理中のみ実行可能にする制御を行うmodifier。 現在リカバリー中かどうかを確認し、状態が一致しない場合に実行を拒否します。

イベント

RecoveryStarted
javascript
event RecoveryStarted(bytes newOwners, uint256 nonce, uint48 expiryTime);

リカバリー処理が開始された時に発行されるイベント。 新しいオーナー候補と期限、nonce を通知します。

パラメータ

  • newOwners
    • 新しいオーナー候補。
  • nonce
    • リカバリー処理の識別番号。
  • expiryTime
    • 実行可能になる時間。
RecoveryExecuted
javascript
event RecoveryExecuted(bytes newOwners, uint256 nonce);

リカバリー処理が完了した時に発行されるイベント。 リカバリー実行後、新しいオーナーが確定したことを示します。

パラメータ

  • newOwners
    • 更新後のオーナー。
  • nonce
    • 対象となるリカバリー処理の識別番号。
RecoveryCanceled
javascript
event RecoveryCanceled(uint256 nonce);

リカバリー処理が取り消された時に発行されるイベント。 ユーザーまたはガーディアンによってキャンセルされたことを示します。

パラメータ

  • nonce
    • 対象となるリカバリー処理の識別番号。

関数

DOMAIN_SEPARATOR_NAME
javascript
function DOMAIN_SEPARATOR_NAME() external view returns (string memory);

署名検証に使用するドメイン名を取得する関数。 EIP712に基づき、署名対象の識別に利用されます。

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

戻り値

  • string
    • ドメイン名。
DOMAIN_SEPARATOR_VERSION
javascript
function DOMAIN_SEPARATOR_VERSION() external view returns (string memory);

署名検証に使用するドメインのバージョンを取得する関数。 署名整合性を保つために利用されます。

戻り値

  • string
    • バージョン情報。
domainSeparatorV4
javascript
function domainSeparatorV4() external view returns (bytes32);

EIP712のドメインセパレータを取得する関数。 署名計算に必要な識別値を返します。

戻り値

  • bytes32
    • ドメインセパレータ。
updateGuardians
javascript
function updateGuardians(RecoveryConfigArg[] recoveryConfigArgs) external onlySelf;

ガーディアンおよびリカバリー設定を更新する関数。 複数のリカバリー設定を同時に登録できます。

引数

  • recoveryConfigArgs
    • 設定内容の配列。
startRecovery
javascript
function startRecovery(
    uint256 configIndex,
    bytes newOwner,
    Permission[] permissions
) external;

リカバリー処理を開始する関数。 ガーディアン署名の検証やポリシー確認を行い、条件が満たされた場合に一時状態を保存します。

引数

  • configIndex
    • 対象となる設定番号。
  • newOwner
    • 新しいオーナー候補。
  • permissions
    • ガーディアンの署名情報。
executeRecovery
javascript
function executeRecovery(uint256 configIndex) external;

リカバリー処理を確定する関数。 ロック期間が終了した後に実行され、オーナーが正式に置き換えられます。

引数

  • configIndex
    • 対象となる設定番号。
cancelRecovery
javascript
function cancelRecovery(uint256 configIndex) external onlySelf InRecovering(policyVerifier);

リカバリー処理を取り消す関数。 コントラクト自身からのみ実行でき、リカバリー中である必要があります。

引数

  • configIndex
    • 対象設定番号。
cancelRecoveryByGuardians
javascript
function cancelRecoveryByGuardians(uint256 configIndex, Permission[] permissions)
    external
    InRecovering(policyVerifier);

ガーディアンがリカバリー処理を取り消す関数。 ガーディアン署名に基づきキャンセルを実行します。

引数

  • configIndex
    • 対象設定番号。
  • permissions
    • ガーディアン署名情報。
isGuardian
javascript
function isGuardian(uint256 configIndex, identity guardian) public view returns (bool);

指定された身元がガーディアンであるかを確認する関数。 対象設定に含まれているかどうかを返します。

引数

  • configIndex
    • 対象設定番号。
  • guardian
    • 確認対象の身元。

戻り値

  • bool
    • ガーディアンであるかどうか。
getRecoveryConfigs
javascript
function getRecoveryConfigs() public view returns (RecoveryConfigArg[] recoveryConfigArgs);

すべてのリカバリー設定を取得する関数。 登録済みの設定内容を一覧で返します。

戻り値

  • recoveryConfigArgs
    • すべての設定情報。
getRecoveryNonce
javascript
function getRecoveryNonce() public view returns (uint256 nonce);

リカバリー処理の nonce を取得する関数。 資産操作の nonce と分離されて管理されます。

戻り値

  • nonce
    • リカバリー処理の識別番号。
getRecoveryStatus
javascript
function getRecoveryStatus(address policyVerifier) public view returns (bool isRecovering, uint48 expiryTime);

リカバリー状態を取得する関数。 現在の状態と実行可能時間を返します。

引数

  • policyVerifier
    • 対象となるポリシーのアドレス。

戻り値

  • isRecovering
    • リカバリー中かどうか。
  • expiryTime
    • 実行可能となる時間。

Recovery Account Workflow

以下は実際の流れを示す例です。

  1. ユーザーがガーディアンとしきい値設定を登録します。
  2. ガーディアンが EIP-712 署名を確認し生成します。
  3. bundler または relayer が startRecovery を実行します。
  4. 署名検証とポリシー検証が行われます。
  5. 条件が満たされれば一時状態が保存されます。
  6. ロック期間後に executeRecovery が実行され、オーナーが更新されます。

補足

多様なガーディアンと柔軟なリカバリーポリシーを実現する理由

ERC7093は、より幅広い種類のガーディアンを扱えるようにし、さらにリカバリーポリシーを柔軟に設定できるようにすることを目的としています。 そのため、リカバリー処理と検証処理を分離する設計を採用しています。 これにより、アカウントの基本的なロジックを変更することなく、新しい仕組みを追加したり拡張したりできます。

一般的なスマートコントラクトアカウントでは、新しい認証方式を導入する場合、コントラクトをアップグレードする必要がありました。 しかし、検証ロジックを外部に切り出すことで、アカウントの本体を触らずに新しい種類のガーディアンや仕組みを扱えるようになります。

外部コントラクトによるVerifierを採用する理由

検証処理を外部コントラクトとして分離する理由は、RecoveryAccount のリカバリーロジックをシンプルに保つためです。 Verifierの呼び出し形式は統一されているため、外部コントラクトを利用してもセキュリティ上のリスクを管理できます。 検証内容を変更したい場合でも、RecoveryAccountを変更する必要がなく、ガーディアン方式の拡張性も確保できます。

この構造により、検証担当のコントラクトは明確な役割を持ち、不要な複雑化を避けながら安全性を維持できます。

recoveryConfigs を安全に管理する必要性

recoveryConfigs は RecoveryAccount にとって重要な設定情報です。 ガーディアンの一覧やしきい値など、リカバリーに関わる内容が含まれているため、適切に保護されて保存されている必要があります。 アクセス権限や更新権限は厳密に制御し、他の領域と分離して扱うことが求められます。

さらに、設定数や保存方法に制限を設けていないため、実装者は用途に応じた柔軟な構成が可能です。 これにより、複数のリカバリーパターンを並行して扱えるアカウント設計にも対応できます。

recoveryNonce を導入する理由

recoveryNonce は、ガーディアンの署名が不正に再利用される攻撃(リプレイ攻撃)を防ぐために導入されています。 リカバリーごとに異なる値を使用することで、過去の署名を使って再度リカバリーを実行されるリスクを減らします。

これにより、すべてのリカバリープロセスに一意性が保証され、ガーディアンの署名が安全に扱われます。 ガーディアンが正しく承認したとしても、その結果が別の場面で悪用されることを防ぐ役割があります。

引用

John Zhang (@johnz1019), Davis Xiang (@xcshuan), Kyle Xu (@kylexyxu), George Zhang (@odysseus0), "ERC-7093: Social Recovery Interface [DRAFT]," Ethereum Improvement Proposals, no. 7093, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7093.

最後に

今回は「リカバリーポリシーを自由にカスタマイズできるソーシャルリカバリーの仕組みを提案しているERC7093」についてまとめてきました! いかがだったでしょうか?

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

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