Skip to main content
Leo is designed from the ground up to compile programs into zero-knowledge proofs. This page explains the fundamental ZK concepts underlying Leo and how the compiler transforms high-level code into circuits that can generate and verify proofs.

What is Zero-Knowledge?

A zero-knowledge proof allows one party (the prover) to prove to another party (the verifier) that a statement is true, without revealing any information beyond the validity of the statement itself.

Key Properties

Completeness: If the statement is true, an honest prover can convince an honest verifier. Soundness: A dishonest prover cannot convince a verifier of a false statement (except with negligible probability). Zero-Knowledge: The verifier learns nothing beyond the fact that the statement is true.

Example Use Case

Prove you know a password without revealing it:
The verifier sees that the password is correct, but never sees the actual password value.
Leo uses zk-SNARKs (Zero-Knowledge Succinct Non-Interactive Arguments of Knowledge) - proofs that are small, fast to verify, and don’t require interaction between prover and verifier.

From Leo to Zero-Knowledge Proofs

Compilation Pipeline

Why Flattening is Essential

Zero-knowledge circuits cannot represent dynamic control flow. The flattening pass transforms all control flow into arithmetic operations:
In circuit form, both branches are always computed, and the condition acts as a selector:
This is why Leo requires:
  • All loops must be unrollable at compile time
  • No recursion (would create unbounded circuits)
  • No dynamic memory allocation
Circuit Size Matters: Every operation in your program contributes to the circuit size. Larger circuits require more proving time and memory. The flattening pass ensures both branches execute, so optimize both paths.

R1CS: Rank-1 Constraint System

Leo programs compile to R1CS, a standard representation for arithmetic circuits.

R1CS Structure

Each constraint has the form:
Where:
  • wᵢ are wire values (variables in the circuit)
  • aᵢ, bᵢ, cᵢ are coefficients
  • The constraint enforces: A · B = C

Example: Addition

Compiles to the R1CS constraint:

Example: Multiplication

Compiles to:

Example: Complex Expression

Requires intermediate variables:
This is exactly what the SSA pass does—it breaks down complex expressions into sequences of simple operations.
Each R1CS constraint can only represent one multiplication (plus arbitrary additions). This is why SSA and flattening are crucial: they decompose programs into sequences of simple operations that map cleanly to R1CS.

Aleo’s Architecture

Leo compiles to Aleo Instructions, which are executed by the Aleo Virtual Machine (AVM).

Aleo Instructions

Aleo uses a register-based instruction set:

Public vs Private Data

Leo distinguishes between public and private data: Private (.private): Hidden from the verifier, included in the proof Public (.public): Visible to everyone, used as public inputs to the proof Constant (.constant): Known at compile time, optimized away

Records: Private State

Records enable private state management:
Records are:
  • Private: Only the owner knows the record exists
  • Consumed: Input records are spent (deleted)
  • Created: Output records are generated
  • Encrypted: Stored encrypted on-chain
Records implement the UTXO model from Bitcoin, but with privacy. Each record can only be spent once, and spending it creates new records.

Mappings: Public State

Mappings provide public on-chain storage:
Mapping operations happen in finalize blocks, which execute after the proof is verified.

Zero-Knowledge Optimizations in Leo

1. Constant Folding

The compiler evaluates constants at compile time to reduce circuit size:

2. Dead Code Elimination

Removes unused computations that would add unnecessary constraints:

3. Common Subexpression Elimination

Reuses computed values to reduce duplicate constraints:

4. Function Inlining

Inlines small functions to eliminate call overhead:
Each optimization pass reduces the number of constraints in the final circuit, directly improving proof generation time and memory usage.

Proof Generation and Verification

The Proving Process

  1. Compile: Leo source → Aleo instructions
  2. Execute: Run the program with inputs to generate a witness (all wire values)
  3. Setup: Generate proving and verifying keys (one-time per program)
  4. Prove: Create a proof that the execution is correct
  5. Verify: Check the proof (fast, ~milliseconds)

Trusted Setup

Aleo uses a universal trusted setup, meaning:
  • One setup ceremony serves all programs
  • No per-program setup required
  • Uses the Marlin proof system

Proof Size and Verification Time

With zk-SNARKs:
  • Proof size: ~1-2 KB (constant, independent of program size)
  • Verification time: ~10 milliseconds (constant)
  • Proving time: Proportional to circuit size (can be seconds to minutes)

Cryptographic Primitives

Leo provides cryptographic operations optimized for zero-knowledge circuits:

Hash Functions

Poseidon: Designed specifically for zk-SNARKs, uses far fewer constraints than SHA-256. BHP: Algebraic hash function, efficient in zero-knowledge.
Avoid using SHA-256 or other traditional hash functions in Leo programs. They are extremely expensive in zero-knowledge circuits (tens of thousands of constraints). Use Poseidon or BHP instead.

Commitments

Commitments hide a value while allowing later revelation.

Signatures

Signature verification in zero-knowledge allows proving authorization without revealing the signature itself.

Circuit Size Analysis

Estimating Circuit Size

The circuit size (number of constraints) depends on:
  1. Arithmetic operations: Each multiplication = 1 constraint
  2. Hash operations: Poseidon2 = ~300 constraints, SHA-256 = ~25,000 constraints
  3. Ternary operations: ~3 constraints
  4. Array accesses: Depends on array size (dynamic access requires one constraint per element)
  5. Loop unrolling: Circuit size multiplied by iteration count

Example Analysis

Optimization Guidelines

  1. Minimize multiplications: Use addition when possible
  2. Hoist loop-invariant computations: Move calculations outside loops
  3. Use ternary instead of if-else: Already done by flattening pass
  4. Inline small functions: Reduces call overhead
  5. Eliminate dead code: Remove unused computations
The Leo compiler automatically applies many optimizations. Focus on algorithmic improvements and choosing the right cryptographic primitives.

Async Functions and Finalize

Aleo introduces a two-phase execution model:

Phase 1: Transition (Off-chain, Private)

Phase 2: Finalize (On-chain, Public)

Key Insight: Finalize blocks don’t generate proofs. They execute deterministically on-chain after the proof is verified, enabling public state updates.

Common Pitfalls

1. Dynamic Array Indexing

2. Unbounded Loops

3. Expensive Hash Functions

4. Unnecessary Branching

Further Reading