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