This document explains the cryptographic proof system used by Shadow-EVM.
Shadow-EVM uses RISC Zero's proof system to generate Zero-Knowledge proofs of EVM execution. The system provides:
- Computational Integrity: Proof that the EVM executed correctly
- Succinctness: Small proof size (~256 bytes for Groth16)
- Zero-Knowledge: Optional privacy for execution details
- On-Chain Verification: Efficient Solidity verification
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ RISC-V Guest │────▶│ STARK Proof │────▶│ Groth16 SNARK │
│ (Execution) │ │ (Succinct) │ │ (On-Chain Ready)│
└─────────────────┘ └─────────────────┘ └─────────────────┘
~10M cycles ~1MB proof ~256 bytes
The RISC-V guest program executes inside the ZK-VM:
- Fetch: Guest reads
ExecutionInputfrom host - Execute:
ShadowExecutorruns the EVM transaction using revm - Commit: Guest writes
ExecutionCommitmentto journal
All memory operations, arithmetic, and I/O are recorded by the prover.
RISC Zero generates a STARK (Scalable Transparent ARgument of Knowledge):
- Proves correct execution of RISC-V instructions
- Recursively composes sub-proofs
- Results in a succinct proof (~1MB)
For on-chain verification, the STARK is wrapped in a Groth16 SNARK:
- Constant-size proof (~256 bytes)
- Constant verification time (~200k gas)
- Requires trusted setup (RISC Zero provides this)
The ExecutionCommitment is the public output of the ZK proof:
pub struct ExecutionCommitment {
/// Hash of ExecutionInput
pub input_hash: Hash,
/// Hash of ExecutionOutput
pub output_hash: Hash,
/// Pre-state root
pub pre_state_root: Hash,
/// Post-state root
pub post_state_root: Hash,
/// Combined commitment: keccak256(input_hash || output_hash)
pub commitment: Hash,
}input_hash = keccak256(serialize(ExecutionInput))
output_hash = keccak256(serialize(ExecutionOutput))
commitment = keccak256(input_hash || output_hash)
This binds the specific execution input to its output, enabling:
- Replay Prevention: Each commitment is unique
- State Verification: On-chain contracts can verify state transitions
- Auditability: Anyone can verify the commitment matches known I/O
The journal is the public output of the ZK proof, formatted as:
┌─────────────────────────────────────────────────────────────────┐
│ Bytes 0-31 │ input_hash │ Keccak256 of ExecutionInput │
│ Bytes 32-63 │ output_hash │ Keccak256 of ExecutionOutput │
│ Bytes 64-95 │ pre_state_root │ State root before execution │
│ Bytes 96-127 │ post_state_root│ State root after execution │
│ Bytes 128-159│ commitment │ Combined cryptographic binding │
└─────────────────────────────────────────────────────────────────┘
Total: 160 bytes
The Image ID is a unique identifier for the guest program:
let image_id: [u8; 32] = /* SHA256 of guest ELF */;This ensures:
- The proof was generated by the exact Shadow-EVM guest code
- No malicious modifications to the executor
- Deterministic program identification
| Property | Guarantee |
|---|---|
| Execution Correctness | EVM executed according to specification |
| State Transition | pre_state + tx = post_state |
| Determinism | Same input always produces same output |
| Program Integrity | Proof was generated by known guest |
| Property | Reason |
|---|---|
| Input Validity | Caller must verify input is appropriate |
| State Existence | Pre-state must be proven to exist on-chain |
| Economic Security | Separate from cryptographic security |
The Solidity verifier performs:
function verify(bytes calldata seal, bytes calldata journal) external {
// 1. Verify Groth16 proof
bool valid = riscZeroVerifier.verify(seal, imageId, sha256(journal));
require(valid);
// 2. Decode commitment from journal
ExecutionCommitment memory c = decodeJournal(journal);
// 3. Record state transition
stateTransitions[c.preStateRoot] = c.postStateRoot;
}| Operation | Approximate Gas |
|---|---|
| Groth16 Verification | ~200,000 |
| Journal Decoding | ~5,000 |
| State Storage | ~20,000 |
| Event Emission | ~5,000 |
| Total | ~230,000 |
Compare to running complex EVM logic on-chain: millions of gas.
shadow-evm prove --input tx.json --output proof.bin --dev- Fast proof generation (~seconds)
- NOT cryptographically secure
- For testing only
shadow-evm prove --input tx.json --output proof.bin- Full STARK + Groth16
- Cryptographically secure
- Takes longer (~minutes to hours)
Multiple proofs can be aggregated into a single proof:
┌───────┐ ┌───────┐ ┌───────┐
│Proof 1│ │Proof 2│ │Proof 3│
└───┬───┘ └───┬───┘ └───┬───┘
│ │ │
└──────────┼──────────┘
│
┌─────▼─────┐
│ Aggregate │
│ Proof │
└───────────┘
This enables:
- Batch verification (pay gas once for many proofs)
- Recursive composition (proofs of proofs)
- Throughput scaling