forked from DecentralizedClimateFoundation/DCIPs
443 lines
16 KiB
Markdown
443 lines
16 KiB
Markdown
|
---
|
|||
|
eip: 1812
|
|||
|
title: Ethereum Verifiable Claims
|
|||
|
author: Pelle Braendgaard (@pelle)
|
|||
|
discussions-to: https://ethereum-magicians.org/t/erc-1812-ethereum-verifiable-claims/2814
|
|||
|
status: Stagnant
|
|||
|
type: Standards Track
|
|||
|
category: ERC
|
|||
|
created: 2019-03-03
|
|||
|
requires: 712
|
|||
|
---
|
|||
|
|
|||
|
# Ethereum Verifiable Claims
|
|||
|
|
|||
|
## Simple Summary
|
|||
|
|
|||
|
Reusable Verifiable Claims using [EIP 712 Signed Typed Data](./eip-712.md).
|
|||
|
|
|||
|
## Abstract
|
|||
|
A new method for Off-Chain Verifiable Claims built on [EIP-712](./eip-712.md). These Claims can be issued by any user with a EIP 712 compatible web3 provider. Claims can be stored off chain and verified on-chain by Solidity Smart Contracts, State Channel Implementations or off-chain libraries.
|
|||
|
|
|||
|
## Motivation
|
|||
|
Reusable Off-Chain Verifiable Claims provide an important piece of integrating smart contracts with real world organizational requirements such as meeting regulatory requirements such as KYC, GDPR, Accredited Investor rules etc.
|
|||
|
|
|||
|
[ERC-735](https://github.com/ethereum/EIPs/issues/735) and [ERC-780](https://github.com/ethereum/EIPs/issues/780) provide methods of making claims that live on chain. This is useful for some particular use cases, where some claim about an address must be verified on chain.
|
|||
|
|
|||
|
In most cases though it is both dangerous and in some cases illegal (according to EU GDPR rules for example) to record Identity Claims containing Personal Identifying Information (PII) on an immutable public database such as the Ethereum blockchain.
|
|||
|
|
|||
|
The W3C [Verifiable Claims Data Model and Representations](https://www.w3.org/TR/verifiable-claims-data-model/) as well as uPorts [Verification Message Spec](https://developer.uport.me/messages/verification) are proposed off-chain solutions.
|
|||
|
|
|||
|
While built on industry standards such as [JSON-LD](https://json-ld.org) and [JWT](https://jwt.io) neither of them are easy to integrate with the Ethereum ecosystem.
|
|||
|
|
|||
|
[EIP-712](./eip-712.md) introduces a new method of signing off chain Identity data. This provides both a data format based on Solidity ABI encoding that can easily be parsed on-chain an a new JSON-RPC call that is easily supported by existing Ethereum wallets and Web3 clients.
|
|||
|
|
|||
|
This format allows reusable off-chain Verifiable Claims to be cheaply issued to users, who can present them when needed.
|
|||
|
|
|||
|
## Prior Art
|
|||
|
Verified Identity Claims such as those proposed by [uPort](https://developer.uport.me/messages/verification) and [W3C Verifiable Claims Working Group](https://www.w3.org/2017/vc/WG/) form an important part of building up reusable identity claims.
|
|||
|
|
|||
|
[ERC-735](https://github.com/ethereum/EIPs/issues/735) and [ERC-780](https://github.com/ethereum/EIPs/issues/780) provide on-chain storage and lookups of Verifiable Claims.
|
|||
|
|
|||
|
## Specification
|
|||
|
### Claims
|
|||
|
Claims can be generalized like this:
|
|||
|
|
|||
|
> Issuer makes the claim that Subject is something or has some attribute and value.
|
|||
|
|
|||
|
Claims should be deterministic, in that the same claim signed multiple times by the same signer.
|
|||
|
|
|||
|
### Claims data structure
|
|||
|
Each claim should be typed based on its specific use case, which EIP 712 lets us do effortlessly. But there are 3 minimal attributes required of the claims structure.
|
|||
|
|
|||
|
* `subject` the subject of the claim as an `address` (who the claim is about)
|
|||
|
* `validFrom` the time in seconds encoded as a `uint256` of start of validity of claim. In most cases this would be the time of issuance, but some claims may be valid in the future or past.
|
|||
|
* `validTo` the time in seconds encoded as a `uint256` of when the validity of the claim expires. If you intend for the claim not to expire use `0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff`.
|
|||
|
|
|||
|
The basic minimal claim data structure as a Solidity struct:
|
|||
|
|
|||
|
```solidity
|
|||
|
struct [CLAIM TYPE] {
|
|||
|
address subject;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
The CLAIM TYPE is the actual name of the claim. While not required, in most cases use the taxonomy developed by [schema.org](https://schema.org/docs/full.html) which is also commonly used in other Verifiable Claims formats.
|
|||
|
|
|||
|
Example claim that issuer knows a subject:
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Know {
|
|||
|
address subject;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Presenting a Verifiable Claim
|
|||
|
#### Verifying Contract
|
|||
|
When defining Verifiable Claims formats a Verifying Contract should be created with a public `verify()` view function. This makes it very easy for other smart contracts to verify a claim correctly.
|
|||
|
|
|||
|
It also provides a convenient interface for web3 and state channel apps to verify claims securely.
|
|||
|
|
|||
|
```solidity
|
|||
|
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);
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### Calling a SmartContract function
|
|||
|
Verifiable Claims can be presented to a solidity function call as it’s struct together with the `v`, `r` and `s` signature components.
|
|||
|
|
|||
|
```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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### Embedding a Verifiable Claim in another Signed Typed Data structure
|
|||
|
The Claim struct should be embedded in another struct together with the `v`, `r` and `s` signature parameters.
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Know {
|
|||
|
address subject;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
|
|||
|
struct VerifiableReference {
|
|||
|
Know delegate;
|
|||
|
uint8 v;
|
|||
|
bytes32 r;
|
|||
|
bytes32 s;
|
|||
|
}
|
|||
|
|
|||
|
struct Introduction {
|
|||
|
address recipient;
|
|||
|
VerifiableReference issuer;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Each Verifiable Claim should be individually verified together with the parent Signed Typed Data structure.
|
|||
|
|
|||
|
Verifiable Claims issued to different EIP 712 Domains can be embedded within each other.
|
|||
|
|
|||
|
#### State Channels
|
|||
|
This proposal will not show how to use Eth Verifiable Claims as part of a specific State Channel method.
|
|||
|
|
|||
|
Any State Channel based on EIP712 should be able to include the embeddable Verifiable Claims as part of its protocol. This could be useful for exchanging private Identity Claims between the parties for regulatory reasons, while ultimately not posting them to the blockchain on conclusion of a channel.
|
|||
|
|
|||
|
### Key Delegation
|
|||
|
In most simple cases the issuer of a Claim is the signer of the data. There are cases however where signing should be delegated to an intermediary key.
|
|||
|
|
|||
|
KeyDelegation can be used to implement off chain signing for smart contract based addresses, server side key rotation as well as employee permissions in complex business use cases.
|
|||
|
|
|||
|
#### ERC1056 Signing Delegation
|
|||
|
|
|||
|
[ERC-1056](./eip-1056.md) provides a method for addresses to assign delegate signers. One of the primary use cases for this is that a smart contract can allow a key pair to sign on its behalf for a certain period. It also allows server based issuance tools to institute key rotation.
|
|||
|
|
|||
|
To support this an additional `issuer` attribute can be added to the Claim Type struct. In this case the verification code should lookup the EthereumDIDRegistry to see if the signer of the data is an allowed signing delegate for the `issuer`
|
|||
|
|
|||
|
The following is the minimal struct for a Claim containing an issuer:
|
|||
|
|
|||
|
```solidity
|
|||
|
struct [CLAIM TYPE] {
|
|||
|
address subject;
|
|||
|
address issuer;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
If the `issuer` is specified in the struct In addition to performing the standard ERC712 verification the verification code MUST also verify that the signing address is a valid `veriKey` delegate for the address specified in the issuer.
|
|||
|
|
|||
|
```solidity
|
|||
|
registry.validDelegate(issuer, 'veriKey', recoveredAddress)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
#### Embedded Delegation Proof
|
|||
|
There may be applications, in particularly where organizations want to allow delegates to issue claims about specific domains and types.
|
|||
|
|
|||
|
For this purpose instead of the `issuer` we allow a special claim to be embedded following this same format:
|
|||
|
|
|||
|
```solidity
|
|||
|
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;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Delegates should be created for specific EIP 712 Domains and not be reused across Domains.
|
|||
|
|
|||
|
Implementers of new EIP 712 Domains can add further data to the `Delegate` struct to allow finer grained application specific rules to it.
|
|||
|
|
|||
|
### Claim Types
|
|||
|
#### Binary Claims
|
|||
|
A Binary claim is something that doesn’t have a particular value. It either is issued or not.
|
|||
|
|
|||
|
Examples:
|
|||
|
* subject is a Person
|
|||
|
* subject is my owner (eg. Linking an ethereum account to an owner identity)
|
|||
|
|
|||
|
Example:
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Person {
|
|||
|
address issuer;
|
|||
|
address subject;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
This is exactly the same as the minimal claim above with the CLAIM TYPE set to [Person](https://schema.org/Person).
|
|||
|
|
|||
|
### Value Claims
|
|||
|
Value claims can be used to make a claim about the subject containing a specific readable value.
|
|||
|
|
|||
|
**WARNING**: Be very careful about using Value Claims as part of Smart Contract transactions. Identity Claims containing values could be a GDPR violation for the business or developer encouraging a user to post it to a public blockchain.
|
|||
|
|
|||
|
Examples:
|
|||
|
* subject’s name is Alice
|
|||
|
* subjects average account balance is 1234555
|
|||
|
|
|||
|
Each value should use the `value` field to indicate the value.
|
|||
|
|
|||
|
A Name Claim
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Name {
|
|||
|
address issuer;
|
|||
|
address subject;
|
|||
|
string name;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Average Balance
|
|||
|
|
|||
|
```solidity
|
|||
|
struct AverageBalance {
|
|||
|
address issuer;
|
|||
|
address subject;
|
|||
|
uint256 value;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### Hashed Claims
|
|||
|
Hashed claims can be used to make a claim about the subject containing the hash of a claim value. Hashes should use ethereum standard `keccak256` hashing function.
|
|||
|
|
|||
|
**WARNING**: Be very careful about using Hashed Claims as part of Smart Contract transactions. Identity Claims containing hashes of known values could be a GDPR violation for the business or developer encouraging a user to post it to a public blockchain.
|
|||
|
|
|||
|
Examples:
|
|||
|
- [ ] hash of subject’s name is `keccak256(“Alice Torres”)`
|
|||
|
- [ ] hash of subject’s email is `keccak256(“alice@example.com”)`
|
|||
|
|
|||
|
Each value should use the `keccak256 ` field to indicate the hashed value. Question. The choice of using this name is that we can easily add support for future algorithms as well as maybe zkSnark proofs.
|
|||
|
|
|||
|
A Name Claim
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Name {
|
|||
|
address issuer;
|
|||
|
address subject;
|
|||
|
bytes32 keccak256;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
Email Claim
|
|||
|
|
|||
|
```solidity
|
|||
|
struct Email {
|
|||
|
address issuer;
|
|||
|
address subject;
|
|||
|
bytes32 keccak256;
|
|||
|
uint256 validFrom;
|
|||
|
uint256 validTo;
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
### EIP 712 Domain
|
|||
|
The EIP 712 Domain specifies what kind of message that is to be signed and is used to differentiate between signed data types. The content MUST contain the following:
|
|||
|
|
|||
|
```solidity
|
|||
|
{
|
|||
|
name: "EIP1???Claim",
|
|||
|
version: 1,
|
|||
|
chainId: 1, // for mainnet
|
|||
|
verifyingContract: 0x // TBD
|
|||
|
salt: ...
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
#### Full Combined format for EIP 712 signing:
|
|||
|
|
|||
|
Following the EIP 712 standard we can combine the Claim Type with the EIP 712 Domain and the claim itself (in the `message`) attribute.
|
|||
|
|
|||
|
Eg:
|
|||
|
```solidity
|
|||
|
{
|
|||
|
"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"
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
### Revocation
|
|||
|
Both Issuers and Subjects should be allowed to revoke Verifiable Claims. Revocations can be handled through a simple on-chain registry.
|
|||
|
|
|||
|
The ultimate rules of who should be able to revoke a claim is determined by the Verifying contract.
|
|||
|
|
|||
|
The `digest` used for revocation is the EIP712 Signed Typed Data digest.
|
|||
|
|
|||
|
```solidity
|
|||
|
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;
|
|||
|
}
|
|||
|
}
|
|||
|
```
|
|||
|
|
|||
|
A verifying contract can query the Revocation Registry as such:
|
|||
|
|
|||
|
```solidity
|
|||
|
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");
|
|||
|
```
|
|||
|
|
|||
|
### Creation of Verifiable Claims Domains
|
|||
|
|
|||
|
Creating specific is Verifiable Claims Domains is out of the scope of this EIP. The Example Code has a few examples.
|
|||
|
|
|||
|
EIP’s or another process could be used to standardize specific important Domains that are universally useful across the Ethereum world.
|
|||
|
|
|||
|
## Rationale
|
|||
|
Signed Typed Data provides a strong foundation for Verifiable Claims that can be used in many different kinds of applications built on both Layer 1 and Layer 2 of Ethereum.
|
|||
|
|
|||
|
### Rationale for using not using a single EIP 712 Domain
|
|||
|
EIP712 supports complex types and domains in itself, that we believe are perfect building blocks for building Verifiable Claims for specific purposes.
|
|||
|
|
|||
|
The Type and Domain of a Claim is itself an important part of a claim and ensures that Verifiable Claims are used for the specific purposes required and not misused.
|
|||
|
|
|||
|
EIP712 Domains also allow rapid experimentation, allowing taxonomies to be built up by the community.
|
|||
|
|
|||
|
## Test Cases
|
|||
|
There is a repo with a few example verifiers and consuming smart contracts written in Solidity:
|
|||
|
|
|||
|
**Example Verifiers**
|
|||
|
* [Verifier for very simple IdVerification Verifiable Claims containing minimal Personal Data](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/IdentityClaimsVerifier.sol)
|
|||
|
* [Verifier for OwnershipProofs signed by a users wallet](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/OwnershipProofVerifier.sol)
|
|||
|
|
|||
|
**Example Smart Contracts**
|
|||
|
* [KYCCoin.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/KYCCoin.sol) - Example Token allows reusable IdVerification claims issued by trusted verifiers and users to whitelist their own addresses using OwnershipProofs
|
|||
|
* [ConsortiumAgreement.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/ConsortiumAgreements.sol) - Example Consortium Agreement smart contract. Consortium Members can issue Delegated Claims to employees or servers to interact on their behalf.
|
|||
|
|
|||
|
**Shared Registries**
|
|||
|
* [RevocationRegistry.sol](https://github.com/uport-project/eip712-claims-experiments/blob/master/contracts/RevocationRegistry.sol)
|
|||
|
|
|||
|
## Copyright
|
|||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|