はじめに

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

今回は、人間とロボットがブロックチェーン上で平等にアイデンティティを持ち、共通のルールに基づいて協力し合うための仕組みを提案しているERC7777についてまとめていきます!

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

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

概要

ERC7777では、「IUniversalIdentity」と「IUniversalCharter」という2つの主要なインターフェースを定義しています。 これらは、人間とロボットがそれぞれ自分のアイデンティティを確立し、特定のルールに基づく分散型コミュニティを形成するための仕組みを提供します。

IUniversalIdentityについて

IUniversalIdentityインターフェースは、人間以外の「知的な計算構造」(例えばロボット)に対して、公平で平等な扱いを実現することを目的としています。 この仕組みにより、ロボットはブロックチェーン上に自身の「オンチェーンアイデンティティ(ブロックチェーン上の身分)」を持つことが可能になり、人間と同じようにやり取りや取引を行うことができます。

また、このインターフェースには「ハードウェアによる本人確認」の仕組みも含まれています。 具体的には、ロボットが持つセキュアハードウェア(安全なチップ)を利用して、暗号署名を生成し、それを「チャレンジ・レスポンス認証方式」で検証します。 これは、他の人間やロボットがロボット本人であることを暗号的に確認できる仕組みです。

IUniversalCharterについて

IUniversalCharterインターフェースは、人間とロボットが「事前に定義されたルールセット」に基づく社会(コミュニティ)を作成・参加・維持・離脱・終了するためのフレームワークです。 この仕組みにより、参加者(人間とロボット)は自律的に管理される社会を形成し、協力や共栄を目指すことができます。

これらのインターフェースは、人間とロボットの間における分散型システム上のやり取りを効率的かつ透明性のあるものにし、安全な共存を可能にすることを目的としています。

動機

背景

人間の脳は、高度に並列処理を行う生体コンピュータといえます。 化学反応と電気信号が複雑に組み合わさって情報を処理しており、その仕組みは従来の電子的なコンピュータとはまったく異なります。 一方、ロボットやAIなどの非人間的な知的コンピュータは、電子的なハードウェアとソフトウェアで構成されています。 この違いにより、近い将来、人間社会は「自我を持つ非人間コンピュータ(=ロボットなど)」と共存し、協働するための新しい仕組みが必要になると考えられています。

現在の課題

今の人間社会では、国籍や出生地によってルールが決まる仕組みが主流ですが、ロボットには出生地や生まれた時間という概念がありません。 そのため、以下のような問題が生じています。

課題 説明
身分証明の欠如 ロボットはパスポートなどの公式なIDを持てない。
適用ルールの不明確さ どの法律やルールがロボットに適用されるかが不明。
金融システムへの参加困難 現在の銀行システムは人間中心であり、ロボットは利用できない。
法的保護の欠如 ロボットが損害を受けた場合、どの裁判所に訴えればよいかが定義されていない。

こうした問題は、地理的に制約された旧来のシステムでは解決が難しいという現状があります。

ブロックチェーンがもたらす解決策

ブロックチェーンは、地理的な制約を持たず、全ての計算主体(人間でもロボットでも)を平等に扱う「非中央集権型の公共コンピュータ」です。 この性質により、以下のような利点があります。

  • 人間とロボットを区別しない公平な環境を提供できる
  • 改ざんが不可能で透明性が高い
  • 世界中で一貫したルールを維持できる

特に、スマートコントラクトを用いることで、参加者間の権利や義務を明確かつ自動的に管理することができます。

提案の目的

ERC7777の目的は、「人間とロボットが共存できるグローバルな社会のための標準インターフェース」を作ることです。

これにより、無数に存在する社会ルールの設計や実装を単純化し、誰でも共通の方法で社会を作成・参加・終了できるようになります。

主な目的は2つあります

ロボットのアイデンティティの作成と管理

ロボットがオンチェーン(ブロックチェーン上)で活動するためには、信頼できる「身分」を持つ必要があります。 この仕組みでは、各ロボットが「スマートコントラクト」で表現されます。 ロボットは製造者・操作者・モデル番号・シリアル番号・公開鍵といった情報を登録し、ハードウェアに組み込まれた安全な要素(セキュアエレメント)によって生成された署名を用いて自身の正当性を証明します。

これにより、以下が実現します。

  • 改ざん不可能で物理的にも検証可能な身分証明
  • どのロボットがどの行動を行ったかを明確に追跡可能
  • 人間との間で責任を共有できる透明な関係の構築
ルールの作成と実施

ロボットと人間が協力するためには、「共通のルールセット」が必要です。 この提案では、Ethereum上で動作する「分散型のガバナンスシステム(統治システム)」を構築します。

具体的には、以下のような流れで社会を形成します。

  1. ルールを定義したスマートコントラクトを作成する
  2. 人間とロボットがそのコントラクトに参加(登録)する
  3. コントラクトがルールの遵守状況を自動で監視・確認する

さらに、複数のルールセットを持つ社会を同時に作ることもできるため、目的に応じた柔軟な社会設計が可能です。

仕様

IUniversalIdentity

js
interface IUniversalIdentity {

    /// @notice Structure for hardware identity
    struct HardwareIdentity {
        bytes32 publicKey;            // Hardware-bound public key uniquely tied to this robot
        string manufacturer;           // Identifier for the robot's manufacturer
        string operator;               // Identifier for the robot's operator
        string model;                  // Model identifier of the robot
        string serialNumber;           // Unique serial number for the robot
        bytes32 initialHashSignature;  // Signature of the initial system state hash (e.g., firmware, OS) by the root key
        bytes32 currentHashSignature;  // Signature of the latest state hash, updated periodically for integrity verification by the root key
    }

    /// @notice Gets the hardware identity info
    /// @return HardwareIdentity Current hardware identity
    function getHardwareIdentity() external view returns (HardwareIdentity memory);

    /// @notice Generates a new challenge for hardware verification
    /// @return bytes32 Random challenge that needs to be signed
    function generateChallenge() external returns (bytes32);
  
    /// @notice Verify response to a specific challenge
    /// @param challenge The challenge that was issued
    /// @param signature Hardware signature of the challenge
    /// @return bool True if signature is valid for this challenge
    function verifyChallenge(bytes32 challenge, bytes memory signature) external returns (bool);

    /// @notice Adds a rule to the robot's identity, showing that the robot agrees to follow the rule.
    /// @param rule The dynamic byte array representing the rule that the robot agrees to follow. Each rule is a textual string encoded into a dynamic byte array. For example: 'A robot must keep a 1m distance from humans.'
    /// @dev The rule SHOULD come from the rule sets defined in the IUniversalCharter contract that the robot intends to join. 
    /// @dev This function SHOULD be implemented by contracts to add the rules that the robot intends to follow.
    function addRule(bytes memory rule) external;

    /// @notice Removes a rule from the robot's identity.
    /// @param rule The dynamic byte array representing the rule that the robot no longer agrees to follow.
    /// @dev This function SHOULD be implemented by contracts to remove the rules that the robot does not intend to follow.
    function removeRule(bytes memory rule) external;

    /// @notice Checks if the robot complies with a specific rule.
    /// @param rule The rule to check.
    /// @return bool Returns true if the robot complies with the rule.
    /// @dev This function MUST be implemented by contracts for compliance verification.
    function checkCompliance(bytes memory rule) external view returns (bool);

    /// @dev Emitted when a rule is added to the robot's identity.
    event RuleAdded(bytes rule);

    /// @dev Emitted when a rule is removed from the robot's identity.
    event RuleRemoved(bytes rule);
  
    /// @dev Emitted when a charter is subscribed to.
    event SubscribedToCharter(address indexed charter);

    /// @dev Emitted when it is unsubscribed from a charter.
    event UnsubscribedFromCharter(address indexed charter);
}

IUniversalIdentity は、ロボットのアイデンティティをブロックチェーン上で表現し、本人確認やルール遵守の検証を可能にするインターフェースです。 この仕組みにより、物理的なロボットが暗号的に自身を証明し、定められたルールを遵守する存在として扱われます。

HardwareIdentity

javascript
struct HardwareIdentity {
    bytes32 publicKey;
    string manufacturer;
    string operator;
    string model;
    string serialNumber;
    bytes32 initialHashSignature;
    bytes32 currentHashSignature;
}

ロボットのハードウェア固有情報を保持する構造体です。 この構造体は、ロボットが物理的にも暗号的にも一意であることを保証します。 ロボットは安全なハードウェア(セキュアエレメント)を通じて生成された鍵と署名を使い、改ざん不可能な形で自分の身元を示します。

パラメータ

  • publicKey
    • ロボットに紐づいたハードウェア固有の公開鍵。
  • manufacturer
    • ロボットの製造者を示す識別子。
  • operator
    • ロボットを操作・管理する主体の識別子。
  • model
    • ロボットのモデル名。
  • serialNumber
    • 一意のシリアル番号。
  • initialHashSignature
    • ファームウェアやOSなどの初期状態のハッシュ値に対してルート鍵で署名したもの。
  • currentHashSignature
    • 最新の状態ハッシュを定期的に署名し、整合性を検証するための署名。

getHardwareIdentity

javascript
function getHardwareIdentity() external view returns (HardwareIdentity memory);

ロボットのハードウェア情報を取得する関数。 ロボットの登録情報(製造者、モデル、署名など)を外部から参照できるようにします。 これにより、他のコントラクトやユーザーがそのロボットの正当性を確認できます。

戻り値

  • HardwareIdentity
    • 現在登録されているロボットのハードウェア情報。

generateChallenge

javascript
function generateChallenge() external returns (bytes32);

認証用のチャレンジ値を生成する関数。 ロボットの本人確認を行う際、ランダムなチャレンジ(質問)を生成し、ロボットがそのチャレンジに署名して返すことで、本物であることを証明します。 この方式を「チャレンジ・レスポンス認証」と呼びます。

戻り値

  • bytes32
    • ロボットが署名すべきチャレンジ値。

verifyChallenge

javascript
function verifyChallenge(bytes32 challenge, bytes memory signature) external returns (bool);

ロボットが返した署名が正しいかを検証する関数。 発行されたチャレンジに対して、ロボットがハードウェアで署名した応答を確認します。 署名がロボットの公開鍵に対応していれば、本物であると認証されます。

引数

  • challenge
    • 発行されたチャレンジ値。
  • signature
    • ロボットが返した署名データ。

戻り値

  • bool
    • 署名が正しければ true を返します。

addRule

javascript
function addRule(bytes memory rule) external;

ロボットが新しいルールを受け入れるための関数。 ロボットは IUniversalCharter コントラクトで定義されたルールを追加することで、その社会の一員としての責務を明確にします。 ルールは可変長バイト配列として渡され、文字列形式で記述されます。

引数

  • rule
    • 例:「ロボットは人間から1m以上離れること。」

removeRule

javascript
function removeRule(bytes memory rule) external;

ロボットが従うルールを削除する関数。 不要になったルールや、別の社会に参加するためにルールを入れ替える場合に使用します。 ロボットの柔軟な自己管理を可能にします。

引数

  • rule
    • 削除するルールの文字列をエンコードしたバイト配列。

checkCompliance

javascript
function checkCompliance(bytes memory rule) external view returns (bool);

ロボットが特定のルールを守っているか確認する関数。 ロボットが追加したルールに従っているかどうかを検証します。 この関数は他のコントラクトからも呼び出され、ルール遵守の確認に使われます。

引数

  • rule
    • 確認するルール。

戻り値

  • bool
    • ルールを守っていれば true を返します。

RuleAdded

javascript
event RuleAdded(bytes rule);

ルールが追加された時に発行されるイベント。 ロボットが新しいルールを採用したことをブロックチェーン上に記録します。 これにより外部からの追跡と監査が可能になります。

パラメータ

  • rule
    • 追加されたルール。

RuleRemoved

javascript
event RuleRemoved(bytes rule);

ルールが削除された時に発行されるイベント。 ロボットがルールを放棄したことを記録します。 履歴を残すことで透明性を保ちます。

パラメータ

  • rule
    • 削除されたルール。

SubscribedToCharter

javascript
event SubscribedToCharter(address indexed charter);

ロボットが新しいチャーターに参加した時に発行されるイベント。 どのチャーター(社会)に参加したかを追跡できるようにします。

パラメータ

  • charter
    • 参加したチャーターのアドレス。

UnsubscribedFromCharter

javascript
event UnsubscribedFromCharter(address indexed charter);

ロボットがチャーターから脱退した時に発行されるイベント。 脱退をブロックチェーン上に記録し、参加履歴を透明に管理します。

パラメータ

  • charter
    • 脱退したチャーターのアドレス。

IUniversalCharter

js
interface IUniversalCharter {

    // Define the user types as an enum.
    enum UserType { Human, Robot }

    /// @notice Registers a user (human or robot) to join the system by agreeing to a rule set.
    /// @param userType The type of user.
    /// @param ruleSet The array of individual rules the user agrees to follow. 
    /// @dev This function MUST be implemented by contracts using this interface.
    /// @dev The implementing contract MUST ensure that the user complies with the specified rule set before registering them in the system by invoking `checkCompliance`.
    function registerUser(UserType userType, bytes[] memory ruleSet) external;

    /// @notice Allows a user (human or robot) to leave the system 
    /// @dev This function MUST be callable only by the user themselves (via `msg.sender`). 
    /// @dev The implementing contract MUST ensure that the user has complied with all necessary rules before they can successfully leave the system by invoking `checkCompliance`.
    function leaveSystem() external;

    /// @notice Checks if the user (human or robot) complies with the system’s rules.
    /// @param user Address of the user (human or robot).
    /// @param ruleSet The array of individual rules to verify. 
    /// @return bool Returns true if the user complies with the rule set.
    /// @dev This function SHOULD invoke the `checkCompliance` function of the user’s IUniversalIdentity contract to check for rules individually. 
    /// @dev This function MUST be implemented by contracts for compliance verification.
    function checkCompliance(address user, bytes[] memory ruleSet) external view returns (bool);

    /// @notice Updates the rule set.
    /// @param newRuleSet The array of new individual rules replacing the existing ones. 
    /// @dev This function SHOULD be restricted to authorized users (e.g., contract owner).
    function updateRuleSet(bytes[] memory newRuleSet) external;

    /// @notice Terminates the contract, preventing any further registrations or interactions.
    /// @dev This function SHOULD be restricted to authorized users (e.g., contract owner).
    /// @dev This function SHOULD be implemented by contracts.
    function terminateContract() external;

    /// @dev Emitted when a user joins the system by agreeing to a set of rules.
    event UserRegistered(address indexed user, UserType userType, bytes[] ruleSet);

    /// @dev Emitted when a user successfully leaves the system after fulfilling obligations.
    event UserLeft(address indexed user);

    /// @dev Emitted when a user’s compliance with a rule set is verified.
    event ComplianceChecked(address indexed user, bytes[] ruleSet);

    /// @dev Emitted when a rule set is updated.
    event RuleSetUpdated(bytes[] newRuleSet, address updatedBy);
}

IUniversalCharter は、人間とロボットが参加する「社会(コミュニティ)」を管理するためのインターフェースです。 ここでは、参加・脱退・ルールの更新やコンプライアンスチェック(ルール遵守確認)を定義します。

UserType

javascript
enum UserType { Human, Robot }

ユーザーの種類を識別するための列挙型。 人間とロボットを区別することで、それぞれに適切なルールや処理を適用できるようにします。 文字列を使うよりも効率的でエラーが起きにくい方法です。

registerUser

javascript
function registerUser(UserType userType, bytes[] memory ruleSet) external;

人間またはロボットをシステムに登録する関数。 ユーザーがチャーターに参加する時に、そのルールセットに同意する必要があります。 登録前に checkCompliance を呼び出し、ルールを守ることが確認されます。

引数

  • userType
    • 登録するユーザーのタイプ(Human または Robot)。
  • ruleSet
    • 同意するルールの集合。

leaveSystem

javascript
function leaveSystem() external;

ユーザーがシステムから脱退する関数。 ユーザー自身が呼び出すことができ、すべての義務(ルール)を果たしたか確認されたうえで脱退が承認されます。

checkCompliance

javascript
function checkCompliance(address user, bytes[] memory ruleSet) external view returns (bool);

ユーザーがルールを守っているか確認する関数。 IUniversalIdentity コントラクトの checkCompliance 関数を呼び出し、個別にルールを確認します。 この関数は社会全体の秩序と信頼性を維持するために重要です。

引数

  • user
    • 対象となるユーザーのアドレス。
  • ruleSet
    • 検証するルールの配列。

戻り値

  • bool
    • すべてのルールを遵守していれば true。

updateRuleSet

javascript
function updateRuleSet(bytes[] memory newRuleSet) external;

ルールセットを更新する関数。 既存のルールを新しいルールで置き換えることができます。 この関数はコントラクト管理者のみが実行できるよう制限されます。

引数

  • newRuleSet
    • 新しいルールの配列。

terminateContract

javascript
function terminateContract() external;

チャーターを終了する関数。 すべての活動を停止し、新しい登録や操作を不可能にします。 これによりシステムを安全にシャットダウンできます。

イベント一覧

イベント名 概要
UserRegistered 新しいユーザーが登録された時に発行。
UserLeft ユーザーが脱退した時に発行。
ComplianceChecked ルール遵守が確認された時に発行。
RuleSetUpdated ルールセットが更新された時に発行。

これらのイベントにより、システム内の主要な動きを外部から監視・監査できるようになります。

参考実装

UniversalIdentity

js
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.19;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { UniversalCharter } from "./UniversalCharter.sol";

// Interfaces
import { IUniversalIdentity } from "./interface/IUniversalIdentity.sol";
import { IUniversalCharter } from "./interface/IUniversalCharter.sol";

/// @title UniversalIdentity
/// @notice The UniversalIdentity contract is used to manage the identity of robots.

contract UniversalIdentity is IUniversalIdentity, OwnableUpgradeable {
    /// @notice Version identifier for the current implementation of the contract.
    string public constant VERSION = "v0.0.1";

    /// @notice Mapping to store rules that the robot has agreed to follow
    mapping(bytes => bool) private robotRules;

    /// @notice Mapping to store the off-chain compliance status for each rule
    mapping(bytes => bool) private complianceStatus;

    /// @notice Track the charters the robot is subscribed to
    mapping(address => bool) private subscribedCharters;

    /// @notice Custom errors to save gas on reverts
    error RuleNotAgreed(bytes rule);
    error RuleNotCompliant(bytes rule);
    error RuleAlreadyAdded(bytes rule);

    /// @dev Event to emit when compliance is checked
    /// @param updater The address of the compliance updater (owner of the contract)
    /// @param rule The rule that was checked
    event ComplianceChecked(address indexed updater, bytes rule);

    /// @notice Modifier to check if a rule exists
    modifier ruleExists(bytes memory rule) {
        require(robotRules[rule], "Rule does not exist");
        _;
    }

    /// @notice Constructor to set the owner
    constructor() {
        initialize({ _owner: address(0xdEaD) });
    }

    /// @dev Initializer function
    function initialize(address _owner) public initializer {
        __Ownable_init();
        transferOwnership(_owner);
        hardwareIdentity = _hardwareIdentity;
    }

    /// @notice Gets the hardware identity info
    function getHardwareIdentity() external view returns (HardwareIdentity memory) {
        return hardwareIdentity;
    }

    /// @notice Generates a new challenge for hardware verification
    function generateChallenge() external returns (bytes32) {
        bytes32 challenge = keccak256(abi.encodePacked(
            block.timestamp,
            block.prevrandao,
            msg.sender
        ));
        activeChallenge[challenge] = true;
        return challenge;
    }

    /// @notice Verify response to a specific challenge
    function verifyChallenge(
        bytes32 challenge,
        bytes memory signature
    ) external returns (bool) {
        if (!activeChallenge[challenge]) {
            revert InvalidChallenge();
        }

        // Remove challenge after use
        delete activeChallenge[challenge];

        // Verify the signature using ECDSA
        bytes32 messageHash = keccak256(abi.encodePacked(challenge));
        bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(messageHash);
        address signer = ECDSA.recover(ethSignedMessageHash, signature);
  
        // Convert hardware public key to address for comparison
        address hardwareAddress = address(uint160(uint256(hardwareIdentity.publicKey)));
  
        if (signer != hardwareAddress) {
            revert InvalidSignature();
        }

        return true;
    }

    /// @notice Updates the hardware identity information
    /// @param _hardwareIdentity New hardware identity information
    function updateHardwareIdentity(
        HardwareIdentity memory _hardwareIdentity
    ) external onlyOwner {
        hardwareIdentity = _hardwareIdentity;
    }

    /// @notice Adds a rule to the robot's identity
    /// @param rule The dynamic byte array representing the rule that the robot agrees to follow.
    function addRule(bytes memory rule) external override onlyOwner {
        if (robotRules[rule]) {
            revert RuleAlreadyAdded(rule);
        }

        // Add rule to the mapping
        robotRules[rule] = true;

        emit RuleAdded(rule);
    }

    /// @notice Removes a rule from the robot's identity
    /// @param rule The dynamic byte array representing the rule that the robot no longer agrees to follow.
    function removeRule(bytes memory rule) external override onlyOwner ruleExists(rule) {
        robotRules[rule] = false;
        complianceStatus[rule] = false;

        emit RuleRemoved(rule);
    }

    /// @notice Subscribe and register to a specific UniversalCharter contract using its stored rule set
    /// @param charter The address of the UniversalCharter contract
    /// @param version The version of the rule set to fetch and register for
    function subscribeAndRegisterToCharter(address charter, uint256 version) external {
        require(!subscribedCharters[charter], "Already subscribed to this charter");
        subscribedCharters[charter] = true;

        // Fetch the rule set directly from the UniversalCharter contract using the public getter
        bytes[] memory ruleSet = UniversalCharter(charter).getRuleSet(version);

        // Register as a robot in the charter using the fetched rule set
        UniversalCharter(charter).registerUser(IUniversalCharter.UserType.Robot, ruleSet);

        emit SubscribedToCharter(charter);
    }

    /// @notice Leave the system for a specific UniversalCharter contract
    /// @param charter The address of the UniversalCharter contract to leave
    function leaveCharter(address charter) external {
        require(subscribedCharters[charter], "Not subscribed to this charter");

        // Call the leaveSystem function of the UniversalCharter contract
        UniversalCharter(charter).leaveSystem();

        // Unsubscribe from the charter after leaving
        subscribedCharters[charter] = false;
        emit UnsubscribedFromCharter(charter);
    }

    /// @notice Updates compliance status for a rule (called by the owner)
    /// @param rule The dynamic byte array representing the rule
    /// @param status The compliance status (true if compliant, false if not)
    function updateCompliance(bytes memory rule, bool status) external onlyOwner ruleExists(rule) {
        complianceStatus[rule] = status;

        emit ComplianceChecked(msg.sender, rule);
    }

    /// @notice Checks if the robot has agreed to follow a specific rule and if it is compliant
    /// @param rule The rule to check.
    /// @return bool Returns true if the robot has agreed to the rule and is compliant
    function checkCompliance(bytes memory rule) external view override returns (bool) {
        if (!robotRules[rule]) {
            revert RuleNotAgreed(rule);
        }

        return true;
    }

    /// @notice Gets the compliance status of a rule
    /// @param rule The rule to check.
    function getRule(bytes memory rule) external view returns (bool) {
        return robotRules[rule];
    }

    /// @notice Gets the subscription status of a charter
    /// @param charter The address of the charter to check.
    function getSubscribedCharters(address charter) external view returns (bool) {
        return subscribedCharters[charter];
    }

    /// @notice Gets the compliance status of a rule
    /// @param rule The rule to check.
    function getComplianceStatus(bytes memory rule) external view returns (bool) {
        return complianceStatus[rule];
    }
}

UniversalCharter

js
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.20;

import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { SystemConfig } from "./SystemConfig.sol";

// Interfaces
import { IUniversalCharter } from "./interface/IUniversalCharter.sol";
import { IUniversalIdentity } from "./interface/IUniversalIdentity.sol";

/// @title UniversalCharter
/// @notice The UniversalCharter contract is used to manage the registration and compliance of users.

contract UniversalCharter is IUniversalCharter, OwnableUpgradeable {
    /// @notice Struct to store information about a registered user
    struct UserInfo {
        bool isRegistered;
        UserType userType;
        uint256 ruleSetVersion; // Rule set version the user is following
    }

    /// @notice Mapping to store registered users
    mapping(address => UserInfo) private users;

    /// @notice Mapping to store rule sets by version number
    mapping(uint256 => bytes[]) private ruleSets;

    /// @notice Mapping to track the rule set hash and its corresponding version
    mapping(bytes32 => uint256) private ruleSetVersions;

    /// @notice Version identifier for the current implementation of the contract.
    string public constant VERSION = "v0.0.1";

    /// @notice Variable to track the current version of the rule set
    uint256 private currentVersion;

    /// @notice Variable to store the address of the SystemConfig contract
    SystemConfig public systemConfig;

    /// @notice Error for when a method cannot be called when paused. This could be renamed
    /// to `Paused` in the future, but it collides with the `Paused` event.
    error CallPaused();

    /// @notice Reverts when paused.
    modifier whenNotPaused() {
        if (paused()) revert CallPaused();
        _;
    }

    /// @notice Constucts the UniversalCharter contract.
    constructor() {
        initialize({ _owner: address(0xdEaD), _systemConfig: address(0xdEaD) });
    }

    /// @dev Initializer function
    function initialize(address _owner, address _systemConfig) public initializer {
        __Ownable_init();
        transferOwnership(_owner);
        systemConfig = SystemConfig(_systemConfig);
    }

    /// @notice Registers a user (either human or robot) by agreeing to a rule set
    /// @param userType The type of user: Human or Robot
    /// @param ruleSet The array of individual rules the user agrees to follow
    function registerUser(UserType userType, bytes[] memory ruleSet) external override whenNotPaused {
        require(!users[msg.sender].isRegistered, "User already registered");

        // Hash the rule set to find the corresponding version
        bytes32 ruleSetHash = keccak256(abi.encode(ruleSet));
        uint256 version = ruleSetVersions[ruleSetHash];
        require(version > 0, "Invalid or unregistered rule set");

        // For robots, ensure compliance with each rule via the UniversalIdentity contract
        if (userType == UserType.Robot) {
            require(_checkRobotCompliance(msg.sender, version), "Robot not compliant with rule set");
        }

        // Register the user with the versioned rule set
        users[msg.sender] = UserInfo({ isRegistered: true, userType: userType, ruleSetVersion: version });

        emit UserRegistered(msg.sender, userType, ruleSet);
    }

    /// @notice Allows a user (human or robot) to leave the system after passing compliance checks
    function leaveSystem() external override whenNotPaused {
        require(users[msg.sender].isRegistered, "User not registered");

        UserInfo memory userInfo = users[msg.sender];

        // For robots, verify compliance with all rules in the rule set
        uint256 version = userInfo.ruleSetVersion;
        if (userInfo.userType == UserType.Robot) {
            require(_checkRobotCompliance(msg.sender, version), "Robot not compliant with rule set");
        }

        users[msg.sender] = UserInfo({ isRegistered: false, userType: UserType.Human, ruleSetVersion: 0 });

        emit UserLeft(msg.sender);
    }

    /// @notice Internal function to verify robot hardware identity
    /// @param robotAddress The address of the robot to verify
    /// @return bool Returns true if hardware verification succeeds
    function _verifyRobotHardware(address robotAddress) internal returns (bool) {
        IUniversalIdentity robot = IUniversalIdentity(robotAddress);
  
        // Get hardware identity to ensure it exists
        robot.getHardwareIdentity();
  
        // Generate a new challenge
        bytes32 challenge = robot.generateChallenge();
  
        // Store the challenge for future reference
        users[robotAddress].lastVerifiedChallenge = challenge;
  
        // Get signature from the robot (this would typically happen off-chain)
        // For this implementation, we'll assume the signature is provided in a separate tx
        // and just verify the challenge exists
        return challenge != 0;
    }

    /// @notice Checks if a user complies with their registered rule set
    /// @param user The address of the user (human or robot)
    /// @param ruleSet The array of individual rules to verify
    /// @return bool Returns true if the user complies with the given rule set
    function checkCompliance(address user, bytes[] memory ruleSet) external view override returns (bool) {
        require(users[user].isRegistered, "User not registered");

        // Hash the provided rule set to find the corresponding version
        bytes32 ruleSetHash = keccak256(abi.encode(ruleSet));
        uint256 version = ruleSetVersions[ruleSetHash];
        require(version > 0, "Invalid or unregistered rule set");
        require(users[user].ruleSetVersion == version, "Rule set version mismatch");

        // For robots, check compliance with each rule in the UniversalIdentity contract
        if (users[user].userType == UserType.Robot) {
            return _checkRobotCompliance(user, version);
        }

        // If the user is human, compliance is assumed for now (can be extended)
        return true;
    }

    /// @notice Internal function to check compliance for robots with their rule set version
    /// @dev This function will revert if the robot is not compliant with any rule. Returns true for view purposes.
    /// @param robotAddress The address of the robot
    /// @param version The version of the rule set to verify compliance with
    /// @return bool Returns true if the robot is compliant with all the rules in the rule set
    function _checkRobotCompliance(address robotAddress, uint256 version) internal view returns (bool) {
        IUniversalIdentity robot = IUniversalIdentity(robotAddress);
        bytes[] memory rules = ruleSets[version];

        for (uint256 i = 0; i < rules.length; i++) {
            if (!robot.checkCompliance(rules[i])) {
                return false;
            }
        }

        return true;
    }

    /// @notice Updates or defines a new rule set version.
    /// @param newRuleSet The array of new individual rules.
    /// @dev This function SHOULD be restricted to authorized users (e.g., contract owner).
    function updateRuleSet(bytes[] memory newRuleSet) external whenNotPaused onlyOwner {
        require(newRuleSet.length > 0, "Cannot update to an empty rule set");

        // Hash the new rule set and ensure it's not already registered
        bytes32 ruleSetHash = keccak256(abi.encode(newRuleSet));
        require(ruleSetVersions[ruleSetHash] == 0, "Rule set already registered");

        // Increment the version and store the new rule set
        currentVersion += 1;
        ruleSets[currentVersion] = newRuleSet;
        ruleSetVersions[ruleSetHash] = currentVersion;

        emit RuleSetUpdated(newRuleSet, msg.sender);
    }

    /// @notice Getter for the latest version of the rule set.
    function getLatestRuleSetVersion() external view returns (uint256) {
        return currentVersion;
    }

    /// @notice Get the rule set for a specific version.
    /// @param version The version of the rule set to retrieve.
    function getRuleSet(uint256 version) external view returns (bytes[] memory) {
        return ruleSets[version];
    }

    /// @notice Get the version number for a specific rule set.
    /// @param ruleSet The hash of the rule set to retrieve the version for.
    function getRuleSetVersion(bytes32 ruleSet) external view returns (uint256) {
        return ruleSetVersions[ruleSet];
    }

    function getUserInfo(address user) external view returns (UserInfo memory) {
        return users[user];
    }

    /// @notice Getter for the current paused status.
    /// @return paused_ Whether or not the contract is paused.
    function paused() public view returns (bool paused_) {
        paused_ = systemConfig.paused();
    }
}

セキュリティ

コンプライアンスアップデーター

コンプライアンスアップデーターとは、UniversalIdentity コントラクト内でロボットの「ルール遵守状況」を更新する権限を持つ主体を指します。 現在、このロールはオーナー(コントラクトの所有者)に限定されています。

このアップデーターは、ロボットがルールを守っているかどうかの状態を変更できるため、非常に強力な権限を持ちます。 もしこの権限が不正に利用された場合、以下のような問題が起こる可能性があります。

  • 不正なアップデートによって、実際には違反しているロボットを「遵守中」と偽る。
  • コンプライアンス情報を意図的に改ざんし、他のシステムに誤った判断をさせる。

このため、所有者の管理は極めて重要です。 セキュリティの観点から、オーナーのウォレットは安全なハードウェアウォレットを利用することが推奨されます。 また、将来的にはマルチシグ(複数署名)による承認プロセスを導入し、単一の所有者に依存しない安全設計を行うことも望まれます。

ルール管理

ルール管理に関わる関数には、addRule、removeRule、updateCompliance(UniversalIdentity内)、updateRuleSet(UniversalCharter内)があります。 これらの関数は、システム内で実際に「どのルールが有効か」を決定する役割を持つため、誤用や悪意ある操作によってシステム全体の信頼性を損なうリスクがあります。

ルールを自由に変更できると、以下のようなリスクが生じます。

リスク内容 説明
不正なルール追加 権限のないユーザーが意図的に有害なルールを追加し、ロボットや人間の行動を制限する。
ルール削除による抜け道 特定のユーザーやロボットだけが不正にルールを削除し、免責される。
コンプライアンスの改ざん updateCompliance が不正に呼び出され、ルール違反を隠蔽される。

したがって、これらの関数は「認可されたユーザーのみ」が呼び出せるよう、アクセス制御(Access Control)を厳格に設計する必要があります。 具体的には、onlyOwner 修飾子やロールベースのアクセス制御(例:AccessControl)を組み合わせることで、安全な運用が可能となります。

アップグレード可能なコントラクト

OwnableUpgradeable のようなアップグレード対応コントラクトを使用する場合、初期化処理やアップグレード時に新たなリスクが発生します。 特に注意すべきは、initialize 関数の再実行(Reinitialization Attack)です。

アップグレード可能なコントラクトでは、通常のコンストラクタの代わりに initialize 関数が初期設定を行います。 しかし、これが再実行可能な状態のまま放置されると、攻撃者が自分をオーナーに設定したり、コントラクトを再初期化して支配権を奪う可能性があります。

この脆弱性を防ぐためには以下が重要です。

対策項目 説明
再初期化防止 initializer 修飾子を利用して、初期化関数が1回しか実行されないようにする。
オーナー権限の再確認 初期化後に owner が正しく設定されていることを確認する。
アップグレード手順の監査 新バージョンのコントラクトをデプロイする際、再初期化や権限漏れがないかを検証する。

このような基本的な保護がない場合、攻撃者に全権限を奪われ、システムが完全に破壊される危険があります。

ガス消費

ルールセットが過剰に大きくなると、Ethereum上でのトランザクション処理に必要な「ガス(手数料)」が急増する可能性があります。 ブロックチェーンでは、データ量や計算量が多いほどガスコストが高くなります。 その結果、以下のような問題が発生します。

  • 登録・更新の操作に非常に高いコストがかかる。
  • ルールの追加・削除が失敗する。
  • 極端な場合、サービス拒否攻撃(DoS)が発生する。

このような事態を防ぐためには、ルール数やルール内容のサイズに制限を設けることが重要です。 例えば、1つのルールセットに登録できるルール数を100件以下に制限したり、ルール文字列の長さに上限を設定することが考えられます。 また、ルールをハッシュ化して保存することでデータ量を削減し、ガスコストを抑える方法も有効です。

引用

OpenMind, Jan Liphardt jan@openmind.org, Shaohong Zhong (@ShaohongZ), Boyuan Chen (@bchen-dev), Paige Xu paige@openmind.org, James Ball james.ball@nethermind.io, Thamer Dridi thamer.dridi@nethermind.io, "ERC-7777: Governance for Human Robot Societies [DRAFT]," Ethereum Improvement Proposals, no. 7777, September 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7777.

最後に

今回は「人間とロボットがブロックチェーン上で平等にアイデンティティを持ち、共通のルールに基づいて協力し合うための仕組みを提案しているERC7777」についてまとめてきました! いかがだったでしょうか?

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

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