1use alloc::collections::BTreeSet;
2use alloc::vec::Vec;
3
4use miden_objects::account::auth::PublicKeyCommitment;
5use miden_objects::account::{AccountComponent, StorageMap, StorageSlot};
6use miden_objects::{AccountError, Word};
7
8use crate::account::components::ecdsa_k256_keccak_multisig_library;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct AuthEcdsaK256KeccakMultisigConfig {
16 approvers: Vec<PublicKeyCommitment>,
17 default_threshold: u32,
18 proc_thresholds: Vec<(Word, u32)>,
19}
20
21impl AuthEcdsaK256KeccakMultisigConfig {
22 pub fn new(
26 approvers: Vec<PublicKeyCommitment>,
27 default_threshold: u32,
28 ) -> Result<Self, AccountError> {
29 if default_threshold == 0 {
30 return Err(AccountError::other("threshold must be at least 1"));
31 }
32 if default_threshold > approvers.len() as u32 {
33 return Err(AccountError::other(
34 "threshold cannot be greater than number of approvers",
35 ));
36 }
37
38 if approvers.len() != approvers.iter().collect::<BTreeSet<_>>().len() {
40 return Err(AccountError::other("duplicate approver public keys are not allowed"));
41 }
42
43 Ok(Self {
44 approvers,
45 default_threshold,
46 proc_thresholds: vec![],
47 })
48 }
49
50 pub fn with_proc_thresholds(
53 mut self,
54 proc_thresholds: Vec<(Word, u32)>,
55 ) -> Result<Self, AccountError> {
56 for (_, threshold) in &proc_thresholds {
57 if *threshold == 0 {
58 return Err(AccountError::other("procedure threshold must be at least 1"));
59 }
60 if *threshold > self.approvers.len() as u32 {
61 return Err(AccountError::other(
62 "procedure threshold cannot be greater than number of approvers",
63 ));
64 }
65 }
66 self.proc_thresholds = proc_thresholds;
67 Ok(self)
68 }
69
70 pub fn approvers(&self) -> &[PublicKeyCommitment] {
71 &self.approvers
72 }
73
74 pub fn default_threshold(&self) -> u32 {
75 self.default_threshold
76 }
77
78 pub fn proc_thresholds(&self) -> &[(Word, u32)] {
79 &self.proc_thresholds
80 }
81}
82
83#[derive(Debug)]
98pub struct AuthEcdsaK256KeccakMultisig {
99 config: AuthEcdsaK256KeccakMultisigConfig,
100}
101
102impl AuthEcdsaK256KeccakMultisig {
103 pub fn new(config: AuthEcdsaK256KeccakMultisigConfig) -> Result<Self, AccountError> {
105 Ok(Self { config })
106 }
107}
108
109impl From<AuthEcdsaK256KeccakMultisig> for AccountComponent {
110 fn from(multisig: AuthEcdsaK256KeccakMultisig) -> Self {
111 let mut storage_slots = Vec::with_capacity(3);
112
113 let num_approvers = multisig.config.approvers().len() as u32;
115 storage_slots.push(StorageSlot::Value(Word::from([
116 multisig.config.default_threshold(),
117 num_approvers,
118 0,
119 0,
120 ])));
121
122 let map_entries = multisig
124 .config
125 .approvers()
126 .iter()
127 .enumerate()
128 .map(|(i, pub_key)| (Word::from([i as u32, 0, 0, 0]), (*pub_key).into()));
129
130 storage_slots.push(StorageSlot::Map(StorageMap::with_entries(map_entries).unwrap()));
132
133 let executed_transactions = StorageMap::default();
135 storage_slots.push(StorageSlot::Map(executed_transactions));
136
137 let proc_threshold_roots = StorageMap::with_entries(
139 multisig
140 .config
141 .proc_thresholds()
142 .iter()
143 .map(|(proc_root, threshold)| (*proc_root, Word::from([*threshold, 0, 0, 0]))),
144 )
145 .unwrap();
146 storage_slots.push(StorageSlot::Map(proc_threshold_roots));
147
148 AccountComponent::new(ecdsa_k256_keccak_multisig_library(), storage_slots)
149 .expect("Multisig auth component should satisfy the requirements of a valid account component")
150 .with_supports_all_types()
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use alloc::string::ToString;
157
158 use miden_objects::Word;
159 use miden_objects::account::AccountBuilder;
160
161 use super::*;
162 use crate::account::wallets::BasicWallet;
163
164 #[test]
166 fn test_multisig_component_setup() {
167 let pub_key_1 = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0]));
169 let pub_key_2 = PublicKeyCommitment::from(Word::from([2u32, 0, 0, 0]));
170 let pub_key_3 = PublicKeyCommitment::from(Word::from([3u32, 0, 0, 0]));
171 let approvers = vec![pub_key_1, pub_key_2, pub_key_3];
172 let threshold = 2u32;
173
174 let multisig_component = AuthEcdsaK256KeccakMultisig::new(
176 AuthEcdsaK256KeccakMultisigConfig::new(approvers.clone(), threshold)
177 .expect("invalid multisig config"),
178 )
179 .expect("multisig component creation failed");
180
181 let account = AccountBuilder::new([0; 32])
183 .with_auth_component(multisig_component)
184 .with_component(BasicWallet)
185 .build()
186 .expect("account building failed");
187
188 let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
190 assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
191
192 for (i, expected_pub_key) in approvers.iter().enumerate() {
194 let stored_pub_key = account
195 .storage()
196 .get_map_item(1, Word::from([i as u32, 0, 0, 0]))
197 .expect("storage map access failed");
198 assert_eq!(stored_pub_key, Word::from(*expected_pub_key));
199 }
200 }
201
202 #[test]
204 fn test_multisig_component_minimum_threshold() {
205 let pub_key = PublicKeyCommitment::from(Word::from([42u32, 0, 0, 0]));
206 let approvers = vec![pub_key];
207 let threshold = 1u32;
208
209 let multisig_component = AuthEcdsaK256KeccakMultisig::new(
210 AuthEcdsaK256KeccakMultisigConfig::new(approvers.clone(), threshold)
211 .expect("invalid multisig config"),
212 )
213 .expect("multisig component creation failed");
214
215 let account = AccountBuilder::new([0; 32])
216 .with_auth_component(multisig_component)
217 .with_component(BasicWallet)
218 .build()
219 .expect("account building failed");
220
221 let threshold_slot = account.storage().get_item(0).expect("storage slot 0 access failed");
223 assert_eq!(threshold_slot, Word::from([threshold, approvers.len() as u32, 0, 0]));
224
225 let stored_pub_key = account
226 .storage()
227 .get_map_item(1, Word::from([0u32, 0, 0, 0]))
228 .expect("storage map access failed");
229 assert_eq!(stored_pub_key, Word::from(pub_key));
230 }
231
232 #[test]
234 fn test_multisig_component_error_cases() {
235 let pub_key = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0]));
236 let approvers = vec![pub_key];
237
238 let result = AuthEcdsaK256KeccakMultisigConfig::new(approvers.clone(), 0);
240 assert!(result.unwrap_err().to_string().contains("threshold must be at least 1"));
241
242 let result = AuthEcdsaK256KeccakMultisigConfig::new(approvers, 2);
244 assert!(
245 result
246 .unwrap_err()
247 .to_string()
248 .contains("threshold cannot be greater than number of approvers")
249 );
250 }
251
252 #[test]
254 fn test_multisig_component_duplicate_approvers() {
255 let pub_key_1 = PublicKeyCommitment::from(Word::from([1u32, 0, 0, 0]));
256 let pub_key_2 = PublicKeyCommitment::from(Word::from([2u32, 0, 0, 0]));
257
258 let approvers = vec![pub_key_1, pub_key_2, pub_key_1];
260 let result = AuthEcdsaK256KeccakMultisigConfig::new(approvers, 2);
261 assert!(
262 result
263 .unwrap_err()
264 .to_string()
265 .contains("duplicate approver public keys are not allowed")
266 );
267 }
268}