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, MAX_DEFERRED_ELEMENTS, Node,
5    NodeType, PrecompileError, 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. [`DeferredStateWire`] is the retained
13/// low-level transport representation, and [`Self::from_wire`] rebuilds this state only after
14/// registry checks, canonical wire checks, and root evaluation.
15#[derive(Debug, Clone)]
16pub struct DeferredState {
17    registry: Arc<PrecompileRegistry>,
18    nodes: BTreeMap<Digest, Node>,
19    pub(super) root: Digest,
20    evals: BTreeMap<Digest, Digest>,
21    remaining_elements: usize,
22}
23
24impl Default for DeferredState {
25    fn default() -> Self {
26        Self::new(Arc::new(PrecompileRegistry::new()))
27            .expect("empty registry initialization cannot fail")
28    }
29}
30
31impl DeferredState {
32    pub fn new(registry: Arc<PrecompileRegistry>) -> Result<Self, PrecompileError> {
33        let mut state = Self::empty(registry);
34        state.initialize_precompile_nodes()?;
35        Ok(state)
36    }
37
38    /// Creates a state seeded only with framework basics.
39    fn empty(registry: Arc<PrecompileRegistry>) -> Self {
40        let mut nodes = BTreeMap::new();
41        nodes.insert(TRUE_DIGEST, Node::TRUE);
42
43        let mut evals = BTreeMap::new();
44        evals.insert(TRUE_DIGEST, TRUE_DIGEST);
45
46        Self {
47            registry,
48            nodes,
49            root: TRUE_DIGEST,
50            evals,
51            remaining_elements: MAX_DEFERRED_ELEMENTS,
52        }
53    }
54
55    /// Loads all precompile initialization nodes, then evaluates each to ensure the bootstrap set
56    /// resolves under this registry.
57    fn initialize_precompile_nodes(&mut self) -> Result<(), PrecompileError> {
58        let init_nodes = self.registry.init_nodes();
59        let init_digests: Vec<Digest> = init_nodes.iter().map(Node::digest).collect();
60
61        // Load the complete set before enforcing child closure. This lets init nodes depend on
62        // TRUE or on any other node in the complete init set, independent of registry order.
63        for node in init_nodes {
64            self.registry.validate_node(&node)?;
65            self.insert_node(node)?;
66        }
67
68        for digest in init_digests {
69            self.evaluate_digest(digest)?;
70        }
71
72        Ok(())
73    }
74
75    /// Adds precompiles to this state without discarding existing nodes, evaluation memos, root, or
76    /// budget accounting.
77    ///
78    /// Registration is additive only: duplicate precompile ids panic via
79    /// [`PrecompileRegistry::merge`], matching setup-time registry construction behavior. The
80    /// state is cloned before mutation so failed precompile initialization leaves `self`
81    /// unchanged.
82    pub fn extend_precompiles(
83        &mut self,
84        precompiles: PrecompileRegistry,
85    ) -> Result<(), PrecompileError> {
86        let mut next = self.clone();
87        Arc::make_mut(&mut next.registry).merge(precompiles);
88        next.initialize_precompile_nodes()?;
89
90        *self = next;
91        Ok(())
92    }
93
94    pub fn registry(&self) -> &PrecompileRegistry {
95        &self.registry
96    }
97
98    /// Returns the current deferred root; [`super::TRUE_DIGEST`] means no statements are logged.
99    pub fn root(&self) -> Digest {
100        self.root
101    }
102
103    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
104        self.nodes.get(digest)
105    }
106
107    /// Returns the already-memoized canonical digest for `digest`, if present.
108    ///
109    /// This is strictly read-only: it does not evaluate `digest`, validate deferred nodes, insert
110    /// canonical results, or mutate the memo table. Missing memos and dangling memos whose
111    /// canonical node is absent from this state both return `None`.
112    pub fn get_canonical_digest(&self, digest: Digest) -> Option<Digest> {
113        let canonical_digest = self.evals.get(&digest).copied()?;
114        self.nodes.contains_key(&canonical_digest).then_some(canonical_digest)
115    }
116
117    /// Returns the already-memoized canonical node for `digest`, if present.
118    ///
119    /// This is strictly read-only and returns only canonical results that are already memoized and
120    /// stored in this state.
121    pub fn get_canonical_node(&self, digest: Digest) -> Option<(Digest, &Node)> {
122        let canonical_digest = self.get_canonical_digest(digest)?;
123        self.nodes.get(&canonical_digest).map(|node| (canonical_digest, node))
124    }
125
126    /// Returns the already-memoized canonical node for `digest` or
127    /// [`PrecompileError::MissingNode`].
128    ///
129    /// This is strictly read-only and never evaluates or mutates deferred state.
130    pub fn require_canonical_node(
131        &self,
132        digest: Digest,
133    ) -> Result<(Digest, &Node), PrecompileError> {
134        self.get_canonical_node(digest).ok_or(PrecompileError::MissingNode)
135    }
136
137    pub fn nodes(&self) -> &BTreeMap<Digest, Node> {
138        &self.nodes
139    }
140
141    /// Rebuilds this state from its root-reachable DAG.
142    ///
143    /// Registered and memoized orphans are dropped, and the fixed element budget is recomputed from
144    /// the retained nodes. Precompile initialization and evaluation use the installed registry.
145    pub(crate) fn compact(self) -> Result<Self, PrecompileError> {
146        let root = self.root;
147        let mut compacted = Self::new(Arc::clone(&self.registry))?;
148        compacted.import_reachable_from(&self, root)?;
149        compacted.root = root;
150        Ok(compacted)
151    }
152
153    /// Merges `other` into this state and reduces their roots in order.
154    ///
155    /// Root-reachable nodes from `other` are re-registered under this state's registry, so shared
156    /// nodes are deduplicated without serializing either state. This state's remaining node budget
157    /// applies to imported nodes.
158    pub(crate) fn merge(mut self, other: Self) -> Result<Self, PrecompileError> {
159        let other_root = other.root();
160        self.import_reachable_from(&other, other_root)?;
161        self.log_statement(other_root)?;
162        Ok(self)
163    }
164
165    /// Returns the approximate number of field elements occupied by registered deferred nodes.
166    pub fn num_elements(&self) -> usize {
167        self.nodes
168            .iter()
169            .filter_map(|(digest, node)| {
170                (*digest != TRUE_DIGEST).then_some(node.storage_felt_len())
171            })
172            .sum()
173    }
174
175    pub fn remaining_elements(&self) -> usize {
176        self.remaining_elements
177    }
178
179    /// Recognizes `tag` under the installed registry and returns its declared outer payload shape.
180    ///
181    /// This does not inspect a payload, validate structural child references, or evaluate
182    /// precompile semantics. [`Self::register`] performs those checks for a complete node.
183    pub fn decode(&self, tag: Tag) -> Result<NodeType, PrecompileError> {
184        self.registry.decode_node_type(tag)
185    }
186
187    /// Registers a `PrecompileRegistry`-valid node in the DAG and evaluates it immediately.
188    ///
189    /// Registration validates the node shape and child references, stores the original node under
190    /// its own digest, evaluates it under the current registry, stores the canonical result node,
191    /// preserves helper nodes registered during evaluation, and records the evaluation memo from
192    /// original digest to canonical digest. The returned digest is always the original node digest.
193    /// If evaluation fails, registration returns that error immediately. Re-registering an
194    /// identical successfully registered node is idempotent and budget-free.
195    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
196        self.validate_node_for_insertion(&node)?;
197        let digest = self.insert_node(node)?;
198        self.evaluate_digest(digest)?;
199        Ok(digest)
200    }
201
202    /// Logs a statement commitment after proving the current root and statement evaluate to TRUE.
203    ///
204    /// The statement digest must already be registered (present in `nodes`), unless it is the
205    /// implicit [`TRUE_DIGEST`]. On success, this inserts the framework AND node, advances the
206    /// deferred root, memoizes the new root as TRUE, and returns the new root.
207    pub fn log_statement(&mut self, statement_digest: Digest) -> Result<Digest, PrecompileError> {
208        let prev_root = self.root;
209
210        self.require_true_eval(prev_root)?;
211        self.require_true_eval(statement_digest)?;
212
213        let and_node = Node::and(prev_root, statement_digest);
214        let new_root = and_node.digest();
215        self.insert_node(and_node)?;
216        self.root = new_root;
217        self.record_eval(new_root, Node::TRUE)?;
218        Ok(new_root)
219    }
220
221    /// Logs a statement only if its constrained transition matches `expected_new_root`.
222    ///
223    /// The VM constrains `log_deferred` as a Poseidon2 fold over the previous deferred root and
224    /// the statement digest. This helper binds the in-memory deferred DAG to that constrained
225    /// transition: it validates the expected root before mutating `self`, then applies the same
226    /// semantic checks as [`Self::log_statement`].
227    pub fn log_verified_statement(
228        &mut self,
229        statement_digest: Digest,
230        expected_new_root: Digest,
231    ) -> Result<Digest, PrecompileError> {
232        let actual_new_root = Node::and(self.root, statement_digest).digest();
233        if actual_new_root != expected_new_root {
234            return Err(DeferredError::InvalidDeferredRootTransition {
235                expected: expected_new_root,
236                actual: actual_new_root,
237            }
238            .into());
239        }
240
241        self.log_statement(statement_digest)
242    }
243
244    /// Evaluates a registered node addressed by digest and returns the canonical node digest.
245    ///
246    /// Evaluation memoization is an implementation detail: callers receive the canonical digest
247    /// whether the result was already known or computed by this call. Use [`Self::get_node`] with
248    /// the returned digest to inspect the canonical node contents.
249    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
250        let node = self.nodes.get(&digest).ok_or(PrecompileError::MissingNode)?.clone();
251        if let Some(canonical_digest) = self.evals.get(&digest) {
252            if self.nodes.contains_key(canonical_digest) {
253                return Ok(*canonical_digest);
254            }
255            return Err(PrecompileError::MissingNode);
256        }
257
258        self.validate_node_for_insertion(&node)?;
259        let canonical = if node.tag() == Tag::TRUE {
260            Node::TRUE
261        } else if node.tag() == Tag::AND {
262            let (lhs, rhs) = node.payload().as_join()?;
263            for child in [lhs, rhs] {
264                self.require_true_eval(child)?;
265            }
266            Node::TRUE
267        } else if node.tag() == Tag::CHUNKS {
268            node
269        } else {
270            let registry = Arc::clone(&self.registry);
271            let mut context = DeferredContext::new(self);
272            registry.evaluate(&node, &mut context)?
273        };
274
275        self.record_eval(digest, canonical)?;
276        self.evals.get(&digest).copied().ok_or(PrecompileError::MissingNode)
277    }
278
279    /// Serializes the root-reachable DAG into compact canonical wire form.
280    ///
281    /// Only nodes reachable from `root` are emitted; registered or memoized orphans are dropped.
282    /// The installed `PrecompileRegistry` determines each node's shape, so graph edges are never
283    /// inferred from opaque payload bytes. Encoding preserves the state representation and does not
284    /// establish its validity; failures to materialize canonical wire are returned to the caller.
285    pub fn to_wire(&self) -> Result<DeferredStateWire, IntegrityError> {
286        DeferredStateWire::from_state(self)
287    }
288
289    /// Rebuilds and verifies a deferred state from untrusted wire data.
290    ///
291    /// The wire root is implicit: empty wire opens [`TRUE_DIGEST`], otherwise the root is the
292    /// digest of the final entry. Rehydration rejects non-canonical or dangling wire, then
293    /// evaluates the implicit root to TRUE under the installed precompiles. The wire remains a
294    /// passive transport representation; this supported low-level operation is the explicit seam
295    /// that establishes semantic validity under a caller-selected registry.
296    pub fn from_wire(
297        registry: Arc<PrecompileRegistry>,
298        wire: &DeferredStateWire,
299    ) -> Result<Self, IntegrityError> {
300        wire.rehydrate(registry)
301    }
302
303    fn import_reachable_from(
304        &mut self,
305        source: &DeferredState,
306        root: Digest,
307    ) -> Result<(), PrecompileError> {
308        let mut pending = alloc::vec![(root, false)];
309        while let Some((digest, children_imported)) = pending.pop() {
310            if digest == TRUE_DIGEST {
311                continue;
312            }
313
314            let node = source.nodes.get(&digest).ok_or(PrecompileError::MissingNode)?;
315            if let Some(existing) = self.nodes.get(&digest) {
316                if existing != node {
317                    return Err(DeferredError::ConflictingNode.into());
318                }
319                continue;
320            }
321
322            if children_imported {
323                self.register(node.clone())?;
324            } else {
325                pending.push((digest, true));
326                pending.extend(node.children().map(|child| (child, false)));
327            }
328        }
329        Ok(())
330    }
331
332    fn validate_node_for_insertion(&self, node: &Node) -> Result<NodeType, PrecompileError> {
333        let node_type = self.registry.validate_node(node)?;
334        for child in node.children() {
335            if child != TRUE_DIGEST && !self.nodes.contains_key(&child) {
336                return Err(PrecompileError::MissingNode);
337            }
338        }
339        Ok(node_type)
340    }
341
342    fn insert_node(&mut self, node: Node) -> Result<Digest, PrecompileError> {
343        let digest = node.digest();
344        match self.nodes.get(&digest) {
345            Some(existing) if existing == &node => Ok(digest),
346            Some(_) => Err(DeferredError::ConflictingNode.into()),
347            None => {
348                let required = node.storage_felt_len();
349                self.remaining_elements = self.remaining_elements.checked_sub(required).ok_or(
350                    DeferredError::DeferredStateTooLarge {
351                        num_elements: required,
352                        max: self.remaining_elements,
353                    },
354                )?;
355                self.nodes.insert(digest, node);
356                Ok(digest)
357            },
358        }
359    }
360
361    /// Records an evaluation memo and stores its canonical node in `nodes` for downstream
362    /// references.
363    fn record_eval(
364        &mut self,
365        input_digest: Digest,
366        canonical: Node,
367    ) -> Result<(), PrecompileError> {
368        if !self.nodes.contains_key(&input_digest) {
369            return Err(PrecompileError::MissingNode);
370        }
371        self.validate_node_for_insertion(&canonical)?;
372        let canonical_digest = self.insert_node(canonical)?;
373        match self.evals.get(&input_digest) {
374            Some(existing) if *existing == canonical_digest => Ok(()),
375            Some(_) => Err(DeferredError::ConflictingNode.into()),
376            None => {
377                self.evals.insert(input_digest, canonical_digest);
378                Ok(())
379            },
380        }
381    }
382
383    fn require_true_eval(&mut self, digest: Digest) -> Result<(), PrecompileError> {
384        if self.evaluate_digest(digest)? != TRUE_DIGEST {
385            return Err(PrecompileError::AssertionFailed);
386        }
387        Ok(())
388    }
389}
390
391// DEFERRED CONTEXT
392// ================================================================================================
393
394/// Capability object passed to precompiles during recursive evaluation.
395///
396/// Precompiles do not own the DAG; they receive this handle to evaluate registered children and to
397/// register helper nodes referenced by compound canonicals. The verifier reuses the same path
398/// during [`DeferredState::from_wire`], so prover and verifier agree on how witnesses are
399/// reconstructed.
400pub struct DeferredContext<'a> {
401    state: &'a mut DeferredState,
402}
403
404impl<'a> DeferredContext<'a> {
405    /// Binds state for one framework-driven evaluation.
406    pub(crate) fn new(state: &'a mut DeferredState) -> Self {
407        Self { state }
408    }
409
410    /// Returns the registered node addressed by `digest`, if present.
411    ///
412    /// This is a syntactic DAG lookup: it does not evaluate the node or canonicalize it.
413    pub fn get_node(&self, digest: &Digest) -> Option<&Node> {
414        self.state.get_node(digest)
415    }
416
417    /// Evaluates a registered child digest and returns the canonical node digest.
418    ///
419    /// The `nodes` membership check keeps local evaluation reproducible by `to_wire` and
420    /// rehydration; memoization is transparent to precompile implementations. Use
421    /// [`Self::get_node`] with the returned digest to inspect the canonical node contents.
422    pub fn evaluate_digest(&mut self, digest: Digest) -> Result<Digest, PrecompileError> {
423        self.state.evaluate_digest(digest)
424    }
425
426    /// Evaluates two registered child digests to their canonical node digests.
427    pub fn evaluate_digest_pair(
428        &mut self,
429        lhs: Digest,
430        rhs: Digest,
431    ) -> Result<(Digest, Digest), PrecompileError> {
432        Ok((self.evaluate_digest(lhs)?, self.evaluate_digest(rhs)?))
433    }
434
435    /// Evaluates two child digests and requires their canonical nodes to be equal.
436    pub fn ensure_equal(&mut self, lhs: Digest, rhs: Digest) -> Result<(), PrecompileError> {
437        let (lhs, rhs) = self.evaluate_digest_pair(lhs, rhs)?;
438        if lhs != rhs {
439            return Err(PrecompileError::AssertionFailed);
440        }
441        Ok(())
442    }
443
444    /// Registers a freshly minted helper node and returns its original digest.
445    ///
446    /// Use this when a compound canonical needs stable child commitments that were created during
447    /// evaluation. Helper registration follows the same eager semantics as ordinary registration.
448    pub fn register(&mut self, node: Node) -> Result<Digest, PrecompileError> {
449        self.state.register(node)
450    }
451}
452
453#[cfg(test)]
454mod tests {
455    use super::*;
456    use crate::{
457        Felt, ZERO,
458        deferred::{Payload, Precompile, precompile_id},
459    };
460
461    #[derive(Debug, Clone, Copy)]
462    struct RejectingPrecompile;
463
464    impl Precompile for RejectingPrecompile {
465        fn name(&self) -> &'static str {
466            "rejecting-registration-fixture"
467        }
468
469        fn id(&self) -> Felt {
470            precompile_id(self.name())
471        }
472
473        fn decode(&self, args: [Felt; 3]) -> Option<NodeType> {
474            (args == [ZERO; 3]).then_some(NodeType::Data)
475        }
476
477        fn evaluate(
478            &self,
479            _args: [Felt; 3],
480            _payload: &Payload,
481            _context: &mut DeferredContext<'_>,
482        ) -> Result<Node, PrecompileError> {
483            Err(PrecompileError::AssertionFailed)
484        }
485    }
486
487    #[test]
488    fn construction_uses_the_fixed_deferred_element_limit() {
489        let state = DeferredState::new(Arc::new(PrecompileRegistry::new())).unwrap();
490        let default_state = DeferredState::default();
491
492        assert_eq!(state.num_elements(), 0);
493        assert_eq!(state.remaining_elements(), MAX_DEFERRED_ELEMENTS);
494        assert_eq!(default_state.remaining_elements(), MAX_DEFERRED_ELEMENTS);
495    }
496
497    #[test]
498    fn register_eagerly_propagates_precompile_evaluation_errors() {
499        let precompile = RejectingPrecompile;
500        let tag =
501            Tag::precompile(precompile.id(), [ZERO; 3]).expect("fixture id is precompile-owned");
502        let registry = Arc::new(PrecompileRegistry::new().with_precompile(precompile));
503        let mut state = DeferredState::new(registry).unwrap();
504        let node = Node::value(tag, [ZERO; 8]).unwrap();
505        let digest = node.digest();
506
507        let error = state.register(node).unwrap_err();
508
509        assert!(matches!(error.root(), PrecompileError::AssertionFailed));
510        assert_eq!(state.get_canonical_digest(digest), None);
511    }
512
513    fn framework_state(statement_depth: usize) -> DeferredState {
514        let mut state = DeferredState::default();
515        let mut statement = TRUE_DIGEST;
516        for _ in 0..statement_depth {
517            statement = state.register(Node::and(statement, TRUE_DIGEST)).unwrap();
518        }
519        state.log_statement(statement).unwrap();
520        state
521    }
522
523    #[test]
524    fn merge_reduces_roots_in_order_and_deduplicates_nodes() {
525        let first = framework_state(1);
526        let second = framework_state(2);
527        let first_root = first.root();
528        let second_root = second.root();
529        let total_nodes = first.nodes().len() + second.nodes().len();
530
531        let merged = first.merge(second).unwrap();
532
533        assert_eq!(merged.root(), Node::and(first_root, second_root).digest());
534        assert!(merged.nodes().len() < total_nodes);
535    }
536
537    #[test]
538    fn merge_preserves_order_and_duplicate_multiplicity() {
539        let first = framework_state(1);
540        let second = framework_state(2);
541        let first_root = first.root();
542        let second_root = second.root();
543
544        let ordered = first.clone().merge(second.clone()).unwrap();
545        let reordered = second.merge(first.clone()).unwrap();
546        let duplicate = first.clone().merge(first).unwrap();
547
548        assert_eq!(ordered.root(), Node::and(first_root, second_root).digest());
549        assert_eq!(reordered.root(), Node::and(second_root, first_root).digest());
550        assert_eq!(duplicate.root(), Node::and(first_root, first_root).digest());
551        assert_ne!(ordered.root(), reordered.root());
552        assert_ne!(duplicate.root(), first_root);
553    }
554
555    #[test]
556    fn merge_enforces_the_combined_element_limit() {
557        let mut first = framework_state(1);
558        let second = framework_state(2);
559        first.remaining_elements = 0;
560
561        let error = first.merge(second).unwrap_err();
562
563        assert!(matches!(
564            error.root(),
565            PrecompileError::Other(DeferredError::DeferredStateTooLarge { .. })
566        ));
567    }
568
569    #[test]
570    fn merge_combines_exact_roots_without_filtering_true() {
571        let settled = DeferredState::default();
572        let unsettled = framework_state(1);
573        let unsettled_root = unsettled.root();
574
575        let merged = settled.merge(unsettled).unwrap();
576
577        assert_eq!(merged.root(), Node::and(TRUE_DIGEST, unsettled_root).digest());
578    }
579}