ERC-7734 A decentralized identity verification standard (Decentralized Identity Verification (DID))
Final · Signatures & auth · created 2024-06-26
A minimal standard that keeps identity information such as names and documents off-chain and puts only the hash of that information on-chain, so that identities can be created, verified, and revoked.
Why it is needed
Today, identity verification is mostly handled by central authorities. The process is cumbersome, personal data gathered in one place is easily exposed to breaches, and users cannot control their own identity data. Blockchain-based decentralized identity (DID) solutions have emerged as an alternative, but existing approaches handle many attributes and interactions, so their structure is complex and developers and users find them hard to adopt. This standard does the opposite and keeps the structure as simple as possible. On-chain, it records only the address, the hash, and the verification status, and leaves detailed identity attributes such as name or age, along with the actual verification procedure, to off-chain systems. As a result, sensitive information is not stored directly on the blockchain, so privacy is protected, and because it is easy to integrate into dapps, it can aim for broader adoption.
What to implement
event IdentityCreated(address indexed userAddress, bytes32 identityHash, uint256 timestamp);: The event emitted when a new identity is created, along with the user address, the identity hash, and the timestamp.event IdentityVerified(address indexed userAddress, bytes32[2] verificationHashes, uint256 timestamp);: The event emitted when the verification status is updated, along with the two verification hashes.event IdentityRevoked(address indexed userAddress, uint256 timestamp);: The event emitted when an identity verification is revoked.function createIdentity(bytes32 identityHash) external;: Creates a new decentralized identity for the caller. It takes the hash of the identity data.function verifyIdentity(bytes32[2] calldata verificationHashes) external;: Sets the caller's identity to the verified status. It takes two verification hashes obtained from sources such as off-chain proofs, and the meaning of the hashes and how they are generated are left to the implementer.function revokeIdentity() external;: Revokes the verification status of the caller's identity.function getIdentity(address userAddress) external view returns (Identity memory);: Looks up the identity struct for the given address.
In depth
The Identity struct: the minimum information kept on-chain
An identity consists of only five fields: userAddress (the user's Ethereum address), identityHash (the hash of the identity data), verificationHashes (the two hashes used for verification, bytes32[2]), isVerified (whether it is verified), and timestamp (the time the identity was created). The identity itself is represented by a unique address (public key), and additional attributes such as name or age are all optional and left to off-chain management. This minimal structure keeps the implementation light, avoids unnecessary complexity, and is the core of a design meant to broaden adoption.
createIdentity: creating an identity
The caller creates their own identity by passing identityHash, the hash of the identity data. The reference implementation checks identities[msg.sender].userAddress == address(0) and reverts with "Identity already exists" if an identity for the same address already exists. On creation, verificationHashes is initialized to the empty hashes [bytes32(0), bytes32(0)], isVerified to false, and timestamp is set to block.timestamp, and an IdentityCreated event is emitted.
verifyIdentity: the meaning of the verification hashes is up to the implementer
When the caller submits bytes32[2] verification hashes, the contract stores them, sets isVerified to true, and emits an IdentityVerified event. The verification hashes can be derived from off-chain proofs, cryptographic challenges, attestations, and so on, and their exact meaning and derivation method are intentionally left to the implementer. The rationale for this design is that leaving the interpretation open lets each implementation flexibly attach the verification method it needs without putting sensitive information on-chain. The reference implementation only checks that the identity exists and does not verify the contents of the hashes themselves.
revokeIdentity and getIdentity: revocation and lookup
revokeIdentity sets the caller's isVerified back to false and emits an IdentityRevoked event. In the reference implementation, revocation only clears the verification status and does not delete the identity struct itself. getIdentity is a lookup function that takes any address and returns that address's Identity struct, and it is the path through which a dapp reads a user's verification status. Creation, verification, and revocation are all recorded as events, which ensures transparency and traceability.
How it differs from other DID standards
Whereas existing decentralized identity solutions try to fit a wide range of identity attributes and interactions into the standard, this standard focuses on minimalism, user control, and privacy. It keeps the on-chain structure simple and relies on off-chain mechanisms for detailed identity management and verification. The proposal argues that this simplicity lowers the barrier to adoption and makes it a good choice for dapps that prioritize a user-centered, secure ecosystem.
Things to watch out for
- Safe hash usage: The identity hash and verification hashes must be generated with a secure hashing algorithm to prevent collisions and preserve data integrity.
- Replay attack protection: The confirmation hash must include a nonce or a timestamp to prevent replay attacks, in which a hash that has already been used is submitted again.
- Implementation responsibility: Implementing developers are responsible for making the generation and verification of the hash robust against tampering.