Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC 939: Identity Claim Holder #939

Closed
Agusx1211 opened this issue Mar 20, 2018 · 19 comments
Closed

ERC 939: Identity Claim Holder #939

Agusx1211 opened this issue Mar 20, 2018 · 19 comments
Labels

Comments

@Agusx1211
Copy link
Contributor

Agusx1211 commented Mar 20, 2018

EIP: <to be assigned>
Title: ERC-939 Identity Claim Holder
Author: Agustin Esteban Aguilar <agustin@ripiocredit.network>
Type: Standard
Category: ERC
Status: Discussion
Created: 2018-03-20

Abstract

The following describes standard functions for adding claims about a subject; a contract or an EOA could make these claims.

Motivation

This standard aims to allow dapps and networks to connect and exchange information using claims. These claims are generic and could be used to communicate different types of data.

Using a bytes field for the data parameter allows the claims to hold complex statements.

Rationale

The idea of a registry of claims it is not new to the community, this approach has the following differences:

Multiple claims by type

An issuer could emit multiple claims of the same type to the same subject; this is useful to make the model even more flexible.

For example, a payment processor could issue a claim every time a subject request a payment, this claim will always contain the same subject, issuer, and type.

Everything is a claim!

There is no method to reject, modify or remove claims, that operations should be implemented as further claims.

This allows to have a more flexible platform and keeps the history of what claims existed in the past.

What is an identity?

In this standard claims are not necessarily done against an ethereum address; a virtual identity could be created at any time and with any scheme.

These virtual identities could only interact with claims; for example, an identity could be built on top of a passport number of an email address. This identity "keys" are outside the realm of the EVM, but could still exist and represent users.

Definition

issuer: Another contract or EOA that generates the claim.

subject: Identity who receives the claim, a bytes32 field allows it to be an ethereum address or an identifier.

claimType: The type of the claim defines how the data should be interpreted, it has the following structure: keccak256(string prefix + string key).

timestamp: Unix time of when the contract saw the claim for the first time.

data: Bytes data of the claim, the claimType field defines the structure.

Claim

The claims issued to the identity

struct Claim {
    bytes32 subject;
    bytes32 claimType;
    address issuer;
    uint64 timestamp;
    bytes data;
}

addClaim

Creates a new claim directly on-chain, the msg.sender becomes the issuer.

function addClaim(bytes32 subject, bytes32 claimType, bytes data) public returns (uint256 index);

validateClaim (signed claims)

Validates a given claim signed by an issuer and saves it.

It allows transferring the cost of making claims to the beneficiary of the claim itself, allowing the issuer to make it without any fee.

It should be kept in mind that this claim does not have a fixed timestamp, the timestamp saved is the first time that the claim is validated.

function validateClaims(bytes32[] subjects, bytes32[] claimTypes, bytes[] data, uint8 v, bytes32 r, bytes32 s) returns (bool)

To generate a signed claim, the issuer should sign the output of one of the following formulas

Single claim

keccak256(bytes32 subject, bytes32 claimType, bytes data);

getClaim

Allows to retrieve a claim using the identity and index of the claim, it returns the hash of the claim.

function getClaim(bytes32 subject, uint256 index) public view returns (bytes32 claimHash);

Allows to retrieve a claim using the unique hash of that claim, the one is calculated with this formula: keccak256(bytes32 subject, address issuer, bytes32 claimType, bytes32 dataHash)

function getClaim(bytes32 claimHash) public view returns (address subject, address issuer, bytes32 claimType, uint64 timestamp, bytes32 dataHash);

claimsOf

Return the number of claims done about a given subject.

function claimsOf(bytes32 subject) public view returns (uint256 number);

findClaims

Search all the claims with the given parameters.

If any of the parameters is 0x0, it should be interpreted as "all".

This is an expensive gas method that it should not be called from another contract.

function findClaims(bytes32 subject, address issuer, bytes32 claimType) public view returns (bytes32[] claims);

readData

Allows to read the data field of a claim

function readData(bytes32 claimHash) public view returns (bytes data);

Allows to read the data field of a claim from a smart contract, it takes padding to read the bytes array using bytes32 words. The padding 0 should return the length of the data field.

function readData(bytes32 claimHash, uint256 padding) public view returns (bytes32);

Standard claim types

Subject ownership

Claims that the issuer is the owner of the subject.

bytes32 public constant TYPE_SUBJECT_OWNERSHIP = keccak256("pub_subject_ownership");

The data field should be empty.

Retracted claim

Aims to "remove" a previous claim, the subject and the issuer should be the same as the claim to remove.

bytes32 public constant TYPE_RETRACTED_CLAIM = keccak256("pub_retract_claim");

The data field contains the hash of the removed claim in the first position.

Standard claim prefixes

pub_: The data field should contain readable public information, for example, an encoded email or a full name.

priv_: The data field should contain the hash of the information of the claim; this is intended for sensible information. For example a physical address or a phone number.

TODO: Add more standard claim types

Events

claimAdded

Should be triggered when a claim is saved in the contract.

event ClaimAdded(bytes32 subject, bytes32 issuer, bytes32 claimType, bytes data);

Solidity Interface

pragma solidity ^0.4.19;

contract ERC939 {
    bytes32 public constant TYPE_SUBJECT_OWNERSHIP = keccak256("pub_subject_ownership");
    bytes32 public constant TYPE_RETRACTED_CLAIM = keccak256("pub_retract_claim");

    event ClaimAdded(bytes32 subject, bytes32 issuer, bytes32 claimType, bytes data);

    struct Claim {
        bytes32 subject;
        bytes32 claimType;
        address issuer;
        uint64 timestamp;
        bytes data;
    }

    function addClaim(bytes32 subject, bytes32 claimType, bytes data) public returns (uint256 index);
    function validateClaim(bytes32 subject, bytes32 claimType, bytes data, uint8 v, bytes32 r, bytes32 s) public returns (bool);
    function getClaim(bytes32 subject, uint256 index) public view returns (bytes32 claimHash);
    function getClaim(bytes32 claimHash) public view returns (address subject, address issuer, bytes32 claimType, uint64 timestamp, bytes32 dataHash);
    function readData(bytes32 claimHash) public view returns (bytes data);
    function readData(bytes32 claimHash, uint256 padding) public view returns (bytes32);
    function claimsOf(bytes32 subject) public view returns (uint256 number);
    function findClaims(bytes32 subject, address issuer, bytes32 claimType) public view returns (bytes32[] claims);
}
@c0chi
Copy link

c0chi commented Mar 21, 2018

Good point, @Agusx1211! I can imagine so many uses for what you are proposing.

You could consider 2 additional optional arguments for ClaimOf, in order to get the count of a certain [type of claim] that occurred after a [given date] (0x0 interpreted as "all”).

I mean, I think in certain cases it's more useful to know if an identity has had "bad claims" (eg: "defaulted debts", "identity theft", etc.) or "good claims" (eg: "verified passport"; " completed course ", etc).

In some cases, it could be interesting to know if a certain type of claim have “ever” occurred (e.g.: ”identity theft”), in other cases only the claims that have occurred "lately" (e.g.: ”defaults occurred in the last 24 months”).

It’s just a suggestion, of course there is always the possibility of using findClaims to get all the data.
Great work!

@Agusx1211
Copy link
Contributor Author

Agusx1211 commented Mar 22, 2018

The claims don't mean anything without knowing who is the issuer, it will be a bad practice for a contract to check only if an address has "bad claims" because that "bad claims" could be put there by anyone; so that call wouldn't return meaningful information.

But is not a bad idea to be able to check if an issuer did make claims on a subject in the past, the search should be gas efficient. Maybe we could keep track of the last claim of a subject from an issuer (with its corresponding type).

@c0chi
Copy link

c0chi commented Mar 24, 2018

I agree. However, I think the biggest gain of what I mentioned is efficiency when identities have any or don't have at all claims of certain types. In some of these cases, you don't need to verify anything else.
But I agree, since anyone can issue a claim and there is no "official" list of issuers, it is always necessary to verify who has made the claim

@SergioDemianLerner
Copy link
Contributor

  1. Does the expression:
    (bytes32[] subjects, bytes32[] claimTypes, bytes[] data)
    has an unique concatenation for
    keccak256(bytes32[] subjects, bytes32[] claimTypes, bytes[] data) ?

I remember Solidity keccak256() does concatenate all fields without separators or prefixed lengths.

If I'm correct,then the signature would also sign alternante versions of the claims where some values are moved between fields. Transaction front-running could be used to claim these alternate versions.

  1. I would like to have a more concrete examples of the use cases.

@Agusx1211
Copy link
Contributor Author

  1. Does the expression:
    (bytes32[] subjects, bytes32[] claimTypes, bytes[] data)
    has an unique concatenation for
    keccak256(bytes32[] subjects, bytes32[] claimTypes, bytes[] data) ?

The concatenation is wrong; It could be fixed adding a separator between the parameters of keccak256, maybe the length of the arrays. But this will only work for the first two inputs, bytes[] data it's a two-dimensional array, so the bytes could be re-arranged and still we would get the same hash.

Also, we have the limitation of Solidity not supporting multi-dimensional inputs in functions; this forces us to use the experimental ABI Encoder (not ready for production) or create our custom encoding for the data.

I don't see a clean fix for this; we could store the length of every data entry on a different array, receive the data field as flat bytes and then split it using the length of every entry, this would be gas heavy, obscure and a kind of a hack.

Maybe we should leave that feature out, at least until Solidity has better support for multi-dimensional arrays.

  1. I would like to have a more concrete examples of the use cases.

The use case, at least from our perspective, is to have a dynamic way to exchange identity and scoring information.

This kind of data varies between countries, companies, and use-cases; some companies could validate only emails, others proof of residency, passport, biometric data, social profiles, SSNs, etc. Each type of validation should have a claim type associated.

Entities and apps on the network could search on the claim registry for all the claims that a subject has and with that build a profile. They could also emit claims if they have something to say about that profile.

@Agusx1211 Agusx1211 changed the title ERC: Identity Claim Holder ERC 939: Identity Claim Holder Mar 27, 2018
@gatra
Copy link

gatra commented Mar 28, 2018

Hi!

You have a typo here:
function findClaims(bytes32 sucject, address issuer, bytes32 claimType) public view returns (bytes32[] claims);

the first parameter should be "subject" instead of "sucject"

@gatra
Copy link

gatra commented Mar 28, 2018

just a comment: the interface seems sufficient, but using this interface it could be cumbersome to know if a claim was retracted or not. You would need to get all retractions for the subject (from the issuer) and then filter.
Similar thing if I wanted to get only un-retracted claims.
Do I make sense? What other claim types did you have in mind?

btw, I like the idea of validateClaim. I wish ERC-20 had something like that...

@MrTibbles
Copy link

@Agusx1211 can you please explain the motivation behind this proposal over #735 ? Sure, refinement can be put toward both proposals, but unsure of reasoning behind a new rival proposal. I have been waiting for someone to pose this question, but no one has done so, which i fear means i am missing something in regards to the crucial differing factor between this and #735. 🤷‍♂️

@Agusx1211
Copy link
Contributor Author

@gatra Currently, the only way of knowing that a claim was retracted is to search trow all the subject claims. This search is not possible for a smart contract and may be a limitation.

To allow claims to be deleted we should evaluate the pros and cons, and also define how to handle the signed claims (validateClaim).

The pros I see is that claims are permanent, in that is not the case, an issuer could emit a claim, use that claim to prove something and then delete it to mitigate the risk of making non-true claims

That is not healthy for the network and makes tricky to reference a claim from another contract because that reference could become invalid at any moment, maybe that could be fixed by not deleting it but just setting a flag.

Another approach to knowing if a claim is valid is to look at the timestamp; for some claim types would be a good idea to take into account if it was recent or not, for example, a proof of address should be recent to be valid, a proof of email not.

@MrTibbles The key factor between this and #735 is that we unlink the identity of the subject from an ethereum address, in a sense is more a rival of #780, because it defines the subject of the claim in the claim itself.

What we found working in RCN is that a lot of times we cannot rely on the final user having a private key to hold his identity, so we must generate a new identifier and build an abstract identity on that.

Another main difference is that here the claims are not limited to one per address + type, this is a huge limitation when you want to make claims that a given identity did something multiple times, for example, requested credit, paid a debt, made a payment, etc.

@MrTibbles
Copy link

@Agusx1211 i see, thanks. I can understand the source of motivation to handle subjects not having an EOA, ie. less friction for on-boarding. But does this not mean that the issuer and subject must have a trusted relationship? If the issuer is signing claims on behalf of the subject it could lead to misrepresentation, this could be seen as a red flag in a trustless environment.

In order for a disgruntled subject to assert a previously made claim about themselves was not accurate they would have to create an EOA, add a claim (TYPE_RETRACTED_CLAIM, i think) and then validate it? However, would that be wasted effort, considering the below?

the subject and the issuer should be the same as the claim to remove.

@Agusx1211
Copy link
Contributor Author

Agusx1211 commented Apr 5, 2018

@MrTibbles Any entity can issue claims about any other subject. The validity of that claim does not have to do with the trust between the issuer and the subjet, but between the trust of the entity who is using that claim. The "reputation" of the issuer is in the stake.

The subject should not be able to remove claims made on itself, that feature would allow a subject to delete bad claims about them, making the system invalid for identity proof or credit scoring...

A Subject could issue a claim with the type TYPE_RETRACTED_CLAIM, but that would create a situation like "your word against mine," making the dispute pointless; maybe the subject could add a proof in the data.

@3esmit
Copy link
Contributor

3esmit commented Apr 9, 2018

There is no method to reject, modify or remove claims, that operations should be implemented as further claims.

I fairly disagree with this: we should work our contracts to use less resource possible, bloating state is not a good idea, especially when storage of information at Casper would be paid yearly, so keeping all this state in identity would become costy as more its used.
You can keep record of transactions if you want to rebuild history.

@MrTibbles
Copy link

@Agusx1211

What we found working in RCN is that a lot of times we cannot rely on the final user having a private key to hold his identity, so we must generate a new identifier and build an abstract identity on that.

The validity of that claim does not have to do with the trust between the issuer and the subjet, but between the trust of the entity who is using that claim. The "reputation" of the issuer is in the stake.

So the end user/subject does not control the private key/the means for signing? That sounds trust-based.

The subject should not be able to remove claims made on itself, that feature would allow a subject to delete bad claims about them, making the system invalid for identity proof or credit scoring...

I know, i agree, i did not suggest otherwise.

create a situation like "your word against mine," making the dispute pointless

Is that not the whole point of a claims system? Web-of-trust systems and staking mechanics come into play in order to surface the most commonly accepted claim.

@Agusx1211
Copy link
Contributor Author

@3esmit We could not store the claims or add to them an expiration date, but I don't see how a "remove claim" function would mitigate the bloating problem.

On a side note, lots of projects are "bloating" the state, with the current version of Ethereum this is not a problem, the gas is only paid at the allocation of the storage.

IF the Casper update ends up adding rent, that will change the whole ecosystem, not only this claim standard but also practically all the ERC721 and ERC20 tokens, I wouldn't think this standard with optimizations against the rest of the ecosystem.

@MrTibbles Yes, it is a trust-based system, maybe a dispute claim would make sense if the issuer of the disputed claim is also trusted. In that scenario is up to the "reader" of the claims to decide which one is valid.

@3esmit
Copy link
Contributor

3esmit commented Apr 19, 2018

@Agusx1211 Good luck bloating your contracts when storage fee gets implemented in mainnet ;)

@frozeman
Copy link
Contributor

@Agusx1211 the proposal looks the same like #735 as @MrTibbles mentioned. I see the point of having claims which would not need to go through an approval process. But 735 could also allow that, as its not enforced that it must be approved. I would like to put work on getting the standard for claims right once, even if you create a sub standard later, but at least know how to interact with those. So joining efforts makes more sense imo.

@bitcoinbrisbane
Copy link
Contributor

I've looked at this and 735 too. Why don't we have a field for expiry date? Claim types like passport the document has an expiry. A method hasExpired() could then assert if the document has etc.

@github-actions
Copy link

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Dec 18, 2021
@github-actions
Copy link

github-actions bot commented Jan 1, 2022

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

@github-actions github-actions bot closed this as completed Jan 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants