Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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:

  1. Decide which plaintext value(s) you want to encrypt.
  2. Wrap each value with a typing helper (e.g. Encryptable.uint32(…)) to construct typed encryptable inputs (see Encryptable — typing inputs).
  3. 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.
  4. Call client.encryptInputs([...]).setConsumingContract(...).execute() to produce a batch-verified result — one ciphertext hash per input, followed by a single shared signature.
  5. Submit a transaction to your contract, passing the hashes and signature as external* + bytes parameters (see Writing encrypted data to a contract).

Prerequisites

  1. 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.

  1. 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 to encryptInputs([...]) — typed as externalEuint* / externalEbool / externalEaddress on the Solidity side.
  • One trailing bytes signature, shared by the whole batch. The CoFHE verifier signs keccak256(h_0 || h_1 || ... || h_n), where each h_i binds 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.

StepDescription
InitTfheLazy-initializes the TFHE WASM module (browser/Node). A no-op after the first call.
FetchKeysFetches (or loads from cache) the FHE public key and CRS for the target chain.
PackPacks the plaintext values into a ZK list ready for proving.
ProveGenerates the ZK proof of knowledge (ZKPoK). Uses a Web Worker when available.
VerifySends 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.

FactoryData typeSolidity input param
Encryptable.bool(value)booleanexternalEbool
Encryptable.uint8(value)bigint | stringexternalEuint8
Encryptable.uint16(value)bigint | stringexternalEuint16
Encryptable.uint32(value)bigint | stringexternalEuint32
Encryptable.uint64(value)bigint | stringexternalEuint64
Encryptable.uint128(value)bigint | stringexternalEuint128
Encryptable.address(value)bigint | stringexternalEaddress

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 as externalEuint* / externalEbool / externalEaddress on the Solidity side).
  • One bytes signature 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() — throws ConsumingContractUninitialized otherwise. It must be the exact contract you submit the hashes/signature to; the verifier binds this address into the signature.
  • Wrong Encryptable type: 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.