Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7866: 去中心化用户资料

具有每个 DApp 资料分段化身的可互操作的去中心化用户身份

Authors Kumar Anirudha (@anistark)
Created 2025-01-22
Discussion Link https://ethereum-magicians.org/t/add-erc-decentralised-profile-standard/22610
Requires EIP-165

摘要

本 EIP 提出了一个去中心化、可互操作的用户资料标准,称为去中心化资料。资料实现为 Soul Bound Tokens (SBTs),它们是不可变的、不可转让的,并且与多个区块链网络中的唯一标识符绑定。该标准为用户元数据提供了一个统一的结构,包括特定于 dApp 的自定义、默认资料和无缝的跨链兼容性。资料可用于身份管理、声誉系统和个性化的 dApp 体验。

动机

现有的去中心化身份和用户资料解决方案缺乏跨链兼容性、特定于 dApp 的自定义和标准化。统一的方法至关重要,可以:

  1. 促进所有链上的可互操作资料。
  2. 利用 SBT 的不可变性和不可转让性来实现安全身份。
  3. 启用特定于 dApp 的自定义,例如独特的化身。
  4. 提供一个强大、基于标准的替代方案,以替代像 Gravatar 这样的中心化解决方案。
  5. 通过存储在 IPFS/Arweave 上的资料确保用户控制和去中心化。
  6. 用于所有链的通用去中心化身份标准。
  7. 充当用户的数字护照,实现无缝的去中心化验证和身份验证。

规范

唯一资料标识符

去中心化资料

每个资料都由以下内容标识:

<username>@<network_slug>.soul
  • username:用户定义的、链上唯一的字符串。
  • network_slug:链的简短标识符(例如,ethpolygonxion)。
  • soul:表示 soul bound token 的固定后缀。

示例: john@eth.soul alice@polygon.soul

去中心化标识符 (DID)

每个资料都与一个 DID 相关联:

did:<chain>:<address>

示例: did:ethereum:0x123... did:xion:xion1abc...

元数据结构

资料元数据结构旨在平衡可扩展性、可用性和与 IPFS 等去中心化存储系统的兼容性。元数据将遵循以下模式:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "UserProfile",
  "type": "object",
  "properties": {
    "username": {
      "type": "string",
      "description": "The unique handle of the user."
    },
    "avatar": {
      "type": "string",
      "format": "uri",
      "description": "IPFS URI pointing to the user's main avatar image."
    },
    "bio": {
      "type": "string",
      "description": "Short description or biography of the user."
    },
    "website": {
      "type": "string",
      "format": "uri",
      "description": "Personal or professional website of the user."
    },
    "socials": {
      "type": "object",
      "description": "User's social links.",
      "properties": {
        "twitter": {
          "type": "string",
          "format": "uri",
          "description": "URL to the user's Twitter profile."
        },
        "github": {
          "type": "string",
          "format": "uri",
          "description": "URL to the user's GitHub profile."
        }
      },
      "required": ["twitter", "github"],
      "additionalProperties": false
    },
    "default_avatar_visibility": {
      "type": "string",
      "enum": ["public", "private"],
      "description": "Default visibility setting for the main avatar."
    },
    "dapp_avatars": {
      "type": "object",
      "description": "Mapping of DApp addresses to their custom avatars and visibility settings.",
      "patternProperties": {
        "^0x[a-fA-F0-9]{40}$": {
          "type": "object",
          "properties": {
            "avatar": {
              "type": "string",
              "format": "uri",
              "description": "IPFS URI for the DApp-specific avatar."
            },
            "visibility": {
              "type": "string",
              "enum": ["public", "private"],
              "description": "Visibility setting for this avatar."
            }
          },
          "required": ["avatar", "visibility"],
          "additionalProperties": false
        }
      },
      "additionalProperties": false
    }
  },
  "required": [
    "username",
    "avatar",
    "bio",
    "website",
    "socials",
    "default_avatar_visibility",
    "dapp_avatars"
  ],
  "additionalProperties": false
}

以下是使用上述结构的示例:

{
  "username": "batman",
  "avatar": "ipfs://QmExampleMainAvatarCID",
  "bio": "Blockchain enthusiast and builder.",
  "website": "https://anirudha.dev",
  "socials": {
    "twitter": "https://twitter.com/kranirudha",
    "github": "https://github.com/anistark"
  },
  "default_avatar_visibility": "public",
  "dapp_avatars": {
    "0xDAppAddress1abcdefabcdefabcdefabcdefabcdefabcd": {
      "avatar": "ipfs://QmExampleAvatar1CID",
      "visibility": "private"
    },
    "0xDAppAddress2abcdefabcdefabcdefabcdefabcdefabcd": {
      "avatar": "ipfs://QmExampleAvatar2CID",
      "visibility": "public"
    }
  }
}

访问控制

  1. 默认头像可见性:用户可以将其默认头像可见性设置为公开或私有。
  2. dApp 特定头像可见性:每个 dApp 特定头像也可以将其可见性设置为公开或私有。

可见性逻辑

  • 如果头像为公开,则任何外部调用者都可以检索它。
  • 如果头像为私有,则只有用户才能检索它。其他调用者将获得带有错误消息的编码响应。

特定于 dApp 的头像自定义

  • 用户可以分配特定于 dApp 的头像或元数据。
  • 如果不存在特定于 dApp 的自定义,则应用默认头像。

理由

去中心化资料标准的设计遵循对统一、可互操作的用户资料系统的需求,该系统可以在所有区块链网络中无缝运行。当前的解决方案,例如 ENS 资料或 Gravatar,要么缺乏跨链功能,要么是中心化的,要么不允许用户为特定的 dApp 自定义资料。该标准解决了这些不足,同时确保了简单性、安全性和可扩展性。

设计决策

  1. 去中心化标识符 (DID)
    • 使用 did:<chain>:<address> 格式为资料提供了一个全局唯一的标识符。这与去中心化身份运动保持一致,并确保与更广泛的 DID 框架兼容。
  2. 去中心化资料
    • 资料格式 (username@networkslug.soul) 使资料具有人类可读性和特定于链的特性,同时保持通用结构。.soul 后缀清楚地标识了符合此标准的资料。
  3. 特定于 dApp 的头像
    • 允许用户分配特定于 dApp 的头像以满足个性化需求并增强用户体验。它支持用户可能希望为不同的应用程序使用不同的表示或元数据的场景。
  4. Soul Bound Tokens (SBTs)
    • 利用 SBT 确保资料不可转让,从而加强了身份所有权的概念。SBT 防止资料被出售或劫持,使其成为基于声誉的系统的理想选择。
  5. 注册表和解析器架构
    • 选择此架构是因为它的可扩展性和成熟的跟踪记录,正如在 ENS 中看到的那样。它将资料标识符的管理(注册表)与元数据的解析(解析器)分开,从而使升级和集成变得简单。
  6. 与现有标准兼容
    • 该资料标准与 ERC-165 集成以进行接口检测,并且可以补充 ENS 或其他命名系统,从而促进互操作性而不是竞争。
  7. 默认元数据和特定于 dApp 的元数据
    • 同时包含默认元数据和特定于 dApp 的元数据可确保灵活性。如果未设置特定于 dApp 的元数据,则默认资料将无缝应用,从而减少了开发人员和用户的摩擦。
  8. 链上数据最小化
    • 元数据存储在链下(例如,IPFS 或 Arweave)以最大程度地降低 gas 成本并支持可扩展的操作。只有 URI 和指针存储在链上。
  9. 访问控制

    • 用户可以完全控制头像可见性,以满足隐私偏好。
    • 具体的基于 dApp 的自定义确保了精细的控制。
  10. dApp 识别
  • 要求 dApp 由地址标识可确保可追溯性和安全性。
  1. 可扩展性
  • 添加元数据字段或新的可见性级别不会中断标准。
  • 资料可以保持轻量级,同时支持未来的可扩展性。
  1. 安全性
  • 访问控制最大程度地降低了敏感数据暴露的风险。
  • 存储在链下的元数据确保了最小的 gas 使用量和灵活性。

考虑的替代设计

  1. 纯链上元数据
    • 考虑过将所有元数据存储在链上,但由于高 gas 成本、有限的存储容量以及支持复杂或大型数据集(例如高分辨率头像)的挑战而被放弃。
  2. 与 ENS 直接集成
    • 虽然探索了与 ENS 集成,但它被认为限制了跨链功能,因为 ENS 主要与 Ethereum 绑定。去中心化资料从 ENS 中汲取灵感,同时确保真正的多链方法。
  3. 完全中心化的系统
    • 中心化系统将简化实施,但与去中心化和用户主权的核心原则相矛盾。
  4. 非基于 SBT 的实施
    • 考虑过使用标准 ERC721 代币作为资料,但由于可转让性不适合身份管理而被拒绝。SBT 强制执行身份所有权的不可变性。

与相关工作的比较

  • ENS: 提供强大的命名服务,但缺乏特定于 dApp 的元数据和跨链功能。
  • Gravatar: 集中式,无法利用区块链特定的优势,例如不变性、去中心化和 SBT 集成。
  • DID 标准: 资料必须与 DID 规范保持一致,确保其适应更广泛的去中心化身份生态系统,同时提供为基于区块链的应用程序量身定制的功能。

可扩展性和可扩展性

注册表和解析器架构,结合链下元数据存储,确保资料可以随着区块链生态系统的增长而扩展。可以添加新的链、元数据类型和自定义,而不会中断核心功能或引入重大更改。

该设计平衡了简单性、可扩展性和用户控制,使其非常适合在各种 dApp、钱包和区块链中采用。

合约接口

该标准包括以下接口:

ISoulProfile

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

interface ISoulProfile {
    struct Metadata {
        string username;
        string avatar;
        string bio;
        string website;
        mapping(string => string) socials;
        string defaultAvatarVisibility;
        mapping(address => DappAvatar) dappAvatars;
    }

    struct DappAvatar {
        string avatarURI;
        string visibility; // "public" or "private"
    }

    event ProfileCreated(address indexed user, string did, string username);
    event AvatarUpdated(address indexed user, string avatarURI, string visibility);
    event DappAvatarUpdated(
        address indexed user,
        address indexed dApp,
        string avatarURI,
        string visibility
    );

    function createProfile(string calldata username) external;
    function setDefaultAvatar(string calldata avatarURI, string calldata visibility) external;
    function setDappAvatar(
        address dApp,
        string calldata avatarURI,
        string calldata visibility
    ) external;
    function getDefaultAvatar(address user) external view returns (string memory, string memory);
    function getDappAvatar(address user, address dApp) external view returns (string memory, string memory);
}

参考实现

SoulProfile

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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./ISoulProfile.sol";

contract SoulProfile is Ownable, ISoulProfile {
    mapping(address => Metadata) private profiles;

    modifier onlyProfileOwner(address user) {
        require(msg.sender == user, "Not authorized");
        _;
    }

    function createProfile(string calldata username) external override {
        require(bytes(username).length > 0, "Username cannot be empty");
        require(profiles[msg.sender].username == "", "Profile already exists");

        profiles[msg.sender].username = username;
        profiles[msg.sender].defaultAvatarVisibility = "public"; // 默认为公开

        emit ProfileCreated(msg.sender, generateDID(msg.sender), username);
    }

    function setDefaultAvatar(string calldata avatarURI, string calldata visibility) external override {
        require(bytes(visibility).length > 0, "Visibility must be set");
        profiles[msg.sender].avatar = avatarURI;
        profiles[msg.sender].defaultAvatarVisibility = visibility;

        emit AvatarUpdated(msg.sender, avatarURI, visibility);
    }

    function setDappAvatar(
        address dApp,
        string calldata avatarURI,
        string calldata visibility
    ) external override {
        require(dApp != address(0), "Invalid dApp address");
        require(bytes(visibility).length > 0, "Visibility must be set");

        profiles[msg.sender].dappAvatars[dApp] = DappAvatar(avatarURI, visibility);

        emit DappAvatarUpdated(msg.sender, dApp, avatarURI, visibility);
    }

    function getDefaultAvatar(address user)
        external
        view
        override
        returns (string memory avatarURI, string memory visibility)
    {
        Metadata storage profile = profiles[user];
        return (profile.avatar, profile.defaultAvatarVisibility);
    }

    function getDappAvatar(address user, address dApp)
        external
        view
        override
        returns (string memory avatarURI, string memory visibility)
    {
        Metadata storage profile = profiles[user];
        DappAvatar storage dappAvatar = profile.dappAvatars[dApp];
        return (dappAvatar.avatarURI, dappAvatar.visibility);
    }

    function generateDID(address user) internal pure returns (string memory) {
        return string(abi.encodePacked("did:ethereum:", Strings.toHexString(user)));
    }
}

当然,可以根据 ENS 或类似标准扩展此功能以准备完整的注册表和解析器。有关更多信息,请参阅上面的“理由”。

向后兼容性

去中心化资料系统的设计旨在确保与现有的去中心化应用程序 (dApps) 和平台顺利集成,同时为未来的增强提供升级途径。以下是向后兼容性的关键考虑因素:

  • ENS 兼容性: 该系统遵循 EIP137(以太坊名称服务)中指定的命名约定和标准。这确保了任何已经使用与 ENS 兼容的名称的 dApp 或工具都可以轻松集成资料,而无需进行额外的更改。

  • 灵活的 dApp 识别: 通过使用钱包地址来识别 dApp,该系统避免了 dApp 中心化注册的需求。任何与以太坊或兼容链交互的现有或新的 dApp 都可以通过简单地将其地址作为参数传递来使用该标准。

  • 默认头像回退: 如果未为 dApp 设置特定的头像,系统将优雅地回退到默认头像。这确保了即使是未实现最新功能的旧 dApp 也可以继续无缝地工作。

  • 可升级的合约: 通过为所有主要合约(解析器、注册表、资料)实施基于代理的架构,该系统确保可以在不中断现有数据或工作流程的情况下部署未来的升级或功能更改。升级通过安全代理机制(如 TransparentUpgradeableProxy)进行。

  • 链不可知性: 去中心化标识符 (DID) 和链特定的网络 slug 的使用确保了跨链的互操作性。这允许资料保持一致性,而不管它们来自哪个链,从而确保与多链生态系统的兼容性。

安全注意事项

资料系统应优先考虑强大的安全性,以保护用户数据并防止未经授权的访问。

  1. 访问控制: 用户可以设置其头像的可见性(公开或私有)。这在解析器和注册表级别强制执行,以确保未经授权的实体无法访问私有头像。修改状态的函数(例如,setDefaultAvatar、setDappAvatar)受到访问控制的保护,确保只有资料所有者才能进行更改。

  2. 数据隐私: 敏感元数据在存储在去中心化存储系统(如 IPFS 或 Arweave)上之前进行加密。可见性标志确保用户可以控制谁可以查看特定的资料元素。

  3. 重入保护: 修改状态的函数实现 checks-effects-interactions 模式或利用 ReentrancyGuard 来防止重入攻击。例如,setDappAvatar 确保在更新状态之前执行所有验证。

  4. 输入验证: 验证所有用户输入(例如,用户名、可见性、dApp)以确保其符合指定的标准。拒绝无效或恶意输入以防止注入攻击或其他漏洞。可见性仅限于预定义的值(“公开”或“私有”)。

  5. 不可变的 DID: 用户的去中心化标识符 (DID) 一旦创建就不可变。这可以防止欺骗或未经授权更改用户身份。

  6. 回退机制: 该系统提供用于获取头像的回退机制。如果未找到 dApp 特定的头像,则返回默认头像,从而确保平稳运行而不会出错。

  7. 可升级的合约: 代理合约用于允许升级,同时保留状态。升级通过多重签名治理流程安全地执行,以最大程度地降低风险。

  8. 速率限制: 可以实施速率限制机制来防止垃圾邮件或滥用资料创建和更新功能。

  9. 审计和最佳实践: 这些合约是按照最佳实践设计的,并将由独立的安全性公司定期进行审计。审查依赖项(例如,OpenZeppelin 合约)并使其保持最新状态,以减轻漏洞。

  10. dApp 地址验证: 与系统交互的所有 dApp 必须由有效地址标识。这确保了未经授权或欺骗的实体无法操纵资料或获取受限数据。

  11. 网络钓鱼缓解: 鼓励面向用户的 dApp 清楚地显示有关链上交互的信息。应警告用户注意潜在的网络钓鱼攻击,并建议他们仅与经过验证的 dApp 交互。

  12. Gas 优化: 优化操作以防止执行期间的 gas 耗尽,这可能导致事务不完整。这确保了即使在拥塞的网络上,系统仍然可以运行。

  13. 安全回退功能: 安全地实施回退功能以防止意外的 Ether 转移或拒绝服务攻击。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Kumar Anirudha (@anistark), "ERC-7866: 去中心化用户资料 [DRAFT]," Ethereum Improvement Proposals, no. 7866, January 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7866.