ethereum / EIPs

The Ethereum Improvement Proposal repository
https://eips.ethereum.org/
Creative Commons Zero v1.0 Universal
12.74k stars 5.17k 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

This is implemented by #725

frozeman commented 6 years ago

I added uint256 signatureType

Arachnid commented 6 years ago

I'm still not sure why you're using signatures here, and thus restricting things to only externally owned accounts using ECDSA sigs. Wouldn't it be more general to rely on caller address?

frozeman commented 6 years ago

In order to auto-verify an claim - which could have been added by anybody - each claim needs to contain a in smart contract verifiable signature from one key of an issuers identity. The signature must sign, claim type, claim holder address and claim reference hash.

This way any contract can auto-verify claims.

There is no way to enforce this standard, and if a supposed claim holder added a claim with address issuer somebody, but that somebody never made that claim, there is no way to proof that, except using signatures.

I added the uint256 signatureType to also allow different signature in the future.

And yes you need an external owned account, or key to sign, to allow fully on chain verification. Or do you have any other method in mind, how we could solve that problem?

I will repost a pure on chain claim verification process:

  1. getClaim by index, which is the type for a trusted issuer. Index is generated: (keccak256(address issuer_address + uint256 _claimType))
  2. Ecrecover signature, retrieve address k
  3. Check if k is still a key hold by the claim issuers identity contract (issuer address)
3esmit commented 6 years ago

@frozeman Would be something like this?

pragma solidity ^0.4.15;
import "https://gist.githubusercontent.com/AugustoL/9ddf7996e23a01c6599323761c0deeef/raw/17ae59c3dc2c82df791b2e48e5d05a5c012fec42/ECRecover.sol"; //todo: change to repo
contract IdentityRegistry {
    //(... other logic)
    enum SignatureType {
        DirectCall,
        SignedMessage
    }
    function addClaim(uint256 _claimType, address issuer, uint256 signatureType, bytes _signature, bytes _claim, string _uri) returns (uint256 claimId){

        if (signatureType == SignatureType.DirectCall) {
            require(issuer == msg.sender);
        } else if (signatureType == SignatureType.SignedMessage) {
            bytes32 signedMsg = keccak256(address(this), _claimType, _claim);
            require(issuer == ECRecover.ecrecovery(_signature, signedMsg));
        } else { revert(); }
        //(... store claim and do stuff)
    }
    //(... more other logic)
}

With this user could call directly without using signed message, and other account could register the user using the signed message?

Arachnid commented 6 years ago

In order to auto-verify an claim - which could have been added by anybody - each claim needs to contain a in smart contract verifiable signature from one key of an issuers identity. The signature must sign, claim type, claim holder address and claim reference hash.

Why not just have a registry of claims, whose smart contract code ensures that only valid claims can be stored there?

coder5876 commented 6 years ago

@frozeman - interesting stuff! On-chain attestations is an important use case and this is a good outline. I think the general idea of having claim types or categories are good, and the specifications you outlined are also useful. We’ve had some similar ideas in uPort although we have mainly focused on off-chain attestations.

I tend to agree with @arachnid that the claim architecture would be more general if we were to allow any transaction to define a claim by using the msg.sender construct. Having a registry where claims are registered and where rules are enforced would allow any ethereum address to be the subject of a claim, and it would allow any smart contract capable of sending a transaction to issue a claim.

3esmit commented 6 years ago

I have a use case for this where a registry is using Oraclize, for example with Oraclize+GitHubGists=user register, and also there will be the organizations in that registry (also by oraclize), so oraclize would be the "claimer", and by default it's a direct message call (not sure if is possible other way). Also they provide a 'bytes proof', which should be stored together with claim? Or this case might be out of ERC735 scope?

frozeman commented 6 years ago

@3esmit the check shouldn’t be at the addClaim happen, but rather when the claim is verified by third party. There the signature of the claim needs to be checked. The signature type went to support different signatures in the future, e.g. it could hold non ecdsa siagntures as well.

@christianlundkvist good that your are here ;) Yes sure a registry would allow for “adding” rules and this could be a useful addition, but it could get complicated when we introduce sharding , as all of those calls between the shards need then to go through an async transaction then. Though there could certainly additional registries for claims, but I think the in-contract claims are important for other off chain use cases.

Claims are not only meant to be only from other smart contracts (but they can come from any), but any off chain entity, as long as they attach a valid signature they could even go through a proxy to add claims to contracts.

Concerning adding claims for external accounts, yes that not possible. But I think it’s not really wanted either, your identity should have more than just a public key, which can be lost or stolen.

The reason why I kept the claim so flexible (e.g. sigtype, bytes for claim etc) is to allow all kind of claim type and verification to be added in the future. Only on chain verifications need the signature check. But claims can slow be checked off chain. Issuers could even attest claims about external accounts, but those doesn’t need to be stored in the Blockchain, this can be a transfer of those claims between two parties only. E.g. if I want to have a claim about me form an issuer, but I don’t want that the person which will verify the claim later can connect it to my on chain identity. In this case only the claim issuer need to be able to make that connection.

I will present tomorrow this ERC at the London ethereum Meetup, and there will be a video of that later on attached to this ERC.

I am also available for a call to discuss the details of how I think this works in a skype call. @christianlundkvist and @Arachnid you should have my skype already.

3esmit commented 6 years ago

Wouldn't be equivalent, or more flexible to have function getClaimIdsByType(uint256 _claimType) constant returns(bytes32[]) over function getClaimsByType(uint256 _claimType) constant returns(Claim[]);?

Also, function addClaim(uint256 _claimType, address issuer, uint256 signatureType, bytes _signature, bytes _claim, string _uri) returns (uint256 claimId) to returns (bytes32 claimId)?

m-schmoock commented 6 years ago

@frozeman Imagine a claim was self-claimed ('self-signed'), i.e. because a users sets up his identity and claims initial data (like real name and such):

  1. The approval process for this would not be required, correct?
  2. How would a second party 'sign' a self-claim, meaning claiming that the self-claimed data is correct?
  3. How would a third party iterate through the chain of claims?

Maybe you can provide some pseudo code of how this would look like..

frozeman commented 6 years ago

@3esmit Thanks for the catches, i fixed it and change the function to getClaimIdsByType, which is something we can certainly make work in todays ABI.

I changed the bytes claim to bytes data in the claim, to adhere to @holiman critic of claim being used twice in different contexts.

@m-schmoock for self assigned claims one wouldn't need the approval process and not even a signature, if the address issuer address is the same as the contract itself, there is no verification necessary, as the contract itself holds and returns the claim.

for 3. you could get claims but type using getClaimIdsByType or your know which claim issuer your trust and which claim type you want, then you can recalculate the index using keccak256(address issuer_address + uint256 _claimType) and retrieve the claim directly using getClaim

frozeman commented 6 years ago

I added a Additional References section.

realcodywburns commented 6 years ago

A disputeClaim function would be a nice feature. This would allow for an issuer to assert a claimType n that is negative in nature (late payments, poor conduct) that could be pertinent to both claiming identity and character, however, is inaccurate. While in dispute, the claim would be unavailable to getClaim and possibly third party arbitration assigned or timeout to remove the lock.

frozeman commented 6 years ago

@realcodywburns according to this standard you approve any claim addition, and you can remove claims at any point. So there is no dispute necessary. But you sure can’t do anything claiming something about you in other places. Like in social media today.

andrewrd commented 6 years ago

@frozeman @realcodywburns Another use case for a disputeClaim would be to flag identities which have been compromised.

realcodywburns commented 6 years ago

Yes, the inability to remove some claims would be the goal. For example: an issuer assets a claim that identity 0xdeadbeaf has participated in their auction contract and is known to the community. As time passes , addational claims are asserted that timely payments have been received as per terms of the contract; these claims would further add credibility to both the character and validity of the identity. In the event that a shipment comes missing, the issuer then adds a claim to this effect. As the history exists, the issuer has attested that 0xdeadbeaf is trustworthy and can remove any derogatory claims with impunity. Anyone interacting with 0xdeadbeef would not know of her more recent bad actions.

If the situation arises by mistake, 0xdeadbeaf could disputeClaim, resolve the situation, and retain the longstanding credible claim. If 0xdeadbeef has actually done wrong, the claim both establishes identity and character and should be kept as record for sometime.

Allowing only positive assertions my establish identity but may not capture the full picture of an individual. At the same time, one should have the right to self defense

MikeD123 commented 6 years ago

I'm not sure if I've misunderstood it here @frozeman but I think a common structure for claimType like this would be awesome (if I'm understanding correctly on first glance). I get that this may be out of scope for this EIP in particular, and the list will always grow over time, but something that outlines things like this?

Then specifying the following:

//

//

Looking up an individual for their sovereign doc, in USA, that is a passport. function getClaimIdsByType(uint256 010284001) constant returns(bytes32[] claimIds);

Feel like I may have misunderstood something, or this is a bit outside of scope, but either way, really excited for this, and thanks for doing it!

buendiadas commented 6 years ago

Great initiative @frozeman, I think using a unique URI field can lead to mistake when different Unique Resource Ids from different protocols are uploaded. Additionally to the collusion risk, as we are aggregating many different protocols, a resolver would not be able to know where to take the resource (should I request from IPFS?).

A solution could be an additional genericbytes32 protocol identifier inside the claim or a real URI creation outside the contract including the protocol distinction.

frozeman commented 6 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 commented 6 years ago

As next steps, it would be good to create a collection of PROs and CONs for having claim issues in a registry vs. having them on your identity contract directly.

Some short ideas i gathered when talking to @Arachnid, I put into this wiki page, which everybody can extend and add to it, so we don't have to do this over a series of issues: https://github.com/ethereum/wiki/wiki/ERC-735:-Claim-Holder-Registry-vs.-in-contract

This will allow us gather a good overview over both sides.

bneiluj commented 6 years ago

@frozeman Great idea the wiki page ! https://github.com/ethereum/wiki/wiki/ERC-735:-Claim-Holder-Registry-vs.-in-contract. Let's create a vote counting with some eth ! 😅

frozeman commented 6 years ago

Keep in mind both can work hand in hand, so a claim registry could be useful for some cases, and for others the attached claims in the contract itself are more useful. So can one claim referencing the claim registry, maybe with claimType 5 :)

jacqueswww commented 6 years ago

@frozeman - so one question I had is about the possibility of expiring claims. I can imagine something like a physical address being semi-permanent, and needing re-verification every couple of years. As far as I understand it - with current layout of a Claim one would use the data field in the struct to store a value in the future? OR one would have to look up the date of ClaimAdded in the event log - which we can't do from within another contract right ;) Simpler option is just returning a date created timestamp as part of the getClaim/claim struct, then a contract/service can decide the the delta from the getClaim call if they are willing to accept the claims' date. Let me know what you think :smile_cat: (I know it could possibly be scope creep over the standard, however one could argue that timestamping is something that almost anyone would be interested in)

frozeman commented 6 years ago

@jacqueswww Currently there would be two ways to handle that situation:

  1. One way it would be that you make the date part of the data field, as you described.

  2. Or the claim holder signs specific claims with keys he will expire on his identity himself. As everybody who will verify a claim should definitely check that the claim signing key is still valid, any claim issuer could remove that claim key based on a time frame.

0xc1c4da commented 6 years ago

Hey guys! We've started the draft implementation of #725 and #735 in both Solidity and Viper. Special thanks to @jacqueswww and @3esmit for whipping them up!

https://github.com/status-im/contracts/tree/master/contracts

stephanlotter commented 6 years ago

@frozeman thanks for this! I'd like to see some kind of approveClaimRequest function where I need to approve a getClaim call. This will be valuable for protecting private claims like proof of address, proof of income, etc. I would not want just anybody to retrieve my address, but I would want to allow say a bank to see it when they want to verify my address. And maybe limit the number of times the requester may see the address too (a lot like approve on ERC-20). This way I can prove my address to a supplier and they can only access it that one time.

frozeman commented 6 years ago

@stephanlotter This function doesn't make much sense, as the data blockchain is publicly readable anyway. But you are right there should be a process of how i can make claims CONTENT data accessible, which is not defined in this standard and needs to be around a claim data retrieval standard.

so whatever claim references you put on your public profile will be readable by everybody. BUT you 1. only put references to claims, not the data itself, and 2. could have a secret claim registry like what uport is building where you can give access on a request basis.

This is more about like your "public profile", which actually only reveals who claim what topics/types, and not what content.

svenstucki commented 6 years ago

I like the idea of adding an expiration date. I think it's better than a creation / issue date, since it's very easy to check. With an issue date only, there's external information needed to check a claim.

It's also much more practical than revoking keys all the time - e.g. imagine you are the equivalent of a CA for ERC725/735 and issue a lot of claims. If you want them to have an expiration date, you'll have to add a separate key for each of them and remove them again at the right time. Not very efficient. There's a similar problem with revocation in that case, which is not easy to solve. That would probably work best with a claim holder registry.


Is the url intentionally not covered by the signature? I.e. allowing users to move their data to a different storage location is permitted (of course the integrity can still be verified)?

frozeman commented 6 years ago

@svenstucki I was discussing this with @chriseth as well. Adding an expiry means this needs to be part of the signature.

It might be inefficient to revoke keys, but would give a real time expiry. But i am open for discussion about adding that.

And yes the location should be irrelevant, as long as the data itself is referenced by hash.

frozeman commented 6 years ago

IMPORTANT CHANGE

I added a change to #735 in the claim structure, in so far that signatureType is changed for scheme. A scheme number will determine how the claim can be validated, e.g. scheme number 25 could mean that data is a function call, and issuer a contract address. It can also have the meaning of a specific type of signature, or a specific way of how the actual claim data can be checked.

I also changed the order of claim addition functions and events in so far that scheme is always coming after claimType. E.g.

function addClaim(uint256 _claimType, uint256 _scheme, address issuer, bytes _signature, bytes _data, string _uri)

svenstucki commented 6 years ago

@frozeman Let's not mix expiration and revocation. For expiration removing keys is not great, the issuer has to actively trigger the transaction at just the right time and the real time aspect actually is a disadvantage.

Whether expiration should be part of the standard is debatable - but if it's included an expiration date field IMHO makes the most sense.

I think a claim registry (in addition to having some claims stored in the identity) makes more and more sense for organizations that want to issue a lot of claims. This would make it easy for them to add mechanisms for revocation/expiration/etc. that fit their needs. The question is whether this should be embraced this now and we define an interface (or adapt this ERC a bit so it works for both). Maybe the signature should be extended to include the claim holder address, otherwise claims can be "copied" from a registry and still be valid (i.e. this could be used to enforce the rules of a registry - if a claim is removed there it's no longer valid).

tbocek commented 6 years ago

@frozeman Can you be a bit more specific with:

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

As far as I understand it, a claim holder that adds a self-issued claim will call approve right away. For any other claim that is added, the claim holder will need to call at a later stageapprove with the respective claimRequestId. How about something like this:

Returns claimRequestId: can be used for the approve function, to approve or reject this claim. If it is a self issued claim, the approve function can be called from within this addClaim function.

frozeman commented 6 years ago

@tbocek as it says "COULD" it means that it doesnt have to, which in return means the implementation can determine that. This is also not really important for standardisation, as long as the right events are fired (Which is what interfaces use to determine if things passed or not). I personally would like to keep it as concise as possible in the spec.

@svenstucki i am also in favour of claim registries in some cases, and in fact an entity can add a claim, which could make use of the change i recently added: Add claim scheme e.g. 20, which could mean that issuer will be the registry contract and data the correct function call on the registry. Then you simply have one claim per registry and the user then simply calls the registry function to check the actual claims existence.

So the question is, do we need an expiry, or isn't this just a thing of the old world, which had no better way to revoke claims. But i am open to adding adding, just would like to see more example use cases.

mrpullen commented 6 years ago

I am not sure what value is provided by creating a finite set of claimTypes as an enumeration...

Is this meant to be some form of typing arrangement so data stored will have a specific format, address could be [string],[lat],[long] to create a standard around how specific types of data are stored into the byte[] data array?

A claim is any statement about a user, that has been agreed to by the issuer of the claim right?

Also could you implement a claim registry by using issuing a set of claims about the "claims" that the specific issuer supports? Since the issuer is declaring it about itself these claims could be self signed - and any contracts that are looking to that issuer would use 1 or more of those claim types. Then the claimTypes an issuer supports can be be referenced by claimTypes = getClaimTypes(address_of_issuer)...

Obviously any identity could also be an issuer, and it would then be up to the implementation of a smart contract to add trusted "issuers" and some claims of it's own about what claimTypes it supports. These could be self signed. This could then incentivize "issuers" to provide those claimTypes. And when they had provided these, those "issuers" could be brought in as "trusted" issuers by the identity of the smart contract..

frozeman commented 6 years ago

I cant really follow your example :(

For the claimTypes. Its a finite number, but a very large one, so there will be enough claim types available for all kind of things. Important for the verification and fetching of the claim data is the claim scheme of the claim.

I would actually propose to rename claimType to topic, as it is the topic of the claim.

nervous-energy commented 6 years ago

@frozeman Really appreciate your work on this. A common use case for an expiration field would be professional certification. For example a doctor claiming a licence to practice in a particular jurisdiction, or claiming that they have a particular professional competency -- e.g. have completed an ATLS trauma course in the past 3 years. This data could be held within the claim itself -- i.e. as part of a JSON object on IPFS -- but validating this off-chain may not be ideal or efficient. We'd need claims to agree upon a standard claim structure for a start.

At worst, expiration would seem to act a fail safe, lowering risk in the ecosystem.

jorpand commented 6 years ago

@frozeman regarding to the Claim IDs, just a quick comment:

claim issuer: is another smart contract or external account, which issues claims about this identity. The claim issuer can be an identity contract itself.

So I assume that a single contract or account with a single address is the issuer of different claims for different identities, the definition says:

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

Isn't it necessary to add the subject_address somehow in the hash calculation to generate unique ID's? otherwise as the issuer issues more than a claim these won't be unique. For example:

keccak256(address issuer_address + address subject_address + uint256 _claimType).

Is this correct or am I wrong???

froid1911 commented 6 years ago

any ideas how we can keep the data secret? not everybody should be able to read the provided data.

tbocek commented 6 years ago

Here is the latest interface for this ERC

pragma solidity ^0.4.18;

contract ERC735 {

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

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

    function getClaim(bytes32 _claimId) public constant returns(uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri);
    function getClaimIdsByType(uint256 _claimType) public constant returns(bytes32[] claimIds);
    function addClaim(uint256 _claimType, uint256 _scheme, address _issuer, bytes _signature, bytes _data, string _uri) public returns (uint256 claimRequestId);
    function removeClaim(bytes32 _claimId) public returns (bool success);
}
frozeman commented 6 years ago

@oed proposed a Claim Holder Registry ERC780. Which i think can work well together with your own self controlled and customised #725 identity.

tbocek commented 6 years ago

@frozeman you mean #725 identity?

adria0 commented 6 years ago

Some random thoughts...

cbruguera commented 6 years ago

@frozeman Nice job on this effort for standardization of identity. These are just some thoughts on the present discussion:

With regard to "claim expiration", I'm not an advocate of making this an explicit functionality of a claim standard, since "expiry date" of a given claim can be different according to different "relying parties". For example, in the banking KYC scenario: Bank A might require that my latest proof of address is no older than 2 years since issued, but Bank B might be a bit more flexible allowing for an address claim to be just not older than 5 years. In this case, time-based validity of claim is not an attribute of the issuace of it, but an aspect of its verification process, which may differ according to different relying parties.

Following on this regard, I think claims should be timestamped. Thus it's on the side of relying parties to decide how to handle the antiquity of certain claims, if needed. Some claims might include expiration as part of their inherent data (such as a government-issued passport), but then this should be an attribute of the corresponding JSON blob for this claim, which most probably wouldn't even be on-chain. For other cases, there's still the manual removal/revocation of previously issued claims.

Another example on the convenience of timestamped claims is that some relying parties (or just smart contracts part of an identity ecosystem) might want to give certain claims more or less value among others based on their antiquity, not necessarily regarding them as "expired" or "not-valid". For example, a claim-based incentivization/reputation scheme that gives more weight/points to claims which are more recent.

Still looking forward to further comprehend the scope and details of this standardization effort, since it'll be really useful for the Ethereum ecosystem and digital identity in general. Any feedback on the given views is appreciated.

SebastienGllmt commented 6 years ago

What was the reasoning behind the following logic for ClaimId

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

Doesn't this block you from having multiple claims of the same claimType on one identity? What if you have multiple addresses or multiple citizenships?

JosefJ commented 6 years ago

Following up on @jarradh implementation here: https://github.com/status-im/contracts/tree/master/contracts specifically on

modifier claimSignerOnly {
      require(keys[msg.sender] == CLAIM_SIGNER_KEY);
       _;
}

Should the key with keyPurpose == CLAIM_SIGNER_KEY be considered an ethereum addresses (msg.sender) only? I had the impression that the CLAIM_SIGNER_KEY types of keys should rather be declaration of "my key pairs" (public keys that you can use to verify claims signed by my private keys), therefore not being limited to Ethereum public keys/addresses only.
=> Use case: The club entrance model that @frozeman described in his presentation at DEVCON3. "By signing your random message with any of the key pairs which I declared I hold, I prove to you this is indeed my identity contract". Just trying to synchronize my understanding here.

If we are not limiting keys to ethereum key pairs, I think the signature should contain some actual cryptographic signature of the claim data which an identity owner can verify against the keys stored in the identity contract of the issuer.

EDIT 1: How about instead of 'scheme' we would pass the actual 'key' bound to identity of a claim issuing party and then got the type of the this key from the issuing identity itself through getKey(key).keyType?

EDIT 2: @frozeman: the event ClaimAdded() is twice in the suggested interface (with and without the scheme parameter) & changeClaim(bytes32 claimId, uint256 claimType, uint256 scheme, address issuer, bytes signature, bytes data, string uri) returns (bool success); is missing

EDIT 3: Dummy implementation with few changes (adding claims through executions) : https://github.com/JosefJ/IdentityContract/blob/master/contracts/ClaimHolder.sol

richard-ramos commented 6 years ago

@frozeman

Reviewing the interface specification I see that for ClaimIds sometimes bytes32 is used, and sometimes uint256, so I assume the original idea was to have everything using uint256, right?

If that's not the case, the approve function of #725 will get complex to implement since it expects to receive uint256 Ids for both executions and claims and we would have uint256 execution ids, and bytes32 claim ids.

computerphysicslab commented 6 years ago

At ERC-735 interface definition I guess function addClaim(uint256 _claimType, uint256 _scheme, address issuer should be instead function addClaim(uint256 _claimType, uint256 _scheme, address _issuer adding an underscore at parameter issuer, right? Thx!

computerphysicslab commented 6 years ago

While claimRequestId is uint256, claimId is bytes32. Why? And why not every claim requested is added inmediatly? Is there any validation process needed to requested claims become added claims? Thx!

tbocek commented 6 years ago

@computerphysicslab The claimRequestId is the id of a request (for adding a claim). Such a claim can be added by approve() from ERC725. The parameter type for the approve function is a uint256. After the approval, the claim gets added, which then can be accessed with the claim id (getClaim), which is a hash over the issuer address and the claim type.