Skip to main content

miden_multisig_client/
procedures.rs

1//! Well-known procedure roots for multisig accounts.
2//!
3//! Extracted from: `cargo run --example procedure_roots -p miden-multisig-client -- --json`
4
5use miden_protocol::Word;
6
7/// Procedure names that can be used for threshold overrides.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub enum ProcedureName {
10    UpdateSigners,
11    UpdateProcedureThreshold,
12    AuthTx,
13    UpdateGuardian,
14    VerifyGuardian,
15    SendAsset,
16    ReceiveAsset,
17}
18
19impl ProcedureName {
20    /// Get the procedure root for this procedure name.
21    ///
22    /// These roots are deterministic based on the MASM bytecode.
23    pub fn root(&self) -> Word {
24        match self {
25            ProcedureName::UpdateSigners => procedure_root_word(
26                "0x34963b067dbba634e57b416bc2f2a9a8d4ac24147f40b2900148c9ba44774274",
27            ),
28            ProcedureName::UpdateProcedureThreshold => procedure_root_word(
29                "0xec74c4b96ce593c11017ae54dec9c0ae5e0d242e8b3074eb3908d961300aed67",
30            ),
31            ProcedureName::AuthTx => procedure_root_word(
32                "0x0708020dce7b91b61116e3eb27e5d686e129a83df3c540e0a7693b4523814e72",
33            ),
34            ProcedureName::UpdateGuardian => procedure_root_word(
35                "0xeceb1f2c2d7d20312dbaf091e9a27a2b63f9fcba120948043069793a5715bc96",
36            ),
37            ProcedureName::VerifyGuardian => procedure_root_word(
38                "0xe6a8a62d37117f55a79b5345aa3d263ab16e973d486bac9a1612663dfdecf82d",
39            ),
40            ProcedureName::SendAsset => procedure_root_word(
41                "0xfb1c73d10de1954e9e8948964e3e77cf4e33759d2e012cb00eb10c50f2974eb4",
42            ),
43            ProcedureName::ReceiveAsset => procedure_root_word(
44                "0x6170fd6d682d91777b551fd866258f43cc657f1291f8f071500f4e56e9c153da",
45            ),
46        }
47    }
48
49    /// Get all available procedure names.
50    pub fn all() -> &'static [ProcedureName] {
51        &[
52            ProcedureName::UpdateSigners,
53            ProcedureName::UpdateProcedureThreshold,
54            ProcedureName::AuthTx,
55            ProcedureName::UpdateGuardian,
56            ProcedureName::VerifyGuardian,
57            ProcedureName::SendAsset,
58            ProcedureName::ReceiveAsset,
59        ]
60    }
61}
62
63/// Per-procedure threshold override.
64///
65/// Allows specifying different signature thresholds for specific procedures.
66///
67/// # Example
68///
69/// ```
70/// use miden_multisig_client::{ProcedureThreshold, ProcedureName};
71///
72/// let receive_threshold = ProcedureThreshold::new(ProcedureName::ReceiveAsset, 1);
73/// let config_threshold = ProcedureThreshold::new(ProcedureName::UpdateSigners, 3);
74/// ```
75#[derive(Debug, Clone, Copy)]
76pub struct ProcedureThreshold {
77    pub procedure: ProcedureName,
78    pub threshold: u32,
79}
80
81impl ProcedureThreshold {
82    pub fn new(procedure: ProcedureName, threshold: u32) -> Self {
83        Self {
84            procedure,
85            threshold,
86        }
87    }
88
89    pub fn procedure_root(&self) -> Word {
90        self.procedure.root()
91    }
92}
93
94impl std::fmt::Display for ProcedureName {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        match self {
97            ProcedureName::UpdateSigners => write!(f, "update_signers"),
98            ProcedureName::UpdateProcedureThreshold => write!(f, "update_procedure_threshold"),
99            ProcedureName::AuthTx => write!(f, "auth_tx"),
100            ProcedureName::UpdateGuardian => write!(f, "update_guardian"),
101            ProcedureName::VerifyGuardian => write!(f, "verify_guardian"),
102            ProcedureName::SendAsset => write!(f, "send_asset"),
103            ProcedureName::ReceiveAsset => write!(f, "receive_asset"),
104        }
105    }
106}
107
108impl std::str::FromStr for ProcedureName {
109    type Err = String;
110
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        match s {
113            "update_signers" => Ok(ProcedureName::UpdateSigners),
114            "update_procedure_threshold" => Ok(ProcedureName::UpdateProcedureThreshold),
115            "auth_tx" => Ok(ProcedureName::AuthTx),
116            "update_guardian" => Ok(ProcedureName::UpdateGuardian),
117            "verify_guardian" => Ok(ProcedureName::VerifyGuardian),
118            "send_asset" => Ok(ProcedureName::SendAsset),
119            "receive_asset" => Ok(ProcedureName::ReceiveAsset),
120            _ => Err(format!("unknown procedure name: {}", s)),
121        }
122    }
123}
124
125fn procedure_root_word(hex_str: &str) -> Word {
126    Word::parse(hex_str).expect("valid procedure root constant")
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn procedure_roots_match_compiled_account() {
135        use miden_confidential_contracts::multisig_guardian::{
136            MultisigGuardianBuilder, MultisigGuardianConfig,
137        };
138        use miden_protocol::{Felt, Word};
139
140        let commit = |s: u64| {
141            Word::from([
142                Felt::new_unchecked(s),
143                Felt::new_unchecked(s + 1),
144                Felt::new_unchecked(s + 2),
145                Felt::new_unchecked(s + 3),
146            ])
147        };
148        let config = MultisigGuardianConfig::new(1, vec![commit(1)], commit(10));
149        let account = MultisigGuardianBuilder::new(config)
150            .with_seed([42u8; 32])
151            .build()
152            .expect("build account");
153
154        let roots: Vec<Word> = account
155            .code()
156            .procedures()
157            .iter()
158            .map(|p| *p.mast_root())
159            .collect();
160
161        assert_eq!(
162            ProcedureName::UpdateSigners.root(),
163            roots[0],
164            "update_signers"
165        );
166        assert_eq!(
167            ProcedureName::UpdateProcedureThreshold.root(),
168            roots[1],
169            "update_procedure_threshold"
170        );
171        assert_eq!(
172            ProcedureName::UpdateGuardian.root(),
173            roots[2],
174            "update_guardian"
175        );
176        assert_eq!(ProcedureName::AuthTx.root(), roots[3], "auth_tx");
177        assert_eq!(
178            ProcedureName::VerifyGuardian.root(),
179            roots[4],
180            "verify_guardian"
181        );
182        assert_eq!(ProcedureName::SendAsset.root(), roots[5], "send_asset");
183        assert_eq!(
184            ProcedureName::ReceiveAsset.root(),
185            roots[6],
186            "receive_asset"
187        );
188    }
189
190    #[test]
191    fn procedure_threshold_new_creates_correctly() {
192        let threshold = ProcedureThreshold::new(ProcedureName::ReceiveAsset, 1);
193        assert_eq!(threshold.procedure, ProcedureName::ReceiveAsset);
194        assert_eq!(threshold.threshold, 1);
195    }
196
197    #[test]
198    fn procedure_threshold_procedure_root_returns_correct_root() {
199        let threshold = ProcedureThreshold::new(ProcedureName::SendAsset, 2);
200        assert_eq!(threshold.procedure_root(), ProcedureName::SendAsset.root());
201    }
202
203    #[test]
204    fn procedure_name_round_trip() {
205        for name in ProcedureName::all() {
206            let s = name.to_string();
207            let parsed: ProcedureName = s.parse().unwrap();
208            assert_eq!(*name, parsed);
209        }
210    }
211
212    #[test]
213    fn procedure_roots_are_valid() {
214        for name in ProcedureName::all() {
215            let _root = name.root();
216        }
217    }
218
219    #[test]
220    fn parse_unknown_returns_error() {
221        let result: Result<ProcedureName, _> = "unknown_proc".parse();
222        assert!(result.is_err());
223    }
224}