# Keystore contract

> The Keystore is the on-chain registry of [viewing keys](/concepts/keys). Recipients register by calling `setAuthPolicy` and then `setViewingKey`, and senders read the public `viewingKeys(address)` mapping to encrypt [notes](/concepts/notes) for them.

The Keystore is the on-chain registry of [viewing keys](/concepts/keys). Recipients register by calling `setAuthPolicy` and then `setViewingKey`, and senders read the public `viewingKeys(address)` mapping to encrypt [notes](/concepts/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](/protocol/circuits/transact) 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](/protocol/contracts/privacy-pool-relay)`.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](/introduction/threat-model#root-freshness-windows).

## 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`](/protocol/contracts/access-control) defined in `utils/RolesConstants.sol`.)

Source: `v2-monorepo/packages/contracts/src/contracts/Keystore.sol`
