Skip to main content

miden_core/deferred/
state.rs

1use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
2
3use super::{
4    DeferredError, DeferredStateWire, Digest, IntegrityError, Node, NodeType, PrecompileError,
5    PrecompileRegistry, TRUE_DIGEST, Tag,
6};
7
8/// In-memory witness for deferred-DAG verification.
9///
10/// The state keeps registered nodes, host-side evaluation memos, and the current deferred root.
11/// Evaluation memos are valid only under the same [`PrecompileRegistry`] semantics used to populate
12/// them. The state is intentionally not serialized directly: partial proofs carry
13/// [`DeferredStateWire`], and [`Self::from_wire`] rebuilds this state only after registry checks,
14/// canonical wire checks, and root evaluation. Final non-empty proofs can instead carry a
15/// precompile VM STARK proof for the same deferred root.
16#[derive(Debug, Clone)]
17pub struct DeferredState {
18    registry: Arc<PrecompileRegistry>,
19    nodes: BTreeMap<Digest, Node>,
20    pub(super) root: Digest,
21    evals: BTreeMap<Digest, Digest>,
22    remaining_elements: usize,
23}
24
25impl Default for DeferredState {
26    fn default() -> Self {
27        Self::new(Arc::new(PrecompileRegistry::new()), usize::MAX)
28            .expect("empty registry initialization cannot fail")
29    }
30}
31
32impl DeferredState {
33    pub fn new(
34        registry: Arc<PrecompileRegistry>,
35        max_elements: usize,
36    ) -> Result<Self, PrecompileError> {
37        let mut state = Self::empty(registry, max_elements);
38        state.initialize_precompile_nodes()?;
39        Ok(state)
40    }
41
42    /// Creates a state seeded only with framework basics.
43    fn empty(registry: Arc<PrecompileRegistry>, max_elements: usize) -> Self {
44        let mut nodes = BTreeMap::new();
45        nodes.insert(TRUE_DIGEST, Node::TRUE);
46
47        let mut evals = BTreeMap::new();
48        evals.insert(TRUE_DIGEST, TRUE_DIGEST);
49
50        Self {
51            registry,
52            nodes,
53            root: TRUE_DIGEST,
54            evals,
55            remaining_elements: max_elements,
56        }
57    }
58
59    /// Loads all precompile initialization nodes, then evaluates each to ensure the bootstrap set
60    /// resolves under this registry.
61    fn initialize_precompile_nodes(&mut self) -> Result<(), PrecompileError> {
62        let init_nodes = self.registry.init_nodes();
63        let init_digests: Vec<Digest> = init_nodes.iter().map(Node::digest).collect();
64
65        // Load the complete set before enforcing child closure. This lets init nodes depend on
66        // TRUE or on any other node in the complete init set, independent of registry order.
67        for node in init_nodes {
68            self.registry.validate_node(&node)?;
69            self.insert_node(node)?;
70        }
71
72        for digest in init_digests {
73            self.evaluate_digest(digest)?;
74        }
75
76        Ok(())
77    }
78
79    /// Adds precompiles to this state without discarding existing nodes, evaluation memos, root, or
80    /// budget accounting.
81    ///
82    /// Registration is additive only: duplicate precompile ids panic via
83    /// [`PrecompileRegistry::merge`], matching setup-time registry construction behavior. The
84    /// state is cloned before mutation so failed precompile initialization leaves `self`
85    /// unchanged.
86    pub fn extend_precompiles(
87        &mut self,
88        precompiles: PrecompileRegistry,
89    ) -> Result<(), PrecompileError> {
90        let mut next = self.clone();
91        Arc::make_mut(&mut next.registry).merge(precompiles);
92        next.initialize_precompile_nodes()?;
93
94        *self = next;
95        Ok(())
96    }
97
98    pub fn registry(&self) -> &PrecompileRegistry {
99        &self.registry
100    }
101
102    /// Returns the current deferred root; [`super::TRUE_DIGEST`] means no statements are logged.
103    pub fn root(&self) -> Digest {
104        self.root
105    }
106
107    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
108        self.nodes.get(digest)
109    }
110
111    /// Returns the already-memoized canonical digest for `digest`, if present.
112    ///
113    /// This is strictly read-only: it does not evaluate `digest`, validate deferred nodes, insert
114    /// canonical results, or mutate the memo table. Missing memos and dangling memos whose
115    /// canonical node is absent from this state both return `None`.
116    pub fn get_canonical_digest(&self, digest: Digest) -> Option<Digest> {
117        let canonical_digest = self.evals.get(&digest).copied()?;
118        self.nodes.contains_key(&canonical_digest).then_some(canonical_digest)
119    }
120
121    /// Returns the already-memoized canonical node for `digest`, if present.
122    ///
123    /// This is strictly read-only and returns only canonical results that are already memoized and
124    /// stored in this state.
125    pub fn get_canonical_node(&self, digest: Digest) -> Option<(Digest, &Node)> {
126        let canonical_digest = self.get_canonical_digest(digest)?;
127        self.nodes.get(&canonical_digest).map(|node| (canonical_digest, node))
128    }
129
130    /// Returns the already-memoized canonical node for `digest` or
131    /// [`PrecompileError::MissingNode`].
132    ///
133    /// This is strictly read-only and never evaluates or mutates deferred state.
134    pub fn require_canonical_node(
135        &self,
136        digest: Digest,
137    ) -> Result<(Digest, &Node), PrecompileError> {
138        self.get_canonical_node(digest).ok_or(PrecompileError::MissingNode)
139    }
140
141    pub fn nodes(&self) -> &BTreeMap<Digest, Node> {
142        &self.nodes
143    }
144
145    pub fn remaining_elements(&self) -> usize {
146        self.remaining_elements
147    }
148
149    /// Updates the remaining deferred-node budget without discarding the installed registry,
150    /// registered nodes, evaluation memos, or current root.
151    ///
152    /// If the current state already exceeds the new budget, future non-idempotent node insertions
153    /// will fail because the remaining budget is set to zero. This lets callers tighten execution
154    /// options without silently dropping proof-relevant deferred state.
155    pub fn set_max_elements(&mut self, max_elements: usize) {
156        let used_elements = self
157            .nodes
158            .iter()
159            .filter_map(|(digest, node)| {
160                (*digest != TRUE_DIGEST).then_some(node.storage_felt_len())
161            })
162            .sum::<usize>();
163        self.remaining_elements = max_elements.saturating_sub(used_elements);
164    }
165
166    /// Recognizes `tag` under the installed registry and returns its declared outer payload shape.
167    ///
168    /// This does not inspect a payload, validate structural child references, or evaluate
169    /// precompile semantics. [`Self::register`] performs those checks for a complete node.
170    pub fn decode(&self, tag: Tag) -> Result<NodeType, PrecompileError> {
171        self.registry.decode_node_type(tag)
172    }
173
174    /// Registers a `PrecompileRegistry`-valid node in the DAG and evaluates it immediately.
175    ///
176    /// Registration validates the node shape and child references, stores the original node under
177    /// its own digest, evaluates it under the current registry, stores the canonical result node,
178    /// preserves helper nodes registered during evaluation, and records the evaluation memo from
179    /// original digest to canonical digest. The returned digest is always the original node digest.
180    /// If evaluation fails, registration returns that error immediately. Re-registering an
181    /// identical successfully registered node is idempotent and budget-free.
182    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
183        self.validate_node_for_insertion(&node)?;
184        let digest = self.insert_node(node)?;
185        self.evaluate_digest(digest)?;
186        Ok(digest)
187    }
188
189    /// Logs a statement commitment after proving the current root and statement evaluate to TRUE.
190    ///
191    /// The statement digest must already be registered (present in `nodes`), unless it is the
192    /// implicit [`TRUE_DIGEST`]. On success, this inserts the framework AND node, advances the
193    /// deferred root, memoizes the new root as TRUE, and returns the new root.
194    pub fn log_statement(&mut self, statement_digest: Digest) -> Result<Digest, PrecompileError> {
195        let prev_root = self.root;
196
197        self.require_true_eval(prev_root)?;
198        self.require_true_eval(statement_digest)?;
199
200        let and_node = Node::and(prev_root, statement_digest);
201        let new_root = and_node.digest();
202        self.insert_node(and_node)?;
203        self.root = new_root;
204        self.record_eval(new_root, Node::TRUE)?;
205        Ok(new_root)
206    }
207
208    /// Logs a statement only if its constrained transition matches `expected_new_root`.
209    ///
210    /// The VM constrains `log_deferred` as a Poseidon2 fold over the previous deferred root and
211    /// the statement digest. This helper binds the in-memory deferred DAG to that constrained
212    /// transition: it validates the expected root before mutating `self`, then applies the same
213    /// semantic checks as [`Self::log_statement`].
214    pub fn log_verified_statement(
215        &mut self,
216        statement_digest: Digest,
217        expected_new_root: Digest,
218    ) -> Result<Digest, PrecompileError> {
219        let actual_new_root = Node::and(self.root, statement_digest).digest();
220        if actual_new_root != expected_new_root {
221            return Err(DeferredError::InvalidDeferredRootTransition {
222                expected: expected_new_root,
223                actual: actual_new_root,
224            }
225            .into());
226        }
227
228        self.log_statement(statement_digest)
229    }
230
231    /// Evaluates a registered node addressed by digest and returns the canonical node digest.
232    ///
233    /// Evaluation memoization is an implementation detail: callers receive the canonical digest
234    /// whether the result was already known or computed by this call. Use [`Self::get_node`] with
235    /// the returned digest to inspect the canonical node contents.
236    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
237        let node = self.nodes.get(&digest).ok_or(PrecompileError::MissingNode)?.clone();
238        if let Some(canonical_digest) = self.evals.get(&digest) {
239            if self.nodes.contains_key(canonical_digest) {
240                return Ok(*canonical_digest);
241            }
242            return Err(PrecompileError::MissingNode);
243        }
244
245        self.validate_node_for_insertion(&node)?;
246        let canonical = if node.tag() == Tag::TRUE {
247            Node::TRUE
248        } else if node.tag() == Tag::AND {
249            let (lhs, rhs) = node.payload().as_join()?;
250            for child in [lhs, rhs] {
251                self.require_true_eval(child)?;
252            }
253            Node::TRUE
254        } else if node.tag() == Tag::CHUNKS {
255            node
256        } else {
257            let registry = Arc::clone(&self.registry);
258            let mut context = DeferredContext::new(self);
259            registry.evaluate(&node, &mut context)?
260        };
261
262        self.record_eval(digest, canonical)?;
263        self.evals.get(&digest).copied().ok_or(PrecompileError::MissingNode)
264    }
265
266    /// Serializes the root-reachable DAG into compact canonical wire form.
267    ///
268    /// Only nodes reachable from `root` are emitted; registered or memoized orphans are dropped.
269    /// The installed `PrecompileRegistry` determines each node's shape, so graph edges are never
270    /// inferred from opaque payload bytes.
271    pub fn to_wire(&self) -> Result<DeferredStateWire, IntegrityError> {
272        DeferredStateWire::from_state(self)
273    }
274
275    /// Rebuilds and verifies a deferred state from untrusted wire data.
276    ///
277    /// The wire root is implicit: empty wire opens [`TRUE_DIGEST`], otherwise the root is the
278    /// digest of the final entry. Rehydration rejects non-canonical or dangling wire, then
279    /// evaluates the implicit root to TRUE under the installed precompiles. This is the basis for
280    /// explicit partial verification: final verification rejects `DeferredProof::Wire`, while the
281    /// partial verifier rehydrates it and verifies the VM proof against the resulting root.
282    pub fn from_wire(
283        registry: Arc<PrecompileRegistry>,
284        wire: &DeferredStateWire,
285        max_elements: usize,
286    ) -> Result<Self, IntegrityError> {
287        wire.rehydrate(registry, max_elements)
288    }
289
290    fn validate_node_for_insertion(&self, node: &Node) -> Result<NodeType, PrecompileError> {
291        let node_type = self.registry.validate_node(node)?;
292        for child in node.children() {
293            if child != TRUE_DIGEST && !self.nodes.contains_key(&child) {
294                return Err(PrecompileError::MissingNode);
295            }
296        }
297        Ok(node_type)
298    }
299
300    fn insert_node(&mut self, node: Node) -> Result<Digest, PrecompileError> {
301        let digest = node.digest();
302        match self.nodes.get(&digest) {
303            Some(existing) if existing == &node => Ok(digest),
304            Some(_) => Err(DeferredError::ConflictingNode.into()),
305            None => {
306                let required = node.storage_felt_len();
307                self.remaining_elements = self.remaining_elements.checked_sub(required).ok_or(
308                    DeferredError::DeferredStateTooLarge {
309                        num_elements: required,
310                        max: self.remaining_elements,
311                    },
312                )?;
313                self.nodes.insert(digest, node);
314                Ok(digest)
315            },
316        }
317    }
318
319    /// Records an evaluation memo and stores its canonical node in `nodes` for downstream
320    /// references.
321    fn record_eval(
322        &mut self,
323        input_digest: Digest,
324        canonical: Node,
325    ) -> Result<(), PrecompileError> {
326        if !self.nodes.contains_key(&input_digest) {
327            return Err(PrecompileError::MissingNode);
328        }
329        self.validate_node_for_insertion(&canonical)?;
330        let canonical_digest = self.insert_node(canonical)?;
331        match self.evals.get(&input_digest) {
332            Some(existing) if *existing == canonical_digest => Ok(()),
333            Some(_) => Err(DeferredError::ConflictingNode.into()),
334            None => {
335                self.evals.insert(input_digest, canonical_digest);
336                Ok(())
337            },
338        }
339    }
340
341    fn require_true_eval(&mut self, digest: Digest) -> Result<(), PrecompileError> {
342        if self.evaluate_digest(digest)? != TRUE_DIGEST {
343            return Err(PrecompileError::AssertionFailed);
344        }
345        Ok(())
346    }
347}
348
349// DEFERRED CONTEXT
350// ================================================================================================
351
352/// Capability object passed to precompiles during recursive evaluation.
353///
354/// Precompiles do not own the DAG; they receive this handle to evaluate registered children and to
355/// register helper nodes referenced by compound canonicals. The verifier reuses the same path
356/// during [`DeferredState::from_wire`], so prover and verifier agree on how witnesses are
357/// reconstructed.
358pub struct DeferredContext<'a> {
359    state: &'a mut DeferredState,
360}
361
362impl<'a> DeferredContext<'a> {
363    /// Binds state for one framework-driven evaluation.
364    pub(crate) fn new(state: &'a mut DeferredState) -> Self {
365        Self { state }
366    }
367
368    /// Returns the registered node addressed by `digest`, if present.
369    ///
370    /// This is a syntactic DAG lookup: it does not evaluate the node or canonicalize it.
371    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
372        self.state.get_node(digest)
373    }
374
375    /// Evaluates a registered child digest and returns the canonical node digest.
376    ///
377    /// The `nodes` membership check keeps local evaluation reproducible by `to_wire` and
378    /// rehydration; memoization is transparent to precompile implementations. Use
379    /// [`Self::get_node`] with the returned digest to inspect the canonical node contents.
380    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
381        self.state.evaluate_digest(digest)
382    }
383
384    /// Evaluates two registered child digests to their canonical node digests.
385    pub fn evaluate_digest_pair(
386        &mut self,
387        lhs: Digest,
388        rhs: Digest,
389    ) -> Result<(Digest, Digest), PrecompileError> {
390        Ok((self.evaluate_digest(lhs)?, self.evaluate_digest(rhs)?))
391    }
392
393    /// Evaluates two child digests and requires their canonical nodes to be equal.
394    pub fn ensure_equal(&mut self, lhs: Digest, rhs: Digest) -> Result<(), PrecompileError> {
395        let (lhs, rhs) = self.evaluate_digest_pair(lhs, rhs)?;
396        if lhs != rhs {
397            return Err(PrecompileError::AssertionFailed);
398        }
399        Ok(())
400    }
401
402    /// Registers a freshly minted helper node and returns its original digest.
403    ///
404    /// Use this when a compound canonical needs stable child commitments that were created during
405    /// evaluation. Helper registration follows the same eager semantics as ordinary registration.
406    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
407        self.state.register(node)
408    }
409}
410
411#[cfg(test)]
412mod tests {
413    use super::*;
414    use crate::{
415        Felt, ZERO,
416        deferred::{Payload, Precompile, precompile_id},
417    };
418
419    #[derive(Debug, Clone, Copy)]
420    struct RejectingPrecompile;
421
422    impl Precompile for RejectingPrecompile {
423        fn name(&self) -> &'static str {
424            "rejecting-registration-fixture"
425        }
426
427        fn id(&self) -> Felt {
428            precompile_id(self.name())
429        }
430
431        fn decode(&self, args: [Felt; 3]) -> Option<NodeType> {
432            (args == [ZERO; 3]).then_some(NodeType::Data)
433        }
434
435        fn evaluate(
436            &self,
437            _args: [Felt; 3],
438            _payload: &Payload,
439            _context: &mut DeferredContext<'_>,
440        ) -> Result<Node, PrecompileError> {
441            Err(PrecompileError::AssertionFailed)
442        }
443    }
444
445    #[test]
446    fn register_eagerly_propagates_precompile_evaluation_errors() {
447        let precompile = RejectingPrecompile;
448        let tag =
449            Tag::precompile(precompile.id(), [ZERO; 3]).expect("fixture id is precompile-owned");
450        let registry = Arc::new(PrecompileRegistry::new().with_precompile(precompile));
451        let mut state = DeferredState::new(registry, usize::MAX).unwrap();
452        let node = Node::value(tag, [ZERO; 8]).unwrap();
453        let digest = node.digest();
454
455        let error = state.register(node).unwrap_err();
456
457        assert!(matches!(error.root(), PrecompileError::AssertionFailed));
458        assert_eq!(state.get_canonical_digest(digest), None);
459    }
460}