Enclave is now The Interfold.
Learn
Cryptography

Cryptography

This page explains the cryptography that protects an E3. It covers threshold BFV encryption, distributed key generation (DKG), and the zero-knowledge (ZK) proofs that make each step publicly verifiable. It is for developers and operators who want to know what the protocol proves and where. At the end, you can map each protocol step to its circuit and to its on-chain verifier. The PVDKG and ZK proof pipeline page gives the full detail.

Building blocks

Building blockImplementationJob in an E3
BFV fully homomorphic encryptionThe gnosisguild/fhe.rs library, scheme ID keccak256("fhe.rs:BFV")Compute on encrypted integers
Threshold BFV (TrBFV)crates/bfv/thresholdSplit the secret key across the committee
Shamir secret sharingPart of TrBFVGive each member a share of each key contribution
ZK proofsNoir circuits in circuits/, proved with Barretenberg UltraHonk (bb)Prove each key, share, and decryption step
CommitmentsThe SAFE sponge with the Poseidon2 permutation (circuits/lib/src/math/safe.nr)Link circuits without publishing large polynomials

BFV

BFV works in the polynomial ring Rq=Zq[X]/(Xd+1)R_q = \mathbb{Z}_q[X]/(X^d + 1). The degree dd is 64 or 8192. The ciphertext modulus qq is a product of CRT moduli qℓq_\ell, and tt is the plaintext modulus.

A key pair has a secret key ss and a public key (pk0,pk1)(\mathrm{pk}_0, \mathrm{pk}_1). A message mm encrypts to a ciphertext (ct0,ct1)(\mathrm{ct}_0, \mathrm{ct}_1), with small random polynomials uu, ee, e0e_0, and e1e_1 and the scale factor Δ=⌊q/t⌋\Delta = \lfloor q/t \rfloor:

pk0=−a s+e,pk1=a\mathrm{pk}_0 = -a\,s + e, \qquad \mathrm{pk}_1 = a ct0=pk0 u+e0+Δm,ct1=pk1 u+e1\mathrm{ct}_0 = \mathrm{pk}_0\,u + e_0 + \Delta m, \qquad \mathrm{ct}_1 = \mathrm{pk}_1\,u + e_1

Decryption computes ct0+ct1s mod q\mathrm{ct}_0 + \mathrm{ct}_1 s \bmod q, which is Δm\Delta m plus small noise. It then scales by t/qt/q and rounds to get m mod tm \bmod t.

The sum of two ciphertexts decrypts to the sum of the two messages. The default template and CRISP both use this addition. BFV also supports multiplication, but each multiplication uses part of a limited noise budget.

Threshold BFV

In threshold BFV, no party holds ss. Three numbers define each committee:

  • N is the committee size that sortition selects.
  • H is the number of dealer contributions that form the key. The active aggregator selects an ascending roster SS of H members that are all ready.
  • T is the threshold. Decryption needs T + 1 shares from the roster.
SizeNHTT + 1Members that can drop before the rosterRoster members that can drop before decryption
Minimum321210
Micro954540
Small191491054

The values come from crates/committee/src/lib.rs and packages/interfold-contracts/contracts/lib/ActiveCryptoConfig.sol.

DKG

Key generation. Members exchange encrypted shares. The key comes from a roster of 14 contributions.

Key generation

Each member hh makes a secret contribution shs_h and a public key share. It splits shs_h into Shamir shares with a polynomial of degree T. It encrypts each share under the recipient's individual BFV key and sends it. The same process runs for smudging noise esme_{\mathrm{sm}}, which later hides each decryption share.

The individual BFV keys protect only the share traffic during DKG. They are different from the threshold key that data providers use.

After the share proofs pass, each member publishes a signed readiness report. The active aggregator then selects the roster SS. The threshold secret and public key are:

s=∑h∈Ssh,pk0=∑h∈Spk0,h,pk1=as = \sum_{h \in S} s_h, \qquad \mathrm{pk}_0 = \sum_{h \in S} \mathrm{pk}_{0,h}, \qquad \mathrm{pk}_1 = a

All members use the same common polynomial aa, so pk1\mathrm{pk}_1 is not a sum. Each roster member ii adds the shares that it received from the roster into ski\mathrm{sk}_i, its Shamir share of ss.

Threshold decryption

Each roster member ii computes a decryption share from the ciphertext output:

di=ct0+ct1 ski+esm,i(modq)d_i = \mathrm{ct}_0 + \mathrm{ct}_1\,\mathrm{sk}_i + e_{\mathrm{sm},i} \pmod{q}

Ciphernodes use the committee key and decryption domain from confirmed chain state. Repeated matching key publications do not start more share calculations or proof requests. After a restart, the node resumes retained work when effects are enabled. Local worker failures retry the same request.

The aggregator takes T + 1 shares with party coordinates xix_i. The coordinates start at 1, and they must be unique and in increasing order. The aggregator combines the shares with Lagrange coefficients at zero:

Li(0)=∏j≠ixjxj−xi,u=∑iLi(0) di(modq)L_i(0) = \prod_{j \neq i} \frac{x_j}{x_j - x_i}, \qquad u = \sum_i L_i(0)\,d_i \pmod{q}

The coefficients Li(0)L_i(0) add up to 1, so u=ct0+ct1su = \mathrm{ct}_0 + \mathrm{ct}_1 s plus small noise. The aggregator then decodes uu to the plaintext, as in normal BFV decryption.

T + 1 roster members that collude can decrypt any ciphertext under the committee key. This limit applies to every threshold scheme. Larger committees raise T.

Publicly verifiable threshold BFV

Publicly verifiable threshold BFV (PV-TBFV) adds a ZK proof to each sensitive step. Anyone can check the proofs without the private values. The DKG part is publicly verifiable DKG (PVDKG). The steps form four phases:

PhaseWhat happensCircuits
P1: Key generationMembers make keys and shares, encrypt the shares, and decrypt themC0, C1, C2a, C2b, C3a, C3b, C4a, C4b
P2: Public key aggregationThe aggregator adds the H roster contributions into one keyC5
P3: User encryptionA data provider proves that an input is a valid BFV encryptionuser_data_encryption_ct0, _ct1, and the user_data_encryption wrapper
P4: Threshold decryptionMembers make decryption shares, and the aggregator combines T + 1C6, C7

The FHE computation runs between P3 and P4. The compute provider proves it with its own proof system (the OpenVM zkVM in the current code), not with these circuits.

A failed proof is attributable to one member. Other members then start an accusation, which can lead to a slash. See Commitment consistency and Exits and slashing.

Circuit map

The circuit IDs match the CircuitName enum in crates/zk/config/src/circuit.rs. The directories are under circuits/bin/.

IDDirectoryCircuitNameWhat the proof shows
C0dkg/pkPkBfvThe member's individual BFV public key is valid, with a commitment to it
C1threshold/pk_generationPkGenerationThe public key share follows BFV key generation from the committed secret contribution
C2adkg/sk_share_computationSkShareComputationThe Shamir shares of the secret contribution are valid (a Reed–Solomon parity check)
C2bdkg/e_sm_share_computationESmShareComputationThe Shamir shares of the smudging noise are valid
C3a, C3bdkg/share_encryptionShareEncryptionEach share is encrypted under the recipient's committed C0 key
C4a, C4bdkg/share_decryptionDkgShareDecryptionThe shares from the roster match the C2 commitments, with a commitment to their sum
C5threshold/pk_aggregationPkAggregationThe threshold public key is the sum of the H roster contributions
P3threshold/user_data_encryption*NoneA user ciphertext is a valid BFV encryption under the threshold key
C6threshold/share_decryptionThresholdShareDecryptionThe decryption share uses the member's committed key share and smudging noise
C7threshold/decrypted_shares_aggregationDecryptedSharesAggregationT + 1 shares combine and decode to the claimed plaintext
ConfigconfigNoneThe preset constants (CRT moduli, bounds, parity matrices) are consistent

The "a" and "b" suffixes are the secret-key track and the smudging-noise track. C3 and C4 use one circuit for both tracks.

Proof aggregation

Members exchange their proofs over libp2p gossip and verify each other's proofs. Recursive circuits in circuits/bin/recursive_aggregation/ then fold the proofs:

CircuitCircuitNameWhat it folds
c2ab_foldC2abFoldC2a and C2b
c3_fold, c3_fold_kernelC3Fold, C3FoldKernelThe C3 proofs, in sequence
c3ab_foldC3abFoldThe final secret-key and smudging-noise C3 folds
c4ab_foldC4abFoldC4a and C4b
node_foldNodeFoldOne member's chain from C0 to C4
nodes_fold, nodes_fold_kernelNodesFold, NodesFoldKernelThe H node_fold proofs, in sequence
dkg_aggregatorDkgAggregatornodes_fold and C5
c6_fold, c6_fold_kernelC6Fold, C6FoldKernelThe T + 1 C6 proofs, in sequence
decryption_aggregatorDecryptionAggregatorc6_fold and C7

Only two proofs go on-chain for each E3:

  1. The DKG proof from dkg_aggregator. The registry verifies it at publishCommittee through BfvPkVerifier. The registry also checks one signed fold attestation from each roster member with DkgFoldAttestationVerifier. It then stores the DKG anchors: the party IDs and the commitments to each member's aggregated key share and smudging noise.
  2. The decryption proof from decryption_aggregator. The Interfold contract verifies it at publishPlaintextOutput through BfvDecryptionVerifier. The verifier compares the commitments in the proof with the stored DKG anchors.

Both proofs bind the committee hash. CommitteeHashLib computes it as the Keccak-256 hash of the committee addresses in ascending order. The decryption proof also binds a decryption domain: the hash of the chain ID, the Interfold address, the e3Id, the committee hash, the ciphertext output hash, and the committee public key. A decryption proof for one E3 therefore fails for any other E3.

Commitments link the circuits

BFV polynomials are too large to publish for every step. Each circuit hashes its polynomials into a short commitment with the SAFE sponge. A later circuit takes the earlier commitment as a public input. It recomputes the commitment from its private inputs and checks that the two values are equal.

These links form one chain from key generation to decryption:

  • C1 to C2: the committed secret contribution and smudging noise.
  • C2 to C3: the shares that each ciphertext encrypts.
  • C1 to C5: the public key shares that form the threshold key.
  • C4 to C6: the aggregated key share and smudging noise of each member.
  • C6 to C7: the decryption shares.

The recursive circuits check most links inside the proofs. node_fold checks the links in one member's chain. dkg_aggregator checks the links between members and from C5 to C1. decryption_aggregator checks the link from C6 to C7. The C4 to C6 link goes through the DKG anchors, which the on-chain decryption verifier compares.

Domain-separation labels (DS_* in circuits/lib/src/math/commitments.nr) keep different kinds of commitments apart.

Recursive verification also binds the complete VK tree. Each sequential fold carries fixed leaf, fold, and genesis key hashes. The final proof commits to every nested key at public input zero. Deployment pins that commitment from the matching artifact pair's .vk_tree_hash, so a prover cannot substitute another circuit within the proof tree.

BFV presets

Two BFV presets exist. Each preset has a threshold parameter set for user data and a DKG parameter set for the individual keys. The values come from crates/bfv/params/src/constants.rs.

PresetparamSetThreshold degreeThreshold CRT moduliPlaintext modulus ttDKG degreeDKG CRT moduli
insecure-64064317,000,000642
secure-819228192317,000,00081922

insecure-64 is for development and tests only. The code marks it as not for production. The deployment script refuses to deploy it on a chain that is not a known test or local chain.

Presets by chain

ActiveCryptoConfig.sol selects the supported presets from block.chainid:

ChainChain IDPresetsCommittee sizes in the circuit build
Ethereum mainnet1secure-8192Minimum, Micro, Small
Sepolia11155111insecure-64, secure-8192Minimum, Micro, Small
Local31337, 1337insecure-64, secure-8192Minimum, Micro, Small

Any chain that is not Sepolia or local gets the mainnet rules. The mainnet launch pricing configuration sets minCommitteeSize to 19. With that value, getE3Quote rejects Minimum and Micro requests on mainnet, so only Small can be requested.

Each request must name the expected circuit configuration in expectedCryptoConfigId. The ID binds the scheme, the parameter hash, and the circuit version (keccak256("interfold-bfv-v5")).

Next steps