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:
| Maximal availability — anyone reconstructs resource history without trust | |
| No external dependencies | |
| 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:
| Journal size reduced — only the call-trigger and app payloads are on-chain | |
| External call triggering is unaffected | |
resource_payload and discovery_payload are not available on chain |
|
| 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 | ||
| 2. Partial state transition | Small | honest Controller / L2 server |