22. Add and remove keys on an ONCHAINID
The keys of an ONCHAINID decide who controls it and who acts for it. The Identity Owner adds a key to enroll a new device, a passkey, an operator or a signing service, and removes a key when a device is lost or a person leaves. Both are management operations: they change who holds authority over the identity, and only a MANAGEMENT key can make them.
At a glance
Triggered by
Identity Owner
Actors
Identity Owner, Platform
Contracts
ONCHAINID, ERC734Validator
Preconditions
The identity exists (see 02); the owner controls a MANAGEMENT key of the identity; for a new signer, its signer data is known
Result
The identity's key set reflects the change: a signer holds a new purpose, or no longer holds one
Actors and roles
Identity Owner
Decides the change and authorizes it.
A MANAGEMENT key of the identity
Platform
Encodes the signer, relays the call and verifies the result.
Whatever keys the owner has authorized
Sequence
Step by step
Step 1. Prepare the change
Who: Platform, on behalf of the Identity Owner
Call: Off-chain, using read calls
Before the call: The owner knows which signer gains or loses which purpose.
What happens: For a new signer, the Platform builds its signer data: the 20 bytes of an address for an externally owned account or a smart-contract wallet, or the address of a verifier followed by the public key for a passkey or another scheme. It computes keyHash as keccak256(signerData). It reads Identity.getKey(keyHash) to see whether the signer is already registered and with which purposes, and Identity.getKeysByPurpose(purpose) to see who already holds the purpose. Before removing a MANAGEMENT purpose, it reads Identity.getKeysByPurpose(1) and confirms that another manager remains.
What changes: Nothing on-chain.
Step 2. Add a key, or a purpose to a key
Who: Identity Owner (MANAGEMENT key)
Call: Identity.addKeyWithData(keyHash, purpose, keyType, signerData, clientData) for a signer that is not registered yet. Identity.addKey(keyHash, purpose, keyType) to give another purpose to a signer that is already registered.
Before the call: Step 1 is done. The call is made by a MANAGEMENT key directly on the identity, or by the identity itself in a user operation signed by a MANAGEMENT key.
What the contract checks:
The caller holds
MANAGEMENTon the identity, or is the identity itself. Otherwise the call reverts withSenderDoesNotHaveManagementKey.For
addKeyWithData:keyHashequalskeccak256(signerData). ForaddKey: the key is already registered. OtherwiseInvalidSignerData.The signer data is at least 20 bytes and within the maximum length, and the client data is within its maximum. Otherwise
InvalidSignerLengthorClientDataTooLong.The purpose is one of the six. Otherwise
InvalidPurpose.The signer is not the key registry module itself. Otherwise
ModuleCannotBeKey.For a signer already registered:
keyTypeis the type it was registered with. OtherwiseKeyTypeMismatch.The signer does not already hold this purpose. Otherwise
KeyAlreadyRegistered.
What changes: The registry records the signer if it is new, and adds the purpose to it. It emits KeyAdded(identity, keyHash, purpose, keyType, signerData, clientData, caller). From the next call, the signer has the authority of that purpose.
Step 3. Remove a purpose from a key
Who: Identity Owner (MANAGEMENT key)
Call: Identity.removeKey(keyHash, purpose)
Before the call: Step 1 is done. To strip a signer entirely, the owner removes each of its purposes.
What the contract checks:
The caller holds
MANAGEMENTon the identity, or is the identity itself. OtherwiseSenderDoesNotHaveManagementKey.The key is registered. Otherwise
KeyNotRegistered.When the purpose is
MANAGEMENTand the key is a signer: anotherMANAGEMENTsigner remains. OtherwiseCannotRemoveLastManagementKey.The key holds the purpose. Otherwise
KeyDoesNotHavePurpose.
What changes: The purpose is removed from the key. When it was the key's last purpose, the key's record is deleted. The registry emits KeyRemoved(identity, keyHash, purpose, caller). From the next call, the signer no longer has that authority. Requests it had queued in the execution queue can no longer be approved.
State after the flow
Identity.keyHasPurpose(keyHash, purpose)reflects the change.Identity.getKeysByPurpose(purpose)includes or no longer includes the key.Identity.getKey(keyHash)returns the key's purposes, type and signer data, or nothing if the key was deleted.The identity always has at least one
MANAGEMENTkey that can sign.The identity's claims, linked wallets, positions and eligibility are unchanged.
Failure modes
Reverts with SenderDoesNotHaveManagementKey
The caller holds ACTION or another purpose, not MANAGEMENT (check 1)
Use a MANAGEMENT key
A user operation that adds a key is rejected
It was signed by an ACTION key. Calls to the identity itself are management operations
Sign with a MANAGEMENT key
Reverts with InvalidSignerData
keyHash is not the hash of signerData, or addKey was used for a signer that is not registered (step 2, check 2)
Recompute the hash. Use addKeyWithData for a new signer
Reverts with InvalidSignerLength
The signer data is shorter than 20 bytes or too long (step 2, check 3)
Encode an address as its 20 bytes. Encode other signers as verifier then key
Reverts with InvalidPurpose
The purpose is not between 1 and 6 (step 2, check 4)
Use a defined purpose
Reverts with KeyTypeMismatch
The type given differs from the one the key was registered with (step 2, check 6)
Pass the registered type, read with getKey
Reverts with KeyAlreadyRegistered
The signer already holds this purpose (step 2, check 7)
Nothing to do
Reverts with KeyNotRegistered
The key is not on the identity (step 3, check 2)
Check the hash. For an address, it is the hash of the 20 address bytes
Reverts with CannotRemoveLastManagementKey
The key is the identity's last manager that can sign (step 3, check 3)
Add another MANAGEMENT key first
Reverts with KeyDoesNotHavePurpose
The key does not hold the purpose (step 3, check 4)
Read getKeyPurposes(keyHash)
A passkey was added and cannot call the identity directly
A direct call is authorized from the caller's address, and a passkey has none
A passkey acts through user operations
Common misunderstandings
Wrong: "Adding a key links a wallet."
Right: Keys and links are separate. A key gives a signer authority over the identity. A link attributes an address, and what it holds, to the identity. An address can be a key and not a linked wallet, or the reverse.
Wrong: "An
ACTIONkey can add anotherACTIONkey."Right: Every change to the key set is a management operation. An
ACTIONkey acts toward other contracts, and has no route to the identity's own keys, through a user operation, a direct call or the execution queue.
Wrong: "Removing a key removes the signer."
Right:
removeKeyremoves one purpose. A signer that holdsMANAGEMENTandACTIONkeeps the other after one call. The record is deleted when the last purpose goes.
Wrong: "The key hash of an address is the hash of its ABI encoding."
Right: A key hash is
keccak256of the signer data. For an address, the signer data is the 20 address bytes, not the 32-byte ABI encoding that earlier versions hashed.
Integration notes
Enroll a second
MANAGEMENTsigner early, on another device. An identity with one manager and no guardians is lost with that key.Use
addKeyWithDatafor every new signer.addKeyonly adds a purpose to a signer the identity already knows.Give each key the least purpose it needs:
ACTIONfor daily use,CLAIM_SIGNERfor a signing service,PROPOSERfor an automated system.When a device is lost, remove each purpose of its key. Then review requests pending in the execution queue.
Index
KeyAddedandKeyRemovedon theERC734Validator, filtered by identity. They carry the caller, so every change is attributable.A Claim Issuer rotates its signing keys with this flow. Removing
CLAIM_SIGNERfrom a key invalidates every claim that key signed, at once.
Related flows
02 Deploy an ONCHAINID : sets the first keys
21 Recover access to an ONCHAINID : when no
MANAGEMENTkey is left07 Link a wallet to an ONCHAINID : links are not keys
19 Revoke a claim : the effect of rotating a Claim Issuer's signing key
Last updated