Encrypting Inputs
encryptInputs encrypts plaintext values into FHE ciphertexts that can be passed as inputs to a confidential smart contract transaction. Values must be encrypted before being passed on-chain to preserve confidentiality.
It returns an EncryptInputsBuilder which lets you configure the encryption and then call .execute() to run it.
The flow is as follows:
- Decide which plaintext value(s) you want to encrypt.
- Wrap each value with a typing helper (e.g.
Encryptable.uint32(…)) to construct typed encryptable inputs (seeEncryptable— typing inputs). - Call
.setConsumingContract(address)with the address of the contract you'll submit the result to — the verifier binds this into the signed digest, so a batch signed for one contract can't be replayed into another. - Call
client.encryptInputs([...]).setConsumingContract(...).execute()to produce a batch-verified result — one ciphertext hash per input, followed by a single shared signature. - Submit a transaction to your contract, passing the hashes and signature as
external*+bytesparameters (see Writing encrypted data to a contract).
Prerequisites
- Create and connect a client (see the client page).
encryptInputs(...) requires a connected client so the SDK can resolve chainId, account, and the underlying RPC clients.
- Know which encrypted type you want to encode each value as.
The type is chosen by which Encryptable.* factory you use (and must match the Solidity parameter type your contract expects, e.g. externalEuint32 vs externalEuint64).
Basic usage
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([
Encryptable.uint32(42n),
Encryptable.bool(true),
Encryptable.address('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'),
])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.execute();
const [eUint32Hash, eBoolHash, eAddressHash, signature] = encrypted;The return value is a typed tuple: one hash per encryptable you passed in (in order), followed by a single signature that authenticates the whole batch. For the exact shape, see What encryptInputs returns.
What encryptInputs returns
Running .execute() returns a HashPlusProofResult tuple: [...hashes, signature].
- One
0x-prefixed handle hash per input, in the same order as the array you passed toencryptInputs([...])— typed asexternalEuint*/externalEbool/externalEaddresson the Solidity side. - One trailing
bytessignature, shared by the whole batch. The CoFHE verifier signskeccak256(h_0 || h_1 || ... || h_n), where eachh_ibinds that ciphertext's hash to its type, security zone, account, and chain ID — a single call to your contract must therefore submit all of a batch's hashes together with that one signature; you can't split a batch apart after the fact.
For the exact shape, see HashPlusProofResult — the result type.
Builder API
.execute() — required, call last
Runs the encryption pipeline and returns the [...hashes, signature] tuple.
await cofheClient.connect(publicClient, walletClient);
const [encryptedAge, encryptedFlag, signature] = await cofheClient
.encryptInputs([Encryptable.uint8(25n), Encryptable.bool(true)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.execute(); .setConsumingContract(address) — required
Sets the contract that will consume the resulting hashes+signature — i.e. the contract whose function call will pass them into FHE.asEuint*s(...). The CoFHE verifier binds this address into the signed digest, so a batch signed for one contract cannot be replayed into another.
This is enforced by the type system: client.encryptInputs(...) returns a builder with no .execute() method, and .setConsumingContract(...) is what returns the builder that has one. Omitting it is a compile error rather than a runtime failure. (JavaScript callers, who get no such check, still hit a ConsumingContractUninitialized throw at .execute().)
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.execute();.setAccount(address) — optional
Override the address that "owns" the encrypted input. Only that address will be allowed to use the encrypted inputs on-chain. Defaults to the account from the connected WalletClient.
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.setAccount('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
.execute();.setChainId(chainId) — optional
Override the chain the encrypted input will be used on. Defaults to the chain ID of the connected PublicClient.
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.setChainId(11155111)
.execute();.setUseWorker(boolean) — optional
Overrides the useWorkers flag from CofheConfig for this specific call. When true (the default), ZK proof generation runs in a Web Worker to avoid blocking the main thread.
No-op in a node environment.
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint32(7n)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.setUseWorker(false)
.execute();.onStep(callback) — optional
Registers a callback that fires at the start and end of each encryption step. Useful for building progress indicators.
The callback receives the current EncryptStep enum value and a context object with isStart, isEnd, and duration (milliseconds, only meaningful on isEnd).
await cofheClient.connect(publicClient, walletClient);
const encrypted = await cofheClient
.encryptInputs([Encryptable.uint64(10n)])
.setConsumingContract('0x5FbDB2315678afecb367f032d93F642f64180aa3')
.onStep((step, ctx) => {
if (ctx?.isStart) console.log(`Starting: ${step}`);
if (ctx?.isEnd) console.log(`Done: ${step} (${ctx.duration}ms)`);
})
.execute();The EncryptStep enum values fired in order:
EncryptStep.InitTfhe; // 'initTfhe'
EncryptStep.FetchKeys; // 'fetchKeys'
EncryptStep.Pack; // 'pack'
EncryptStep.Prove; // 'prove'
EncryptStep.Verify; // 'verify'The encryption flow
Calling .execute() runs five sequential steps. You can observe them via the .onStep() callback.
| Step | Description |
|---|---|
InitTfhe | Lazy-initializes the TFHE WASM module (browser/Node). A no-op after the first call. |
FetchKeys | Fetches (or loads from cache) the FHE public key and CRS for the target chain. |
Pack | Packs the plaintext values into a ZK list ready for proving. |
Prove | Generates the ZK proof of knowledge (ZKPoK). Uses a Web Worker when available. |
Verify | Sends the proof to the CoFHE verifier's /verifyBatch endpoint. Returns the hashes and one shared batch signature. |
Encryptable — typing inputs
Use the Encryptable factory to create the items you want to encrypt. Each factory function accepts the plaintext value and an optional securityZone.
| Factory | Data type | Solidity input param |
|---|---|---|
Encryptable.bool(value) | boolean | externalEbool |
Encryptable.uint8(value) | bigint | string | externalEuint8 |
Encryptable.uint16(value) | bigint | string | externalEuint16 |
Encryptable.uint32(value) | bigint | string | externalEuint32 |
Encryptable.uint64(value) | bigint | string | externalEuint64 |
Encryptable.uint128(value) | bigint | string | externalEuint128 |
Encryptable.address(value) | bigint | string | externalEaddress |
You can also use the generic Encryptable.create(type, value) form:
Encryptable.create('uint32', 42n);
Encryptable.create('bool', false);
Encryptable.create('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');Bit limit: A single encryptInputs call may encrypt at most 2048 bits of plaintext in total. Exceeding this limit throws a ZkPackFailed error.
Result types
HashPlusProofResult — the result type
.execute() always returns a typed tuple of the form [...hashes, signature]:
- One
0x-prefixed 32-byte hash string per input (typed asexternalEuint*/externalEbool/externalEaddresson the Solidity side). - One
bytessignature at the end, authenticating the entire batch of hashes together.
// For one input:
// [externalHash, signature]
// For two inputs:
// [externalHash1, externalHash2, signature]Use this with Solidity functions that accept external* handles plus a bytes parameter for the shared signature — see Writing Encrypted Data to a Contract for contract examples, and the @cofhe/abi reference for how insertEncryptedValues threads the hashes/signature into arbitrary contract calls automatically.
Common pitfalls
- Missing
.setConsumingContract(...): required before.execute()— throwsConsumingContractUninitializedotherwise. It must be the exact contract you submit the hashes/signature to; the verifier binds this address into the signature. - Wrong
Encryptabletype:Encryptable.uint32(...)must match what your Solidity function expects (e.g.externalEuint32). - Splitting a batch: the signature authenticates all the hashes in that
execute()call together, in order. You can't reuse a subset of the hashes with a different signature, or reorder them. - Wrong account / chain: encrypted inputs are authorized for a specific
account + chainId. If you override these (or connect to the wrong network/wallet), your inputs may not be usable for the intended transaction. - Bit limit exceeded: a single call can encrypt at most 2048 bits of plaintext. Exceeding this throws
ZkPackFailed.