miden_core/deferred/precompile.rs
1//! Trait and id scheme for precompiles in the deferred framework.
2//!
3//! A [`Precompile`] owns a stable slice of tag space and supplies the semantics the framework
4//! cannot know: which tags are valid, what their bodies mean, and how nodes evaluate to canonical
5//! form. The framework owns only id derivation and routing.
6
7use alloc::{format, vec::Vec};
8
9use super::{DeferredContext, Node, NodeType, Payload, PrecompileError};
10use crate::{Felt, utils::hash_string_to_word};
11
12// PRECOMPILE TRAIT
13// ================================================================================================
14
15/// Semantic module installed in a [`PrecompileRegistry`](crate::deferred::PrecompileRegistry).
16///
17/// Each precompile owns one stable id and interprets that id's three local tag felts.
18pub trait Precompile: Send + Sync {
19 /// Stable name hashed into the precompile id; renaming changes every tag this precompile owns.
20 fn name(&self) -> &'static str;
21
22 /// Stable tag id for this precompile.
23 ///
24 /// The registry validates this against [`precompile_id`] and rejects framework-reserved ids,
25 /// turning id drift into a setup-time failure.
26 fn id(&self) -> Felt;
27
28 /// Canonical constants this precompile wants registered before execution.
29 ///
30 /// State initialization loads every installed precompile's init nodes into one bootstrap set,
31 /// then evaluates each init node to ensure the set resolves under the installed registry. The
32 /// default contributes no constants.
33 fn init(&self) -> Vec<Node> {
34 Vec::new()
35 }
36
37 /// Declares the body shape for recognized local tag arguments.
38 ///
39 /// Returning `None` rejects the tag. The registry has already matched the precompile id, so
40 /// this only interprets the tag's local arguments.
41 fn decode(&self, args: [Felt; 3]) -> Option<NodeType>;
42
43 /// Evaluates one owned node to its canonical form.
44 ///
45 /// The registry has already matched the tag id; implementors receive only local `args` and a
46 /// payload whose outer shape passed [`Self::decode`]. Use [`DeferredContext`] to evaluate
47 /// registered child digests (digests present in the state's node store) or to register helper
48 /// nodes referenced by a compound canonical.
49 ///
50 /// Common conventions:
51 /// - canonical values return themselves after validating payload contents;
52 /// - producing ops evaluate structural children and return the resulting canonical node;
53 /// - predicates return [`Node::TRUE`] on success and [`PrecompileError::AssertionFailed`] on
54 /// mismatch;
55 /// - multi-chunk data nodes usually evaluate to a single-chunk value.
56 fn evaluate(
57 &self,
58 args: [Felt; 3],
59 payload: &Payload,
60 context: &mut DeferredContext<'_>,
61 ) -> Result<Node, PrecompileError>;
62}
63
64// PRECOMPILE ID DERIVATION
65// ================================================================================================
66
67/// Keeps precompile ids in a namespace separate from event ids even when names overlap.
68const PRECOMPILE_ID_DOMSEP: &str = "miden-deferred-precompile/v1";
69
70/// Derives the canonical id a registry expects for a precompile name.
71///
72/// The domain and length prefixes make the id stable, unambiguous, and disjoint from event ids.
73/// [`PrecompileRegistry::with_precompile`](crate::deferred::PrecompileRegistry::with_precompile)
74/// uses this to catch accidental id drift at setup time.
75pub fn precompile_id(name: &str) -> Felt {
76 let domain_separated = format!("{PRECOMPILE_ID_DOMSEP}:{}:{name}", name.len());
77 hash_string_to_word(domain_separated.as_str())[0]
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 #[test]
85 fn id_derivation_is_stable_unique_and_domain_separated() {
86 assert_eq!(precompile_id("foo"), precompile_id("foo"));
87 assert_ne!(precompile_id("foo"), precompile_id("bar"));
88
89 // Precompile ids and event ids share the hash_string_to_word helper but live in separate
90 // namespaces: the precompile path domain-separates (domsep + length prefix), so the same
91 // name must derive a different felt on each path.
92 let name = "my_precompile";
93 assert_ne!(precompile_id(name), crate::events::EventId::from_name(name).as_felt());
94 }
95}