Enclave is now The Interfold.
Build
Noir Circuits

Noir Circuits

This page is for developers who build, change, or reuse the Noir circuits in the circuits/ directory of the Interfold repository. It shows where each package lives and which tool versions to install. It also lists the scripts that build the circuits and the Solidity verifiers, and it shows how the Rust prover finds the artifacts. At the end, you can build the circuits for a preset and a committee size. You can also use the circuit library in your own Noir project.

For what the circuits prove (the PV-TBFV phases and the circuit IDs C0 to C7), read Cryptography first. The Noir sources and their tests are the source of truth for constraints and public inputs.

Workspace layout

All binary packages depend on one shared library. The Nargo package name of the library is lib (circuits/lib/Nargo.toml).

      • Nargo.toml
        • lib.nr
  • DirectoryContents
    lib/src/mathPolynomials, the SAFE sponge (Poseidon2 and Keccak256), modular arithmetic, and the commitment functions with their domain separators.
    lib/src/coreThe circuit logic that the binary packages call. core/dkg holds C0 and C2 to C4. core/threshold holds C1, C5, the two user-encryption legs, C6, and C7.
    lib/src/configsThe BFV presets (insecure, secure) and the committee sizes. default/mod.nr and committee/active.nr select the active pair. The build script writes both files.
    bin/dkgNargo workspace: pk (C0), sk_share_computation (C2a), e_sm_share_computation (C2b), share_encryption (C3), share_decryption (C4).
    bin/thresholdNargo workspace: pk_generation (C1), pk_aggregation (C5), user_data_encryption_ct0 and user_data_encryption_ct1 with their wrapper user_data_encryption (P3), share_decryption (C6), decrypted_shares_aggregation (C7).
    bin/recursive_aggregationTwelve packages that fold proofs together. The two final circuits are dkg_aggregator and decryption_aggregator.
    bin/configPackage validate_secure_configs. It checks the secure preset constants (CRT moduli, bounds, and parity matrices).
    benchmarksTiming scripts and reports.

    The package directory names match the CircuitName enum in crates/zk/config/src/circuit.rs. For example, CircuitName::PkBfv is pk, and CircuitName::DkgAggregator is dkg_aggregator. The full package index is in circuits/README.md (opens in a new tab).

    How the proofs combine

    Each ciphernode proves its DKG steps with the inner circuits. The recursive aggregation packages fold these proofs, first for each ciphernode and then across the committee. Only the two aggregator proofs go on-chain.

    1. Inner proofsC0 to C4 for each ciphernode.
    2. Step foldsc2ab_fold, c3_fold, c3ab_fold, c4ab_fold.
    3. node_foldOne fold for each ciphernode.
    4. nodes_foldFolds the node_fold proofs of the committee.
    5. dkg_aggregatorAdds C5. Verified on-chain.

    The decryption path is shorter. The C6 proofs of the committee go through c6_fold, and decryption_aggregator adds C7. The *_kernel packages make the first proof of each sequential fold (c3_fold, c6_fold, nodes_fold).

    The Solidity verifiers for the two aggregators are committed to the repository as DkgAggregatorVerifier.sol and DecryptionAggregatorVerifier.sol. BfvPkVerifier and BfvDecryptionVerifier hold the address of one of these verifiers and call it through ICircuitVerifier.

    Toolchain

    A circuit build needs two tools: nargo compiles Noir, and bb (Barretenberg) writes verification keys, proofs, and Solidity verifiers. Use the pinned versions. Artifacts from other versions do not match the prover or the committed verifiers.

    This table describes repository source builds. Released binaries keep the versions pinned at their release.

    ComponentVersionPinned in
    nargo1.0.0-rc.3NOIR_TOOLCHAIN in .github/workflows/ci.yml. The Noir crates in crates/zk/prover/Cargo.toml use the same tag.
    bb6.0.0required_bb_version in crates/zk/prover/versions.json, and BB_VERSION in ci.yml
    Circuit artifacts for source buildsBuild from this checkoutpnpm build:circuits
    bb_proof_verification (Noir)v6.0.0The Nargo.toml of each package that verifies a proof
    @aztec-foundation/bb.js, @noir-lang/noir_js6.0.0, 1.0.0-rc.3packages/interfold-sdk/package.json

    For a source build, compile the circuits from the same checkout. interfold noir setup installs the CLI's release archive; it does not compile the checkout. Do not reuse an archive built with a different toolchain. A release needs a matching archive before nodes can use it.

    Install nargo

    Install noirup, then use it to install the pinned compiler:

    curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
    export PATH="$PATH:$HOME/.nargo/bin"
    noirup -v v1.0.0-rc.3

    The installer adds ~/.nargo/bin to your shell profile. The export line makes it available in the current shell.

    Install bb

    For this checkout, install the binary for your platform from the Barretenberg 6.0.0 release (opens in a new tab).

    For a released node, run interfold noir setup. It downloads the CLI's pinned bb, checks it against the checksum in versions.json, and installs that release's circuit archive:

    interfold noir setup

    Without an Interfold config file, the CLI installs into ~/.interfold/noir: bin/bb, circuits/, and version.json. With a config file, it uses .interfold/noir next to that file.

    Put bb on your PATH

    The repository scripts call bb from your PATH. For a source build, add the directory with the 6.0.0 binary. To use the copy installed by a matching released CLI, add its directory:

    export PATH="$HOME/.interfold/noir/bin:$PATH"

    Check the versions

    nargo --version
    bb --version

    For source builds, nargo reports 1.0.0-rc.3 and bb reports 6.0.0.

    interfold noir commands

    interfold noir manages the bb binary and the circuit artifacts that a ciphernode uses (crates/cli/src/noir.rs).

    CommandWhat it does
    interfold noir statusPrints the path and version of bb and of the circuits, then the status: Ready, Barretenberg needs update, Circuits need update, or Setup required.
    interfold noir setupInstalls what is missing or out of date. If everything matches versions.json, it prints the versions and stops.
    interfold noir setup --forceDownloads bb and the circuit archive again.
    interfold noir setup --circuits-archive <PATH>Installs the circuits from a local release archive instead of the download.

    The circuit archive comes from the GitHub release pinned in required_circuits_version. The node selects the artifact directory from the BFV parameter set and the committee size of each E3.

    Build the circuits in the repository

    Run the build from the repository root. It needs nargo and bb on your PATH, the Rust toolchain (the script runs two Rust generators), and the JavaScript dependencies (pnpm install).

    pnpm build:circuits                                        # insecure-64, minimum
    pnpm build:circuits --preset secure-8192 --committee small

    A build selects one BFV preset and one committee size:

    OptionValuesDefault
    --presetinsecure-64, secure-8192, allinsecure-64
    --committeeminimum, micro, small, allminimum
    CommitteeNTH
    minimum312
    micro945
    small19914

    N is the committee size, T is the threshold, and H is the number of honest parties that the DKG expects. Decryption needs T + 1 shares. The mainnet contracts accept only the secure parameter set. Sepolia and local chains accept both sets (ActiveCryptoConfig.sol).

    For each pair, the Rust build owner does these steps (crates/zk/build):

    1. It writes configs/default/mod.nr and configs/committee/active.nr for the pair.
    2. It regenerates the parity matrices and the C1 and C2 bounds with the Rust generators.
    3. It updates packages/interfold-contracts/scripts/utils.ts and packages/interfold-contracts/contracts/lib/BfvProtocol.sol.
    4. It compiles every package in bin/dkg, bin/threshold, and bin/recursive_aggregation with nargo compile.
    5. It writes verification keys with bb write_vk. Each base circuit gets evm, noir-recursive-no-zk, and noir-recursive keys. Each fold circuit gets a noir-recursive-no-zk key, and the two aggregators also get an evm key.
    6. It copies the artifacts to dist/circuits/<preset>/<committee>/, with one directory for each variant (default, recursive, evm). Then it writes SHA256SUMS and checksums.json.

    The build records the active pair in circuits/bin/.active-preset.json.

    Downloaded release archives must match the SHA-256 compiled into the binary for their version. The binary includes pins from crates/zk/prover/versions.json (circuits_checksums) and the release build input. The installer checks it before extraction, then requires a nonempty internal manifest that covers every artifact file. Root manifests, SOURCE_HASH, and build stamps are metadata. noir setup --circuits-archive requires the same pin for a local archive. Add --allow-unpinned-archive to install another archive, such as a local build.

    The installer requires every pair in crates/zk/prover/supported-configurations.json by default, including for noir setup --circuits-archive. The release validator uses the same matrix. For a local archive, repeat --circuits-configuration <PRESET/COMMITTEE> to select a subset explicitly. For example, --circuits-configuration insecure-64/minimum selects the development configuration. Local and CI callers can request a nonempty subset through download_circuits_for_configurations or install_circuits_archive_for_configurations. The required set never depends on archive contents. Each required or included pair must contain every artifact in crates/zk/prover/required-artifacts.json. The release validator and installer share this inventory. The manifest must cover each artifact.

    If circuit replacement or the version update fails, the installer attempts rollback and returns the original installation error. Each rollback failure logs the source and target paths. If the previous circuits cannot be restored, the installer keeps the staging directory and logs its path for recovery.

    The release workflow packages the archive first and passes its SHA-256 as E3_CIRCUITS_ARCHIVE_SHA256 to binary builds and the ciphernode Docker build. The build binds this pin to the crate version. Each binary and image build checks the pin reported by interfold noir status before publication. Builds without this input still work. A download for an unpinned version fails with the missing archive name. The workflow publishes the exact archive that it hashed. Repacking changes the digest even when artifact bytes stay the same. SOURCE_HASH identifies source inputs and is not an archive digest.

    ⚠️

    The preset and the committee size stay selected after the build, and they change tracked files. Always switch them with pnpm build:circuits --preset <name> --committee <name>. Do not edit active.nr, utils.ts, or a parity_*.nr file by hand. The pre-push hook runs pnpm check:committee, which rejects files that disagree. Circuit artifacts and verifiers are specific to the pair, so a changed H needs new circuits and new verifiers.

    Other circuit commands:

    CommandWhat it does
    pnpm build:circuits --dry-runLists the circuits that a build compiles, and stops.
    pnpm build:circuits --group dkg --skip-vkCompiles one group and skips the verification keys.
    pnpm build:circuits hashPrints the source hash that identifies a build.
    pnpm build:circuits sync-config --preset <p> --committee <c>Updates utils.ts and BfvProtocol.sol only. It does not compile.
    pnpm check:committeeChecks that the committee and BFV values agree across Noir, Rust, TypeScript, and Solidity.
    pnpm noir:lint (scripts/circuits/lint.sh)Runs nargo fmt --check and nargo check on the library and each package. It exits with success if nargo is missing.
    pnpm noir:test (scripts/circuits/test.sh)Runs nargo test on circuits/lib and decryption_aggregator, then nargo execute on bin/config, then the dkg_aggregator and node_fold tests for three preset and committee pairs.

    The Rust benchmark runner creates artificial cryptographic material and executes production witness preparation directly. Its preset and committee must match the selected artifacts:

    pnpm bench:circuits run --mode insecure --committee minimum --circuit threshold/pk_generation --witness-only --leaf-only

    Use --export-toml only for debug ABI inputs. Samples are not live application inputs. pnpm bench:circuits list prints the selected leaf cases. The Rust configuration library owns static configuration; pnpm build:circuits sync-preset writes the selected preset's constants.

    Generate the Solidity verifiers

    The Honk verifiers for dkg_aggregator and decryption_aggregator are committed under packages/interfold-contracts/contracts/verifiers/bfv/honk/. The files at the top of that directory are for insecure-64 with minimum. Every other pair has its own directory, honk/<preset>/<committee>/.

    pnpm build:circuits --preset insecure-64 --committee minimum
    pnpm generate:verifiers        # rewrite the committed verifiers
    pnpm check:verifiers           # compare them with the current keys, write nothing

    generate:verifiers compiles each circuit if necessary, runs bb write_vk -t evm and bb write_solidity_verifier, and renames the contract from HonkVerifier to a name such as DkgAggregatorVerifier. It refuses to run when the build stamp in dist/circuits or circuits/bin/.active-preset.json does not match the requested pair. For another pair, add --preset <name> --committee <name>.

    Each generated verifier has this function, the same as ICircuitVerifier (contracts/interfaces/ICircuitVerifier.sol):

    function verify(
      bytes calldata _proof,
      bytes32[] calldata _publicInputs
    ) external view returns (bool);

    Verifiers for your own circuits

    An E3 program can check its inputs with its own circuits. CRISP does this in scripts/compile_circuits.sh (opens in a new tab) in the CRISP repository. The user_data_encryption circuits and their keys come compiled from the Interfold release. For each ballot stack, the script does these steps:

    1. It compiles the application circuit and writes its verification key with bb write_vk -t noir-recursive-no-zk.
    2. It compiles the fold circuit, which verifies both proofs, and writes its key with bb write_vk --oracle_hash keccak.
    3. It runs bb write_solidity_verifier on the fold key and copies the result to packages/crisp-contracts/contracts/verifiers/.

    The script makes two verifiers: CRISPVerifier.sol from the crisp and fold circuits, and CRISPOnchainVerifier.sol from crisp_onchain and fold_onchain. The verifiers do not depend on the BFV preset. The fold circuit accepts the key hashes of both presets.

    The custom circuits tutorial follows these steps for a new circuit.

    The SDK function generateProof() proves only the three user-data-encryption circuits. It embeds artifacts built for insecure-64 with minimum. See the SDK page.

    Use circuits/lib in another Noir project

    Add the library as a dependency in your Nargo.toml. Use the release tag that matches the prover and the circuits that your deployment uses. CRISP uses the name interfold_lib:

    Nargo.toml
    [dependencies]
    interfold_lib = { git = "https://github.com/theinterfold/interfold", tag = "v0.18.0", directory = "circuits/lib" }

    Then import from it by that name:

    use interfold_lib::configs::default::threshold::{L, N};
    use interfold_lib::math::commitments::compute_ciphertext_commitment;
    use interfold_lib::math::polynomial::Polynomial;

    The library at the tag v0.18.0 selects insecure-64 with minimum. The build script writes that selection into the source. For another pair, use a path dependency on a local checkout.

    To use another pair, run pnpm build:circuits for the pair in the checkout, then point the dependency at it:

    Nargo.toml
    [dependencies]
    interfold_lib = { path = "<checkout>/circuits/lib" }

    The library itself depends on poseidon v0.3.0 and keccak256 v0.1.2 from noir-lang, and on bignum v0.4.1 from gnosisguild/noir-bignum.

    Rust integration

    The crate e3-zk-prover (crates/zk/prover) runs the circuits for the ciphernode.

    ItemRole
    ZkBackendFinds bb and the circuit directory. check_status, ensure_installed, download_bb, and download_circuits implement interfold noir.
    ZkProverRuns bb prove and bb verify as a subprocess. It reads artifacts from <circuits>/<preset>/<committee>/<variant>/.
    ProvableThe trait for one circuit. It names the CircuitName and builds the witness inputs.
    CircuitVariant (e3-zk-config)Default (noir-recursive-no-zk), Recursive (noir-recursive), and Evm (evm). These are the directory names in the archive.
    ProofRequestActor, ProofVerificationActor, ShareVerificationActorThe actors that request proofs and check the proofs of other ciphernodes.

    Circuit identity/variants belong to crates/zk/config; ProofType and proof payloads remain in crates/events. The protocol flow that uses them is in proof statements (opens in a new tab) and DKG (opens in a new tab).

    Add or rename a circuit

    1. Put the package in bin/dkg, bin/threshold, or bin/recursive_aggregation. The build script finds packages in these groups automatically.
    2. Add the package to the members list of the group workspace, for bin/dkg and bin/threshold.
    3. Add the directory to DIRS in scripts/circuits/lint.sh, for a package in bin/recursive_aggregation.
    4. Add a CircuitName variant in crates/zk/config if the Rust prover must run the circuit.
    5. Rebuild each pair that you ship, and regenerate the verifiers if the circuit goes on-chain.

    Next steps