miden_standards/account/interface/
component.rs1use alloc::string::{String, ToString};
2use alloc::vec::Vec;
3
4use miden_protocol::account::auth::{AuthScheme, PublicKeyCommitment};
5use miden_protocol::account::{AccountId, AccountProcedureRoot, AccountStorage, StorageSlotName};
6use miden_protocol::note::PartialNote;
7use miden_protocol::{Felt, Word};
8
9use crate::AuthMethod;
10use crate::account::auth::{
11 AuthGuardedMultisig,
12 AuthMultisig,
13 AuthMultisigSmart,
14 AuthSingleSig,
15 AuthSingleSigAcl,
16 NetworkAccountNoteAllowlist,
17 NetworkAccountTxScriptAllowlist,
18};
19use crate::account::interface::AccountInterfaceError;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum AccountComponentInterface {
27 BasicWallet,
29 FungibleFaucet,
32 Authority,
35 Ownable2Step,
38 RoleBasedAccessControl,
42 AuthSingleSig,
45 AuthSingleSigAcl,
48 AuthMultisig,
51 AuthMultisigSmart,
54 AuthGuardedMultisig,
57 AuthNoAuth,
62 AuthNetworkAccount,
69 Custom(Vec<AccountProcedureRoot>),
74}
75
76impl AccountComponentInterface {
77 pub fn name(&self) -> String {
83 match self {
84 AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
85 AccountComponentInterface::FungibleFaucet => "Fungible Faucet".to_string(),
86 AccountComponentInterface::Authority => "Authority".to_string(),
87 AccountComponentInterface::Ownable2Step => "Ownable2Step".to_string(),
88 AccountComponentInterface::RoleBasedAccessControl => {
89 "Role Based Access Control".to_string()
90 },
91 AccountComponentInterface::AuthSingleSig => "SingleSig".to_string(),
92 AccountComponentInterface::AuthSingleSigAcl => "SingleSig ACL".to_string(),
93 AccountComponentInterface::AuthMultisig => "Multisig".to_string(),
94 AccountComponentInterface::AuthMultisigSmart => "Multisig Smart".to_string(),
95 AccountComponentInterface::AuthGuardedMultisig => "Guarded Multisig".to_string(),
96 AccountComponentInterface::AuthNoAuth => "No Auth".to_string(),
97 AccountComponentInterface::AuthNetworkAccount => "Network Account Auth".to_string(),
98 AccountComponentInterface::Custom(proc_root_vec) => {
99 let result = proc_root_vec
100 .iter()
101 .map(|proc_root| proc_root.mast_root().to_hex()[..9].to_string())
102 .collect::<Vec<_>>()
103 .join(", ");
104 format!("Custom([{result}])")
105 },
106 }
107 }
108
109 pub fn is_auth_component(&self) -> bool {
113 matches!(
114 self,
115 AccountComponentInterface::AuthSingleSig
116 | AccountComponentInterface::AuthSingleSigAcl
117 | AccountComponentInterface::AuthMultisig
118 | AccountComponentInterface::AuthMultisigSmart
119 | AccountComponentInterface::AuthGuardedMultisig
120 | AccountComponentInterface::AuthNoAuth
121 | AccountComponentInterface::AuthNetworkAccount
122 )
123 }
124
125 pub fn get_auth_methods(&self, storage: &AccountStorage) -> Vec<AuthMethod> {
127 match self {
128 AccountComponentInterface::AuthSingleSig => vec![extract_singlesig_auth_method(
129 storage,
130 AuthSingleSig::public_key_slot(),
131 AuthSingleSig::scheme_id_slot(),
132 )],
133 AccountComponentInterface::AuthSingleSigAcl => vec![extract_singlesig_auth_method(
134 storage,
135 AuthSingleSigAcl::public_key_slot(),
136 AuthSingleSigAcl::scheme_id_slot(),
137 )],
138 AccountComponentInterface::AuthMultisig => {
139 vec![extract_multisig_auth_method(
140 storage,
141 AuthMultisig::threshold_config_slot(),
142 AuthMultisig::approver_public_keys_slot(),
143 AuthMultisig::approver_scheme_ids_slot(),
144 )]
145 },
146 AccountComponentInterface::AuthGuardedMultisig => {
147 vec![extract_multisig_auth_method(
148 storage,
149 AuthGuardedMultisig::threshold_config_slot(),
150 AuthGuardedMultisig::approver_public_keys_slot(),
151 AuthGuardedMultisig::approver_scheme_ids_slot(),
152 )]
153 },
154 AccountComponentInterface::AuthMultisigSmart => {
155 vec![extract_multisig_auth_method(
156 storage,
157 AuthMultisigSmart::threshold_config_slot(),
158 AuthMultisigSmart::approver_public_keys_slot(),
159 AuthMultisigSmart::approver_scheme_ids_slot(),
160 )]
161 },
162 AccountComponentInterface::AuthNoAuth => vec![AuthMethod::NoAuth],
163 AccountComponentInterface::AuthNetworkAccount => {
164 vec![extract_network_account_auth_method(storage)]
165 },
166 _ => vec![], }
168 }
169
170 pub(crate) fn send_note_body(
210 &self,
211 sender_account_id: AccountId,
212 notes: &[PartialNote],
213 ) -> Result<String, AccountInterfaceError> {
214 let mut body = String::new();
215
216 for partial_note in notes {
217 if partial_note.metadata().sender() != sender_account_id {
218 return Err(AccountInterfaceError::InvalidSenderAccount(
219 partial_note.metadata().sender(),
220 ));
221 }
222
223 body.push_str(&format!(
224 "
225 push.{recipient}
226 push.{note_type}
227 push.{tag}
228 # => [tag, note_type, RECIPIENT, pad(16)]
229 ",
230 recipient = partial_note.recipient_digest(),
231 note_type = Felt::from(partial_note.metadata().note_type()),
232 tag = Felt::from(partial_note.metadata().tag()),
233 ));
234
235 match self {
236 AccountComponentInterface::FungibleFaucet => {
237 if partial_note.assets().num_assets() != 1 {
238 return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
239 }
240
241 let asset =
243 partial_note.assets().iter().next().expect("note should contain an asset");
244
245 if asset.faucet_id() != sender_account_id {
246 return Err(AccountInterfaceError::IssuanceFaucetMismatch(
247 asset.faucet_id(),
248 ));
249 }
250
251 body.push_str(&format!(
252 "
253 push.{ASSET_VALUE}
254 push.{ASSET_KEY}
255 # => [ASSET_KEY, ASSET_VALUE, tag, note_type, RECIPIENT, pad(16)]
256
257 call.::miden::standards::faucets::fungible::mint_and_send
258 # => [note_idx, pad(29)]
259
260 swapdw dropw dropw swapdw dropw dropw
261 # => [note_idx, pad(13)]\n
262 ",
263 ASSET_KEY = asset.to_key_word(),
264 ASSET_VALUE = asset.to_value_word(),
265 ));
266 },
267 AccountComponentInterface::BasicWallet => {
268 body.push_str(
269 "
270 exec.::miden::protocol::output_note::create
271 # => [note_idx, pad(16)]\n
272 ",
273 );
274
275 for asset in partial_note.assets().iter() {
276 body.push_str(&format!(
277 "
278 # duplicate note index
279 padw push.0 push.0 push.0 dup.7
280 # => [note_idx, pad(7), note_idx, pad(16)]
281
282 push.{ASSET_VALUE}
283 push.{ASSET_KEY}
284 # => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7), note_idx, pad(16)]
285
286 call.::miden::standards::wallets::basic::move_asset_to_note
287 # => [pad(16), note_idx, pad(16)]
288
289 dropw dropw dropw dropw
290 # => [note_idx, pad(16)]\n
291 ",
292 ASSET_KEY = asset.to_key_word(),
293 ASSET_VALUE = asset.to_value_word(),
294 ));
295 }
296 },
297 _ => {
298 return Err(AccountInterfaceError::UnsupportedInterface {
299 interface: self.clone(),
300 });
301 },
302 }
303
304 for attachment in partial_note.attachments().iter() {
305 let attachment_scheme = attachment.attachment_scheme().as_u16();
306 let attachment_commitment = attachment.content().to_commitment();
307
308 body.push_str(&format!(
309 "
310 dup
311 push.{attachment_commitment}
312 push.{attachment_scheme}
313 # => [attachment_scheme, ATTACHMENT_COMMITMENT, note_idx, note_idx, pad(16)]
314 exec.::miden::protocol::output_note::add_attachment
315 # => [note_idx, pad(16)]
316 ",
317 ));
318 }
319
320 body.push_str(
321 "
322 # drop the note idx
323 drop
324 # => [pad(16)]
325 ",
326 );
327 }
328
329 Ok(body)
330 }
331}
332
333fn extract_singlesig_auth_method(
338 storage: &AccountStorage,
339 public_key_slot: &StorageSlotName,
340 scheme_id_slot: &StorageSlotName,
341) -> AuthMethod {
342 let pub_key = PublicKeyCommitment::from(
343 storage
344 .get_item(public_key_slot)
345 .expect("invalid storage index of the public key"),
346 );
347
348 let scheme_id = storage
349 .get_item(scheme_id_slot)
350 .expect("invalid storage index of the scheme id")[0]
351 .as_canonical_u64() as u8;
352
353 let auth_scheme =
354 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
355
356 AuthMethod::SingleSig { approver: (pub_key, auth_scheme) }
357}
358
359fn extract_multisig_auth_method(
361 storage: &AccountStorage,
362 config_slot: &StorageSlotName,
363 approver_public_keys_slot: &StorageSlotName,
364 approver_scheme_ids_slot: &StorageSlotName,
365) -> AuthMethod {
366 let config = storage
369 .get_item(config_slot)
370 .expect("invalid slot name of the multisig configuration");
371
372 let threshold = config[0].as_canonical_u64() as u32;
373 let num_approvers = config[1].as_canonical_u64() as u8;
374
375 let mut approvers = Vec::new();
376
377 for key_index in 0..num_approvers {
379 let map_key = Word::from([key_index as u32, 0, 0, 0]);
381
382 let pub_key_word =
383 storage.get_map_item(approver_public_keys_slot, map_key).unwrap_or_else(|_| {
384 panic!(
385 "Failed to read public key {} from multisig configuration at storage slot {}. \
386 Expected key pattern [index, 0, 0, 0].",
387 key_index, approver_public_keys_slot
388 )
389 });
390
391 let pub_key = PublicKeyCommitment::from(pub_key_word);
392
393 let scheme_word = storage
394 .get_map_item(approver_scheme_ids_slot, map_key)
395 .unwrap_or_else(|_| {
396 panic!(
397 "Failed to read scheme id for approver {} from multisig configuration at storage slot {}. \
398 Expected key pattern [index, 0, 0, 0].",
399 key_index, approver_scheme_ids_slot
400 )
401 });
402
403 let scheme_id = scheme_word[0].as_canonical_u64() as u8;
404 let auth_scheme =
405 AuthScheme::try_from(scheme_id).expect("invalid auth scheme id in the scheme id slot");
406 approvers.push((pub_key, auth_scheme));
407 }
408
409 AuthMethod::Multisig { threshold, approvers }
410}
411
412fn extract_network_account_auth_method(storage: &AccountStorage) -> AuthMethod {
414 let allowlist = NetworkAccountNoteAllowlist::try_from(storage)
415 .expect("network account note allowlist slot should be present and valid");
416 let tx_script_allowlist = NetworkAccountTxScriptAllowlist::try_from(storage)
417 .expect("network account tx script allowlist slot should be present and valid");
418
419 AuthMethod::NetworkAccount {
420 allowed_script_roots: allowlist.into_allowed_script_roots(),
421 allowed_tx_script_roots: tx_script_allowlist.into_allowed_script_roots(),
422 }
423}