miden_lib/account/auth/
mod.rs

1use alloc::{string::ToString, vec::Vec};
2
3use miden_objects::{
4    AccountError, Digest, Felt, FieldElement,
5    account::{AccountCode, AccountComponent, StorageMap, StorageSlot},
6    crypto::dsa::rpo_falcon512::PublicKey,
7};
8
9use crate::account::components::{
10    no_auth_library, rpo_falcon_512_library, rpo_falcon_512_procedure_acl_library,
11};
12
13/// An [`AccountComponent`] implementing the RpoFalcon512 signature scheme for authentication of
14/// transactions.
15///
16/// It reexports the procedures from `miden::contracts::auth::basic`. When linking against this
17/// component, the `miden` library (i.e. [`MidenLib`](crate::MidenLib)) must be available to the
18/// assembler which is the case when using [`TransactionKernel::assembler()`][kasm]. The procedures
19/// of this component are:
20/// - `auth__tx_rpo_falcon512`, which can be used to verify a signature provided via the advice
21///   stack to authenticate a transaction.
22///
23/// This component supports all account types.
24///
25/// [kasm]: crate::transaction::TransactionKernel::assembler
26pub struct RpoFalcon512 {
27    public_key: PublicKey,
28}
29
30impl RpoFalcon512 {
31    /// Creates a new [`RpoFalcon512`] component with the given `public_key`.
32    pub fn new(public_key: PublicKey) -> Self {
33        Self { public_key }
34    }
35}
36
37impl From<RpoFalcon512> for AccountComponent {
38    fn from(falcon: RpoFalcon512) -> Self {
39        AccountComponent::new(
40            rpo_falcon_512_library(),
41            vec![StorageSlot::Value(falcon.public_key.into())],
42        )
43        .expect("falcon component should satisfy the requirements of a valid account component")
44        .with_supports_all_types()
45    }
46}
47
48/// An [`AccountComponent`] implementing a procedure-ACL based RpoFalcon512 signature scheme for
49/// authentication of transactions.
50///
51/// This component only requires authentication when any of the specified procedures are called
52/// during the transaction. It stores a list of procedure roots that require authentication, and
53/// the signature verification is only performed if at least one of these procedures was invoked.
54///
55/// It exports the procedure `auth__tx_rpo_falcon512_procedure_acl`, which:
56/// - Checks if any of the specified auth trigger procedures were called during the transaction
57/// - If none were called, authentication is skipped
58/// - If at least one was called, performs standard RpoFalcon512 signature verification
59/// - Always increments the nonce
60///
61/// The storage layout is:
62/// - Slot 0(value): Public key (same as RpoFalcon512)
63/// - Slot 1(value): Number of trigger procedures
64/// - Slot 2(map): A map with trigger procedure roots
65///
66/// This component supports all account types.
67pub struct RpoFalcon512ProcedureAcl {
68    public_key: PublicKey,
69    auth_trigger_procedures: Vec<Digest>,
70}
71
72impl RpoFalcon512ProcedureAcl {
73    /// Creates a new [`RpoFalcon512ProcedureAcl`] component with the given `public_key` and
74    /// list of procedure roots that require authentication.
75    ///
76    /// # Panics
77    /// Panics if more than [AccountCode::MAX_NUM_PROCEDURES] procedures are specified.
78    pub fn new(
79        public_key: PublicKey,
80        auth_trigger_procedures: Vec<Digest>,
81    ) -> Result<Self, AccountError> {
82        let max_procedures = AccountCode::MAX_NUM_PROCEDURES;
83        if auth_trigger_procedures.len() > max_procedures {
84            return Err(AccountError::AssumptionViolated(
85                "Cannot track more than {max_procedures} procedures (account limit)".to_string(),
86            ));
87        }
88
89        Ok(Self { public_key, auth_trigger_procedures })
90    }
91}
92
93impl From<RpoFalcon512ProcedureAcl> for AccountComponent {
94    fn from(falcon: RpoFalcon512ProcedureAcl) -> Self {
95        let mut storage_slots = Vec::with_capacity(3);
96
97        // Slot 0: Public key
98        storage_slots.push(StorageSlot::Value(falcon.public_key.into()));
99
100        // Slot 1: Number of tracked procedures
101        let num_procs = Felt::from(falcon.auth_trigger_procedures.len() as u32);
102        storage_slots.push(StorageSlot::Value([num_procs, Felt::ZERO, Felt::ZERO, Felt::ZERO]));
103
104        // Slot 2: A map with tracked procedure roots
105        // We add the map even if there are no trigger procedures, to always maintain the same
106        // storage layout.
107        let map_entries =
108            falcon.auth_trigger_procedures.iter().enumerate().map(|(i, proc_root)| {
109                (
110                    [Felt::from(i as u32), Felt::ZERO, Felt::ZERO, Felt::ZERO].into(),
111                    proc_root.into(),
112                )
113            });
114
115        // Safe to unwrap because we know that the map keys are unique.
116        storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap()));
117
118        AccountComponent::new(rpo_falcon_512_procedure_acl_library(), storage_slots)
119            .expect("Procedure ACL auth component should satisfy the requirements of a valid account component")
120            .with_supports_all_types()
121    }
122}
123
124/// An [`AccountComponent`] implementing a no-authentication scheme.
125///
126/// This component provides minimal authentication by only checking if the account
127/// state has actually changed during transaction execution. It compares the initial
128/// account commitment with the current commitment and only increments the nonce if
129/// they differ, avoiding unnecessary nonce increments for transactions that don't
130/// modify the account state.
131///
132/// It exports the procedure `auth__no_auth`, which:
133/// - Checks if the account state has changed by comparing initial and final commitments
134/// - Only increments the nonce if the account state has actually changed
135/// - Provides no cryptographic authentication
136///
137/// This component supports all account types.
138pub struct NoAuth;
139
140impl NoAuth {
141    /// Creates a new [`NoAuth`] component.
142    pub fn new() -> Self {
143        Self
144    }
145}
146
147impl Default for NoAuth {
148    fn default() -> Self {
149        Self::new()
150    }
151}
152
153impl From<NoAuth> for AccountComponent {
154    fn from(_: NoAuth) -> Self {
155        AccountComponent::new(no_auth_library(), vec![])
156            .expect("NoAuth component should satisfy the requirements of a valid account component")
157            .with_supports_all_types()
158    }
159}
160
161// TESTS
162// ================================================================================================
163
164#[cfg(test)]
165mod tests {
166    use miden_objects::{Word, ZERO, account::AccountBuilder};
167
168    use super::*;
169    use crate::account::{components::basic_wallet_library, wallets::BasicWallet};
170
171    #[test]
172    fn test_rpo_falcon_512_procedure_acl_no_procedures() {
173        let public_key = PublicKey::new([ZERO; 4]);
174        let component =
175            RpoFalcon512ProcedureAcl::new(public_key, vec![]).expect("component creation failed");
176
177        let (account, _) = AccountBuilder::new([0; 32])
178            .with_auth_component(component)
179            .with_component(BasicWallet)
180            .build()
181            .expect("account building failed");
182
183        let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
184        assert_eq!(public_key_slot, Word::from(public_key).into());
185
186        let num_procs_slot = account.storage().get_item(1).expect("storage slot 1 access failed");
187        assert_eq!(num_procs_slot, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO].into());
188
189        let proc_root = account
190            .storage()
191            .get_map_item(2, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO])
192            .expect("storage map access failed");
193        // This should be filled with zeros because there are no auth trigger procedures
194        assert_eq!(proc_root, Word::default());
195    }
196
197    #[test]
198    fn test_rpo_falcon_512_procedure_acl_with_two_procedures() {
199        let public_key = PublicKey::new([ZERO; 4]);
200
201        // Get the two trigger procedures from BasicWallet: `receive_asset`, `move_asset_to_note`.
202        // TODO refactor to fetch procedure digests by name after
203        // https://github.com/0xMiden/miden-base/pull/1532
204        let auth_trigger_procedures: Vec<Digest> = basic_wallet_library()
205            .module_infos()
206            .next()
207            .expect("at least one module expected")
208            .procedures()
209            .map(|(_, proc_info)| proc_info.digest)
210            .collect();
211
212        assert_eq!(auth_trigger_procedures.len(), 2);
213
214        let component = RpoFalcon512ProcedureAcl::new(public_key, auth_trigger_procedures.clone())
215            .expect("component creation failed");
216
217        let (account, _) = AccountBuilder::new([0; 32])
218            .with_auth_component(component)
219            .with_component(BasicWallet)
220            .build()
221            .expect("account building failed");
222
223        let public_key_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
224        assert_eq!(public_key_slot, Word::from(public_key).into());
225
226        let num_procs_slot = account.storage().get_item(1).expect("storage slot 1 access failed");
227        assert_eq!(num_procs_slot, [Felt::new(2), Felt::ZERO, Felt::ZERO, Felt::ZERO].into());
228
229        let proc_root_0 = account
230            .storage()
231            .get_map_item(2, [Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO])
232            .expect("storage map access failed");
233        assert_eq!(proc_root_0, Word::from(auth_trigger_procedures[0]));
234
235        let proc_root_1 = account
236            .storage()
237            .get_map_item(2, [Felt::ONE, Felt::ZERO, Felt::ZERO, Felt::ZERO])
238            .expect("storage map access failed");
239        assert_eq!(proc_root_1, Word::from(auth_trigger_procedures[1]));
240    }
241
242    #[test]
243    fn test_no_auth_component() {
244        // Create an account using the NoAuth component
245        let (_account, _) = AccountBuilder::new([0; 32])
246            .with_auth_component(NoAuth)
247            .with_component(BasicWallet)
248            .build()
249            .expect("account building failed");
250    }
251}