miden_lib/account/interface/component.rs
1use alloc::collections::BTreeMap;
2use alloc::string::{String, ToString};
3use alloc::vec::Vec;
4
5use miden_objects::account::auth::PublicKeyCommitment;
6use miden_objects::account::{AccountId, AccountProcedureInfo, AccountStorage};
7use miden_objects::note::PartialNote;
8use miden_objects::{Felt, FieldElement, Word};
9
10use crate::AuthScheme;
11use crate::account::components::WellKnownComponent;
12use crate::account::interface::AccountInterfaceError;
13
14// ACCOUNT COMPONENT INTERFACE
15// ================================================================================================
16
17/// The enum holding all possible account interfaces which could be loaded to some account.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum AccountComponentInterface {
20 /// Exposes procedures from the [`BasicWallet`][crate::account::wallets::BasicWallet] module.
21 BasicWallet,
22 /// Exposes procedures from the
23 /// [`BasicFungibleFaucet`][crate::account::faucets::BasicFungibleFaucet] module.
24 ///
25 /// Internal value holds the storage slot index where faucet metadata is stored. This metadata
26 /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`.
27 BasicFungibleFaucet(u8),
28 /// Exposes procedures from the
29 /// [`NetworkFungibleFaucet`][crate::account::faucets::NetworkFungibleFaucet] module.
30 ///
31 /// Internal value holds the storage slot index where faucet metadata is stored. This metadata
32 /// slot has a format of `[max_supply, faucet_decimals, token_symbol, 0]`.
33 NetworkFungibleFaucet(u8),
34 /// Exposes procedures from the
35 /// [`AuthRpoFalcon512`][crate::account::auth::AuthRpoFalcon512] module.
36 ///
37 /// Internal value holds the storage slot index where the public key for the RpoFalcon512
38 /// authentication scheme is stored.
39 AuthRpoFalcon512(u8),
40 /// Exposes procedures from the
41 /// [`AuthRpoFalcon512Acl`][crate::account::auth::AuthRpoFalcon512Acl] module.
42 ///
43 /// Internal value holds the storage slot index where the public key for the RpoFalcon512
44 /// authentication scheme is stored.
45 AuthRpoFalcon512Acl(u8),
46 /// Exposes procedures from the multisig RpoFalcon512 authentication module.
47 ///
48 /// Internal value holds the storage slot index where the multisig configuration is stored.
49 AuthRpoFalcon512Multisig(u8),
50 /// Exposes procedures from the [`NoAuth`][crate::account::auth::NoAuth] module.
51 ///
52 /// This authentication scheme provides no cryptographic authentication and only increments
53 /// the nonce if the account state has actually changed during transaction execution.
54 AuthNoAuth,
55 /// A non-standard, custom interface which exposes the contained procedures.
56 ///
57 /// Custom interface holds procedures which are not part of some standard interface which is
58 /// used by this account. Each custom interface holds procedures with the same storage offset.
59 Custom(Vec<AccountProcedureInfo>),
60}
61
62impl AccountComponentInterface {
63 /// Returns a string line with the name of the [AccountComponentInterface] enum variant.
64 ///
65 /// In case of a [AccountComponentInterface::Custom] along with the name of the enum variant
66 /// the vector of shortened hex representations of the used procedures is returned, e.g.
67 /// `Custom([0x6d93447, 0x0bf23d8])`.
68 pub fn name(&self) -> String {
69 match self {
70 AccountComponentInterface::BasicWallet => "Basic Wallet".to_string(),
71 AccountComponentInterface::BasicFungibleFaucet(_) => {
72 "Basic Fungible Faucet".to_string()
73 },
74 AccountComponentInterface::NetworkFungibleFaucet(_) => {
75 "Network Fungible Faucet".to_string()
76 },
77 AccountComponentInterface::AuthRpoFalcon512(_) => "RPO Falcon512".to_string(),
78 AccountComponentInterface::AuthRpoFalcon512Acl(_) => "RPO Falcon512 ACL".to_string(),
79 AccountComponentInterface::AuthRpoFalcon512Multisig(_) => {
80 "RPO Falcon512 Multisig".to_string()
81 },
82 AccountComponentInterface::AuthNoAuth => "No Auth".to_string(),
83 AccountComponentInterface::Custom(proc_info_vec) => {
84 let result = proc_info_vec
85 .iter()
86 .map(|proc_info| proc_info.mast_root().to_hex()[..9].to_string())
87 .collect::<Vec<_>>()
88 .join(", ");
89 format!("Custom([{result}])")
90 },
91 }
92 }
93
94 /// Returns true if this component interface is an authentication component.
95 ///
96 /// TODO: currently this can identify only standard auth components
97 pub fn is_auth_component(&self) -> bool {
98 matches!(
99 self,
100 AccountComponentInterface::AuthRpoFalcon512(_)
101 | AccountComponentInterface::AuthRpoFalcon512Acl(_)
102 | AccountComponentInterface::AuthRpoFalcon512Multisig(_)
103 | AccountComponentInterface::AuthNoAuth
104 )
105 }
106
107 /// Returns the authentication schemes associated with this component interface.
108 pub fn get_auth_schemes(&self, storage: &AccountStorage) -> Vec<AuthScheme> {
109 match self {
110 AccountComponentInterface::AuthRpoFalcon512(storage_index)
111 | AccountComponentInterface::AuthRpoFalcon512Acl(storage_index) => {
112 vec![AuthScheme::RpoFalcon512 {
113 pub_key: PublicKeyCommitment::from(
114 storage
115 .get_item(*storage_index)
116 .expect("invalid storage index of the public key"),
117 ),
118 }]
119 },
120 AccountComponentInterface::AuthRpoFalcon512Multisig(storage_index) => {
121 vec![extract_multisig_auth_scheme(storage, *storage_index)]
122 },
123 AccountComponentInterface::AuthNoAuth => vec![AuthScheme::NoAuth],
124 _ => vec![], // Non-auth components return empty vector
125 }
126 }
127
128 /// Creates a vector of [AccountComponentInterface] instances. This vector specifies the
129 /// components which were used to create an account with the provided procedures info array.
130 pub fn from_procedures(procedures: &[AccountProcedureInfo]) -> Vec<Self> {
131 let mut component_interface_vec = Vec::new();
132
133 let mut procedures: BTreeMap<_, _> = procedures
134 .iter()
135 .map(|procedure_info| (*procedure_info.mast_root(), procedure_info))
136 .collect();
137
138 // Well known component interfaces
139 // ----------------------------------------------------------------------------------------
140
141 // Get all available well known components which could be constructed from the `procedures`
142 // map and push them to the `component_interface_vec`
143 WellKnownComponent::extract_well_known_components(
144 &mut procedures,
145 &mut component_interface_vec,
146 );
147
148 // Custom component interfaces
149 // ----------------------------------------------------------------------------------------
150
151 let mut custom_interface_procs_map = BTreeMap::<u8, Vec<AccountProcedureInfo>>::new();
152 procedures.into_iter().for_each(|(_, proc_info)| {
153 match custom_interface_procs_map.get_mut(&proc_info.storage_offset()) {
154 Some(proc_vec) => proc_vec.push(*proc_info),
155 None => {
156 custom_interface_procs_map.insert(proc_info.storage_offset(), vec![*proc_info]);
157 },
158 }
159 });
160
161 if !custom_interface_procs_map.is_empty() {
162 for proc_vec in custom_interface_procs_map.into_values() {
163 component_interface_vec.push(AccountComponentInterface::Custom(proc_vec));
164 }
165 }
166
167 component_interface_vec
168 }
169
170 /// Generates a body for the note creation of the `send_note` transaction script. The resulting
171 /// code could use different procedures for note creation, which depends on the used interface.
172 ///
173 /// The body consists of two sections:
174 /// - Pushing the note information on the stack.
175 /// - Creating a note:
176 /// - For basic fungible faucet: pushing the amount of assets and distributing them.
177 /// - For basic wallet: creating a note, pushing the assets on the stack and moving them to
178 /// the created note.
179 ///
180 /// # Examples
181 ///
182 /// Example script for the [`AccountComponentInterface::BasicWallet`] with one note:
183 ///
184 /// ```masm
185 /// push.{note_information}
186 /// call.::miden::output_note::create
187 ///
188 /// push.{note asset}
189 /// call.::miden::contracts::wallets::basic::move_asset_to_note dropw
190 /// dropw dropw dropw drop
191 /// ```
192 ///
193 /// Example script for the [`AccountComponentInterface::BasicFungibleFaucet`] with one note:
194 ///
195 /// ```masm
196 /// push.{note information}
197 ///
198 /// push.{asset amount}
199 /// call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop
200 /// ```
201 ///
202 /// # Errors:
203 /// Returns an error if:
204 /// - the interface does not support the generation of the standard `send_note` procedure.
205 /// - the sender of the note isn't the account for which the script is being built.
206 /// - the note created by the faucet doesn't contain exactly one asset.
207 /// - a faucet tries to distribute an asset with a different faucet ID.
208 pub(crate) fn send_note_body(
209 &self,
210 sender_account_id: AccountId,
211 notes: &[PartialNote],
212 ) -> Result<String, AccountInterfaceError> {
213 let mut body = String::new();
214
215 for partial_note in notes {
216 if partial_note.metadata().sender() != sender_account_id {
217 return Err(AccountInterfaceError::InvalidSenderAccount(
218 partial_note.metadata().sender(),
219 ));
220 }
221
222 body.push_str(&format!(
223 "push.{recipient}
224 push.{execution_hint}
225 push.{note_type}
226 push.{aux}
227 push.{tag}\n",
228 recipient = partial_note.recipient_digest(),
229 note_type = Felt::from(partial_note.metadata().note_type()),
230 execution_hint = Felt::from(partial_note.metadata().execution_hint()),
231 aux = partial_note.metadata().aux(),
232 tag = Felt::from(partial_note.metadata().tag()),
233 ));
234 // stack => [tag, aux, note_type, execution_hint, RECIPIENT]
235
236 match self {
237 AccountComponentInterface::BasicFungibleFaucet(_) => {
238 if partial_note.assets().num_assets() != 1 {
239 return Err(AccountInterfaceError::FaucetNoteWithoutAsset);
240 }
241
242 // SAFETY: We checked that the note contains exactly one asset
243 let asset =
244 partial_note.assets().iter().next().expect("note should contain an asset");
245
246 if asset.faucet_id_prefix() != sender_account_id.prefix() {
247 return Err(AccountInterfaceError::IssuanceFaucetMismatch(
248 asset.faucet_id_prefix(),
249 ));
250 }
251
252 body.push_str(&format!(
253 "push.{amount}
254 call.::miden::contracts::faucets::basic_fungible::distribute dropw dropw drop\n",
255 amount = asset.unwrap_fungible().amount()
256 ));
257 // stack => []
258 },
259 AccountComponentInterface::BasicWallet => {
260 body.push_str("call.::miden::output_note::create\n");
261 // stack => [note_idx]
262
263 for asset in partial_note.assets().iter() {
264 body.push_str(&format!(
265 "push.{asset}
266 call.::miden::contracts::wallets::basic::move_asset_to_note dropw\n",
267 asset = Word::from(*asset)
268 ));
269 // stack => [note_idx]
270 }
271
272 body.push_str("dropw dropw dropw drop\n");
273 // stack => []
274 },
275 _ => {
276 return Err(AccountInterfaceError::UnsupportedInterface {
277 interface: self.clone(),
278 });
279 },
280 }
281 }
282
283 Ok(body)
284 }
285}
286
287// HELPER FUNCTIONS
288// ================================================================================================
289
290/// Extracts authentication scheme from a multisig component.
291fn extract_multisig_auth_scheme(storage: &AccountStorage, storage_index: u8) -> AuthScheme {
292 // Read the multisig configuration from the config slot
293 // Format: [threshold, num_approvers, 0, 0]
294 let config = storage
295 .get_item(storage_index)
296 .expect("invalid storage index of the multisig configuration");
297
298 let threshold = config[0].as_int() as u32;
299 let num_approvers = config[1].as_int() as u8;
300
301 // The multisig component has a fixed storage layout:
302 // - Slot 0: [threshold, num_approvers, 0, 0]
303 // - Slot 1: Map with public keys
304 // - Slot 2: Map with executed transactions
305 // The public keys are always stored in slot 1, regardless of storage_index
306 let pub_keys_map_slot = storage_index + 1;
307
308 let mut pub_keys = Vec::new();
309
310 // Read each public key from the map
311 for key_index in 0..num_approvers {
312 // The multisig component stores keys using pattern [index, 0, 0, 0]
313 let map_key = [Felt::new(key_index as u64), Felt::ZERO, Felt::ZERO, Felt::ZERO];
314
315 match storage.get_map_item(pub_keys_map_slot, map_key.into()) {
316 Ok(pub_key) => {
317 pub_keys.push(PublicKeyCommitment::from(pub_key));
318 },
319 Err(_) => {
320 // If we can't read a public key, panic with a clear error message
321 panic!(
322 "Failed to read public key {} from multisig configuration at storage slot {}. \
323 Expected key pattern [index, 0, 0, 0]. \
324 This indicates corrupted multisig storage or incorrect storage layout.",
325 key_index, pub_keys_map_slot
326 );
327 },
328 }
329 }
330
331 AuthScheme::RpoFalcon512Multisig { threshold, pub_keys }
332}