ERC-1812: 以太坊可验证声明
Authors | Pelle Braendgaard (@pelle) |
---|---|
Created | 2019-03-03 |
Discussion Link | https://ethereum-magicians.org/t/erc-1812-ethereum-verifiable-claims/2814 |
Requires | EIP-712 |
Table of Contents
以太坊可验证声明
简述
使用 EIP 712 签名类型化数据 的可重用可验证声明。
摘要
基于 EIP-712 构建的 Off-Chain 可验证声明的一种新方法。这些声明可以由任何具有 EIP 712 兼容 web3 提供商的用户发布。声明可以存储在链下,并通过 Solidity 智能合约、状态通道实现或链下库在链上进行验证。
动机
可重用的 Off-Chain 可验证声明提供了一个重要的组成部分,用于将智能合约与真实世界的组织要求集成,例如满足 KYC、GDPR、合格投资者规则等监管要求。
ERC-735 和 ERC-780 提供了创建链上声明的方法。 这对于某些特定的用例很有用,在这些用例中,必须在链上验证关于地址的某些声明。
但在大多数情况下,在诸如 以太坊 区块链这样的不可变的公共数据库上记录包含个人身份信息 (PII) 的身份声明既是危险的,在某些情况下也是非法的(例如,根据欧盟 GDPR 规则)。
W3C 可验证声明数据模型和表示 以及 uPorts 验证消息规范 是建议的链下解决方案。
虽然建立在 JSON-LD 和 JWT 等行业标准之上,但它们都不容易与 以太坊 生态系统集成。
EIP-712 引入了一种新的链下身份数据签名方法。 这提供了一种基于 Solidity ABI 编码的数据格式,可以轻松地在链上解析,以及一种新的 JSON-RPC 调用,可以轻松地被现有的 以太坊 钱包和 Web3 客户端支持。
这种格式允许廉价地向用户发布可重用的链下可验证声明,用户可以在需要时出示它们。
先有技术
由 uPort 和 W3C 可验证声明工作组 提出的已验证身份声明构成了构建可重用身份声明的重要组成部分。
ERC-735 和 ERC-780 提供了可验证声明的链上存储和查找。
规范
声明
声明可以概括为这样:
颁发者声明主体是某事物或具有某些属性和值。
声明应该是确定性的,即相同的签名者多次签署相同的声明。
声明数据结构
每个声明都应该根据其特定的用例进行类型化,EIP 712 让我们毫不费力地做到这一点。 但是声明结构至少需要 3 个属性。
subject
声明的主体,作为address
(声明是关于谁的)validFrom
以uint256
编码的声明有效开始的时间(以秒为单位)。 在大多数情况下,这将是颁发时间,但某些声明可能在将来或过去有效。validTo
以uint256
编码的声明到期的时间(以秒为单位)。 如果你希望声明永不过期,请使用0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
。
作为 Solidity 结构的基本最小声明数据结构:
struct [CLAIM TYPE] {
address subject;
uint256 validFrom;
uint256 validTo;
}
CLAIM TYPE
是声明的实际名称。 虽然不是必需的,但在大多数情况下,请使用由 schema.org 开发的分类法,该分类法也常用于其他可验证声明格式。
颁发者知道主体的示例声明:
struct Know {
address subject;
uint256 validFrom;
uint256 validTo;
}
呈现可验证声明
验证合约
在定义可验证声明格式时,应创建一个带有公共 verify()
视图函数的验证合约。 这使得其他智能合约可以非常容易地正确验证声明。
它还为 web3 和状态通道应用程序提供了一个方便的接口来安全地验证声明。
function verifyIssuer(Know memory claim, uint8 v, bytes32 r, bytes32 s) public returns (address) {
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(claim)
)
);
require(
(claim.validFrom >= block.timestamp) && (block.timestamp < claim.validTo)
, "invalid issuance timestamps"); // 无效的颁发时间戳
return ecrecover(digest, v, r, s);
}
调用智能合约函数
可验证声明可以作为它的结构以及 v
、r
和 s
签名组件呈现给 solidity 函数调用。
function vouch(Know memory claim, uint8 v, bytes32 r, bytes32 s) public returns (bool) {
address issuer = verifier.verifyIssuer(claim, v, r, s);
require(issuer !== '0x0');
knows[issuer][claim.subject] = block.number;
return true;
}
将可验证声明嵌入到另一个签名类型化数据结构中
Claim
结构应与 v
、r
和 s
签名参数一起嵌入到另一个结构中。
struct Know {
address subject;
uint256 validFrom;
uint256 validTo;
}
struct VerifiableReference {
Know delegate;
uint8 v;
bytes32 r;
bytes32 s;
}
struct Introduction {
address recipient;
VerifiableReference issuer;
}
每个可验证声明都应与父签名类型化数据结构一起单独验证。
颁发给不同 EIP 712 域的可验证声明可以相互嵌入。
状态通道
本提案将不展示如何将 Eth 可验证声明用作特定状态通道方法的一部分。
任何基于 EIP712 的状态通道都应该能够包含可嵌入的可验证声明作为其协议的一部分。 这对于在各方之间交换私有身份声明以满足监管要求可能很有用,同时最终不会在通道结束时将其发布到区块链。
密钥委托
在大多数简单的情况下,声明的颁发者是数据的签名者。 但是,在某些情况下,签名应委托给中间密钥。
密钥委托可用于为基于智能合约的地址实现链下签名、服务器端密钥轮换以及复杂业务用例中的员工权限。
ERC1056 签名委托
ERC-1056 提供了一种地址分配委托签名者的方法。 它的主要用例之一是智能合约可以允许密钥对在一定时间内代表它签名。 它还允许基于服务器的颁发工具来建立密钥轮换。
为了支持这一点,可以将额外的 issuer
属性添加到 Claim Type
结构中。 在这种情况下,验证代码应查找 EthereumDIDRegistry
,以查看数据签名者是否是 issuer
的允许签名委托。
以下是包含颁发者的声明的最小结构:
struct [CLAIM TYPE] {
address subject;
address issuer;
uint256 validFrom;
uint256 validTo;
}
如果在结构中指定了 issuer
,除了执行标准 ERC712 验证之外,验证代码还 必须 验证签名地址是否是 issuer
中指定的地址的有效 veriKey
委托。
registry.validDelegate(issuer, 'veriKey', recoveredAddress)
嵌入式委托证明
可能存在一些应用程序,尤其是在组织希望允许委托人颁发关于特定域和类型的声明的情况下。
为此,我们允许嵌入一个遵循相同格式的特殊声明,而不是 issuer
:
struct Delegate {
address issuer;
address subject;
uint256 validFrom;
uint256 validTo;
}
struct VerifiableDelegate {
Delegate delegate;
uint8 v;
bytes32 r;
bytes32 s;
}
struct [CLAIM TYPE] {
address subject;
VerifiedDelegate issuer;
uint256 validFrom;
uint256 validTo;
}
应为特定的 EIP 712 域创建委托,并且不应跨域重复使用。
新的 EIP 712 域的实现者可以向 Delegate
结构添加更多数据,以便对其进行更细粒度的特定于应用程序的规则。
声明类型
二进制声明
Binary
声明是没有特定值的声明。 它是颁发的或未颁发的。
例子:
- 主体是
Person
- 主体是我的所有者(例如,将 以太坊 帐户链接到所有者身份)
例子:
struct Person {
address issuer;
address subject;
uint256 validFrom;
uint256 validTo;
}
这与上面的最小声明完全相同,只是 CLAIM TYPE
设置为 Person。
值声明
值声明可用于声明包含特定可读值的主体。
警告:在智能合约交易中部分使用值声明时要非常小心。 包含值的身份声明可能是企业或开发人员鼓励用户将其发布到公共区块链的 GDPR 违规行为。
例子:
- 主体的名字是 Alice
- 主体的平均账户余额是 1234555
每个值都应使用 value
字段来指示该值。
名称声明
struct Name {
address issuer;
address subject;
string name;
uint256 validFrom;
uint256 validTo;
}
平均余额
struct AverageBalance {
address issuer;
address subject;
uint256 value;
uint256 validFrom;
uint256 validTo;
}
哈希声明
哈希声明可用于声明包含声明值的哈希值的主体。 哈希值应使用 以太坊 标准 keccak256
哈希函数。
警告:在智能合约交易中部分使用哈希声明时要非常小心。 包含已知值的哈希值的身份声明可能是企业或开发人员鼓励用户将其发布到公共区块链的 GDPR 违规行为。
例子:
- 主体的名字的哈希是
keccak256(“Alice Torres”)
- 主体的电子邮件的哈希是
keccak256(“alice@example.com”)
每个值都应使用 keccak256
字段来指示哈希值。 问题:选择使用此名称是因为我们可以轻松添加对未来算法以及可能的 zkSnark 证明的支持。
名称声明
struct Name {
address issuer;
address subject;
bytes32 keccak256;
uint256 validFrom;
uint256 validTo;
}
电子邮件声明
struct Email {
address issuer;
address subject;
bytes32 keccak256;
uint256 validFrom;
uint256 validTo;
}
EIP 712 域
EIP 712 域指定要签名的消息类型,并用于区分签名的数据类型。 内容必须包含以下内容:
{
name: "EIP1???Claim",
version: 1,
chainId: 1, // for mainnet
verifyingContract: 0x // TBD
salt: ...
}
EIP 712 签名的完整组合格式:
按照 EIP 712 标准,我们可以将 Claim Type
与 EIP 712 Domain
和声明本身(在 message
中)属性结合起来。
例:
{
"types": {
"EIP712Domain": [
{
"name": "name",
"type": "string"
},
{
"name": "version",
"type": "string"
},
{
"name": "chainId",
"type": "uint256"
},
{
"name": "verifyingContract",
"type": "address"
}
],
"Email": [
{
"name": "subject",
"type": "address"
},
{
"name": "keccak256",
"type": "bytes32"
},
{
"name": "validFrom",
"type": "uint256"
},
{
"name": "validTo",
"type": "uint256"
}
]
},
"primaryType": "Email",
"domain": {
"name": "EIP1??? Claim",
"version": "1",
"chainId": 1,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"message": {
"subject": "0x5792e817336f41de1d8f54feab4bc200624a1d9d",
"value": "9c8465d9ae0b0bc167dee7f62880034f59313100a638dcc86a901956ea52e280",
"validFrom": "0x0000000000000000000000000000000000000000000000000001644b74c2a0",
"validTo": "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}
撤销
应允许颁发者和主体撤销可验证声明。 撤销可以通过一个简单的链上注册表来处理。
谁应该能够撤销声明的最终规则由验证合约确定。
用于撤销的 digest
是 EIP712 签名类型化数据摘要。
contract RevocationRegistry {
mapping (bytes32 => mapping (address => uint)) public revocations;
function revoke(bytes32 digest) public returns (bool) {
revocations[digest][msg.sender] = block.number;
return true;
}
function revoked(address party, bytes32 digest) public view returns (bool) {
return revocations[digest][party] > 0;
}
}
验证合约可以这样查询 Revocation Registry
:
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
hash(claim)
)
);
require(valid(claim.validFrom, claim.validTo), "invalid issuance timestamps"); // 无效的颁发时间戳
address issuer = ecrecover(digest, v, r, s);
require(!revocations.revoked(issuer, digest), "claim was revoked by issuer"); // 声明已被颁发者撤销
require(!revocations.revoked(claim.subject, digest), "claim was revoked by subject"); // 声明已被主体撤销
创建可验证声明域
创建特定的可验证声明域超出了本 EIP 的范围。 示例代码有一些示例。
EIP 或另一个过程可用于标准化在 以太坊 世界中普遍有用的特定重要域。
理由
签名类型化数据为可验证声明提供了坚实的基础,可用于在 以太坊 的第 1 层和第 2 层上构建的许多不同类型的应用程序。
不使用单个 EIP 712 域的理由
EIP712 本身支持复杂的类型和域,我们认为它们是为特定目的构建可验证声明的完美构建块。
声明的类型和域本身就是声明的重要组成部分,并确保可验证声明用于所需的特定目的,而不是被滥用。
EIP712 域还允许快速实验,从而允许社区建立分类法。
测试用例
有一个代码仓库,其中包含一些用 Solidity 编写的示例验证器和使用智能合约:
示例验证器
示例智能合约
- KYCCoin.sol - 示例 Token 允许可信验证器颁发的可重用 IdVerification 声明,并允许用户使用 OwnershipProofs 将他们自己的地址列入白名单
- ConsortiumAgreement.sol - 示例联盟协议智能合约。 联盟成员可以向员工或服务器颁发委托声明,以代表他们进行交互。
共享注册表
版权
通过 CC0 放弃版权及相关权利。
Citation
Please cite this document as:
Pelle Braendgaard (@pelle), "ERC-1812: 以太坊可验证声明 [DRAFT]," Ethereum Improvement Proposals, no. 1812, March 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1812.