Deferred state semantics and API contract
DeferredState is the host-side witness for deferred DAG verification and the deferred root
commitment. The in-memory state is not serialized directly; ExecutionProof carries
DeferredStateWire, and DeferredState::from_wire rebuilds a trusted state by decoding canonical
wire and evaluating the wire's implicit root under the installed PrecompileRegistry.
The simplified state model is:
pub struct DeferredState {
registry: Arc<PrecompileRegistry>,
nodes: BTreeMap<Digest, Node>,
root: Digest,
remaining_elements: usize,
// evaluation results may be memoized internally, but this is not part of the public contract
}
Vocabulary
- Registered means a digest has an entry in
DeferredState.nodes. Registration can happen throughDeferredState::register, evaluation storing canonical/helper nodes,log_statementstoring frameworkANDnodes, or wire rehydration rebuilding entries. - Evaluated means a registered input digest has been semantically reduced to a canonical node
under the installed
PrecompileRegistry. The canonical node is also stored innodesso it can be referenced by downstream nodes. - Logged or root-reachable means a registered digest contributes to
DeferredState.root. Only the root-reachable closure is serialized byto_wire; registered/evaluated orphans are dropped.
Registered nodes
nodes is the durable node store.
TRUE_DIGESTis always present and maps toNode::TRUE.Node::TRUEcosts no budget.- Every non-TRUE node is keyed by
node.digest(). - Structural nodes may reference only children already present in
nodes, except for the implicitTRUE_DIGEST:Joinhas two child digests.PairListhas one or more pairs of child digests.
- Re-registering identical content is idempotent and free.
- Reusing an existing digest for different content is rejected as a conflicting node.
Registration stores and shape-checks a node in nodes, evaluates it immediately, and stores the
canonical result. False predicates and other semantic evaluation failures are reported by
registration.
One remaining budget
DeferredState::new(registry, max_elements) initializes one total budget:
remaining_elements = max_elements
Initialization also installs the registry's init() constants, charging them against that same
budget. extend_precompiles(precompiles) merges additional precompiles into an existing state
without discarding existing nodes, evaluation results, root, or budget accounting.
Every new unique durable node inserted into nodes decrements remaining_elements by the node's
field-element footprint using checked subtraction. Duplicate insertion is free, so registering the
same data node at the exact budget limit succeeds. Evaluation results do not have a separate budget
and do not double-count canonical payloads; only canonical/helper nodes newly inserted into nodes
are charged.
The precompile's decode result is the framework shape gate:
NodeType::Dataauthorizes a non-empty data payload. For memory-backed registration, the host reads exactly the stack-suppliedn_chunks; precompile evaluation checks any tag-derived semantic data length.NodeType::Joinauthorizes exactly one 8-felt payload block, interpreted as two child digests.NodeType::PairListauthorizes a non-empty list oflhs_digest || rhs_digestchunks. Precompile evaluation checks any tag-derived semantic pair count.
Processor handlers perform a cheap deferred-budget pre-check before allocating or reading a memory-backed payload, but exact data/pair-list arity remains precompile-specific semantics.
If insertion exhausts the remaining budget, execution aborts with a budget error. The insertion path owns this accounting; processor deferred handlers do not perform post-mutation deferred budget checks.
Evaluation
Evaluation first requires the input digest to be present in nodes; evaluation state alone never
creates durable DAG membership. A call to evaluate_digest(digest) returns the digest of the
canonical node. This is a semantic operation: it may compute the result or use internal
memoization, but callers do not observe that distinction. Callers that need canonical node contents
can compose evaluate_digest with get_node.
Framework nodes evaluate as follows:
Node::TRUE => Node::TRUE
Node::AND(lhs, rhs) =>
require evaluate_digest(lhs) == TRUE_DIGEST
require evaluate_digest(rhs) == TRUE_DIGEST
Node::TRUE
Precompile-owned nodes are evaluated by PrecompileRegistry::evaluate, which dispatches to the
owning Precompile with a DeferredContext.
DeferredContext gives precompile implementations the same semantic split:
get_node(digest)queries the registered/original node by digest without evaluating it.evaluate_digest(digest)evaluates a registered child digest to its canonical digest.evaluate_digest_pair(lhs, rhs)evaluates two registered child digests to canonical digests.ensure_equal(lhs, rhs)evaluates two children and requires their canonical digests to match.register(node)inserts a freshly minted helper node and returns its original digest.
Root and wire
root starts at TRUE_DIGEST. log_statement(stmt_digest) evaluates the current root and
statement, requires both to evaluate to Node::TRUE, then appends one framework AND node:
next_root = digest(Node::and(previous_root, stmt_digest))
to_wire serializes only the root-reachable closure in canonical child-first order:
- data entries carry literal data chunks;
- join entries emit two child indices;
- pair-list entries emit pairs of child indices.
The wire root is implicit: empty wire opens TRUE_DIGEST, otherwise the root is the digest of the
final entry. from_wire(registry, wire, max_elements) decodes untrusted wire, rejects non-canonical
or dangling wire by requiring state.to_wire() == wire, then evaluates the implicit wire root to
Node::TRUE. Evaluation may insert canonical/helper nodes in addition to the wire nodes. Proof
verification compares the returned state.root() to the externally committed deferred root.
Low-level framework API
The preferred low-level miden_core::deferred::DeferredState surface is small. These APIs are
framework APIs, not the public proof-verifier policy surface:
DeferredState::new(registry, max_elements)for a state booted with precompile constantsextend_precompiles(precompiles)for additive setupregistry()root()remaining_elements()get_node(digest)andnodes() -> &BTreeMap<Digest, Node>for registered-node inspectiondecode(tag)for structural tag decodingregister(node)for inserting concrete node contentevaluate_digest(digest)for the canonical digestlog_statement(stmt_digest)to_wire()from_wire(registry, wire, max_elements)
Callers that have a concrete node should explicitly register it; they may call
evaluate_digest on the returned digest when they need the canonical result, and then get_node if
they need canonical node contents. Raw evaluation memoization and direct root mutation are not part
of the public contract.
Scope note
Deferred state is the proof-bound precompile witness model: proofs carry DeferredStateWire,
verification rehydrates it under the built-in miden_precompiles::registry() in the public
VM/prover/verifier path, and the final deferred root is the public value checked by the STARK proof.
The lower-level DeferredState APIs remain parameterized by PrecompileRegistry for framework
construction, tests, and non-verifier wire validation. Public proof verification always uses the
built-in miden_precompiles::registry() policy.