1use std::{
6 collections::{BTreeMap, HashSet},
7 fmt::{Display, Formatter, Write},
8 fs,
9 fs::File,
10 io::BufReader,
11 path::{Path, PathBuf},
12};
13
14use anyhow::{Context, anyhow, bail, ensure};
15use bip32::DerivationPath;
16use bip39::{Language, Mnemonic, Seed};
17use iota_types::{
18 base_types::IotaAddress,
19 crypto::{
20 EncodeDecodeBase64, IotaKeyPair, PublicKey, Signature, SignatureScheme, enum_dispatch,
21 get_key_pair_from_rng,
22 },
23};
24use rand::{SeedableRng, rngs::StdRng};
25use regex::Regex;
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27use shared_crypto::intent::{Intent, IntentMessage};
28
29use crate::{
30 key_derive::{derive_key_pair_from_path, generate_new_key},
31 random_names::{random_name, random_names},
32};
33
34#[derive(Serialize, Deserialize)]
35#[enum_dispatch(AccountKeystore)]
36pub enum Keystore {
37 File(FileBasedKeystore),
38 InMem(InMemKeystore),
39}
40#[enum_dispatch]
41pub trait AccountKeystore: Send + Sync {
42 fn add_key(&mut self, alias: Option<String>, keypair: IotaKeyPair)
43 -> Result<(), anyhow::Error>;
44 fn keys(&self) -> Vec<PublicKey>;
45 fn get_key(&self, address: &IotaAddress) -> Result<&IotaKeyPair, anyhow::Error>;
46
47 fn sign_hashed(&self, address: &IotaAddress, msg: &[u8])
48 -> Result<Signature, signature::Error>;
49
50 fn sign_secure<T>(
51 &self,
52 address: &IotaAddress,
53 msg: &T,
54 intent: Intent,
55 ) -> Result<Signature, signature::Error>
56 where
57 T: Serialize;
58 fn addresses(&self) -> Vec<IotaAddress> {
59 self.keys().iter().map(|k| k.into()).collect()
60 }
61 fn addresses_with_alias(&self) -> Vec<(&IotaAddress, &Alias)>;
62 fn aliases(&self) -> Vec<&Alias>;
63 fn aliases_mut(&mut self) -> Vec<&mut Alias>;
64 fn alias_names(&self) -> Vec<&str> {
65 self.aliases()
66 .into_iter()
67 .map(|a| a.alias.as_str())
68 .collect()
69 }
70 fn get_alias_by_address(&self, address: &IotaAddress) -> Result<String, anyhow::Error>;
72 fn get_address_by_alias(&self, alias: String) -> Result<&IotaAddress, anyhow::Error>;
73 fn alias_exists(&self, alias: &str) -> bool {
75 self.alias_names().contains(&alias)
76 }
77
78 fn create_alias(&self, alias: Option<String>) -> Result<String, anyhow::Error>;
79
80 fn update_alias(
81 &mut self,
82 old_alias: &str,
83 new_alias: Option<&str>,
84 ) -> Result<String, anyhow::Error>;
85
86 fn update_alias_value(
88 &mut self,
89 old_alias: &str,
90 new_alias: Option<&str>,
91 ) -> Result<String, anyhow::Error> {
92 if !self.alias_exists(old_alias) {
93 bail!("The provided alias {old_alias} does not exist");
94 }
95
96 let new_alias_name = self.create_alias(new_alias.map(str::to_string))?;
97
98 for a in self.aliases_mut() {
99 if a.alias == old_alias {
100 let pk = &a.public_key_base64;
101 *a = Alias {
102 alias: new_alias_name.clone(),
103 public_key_base64: pk.clone(),
104 };
105 }
106 }
107 Ok(new_alias_name)
108 }
109
110 fn generate_and_add_new_key(
111 &mut self,
112 key_scheme: SignatureScheme,
113 alias: Option<String>,
114 derivation_path: Option<DerivationPath>,
115 word_length: Option<String>,
116 ) -> Result<(IotaAddress, String, SignatureScheme), anyhow::Error> {
117 let (address, kp, scheme, phrase) =
118 generate_new_key(key_scheme, derivation_path, word_length)?;
119 self.add_key(alias, kp)?;
120 Ok((address, phrase, scheme))
121 }
122
123 fn import_from_mnemonic(
124 &mut self,
125 phrase: &str,
126 key_scheme: SignatureScheme,
127 derivation_path: Option<DerivationPath>,
128 alias: Option<String>,
129 ) -> Result<IotaAddress, anyhow::Error> {
130 let mnemonic = Mnemonic::from_phrase(phrase, Language::English)
131 .map_err(|e| anyhow::anyhow!("Invalid mnemonic phrase: {:?}", e))?;
132 let seed = Seed::new(&mnemonic, "");
133 self.import_from_seed(seed.as_bytes(), key_scheme, derivation_path, alias)
134 }
135
136 fn import_from_seed(
137 &mut self,
138 seed: &[u8],
139 key_scheme: SignatureScheme,
140 derivation_path: Option<DerivationPath>,
141 alias: Option<String>,
142 ) -> Result<IotaAddress, anyhow::Error> {
143 match derive_key_pair_from_path(seed, derivation_path, &key_scheme) {
144 Ok((address, kp)) => {
145 self.add_key(alias, kp)?;
146 Ok(address)
147 }
148 Err(e) => Err(anyhow!("error getting keypair {:?}", e)),
149 }
150 }
151}
152
153impl Display for Keystore {
154 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
155 let mut writer = String::new();
156 match self {
157 Keystore::File(file) => {
158 writeln!(writer, "Keystore Type: File")?;
159 write!(writer, "Keystore Path : {:?}", file.path)?;
160 write!(f, "{}", writer)
161 }
162 Keystore::InMem(_) => {
163 writeln!(writer, "Keystore Type: InMem")?;
164 write!(f, "{}", writer)
165 }
166 }
167 }
168}
169
170#[derive(Serialize, Deserialize, Clone, Debug)]
171pub struct Alias {
172 pub alias: String,
173 pub public_key_base64: String,
174}
175
176#[derive(Default)]
177pub struct FileBasedKeystore {
178 keys: BTreeMap<IotaAddress, IotaKeyPair>,
179 aliases: BTreeMap<IotaAddress, Alias>,
180 path: PathBuf,
181}
182
183impl Serialize for FileBasedKeystore {
184 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
185 where
186 S: Serializer,
187 {
188 serializer.serialize_str(self.path.to_str().unwrap_or(""))
189 }
190}
191
192impl<'de> Deserialize<'de> for FileBasedKeystore {
193 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
194 where
195 D: Deserializer<'de>,
196 {
197 use serde::de::Error;
198 FileBasedKeystore::new(&PathBuf::from(String::deserialize(deserializer)?))
199 .map_err(D::Error::custom)
200 }
201}
202
203impl AccountKeystore for FileBasedKeystore {
204 fn sign_hashed(
205 &self,
206 address: &IotaAddress,
207 msg: &[u8],
208 ) -> Result<Signature, signature::Error> {
209 Ok(Signature::new_hashed(
210 msg,
211 self.keys.get(address).ok_or_else(|| {
212 signature::Error::from_source(format!("Cannot find key for address: [{address}]"))
213 })?,
214 ))
215 }
216 fn sign_secure<T>(
217 &self,
218 address: &IotaAddress,
219 msg: &T,
220 intent: Intent,
221 ) -> Result<Signature, signature::Error>
222 where
223 T: Serialize,
224 {
225 Ok(Signature::new_secure(
226 &IntentMessage::new(intent, msg),
227 self.keys.get(address).ok_or_else(|| {
228 signature::Error::from_source(format!("Cannot find key for address: [{address}]"))
229 })?,
230 ))
231 }
232
233 fn add_key(
234 &mut self,
235 alias: Option<String>,
236 keypair: IotaKeyPair,
237 ) -> Result<(), anyhow::Error> {
238 let address: IotaAddress = (&keypair.public()).into();
239 let alias = self.create_alias(alias)?;
240 self.aliases.insert(
241 address,
242 Alias {
243 alias,
244 public_key_base64: keypair.public().encode_base64(),
245 },
246 );
247 self.keys.insert(address, keypair);
248 self.save()?;
249 Ok(())
250 }
251
252 fn aliases(&self) -> Vec<&Alias> {
255 self.aliases.values().collect()
256 }
257
258 fn addresses_with_alias(&self) -> Vec<(&IotaAddress, &Alias)> {
259 self.aliases.iter().collect::<Vec<_>>()
260 }
261
262 fn aliases_mut(&mut self) -> Vec<&mut Alias> {
265 self.aliases.values_mut().collect()
266 }
267
268 fn keys(&self) -> Vec<PublicKey> {
269 self.keys.values().map(|key| key.public()).collect()
270 }
271
272 fn create_alias(&self, alias: Option<String>) -> Result<String, anyhow::Error> {
276 match alias {
277 Some(a) if self.alias_exists(&a) => {
278 bail!("Alias {a} already exists. Please choose another alias.")
279 }
280 Some(a) => validate_alias(&a),
281 None => Ok(random_name(
282 &self
283 .alias_names()
284 .into_iter()
285 .map(|x| x.to_string())
286 .collect::<HashSet<_>>(),
287 )),
288 }
289 }
290
291 fn get_address_by_alias(&self, alias: String) -> Result<&IotaAddress, anyhow::Error> {
293 self.addresses_with_alias()
294 .iter()
295 .find(|x| x.1.alias == alias)
296 .ok_or_else(|| anyhow!("Cannot resolve alias {alias} to an address"))
297 .map(|x| x.0)
298 }
299
300 fn get_alias_by_address(&self, address: &IotaAddress) -> Result<String, anyhow::Error> {
302 match self.aliases.get(address) {
303 Some(alias) => Ok(alias.alias.clone()),
304 None => bail!("Cannot find alias for address {address}"),
305 }
306 }
307
308 fn get_key(&self, address: &IotaAddress) -> Result<&IotaKeyPair, anyhow::Error> {
309 match self.keys.get(address) {
310 Some(key) => Ok(key),
311 None => Err(anyhow!("Cannot find key for address: [{address}]")),
312 }
313 }
314
315 fn update_alias(
318 &mut self,
319 old_alias: &str,
320 new_alias: Option<&str>,
321 ) -> Result<String, anyhow::Error> {
322 let new_alias_name = self.update_alias_value(old_alias, new_alias)?;
323 self.save_aliases()?;
324 Ok(new_alias_name)
325 }
326}
327
328impl FileBasedKeystore {
329 pub fn new(path: &PathBuf) -> Result<Self, anyhow::Error> {
330 let keys = if path.exists() {
331 let reader =
332 BufReader::new(File::open(path).with_context(|| {
333 format!("Cannot open the keystore file: {}", path.display())
334 })?);
335 let kp_strings: Vec<String> = serde_json::from_reader(reader).with_context(|| {
336 format!("Cannot deserialize the keystore file: {}", path.display(),)
337 })?;
338 kp_strings
339 .iter()
340 .map(|kpstr| {
341 let key = IotaKeyPair::decode(kpstr);
342 key.map(|k| (IotaAddress::from(&k.public()), k))
343 })
344 .collect::<Result<BTreeMap<_, _>, _>>()
345 .map_err(|e| anyhow!("Invalid keystore file: {}. {}", path.display(), e))?
346 } else {
347 BTreeMap::new()
348 };
349
350 let mut aliases_path = path.clone();
352 aliases_path.set_extension("aliases");
353
354 let aliases = if aliases_path.exists() {
355 let reader = BufReader::new(File::open(&aliases_path).with_context(|| {
356 format!(
357 "Cannot open aliases file in keystore: {}",
358 aliases_path.display()
359 )
360 })?);
361
362 let aliases: Vec<Alias> = serde_json::from_reader(reader).with_context(|| {
363 format!(
364 "Cannot deserialize aliases file in keystore: {}",
365 aliases_path.display(),
366 )
367 })?;
368
369 aliases
370 .into_iter()
371 .map(|alias| {
372 let key = PublicKey::decode_base64(&alias.public_key_base64);
373 key.map(|k| (Into::<IotaAddress>::into(&k), alias))
374 })
375 .collect::<Result<BTreeMap<_, _>, _>>()
376 .map_err(|e| {
377 anyhow!(
378 "Invalid aliases file in keystore: {}. {}",
379 aliases_path.display(),
380 e
381 )
382 })?
383 } else if keys.is_empty() {
384 BTreeMap::new()
385 } else {
386 let names: Vec<String> = random_names(HashSet::new(), keys.len());
387 let aliases = keys
388 .iter()
389 .zip(names)
390 .map(|((iota_address, ikp), alias)| {
391 let public_key_base64 = ikp.public().encode_base64();
392 (
393 *iota_address,
394 Alias {
395 alias,
396 public_key_base64,
397 },
398 )
399 })
400 .collect::<BTreeMap<_, _>>();
401 let aliases_store = serde_json::to_string_pretty(&aliases.values().collect::<Vec<_>>())
402 .with_context(|| {
403 format!(
404 "Cannot serialize aliases to file in keystore: {}",
405 aliases_path.display()
406 )
407 })?;
408 fs::write(aliases_path, aliases_store)?;
409 aliases
410 };
411
412 Ok(Self {
413 keys,
414 aliases,
415 path: path.to_path_buf(),
416 })
417 }
418
419 pub fn set_path(&mut self, path: &Path) {
420 self.path = path.to_path_buf();
421 }
422
423 pub fn save_aliases(&self) -> Result<(), anyhow::Error> {
424 let aliases_store = serde_json::to_string_pretty(
425 &self.aliases.values().collect::<Vec<_>>(),
426 )
427 .with_context(|| {
428 format!(
429 "Cannot serialize aliases to file in keystore: {}",
430 self.path.display()
431 )
432 })?;
433
434 let mut aliases_path = self.path.clone();
435 aliases_path.set_extension("aliases");
436 fs::write(&aliases_path, aliases_store)
437 .map_err(|e| anyhow!("Couldn't save aliases to {}: {e}", aliases_path.display()))?;
438 Ok(())
439 }
440
441 pub fn save_keystore(&self) -> Result<(), anyhow::Error> {
446 let store = serde_json::to_string_pretty(
447 &self
448 .keys
449 .values()
450 .map(|k| k.encode())
451 .collect::<Result<Vec<_>, _>>()
452 .map_err(|e| anyhow!(e))?,
453 )
454 .with_context(|| format!("Cannot serialize keystore to file: {}", self.path.display()))?;
455 fs::write(&self.path, store)
456 .map_err(|e| anyhow!("Couldn't save keystore to {}: {e}", self.path.display()))?;
457 Ok(())
458 }
459
460 pub fn save(&self) -> Result<(), anyhow::Error> {
461 self.save_aliases()?;
462 self.save_keystore()?;
463 Ok(())
464 }
465
466 pub fn key_pairs(&self) -> Vec<&IotaKeyPair> {
467 self.keys.values().collect()
468 }
469}
470
471#[derive(Default, Serialize, Deserialize)]
472pub struct InMemKeystore {
473 aliases: BTreeMap<IotaAddress, Alias>,
474 keys: BTreeMap<IotaAddress, IotaKeyPair>,
475}
476
477impl AccountKeystore for InMemKeystore {
478 fn sign_hashed(
479 &self,
480 address: &IotaAddress,
481 msg: &[u8],
482 ) -> Result<Signature, signature::Error> {
483 Ok(Signature::new_hashed(
484 msg,
485 self.keys.get(address).ok_or_else(|| {
486 signature::Error::from_source(format!("Cannot find key for address: [{address}]"))
487 })?,
488 ))
489 }
490 fn sign_secure<T>(
491 &self,
492 address: &IotaAddress,
493 msg: &T,
494 intent: Intent,
495 ) -> Result<Signature, signature::Error>
496 where
497 T: Serialize,
498 {
499 Ok(Signature::new_secure(
500 &IntentMessage::new(intent, msg),
501 self.keys.get(address).ok_or_else(|| {
502 signature::Error::from_source(format!("Cannot find key for address: [{address}]"))
503 })?,
504 ))
505 }
506
507 fn add_key(
508 &mut self,
509 alias: Option<String>,
510 keypair: IotaKeyPair,
511 ) -> Result<(), anyhow::Error> {
512 let address: IotaAddress = (&keypair.public()).into();
513 let alias = alias.unwrap_or_else(|| {
514 random_name(
515 &self
516 .aliases()
517 .iter()
518 .map(|x| x.alias.clone())
519 .collect::<HashSet<_>>(),
520 )
521 });
522
523 let public_key_base64 = keypair.public().encode_base64();
524 let alias = Alias {
525 alias,
526 public_key_base64,
527 };
528 self.aliases.insert(address, alias);
529 self.keys.insert(address, keypair);
530 Ok(())
531 }
532
533 fn aliases(&self) -> Vec<&Alias> {
535 self.aliases.values().collect()
536 }
537
538 fn addresses_with_alias(&self) -> Vec<(&IotaAddress, &Alias)> {
539 self.aliases.iter().collect::<Vec<_>>()
540 }
541
542 fn keys(&self) -> Vec<PublicKey> {
543 self.keys.values().map(|key| key.public()).collect()
544 }
545
546 fn get_key(&self, address: &IotaAddress) -> Result<&IotaKeyPair, anyhow::Error> {
547 match self.keys.get(address) {
548 Some(key) => Ok(key),
549 None => Err(anyhow!("Cannot find key for address: [{address}]")),
550 }
551 }
552
553 fn get_alias_by_address(&self, address: &IotaAddress) -> Result<String, anyhow::Error> {
555 match self.aliases.get(address) {
556 Some(alias) => Ok(alias.alias.clone()),
557 None => bail!("Cannot find alias for address {address}"),
558 }
559 }
560
561 fn get_address_by_alias(&self, alias: String) -> Result<&IotaAddress, anyhow::Error> {
563 self.addresses_with_alias()
564 .iter()
565 .find(|x| x.1.alias == alias)
566 .ok_or_else(|| anyhow!("Cannot resolve alias {alias} to an address"))
567 .map(|x| x.0)
568 }
569
570 fn create_alias(&self, alias: Option<String>) -> Result<String, anyhow::Error> {
574 match alias {
575 Some(a) if self.alias_exists(&a) => {
576 bail!("Alias {a} already exists. Please choose another alias.")
577 }
578 Some(a) => validate_alias(&a),
579 None => Ok(random_name(
580 &self
581 .alias_names()
582 .into_iter()
583 .map(|x| x.to_string())
584 .collect::<HashSet<_>>(),
585 )),
586 }
587 }
588
589 fn aliases_mut(&mut self) -> Vec<&mut Alias> {
590 self.aliases.values_mut().collect()
591 }
592
593 fn update_alias(
596 &mut self,
597 old_alias: &str,
598 new_alias: Option<&str>,
599 ) -> Result<String, anyhow::Error> {
600 self.update_alias_value(old_alias, new_alias)
601 }
602}
603
604impl InMemKeystore {
605 pub fn new_insecure_for_tests(initial_key_number: usize) -> Self {
606 let mut rng = StdRng::from_seed([0; 32]);
607 let keys = (0..initial_key_number)
608 .map(|_| get_key_pair_from_rng(&mut rng))
609 .map(|(ad, k)| (ad, IotaKeyPair::Ed25519(k)))
610 .collect::<BTreeMap<IotaAddress, IotaKeyPair>>();
611
612 let aliases = keys
613 .iter()
614 .zip(random_names(HashSet::new(), keys.len()))
615 .map(|((iota_address, ikp), alias)| {
616 let public_key_base64 = ikp.public().encode_base64();
617 (
618 *iota_address,
619 Alias {
620 alias,
621 public_key_base64,
622 },
623 )
624 })
625 .collect::<BTreeMap<_, _>>();
626
627 Self { aliases, keys }
628 }
629}
630
631fn validate_alias(alias: &str) -> Result<String, anyhow::Error> {
632 let re = Regex::new(r"^[A-Za-z][A-Za-z0-9-_\.]*$")
633 .map_err(|_| anyhow!("Cannot build the regex needed to validate the alias naming"))?;
634 let alias = alias.trim();
635 ensure!(
636 re.is_match(alias),
637 "Invalid alias. A valid alias must start with a letter and can contain only letters, digits, hyphens (-), dots (.), or underscores (_)."
638 );
639 Ok(alias.to_string())
640}
641
642#[cfg(test)]
643mod tests {
644 use crate::keystore::validate_alias;
645
646 #[test]
647 fn validate_alias_test() {
648 assert!(validate_alias("A.B_dash").is_ok());
650 assert!(validate_alias("A.B-C1_dash").is_ok());
651 assert!(validate_alias("abc_123.iota").is_ok());
652 assert!(validate_alias("A.B-C_dash!").is_err());
654 assert!(validate_alias(".B-C_dash!").is_err());
655 assert!(validate_alias("_test").is_err());
656 assert!(validate_alias("123").is_err());
657 assert!(validate_alias("@@123").is_err());
658 assert!(validate_alias("@_Ab").is_err());
659 assert!(validate_alias("_Ab").is_err());
660 assert!(validate_alias("^A").is_err());
661 assert!(validate_alias("-A").is_err());
662 }
663}