The problem with general-purpose zkVMs
Traditional ISAs were designed to maximize throughput on physical hardware, not to minimize the cost of cryptographic proving. A zkVM that reuses one ends up with needlessly complex constraint systems and slower proof generation.
OpenZKVM does not adapt an existing ISA. We designed its instruction set, ZK IR, from first principles for STARK-based proving systems.
The theoretical foundations and full formal analysis of ZK IR, including security proofs, are in our peer-reviewed paper: ZK IR: A Minimalist Instruction Set Architecture for Efficient Zero-Knowledge Proof Generation
Design principles
Principle 1: minimize constraints
Each instruction is designed to need as few algebraic constraints in the STARK proof as possible. To that end, all ZK IR instructions share one 32-bit format:
[opcode:6][rd:5][rs1:5][rs2:5][imm:11]
The constraint system therefore needs no instruction decoding logic, saving approximately 20 constraints per instruction compared with variable-length encodings.
Constraint counts by instruction class:
| Instruction Type | ZK IR Constraints | RISC-V Constraints |
|---|---|---|
| ALU (add, sub, xor) | 5-6 | 12-15 |
| Memory Load | 8-10 | 18-22 |
| Memory Store | 7-9 | 16-20 |
| Branch | 12-14 | 25-30 |
Principle 2: field-native arithmetic
ZK IR works natively over the Baby Bear prime field (p = 2³¹ - 2²⁷ + 1), so there is no overflow handling and modular arithmetic is efficient:
Field characteristics:
- Prime: p = 2013265921
- 32-bit native representation
- Efficient Montgomery multiplication
- Two's complement compatible for signed operations
This matches Plonky3's field requirements exactly and minimizes conversion overhead.
Principle 3: deterministic execution
ZK IR removes all the sources of non-determinism found in traditional architectures:
- no interrupts or exceptions during proving
- deterministic memory allocation
- fixed instruction timing (no caches or speculation)
- bounded loop iterations
Registers
ZK IR has 32 general-purpose registers, with a calling convention optimized for common cryptographic patterns:
r0 : Zero register (hardwired to 0)
r1-r8 : Argument/return registers
r9-r16 : Caller-saved temporaries
r17-r24 : Callee-saved registers
r25-r28 : Reserved for runtime
r29 (fp) : Frame pointer
r30 (lr) : Link register
r31 (sp) : Stack pointer
Compilation pipeline
OpenZKVM integrates with standard toolchains through our LLVM backend:
┌─────────────────┐
│ Source Code │ Rust, C, C++, Go
│ (High-level) │
└────────┬────────┘
│
▼
┌─────────────────┐
│ LLVM IR │ Platform-independent
│ │ intermediate representation
└────────┬────────┘
│
▼
┌─────────────────┐
│ zkir-llvm │ Our custom LLVM backend
│ │
└────────┬────────┘
│
▼
┌─────────────────┐
│ ZK IR Bytecode │ Optimized for proving
│ │
└────────┬────────┘
│
▼
┌─────────────────┐
│ zkir-prover │ Plonky3-based proof generation
│ │
└────────┬────────┘
│
▼
┌─────────────────┐
│ STARK Proof │ Succinct, verifiable proof
└─────────────────┘
Benchmarks
We benchmarked OpenZKVM against SP1 and RISC Zero, two leading zkVMs, on standard cryptographic workloads:
| Benchmark | OpenZKVM | SP1 | RISC Zero |
|---|---|---|---|
| SHA-256 (1KB) | 1.0x | 1.3x | 1.6x |
| Keccak-256 (1KB) | 1.0x | 1.4x | 1.7x |
| ECDSA Verify | 1.0x | 1.2x | 1.4x |
| Merkle Proof (depth 20) | 1.0x | 1.5x | 1.8x |
Lower is better. OpenZKVM normalized to 1.0x.
Proof size and verification time
| System | Proof Size | Verification Time |
|---|---|---|
| OpenZKVM | ~100 KB | 12ms |
| SP1 | ~150 KB | 18ms |
| RISC Zero | ~200 KB | 25ms |
Implementation details
Memory model
OpenZKVM memory is word-addressed, with separate instruction and data regions:
- Instruction memory: read-only, loaded at initialization
- Data memory: read-write, initialized to zero
- Stack: grows downward from high addresses
- Heap: grows upward from low addresses
Cryptographic precompiles
OpenZKVM provides native precompiled functions for performance-critical operations:
// Native hash computation (single constraint)
let hash = zkir::precompile::sha256(data);
// Native field arithmetic
let result = zkir::precompile::field_mul(a, b);
Source code
The code is open source:
- openzkvm: ISA specification, VM implementation and documentation
- zkir-llvm: LLVM backend for compiling high-level languages
- zkir-prover: Plonky3-based proving system