Skip to main content


This primer acts as an intro guide to writing complex circuits and compiling them.

Dependency management

Most circuits that cover common use cases (e.g., circuits verifying state transitions) require the use of external dependencies.

In such cases, using clang or rustc directly is discouraged. Instead, it is preferable to use a dedicated build management system:

  • CMake for C++
  • Cargo for Rust

When calling clang directly, dependency management can also be handled via the following methods:

  • Via the CLI
  • Via env variables
  • Via updating the system path

Using CMake is still preferable as it offers several valuable features (such as external library detection) that simplify working with complex circuits.


This primer uses crypto3 (C++) and arkworks (Rust) to show how to use external libraries in a circuit. The primer can be reused for any other suitable library.

Using CMake

Switch to the working directory:

mkdir new_circuit && cd new_circuit

Create the circuit:

cd .. && mkdir src
cd src && touch main.cpp

Add circuit code that references an external library:

#include <nil/crypto3/hash/algorithm/hash.hpp>
#include <nil/crypto3/hash/sha2.hpp>


[[circuit]] bool circuit_func() {...}

Create a configuration file for CMake:

cd .. && touch CMakeLists.txt

Inside CMakeLists.txt, set the project config and include the CircuitCompile.cmake module:

cmake_minimum_required(VERSION 3.2)

DESCRIPTION "Tutorial circuit with hashes"

list(APPEND CMAKE_MODULE_PATH "/usr/lib/zkllvm/share/zkllvm")


Add the required library to the project configuration:

set(crypto3_DIR "path/to/crypto3/installation")

find_package(crypto3 REQUIRED)

Set the circuit source and add a build target:

set(SOURCES src/main.cpp)


, where circuit is the desired target name.

Compile the circuit with:

cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release .
make -C ./build circuit -j$(nproc)

add_circuit() will generate the circuit IR under the name circuit.ll.


The CircuitCompile.cmake module does not support using custom paths to clang built from sources (as well as custom paths to a linker). To use this module, it is necessary to install the zkllvm Deb package.


Instead of specifying crypto3_DIR in CMakeLists.txt, it is possible to specify it when calling cmake:

cmake -G "Unix Makefiles" -B ./build -DCMAKE_BUILD_TYPE=Release . -Dcrypto3_DIR=/path/to/crypto3/installation

This method is useful if crypto3_DIR is not a fixed path and cannot be 'hardcoded' in the CMake configuration file.

Using Cargo

Create a new cargo project:

cargo new new_circuit --bin
cd new_circuit

Optionally add the --vcs none option to prevent cargo from automatically creating a Git repository.

Open the Cargo.toml file and add the following dependencies and feature flags:

ark-pallas = { git = "" }
unroll = { git = "" }


zkllvm = ["ark-ff/zkllvm"]

Adding unroll is mandatory for circuits that use loops.


The "zkllvm" feature flag is necessary to ensure compatibility with the original Rust compiler.

When calling Cargo, use the --features zkllvm option to enable the feature for the specified package. If the feature is enabled, built-in zkLLVM types are used to represent curves, fields, and other elements. If this feature is disabled, type definitions from the arkworks project are used.

The flag should only be used if assigner-unknown-unknown is the specified build target when calling cargo. If it is not used, calling assigner on the resulting circuit IR may fail.

If cargo is called for any other build target, omit the --features zkllvm option when calling Cargo.

Add circuit code that references an external library:


use ark_pallas::Fq;

type BlockType = [Fq; 2];

pub fn sha256_example() -> BlockType {...}

Compile the circuit with:

cargo +zkllvm build --target assigner-unknown-unknown --features zkllvm --release

To learn more about additional compilation options, click here.