Verify STARK
A guest library for recursive verification of OpenVM STARK proofs. The library uses the deferral framework to offload proof verification to the aggregation phase, making recursive verification efficient within guest programs.
You can find the library source code here.
Guest
The guest library (openvm-verify-stark-guest) provides functions to request and consume deferred STARK proof verification.
verify_stark_unchecked
pub fn verify_stark_unchecked<const DEF_IDX: u16>(input_commit: &Commit) -> ProofOutputInvokes a deferred call at deferral index DEF_IDX with the given input_commit (a 32-byte commitment identifying the proof). Returns a ProofOutput containing:
app_exe_commit: Commit— commitment to the app executable whose execution is being verifiedapp_vm_commit: Commit— commitment to the app VM configurationuser_public_values: Vec<u8>— public values revealed by the verified app
When correctly configured with the DeferredVerifyCircuit, verification is performed outside the VM circuit as part of the deferral flow. From the guest perspective, this establishes that there exists a proof matching input_commit that verifies and yields the returned ProofOutput; the proof itself is never accessible from the guest program.
DEF_IDX must match the index of the corresponding DeferralFn in DeferralExtension.fns, as configured when building the VM extension. Each STARK verifying key will correspond to a different deferral circuit, and thus a different DEF_IDX.
verify_stark
pub fn verify_stark<const DEF_IDX: u16>(input_commit: &Commit, expected: &ProofOutput)Calls verify_stark_unchecked and panics if the result does not match expected.
Circuit
The circuit crate (openvm-verify-stark-circuit) provides the DeferredVerifyCircuit, which is the deferral circuit that actually verifies the STARK proof during aggregation. It:
- Verifies the child STARK proof using the recursion verifier sub-circuit
- Constrains that the user public values are present in the final memory state
- Produces the output commitment containing
app_exe_commit,app_vm_commit, anduser_public_values
There should be a unique DeferredVerifyCircuit for each STARK verifying key you want to support.
Using DeferralExtension
To use DeferredVerifyCircuit in an app, you must wire it into your VM config via DeferralExtension. A VM config can support up to 1024 different deferral circuits at one time. DeferralExtension stores:
fns: Vec<Arc<DeferralFn>>- one function per deferral circuitdef_circuit_commits: Vec<[u8; 32]>- the correspondingdef_circuit_commitvalues
These vectors are aligned by index.
It is highly recommended that guest programs that use deferrals are proved using the SDK. The flow is:
- Build a
DeferralProverwhose single-circuit prover is aDeferredVerifyCircuitProver. - Create the extension with
make_extension, passing the deferral function(s). - Set
SdkVmConfig.deferral = Some(deferral_ext). - Build your SDK with
Sdk::builder().deferral_prover(deferral_prover).
For example, to initialize the SDK with the verify-stark deferral circuit:
let def_idx = 0;
let deferred_verify_prover = DeferredVerifyCpuProver::new::<E>(
ir_vk,
ir_pcs_data.commitment.into(),
def_circuit_params,
memory_dimensions,
num_user_pvs,
None,
def_idx,
);
let verify_stark_prover = DeferredVerifyCpuCircuitProver::new(deferred_verify_prover);
let deferral_prover = DeferralProver::new(verify_stark_prover, agg_config, hook_params);
let deferral_ext =
deferral_prover.make_extension(vec![Arc::new(DeferralFn::new(verify_stark_deferral_fn))]);
let mut vm_config = SdkVmConfig::riscv32();
vm_config.deferral = Some(deferral_ext);
let sdk = CpuSdk::builder()
.app_config(AppConfig::new(vm_config, app_params))
.agg_params(agg_params)
.deferral_prover(deferral_prover)
.build()?;Each guest call using DEF_IDX corresponds to the deferral circuit inserted at DEF_IDX. If there is exactly one deferral function registered (as above), DEF_IDX = 0.