Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cofhe-batch-bench

Measuring what a single FHE operation actually costs on Fhenix CoFHE, and how many of them fit in one transaction.

CoFHE turns every FHE operation inside a contract into one createTask call on the TaskManager, which emits one TaskCreated event. That makes the cost separable: chain a known number of operations in a single transaction, measure gas across several sizes, and regress.

I could not find published numbers for this, so here are some.

Results

Arbitrum Sepolia (chain 421614), CoFHE TaskManager at 0xeA30c4B8b44078Bbf8a6ef5b9f1eC1626C7848D9, contract compiled with solc 0.8.28 (optimizer on, 200 runs).

bench(n) FHE ops gas used gas / op fee (ETH) tx
1 3 201,047 67,016 0.00000408 0x28e297…
8 12 483,317 40,276 0.00000999 0x680722…
12 17 584,822 34,401 0.00001189 0xd1b518…

Least-squares fit over the three points:

gasUsed ≈ 27,900 × ops + 125,600      (residuals within ±5%)

So:

  • ~27,900 gas is the marginal cost of one additional FHE operation
  • ~125,600 gas is fixed transaction overhead

The naive "gas / op" column falls from 67k to 34k as the batch grows, which is just the fixed overhead being amortised. Batching is worth doing: going from 3 operations to 17 is 5.7× the work for 2.9× the gas.

Gas used on Arbitrum folds in the L1 calldata component. Calldata is effectively constant across this sweep (one uint256 argument), so that cost lands in the intercept rather than the slope.

Three points is a thin fit. See Reproducing below to extend it.

The estimation ceiling

While collecting the above through Remix + WalletConnect, every call with opCount ≥ 13 failed with a client-side error before broadcasting:

transact to FHEOpsBench.bench errored: Error occurred: _context7.t3.error.indexOf is not a function.
If the transaction failed for not having enough gas, try increasing the gas limit gently.

The suggestion to raise the gas limit is a red herring. The measurements above show 17 operations consuming 585k gas against a configured limit of 8,000,000 — under 8% of it. Raising the limit further changed nothing.

The contract address shows only the successful transactions and no failed ones, which settles what is happening: these calls never reached the chain at all. The failure is in the eth_estimateGas step, and the malformed error is the tooling crashing while formatting whatever the provider returned.

So the practical ceiling of ~17 chained operations is a tooling limit, not a protocol limit. scripts/bench.js works around it by setting an explicit gasLimit on every transaction and never calling estimateGas. Anyone extending this benchmark past 17 operations should do the same.

A second finding: allowGlobal reverts

While extending this into a decryption benchmark, FHE.allowGlobal() turned out to fail against the deployed TaskManager on Arbitrum Sepolia, in the same silent-estimation-failure way described above.

contracts/AllowGlobalProbe.sol isolates it. Both of its functions do identical FHE work — one trivial encryption, one addition, allowThis, allowSender. The only difference is that withGlobal() adds a single FHE.allowGlobal() call.

call result
baseline() succeeds — block 302238339, 4 logs
withGlobal() fails, back to back, same wallet, same explicit gas limit

Probe deployed at 0x5B5AddDe8C469894E5ae2EAB60C4f16a528A4D79.

The revert reason is not recoverable from outside: the TaskManager sits behind an unverified proxy, so there is no way to tell whether the deployed implementation exposes allowGlobal(uint256) at all, or exposes it and rejects the caller in the ACL check. Reading ACL.allowGlobal in cofhe-contracts, the requester should already hold a transient allowance from createTask, so it looks like it ought to pass.

This blocks more than it first appears: @cofhe/sdk's decryptForTx(...).withoutACP() path is built on global allowance, so on-chain decryption cannot be exercised on this chain while allowGlobal is unusable.

Filed upstream as FhenixProtocol/cofhe-contracts#119.

Reproducing

npm install
cp .env.example .env        # add PRIVATE_KEY (use a throwaway wallet)
npm run compile
npm run deploy              # prints CONTRACT_ADDRESS — put it in .env
npm run bench               # default sweep: 1 2 4 8 12 16 24 32
npm run bench -- 1 8 32 64 128   # or pick your own sizes

Results are written to results.json with the fitted model, per-size gas, and transaction hashes.

npm run compile uses solc directly rather than Hardhat, so it works in environments that cannot reach binaries.soliditylang.org.

How the task count is derived

bench(n) produces 2 + n + floor((n + 1) / 4) tasks:

  • two FHE.asEuint32 trivial encryptions to seed the accumulator — 2 tasks
  • n loop iterations, each performing one operation — n tasks
  • every fourth iteration takes the mul branch, which contains an inline FHE.asEuint32(2) — one extra task each

The script asserts this against receipt.logs.length - 1 (one Benched event plus one TaskCreated per operation) and flags any mismatch.

Ciphertext hashes in CoFHE are deterministic in the operation inputs and function id, so identical operations on identical inputs collapse to the same handle. The contract carries a seed counter and rotates between add/xor/mul/sub specifically to keep every task distinct — otherwise repeated runs would silently produce duplicate ctHashes.

Layout

contracts/FHEOpsBench.sol       benchmark contract
contracts/AllowGlobalProbe.sol  minimal repro for the allowGlobal failure
scripts/deploy.js               deploy, with a TaskManager presence check
scripts/bench.js                gas sweep with fixed gasLimit, writes results.json
tools/compile.js                solc-js compiler (no Hardhat dependency)

Notes

The measurements above came from an earlier deployment of this contract at 0x298E0AB22d8d93b8bDF7eA3431FC612E0dCbcB5F under a different contract name. The logic is unchanged; only the name and the comments differ, so the bytecode is not identical. Redeploying with npm run deploy and re-running the sweep reproduces the numbers independently.

MIT.

About

Measuring the gas cost of chained FHE operations on Fhenix CoFHE.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages