Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-1261: 成员资格验证 Token (MVT)

Authors Chaitanya Potti (@chaitanyapotti), Partha Bhattacharya (@pb25193)
Created 2018-07-14
Discussion Link https://github.com/ethereum/EIPs/issues/1261
Requires EIP-165, EIP-173

简单总结

成员资格验证 Token(MVT) 的标准接口。

摘要

以下标准允许在智能合约(称为实体)中实现成员资格验证 Token 的标准 API。此标准提供基本功能来跟踪个人在某些链上“组织”中的成员资格。这允许一些用例,如自动化合规性,以及几种形式的治理和成员结构。

我们考虑了将 MVT 分配给个人的用例,这些 MVT 是不可转让的,并且可以被所有者撤销。MVT 可以代表认可证明、成员资格证明、投票权证明以及区块链上几种其他抽象概念。以下是这些用例的一些示例,并且有可能提出其他几个用例:

  • 投票:投票本质上应该是一项需要授权的活动。到目前为止,链上投票系统只能使用基于代币余额的投票来进行投票。这种情况现在可以改变,并呈现出各种形状和形式。
  • 护照签发、社会福利分配、旅行许可证签发、驾驶执照签发都可以抽象为成员资格,即个人属于一小部分,被某些机构承认为具有某些权利,而不需要任何个人特定信息(福利权、行动自由、操作车辆的授权、移民)
  • 投资者授权:使法规遵从成为一个简单的链上流程。证券代币化,简化为仅流向经过认证的地址,追踪和认证用于 AML 目的的链上地址。
  • 软件许可:像游戏开发者这样的软件公司可以使用该协议来授权某些硬件单元(游戏机)下载和使用特定软件(游戏)

一般来说,一个人在日常生活中可以拥有不同的成员资格。该协议允许创建将所有内容集中在一个地方的软件。他们的身份可以立即得到验证。想象一下,你无需携带装满身份证件的钱包(护照、健身会员卡、SSN、公司 ID 等),组织可以轻松跟踪其所有成员的世界。组织可以轻松识别和禁止虚假身份。

属性是 ERC-1261 的重要组成部分,它有助于存储有关其成员的可识别信息。民意调查可以利用属性来计算选民基础。 例如:用户应该属于美国实体,并且不属于华盛顿州属性才能成为民意调查的一部分。

将存在一个映射表,将属性标头映射到所有可能属性的数组。这样做是为了将实体细分为互斥且详尽的子组。例如, 标头:血型字母 数组:[ o, a, b, ab ] 标头:血型符号 数组:[ +, - ]

不是互斥详尽的例子: 标头:视频订阅 数组:[ Netflix, HBO, Amazon ] 因为一个人不必一定只拥有一个元素。他或她可能没有或有多个。

动机

标准接口允许任何用户、应用程序与以太坊上的任何 MVT 交互。我们为简单的 ERC-1261 智能合约提供支持。其他应用程序将在下面讨论。

这个标准的灵感来自于区块链上的投票是通过代币余额权重来完成的。尽管区块链提供了巨大的治理潜力,但这极大地损害了区块链上灵活治理系统的形成。这个想法是创建一个授权系统,允许组织在区块链上对人员进行一次审查,然后可以在可以进行的治理类型中获得极大的灵活性。

我们还审查了其他成员资格 EIP,包括 EIP-725/735 声明注册表。#735 声明和 #1261 MVT 之间的一个显着区别是信息所有权。在 #735 中,声明持有人拥有任何关于他们的声明。这样做的问题是,声明发布者一旦发布了声明,就无法撤销或更改声明。虽然 #735 确实指定了一个 removeClaim 方法,但恶意实现可能会简单地忽略该方法调用,因为他们拥有该声明。

假设 SafeEmploy™ 是一家背景调查公司,发布了关于 Timmy 的声明。该声明指出 Timmy 从未被判定犯有任何重罪。Timmy 做出了一些糟糕的决定,现在该声明不再真实。SafeEmploy™ 执行 removeClaim,但 Timmy 的 #735 合约只是忽略了它,因为 Timmy 想继续工作(并且精通加密技术)。#1261 MVT 没有这个问题。徽章/声明的所有权完全由发布徽章的合约决定,而不是接收徽章的合约决定。发布者可以随意删除或更改这些徽章。

不信任性和可用性之间的权衡: 要真正理解该协议的价值,重要的是理解我们所面临的权衡。MVT 合约允许创建者撤销代币,并且本质上没收相关成员的成员资格。对一些人来说,这似乎是一个不可接受的缺陷,但这是一种设计选择,而不是缺陷。 这种选择可能似乎将大量的信任放在管理实体合约的个人(实体所有者)身上。如果实体所有者的利益与成员的利益相冲突,则所有者可能会采取添加虚假地址(以支配共识)或驱逐成员(以审查不利决定)的手段。乍一看,这似乎是一个主要的缺点,因为区块链空间在大多数情况下都习惯于完全消除权威。即使是 dapp 的官方定义也要求不存在任何管理应用程序提供的服务的一方。但是,如果可以确保实体所有者的利益与成员的利益一致,那么对实体所有者的信任就不会错位。 对此类系统的另一个批评是,区块链中介的标准优势——“如果你不知道该贿赂谁,就无法贿赂系统”——不再成立。可以贿赂实体所有者屈服,并让他们审查或伪造投票。有几种方法可以回应这个论点。首先,所有活动,如添加成员和删除成员,都可以在区块链上进行跟踪,并且无法删除此类活动的痕迹。构建分析工具来检测恶意活动(突然添加 100 个虚假成员,他们朝着某个方向投票/突然删除一些朝着某个方向投票的成员)并不困难。其次,实体所有者的权力仅限于添加和删除成员。这意味着他们不能篡改任何投票。他们只能更改计数系统以包括虚假选民或删除真实选民。任何明智的审计员都可以识别恶意/受害者地址并创建一个开源审计工具来找出正确的结果。在这种攻击中最大的输家将是实体所有者,他会损失声誉。 最后,必须理解为什么我们在此权衡中放弃不信任性。答案是可用性。引入授权系统扩大了可以通过区块链交付的产品和服务范围,同时利用了区块链的其他方面(廉价、不可变、无繁文缛节、安全)。以驾驶执照签发机构使用 ERC-1300 标准为例。这项服务根本无法在完全不信任的环境中部署。权限系统的引入扩大了区块链上服务的范围,以涵盖这项特定服务。当然,他们有权无缘无故地撤销一个人的执照。但他们会这样做吗?如果该机构的行为不稳定,谁的损失最大?该机构本身。现在考虑另一种选择,即颁发执照(不一定只是驾驶执照,而是像股东证明等),所花费的时间,完全缺乏透明度。有人可能会争辩说,如果提供这些服务的传统系统真的想在执行这些服务时进行腐败和裙带关系,那么目前的系统会更容易这样做。而且,它们不透明,这意味着甚至无法检测到它们是否恶意行事。 话虽如此,我们非常高兴与社区分享我们的提案,并欢迎大家提出建议。

规范

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。

每个符合 ERC-1261 的合约必须实现 ERC1261ERC173ERC165 接口 (受以下“注意事项”的约束):

/// @title ERC-1261 MVT 标准
/// @dev 查看 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1261.md
///  构造函数应该为此 MVT 定义属性集。
///  注意:此接口的 ERC-165 标识符是 0x1d8362cf。
interface IERC1261 {/* is ERC173, ERC165 */
    /// @dev 当 token 分配给成员时,会发出此事件。
    event Assigned(address indexed _to, uint[] attributeIndexes);

    /// @dev 当成员资格被撤销时,会发出此事件。
    event Revoked(address indexed _to);

    /// @dev 当用户放弃他的成员资格时,会发出此事件
    event Forfeited(address indexed _to);

    /// @dev 当成员资格请求被接受时,会发出此事件
    event ApprovedMembership(address indexed _to, uint[] attributeIndexes);

    /// @dev 当用户请求成员资格时,会发出此事件
    event RequestedMembership(address indexed _to);

    /// @dev 当成员的数据被修改时,会发出此事件。
    ///  在创建新成员资格并分配数据时,不会发出此事件。
    event ModifiedAttributes(address indexed _to, uint attributeIndex, uint attributeValueIndex);

    /// @notice 将新的属性 (键, 值) 对添加到预先存在的属性集中。
    /// @dev 将新属性添加到属性数组的末尾,并将其映射到 `values`。
    ///  合约可以设置最大属性数,如果达到限制则抛出。
    /// @param _name 要添加的属性的名称。
    /// @param values 指定属性的值列表。
    function addAttributeSet(bytes32 _name, bytes32[] calldata values) external;

    /// @notice 修改给定 `_to` 地址的特定属性的属性值。
    /// @dev 使用适当的检查来确定用户/管理员是否可以修改数据。
    ///  最佳实践是使用 ERC173 中的 onlyOwner 修饰符。
    /// @param _to 要修改其属性的地址。
    /// @param _attributeIndex 要修改的属性的索引。
    /// @param _modifiedValueIndex 要分配给用户属性的新值的索引。
    function modifyAttributeByIndex(address _to, uint _attributeIndex, uint _modifiedValueIndex) external;

    /// @notice 从任何地址请求成员资格。
    /// @dev 如果 `msg.sender` 已经拥有 token,则抛出。
    ///  如果满足某些现有标准,则个人 `msg.sender` 可以请求成员资格。
    ///  当请求成员资格时,此函数会发出 RequestedMembership 事件。
    ///  dev 可以存储成员资格请求,并稍后使用 `approveRequest` 分配成员资格
    ///  dev 还可以对请求进行 oraclize,以便稍后分配成员资格
    /// @param _attributeIndexes 与成员关联的属性数据。
    ///  这是一个包含属性索引的数组。
    function requestMembership(uint[] calldata _attributeIndexes) external payable;

    /// @notice 用户可以放弃他的成员资格。
    /// @dev 如果 `msg.sender` 已经没有 token,则抛出。
    ///  个人 `msg.sender` 可以撤销他/她的成员资格。
    ///  当 token 被撤销时,此函数会发出 Revoked 事件。
    function forfeitMembership() external payable;

    /// @notice 所有者批准来自任何地址的成员资格。
    /// @dev 如果 `_user` 没有待处理的请求,则抛出。
    ///  如果 `msg.sender` 不是所有者,则抛出。
    ///  批准待处理的请求
    ///  使 oraclize 回调调用此函数
    ///  当 token 被分配时,此函数会发出 `ApprovedMembership` 和 `Assigned` 事件。
    /// @param _user 其成员资格请求将被批准的用户。
    function approveRequest(address _user) external;

    /// @notice 所有者丢弃来自任何地址的成员资格。
    /// @dev 如果 `_user` 没有待处理的请求,则抛出。
    ///  如果 `msg.sender` 不是所有者,则抛出。
    ///  丢弃待处理的请求
    ///  如果标准不满足,则使 oraclize 回调调用此函数
    /// @param _user 其成员资格请求将被丢弃的用户。
    function discardRequest(address _user) external;

    /// @notice 将 MVT 的成员资格从所有者地址分配到另一个地址。
    /// @dev 如果成员已经拥有 token,则抛出。
    ///  如果 `_to` 是零地址,则抛出。
    ///  如果 `msg.sender` 不是所有者,则抛出。
    ///  实体将成员资格分配给每个人。
    ///  当 token 被分配时,此函数会发出 Assigned 事件。
    /// @param _to 要将 token 分配到的地址。
    /// @param _attributeIndexes 与成员关联的属性数据。
    ///  这是一个包含属性索引的数组。
    function assignTo(address _to, uint[] calldata _attributeIndexes) external;

    /// @notice 只有所有者才能撤销成员资格。
    /// @dev 这将删除用户的成员资格。
    ///  如果 `_from` 不是 token 的所有者,则抛出。
    ///  如果 `msg.sender` 不是所有者,则抛出。
    ///  如果 `_from` 是零地址,则抛出。
    ///  当事务完成时,此函数会发出 Revoked 事件。
    /// @param _from MVT 的当前所有者。
    function revokeFrom(address _from) external;

    /// @notice 查询成员是否是该组织的当前成员。
    /// @dev 分配给零地址的 MVT 被认为是无效的,并且此
    ///  函数会针对有关零地址的查询抛出。
    /// @param _to 要查询其成员资格的地址。
    /// @return 该成员是否拥有 token。
    function isCurrentMember(address _to) external view returns (bool);

     /// @notice 获取属性的值集合。
    /// @dev 以 bytes32 数组的形式返回属性的值。
    /// @param _name 要获取其值的属性的名称
    /// @return 属性的值。
    function getAttributeExhaustiveCollection(bytes32 _name) external view returns (bytes32[] memory);

    /// @notice 返回所有过去和现在的成员的列表。
    /// @dev 将此函数与 isCurrentMember 一起使用,以在 JavaScript 中查找 wasMemberOf()。
    ///  它可以计算为存在于 getAllMembers() 中并且 !isCurrentMember()。
    /// @return 拥有 token 且当前拥有 token 的地址列表。
    function getAllMembers() external view returns (address[]);

    /// @notice 返回所有当前成员的计数。
    /// @dev 在民意调查中使用此函数作为分母,以获取已投票成员的百分比。
    /// @return 当前成员的计数。
    function getCurrentMemberCount() external view returns (uint);

    /// @notice 返回所有属性名称的列表。
    /// @dev 以 bytes32 数组的形式返回属性的名称。
    ///  AttributeNames 存储在 bytes32 数组中。
    ///  每个 attributeName 的可能值都存储在映射中 (attributeName => attributeValues)。
    ///  AttributeName 是 bytes32,attributeValues 是 bytes32[]。
    ///  特定用户的属性存储在 bytes32[] 中。
    ///  对于数组中的每个 attributeName,都有一个 attributeValue。
    ///  使用 web3.toAscii(data[0]).replace(/\u0000/g, "") 在 JavaScript 中转换为字符串。
    /// @return 属性的名称。
    function getAttributeNames() external view returns (bytes32[] memory);

    /// @notice 返回 `_to` 地址的属性。
    /// @dev 如果 `_to` 是零地址,则抛出。
    ///  使用 web3.toAscii(data[0]).replace(/\u0000/g, "") 在 JavaScript 中转换为字符串。
    /// @param _to 要返回其当前属性的地址。
    /// @return 与 `_to` 地址关联的属性。
    function getAttributes(address _to) external view returns (bytes32[]);

    /// @notice 返回针对 `_to` 地址存储的 `attribute`。
    /// @dev 查找 `attribute` 的索引。
    ///  如果属性不在预定义的属性中,则抛出。
    ///  返回指定 `attribute` 的 attributeValue。
    /// @param _to 请求其属性的地址。
    /// @param _attributeIndex 需要的属性索引。
    /// @return 指定名称的属性值。
    function getAttributeByIndex(address _to, uint _attributeIndex) external view returns (bytes32);
}

interface ERC173 /* is ERC165 */ {
    /// @dev 当合约的所有权发生变化时,会发出此事件。
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /// @notice 获取所有者的地址
    /// @return 所有者的地址。
    function owner() external view;

    /// @notice 设置合约新所有者的地址
    /// @param _newOwner 合约新所有者的地址
    function transferOwnership(address _newOwner) external;
}

interface ERC165 {
    /// @notice 查询合约是否实现了接口
    /// @param interfaceID 接口标识符,如 ERC-165 中指定
    /// @dev 接口识别在 ERC-165 中指定。此函数
    ///  使用的 gas 小于 30,000。
    /// @return 如果合约实现了 `interfaceID` 并且
    ///  `interfaceID` 不是 0xffffffff,则为 `true`,否则为 `false`
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

元数据扩展 对于 ERC-1261 智能合约是可选的(参见下面的“注意事项”)。这允许查询您的智能合约的名称以及有关您的 MV token 所代表的组织的详细信息。

/// @title ERC-1261 MVT 标准,可选元数据扩展
/// @dev 查看 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1261.md
interface ERC1261Metadata /* is ERC1261 */ {
    /// @notice 此合约中 MVT 集合的描述性名称
    function name() external view returns (string _name);

    /// @notice 此合约中 MVT 的缩写名称
    function symbol() external view returns (string _symbol);
}

这是上面引用的“ERC1261 元数据 JSON 模式”。

{
  "title": "组织元数据",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "标识此 MVT 所代表的组织"
    },
    "description": {
      "type": "string",
      "description": "描述此 MVT 所代表的组织"
    }
  }
}

注意事项

0.4.24 Solidity 接口语法不足以记录 ERC-1261 标准。符合 ERC-1261 的合约还必须遵守以下内容:

  • Solidity issue #3412:上述接口包括每个函数的显式可变性保证。可变性保证按从弱到强的顺序排列:payable、隐式 nonpayable、viewpure。您的实现必须满足此接口中的可变性保证,并且您可以满足更强的保证。例如,此接口中的 payable 函数可以在您的合约中实现为 nonpayble(未指定状态可变性)。我们希望更高版本的 Solidity 允许您的更严格的合约从此接口继承,但对于版本 0.4.24 的一种解决方法是,您可以在从您的合约继承之前编辑此接口以添加更严格的可变性。
  • Solidity issue #3419:实现 ERC1261Metadata 的合约 SHALL 也实现 ERC1261
  • Solidity issue #2330:如果此规范中将函数显示为 external,则如果合约使用 public 可见性,则合约将符合规范。作为版本 0.4.24 的解决方法,您可以在从您的合约继承之前编辑此接口以切换到 public
  • Solidity issue #3494、#3544:使用 this.*.selector 被 Solidity 标记为警告,Solidity 的未来版本不会将其标记为错误。

如果新版本的 Solidity 允许在代码中表达这些注意事项,则可以更新此 EIP 并删除这些注意事项,这将等效于原始规范。

理由

以太坊智能合约的许多潜在用途都依赖于跟踪成员资格。现有或计划的 MVT 系统示例包括 Vault(一个 DAICO 平台)和 Stream(一个安全 token 框架)。未来的用途包括实施直接民主、游戏内成员资格和徽章、许可证和旅行证件签发、电子投票机记录、软件许可等等。

MVT 词语选择:

由于 token 不可转让且可撤销,因此它们的功能类似于会员卡。因此,使用词语成员资格验证 token。

转移机制

MVT 无法转移。这是一种设计选择,也是区别该协议的特征之一。 任何成员都可以随时要求发行人从现有地址撤销 token 并分配给新地址。 可以将 MVT 集合视为标识用户,并且您不能将用户分成几个部分并使其成为同一用户,但是您可以将用户转移到新的私钥。

分配和撤销机制

分配和撤销函数的文档仅指定事务必须抛出的条件。您的实现也可能在其他情况下抛出。这允许实现获得有趣的结果:

  • 在满足条件后禁止添加其他成员资格 — GitHub 上提供了示例合约
  • 将某些地址列入接收 MV token 的黑名单 — GitHub 上提供了示例合约
  • 在达到某个时间后禁止添加其他成员资格 — GitHub 上提供了示例合约
  • 向事务用户收取费用 — 在调用 assignrevoke 时需要付款,以便可以从外部来源进行条件检查

ERC-173 接口

我们选择了所有权标准接口 (ERC-173) 来管理 ERC-1261 合约的所有权。

未来的 EIP/Zeppelin 可能会创建一个用于所有权的多所有者实现。我们强烈支持这样的 EIP,它将允许您的 ERC-1261 实现通过委托给单独的合约来实现 ERC1261Metadata 或其他接口。

ERC-165 接口

我们选择了标准接口检测 (ERC-165) 来公开 ERC-1261 智能合约支持的接口。

未来的 EIP 可能会创建一个合约接口的全局注册表。我们强烈支持这样的 EIP,它将允许您的 ERC-1261 实现通过委托给单独的合约来实现 ERC1261Metadata 或其他接口。

Gas 和复杂性(关于枚举扩展)

此规范考虑了管理少量和_任意数量_ MVT 的实现。如果您的应用程序能够增长,请避免在代码中使用 for/while 循环。这些表明您的合约可能无法扩展,并且 gas 成本会随着时间的推移无限期上涨

隐私

个人信息:该协议不会将任何个人信息放到区块链上,因此在这方面不会泄露隐私。 成员资格隐私:该协议的设计使其公开哪些地址是/不是成员。如果不公开这些信息,将无法独立审计治理活动或跟踪管理员(实体所有者)活动。

元数据选择(元数据扩展)

我们已在元数据扩展中要求 namesymbol 函数。我们审查的每个 token EIP 和草案(ERC-20、ERC-223、ERC-677、ERC-777、ERC-827)都包含这些函数。

我们提醒实现作者,如果您反对使用此机制,则空字符串是对 namesymbol 的有效响应。我们还提醒大家,任何智能合约都可以使用与_您的_合约相同的名称和符号。客户端如何确定哪些 ERC-1261 智能合约是众所周知的(规范的)超出了此标准的范围。

提供了一种将 MVT 与 URI 关联的机制。我们希望许多实现将利用此机制为每个 MVT 系统提供元数据。URI 可能是可变的(即,它会不时更改)。我们考虑了一个代表某个场所成员资格的 MVT,在这种情况下,有关组织的元数据自然会发生变化。

元数据作为字符串值返回。目前,这仅可用作从 web3 调用,而不是从其他合约调用。这是可以接受的,因为我们还没有考虑链上应用程序会查询此类信息的用例。

考虑的其他选择:将每个资产的所有元数据放在区块链上(太昂贵),使用 URL 模板查询元数据部分(URL 模板不适用于所有 URL 方案,尤其是 P2P URL),multiaddr 网络地址(不够成熟)

社区共识

我们在这个过程中非常具有包容性,并邀请有任何问题或贡献的人参与我们的讨论。但是,此标准仅用于支持此处列出的已识别的用例。

向后兼容性

我们采用了 ERC-20 规范中的 namesymbol 语义。

截至 2018 年 7 月的 MVT 实现示例:

  • 成员资格验证 Token (https://github.com/chaitanyapotti/MembershipVerificationToken)

测试用例

成员资格验证 Token ERC-1261 Token 包括使用 Truffle 编写的测试用例。

实现

成员资格验证 Token ERC1261 – 参考实现

  • MIT 许可,因此您可以免费将其用于您的项目
  • 包括测试用例
  • 也可作为 npm 包使用 - npm i membershipverificationtoken

引用

标准

  1. ERC-20 Token 标准. ./eip-20.md
  2. ERC-165 标准接口检测. ./eip-165.md
  3. ERC-725/735 声明注册表 ./eip-725.md
  4. ERC-173 所有权标准. ./eip-173.md
  5. JSON 模式. https://json-schema.org/
  6. Multiaddr. https://github.com/multiformats/multiaddr
  7. RFC 2119 用于在 RFC 中指示需求级别的关键词. https://www.ietf.org/rfc/rfc2119.txt

问题

  1. 原始 ERC-1261 问题. https://github.com/ethereum/eips/issues/1261
  2. Solidity Issue #2330 – 接口函数为 Axternal. https://github.com/ethereum/solidity/issues/2330
  3. Solidity Issue #3412 – 实现接口:允许更严格的可变性。https://github.com/ethereum/solidity/issues/3412
  4. Solidity Issue #3419 – 接口无法继承。https://github.com/ethereum/solidity/issues/3419

讨论

  1. Gitter #EIPs(首次实时讨论的公告)。https://gitter.im/ethereum/EIPs?at=5b5a1733d2f0934551d37642
  2. ERC-1261(首次实时讨论的公告)。https://github.com/ethereum/eips/issues/1261

MVT 实现和其他项目

  1. 成员资格验证 Token ERC-1261 Token. https://github.com/chaitanyapotti/MembershipVerificationToken

版权

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

Citation

Please cite this document as:

Chaitanya Potti (@chaitanyapotti), Partha Bhattacharya (@pb25193), "ERC-1261: 成员资格验证 Token (MVT) [DRAFT]," Ethereum Improvement Proposals, no. 1261, July 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1261.