Skip to content

Deferral

The deferral extension enables guest programs to offload expensive computations to standalone deferral circuits that are proven separately during the aggregation phase. Rather than executing the computation inside the VM, the guest issues a deferred call that records an input commitment and receives an output commitment back. The actual computation is proven outside the VM and linked back via cryptographic accumulators.

This is particularly useful for recursive STARK verification, where verifying a proof inside the VM would be prohibitively expensive. See the Verify STARK guest library for the primary use case.

For the full technical specification, see the Deferral Framework spec.

How It Works

  1. The guest program calls deferred_compute, passing an input commitment (a 32-byte hash identifying the input). The VM records this commitment and returns an output key containing the output commitment and output length.
  2. The guest allocates a buffer, then calls get_deferred_output to have the VM write the raw output bytes into that buffer.
  3. During aggregation, each deferred computation is independently proven by its corresponding deferral circuit, and the accumulators are reconciled with the VM's final memory state.

From the guest's perspective, the deferral acts like a black-box function call: given an input commitment, it produces a verified output. The guest never sees the proof or the raw input — only the commitments and the output.

Guest Library

The guest crate openvm-deferral-guest provides the following functions:

deferred_compute

pub fn deferred_compute<const DEFERRAL_IDX: u16>(input_commit: &Commit) -> OutputKey

Issues a deferred call at compile-time deferral index DEFERRAL_IDX. Returns an OutputKey containing the output commitment and length.

get_deferred_output

pub fn get_deferred_output<const DEFERRAL_IDX: u16>(output: &mut [u8], output_key: &OutputKey)

Retrieves the raw output bytes for a previous deferred call and authenticates the full output key. The caller must allocate a buffer of exactly output_key.output_len bytes.

Types

  • Commit = [u8; 32] — a 32-byte commitment used as input/output identifier
  • OutputKey { output_commit: Commit, output_len: u64 } — returned by deferred_compute, passed to get_deferred_output

DEFERRAL_IDX is a compile-time constant that identifies which deferral circuit to invoke. A VM configuration can support up to MAX_DEF_CIRCUITS = 512 deferral circuits. Each index corresponds to a deferral circuit entry in the VM's DeferralConfig, which is converted into the runtime DeferralExtension.

Host Configuration

The deferral extension is composed of zero or more deferral circuits, indexed by DEFERRAL_IDX. For each deferral circuit, the extension holds:

  • a DeferralFn — host-side function that computes the output from the input, used during proving to produce the deferred computation's result.
  • a def_circuit_commit — a 32-byte commitment to the deferral circuit, which binds a deferral index to the specific circuit allowed to prove it. This is the commit value serialized into openvm.toml.

The deferral extension requires programmatic setup through the SDK because each deferral circuit (and its DeferralFn) needs to be initialized on the host side. Once that setup has produced a DeferralConfig, supported deferral circuits such as verify-stark can be serialized into openvm.toml.

The default SDK memory config reserves cells for the deferral address space. SdkVmConfig::optimize() removes that address space when no deferrals are configured, and keeps it when deferral is present.

To enable deferrals in your VM configuration:

  1. Build a MultiDeferralCircuitProver with the appropriate single-circuit prover for your use case.
  2. Create the deferral config with MultiDeferralCircuitProver::make_config, passing the supported deferral type(s).
  3. Build SdkVmConfig with .deferral(deferral_config) before calling .optimize().
  4. Build the SDK with Sdk::builder().multi_deferral_circuit_prover(multi_deferral_circuit_prover).

See the Verify STARK guest library for a complete example of wiring up the deferral extension with the STARK verification circuit.

Inputs and Proving

The guest program only passes an input commitment to deferred_compute; the raw input never enters VM memory, and the deferred operation itself is not constrained in the VM. So the host must supply the deferral data for two phases: execution and proving.

DeferralState (for execution)

To resolve an input commitment to its output during execution, the host sets StdIn.deferrals, a Vec<DeferralState> with one entry per deferral circuit, indexed by DEFERRAL_IDX:

stdin.deferrals = vec![deferral_state /* for DEFERRAL_IDX 0 */, /* ... */];

DeferralInput (for proving)

The host must also give the prover the actual input data, so each deferral circuit can prove its computation.

pub struct DeferralInput {
    pub byte_vec: Vec<Vec<u8>>,
}

Each inner Vec<u8> is the encoded input for one deferred call to that circuit, in call order; there must be exactly as many inputs as deferred calls the guest made to it. Pass a slice of DeferralInput to Sdk::prove (or Sdk::prove_evm), one per deferral circuit, indexed by DEFERRAL_IDX:

let (proof, _) = sdk.prove(exe, stdin, &def_inputs)?;

Both DeferralState and DeferralInput must be consistent with the input commitments the guest passed to deferred_compute; otherwise execution/proving will fail, or the generated proof will not verify.