Skip to content

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) -> ProofOutput

Invokes 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 verified
  • app_vm_commit: Commit — commitment to the app VM configuration
  • user_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:

  1. Verifies the child STARK proof using the recursion verifier sub-circuit
  2. Constrains that the user public values are present in the final memory state
  3. Produces the output commitment containing app_exe_commit, app_vm_commit, and user_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 circuit
  • def_circuit_commits: Vec<[u8; 32]> - the corresponding def_circuit_commit values

These vectors are aligned by index.

It is highly recommended that guest programs that use deferrals are proved using the SDK. The flow is:

  1. Build a DeferralProver whose single-circuit prover is a DeferredVerifyCircuitProver.
  2. Create the extension with make_extension, passing the deferral function(s).
  3. Set SdkVmConfig.deferral = Some(deferral_ext).
  4. 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.