Skip to main content

Getting Started

The best entry point to start the circuit development process is our template repository. It contains a Dockerfile with all the dependencies installed. It also contains a sample circuit that you can use to get started.

In case if you don't want to use Docker, you can install the compiler manually. It can be done by following the installation guide.

Once you've installed the compiler or built the Docker image - you can start writing your circuit.

Circuit Examples

In this series of simple examples, we are going to learn how to build a provable computations circuit using the C++ SDK and introduce important concepts as we go along.

Hello World Example

Every provable computations 101 starts with this example, so let's follow the tradition. Every provable computations circuit starts with an entry point function marked with [[circuit]] attribute. The function takes some arguments and returns a result. The function body represents an algorithm, which is going to be compiled into a circuit, which can further be used for proof generation.

warning

There can only be a single [[circuit]] directive in a project.

Standard library integral types, such as int , long are supported. The standard types int32_t are the modified versions of a fork of the std C++ library, which the compiler includes internally. The user is hidden from this complexity. Some types ex: strings are currently not supported (but will soon - see limitations).

[[circuit]] std::int32_t addition_std_example(
std::int32_t a,
std::int32_t b) {
auto c = a*b;
return c;
}

The function takes two arguments - two numbers - and multiplies them.

Next, let's see the same example but using the bls12-381 base field type. We will use the interface from the crypto3 library.

#include <nil/crypto3/algebra/curves/bls12.hpp>

using namespace nil::crypto3::algebra::curves;

[[circuit]] typename bls12<381>::base_field_type::value_type addition_example(
typename bls12<381>::base_field_type::value_type a,
typename bls12<381>::base_field_type::value_type b) {

typename bls12<381>::base_field_type::value_type c = a*b;
return c;
}

The most performant algorithms and types will be from the crypto3 SDK. However, the user can still write circuits without including the library, as the compiler provides a C++ dialect with such types supported.

Hence our addition example can be re-written as:

[[circuit]] __zkllvm_field_pallas_base addition_example(
__zkllvm_field_pallas_base a,
__zkllvm_field_pallas_base b) {

__zkllvm_field_pallas_base c = a*b;
return c;
}

Please see Builtin types for more details.

Development Flow

Using the code of the hello world example, we will walk through the development flow of a circuit.

1. Compile Circuit

A circuit developer writes his circuit in a high-level language of his choice (C++/Rust). This circuit is next compiled using the clang compiler generated by zkLLVM. This step outputs a *ll file, an intermediate representation of the circuit.

2. Publish Circuit (Proof Market)

The generated IR can be pushed to the proof market. This enables Proof requesters & generators to serve each other. The proof market project handles the process of publishing circuits.

3. Publish On chain verifiers (ex: EVM)

The circuit developer will also be generating smart contracts for the circuits they have created. This will enable on-chain verification of the proof. The smart contracts consist of gate representations of the circuit.

These contracts work in conjunction with the placeholder proof validation smart contracts. The lorem-ipsum project handles the process to transpile the circuit into smart contracts.

info

EVM is one of the first supported protocols. The transpiler will be extended to generate verification in other VMs.

Next Steps

If you want to know more about writing circuits, we've prepared a tutorial on how to implement a zk-Bridge using zkLLVM in the Tutorials section.

To know more about limitations of the compiler, please see Limitations.