ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.8k stars 5.22k forks source link

ERC: Claim Holder #735

Closed frozeman closed 2 years ago

frozeman commented 6 years ago
EIP: 735
Title: Claim Holder
Author: Fabian Vogelsteller (@frozeman)
Type: Standard
Category: ERC
Status: Discussion
Created: 2017-10-09

NOTE: Due to the changes in ERC725, this spec is not fully compatible with the current ERC725v2. If you're interested in adopting this spec to work with 725v2, please comment below, or send a gist with changes.

Abstract

The following describes standard functions for adding, removing and holding of claims. These claims can attested from third parties (issuers) or self attested.

Motivation

This standardised claim holder interface will allow Dapps and smart contracts to check the claims about a claim holder. Trust is here transfered to the issuers of claims.

Definitions

Specification

Claim Holder

claim structure

The claims issued to the identity. Returns the claim properties.

struct Claim {
    uint256 topic;
    uint256 scheme;
    address issuer; // msg.sender
    bytes signature; // this.address + topic + data
    bytes data;
    string uri;
}

getClaim

Returns a claim by ID.

function getClaim(bytes32 _claimId) constant returns(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri);

getClaimIdsByTopic

Returns an array of claim IDs by topic.

function getClaimIdsByTopic(uint256 _topic) constant returns(bytes32[] claimIds);

addClaim

Requests the ADDITION or the CHANGE of a claim from an issuer. Claims can requested to be added by anybody, including the claim holder itself (self issued).

_signature is a signed message of the following structure: keccak256(address identityHolder_address, uint256 topic, bytes data).

Claim IDs are generated using keccak256(address issuer_address + uint256 topic).

This COULD implement an approval process for pending claims, or add them right away.

Possible claim topics:

(TODO: add more in the initial standard? 3: Claim registry?)

Returns claimRequestId: COULD be send to the approve function, to approve or reject this claim.

Triggers if the claim is new Event and approval process exists: ClaimRequested Triggers if the claim is new Event and is added: ClaimAdded Triggers if the claim index existed Event: ClaimChanged

function addClaim(uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) returns (uint256 claimRequestId)

removeClaim

Removes a claim. Can only be removed by the claim issuer, or the claim holder itself.

Triggers Event: ClaimRemoved

function removeClaim(bytes32 _claimId) returns (bool success)

Events

ClaimRequested

COULD be triggered when addClaim was successfully called.

event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri)

ClaimAdded

MUST be triggered when a claim was successfully added.

event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri))

ClaimRemoved

MUST be triggered when removeClaim was successfully called.

event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri))

ClaimChanged

MUST be triggered when changeClaim was successfully called.

event ClaimChanged(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri)

Solidity Interface

pragma solidity ^0.4.18;

contract ERC735 {

    event ClaimRequested(uint256 indexed claimRequestId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimAdded(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimRemoved(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);
    event ClaimChanged(bytes32 indexed claimId, uint256 indexed topic, uint256 scheme, address indexed issuer, bytes signature, bytes data, string uri);

    struct Claim {
        uint256 topic;
        uint256 scheme;
        address issuer; // msg.sender
        bytes signature; // this.address + topic + data
        bytes data;
        string uri;
    }

    function getClaim(bytes32 _claimId) public constant returns(uint256 topic, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
    function getClaimIdsByTopic(uint256 _ topic) public constant returns(bytes32[] claimIds);
    function addClaim(uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (uint256 claimRequestId);
    changeClaim(bytes32 _claimId, uint256 _topic, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) returns (bool success);
    function removeClaim(bytes32 _claimId) public returns (bool success);
}

Constraints

Additional References

frozeman commented 6 years ago

CHANGE: I renamed claimType to topic, to be more precise. Implementations should change that accordingly.

@svenstucki Thanks for your comments. The signature does contain the claim holder (formerly named "subject") already, so that claims are subject specific.

@tbocek concerning auto accepting self-claims, this depends. If an attacker overtakes one of your keys he shouldn't be able to add self-claims right away, so going through the approval function might still be important here.

@adriamb Im not sure how a validity would help with validity checking? Im also not clear about the proof-of-possession, could you elaborate? The idea is that the signature contains the data that is necessary to check the validity of a claim, and as you will extract the key from the signature, there is not need to add that signing key specifically to the claim itself. You would go to the issuer identity contract and check for the key, that your retrieved from the signature.

@cbruguera thats a good point. Blockchains have timestamps basically build in, so you can already know when the claim was added, though you can't check that directly from within a smart contract.

@SebastienGllmt The idea behind that is that claimIDs are deterministic, meaning you can calculate in beforehand whats the claim ID of the claim you want. Its true that this allows only for one claimID per topic and issuer. If you have a good idea how we can get around that, without loosing the predicability, to easily retrieve claims let me know :)

@JosefJ concerning the implementation: Yes keys can be anything so only checking for msg.sender wouldn't really work. This Implementation was probably done before we add the more complex key structure. Though in this example its definitely a bit restrictive, as it assumed that people will only add claims with its claim signer keys, which wont be the case, as the claim issue is what adds the claim, which in return holds a claim signer key. So the claimSignerOnly check would need to be removed.

concerning the rest of your comment: yes that actually how it is meant. You receive the key from the signature and check if that key is present on the issuer identity contract.

Thanks for the findings in the interface.

@richard-ramos can you point me to the place where its using uint256? As far as i see its using bytes32 everywhere.

@computerphysicslab The claimRequest id is a number that is given to track the claim adding process using an approval process. The reason why claims are not added by default, is that a claim on your identity should be approved by you. Otherwise anybody can make claims about anybody else, and make it look like its agreed by you. This is different for identity contracts that are not people. Here the claim can be added right aways thats why it says This COULD implement an approval process for pending claims, or add them right away. and not MUST :)

@everyone, how comfortable do you guys feel on adding a list of claim topic numbers already, so that current implementations have some dough to play with?

Im thinking here about number ranges, to allow future "sub topics" to be added as well. e.g. 1000-2000 could be residence types, where 1000 is "main residence", etc.

We could also say topic 0 means the version of the topic list used, so that every identity is encouraged to add a self claim as first claim stating the topic list version used for this identity?

tyleryasaka commented 6 years ago

@frozeman I like the idea of standardizing topic numbers (at least to some degree) sooner rather than later. It will provide us useful guidance for our implementation at @originprotocol. Right now we're just using arbitrary numbers which will most likely not be compatible with others' implementations.

And the claimType -> topic rename makes a lot of sense. We'll be updating our implementation accordingly.

mirceapasoi commented 6 years ago

@frozeman Your top-level description still mentions getClaimIdsByType, I'm guessing it should be getClaimIdsByTopic?

NEO2756 commented 6 years ago

addClaim: This SHOULD create a pending claim, which SHOULD to be approved or rejected by n of m approve calls from keys of purpose 1. @frozeman If we need to accommodate this change, how would someone come to know that we are in approve function for the execution of any pending transaction OR its approval for claim addition? OR I am going totally in wrong direction.

m-schmoock commented 6 years ago

@NEO2756 since you mentioned addClaim: so when we have a pending addClaim execution request, is it correct that fronend has to parse the the function call (function signature hash and parameter encoding) for that pending execution request? For security reasons this seems plausible to me, but its very clumsy to implement (imho).

NEO2756 commented 6 years ago

@m-schmoock If we have to adhere to draft and implement n of m approval scheme, I think we need to do this. we only emit event for frontend (executionId) for further required approval(s) against executionId.

frozeman commented 6 years ago

Concerning the addClaim, as far as is understand the spec conventions SHOULD means can, but not must, MUST means must, and COULD means totally optional.

So if its 1 out of 1, the execution could go through immediately, but should still fire the events.

Im not understanding why you want to parse the function hash?

Concerning the topic numbers: http://schema.org/Person could be a good start reference and we sign numbers or number ranges for such. e.g. memberOf could be range 5000000, meaning we have 5M possible membership topics.

@mirceapasoi thanks for the catch, i changed it

m-schmoock commented 6 years ago

@frozeman @NEO2756

anybody can add an execution request for a function that is implemented by the ClaimHolder imlpementing contract, right? This includes: addClaim (obviously), but also removeClaim, possibly malicious encoded event execution (Im not yet sure about this one), possibly functions that are offered by this specific (bad/extensive) implementation of the ERC735 contract, and maybe other attack vectors. As per design "execution requests" may be used to execute modified/malicious code on behalf of the user.

Even if it would only be addClaim and removeClaim, one would still have to parse the execution request signature in order to show the correct GUI dialog, since add and remove are two functions with a different expected outcome for the user.

When looking at out in the wild test implementations (i.e. Origin Protocal - https://github.com/OriginProtocol/identity-playground/blob/master/contracts/ClaimHolder.sol ) we see that also an "addKey(...)" could be called as they implement erc735 and KeyHolder (725) in the same contract.

Hopefully this is just a stupid misunderstanding by me...

frozeman commented 6 years ago

I am not sure about the origin implementation. The idea is that the execution request is only fired if the executed function is called. For addClaim it should fire the respective event, which then would make the user call the improve function, with the right ID. There might be some confusion here when execute is used On 29. Jun 2018, 12:

NEO2756 commented 6 years ago

@m-schmoock @frozeman I am also not aware of origin implementation. But I don't want anybody to add claim to my identity without my permission/approval. So i recommend to have MUST in case claim issuer is not MANAGEMENT key holder (i.e. no approval required for self claim) and approval process (via claimRequested event ) for any other execution for claim addition.

bitcoinbrisbane commented 5 years ago

Id like to see the struct have an Uint256 for expire date time. Most documents have an expiration date.

aihuawu commented 5 years ago

for scheme rsa, should the public key be attached along with signature?

aihuawu commented 5 years ago

topic number should be standardised. otherwise every one just use its own numbers. the claim is not portable.

nathanawmk commented 5 years ago

@realcodywburns @andrewrd the dispute claim is IMO not necessary. This standard is a shell for your own identity. First of you don't want to have every buy transaction references with your real world identity INSIDE you identity smart contract, that would be a huge privacy issue. And second there can be many reputation systems referencing this identity, this doesn't mean, every step needs to be a claim added to your identity.

Technically everybody can issuer a claim and post it anywhere (e.g. a reputation system, or facebook, twitter, etc) You can't defend yourself from that anyway (and shouldn't, as it might be legit). Allowing a claim on YOUR identity means, YOU approve it. Again don't get me wrong, reputation systems should exists, which allow sellers to verify the credibility of a buyer, but this is outside of this standard, and might just happen in a peer2peer way by sending over reputation claims you collected over time, or referencing your identity AND a reputation system you are using (These two don't have to be publicly connected, as long as you can proof owner ship of both)

@MikeD123 I think you get it a bit wrong. The idea of claimTypes is more of the nature of properties of you, e.g. 1: biometric data will obviously means your a person and not a business. 2. address will show that you are based somewhere, means you have a physical address or reference point. We could add claim types for certain social media, but thats up to discussion.

If you want to verify a claim, you most likely will not look for the type, but for a specific issuer you trust, so you can generate the ID of the claim in advance e.g.:

keccak256(address issuer_address + uint256 _claimType) -> keccak256('0x123456789...' + 1)

will generate you the hash of the claim you can get using getClaim(bytes32 _Id). If the claim doesn't exist, you either look for another, or you have to manually verify that person, or not at all. If the claim exists, you verify the signature inside the claim, and see if that recovered key is still hold by the claim issuer.

As over time, there will be trusted claim issuer, like the US gov, or some decentralised service, people can always know the index of the claims they want to retrieve and even hardcode that in their smart contracts.

On the end in most cases we don't care about the data itself, but that somebody verified it.

Concerning the subTypes. I wouldn't go so detailed, as then people can know why sub type of claim was changed when, which could be a privacy leak. For some data it might make sense to give them a separate type, but for example for biometric data, just that might be enough.

To auto verify a person in front of you, the claim issuer for biometric data needs to have some bio matching services, or zero knowledge proof smart contracts, which can take some takes biometric data, and match it against their data set, and return true or false, should the data match the person claim data reference you gave them.

@buendiadas concerning the URI, it should contain the protocol like ipfs://..., or bzz:// or https://

@frozeman I am looking to engineer secure enclaves (keystone enclave perhaps?) + zkp for attestations at scale. Is this something that you are looking at as well?

Nathan Aw

ivica7 commented 5 years ago

It doesn‘t look intuitive to me that someone else is claiming something about subject’s identity. Isn’t it more natural that the subject states claims about itself and trustworthy endorsers are confirming these claims. Self issued claim would be a claim that has no endorsers. I also think for standardization purposes that it would be better to have separate registry contracts for seprate claim topics. This way the contract’s address is the topic.

guix77 commented 5 years ago

Concerning the topic numbers: http://schema.org/Person could be a good start reference

I like this proposal, but how could be map it exactly ?

Proposal 1

Mapping Schema.org > People => topic in the order. First property on the page is additionalName so = topic 1 and so on. But what if properties get added to Schema.org > People? And additionalName = topic 1 makes no sense.

Proposal 2

We define here a "manual" list for Schema properties that makes sense, like familyName = topic 1, givenName = topic 2, ... Maybe we can find another referential that already has mapped properties to integers, or that is easier to map.

Proposal 3

I like the Schema.org > People reference and I would prefer the mapping to be deterministic. Maybe we could just convert the schema.org property from ASCII to integer.

Example: http://schema.org/Person, givenName property

Storing it from UI to BC

This approach would allow (2^256 - 1) / 3 = about 10^25 triplets = up to 25 characters for properties.

Some properties topic IDs with this conversion:

For properties that have their first character ASCII code beginning with 0, we'll just remove the first zero:

This approach could add many other properties of other schemas, the only limits being:

The other problem with this approach is that it would consume a lot of topics ID distributed more or less randomly according to ASCII to integer conversion and that we have no more ranges for other custom topics not on Schema.org.

One solution could be to treat all those custom topics like if they were in Schema.org with a first ASCII character encoded in integer on 3 decimals but that is not on the alphabet, like for instance | (124).

Example implementation: https://github.com/guix77/erc735example

Example uses a helper module to convert property names from ASCII to BigNumber, and from BigNumber to ASCII: https://github.com/guix77/erc735js

frozeman commented 5 years ago

In light of ERC 725 v2, this standard needs a bit of re-work, as now it can sit separate from the ERC725 proxy account, in its own contract.

We would now need to manage the approver different, as well as probably add a subject to the claims. It could also be transformed into a standard claim registry like #780, but with more a sophisticated claim structure.

bitcoinbrisbane commented 5 years ago

Seems to have a missing function before changeClaim method on the Solidity Interface example code.

bitcoinwarrior1 commented 5 years ago

Hi All, I like what this EIP is trying to achieve but think it could be better implemented. I discussed the idea of using a salted merkle tree here and I think it is more sensible because it preserves privacy and enables link-ability to the source e.g. drivers license -> DOB -> above 21.

m1cm1c commented 4 years ago

Why does addClaim() return a uint256? Everywhere else, claim IDs are of type bytes32.

Does the plus symbol in keccak256(address issuer_address + uint256 topic) for generation of claim IDs symbolize concatenation or addition? If it symbolizes concatenation, that could be made more explicit. If it symbolizes addition, that's a bad idea to begin with. It might not be obvious to people implementing the interface that they need to check whether the resulting ID is already in use by a different issuer address.

m1cm1c commented 4 years ago

If an identity contract, it should hold the key with which the above message was signed

Does that mean that the identity contract's owner should be the address used for signature verification? If not: How is the address / the public key specified?

m1cm1c commented 4 years ago

I think that if the identity contract does not "hold the key with which the above message was signed" (whatever this means) anymore, rendering the claim invalid, anyone should be able to remove it. Not just the subject or the issuer. Maybe the standard should even specify a preferred mechanism for removing such claim automatically.

drgorb commented 4 years ago

a claim should only be removed by the subject (the identity holder) because only they can decide what should be forgotten about them.

drgorb commented 4 years ago

I would prefer to have an addClaim and a separate updateClaim function. It reduces the potential for confusion and error. When addClaim is called for an existing claim, the transaction is reverted. Same thing for an updateClaim on a missing claim.

drgorb commented 4 years ago

I believe it is necessary to separate the revocation of claims from their update. While a revocation is a special kind of update, it MUST be available to both the subject and the issuer WITHOUT a permission. It MUST NOT be possible for the subject to prevent the revocation of a claim.

Arguably it is the only permissible update to a claim. If a claim is not current anymore, it should be removed and a new one should be added. The claimId MUST be unique. Indeed, if applications can trust that a claim can not changed (except for early revocation) they will not need to verify the claim each time it is read.

m1cm1c commented 4 years ago

a claim should only be removed by the subject (the identity holder) because only they can decide what should be forgotten about them.

A claim's issuer should have the authority to retract his assessment. Besides: Your premise is flawed. The blockchain does not forget.

I would prefer to have an addClaim and a separate updateClaim function. It reduces the potential for confusion and error. When addClaim is called for an existing claim, the transaction is reverted. Same thing for an updateClaim on a missing claim.

addClaim() can do a perfectly good job at updating a claim. Your idea merely makes the interface fatter.

I believe it is necessary to separate the revocation of claims from their update. While a revocation is a special kind of update, it MUST be available to both the subject and the issuer WITHOUT a permission. It MUST NOT be possible for the subject to prevent the revocation of a claim.

Didn't you just say that only the subject should be able to remove a claim?

Arguably it is the only permissible update to a claim. If a claim is not current anymore, it should be removed and a new one should be added. The claimId MUST be unique. Indeed, if applications can trust that a claim can not changed (except for early revocation) they will not need to verify the claim each time it is read.

Couldn't you just store a hash for that purpose?

drgorb commented 4 years ago

A claim's issuer should have the authority to retract his assessment. Besides: Your premise is flawed. The blockchain does not forget.

retracting the assessment is what revocation is for. A deletion makes the claim disappear from the current state.

I believe it is necessary to separate the revocation of claims from their update. While a revocation is a special kind of update, it MUST be available to both the subject and the issuer WITHOUT a permission. It MUST NOT be possible for the subject to prevent the revocation of a claim.

Didn't you just say that only the subject should be able to remove a claim?

revoking and removing are two very different things. A removed claim will not be returned by getClaim(claimId) whereas a revoked claim will.

Arguably it is the only permissible update to a claim. If a claim is not current anymore, it should be removed and a new one should be added. The claimId MUST be unique. Indeed, if applications can trust that a claim can not changed (except for early revocation) they will not need to verify the claim each time it is read.

Couldn't you just store a hash for that purpose?

Of course you can create workarounds. What is your argument for updating a claim instead of creating a new one?

addClaim() can do a perfectly good job at updating a claim. Your idea merely makes the interface fatter.

there are a few arguments for making the interface fatter:

  1. it should be clear from its name what a function does. At least the name should be changed to upsert (borrowed from Mongo) or similar
  2. it costs more gas to check for the claim's existence every time. If I know that I'm creating new claims why should I pay for the query?
  3. it allows a developer to rely on the fact that only their intention is carried out. I use add in order to create new things, update to make changes and upsert when I don't care
mgsgde commented 3 years ago

Is there any reason why the claim id does not contain the identityHolder_address. By including the identityHolder_address the claim id would be globally unique, which comes in handy when tracking claims from multiple 735 instances.

trilloc commented 3 years ago

hi all, do we have a beta version of this code just like erc725.. thanks much in advance!

cheers

github-actions[bot] commented 2 years ago

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[bot] commented 2 years ago

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.