Keystore contract
The Keystore is the on-chain registry of viewing keys. Recipients register by calling setAuthPolicy and then setViewingKey, and senders read the public viewingKeys(address) mapping to encrypt notes for them.
State
viewingKeys(address) → bytes32 The X25519 viewing public key registered for this EVM address. Returns 0x0 if unregistered. This is an external view getter over ERC-7201 namespaced storage (not a public state variable). The SDK wraps it as keystoreInteractor.getViewingKey().nullifyingKeys(address) → uint256 The on-chain commitment to the address's nullifying key (nullifyingKeyHash), set once per account by setAuthPolicy. Returns 0 if the account hasn't registered. There is no authPolicy accessor or AuthPolicy struct, and no revocable-key index is stored on-chain. Clients track that index off-chain.currentRoot() → uint256 Current Merkle root over all registered accounts (capped at 2^18 leaves by maxKeystoreTreeSize at the current deployment, while the LeanIMT depth grows dynamically as accounts register). Used by transact circuit witnesses. isKnownRoot(uint256 root) → bool returns true if root is the current root or a superseded root still within its liveness window. previousRoots(uint256 root) → uint256 is the mapping accessor that returns the validUntil deadline (0 if the root was never superseded). It is not a boolean checker.Main functions
setAuthPolicy(uint256 _authDigest, uint256 _nullifyingKeyHash) Initial registration. Binds your _nullifyingKeyHash (the commitment to your privateNullifyingKey) to the account, storing it once to be read back on every rotation, and sets the first authDigest = Poseidon(AUTH_TYPE_DIRECT_KEY, privateRevocableKey). The nullifying-key hash is not a rotation index. Clients track the revocableKeyIndex off-chain and derive a fresh authDigest per rotation.setAuthPolicyWithSig(address _account, uint256 _authDigest, uint256 _nullifyingKeyHash, uint256 _deadline, bytes _signature) Relayed registration. Anyone may submit it; the registration is authorised by an EIP-712 signature from _account over SetAuthPolicy(account, authDigest, nullifyingKeyHash, deadline) under the PrivacyPoolsKeystore v1 domain (SET_AUTH_POLICY_TYPEHASH), checked with SignatureChecker, so an ERC-1271 contract account such as a Safe can register. Reverts Keystore_SignatureExpired past _deadline. Every guard, the leaf, and the event are keyed on _account, not msg.sender, so a relayed registration is on-chain indistinguishable from self-registration. No nonce is needed: registration is one-shot per account, so a signature can only ever succeed once. PrivacyPoolRelay.registerAndRelay bundles it with a withdrawal.updateAuthPolicy(uint256 _oldAuthDigest, uint256 _updatedAuthDigest, uint256[] _siblingNodes) Rotation. Swaps the revocable-key authDigest in place, proving the current keystore leaf with _siblingNodes. The stored nullifying-key hash from registration is reused unchanged. Rotation never rebinds it.setViewingKey(bytes32 _viewingKey) Publish the X25519 pubkey for discoverable receives. Permissionless and repeatable. Passing bytes32(0) unregisters (disables) the viewing key.viewingKeys(address) → bytes32 Read someone's registered viewing key (external view getter; see State above).Initialization
initialize(uint256 _keystoreRootLiveness, uint256 _maxKeystoreTreeSize) UUPS initializer. Sets the grace window (seconds) during which a superseded root stays valid and the maximum keystore tree size cap. The deployed configuration uses keystoreRootLiveness = 1200 (twenty minutes) and maxKeystoreTreeSize = 2^18.Root liveness is a revocation delay
Every registration or rotation replaces the root, and the replaced root is stamped validUntil = block.timestamp + keystoreRootLiveness. isKnownRoot accepts the current root or any superseded root still inside its window, so a proof built moments before someone else registered still lands. The same window means that after you rotate your revocable key with updateAuthPolicy, a proof built against the old leaf remains acceptable for up to twenty minutes. Treat rotation as recovery with a bounded delay, not as instant revocation; see the threat model.
Events
AuthPolicySet(address indexed _account, uint256 _nullifyingKeyHash, uint256 _authDigest) Emitted on the first setAuthPolicy for an account.AuthPolicyUpdated(address indexed _account, uint256 _oldAuthDigest, uint256 _authDigest) Emitted on updateAuthPolicy.ViewingKeySet(address indexed _account, bytes32 _oldViewingKey, bytes32 _newViewingKey) Emitted on setViewingKey.KeystoreRootLivenessUpdated(uint256 _previousLiveness, uint256 _newLiveness) Emitted on setKeystoreRootLiveness.MaxKeystoreTreeSizeUpdated(uint256 _previousSize, uint256 _newSize) Emitted on setMaxKeystoreTreeSize.X25519 viewing-key format
The viewing key is 32 bytes (the X coordinate). Current deployments use 32-byte X25519 viewing keys, while older deployments may carry incompatible 33-byte compressed secp256k1 keys, and mixing the two trips schema validation.
Roles
UPGRADER_ROLE Admin role for the Keystore. Gates setKeystoreRootLiveness, setMaxKeystoreTreeSize, and the UUPS upgrade authorization. (There is no separate KEYSTORE_KEEPER_ROLE. Admin operations share the UPGRADER_ROLE defined in utils/RolesConstants.sol.)Source: v2-monorepo/packages/contracts/src/contracts/Keystore.sol