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