ERC: Claim Holder
frozeman opened this issue · comments
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
claim issuer
: is another smart contract or external account, which issues claims about this identity. The claim issuer can be an identity contract itself.claim
: A claim is an information an issuer has about the identity holder. This contains the following:topic
: Auint256
number which represents the topic of the claim. (e.g. 1 biometric, 2 residence (ToBeDefined: number schemes, sub topics based on number ranges??))scheme
: The scheme with which this claim SHOULD be verified or how it should be processed. Its auint256
for different schemes. E.g. could3
mean contract verification, where thedata
will be call data, and theissuer
a contract address to call (ToBeDefined). Those can also mean different key types e.g. 1 = ECDSA, 2 = RSA, etc. (ToBeDefined)issuer
: The issuers identity contract address, or the address used to sign the above signature. If an identity contract, it should hold the key with which the above message was signed, if the key is not present anymore, the claim SHOULD be treated as invalid. The issuer can also be a contract address itself, at which the claim can be verified using the calldata
.signature
: Signature which is the proof that the claim issuer issued a claim oftopic
for this identity. it MUST be a signed message of the following structure:keccak256(address identityHolder_address, uint256 _ topic, bytes data)
// orkeccak256(abi.encode(identityHolder_address, topic, data))
?data
: The hash of the claim data, sitting in another location, a bit-mask, call data, or actual data based on the claim scheme.uri
: The location of the claim, this can be HTTP links, swarm hashes, IPFS hashes, and such.
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:
1
: Biometric data2
: Permanent address
(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
- A claim can only be one per topic per issuer.
Additional References
This is implemented by #725
I added uint256 signatureType
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?
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:
getClaim
by index, which is the type for a trusted issuer. Index is generated:(keccak256(address issuer_address + uint256 _claimType))
- Ecrecover signature, retrieve address
k
- Check if
k
is still a key hold by the claim issuers identity contract (issuer address)
@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?
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?
@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.
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?
@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.
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)
?
@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):
- The approval process for this would not be required, correct?
- How would a second party 'sign' a self-claim, meaning claiming that the self-claimed data is correct?
- How would a third party iterate through the chain of claims?
Maybe you can provide some pseudo code of how this would look like..
@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
I added a Additional References
section.
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.
@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.
@frozeman @realcodywburns Another use case for a disputeClaim
would be to flag identities which have been compromised.
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
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?
01
- Individual02
- Business
Then specifying the following:
01
- Social01
- video02
- photo03
- phone04
- video05
- platform (e.g. platformURL
//
02
- SovereignisoNumericM49
// country specification01
- passport02
- nationalIdNumber03
- governmentId04
- proofOfAddress05
- bankStatement
//
03
- Device/Machine01
- ip02
- os03
- browser04
- etc..
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!
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.
@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://
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.
@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 ! 😅
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
:)
@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 😸 (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)
@jacqueswww Currently there would be two ways to handle that situation:
-
One way it would be that you make the date part of the data field, as you described.
-
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.
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
@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.
@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.
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)?
@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.
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)
@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).
@frozeman Can you be a bit more specific with:
Returns
claimRequestId
: COULD be send to theapprove
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.
@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.
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..
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.
@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.
@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???
any ideas how we can keep the data secret? not everybody should be able to read the provided data.
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);
}
@oed proposed a Claim Holder Registry ERC780. Which i think can work well together with your own self controlled and customised #725 identity.
Some random thoughts...
-
Include all data except the signature itself in the signature (so add
scheme
also) -
What about adding a
validity
field to a claim? Since is not possible to "automatically" prove that the ERC735 implementation will allow the claim issuer to remove this claim in the future (due to a bug, or bad intentions), this it's a good way to ensure that this assertion is not going to be valid forever without claim issuer permission. -
The definition of the signature field is that "which is the proof that the claim issuer issued a claim" but in some cases, like X509 certificates, this signature is part of the claim data itself. There's the option of extracting the signature from the certificate and placing it into the
signature
field, another is to use this field to create as a proof-of-possession about the public key embedded into the certificate. But it seems that the proof-of-possession makes more sense to be added in ERC #725 since there's a specific key spec for that. What about:-
Add a
proofOfPossessionType
,proofOfPossessionData
fields in theKey
structure of ERC #725, that in the case of e.g. X509/RSA2048 certificates could be like + proofOfPossessionData=rsa_sha256_signature(publicKey||this.address)` -
In the ERC #735 add a way to link the possession of a public key to a claim?
-
@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.
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?
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
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.
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!
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!
@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.
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?
@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.
@frozeman Your top-level description still mentions getClaimIdsByType
, I'm guessing it should be getClaimIdsByTopic
?
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.
@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).
@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.
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
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...
@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.
Id like to see the struct have an Uint256 for expire date time. Most documents have an expiration date.
for scheme rsa, should the public key be attached along with signature?
topic number should be standardised. otherwise every one just use its own numbers. the claim is not portable.
@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
orfalse
, should the data match the person claimdata
reference you gave them.@buendiadas concerning the URI, it should contain the protocol like
ipfs://...
, orbzz://
orhttps://
@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
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.
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
- g = 103, i = 105, v = 118, e 101, n 110, N 078, a 097, m 109, e 101
- givenName = 103105118101110078097109101
- in JS, we store it as a string to pass it as a BigNumber in web3: '103105118101110078097109101'
- we call contractInstance.addClaim('103105118101110078097109101', ...)
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:
- givenName: 103105118101110078097109101
- familyName: 102097109105108121078097109101
- jobTitle: 106111098084105116108101
- url (from Thing, can be the website): 117114108
- email: 101109097105108
- description: 100101115099114105112116105111110
For properties that have their first character ASCII code beginning with 0, we'll just remove the first zero:
- additionalName = 097100100105116105111110097108078097109101 => 97100100105116105111110097108078097109101
This approach could add many other properties of other schemas, the only limits being:
- that they should all have a unique ASCII name (which I'm not sure they have on Schema.org, but properties should be unique I guess)
- that they should not exceed 25 characters (the longest People > Thing property is disambiguatingDescription and is already 25 characters)
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
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.
Seems to have a missing function
before changeClaim
method on the Solidity Interface example code.
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.
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.
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?
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.
a claim should only be removed by the subject (the identity holder) because only they can decide what should be forgotten about them.
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.
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.
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 separateupdateClaim
function. It reduces the potential for confusion and error. WhenaddClaim
is called for an existing claim, the transaction is reverted. Same thing for anupdateClaim
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?
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:
- 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 - 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?
- 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 andupsert
when I don't care
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.
hi all, do we have a beta version of this code just like erc725.. thanks much in advance!
cheers
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.
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.