Multi-CA aggregator that fronts the IIdentityRegistry interface for the protocol.
IdentityGate is the contract every flow consults to check whether a
given address is identity-verified. It’s a thin aggregator —
verification logic itself lives in one or more underlying
IIdentityRegistry instances (one per CA / attestation issuer); the
gate combines their results so callers (CommitmentPool,
RelayerRegistry) only need to know about a single address.
Conceptual overview: see Architecture / Identity gate. This page is the on-chain reference.
Storage
IIdentityRegistry[] public registries; // ordered list of CAs
mapping(address => bool) public registryExists; // dedupe set
uint256 public constant MAX_REGISTRIES = 10;The gate itself has no per-user storage — every read fans out to the registered CAs.
External entry points
Registry management (owner-only)
function addRegistry(address _registry) external;
function removeRegistry(address _registry) external;
function getRegistryCount() external view returns (uint256);
function getRegistries() external view returns (address[] memory);removeRegistry keeps at least one entry (NoRegistries revert if
you’d empty the gate); add/remove cycles use a swap-and-pop on the
registries array.
IIdentityRegistry implementation
function isVerified(address user) external view returns (bool);
function verifiedUntil(address user) external view returns (uint64);
function paused() external view returns (bool);| Method | Behaviour |
|---|---|
isVerified(user) | true if any registered CA returns true. Reverts in any one CA are caught and skipped — one misbehaving registry can’t DoS the whole gate. |
verifiedUntil(user) | Latest expiry across all CAs (0 = unverified everywhere). |
paused() | Conservative: returns true if any one CA is paused. Doesn’t actually block isVerified (other CAs can still pass). |
Deployment pattern
zkScatter’s reference deployment uses two separate gate instances:
| Instance | Wired into | What it gates |
|---|---|---|
User IdentityGate | CommitmentPool (deposit + withdraw) | End-user deposits and withdrawals |
Relayer IdentityGate | RelayerRegistry.identityRegistry | Relayer registration |
Each gate can hold its own list of CAs — e.g. relayer registration might require institutional KYC while user deposits accept retail attestations.
Events
event RegistryAdded(address indexed registry);
event RegistryRemoved(address indexed registry);Errors
| Error | When |
|---|---|
RegistryAddressZero | addRegistry(address(0)) or constructor with zero |
RegistryAlreadyAdded | Adding the same registry twice |
RegistryNotFound | removeRegistry for an address that isn’t registered |
NoRegistries | removeRegistry would empty the list |
TooManyRegistries | More than MAX_REGISTRIES (10) |
RenounceOwnershipDisabled | renounceOwnership() is bricked to prevent lockout |
Where the underlying CAs come from
The actual zk-X509 verification logic lives in the
IIdentityRegistry instances the gate aggregates over —
zkX509IdentityRegistry, sanctions wrappers, manual allowlists,
etc. Each must implement:
interface IIdentityRegistry {
function isVerified(address user) external view returns (bool);
function verifiedUntil(address user) external view returns (uint64);
function paused() external view returns (bool);
}The gate doesn’t dictate which scheme they use; a CA could be a
zk-X509 verifier, a curated manual allowlist, or a sanctions
denial layer (note that “denial layers” are typically wired
elsewhere — CommitmentPool.sanctionsList is its own slot).
SDK status
The @zkscatter/sdk/identity module — client-side zk-X509 proof
generation and attest() calldata — ships in Phase 7. Phase
0–3 of the SDK don’t expose identity primitives; the gate is wired
into the contracts but apps expect the user to have already
attested via a separate flow.
Address by network
NetworkConfig.contracts.identityGate. ABI ships as
IDENTITY_GATE_ABI / IDENTITY_GATE_IFACE.