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