EIPs/EIPS/eip-1484.md

448 lines
26 KiB
Markdown
Raw Normal View History

---
eip: 1484
title: Digital Identity Aggregator
author: Anurag Angara <anurag.angara@gmail.com>, Andy Chorlian <andychorlian@gmail.com>, Shane Hampton <shanehampton1@gmail.com>, Noah Zinsmeister <noahwz@gmail.com>
discussions-to: https://github.com/ethereum/EIPs/issues/1495
status: Draft
type: Standards Track
category: ERC
created: 2018-10-12
---
## Simple Summary
A protocol for aggregating digital identity information that's broadly interoperable with existing, proposed, and hypothetical future digital identity standards.
## Abstract
This EIP proposes an identity management and aggregation framework on the Ethereum blockchain. It allows entities to claim an identity via a singular `Identity Registry` smart contract, associate this `Identity` with Ethereum addresses in a variety of meaningful ways, and use it to interface with smart contracts. This enables arbitrarily complex identity-related functionality. Notably (among other features) this proposal is [DID compliant](https://github.com/hydrogen-dev/ERC-1484/blob/master/best-practices/DID-Method.md), can natively support [ERC-725](https://github.com/hydrogen-dev/ERC-1484/tree/master/contracts/examples/Resolvers/ERC725) and [ERC-1056](https://github.com/hydrogen-dev/ERC-1484/tree/master/contracts/examples/Resolvers/ERC1056) identities, and lays a robust foundation for [meta-transactions](https://github.com/hydrogen-dev/ERC-1484/tree/master/contracts/examples/Providers/MetaTransactions).
## Motivation
Emerging identity standards and related frameworks proposed by the Ethereum community (including ERCs/EIPs [725](https://github.com/ethereum/EIPs/issues/725), [735](https://github.com/ethereum/EIPs/issues/735), [780](https://github.com/ethereum/EIPs/issues/780), [1056](https://github.com/ethereum/EIPs/issues/1056), etc.) define and instrumentalize digital identity in a variety of ways. As existing approaches mature, new standards emerge, and isolated, non-standard approaches to identity develop, managing multiple identities will become increasingly burdensome and involve the unnecessary duplication of work.
The proliferation of on-chain identity solutions can be traced back to the fact that each codifies a notion of identity and links it to specific aspects of Ethereum (claims protocols, per-identity smart contracts, signature verification schemes, etc.). This proposal eschews that approach, instead introducing a protocol layer in between the Ethereum network and individual identity applications. This solves identity management and interoperability challenges by enabling any identity-driven application to leverage an un-opinionated identity management protocol.
## Definitions
- `Identity Registry`: A single smart contract which is the hub for all `Identities`. The primary responsibility of the `Registry` is to enforce a global namespace for `Identities`, which are individually denominated by Ethereum Identification Numbers (EINs).
- `Identity`: A data structure containing all the information relevant to a particular identity. They are denominated by EINs (incrementing `uint`s), which are unique but uninformative.
- `Associated Address`: An Ethereum address publicly associated with an `Identity`. In order for an address to become an `Associated Address` for an `Identity`, the `Identity` must produce signed messages from the candidate address and an existing `Associated Address` indicating this intent. Identities can remove an `Associated Address` by producing a signed message indicating intent to disassociate itself from the `Identity`. Signatures must include a timestamp within a rolling lagged window of the current `block.timestamp` to prevent replay attacks. An address may only be an `Associated Address` for one `Identity` at any given time.
- `Provider`: An Ethereum address (typically but not by definition a smart contract) authorized to add and remove `Associated Addresses`, `Providers`, and `Resolvers` from `Identities` who have authorized the `Provider` to act on their behalf. `Providers` exist to facilitate user adoption, and make it easier to manage `Identities`.
- `Resolver`: A smart contract containing arbitrary information pertaining to `Identities`. A resolver may implement an identity standard, such as ERC-725, or may consist of a smart contract leveraging or declaring identifying information about `Identities`. These could be simple attestation structures or more sophisticated financial dApps, social media dApps, etc. Each `Resolver` added to an `Identity` makes the `Identity` more informative.
- `Recovery Address`: An Ethereum address (either an account or smart contract) that can be used to recover lost identities as outlined in the [Recovery](#recovery) section.
- `Poison Pill`: In the event of irrecoverable control of an `Identity`, the `Poison Pill` offers a contingency measure to permanently disable the `Identity`. It removes all `Associated Addresses` and `Providers` while preserving the `Identity` (and optionally, `Resolvers`). Evidence of the existence of the `Identity` persists, while control over the `Identity` is nullified.
## Specification
A digital identity in this proposal can be viewed as an omnibus account, containing more information about an identity than any individual identity application could. This omnibus identity is resolvable to an unlimited number of sub-identities called `Resolvers`. `Resolvers` recognize identities by any of their `Associated Addresses`. The protocol allows for an atomic entity, the `Identity`, to be resolvable to abstract data structures, the `Resolvers`.
The protocol revolves around claiming an `Identity` and managing `Associated Addresses` and `Resolvers`. Identities delegate much of this responsibility to one or more `Providers`. `Provider` smart contracts or addresses may add and remove `Resolvers` indiscriminately, but may only add and remove `Associated Addresses` or other `Providers` with the appropriate permissions.
### Identity Registry
The `Identity Registry` contains functionality to mint new `Identities` and for existing `Identities` to manage their `Providers`, `Associated Addresses`, and `Resolvers`. It is important to note that this registry fundamentally requires transactions for every aspect of building out an `Identity`. However, recognizing the importance of accessibility to dApps and identity applications, we empower `Providers` to build out `Identities` on the behalf of users, without requiring users to pay gas costs. An example of this pattern, often referred to as a meta transactions, can be [seen in the reference implementation](https://github.com/hydrogen-dev/ERC-1484/tree/master/contracts/examples/Providers/MetaTransactions).
Due to the fact that multiple addresses can be associated with a given identity (though not the reverse), `Identities` are denominated by EINs. This `uint` can be encoded in QR format or transformed into more user-intuitive formats, such as a `string`, in registries at the `Provider` or `Resolver` levels.
### Address Management
The address management function consists of trustlessly connecting multiple user-owned `Associated Addresses` to an `Identity`. It does not give special status to any particular `Associated Address`, rather leaving this specification to identity applications built on top of the protocol - for instance, `management`, `action`, `claim` and `encryption` keys denominated in the ERC-725 standard, or `Identifiers` and `delegates` as denominated in ERC-1056. This allows a user to access common identity data from multiple wallets while still:
- retaining flexibility to interact with contracts outside of their identity
- taking advantage of address-specific permissions established at the application layer of a user's identity.
Trustlessness in the address management function is achieved through a signature and verification scheme that requires two signatures - one from an address already within the registry and one from the address to be claimed. Importantly, the transaction need not come from the original user, which allows entities, governments, etc. to bear the overhead of creating a core identity. To prevent a compromised `Associated Address` from unilaterally removing other `Associated Addresses`, removal of an `Associated Address` also requires a signature from the address to be removed.
### Provider Management
While the protocol allows for users to directly call identity management functions, it also aims to be more robust and future-proof by allowing arbitrary smart contracts to perform identity management functions on a user's behalf. A `Provider` set by an identity can perform address management and resolver management functions by passing a user's `EIN` in function calls. In order to prevent `Identities` from adding an initial `Provider` that does not implement the functionality to add other `Providers`, identities may add `Providers` directly from the `Identity Registry`.
### Resolver Management
A `Resolver` is any smart contract that encodes information which resolves to an `Identity`. We remain agnostic about the specific information that can be encoded in a resolver and the functionality that this enables. The existence of resolvers is primarily what makes this ERC an identity *protocol* rather than an identity *application*. `Resolvers` resolve abstract data in smart contracts to an atomic entity, the `Identity`.
### Recovery
The specification includes a `Recovery Address` to account for instances when users lose control over an `Associated Address`. Upon `Identity` creation, the public `Recovery Address` is passed as a parameter by a provider. Recovery functionality is triggered in three scenarios:
**1. Changing Recovery Address**: If a recovery key is lost, a provider can [initiateRecoveryAddressChange](#initiaterecoveryaddresschange) through a Provider. To prevent malicious behavior from someone who has gained control of an `Associated Address` or `Provider` and is changing the `Recovery Address` to one under their control, this triggers a 14 day challenge period during which the old `Recovery Address` may reject the change. If the `Recovery Address` does not reject the change within 14 days, the `Recovery Address` is changed. However, during the fourteen day period, the `Recovery Address` can dispute the change request by calling [triggerRecovery](#triggerrecovery).
**2. Recovery**: Recovery occurs when a user recognizes that an `Associated Address` or the `Recovery Address` belonging to the user is lost or stolen. In this instance a `Recovery Address` must call [triggerRecovery](#triggerrecovery). This removes all `Associated Addresses` and `Providers` from the corresponding `Identity` and replaces them with an address passed in the function call. The `Identity` and associated `Resolvers` maintain integrity. The user is now responsible for adding the appropriate un-compromised addresses back to their `Identity`.
**3. Poison Pill**
The Recovery scheme offers considerable power to a `Recovery Address`; accordingly, the `Poison Pill` is a nuclear option to combat malicious control over an `Identity` when a `Recovery Address` is compromised. If a malicious actor compromises a user's `Recovery Address` and triggers recovery, any address removed in the `Recovery` process can call [triggerPoisonPill](#triggerPoisonPill) within 14 days to permanently disable the `Identity`. The user would then need to create a new `Identity`, and would be responsible for engaging in recovery schemes for any identity applications built in the `Resolver` or `Provider` layers.
#### Alternative Recovery Considerations
We considered many possible alternatives when devising the Recovery process outlined above. We ultimately selected the scheme that was most un-opinionated, modular, and consistent with the philosophy behind the `Associated Address`, `Provider`, and `Resolver` components. Still, we feel that it is important to highlight some of the other recovery options we considered, to provide a rationale as to how we settled on what we did.
**High Level Concerns**
Fundamentally, a Recovery scheme needs to be resilient to a compromised address taking control of a user's `Identity`. A secondary concern is preventing a compromised address from maliciously destroying a user's identity due to off-chain utility, which is not an optimal scenario, but is strictly better than if they've gained control.
**Alternative 1: Nuclear Option**
This approach would allow any `Associated Address` to destroy an `Identity` whenever another `Associated Address` is compromised. While this may seem severe, we strongly considered it because this ERC is an identity *protocol*, not an identity *application*. This means that though a user's compromised `Identity` is destroyed, they should still have recourse to whatever restoration mechanisms are available in each of their actual identities at the `Resolver` and/or `Provider` level. We ultimately dismissed this approach for two main reasons:
- It is not robust in cases where a user has only one `Associated Address`
- It would increase the frequency of recovery requests to identity applications due to its unforgiving nature.
**Alternative 2: Unilateral Address Removal via Providers**
This would allow `Providers` to remove an `Associated Address` without a signature from said address. This implementation would allow `Providers` to include arbitrarily sophisticated schemes for removing a rogue address - for instance, multi-sig requirements, centralized off-chain verification, user controlled master addresses, deferral to a jurisdictional contract, and more. To prevent a compromised `Associated Address` from simply setting a malicious `Provider` to remove un-compromised addresses, it would have required a waiting period between when a `Provider` is set and when they would be able to remove an `Associated Address`. We dismissed this approach because we felt it placed too high of a burden on `Providers`. If a `Provider` offered a sophisticated range of functionality to a user, but post-deployment a threat was found in the Recovery logic of the provider, `Provider`-specific infrastructure would need to be rebuilt. We also considered including a flag that would allow a user to decide whether or not a `Provider` may remove `Associated Addresses` unilaterally. Ultimately, we concluded that only allowing removal of `Associated Addresses` via the `Recovery Address` enables equally sophisticated recovery logic while separating the functionality from `Providers`, leaving less room for users to relinquish control to potentially flawed implementations.
*Importantly, the `Recovery Address` can be a user-controlled wallet or another address such as a multisig wallet or smart contract. This allows for sophisticated recovery structures that can be compliant, e.g. with standards such as [DID](https://decentralized.id/).*
## Rationale
We find that at a protocol layer, identities should not rely on specific claim or attestation structures, but should instead be a part of a trustless framework upon which arbitrarily sophisticated claim and attestation structures may be built.
The main criticism of existing identity solutions is that they're overly restrictive. We aim to limit requirements, keep identities modular and future-proof, and remain un-opinionated regarding any functionality a particular identity component may have. This proposal gives users the option to interact on the blockchain using an arbitrarily robust `Identity` rather than just an address.
## Implementation
**The reference implementation for ERC-1484 may be found in [hydrogen-dev/ERC-1484](https://github.com/hydrogen-dev/ERC-1484).**
#### identityExists
Returns a `bool` indicating whether or not an `Identity` denominated by the passed `EIN` exists.
```solidity
function identityExists(uint ein) public view returns (bool);
```
#### hasIdentity
Returns a `bool` indicating whether or not the passed `_address` is associated with an `Identity`.
```solidity
function hasIdentity(address _address) public view returns (bool);
```
#### getEIN
Returns the `EIN` associated with the passed `_address`. Throws if the address is not associated with an `EIN`.
```solidity
function getEIN(address _address) public view returns (uint ein);
```
#### isAddressFor
Returns a `bool` indicating whether or not the passed `_address` is associated with the passed `EIN`.
```solidity
function isAddressFor(uint ein, address _address) public view returns (bool);
```
#### isProviderFor
Returns a `bool` indicating whether or not the passed `provider` has been set by the passed `EIN`.
```solidity
function isProviderFor(uint ein, address provider) public view returns (bool);
```
#### isResolverFor
Returns a `bool` indicating whether or not the passed `resolver` has been set by the passed `EIN`.
```solidity
function isResolverFor(uint ein, address resolver) public view returns (bool);
```
#### getDetails
Returns the `recoveryAddress`, `associatedAddresses`, `providers` and `resolvers` of the passed `EIN`.
```solidity
function getDetails(uint ein) public view returns (
address recoveryAddress, address[] associatedAddresses, address[] providers, address[] resolvers
);
```
#### mintIdentity
Mints an `Identity`, setting the `Provider` to the `msg.sender`. Returns the `EIN` of the new `Identity`.
```solidity
function mintIdentity(address recoveryAddress, address provider, address[] resolvers) public returns (uint ein);
```
Triggers event: [IdentityMinted](#identityminted)
#### mintIdentityDelegated
Preforms the same logic as `mintIdentity`, but is called by a `Provider`. This function requires a signature for the `associatedAddress` to confirm their consent.
```solidity
function mintIdentityDelegated(
address recoveryAddress, address associatedAddress, address[] resolvers,
uint8 v, bytes32 r, bytes32 s, uint timestamp
) public returns (uint ein);
```
Triggers event: [IdentityMinted](#identityminted)
#### addAddress
Adds the `addressToAdd` to the `EIN` of the `approvingAddress`. Requires signatures from both the `approvingAddress` and the `addressToAdd`.
```solidity
function addAddress(
address approvingAddress, address addressToAdd, uint8[2] v, bytes32[2] r, bytes32[2] s, uint timestamp
) public;
```
Triggers event: [AddressAdded](#addressadded)
#### removeAddress
Removes the `addressToRemove` from its associated `EIN`. Requires a signature from the `addressToRemove`.
```solidity
function removeAddress(address addressToRemove, uint8 v, bytes32 r, bytes32 s, uint timestamp) public;
```
Triggers event: [AddressRemoved](#addressremoved)
#### addProviders
Adds an array of `Providers` to the `Identity` of the `msg.sender`.
```solidity
function addProviders(address[] providers) public;
```
Triggers event: [ProviderAdded](#provideradded)
#### addProviders
Adds an array of `Providers` to the `Identity` referenced by the passed `EIN`. This must be called by a `Provider`.
```solidity
function addProviders(uint ein, address[] providers) public;
```
Triggers event: [ProviderAdded](#provideradded)
#### removeProviders
Removes an array of `Providers` from the `Identity` of the `msg.sender`.
```solidity
function removeProviders(address[] providers) public;
```
Triggers event: [ProviderRemoved](#providerremoved)
#### removeProviders
Removes an array of `Providers` to the `Identity` referenced by the passed `EIN`. This must be called by a `Provider`.
```solidity
function addProviders(uint ein, address[] providers) public;
```
Triggers event: [ProviderRemoved](#providerremoved)
#### addResolvers
Adds an array of `Resolvers` to the passed `EIN`. This must be called by a `Provider`.
```solidity
function addResolvers(uint ein, address[] resolvers) public;
```
Triggers event: [ResolverAdded](#resolveradded)
#### removeResolvers
Removes an array of `Resolvers` from the passed `EIN`. This must be called by a `Provider`.
```solidity
function removeResolvers(uint ein, address[] resolvers) public;
```
Triggers event: [ResolverRemoved](#resolverremoved)
#### initiateRecoveryAddressChange
Initiates a change in the current `recoveryAddress` for a given `EIN`.
```solidity
function initiateRecoveryAddressChange(uint ein, address newRecoveryAddress) public;
```
Triggers event: [RecoveryAddressChangeInitiated](#recoveryaddresschangeinitiated)
#### triggerRecovery
Triggers `EIN` recovery from the current `recoveryAddress`, or the old `recoveryAddress` if changed within the last 2 weeks.
```solidity
function triggerRecovery(uint ein, address newAssociatedAddress, uint8 v, bytes32 r, bytes32 s) public;
```
Triggers event: [RecoveryTriggered](#recoverytriggered)
#### triggerPoisonPill
Triggers the `poison pill` on an `EIN`. This renders the `Identity` permanently unusable.
```solidity
function triggerPoisonPill(uint ein, address[] firstChunk, address[] lastChunk, bool clearResolvers) public;
```
Triggers event: [Poisoned](#poisoned)
### Events
#### IdentityMinted
MUST be triggered when an `Identity` is minted.
```solidity
event IdentityMinted(
uint indexed ein,
address recoveryAddress,
address associatedAddress,
address provider,
address[] resolvers,
bool delegated
);
```
#### AddressAdded
MUST be triggered when an address is added to an `Identity`.
```solidity
event AddressAdded(uint indexed ein, address addedAddress, address approvingAddress, address provider);
```
#### AddressRemoved
MUST be triggered when an address is removed from an `Identity`.
```solidity
event AddressRemoved(uint indexed ein, address removedAddress, address provider);
```
#### ProviderAdded
MUST be triggered when a provider is added to an `Identity`.
```solidity
event ProviderAdded(uint indexed ein, address provider, bool delegated);
```
#### ProviderRemoved
MUST be triggered when a provider is removed.
```solidity
emit ProviderRemoved(uint indexed ein, address provider, bool delegated);
```
#### ResolverAdded
MUST be triggered when a resolver is added.
```solidity
event ResolverAdded(uint indexed ein, address resolvers, address provider);
```
#### ResolverRemoved
MUST be triggered when a resolver is removed.
```solidity
event ResolverRemoved(uint indexed ein, address resolvers, address provider);
```
#### RecoveryAddressChangeInitiated
MUST be triggered when a recovery address change is initiated.
```solidity
event RecoveryAddressChangeInitiated(uint indexed ein, address oldRecoveryAddress, address newRecoveryAddress);
```
#### RecoveryTriggered
MUST be triggered when recovery is initiated.
```solidity
event RecoveryTriggered(
uint indexed ein, address recoveryAddress, address[] oldAssociatedAddresses, address newAssociatedAddress
);
```
#### Poisoned
MUST be triggered when an `Identity` is poisoned.
```solidity
event Poisoned(uint indexed ein, address recoveryAddress, address poisoner, bool resolversCleared);
```
### Solidity Interface
```solidity
pragma solidity ^0.4.24;
contract ERC1484 {
event IdentityMinted(
uint indexed ein,
address recoveryAddress,
address associatedAddress,
address provider,
address[] resolvers,
bool delegated
);
event AddressAdded(uint indexed ein, address addedAddress, address approvingAddress, address provider);
event AddressRemoved(uint indexed ein, address removedAddress, address provider);
event ProviderAdded(uint indexed ein, address provider, bool delegated);
event ProviderRemoved(uint indexed ein, address provider, bool delegated);
event ResolverAdded(uint indexed ein, address resolvers, address provider);
event ResolverRemoved(uint indexed ein, address resolvers, address provider);
event RecoveryAddressChangeInitiated(uint indexed ein, address oldRecoveryAddress, address newRecoveryAddress);
event RecoveryTriggered(
uint indexed ein, address recoveryAddress, address[] oldAssociatedAddresses, address newAssociatedAddress
);
event Poisoned(uint indexed ein, address recoveryAddress, address poisoner, bool resolversCleared);
function identityExists(uint ein) public view returns (bool);
function hasIdentity(address _address) public view returns (bool);
function getEIN(address _address) public view returns (uint ein);
function isAddressFor(uint ein, address _address) public view returns (bool);
function isProviderFor(uint ein, address provider) public view returns (bool);
function isResolverFor(uint ein, address resolver) public view returns (bool);
function getDetails(uint ein) public view returns (
address recoveryAddress, address[] associatedAddresses, address[] providers, address[] resolvers
);
function mintIdentity(address recoveryAddress, address provider, address[] resolvers) public returns (uint ein);
function mintIdentityDelegated(
address recoveryAddress, address associatedAddress, address[] resolvers,
uint8 v, bytes32 r, bytes32 s, uint timestamp
) public returns (uint ein);
function addAddress(
address approvingAddress, address addressToAdd, uint8[2] v, bytes32[2] r, bytes32[2] s, uint timestamp
) public;
function removeAddress(address addressToRemove, uint8 v, bytes32 r, bytes32 s, uint timestamp) public;
function addProviders(address[] providers) public;
function addProviders(uint ein, address[] providers) public;
function removeProviders(address[] providers) public;
function removeProviders(uint ein, address[] providers) public;
function addResolvers(uint ein, address[] resolvers) public;
function removeResolvers(uint ein, address[] resolvers) public;
function initiateRecoveryAddressChange(uint ein, address newRecoveryAddress) public;
function triggerRecovery(uint ein, address newAssociatedAddress, uint8 v, bytes32 r, bytes32 s) public;
function triggerPoisonPill(uint ein, address[] firstChunk, address[] lastChunk, bool clearResolvers) public;
}
```
## Backwards Compatibility
`Identities` established under this standard consist of existing Ethereum addresses; accordingly, there are no backwards compatibility issues. Deployed, non-upgradeable smart contracts that wish to become `Resolvers` for `Identities` will need to write wrapper contracts that resolve addresses to `EIN`-denominated `Identities`.
## Additional References
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).