Writing Encrypted Data to Contract
This page covers the “encrypt → write tx” flow: encrypt plaintext values into ciphertext hashes and pass them, plus one shared signature, directly into a contract call.
encryptInputs returns a [...hashes, signature] tuple — one external* handle per input, followed by a single signature that authenticates the whole batch together. The on-chain CoFHE library validates that signature before the contract can use any of the ciphertexts.
The flow is as follows:
- Ensure your contract function accepts
external*handles, each followed by abytesparameter carrying the shared signature. - Encrypt the plaintext values with
encryptInputs. - Send a transaction, passing the hashes and the signature as arguments.
Prerequisites
-
Create and connect a client (see the client page).
-
Your contract function must accept
external*handles (externalEbool,externalEuint32, etc.) plus abytesparameter carrying the shared batch signature. Convention is to place thebytesimmediately after the handle it authenticates —FHE.asEuintXX(hash, proof)is a pair. Other arguments may follow.
The encrypted type you choose in JavaScript/TypeScript must match the Solidity parameter type:
Encryptable.uint32(...)→externalEuint32Encryptable.bool(...)→externalEboolEncryptable.address(...)→externalEaddress
Example: encrypt and call a contract
The reference contract used throughout these docs is EncryptedCounter.sol. It exposes setCount(externalEuint32[], bytes) which is the canonical “encrypt input → send in the same transaction” flow.
// From EncryptedCounter.sol (see /reference/encrypted-counter-sol)
function setCount(externalEuint32[] memory _inCounts, bytes memory _signature) external onlyOwner {
count = FHE.asEuint32s(_inCounts, _signature)[0];
FHE.allowThis(count);
FHE.allowSender(count);
decrypted = false;
decryptedCount = 0;
}Automatic argument wiring with @cofhe/abi
For contracts with a mix of plain and encrypted arguments, @cofhe/abi's extractEncryptableValues/insertEncryptedValues will detect the external* parameters and inject the signature for you, so you can call encryptInputs and build the final call args from the ABI alone — see the @cofhe/react useCofheEncryptAndWriteContract hook for a ready-made version of this pattern.
Common pitfalls
- Wrong
Encryptabletype: theEncryptable.*factory must match the Solidity parameter type (externalEuint32vsexternalEuint64, etc). - Missing or misplaced signature parameter: a contract function with
external*inputs needs a plainbytesparameter immediately after them to receive the shared batch signature. Other arguments may follow it, but the encrypted parameters themselves must be adjacent. - Wrong account / chain: encrypted inputs are authorized for a specific
account + chainId. If you encrypt under the wrong wallet/network, the contract call may revert. - Splitting a batch: don't reorder the hashes relative to how they were encrypted, and don't pass a subset of a batch's hashes with its signature — the signature covers the exact ordered set produced by that
execute()call.