ethereum/EIPs

ERC: Lightweight Identity

oed opened this issue ยท 31 comments

oed commented
EIP: 1056
Title: ERC: Lightweight Identity
Author: Pelle Braendgaard <pelle.braendgaard@consensys.net>, Joel Torstensson <oed@consensys.net>
Type: Standards Track
Category: ERC
Status: Draft
Created: 2018-05-03

Simple Summary

A registry for key and attribute management of lightweight blockchain identities.

Abstract

This ERC describes a standard for creating and updating identities with a limited use of blockchain resources. An identity can have an unlimited number of delegates and attributes associated with it. Identity creation is as simple as creating a regular key pair ethereum account, which means that it's fee (no gas costs) and all ethereum accounts are valid identities. Furthermore this ERC is fully DID compliant.

Motivation

As we have been developing identity systems for the last couple of years at uPort it has become apparent that the cost of identity creation is a large issue. The previous Identity proposal ERC725 faces this exact issue. Our requirements when creating this ERC is that identity creation should be free, and should be possible to do in an offline environment (e.g. refugee scenario). However it must also be possible to rotate keys without changing the primary identifier of the identity. The identity system should be fit to use off-chain as well as on-chain.

Definitions

  • Identifier: a piece of data that uniquely identifies the identity, an ethereum address
  • delegate: an address that is delegated for a specific time to perform some sort of function on behalf of an identity
  • delegateType: the type of a delegate, is determined by a protocol or application higher up
    Examples:
    • did-jwt
    • raiden
  • attribute: a piece of data associated with the identity

Specification

This ERC specifies a contract called EthereumDIDRegistry that is deployed once and can then be commonly used by everyone.

Identity ownership

By default an identity is owned by itself, meaning whoever controls the ethereum account with that address. The owner can be updated to a new key pair account or to a multisig account etc.

identityOwner

Returns the owner of the given identity.

function identityOwner(address identity) public view returns(address);

changeOwner

Sets the owner of the given identity to another ethereum account.

function changeOwner(address identity, address newOwner) public;

changeOwnerSigned

Same as above but with raw signature.

function changeOwnerSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, address newOwner) public;

Delegate management

Delegates can be used both on- and off-chain. They all have a delegateType which can be used to specify the purpose of the delegate.

validDelegate

Returns true if the given delegate is a delegate with type delegateType of identity.

function validDelegate(address identity, bytes32 delegateType, address delegate) public view returns(bool);

addDelegate

Adds a new delegate with the given type. validity indicates the number of seconds that the delegate will be valid for, after which it will no longer be a delegate of identity.

function addDelegate(address identity, bytes32 delegateType, address delegate, uint validity) public;

addDelegateSigned

Same as above but with raw signature.

function addDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate, uint validity) public;

revokeDelegate

Revokes the given delegate for the given identity.

function revokeDelegate(address identity, bytes32 delegateType, address delegate) public;

revokeDelegateSigned

Same as above but with raw signature.

function revokeDelegateSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 delegateType, address delegate) public;

Attribute management

Attributes contain simple data about the identity. They can be managed only by the owner of the identity.

setAttribute

Sets an attribute with the given name and value, valid for validity seconds.

function setAttribute(address identity, bytes32 name, bytes value, uint validity) public;

setAttributeSigned

Same as above but with raw signature.

function setAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes value, uint validity) public;

revokeAttrubte

Revokes an attribute.

function revokeAttribute(address identity, bytes32 name, bytes value) public;

revokeAttributeSigned

Same as above but with raw signature.

function revokeAttributeSigned(address identity, uint8 sigV, bytes32 sigR, bytes32 sigS, bytes32 name, bytes value) public;

Events

DIDOwnerChanged

MUST be triggered when changeOwner or changeOwnerSigned was successfully called.

event DIDOwnerChanged(
  address indexed identity,
  address owner,
  uint previousChange
);

DIDDelegateChanged

MUST be triggered when a change to a delegate was successfully made.

event DIDDelegateChanged(
  address indexed identity,
  bytes32 delegateType,
  address delegate,
  uint validTo,
  uint previousChange
);

DIDAttritueChanged

MUST be triggered when a change to an attribute was successfully made.

event DIDAttributeChanged(
  address indexed identity,
  bytes32 name,
  bytes value,
  uint validTo,
  uint previousChange
);

Efficient lookup of events through linked identity events

Contract Events are a useful feature for storing data from smart contracts exclusively for off-chain use. Unfortunately current ethereum implementations provide a very inefficient lookup mechanism. By using linked events that always link to the previous block with a change for the identity, we can solve this problem with much improved performance. Each identity has its previously changed block stored in the changed mapping.

  1. Lookup previousChange block for identity
  2. Lookup all events for given identity address using web3, but only for the previousChange block
  3. Do something with event
  4. Find previousChange from the event and repeat

Example code:

const history = []
previousChange = await didReg.changed(identity)
while (previousChange) {
  const filter = await didReg.allEvents({topics: [identity], fromBlock: previousChange, toBlock: previousChange})
  const events = await getLogs(filter)
  previousChange = undefined
  for (let event of events) {
    history.unshift(event)
    previousChange = event.args.previousChange
  }
}     

Building a DID document for an identity

The primary owner key should be looked up using identityOwner(identity). This should be the first of the publicKeys listed. Iterate through the DIDDelegateChanged events to build a list of additional keys and authentication sections as needed. The list of delegateTypes to include is still to be determined. Iterate through DIDAttributeChanged events for service entries, encryption public keys and other public names. The attribute names are still to be determined.

Rationale

For on-chain interactions Ethereum has a built in account abstraction that can be used regardless of whether the account is a smart contract or a key pair. Any transaction has a msg.sender as the verified send of the transaction.

Since each Ethereum transaction has to be funded, there is a growing trend of on-chain transactions that are authenticated via an externally created signature and not by the actual transaction originator. This allows 3rd party funding services or receiver pays without any fundamental changes to the underlying Ethereum architecture. These kinds of transactions have to be signed by an actual key pair and thus can not be used to represent smart contract based Ethereum accounts.

We propose a way of a Smart Contract or regular key pair delegating signing for various purposes to externally managed key pairs. This allows a smart contract to be represented both on-chain as well as off-chain or in payment channels through temporary or permanent delegates.

Backwards Compatibility

All ethereum accounts are valid identities (and DID compatible) using this standard. This means that any wallet provider that uses key pair accounts already supports the bare minimum of this standard, and can implement delegate and attribute functionality by simply using the ethr-did referenced below. As the DID Auth standard solidifies it also means that all of these wallets will be compatible with the DID decentralized login system.

Implementation

ethr-did-registry (EthereumDIDRegistry contract implementation)
ethr-did-resolver (DID compatible resolver)
ethr-did (javascript library for using the identity)

Deployment

The address for the EthereumDIDRegistry will be specified here once deployed.

Copyright

Copyright and related rights waived via CC0.

This looks pretty mature - you should submit it as a PR so it can be merged as a draft.

Since each Ethereum transaction has to be funded, there is a growing trend of on-chain transactions that are authenticated via an externally created signature and not by the actual transaction originator.

This is similar to what is being done in #865. Is it worth trying to establish a standard around how such pre signed transactions are implemented?

oed commented

@ptrwtts as @aldigjo mentioned we have a general construction for doing pre singed / meta transactions. There is some documentation availiable here. However I'm not sure it makese sense to standardize this, as different projects might have different requirements. What do you think?

When the data (adding/changing attributes/delegates) is put on the blockchain someone still has to pay though? Do I understand this EIP correctly that you are only simplifying the flow so that you can save the cost from proxy contract creations etc?

oed commented

@alexandermuehle Yes you understand it correctly. However not that setting an attribute only emits an event and does no regular on-chain storage, so it's quite cheap.

@oed cool, so its only gas cost for updating the "changed" mapping + 375 G_log + 8 G_logdata * data size + 375 G_logstopic * (4 or 6), thats a big saving

The identity system should be fit to use off-chain as well as on-chain.

how is that possible if you use log events to store the data though? Is there a way for a contract to access event data now?

[...] exclusively for off-chain use

You are contradicting yourself here (or more likely you mean something else with "fit to use off-chain as well as on-chain"), maybe clarify this point in the motivation section

@alexandermuehle

how is that possible if you use log events to store the data though? Is there a way for a contract to access event data now?

The idea is for off-chain use cases to be handled through the event logs and onchain use cases to be handled through an Ethereum Claims Registry (i.e. ERC780)

There are several issues with this proposal and a few items that are unclear

  1. What is the difference between the changeOwner and changeOwnerSigned messages? Whose signature is in the method? Why do we need both?

  2. The idea that the identity changes its owner subjects the system to a race condition. Its possible that a TOCTOU attack could occur between the check and use of identity and a change of ownership in between. Given this occurs on-chain, a 15s block time would make this easily exploited.

  3. The delegate/revoke system is insufficiently specified. The above enables delegating, revoking and re-delegating the same delegate multiple times. This will break several audit certainty, especially due to race conditions.

Suggestions:

  1. Have a thinner API
  2. Changing owners is dangerous and unnecessary. Drop it. A multisig owner can change the owners of the multisig itself.
  3. Delegation should be grant/revoke. Once revoked, it should not be possible to re-grant.

Note: Technically W3C DID spec requires the url/json serialization format, so this is not DID compliant. My suggestion is not to bend backwards to be DID compliant.

Zero Knowledge API Keys provide all the functionalities of this spec without race conditions and is a lot simpler to code against.

Any thoughts on including attestations for given attributes?

Im unclear as to what attributes are used for. Assuming that they are just tags such as location:USA or customertier:silver. Attribute revocation has the same race condition, but it may not be as critical as transfer of ownership in terms of exploit cost. The validity in seconds is imprecise on-chain but again, for attributes it may not be a major issue.

Overall, creation and revocation without updates is the best way. Identities should be cheap and abundant for decentralization, just like a bitcoin address or ethereum account. You should not feel the need to update attributes on them or transfer ownership. Just create new ones.

Note that reputation works well with addresses today: exchange wallets are well known and when a deposit is made from such an address, other exchanges can credit immediately, so there should not be a strong attachment to a given address/identity.

EDIT: I don't see why attributes need to be on chain. Apparently, the identity owner himself is claiming certain attributes. This seems absurd since anyone can claim anything they want. The value of an attribute system is when OTHERS attest to attributes. Compare a user claiming to be from USA. It has no evidentiary support or is usable in anyway. My guess is that attribution is a completely orthogonal concern that should be in a completely different EIP where others can tag certain identities (assuming its possible to do it in a sybil resistant way)

@bharathrao agreed that this feels like a "self-proclaimed" identity which does seem odd.

Why this is called lightweight? How much cost to initialize an identity? More than to create a forwarder contract?

This approach looks like a token/badge and imply limitations on the Identity because all identities in the network are one address, so operations in between contracts all need to be aware of that contract.

Seems like that one address can later be owned by other address, this seems very strange. Would be better then that each identity created is an unique number, not sure.

However this might be useful somehow I'm not able to understand now, can you elaborate on that?

oed commented

@alexandermuehle

how is that possible if you use log events to store the data though? Is there a way for a contract to access event data now?

If you have a look at the implementation owners and delegates are also stored on-chain.

@bharathrao

  1. The idea that the identity changes its owner subjects the system to a race condition. Its possible that a TOCTOU attack could occur between the check and use of identity and a change of ownership in between. Given this occurs on-chain, a 15s block time would make this easily exploited.

Can you elaborate on how this would be an issue on-chain? To me a call to the identityOwner function would be safe.

  1. The delegate/revoke system is insufficiently specified. The above enables delegating, revoking and re-delegating the same delegate multiple times. This will break several audit certainty, especially due to race conditions.

I agree with this and am planning an update so that it's possible to add delegates that are not revokable. However the revokable delegates are useful in cases where you want to instantly revoke a larger number of claims simultaniously. That can be claims in the ERC780 registry or off-chain claims.

  1. Changing owners is dangerous and unnecessary. Drop it. A multisig owner can change the owners of the multisig itself.

Unfortunataly it's not possible to create multisigs without an on-chain transaction.

  1. Delegation should be grant/revoke. Once revoked, it should not be possible to re-grant.

Considering this. Can you elaborate with an example when this would be a problem maybe.

Note: Technically W3C DID spec requires the url/json serialization format, so this is not DID compliant. My suggestion is not to bend backwards to be DID compliant.

Please have a look at the ethr-did-resovler

Zero Knowledge API Keys provide all the functionalities of this spec without race conditions and is a lot simpler to code against.

Thanks, will have a closer look at this.

@alexandermuehle how is that possible if you use log events to store the data though? Is there a way for a contract to access event data now?

If you have a look at the implementation owners and delegates are also stored on-chain.

I saw that, but the attributes/verifiable claims are not though, so for any use-case beyond the basics, contracts have to rely on other registries, like @christianlundkvist himself said onchain use-cases would still use an ERC780 like on-chain registry solution, I'm only pointing out that this is not really fit for BOTH on- and off-chain.

@bharathrao

What is the difference between the changeOwner and changeOwnerSigned messages? Whose signature is in the method? Why do we need both?

changeOwnerSigned accepts these signature parameters because in the context of uport we have something called meta transactions. These are transactions which are forwarded by a 3rd party โ€œrelayerโ€ on behalf of the user, but the actions performed on-chain are what the user intended to do, and they are verified to be from the user using these signature params. This is important to uport because we do not expect every user to own Ether, and have come up with this scheme to allow for 3rd party gas payment. Please See the article I linked above. https://medium.com/uport/making-uport-smart-contracts-smarter-part-3-fixing-user-experience-with-meta-transactions-105209ed43e0

@bharathrao

The idea that the identity changes its owner subjects the system to a race condition. Its possible that a TOCTOU attack could occur between the check and use of identity and a change of ownership in between.

Could you possibly elaborate? I may be missing something b/c I donโ€™t see a TOCTOU attack here on-chain. When considering an ethereum transaction, check & use happen in the same transaction.

@AdamJLemmon

Any thoughts on including attestations for given attributes?

uPort supports both on-chain and off-chain attestations. We expect these to be used in the appropriate contexts, as most of all data does not belong on-chain. Our version of what you are asking is ERC 780, where the address signing the transaction, makes some sort of claim about a subject. For off-chain attestations we use JWTโ€™s, where we can sign a set of claims and store them locally on the device.

@bharathrao

Im unclear as to what attributes are used for. Assuming that they are just tags such as location:USA or customertier:silver. Attribute revocation has the same race condition, but it may not be as critical as transfer of ownership in terms of exploit cost. The validity in seconds is imprecise on-chain but again, for attributes it may not be a major issue.

If others make their own clients to interact with this contract, they can use attributes as they wish, for their Own identity. In the context of uPort, we consider the data resolved from this smart contract to be a userโ€™s public DID document. For example, an attribute could be a public key, so that another user B can verify the JWTโ€™s signed by the first user. This would be done off-chain using something like the ethr-did-resolver

@bharathrao

EDIT: I don't see why attributes need to be on-chain. Apparently, the identity owner himself is claiming certain attributes. This seems absurd since anyone can claim anything they want. The value of an attribute system is when OTHERS attest to attributes. Compare a user claiming to be from USA. It has no evidentiary support or is usable in anyway. My guess is that attribution is a completely orthogonal concern that should be in a completely different EIP where others can tag certain identities (assuming its possible to do it in a sybil resistant way)

Attributes do Not have to be on-chain, this is an option in the form of ERC 780. This is an identity standard which can complement other on-chain solutions, but is primarily for resolving a DID document which help verify off-chain messages. Identity attributes which need to be public (e.g a public key, or verification algorithm) can easily be resolved and are not expensive to store. In ERC 780 you can make a claim about yourself, or others can about you. I don't see the necessity to limit users from making claims about themselves, whether it's in an off-chain or on-chain context.

@3esmit

Why this is called lightweight? How much cost to initialize an identity? More than to create a forwarder contract?

It does not cost anything. An address owns itself by default.

@3esmit

This approach looks like a token/badge and imply limitations on the Identity because all identities in the network are one address, so operations in between contracts all need to be aware of that contract.

All identities are not one address, the address is created by the user. By default this address owns itself, but the owner can be changed to allow rotation of keys. The identity itself can be a contract like a proxy, which would actually be intended to hold tokens.

@3esmit

Seems like that one address can later be owned by other address, this seems very strange. Would be better then that each identity created is an unique number, not sure.

This allows for a persistent identifier, with the ability to rotate keys. You can also generate more Ethereum addresses, on your device, and these would be instantly โ€œcreatedโ€, unique identities.

when considering an ethereum transaction, check & use happen in the same transaction.

I see. So this ID scheme cannot be used for non-ethereum applications?

@bharathrao this id scheme implies that the self sovereign identity management is done on an ethereum chain supported by these contracts. You can use this scheme to manage identities that use any other technologies as well, but if you want to do on-chain verification of things (like signature verification), you'd need to write a relayer or bridge that brings that information from the ethereum chain where these identities are managed to your other chain. You can use this identity system for off-chain verification of self-sovereign identity by just talking to the ethereum chain that the identity claims it represents.

While working through implementations and building with this registry a few concerns have come up. Our primary concern is that no on-chain account recovery methods exist for 1056 at this time. Not as importantly, a lack of multiple owned addresses, and the confusing nature of an address being owned by another address were also concerns.

When working with something which is as important as an identity, in my opinion, it is extremely important to make sure that there are robust safety precautions in place should a user lose access to an address. This can be manifest in a few ways such as multiple addresses or a means for users to render an account useless should they lose access. We ran into a lot of these same issues while working on ERC-1484 (a digital ID aggregator) and I feel that some of the solutions we came up with could be a benefit to 1056 were they to be implemented. For example, we allow for a user to add multiple owned addresses for their identity and we allow for a fail safe recovery should a users owned address be maliciously compromised. Identity is too important of a field to leave the protection of identities up to the end user completely. This will be especially true as DIDs gain more traction and less tech savvy users are controlling them.

In abstract it says that it's fee (no gas costs), it should say that it's free (no gas costs), i.e. change fee to free

There has been some chatter about archiving logs, pruning state size, which would seem to affect this ERC's strategy for storing some data. Will this strategy need to be revisited?

https://github.com/ethereum/pm/blob/master/All%20Core%20Devs%20Meetings/Eth1x%20Sync%201.md

davux commented

How does this work on alternate (private) networks? I see at least 3 main issues:

  1. Some addresses exist on one network and not on another. This is especially true for smart contract addresses.
  2. Delegates defined on one network will not be the same as delegates defined on another. When looking at a given ethr DID, how do you know on which network to look?
  3. How do you discover the address of the registry to use on a particular network? Discovery should be part of the specification, and a Markdown file on a Github repository hardly works as a discovery algorithm, especially when dealing with an arbitrary number of private networks worldwide.

One way to mitigate that would be to include the network ID (possibly using a human-readable alias) as part of a DID, either litterally or using some checksum-based algorithm such as MNID.

Zerim commented

I know this is already merged but I have a few questions:

  • Was counterfactual instantiation considered as a way of supporting off-chain identities (rather than the every Ethereum account is a DID approach?
  • Were the future effects of state rents considered in choosing to go with a "commons" contract vs. every user deploys (or counterfactually instantiates) their own identity contract (that they would be responsible for paying rents on).

In the abstract you say

which means that it's fee (no gas costs)

I think it should say free

hello community,

in the abstract it is said that delegates of the DID can perform operations on behalf of the identity owner. But as specified in the smart contract right now, it is only possible for the identity owner of the DID to perform operations on a DID on-chain. They should update this in their document, I was very confused that delegation would not enable me to change a DID. However, shouldn't valid Delegates also be able to perform operations on behalf of the DID owner on-chain?

Generally, all methods using raw signatures shall have a mechanism to prevent replay attacks. Not sure if this is the case. For example, when delegate is added using raw signature, and then revoke is called, can the delegate add itself back by re-using the signature?

@ejossev the sig is using a nonce in the hash so that each sig can be submitted once https://github.com/uport-project/ethr-did-registry/blob/develop/contracts/EthereumDIDRegistry.sol#L112

Anyway, I think this allows replays over different chains... You can use a Ropsten sig in Mainnet for example. This contract was deployed via single address so the field this in the sig does not prevent this replay attack, it would usually do so.

Should the implementation be updated to EIP-712?

@ilanolkies I see, that is not a bad idea, but then the format of the data put into hash calculations shall be specified as well, shouldn't it?
EIP-712 probably does not make sense until fully implemented.

Yes, I mentioned EIP-712 just because it adds chainId value to the hash to prevent this

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.