Introduction
ZisK is a high-performance zkVM (Zero-Knowledge Virtual Machine) designed to generate zero-knowledge proofs of arbitrary program execution. It enables developers to prove the correctness of a computation without revealing its internal state, making ZisK a powerful tool for privacy-preserving and verifiable computation.
Proving systems traditionally involve complex cryptographic operations that require deep expertise and significant computational resources. ZisK abstracts these complexities by providing an optimized toolstack that minimizes computational overhead, making ZK technology accessible to a broader range of developers. With Rust-based execution and planned multi-language support, ZisK is designed to be developer-friendly while maintaining high performance and robust security.
Why ZisK?
- High-performance architecture optimized for low-latency proof generation.
- Rust-based zkVM, with future support for additional languages.
- No recompilation required across different programs.
- Standardized prover interface (JSON-RPC, GRPC, CLI).
- Flexible integration: usable as a standalone service or as a library.
- Decentralized architecture for trustless proof generation.
- Optimized proof generation costs for real-world applications.
- Fully open-source and backed by Polygon zkEVM and Plonky3 technology.
Installation Guide
ZisK can be installed from prebuilt binaries (recommended) or by building ZisK tools, toolchain and setup files from source.
System Requirements
ZisK currently supports Linux x86_64 systems. Proof generation on macOS is not supported.
Required Tools (Linux & macOS)
Ensure the following tools are installed:
Installing Dependencies
Ubuntu
Ubuntu 22.04 or higher is required.
Install all required dependencies with:
sudo apt-get install -y xz-utils jq curl build-essential qemu-system libomp-dev libgmp-dev nlohmann-json3-dev protobuf-compiler uuid-dev libgrpc++-dev libsecp256k1-dev libsodium-dev libpqxx-dev nasm
macOs
-
Install Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
Install protobuf (required for
cargo build
):brew install protobuf
-
Install libusb & jq (required for
ziskup
):brew install libusb jq
-
Install Node.js:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash source $HOME/.bashrc nvm install 19 nvm use 19
-
Install Circom:
https://docs.circom.io/getting-started/installation/
Alternative: Using Nix Flake
You can use Nix to install all dependencies.
-
Follow the instructions to install Nix on your system.
-
Use the
flake.nix
file from the ZisK repository to set up the development environment:nix main
-
To start a shell with ZisK’s environment:
nix main -c zsh
This will open a shell with the
PATH
andLD_LIBRARY_PATH
correctly configured for building the project. Exit the shell withCtrl+D
.
Installing ZisK
Option 1: Prebuilt Binaries (Recommended)
-
Install the ZisK installer
ziskup
:curl https://raw.githubusercontent.com/0xPolygonHermez/zisk/main/ziskup/install.sh | bash
This will enable the
ziskup
command in your terminal.Restart your terminal session or run:
source $HOME/.bashrc
-
Install the ZisK toolchain and CLI tools:
ziskup
-
Verify the Rust toolchain: (which includes support for the
riscv64ima-polygon-ziskos
compilation target):rustup toolchain list
The output should include an entry for
zisk
, similar to this:stable-x86_64-unknown-linux-gnu (default) nightly-x86_64-unknown-linux-gnu zisk
-
Verify the
cargo-zisk
CLI tool:cargo-zisk --version
-
Download and install setup files:
Option 1: Download the proving key files:
curl -O https://storage.googleapis.com/zisk/zisk-provingkey-0.4.0.tar.gz curl -O https://storage.googleapis.com/zisk/zisk-provingkey-0.4.0.tar.gz.md5
Verify the MD5 checksum:
md5sum -c zisk-provingkey-0.4.0.tar.gz.md5
Extract the file to the
$HOME/.zisk
directory:tar --overwrite -xvf zisk-provingkey-0.4.0.tar.gz -C $HOME/.zisk
Option 2: Alternatively, if you only need to verify proofs, download and install the verify key files:
curl -O https://storage.googleapis.com/zisk/zisk-verifykey-0.4.0.tar.gz curl -O https://storage.googleapis.com/zisk/zisk-verifykey-0.4.0.tar.gz.md5
Then, follow the same verification and installation steps as for the proving key files.
To update ZisK to the latest version, simply run again the previous steps.
Option 2: Building from Source
Build ZisK
-
Ensure all dependencies required to build the Rust toolchain from source are installed.
-
Clone the ZisK repository:
git clone https://github.com/0xPolygonHermez/zisk.git cd zisk
-
Build ZisK tools:
cargo build --release
-
Copy the tools to
~/.zisk/bin
directory:mkdir -p $HOME/.zisk/bin cp target/release/cargo-zisk target/release/ziskemu target/release/riscv2zisk target/release/libzisk_witness.so $HOME/.zisk/bin
-
Add
~/.zisk/bin
to your profile file, for example for.bashrc
executing the following commands:echo >>$HOME/.bashrc && echo "export PATH=\"\$PATH:$HOME/.zisk/bin\"" >> $HOME/.bashrc source $HOME/.bashrc
-
Build the Rust ZisK toolchain:
cargo-zisk sdk build-toolchain
-
Install the Rust ZisK toolchain:
ZISK_TOOLCHAIN_SOURCE_DIR=. cargo-zisk sdk install-toolchain
-
Verify the installation:
rustup toolchain list
Ensure
zisk
appears in the list of installed toolchains.
Build Setup
The setup building process is highly intensive in terms of CPU and memory usage. You will need a machine with at least the following hardware requirements:
- 32 CPUs
- 512 GB of RAM
- 100 GB of free disk space
Please note that the process can be long, taking approximately 2–3 hours depending on the machine used.
NodeJS version 20.x or higher is required to build the setup files.
-
Clone the following repositories in the parent folder of the
zisk
folder created in the previous section:git clone https://github.com/0xPolygonHermez/pil2-compiler.git git clone https://github.com/0xPolygonHermez/pil2-proofman.git git clone https://github.com/0xPolygonHermez/pil2-proofman-js
-
Install packages:
(cd pil2-compiler && npm i) (cd pil2-proofman-js && npm i)
-
Note: All subsequent commands must be executed from the
zisk
folder created in the previous section. -
Compile ZisK PIL: (Note that this command may take 20-30 minutes to complete)
node --max-old-space-size=131072 ../pil2-compiler/src/pil.js pil/zisk.pil -I pil,../pil2-proofman/pil2-components/lib/std/pil,state-machines,precompiles -o pil/zisk.pilout
This command will create the
pil/zisk.pilout
file -
Generate fixed data:
cargo run --release --bin keccakf_fixed_gen mkdir build mv precompiles/keccakf/src/keccakf_fixed.bin build
These commands generates the
keccakf_fixed.bin
file in thebuild
directory. -
Generate setup data: (Note that this command may take 2–3 hours to complete):
node --max-old-space-size=65536 ../pil2-proofman-js/src/main_setup.js -a ./pil/zisk.pilout -b build -i ./build/keccakf_fixed.bin -r
This command generates the
provingKey
directory. -
Copy (or move) the
provingKey
directory to$HOME/.zisk
directory:cp -R build/provingKey $HOME/.zisk
Uninstall Zisk
To uninstall ZisK, run:
rm -rf $HOME/.zisk
Quickstart
In this guide, you will learn how to create and run a simple program using ZisK.
Create a Project
The first step is to generate a new example project using the cargo-zisk sdk new <name>
command. This command creates a new directory named <name>
in your current directory. For example:
cargo-zisk sdk new sha_hasher
cd sha_hasher
This will create a project with the following structure:
.
├── build.rs
├── Cargo.lock
├── Cargo.toml
├── .gitignore
└── src
└── main.rs
The example program takes a number n
as input and computes the SHA-256 hash n
times.
The build.rs
file generates an input.bin
file containing the value of n
(e.g., 20). This file is used in main.rs
as input to calculate the hash.
You can run the program on your native architecture with the following command:
cargo run
The output will be:
public 0: 0x98211882
public 1: 0xbd13089b
public 2: 0x6ccf1fca
public 3: 0x81f7f0e4
public 4: 0xabf6352a
public 5: 0x0c39c9b1
public 6: 0x1f142cac
public 7: 0x233f1280
Build
The next step is to build the program using the cargo-zisk
command to generate an ELF file (RISC-V), which will be used later to generate the proof. Execute:
cargo-zisk build --release
This command builds the program using the riscv64ima_polygon_ziskos
target. The resulting sha_hasher
ELF file (without extension) is generated in the ./target/riscv64ima-polygon-ziskos-elf/release
directory.
Execute
Before generating a proof, you can test the program using the ZisK emulator to ensure its correctness. Specify the ELF file (using the -e
or --elf flag
) and the input file input.bin
(using the -i
or --inputs
flag):
ziskemu -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin
The output will be:
98211882
bd13089b
6ccf1fca
81f7f0e4
abf6352a
0c39c9b1
1f142cac
233f1280
Alternatively, you can build and run the program with:
cargo-zisk run --release
This command uses the file located at build/input.bin
as the input file.
Prove
You can generate and verify a proof using the cargo-zisk prove
command by providing the ELF file (with the -e
or --elf
flag) and the input file (with the -i
or --input-data
flag).
To generate and verify a proof for the previously built ELF and input files, execute:
cargo-zisk prove -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.so -k $HOME/.zisk/provingKey -o proof -a -y
This command generates the proof in the ./proof directory
. If everything goes well, you will see a message similar to:
...
[INFO ] ProofMan: ✓ Vadcop Final proof was verified
[INFO ] stop <<< GENERATING_VADCOP_PROOF 91706ms
[INFO ] ProofMan: Proofs generated successfully
Distributed prove
Zisk can run proves using multiple processes in the same server or in multiple servers. To use zisk in distributed mode you need to have installed a mpi library. To use the distributed mode the compilation command is:
cargo-zisk build --release --features "distributed"
Then the execution command will be:
mpirun --bind-to none -np <number_processes> -x OMP_NUM_THREADS=<number_of_threads_per_process> target/release/cargo-zisk prove -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.so -k $HOME/.zisk/provingKey -o proof -a -y
Writing Programs
This document explains how to write or modify a Rust program for execution in ZisK.
Setup
Code changes
Writing a Rust program for ZisK is similar to writing a standard Rust program, with a few minor modifications. Follow these steps:
-
Modify
main.rs
file:Add the following code to mark the main function as the entry point for ZisK:
#![allow(unused)] #![no_main] fn main() { ziskos::entrypoint!(main); }
-
Modify
Cargo.toml
file:Add the
ziskos
crate as a dependency:[dependencies] ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git" }
Let's show these changes using the example program from the Quickstart section.
Example program
main.rs
:
// This example program takes a number `n` as input and computes the SHA-256 hash `n` times sequentially. // Mark the main function as the entry point for ZisK #![no_main] ziskos::entrypoint!(main); use sha2::{Digest, Sha256}; use std::convert::TryInto; use ziskos::{read_input, set_output}; use byteorder::ByteOrder; fn main() { // Read the input data as a byte array from ziskos let input: Vec<u8> = read_input(); // Get the 'n' value converting the input byte array into a u64 value let n: u64 = u64::from_le_bytes(input.try_into().unwrap()); let mut hash = [0u8; 32]; // Compute SHA-256 hashing 'n' times for _ in 0..n { let mut hasher = Sha256::new(); hasher.update(hash); let digest = &hasher.finalize(); hash = Into::<[u8; 32]>::into(*digest); } // Split 'hash' value into chunks of 32 bits and write them to ziskos output for i in 0..8 { let val = byteorder::BigEndian::read_u32(&mut hash[i * 4..i * 4 + 4]); set_output(i, val); } }
Cargo.toml
:
[package]
name = "sha_hasher"
version = "0.1.0"
edition = "2021"
default-run = "sha_hasher"
[dependencies]
byteorder = "1.5.0"
sha2 = "0.10.8"
ziskos = { git = "https://github.com/0xPolygonHermez/zisk.git" }
Input/Output Data
To provide input data for ZisK, you need to write that data in a binary file (e.g., input.bin
).
If your program requires complex input data, consider using a serialization mechanism (like bincode
crate) to store it in input.bin
file.
In your program, use the ziskos::read_input()
function to retrieve the input data from the input.bin
file:
#![allow(unused)] fn main() { // Read the input data as a byte array from ziskos let input: Vec<u8> = read_input(); }
To write public output data, use the ziskos::set_output()
function. Since the function accepts u32
values, split the output data into 32-bit chunks if necessary and increase the id
parameter of the function in each call:
#![allow(unused)] fn main() { // Split 'hash' value into chunks of 32 bits and write them to ziskos output for i in 0..8 { let val = byteorder::BigEndian::read_u32(&mut hash[i * 4..i * 4 + 4]); set_output(i, val); } }
Build
Before compiling your program for ZisK, you can test it on the native architecture just like any regular Rust program using the cargo
command.
Once your program is ready to run on ZisK, compile it into an ELF file (RISC-V architecture), using the cargo-zisk
CLI tool:
cargo-zisk build
This command compiles the program using the riscv64ima_polygon_ziskos
target. The resulting sha_hasher
ELF file (without extension) is generated in the ./target/riscv64ima-polygon-ziskos-elf/debug
directory.
For production, compile the ELF file with the --release
flag, similar to how you compile Rust projects:
cargo-zisk build --release
In this case, the sha_hasher
ELF file will be generated in the ./target/riscv64ima-polygon-ziskos-elf/release
directory.
Execute
You can test your compiled program using the ZisK emulator (ziskemu
) before generating a proof. Use the -e
(--elf
) flag to specify the location of the ELF file and the -i
(--inputs
) flag to specify the location of the input file:
cargo-zisk build --release
ziskemu -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin
Alternatively, you can build and execute the program in the ZisK emulator with a single command:
cargo-zisk run --release
This command builds the ELF file and executes it using ziskemu
along with the input.bin
file that must be located in the .build
directory at the root of your Rust project:
.
├── build
| └── input.bin
├── src
| └── main.rs
├── Cargo.lock
├── Cargo.toml
Alternatively, you can specify the location of the input file using -i
(--input
) flag:
cargo-zisk run -i build/input.bin
If the program requires a large number of ZisK steps, you might encounter the following error:
Error during emulation: EmulationNoCompleted
Error: Error executing Run command
To resolve this, you can increase the number of execution steps using the -n
(--max-steps
) flag. For example:
ziskemu -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -n 10000000000
Metrics and Statistics
Performance Metrics
You can get performance metrics related to the program execution in ZisK using the -m
(--log-metrics
) flag in the cargo-zisk run
command or in ziskemu
tool:
cargo-zisk run --release -m
Or
ziskemu -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -m
The output will include details such as execution time, throughput, and clock cycles per step:
process_rom() steps=85309 duration=0.0009 tp=89.8565 Msteps/s freq=3051.0000 33.9542 clocks/step
98211882
bd13089b
6ccf1fca
...
Execution Statistics
You can get statistics related to the program execution in Zisk using the -x
(--stats
) flag in the cargo-zisk run
command or in ziskemu
tool:
cargo-zisk run --release -x
Or
ziskemu -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -x
The output will include details such as cost definitions, total cost, register reads/writes, opcode statistics, etc:
Cost definitions:
AREA_PER_SEC: 1000000 steps
COST_MEMA_R1: 0.00002 sec
COST_MEMA_R2: 0.00004 sec
COST_MEMA_W1: 0.00004 sec
COST_MEMA_W2: 0.00008 sec
COST_USUAL: 0.000008 sec
COST_STEP: 0.00005 sec
Total Cost: 12.81 sec
Main Cost: 4.27 sec 85308 steps
Mem Cost: 2.22 sec 222052 steps
Mem Align: 0.05 sec 2701 steps
Opcodes: 6.24 sec 1270 steps (81182 ops)
Usual: 0.03 sec 4127 steps
Memory: 135563 a reads + 1625 na1 reads + 10 na2 reads + 84328 a writes + 524 na1 writes + 2 na2 writes = 137198 reads + 84854 writes = 222052 r/w
Registy: reads=130928=95% writes=81236=95% total=212164=95% r/w
Reg 0 reads=0=0% writes=0=0% r/w=0=0%
Reg 1 reads=2772=2% writes=1253=3% r/w=4025=1%
Reg 2 reads=8275=6% writes=134=10% r/w=8409=3%
Reg 3 reads=1=0% writes=2=0% r/w=3=0%
...
Reg 29 reads=2520=1% writes=1180=3% r/w=3700=1%
Reg 30 reads=2680=2% writes=1540=3% r/w=4220=1%
Reg 31 reads=2180=1% writes=980=2% r/w=3160=1%
Opcodes:
flag: 0.00 sec (0 steps/op) (89 ops)
copyb: 0.00 sec (0 steps/op) (10568 ops)
add: 1.12 sec (77 steps/op) (14569 ops)
ltu: 0.01 sec (77 steps/op) (101 ops)
...
xor: 1.06 sec (77 steps/op) (13774 ops)
signextend_b: 0.03 sec (109 steps/op) (320 ops)
signextend_w: 0.03 sec (109 steps/op) (320 ops)
98211882
bd13089b
6ccf1fca
...
Prove
Verify Constraints
Before to generate a proof (that can take some time) you can verify that all the constraints are satisfied:
cargo-zisk verify-constraints -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.so -k $HOME/.zisk/provingKey
If everything is correct, you will see an output similar to:
[INFO ] GlCstVfy: --> Checking global constraints
[INFO ] CstrVrfy: ··· ✓ All global constraints were successfully verified
[INFO ] CstrVrfy: ··· ✓ All constraints were verified
Generate Proof
To generate a proof, run following command:
cargo-zisk prove -e target/riscv64ima-polygon-ziskos-elf/release/sha_hasher -i build/input.bin -w $HOME/.zisk/bin/libzisk_witness.so -k $HOME/.zisk/provingKey -o proof -a -y
In this command:
-e
(--elf
) flag is used to specify the ELF file localtion.-i
(--inputs
) flag is used specify the input file location.-w
(--witness
) and-k
(--proving-key
) flags are used to specify the location of the witness library and proving key files required for proof generation (located in the$HOME/.zisk
installation folder by default).-o
(--output
) flag determines the output directory (in this exampleproof
).-a
(--aggregation
) flag indicates that a final aggregated proof (containing all generated sub-proofs) should be produced.-y
(--verify-proofs
) flag instructs the tool to verify the proof immediately after it is generated (verification can also be performed later using thecargo-zisk verify
command).
If the process is successful, you should see a message similar to:
...
[INFO ] ProofMan: ✓ Vadcop Final proof was verified
[INFO ] stop <<< GENERATING_VADCOP_PROOF 91706ms
[INFO ] ProofMan: Proofs generated successfully
Verify Proof
To verify a generated proof, use the following command:
cargo-zisk verify -p ./proof/proofs/vadcop_final_proof.json -u ./proof/publics.json -s $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.starkinfo.json -e $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verifier.bin -k $HOME/.zisk/provingKey/zisk/vadcop_final/vadcop_final.verkey.json
In this command:
-p
(--proof
) flag specifies the final proof file generated with cargo-zisk prove.-u
(--public_inputs
) flag provides the path to the public inputs associated with the proof.- The remaining flags specify the files required for verification, located in the
$HOME/.zisk
directory by default.
Precompiles
Precompiles are built-in system functions within ZisK’s operating system that accelerate computationally expensive and frequently used operations such as the Keccak-f permutation and Secp256k1 addition and doubling.
These precompiles improve proving efficiency by offloading intensive computations from ZisK programs to dedicated, pre-integrated sub-processors. ZisK manages precompiles as system calls using the RISC-V ecall
instruction.
How Precompiles Work
Precompiles are primarily used to patch third-party crates, replacing costly operations with system calls. This ensures that commonly used cryptographic primitives like Keccak hashing and elliptic curve operations can be efficiently executed within ZisK programs.
Typically, precompiles are used to patch third-party crates that implement these operations and are then used as dependencies in the Zisk programs we write.
You can see here an example of the patched tiny-keccak
crate.
Supported Precompiles
⚠️ Currently, ZisK only supports the keccak precompile, but work is underway to introduce additional cryptographic primitives, including:
- Secp256k1 group operations.
- BN254 group operations.
- SHA2-256.
Available Precompiles in ZisK
#![allow(unused)] fn main() { extern "C" { pub fn syscall_keccak_f(state: *mut [u64; 25]); } }
syscall_keccak_f
: Executes a Keccak permutation function on a 25-element state array.
Ziskof
Riscof tests
The following test generates the riscof test files, converts the corresponding .elf files into ZisK ROMs, and executes them providing the output in stdout for comparison against a reference RISCV implementation. This process is not trivial and has been semi-automatized.
First, compile the ZisK Emulator:
$ cargo clean
$ cargo build --release
Second, download and run a docker image from the riscof repository to generate and run the riscof tests:
$ docker run --rm -v ./target/release/ziskemu:/program -v ./riscof/:/workspace/output/ -ti hermeznetwork/ziskof:latest
The test can take a few minutes to complete. Any error would be displayed in red.