Credential lifecycle
A credential is not a token. It is an entry in KeyringCore’s storage: an
expiration timestamp for a (policyId, entity) pair, plus a blacklist flag.
Nothing is minted and nothing is transferable.
(policyId, entity) pair plus a blacklist flag.Issuance
After a successful verification, the extension hands the app a
CredentialData object whose fields map directly onto
createCredential(tradingAddress, policyId, chainId, validUntil, cost, key, signature, backdoor). The transaction pays cost in native units
(msg.value must match exactly), and the issuer signature binds every
parameter, including the chain ID.
The backdoor bytes accompany the credential on chain and are emitted with
CredentialCreated. They contain the regime-encrypted identity described in
Learn: credentials; the contract stores no identity
data itself.
Validity
- A credential is valid while
block.timestamp < expand the entity is not blacklisted. - Re-issuing extends
exp; a new credential’svalidUntilmust exceed the current one. Validity windows are set by the policy, so different policies produce different credential durations. - Expiry is passive. Nothing needs to be revoked or cleaned up; checks simply begin returning false.
Frontends can read entityExp(policyId, entity) to warn holders ahead of
expiry and route them through a refresh, which for an unexpired attestation
does not require a new proof (see
tracking state).
Revocation and blacklisting
Two mechanisms end a credential early:
- Blacklisting (
blacklistEntity) is policy-scoped and reversible. While blacklisted, an entity fails every check for that policy and cannot be issued a new credential. - Issuer key revocation (
revokeKey) invalidates the issuing key for future credentials. Existing credentials keep their stored expirations.
Cost
The cost parameter is set per credential by the issuing policy and paid at
creation. Fees accumulate in the contract; the operator role withdraws them
with collectFees(recipient). A cost of zero is rejected.
Chain scoping
createCredential requires chainId == block.chainid, and the chain ID is
inside the signed message. A credential is therefore only ever valid on the
chain it was issued for; multi-chain apps run one verification per chain, and
each chain’s gate reads its own deployment.