Permit model
How EIP-712 signed permits authorize FHE decryption for specific contracts.
Decrypting an on-chain FHE ciphertext requires two things: a transport key pair (generated once, stored persistently — see Security Model) and a signed permit that authorizes decryption for specific contract addresses. This page explains how permits work.
What is a permit
A permit is an EIP-712 typed data signature from the user's wallet. It binds:
A set of contract addresses (up to 10 per permit).
A chain ID.
A start timestamp and duration (derived from
permitTTL).The signer address.
An optional delegator address (for delegated decryption).
The relayer verifies this signature before re-encrypting any ciphertext. Without a valid permit covering the target contract, the relayer rejects the request.
Key properties
Immutable — the signed contract addresses are part of the EIP-712 payload and cannot be edited after signing.
Chunked — each permit covers at most 10 contracts. When more are needed, the SDK chunks the addresses and requests one wallet signature per chunk.
Chain-scoped — stored under
(signerAddress, chainId, delegatorAddress), so permits on Sepolia never collide with permits on Mainnet, and direct permits never collide with delegated ones.Additive — calling
permits.grantPermit()with new contracts signs additional permits for the uncovered subset only. Existing permits remain valid and are not re-signed.Time-bounded — each permit records its creation timestamp and duration. Expired permits are pruned on next access.
Lifecycle
First visit
User connects wallet.
App calls
permits.grantPermit([contractA, contractB]).SDK checks storage — no permits cover these contracts yet.
SDK builds EIP-712 typed data (contract addresses, timestamp, duration) and requests a wallet signature.
Wallet signs → permit is stored (keyed by signer address, chain ID, and delegator).
Ready — subsequent decrypts reuse the stored permit silently.
Returning visit
Page loads → SDK reads permits from storage.
Permits cover the requested contracts and haven't expired.
Ready — no wallet popup.
New contract coverage
Decrypt request arrives for a contract not covered by existing permits.
SDK signs a new permit for the uncovered contracts only.
New permit is appended alongside existing ones — nothing is invalidated.
Expiration
A permit's duration elapses (default: 30 days, configurable via
permitTTL).Permit is pruned from storage on next access.
The next decrypt for that contract set prompts a single wallet re-sign.
The transport key pair is not affected.
Each permit records its start timestamp and duration at creation time. Changing permitTTL between sessions does not retroactively alter existing permits — they use their original duration.
How additive permits work
Unlike a session model where re-authorizing replaces the previous authorization, permits are purely additive:
This means users see fewer wallet popups over time. As they interact with more contracts, their permit coverage grows without invalidating earlier permits.
Batch all contract addresses you expect to need into a single permits.grantPermit() call to minimize wallet popups. Each uncovered chunk of up to 10 contracts triggers one signature prompt.
Revocation
Permits can be removed in two ways:
Selective —
sdk.permits.revokePermits(["0xTokenA"])removes permits touching those contracts on the current chain. Other permits are untouched.Full wipe —
sdk.permits.revokePermits()removes all permits for the current signer across all chains and delegators. The transport key pair is not affected.
For a complete "log out" that also removes the transport key pair, use sdk.permits.clear(). See the ZamaSDK reference for the full API.
Wallet account changes
The SDK automatically manages permits when the wallet state changes:
Disconnect / lock
All permits and transport key pair cleared for the previous account
Account switch
Previous account's permits cleared; new account starts fresh
Chain switch
Permits are chain-scoped, so existing permits on the previous chain remain intact
See ZamaSDK.onWalletAccountChange for programmatic access to these transitions.
Related
Security Model — transport key pair storage, threat model, and trust assumptions
Configuration —
transportKeyPairTTLandpermitTTLsettingsZamaSDK —
permits.grantPermit(),permits.revokePermits(),permits.clear()API
Last updated