Skip to main content

Motivation

Consider the following scenario: Your contract receives an encrypted input that should remain confidential.
If there were no access control mechanisms, someone could observe the handle value used above and reuse it by initializing a local variable with the same value.
To prevent misuse, all FHE operations verify that the caller has explicit permission on every input ciphertext handle.

How Access Control Works

In practice, the code above will revert with an ACLNotAllowed error because the calling contract doesn’t have permission for that ciphertext handle. Any FHE operation will fail if the caller lacks permission for all input handles.

Example: Unauthorized Operations Fail

By default, newly created ciphertext handles are accessible to the contract that created them, but only for the duration of the transaction. Any additional access must be explicitly granted.

What the ACL Does Not Gate

The ACL governs operation creation. Computing on a handle, and requesting its decryption. It does not gate the onchain publication of a decrypt result:
publishDecryptResult (and verifyDecryptResult) only check that signature is a valid ECDSA signature from the Threshold Network over that exact (ctHash, result, chainId, encType) tuple. Publication is deliberately permissionless so clients and relayers can settle results onchain. What protects the value is that an attacker cannot obtain that signature. The signature is only issued by the Threshold Network in response to a decryption request, and that request is ACL-gated: the requester must either hold permission on the handle (with a valid ACP) or the handle must have been marked with FHE.allowPublic(). Without permission, the request is denied and no signature is ever produced, so there is nothing valid to publish.
Treat FHE.allowPublic() as the real decision point. Once a handle is marked public, anyone can request its decryption and publish the plaintext onchain, permanently.

Granting Access

CoFHE provides six methods to grant access to ciphertext handles:

FHE.allowThis()

FHE.allowThis(CIPHERTEXT_HANDLE)Allows the current contract access to the handle. Use this when you want the contract itself to retain access to a ciphertext beyond the current transaction.

FHE.allowSender()

FHE.allowSender(CIPHERTEXT_HANDLE)Allows the transaction sender (msg.sender) access to the handle. Use this when you want to grant the caller of the function access to the ciphertext.

FHE.allow()

FHE.allow(CIPHERTEXT_HANDLE, ADDRESS)Allows a specific address persistent access to the handle. Use this when you want to grant permanent access to another contract or user.

FHE.allowTransient()

FHE.allowTransient(CIPHERTEXT_HANDLE, ADDRESS)Allows a specific address temporary access to the handle for the duration of the transaction only. Use this for cross-contract calls within the same transaction.

FHE.allowPublic()

FHE.allowPublic(CIPHERTEXT_HANDLE)Marks a ciphertext handle as eligible for public decryption. Anyone can then request decryption of this value offchain via decryptForTx and publish or verify the result onchain.Use this when a value is intended to become public, for example, the amount being unshielded in an FHERC20 unwrap flow.
allowPublic does not reveal the value immediately. It only grants permission for anyone to request decryption. The value is revealed only when someone submits the plaintext and signature onchain via FHE.publishDecryptResult or FHE.verifyDecryptResult.

Decryption and Access Control

Decryption is a multi-step process: a client requests the plaintext and a threshold signature offchain via decryptForTx, then publishes or verifies the result onchain. Access control governs who can request decryption:
  • If the ciphertext was marked with FHE.allowPublic(), anyone can request decryption without an ACP (.withoutACP()).
  • Otherwise, only addresses with explicit permission on the handle can request decryption, and must provide a valid ACP (.withACP()).
If the requester does not have permission on the ciphertext handle, the decryption request will be denied by the access control system. Grant appropriate permissions before attempting to decrypt, use FHE.allowPublic() for values intended to become public, or FHE.allow() / FHE.allowSender() for restricted access.

Behind the Scenes

Every blockchain integrating CoFHE includes a deployed ACL.sol contract. This contract manages ownership records for each ciphertext, ensuring that only authorized owners can perform operations on their encrypted data.

ACL Storage Structure

The ACL contract contains the following mapping which tracks the ownership of each ciphertext handle:
This two-level mapping allows efficient lookup of whether a specific address has permission to access a specific ciphertext handle.

Best Practices

Principle of Least Privilege

Only grant access to addresses that genuinely need it. Avoid using allowPublic() unless the data is truly meant to be public.

Use Transient for Cross-Contract Calls

When calling other contracts within a transaction, use allowTransient() instead of permanent access to limit exposure.

Track Your Permissions

Keep track of which addresses have access to which ciphertexts, especially in complex multi-contract systems.

Clean Up When Possible

Consider the lifecycle of your encrypted data and whether permissions should be revoked after certain operations.

Practical Examples

For detailed examples on how to explicitly manage ciphertext allowances in contracts, see the ACL Usage Examples guide.

Quick Example: Token Transfer