The ZK-VM
Now that you understand what Ligetron is, let's explore how it works as a zero-knowledge virtual machine. Unlike traditional VMs that simply execute code, Ligetron executes your programs while simultaneously generating cryptographic proofs of correct execution, proving computation was performed correctly without revealing private data.
Instance and Witness
When building zero-knowledge applications, you'll work with two types of inputs: public data that everyone can see, and private data that only the prover knows. Think of the public data as the "claim" being made, and the private data as the "secret evidence" that proves it. For example, in age verification: the public claim is "I am over 18 years old," and the private evidence is your actual birthdate.
In zero-knowledge proof literature, these are called instance and witness:
- Instance: All public data known to both prover and verifier, including the program (WASM binary), public inputs (e.g., today's date, a hash value, a threshold), and other public parameters
- Witness: Private inputs known only to the prover (e.g., your birthdate, a password, account balances)
Crucially, your program code doesn't specify which inputs belong to the instance, and which belong to the witness (you may have noticed this in the toy example). Privacy is configured at runtime using the private-indices parameter when running the prover, allowing the same program to be used with different privacy configurations. When running the verifier, the witness can be replaced with placeholder values since the verifier only needs the instance and the proof itself.
WebAssembly as a Circuit Language
When your program runs, every computation gets converted into an arithmetic circuit, a collection of mathematical constraints that represent your program's logic. Each constraint verifies an elementary operation (addition, multiplication, assertion) in the proof system.
Unlike traditional ZK systems that compile to low-level constraint systems ahead of time, Ligetron uses WebAssembly (WASM) as its circuit representation and generates constraints on the fly at runtime. WebAssembly is a portable binary instruction format with mature, widely-used toolchains for C++ and Rust.
Why WASM? It strikes the right balance: low-level enough to connect efficiently with the proof system, yet high-level enough to retain important semantic information like scoping and control flow. This enables efficient memory management and allows the proof system to connect smoothly with WASM's execution model. You write programs in familiar languages, compile them with standard tools, and Ligetron handles the rest.
The Execution Pipeline
Now that you understand how WASM becomes a circuit of constraints, let's see how Ligetron transforms your program into a zero-knowledge proof. We'll examine this from three perspectives: the complete end-to-end flow, how the prover generates proofs, and how the verifier checks them.
Complete End-to-End Flow
The journey from WASM program to verified proof:
Key insight: The prover executes your program three times, once in each stage for a different purpose (commit, constrain, sample). This is what gives Ligetron its space efficiency: at any point in time when generating the proof, only a small portion of the witness matrix needs to be kept in memory, and once that portion is no longer needed, it can be garbage collected.
Prover and Verifier
- Prover
- Verifier
The prover generates proofs through three distinct stages:
What each stage does:
-
Commit (Stage 1): Records witness values as the program executes, encodes them, then hashes each column and builds a Merkle tree commitment.
-
Constrain (Stage 2): Re-executes the program to generate constraint polynomials (code, linear, quadratic) encoding all operations, then hashes them to create a challenge seed.
-
Sample (Stage 3): Executes the program a third time, uses the challenge seed to randomly select sample points, then generates Merkle proofs at those points.
After all three stages complete, the prover validates that all constraints are satisfied before outputting the final proof.
The verifier checks the proof through five main steps:
Why verification is fast: The verifier only checks sampled points (not all values) and deterministically recreates the challenge from the seed, making verification orders of magnitude faster than proof generation.
Generating Constraints with the API
As your WASM program executes, you use API functions to define what properties you want to prove. Ligetron provides two levels of API depending on your needs:
High-Level API: For Program Logic
For most applications, use the simple assertion functions that work directly on boolean expressions and integer values:
// Example: Prove edit distance is less than 5
int distance = calculate_distance(string_a, string_b);
assert_one(distance < 5); // Proves: distance < 5
// Example: Prove two values are equal
assert_one(computed_hash == expected_hash);
// Example: Prove a value is zero
int balance_check = total_in - total_out;
assert_zero(balance_check); // Proves: balance_check = 0
Available functions:
assert_one(condition)- Proves the condition evaluates to true (equals 1)assert_zero(value)- Proves the value equals zerooblivious_if(cond, a, b)- Oblivious selection: returnsaifcond, elseboblivious_min(a, b)/oblivious_max(a, b)- Oblivious comparison
These high-level functions work with regular C++ types and expressions. Behind the scenes, they generate constraints using the BN254 scalar field (Fr), but you don't need to manage field elements explicitly.
Cryptographic Libraries
For cryptographic operations (hashing, signatures, elliptic curves), Ligetron provides ready-to-use libraries that handle field arithmetic internally:
// Example: Poseidon hash using the SDK library
#include <ligetron/poseidon.h>
bn254fr_class input(input_string);
bn254fr_class reference(expected_hash);
poseidon_context<poseidon_permx5_254bit_5>::global_init();
poseidon_context<poseidon_permx5_254bit_5> ctx;
ctx.digest_update(input);
ctx.digest_final();
bn254fr_class::assert_equal(ctx.state[0], reference); // Prove: hash matches
// Example: EdDSA signature verification
#include <ligetron/eddsa.h>
#include <ligetron/poseidon2.h>
bn254fr_class message = 42;
jubjub_point public_key { ... };
eddsa_signature signature { ... };
bn254fr_class digest;
auto *ctx = poseidon2_bn254_context_new();
poseidon2_bn254_digest_update(ctx, message);
poseidon2_bn254_digest_final(ctx, digest);
eddsa_verify(signature, public_key, digest); // Proves: signature is valid
Available cryptographic libraries:
poseidon/poseidon2- Zero-knowledge friendly hash functionseddsa- EdDSA signature verification on Baby JubJub curvebabyjubjub- Elliptic curve point operationsbn254fr_class- C++ wrapper for field elements (RAII, no manual memory management)
When to use cryptographic libraries:
- Verifying signatures in zero-knowledge
- Computing cryptographic hashes (Poseidon, SHA256)
- Implementing privacy-preserving authentication
- Building protocols that need elliptic curve operations
When to use high-level API:
- Regular program logic and computations
- Boolean conditions and comparisons
- Non-cryptographic zero-knowledge applications
The API functions above generate different types of constraints that affect proof generation time and size. To learn what constraints are, how they're generated from WASM operations, and how to optimize performance, see Performance and Parameters.
Deployment Options
Ligetron supports flexible deployment for different environments:
- Native builds: Via Dawn (Metal/Vulkan/DirectX 12) for server-side proving
- Web builds: Via WebGPU API in browsers with zero installation
Both platforms enable billion-gate proofs on commodity hardware, including the first billion-gate proofs running directly in browsers.
See the Installation Guide for setup instructions.