I’m trying to understand how the ciphertextWithZKProof parameter (bytes calldata) is generated for the verifyProofRequest contract function.
I’ve searched through the FHEVM codebase but couldn’t find the actual generation logic. I only found test cases using 64-byte random values, but those appear to be just for unit testing purposes.
Could someone please clarify:
What is its encoding format internally? Is it a concatenation of ciphertext and ZK proof?
What is the typical size? I couldn’t find its construction logic in the FHEVM repo, and only saw 64-byte random mocks in tests.
Could someone clarify the real workflow or point me to the relevant code/docs? Thanks a lot
But to answer your questions:
The ciphertextWithZKProof in verifyProofRequest is a packed payload combining encrypted values (FHE ciphertext) and a Zero-Knowledge Proof-of-Knowledge (ZKPoK) proving the user knows the plaintext and that the ciphertext is well-formed. It’s generated off-chain (e.g. via the relayer-sdk), includes metadata like user and contract addresses, and is typically ~16–20KB. Coprocessors verify the ZK proof, unpack the ciphertext, derive handles, and return attestations for use in smart contracts.
I searched for verifyProofRequest across all zama-ai repositories and found that most usages are mocks, with very little real implementation detail.
What I’m really trying to understand is the end-to-end flow: is it that the client first encrypts to obtain a ciphertext handle, the actual ciphertext, and the ZKPoK data, then uploads the ciphertext to the coprocessor and calls verifyProofRequest for verification? If so, how should ciphertextWithZKProof be constructed — by combining the ciphertext handle and the ZKPoK data?
It looks like ciphertextWithZKProof corresponds to the ProvenCompactCiphertextList structure defined in tfhe-rs — is that correct?
Hi @yuucyf ,
let’s do one step back, what is the application that you are trying to achieve? Maybe there is some big picture overview I can help you understand, because you are asking super specific questions and maybe it would be easier for me to reply if understand what you are trying to achieve.
Also I will give you a little tool, here is our chatGPT that is trained on our docs, although some answers might be inaccurate it could perhaps help you gain some understanding.
Check it out and let me know if you have any more questions afterwards.
Validity path for user-encrypted ciphertexts in fhevm
If a user encrypts data locally and wants to use it in fhevm, do they have to first call verifyProofRequest, so that the coprocessor verifies the ciphertext and ZKPoK and then returns a valid handle?
In other words, when calling fhe.fromExternal in a contract, must the handle and signature returned by verifyProofResponse be provided, otherwise the ciphertext is not accepted by the system?
Why must verification be initiated on L1
Since verifyProofRequest requires submitting the raw ciphertext and ZKPoK on-chain, the calldata size is large and gas costs are very high.
Why not instead let users submit ciphertexts to coprocessors via RPC, have the coprocessor network perform verification and consensus off-chain, and only return a valid handle to the users?
From a security perspective, this seems sufficient and much cheaper.
A ciphertext produced off-chain is not usable in a contract unless it has gone through verifyProofRequest and resulted in a verified handle. fhe.fromExternal expects a handle that is backed by a successful coprocessor verification + attestation. The handle is derived after proof verification; users do not pre-compute it or bundle it themselves.
The reason this flow is initiated on L1 is to anchor the exact ciphertext + proof in canonical chain state. That prevents equivocation, forged handles, or mismatched verification contexts. If verification were purely RPC-based, contracts would have no deterministic, consensus-backed guarantee that a given handle corresponds to a specific verified ciphertext.
Tldr: a lot of our architecture is how to make things work with least trust assumptions, gas cost is a secondary consideration.