Overview
The old decryption pattern usedFHE.decrypt(ctHash) to trigger an asynchronous decryption, followed by FHE.getDecryptResultSafe(ctHash) to read the result once available. The new pattern replaces FHE.decrypt with an offchain decryption step using the Client SDK, and uses FHE.publishDecryptResult to submit the result onchain with a cryptographic proof.
FHE.decrypt no longer exists in FHE.sol. A contract that still calls it does not compile.
This guide walks through concrete before/after Solidity examples.
What changed
The key difference:
FHE.decrypt triggered decryption without any proof. The new flow returns a signature from Teecryptor, which decrypts inside a hardware-attested TEE. Your contract verifies that signature before it uses the plaintext.The new decryption flow
1
Grant decryption permission (onchain)
Instead of calling
FHE.decrypt(), mark the value as decryptable:2
Decrypt offchain (client-side)
The client requests decryption from Teecryptor, which returns the plaintext and an ECDSA signature over it:
3
Submit result onchain with proof
The decrypted value and signature are submitted to your contract:
Example 1: Counter contract
A minimal example showing how the reveal pattern changes.Example 2: Token unshield (FHERC20Wrapper)
The unshield flow is whereFHE.decrypt was most commonly used. It already followed a two-step pattern (unshield + claim), which maps naturally to the new flow.
FHE.decrypt(burned)toFHE.allowPublic(burned): no onchain decryption is triggered, only a permission grantclaimUnshielded(bytes32 ctHash)toclaimUnshielded(bytes32 ctHash, uint64 decryptedAmount, bytes signature): the caller now provides the decrypted value + proofFHE.getDecryptResultSafetoFHE.publishDecryptResult: the contract verifies the Teecryptor signature instead of polling for a result
Example 3: Revealing a vote result
Reveal a single encrypted vote count after a deadline.publishDecryptResult vs verifyDecryptResult
verifyDecryptResult reverts when the signature is invalid. verifyDecryptResultSafe returns false instead, so you can choose your own revert message.
Use either one when you only need to confirm the plaintext is authentic and don’t need other contracts or future calls to read it:
Migration checklist
1
Find all FHE.decrypt calls
Search your contracts for
FHE.decrypt(. Each call needs to be replaced.2
Replace FHE.decrypt with FHE.allowPublic
In the function that previously called
FHE.decrypt(ctHash), replace it with FHE.allowPublic(ctHash).3
Add a finalize function
Create a new function that accepts
(plaintext, signature) parameters and calls FHE.publishDecryptResult or FHE.verifyDecryptResult.4
Update client code
Add the offchain decryption step using
client.decryptForTx() between the two onchain calls.Next steps
- Read about Decryption Operations for the full reference
- See Adding FHE to an Existing Contract for a complete contract migration
- Learn about Access Control for managing decrypt permissions
- Check the Client SDK decrypt guide for the full client-side API