Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Circuit and Proof

Circuit Example

zkMove is designed to be developer-friendly and fits naturally into existing Move development workflows. By leveraging the standard Move package structure, zkMove lets you define zk circuits alongside your Move code with minimal configuration changes.

The example below is a Move module that computes the Fibonacci sequence. The full source can be found in the example directory of the halo2-verifier.move repository. We will build a zk circuit for the test_fibonacci entry function.

// fibonacci.move
module 0x1::fibonacci {
    public entry fun test_fibonacci(n: u64) {
        let value1 = 0u256;
        let value2 = 1u256;
        let fibo = 0u256;

        let i = 0u64;
        while (i < n) {
            fibo = value1 + value2;
            value1 = value2;
            value2 = fibo;
            i = i + 1;
        };
        fibo;
    }
}

To define a circuit for this function, add a [circuit.<name>] section to the package manifest Move.toml. In this example, the circuit is named fibonacci:

[package]
name = "example"
version = "0.0.1"

[dependencies]
MoveStdlib = { git = "https://github.com/zkmove/aptos-core.git", subdir = "third_party/move/move-stdlib", rev = "witnessing" }

[addresses]
std = "0x1"

[circuit.fibonacci]
max_execution_rows = 278     # Max rows for the execution subcircuit.
max_poseidon_rows = 100      # Max rows for the Poseidon subcircuit.
entry = { module_id = "0x1::fibonacci", function_name = "test_fibonacci" }

Generate a Witness

First, build the example package. You can use zkmove vm compile, which is equivalent to move build. The zkmove vm commands read the compiled package from the Move build output.

# Run from the package root (the directory containing `Move.toml`).
zkmove vm compile --package-path ./ --skip-fetch-latest-git-deps

Then execute the entry function with zkmove vm run to generate the witness. Specify the entry function with --module-id and --function-name. By default, witness files are written to the witnesses/ directory:

zkmove vm run \
  --package-path ./ \
  --module-id 0x1::fibonacci \
  --function-name test_fibonacci \
  --args 10u64

# Reuse the witness generated by this run in the following commands.
export WITNESS="$(find witnesses -type f -name 'test_fibonacci-*.json' -print | sort | tail -n 1)"
test -f "$WITNESS"

Setup Circuit Artifacts

Before proving, generate the circuit artifacts for this circuit. Use --circuit-name to select the [circuit.<name>] section in Move.toml. The command writes params.bin, pk.bin, vk.bin, and metadata.json to setup/ by default.

There are two ways to set up the circuit:

Option 1: Setup from the entry function

By default, setup builds the circuit directly from the entry function declared in the [circuit.<name>] section of Move.toml, sizing it according to max_execution_rows and max_poseidon_rows:

zkmove vm setup \
  --package-path ./ \
  --circuit-name fibonacci \
  --params-path params/kzg_bn254_12.srs

Option 2: Setup from a witness

Alternatively, pass a witness file with --witness. The circuit is then built and sized from the actual execution trace. The witness must be generated from the same entry function declared in the circuit section, otherwise the command fails.

The advantage of this approach is that developers do not need to set max_execution_rows and max_poseidon_rows in Move.toml—the circuit dimensions are derived automatically from the execution trace. For beginners, choosing reasonable values for max_execution_rows and max_poseidon_rows can be quite difficult, so this approach is easier to start with.

However, it also has a limitation: it is only suitable for code with a fixed execution path (no branches or loops). If the execution path varies with the input arguments, a circuit sized from one particular run may not fit other runs, in which case you should use Option 1 with explicitly configured row limits.

zkmove vm setup \
  --package-path ./ \
  --circuit-name fibonacci \
  --params-path params/kzg_bn254_12.srs \
  --witness "$WITNESS"

Public inputs

With either option, if your circuit exposes one or more entry arguments as public inputs, add the public-input indices with --pubs-indices. For example:

zkmove vm setup \
  --package-path ./ \
  --circuit-name fibonacci \
  --params-path params/kzg_bn254_12.srs \
  --witness "$WITNESS" \
  --pubs-indices 0

Use the same --pubs-indices values later when building on-chain verifier artifacts for this circuit.

The setup step records the circuit’s entry function and configuration in setup/metadata.json, so the prove and verify commands below no longer need --circuit-name.


Generate a Proof

Run the following command from the package root. prove runs the entry function recorded in the setup metadata with the given arguments to generate the witness, then produces the proof. Proof artifacts are written to the proofs/ directory by default:

zkmove vm prove \
  --package-path ./ \
  --args 10u64

# Locate the proof artifacts generated by this run.
export RUN_ID="$(basename "$(find proofs -type f -name 'test_fibonacci-*.proof' -print | sort | tail -n 1)" .proof)"
export PUBS_PATH="proofs/${RUN_ID}.instance"
export PROOF_PATH="proofs/${RUN_ID}.proof"

Optional: verify locally before submitting on-chain.

zkmove vm verify \
  --package-path ./ \
  --pubs-path "$PUBS_PATH" \
  --proof-path "$PROOF_PATH"

prove and verify read setup/metadata.json, setup/params.bin, setup/pk.bin, and setup/vk.bin by default. Use --setup-dir <dir> if you store those setup artifacts elsewhere.