Execution Proof — Data Availability Strategies

Background

The ARM execution proof is a ZK rollup-style protocol. The ExecutionProofInstance is
committed to the RISC0 journal — the canonical public record — and downstream verifiers
chain proofs by matching pre/post tree roots. The current instance structure is:

ExecutionProofInstance {
    old_commitment_tree_root: Digest,
    old_nullifier_tree_root:  Digest,
    new_commitment_root:      Digest,
    new_nullifier_tree_root:  Digest,
    tx_infos: Vec<TxInfo>,        // ResourceAppData per resource, per action, per tx
    batch_aggregation_vk: Digest,
    compliance_vk:        Digest,
}

Each resource contributes one [ResourceAppData] entry:

ResourceAppData {
    tag:      Digest,   // nullifier (consumed) or commitment (created)
    vk:       Digest,   // logic circuit verifying key
    app_data: AppData,  // four payload categories, fully published
}

[AppData] carries four semantically distinct blob categories:

AppData {
    resource_payload:     Vec<ExpirableBlob>, // usually encrypted resource data
    discovery_payload:    Vec<ExpirableBlob>, // discovery metadata
    external_payload:     Vec<ExpirableBlob>, // triggers external calls
    application_payload:  Vec<ExpirableBlob>, // app-specific payloads
}

external_payload carries the minimal information required to trigger external calls —
contract invocations, or other side effects initiated by reading the
journal directly. It must always be present in the journal; any strategy that moves it
off-chain breaks external systems that watch the journal for call triggers.


What is Public vs. Private

Data Visibility Location
Tree state transitions (4 roots) Public journal
Verifying keys (2 digests) Public journal
Resource tags (nullifier / commitment) per resource Public ResourceAppData.tag
Logic VK per resource Public ResourceAppData.vk
AppData blobs (all 4 categories) Public ResourceAppData.app_data
Action tree roots Public ActionInfo.action_tree_root
Full resource fields (quantity, nonce, label, etc.) Private witness only
Compliance instance details (delta, logic refs) Private witness only
Individual proof bytes Private host-only via #[serde(skip)]

Key observation: the four tree roots and verifying keys (aggregation and compliance) are
the minimal state transition record. Everything in tx_infos is application-layer data
whose availability is a policy choice, not a protocol necessity — except external_payload,
which must always be on-chain to serve as a call trigger surface.


Two Strategies

Strategy 1 — Full Calldata (Status Quo)

All AppData blobs are written verbatim into the journal. Every verifier has everything.

Trade-offs:

:white_check_mark: Maximal availability — anyone reconstructs resource history without trust
:white_check_mark: No external dependencies
:cross_mark: Journal size grows linearly with payload volume

Strategy 2 — Partial State Transition

Strip resource_payload and discovery_payload from the journal. external_payload and
application_payload remain in ResourceAppData: external_payload is the on-chain
trigger surface for external calls, and application_payload carries the minimal
app-specific info needed on-chain. The proof still attests to the full state transition via
tree roots; parties already hold their own resource and discovery data.

ResourceAppData becomes:

pub struct ResourceAppData {
    pub tag:              Digest,
    pub vk:               Digest,
    pub external_payload: Vec<ExpirableBlob>, // on-chain: needed to trigger external calls
    pub application_payload: Vec<ExpirableBlob>, // on-chain: app-specific minimal info
}

ActionInfo loses the per-resource wrapper for the dropped fields:

pub struct ActionInfo {
    pub action_tree_root:             Digest,
    pub consumed_resource_app_data:   Vec<ResourceAppData>,
    pub created_resource_app_data:    Vec<ResourceAppData>,
}

Guest circuit change (collect_action_logic): build ResourceAppData with
external_payload and application_payload; omit resource_payload and
discovery_payload from the output path entirely.

What is lost: resource_payload and discovery_payload are no longer available from
the journal. Indexers and protocol verifiers that need them can fetch them from the raw
transactions or from controllers / L2 servers.

Trade-offs:

:white_check_mark: Journal size reduced — only the call-trigger and app payloads are on-chain
:white_check_mark: External call triggering is unaffected
:cross_mark: resource_payload and discovery_payload are not available on chain
:cross_mark: Cannot reconstruct full resource history from the journal alone

Comparison

Strategy Journal size External DA? External calls Resource history
1. Full calldata (current) Large None :white_check_mark: :white_check_mark: Full
2. Partial state transition Small honest Controller / L2 server :white_check_mark: :cross_mark: on chain