miden_client/keystore/
fs_keystore.rs1use alloc::string::String;
2use std::fs;
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::path::{Path, PathBuf};
5use std::string::ToString;
6
7use miden_protocol::Word;
8use miden_protocol::account::auth::{AuthSecretKey, PublicKey, PublicKeyCommitment, Signature};
9use miden_tx::AuthenticationError;
10use miden_tx::auth::{SigningInputs, TransactionAuthenticator};
11use miden_tx::utils::{Deserializable, Serializable};
12
13use super::KeyStoreError;
14
15#[derive(Debug, Clone)]
19pub struct FilesystemKeyStore {
20 pub keys_directory: PathBuf,
22}
23
24impl FilesystemKeyStore {
25 pub fn new(keys_directory: PathBuf) -> Result<Self, KeyStoreError> {
27 if !keys_directory.exists() {
28 fs::create_dir_all(&keys_directory)
29 .map_err(keystore_error("error creating keys directory"))?;
30 }
31
32 Ok(FilesystemKeyStore { keys_directory })
33 }
34
35 pub fn add_key(&self, key: &AuthSecretKey) -> Result<(), KeyStoreError> {
37 let pub_key_commitment = key.public_key().to_commitment();
38 let file_path = key_file_path(&self.keys_directory, pub_key_commitment);
39 write_secret_key_file(&file_path, key)
40 }
41
42 pub fn remove_key(&self, pub_key: PublicKeyCommitment) -> Result<(), KeyStoreError> {
44 let file_path = key_file_path(&self.keys_directory, pub_key);
45 if !file_path.exists() {
46 return Ok(());
47 }
48
49 fs::remove_file(file_path).map_err(keystore_error("error removing secret key file"))
50 }
51
52 pub fn get_key(
54 &self,
55 pub_key: PublicKeyCommitment,
56 ) -> Result<Option<AuthSecretKey>, KeyStoreError> {
57 let file_path = key_file_path(&self.keys_directory, pub_key);
58 if !file_path.exists() {
59 return Ok(None);
60 }
61
62 let secret_key = read_secret_key_file(&file_path)?;
63 Ok(Some(secret_key))
64 }
65}
66
67impl TransactionAuthenticator for FilesystemKeyStore {
68 async fn get_signature(
76 &self,
77 pub_key: PublicKeyCommitment,
78 signing_info: &SigningInputs,
79 ) -> Result<Signature, AuthenticationError> {
80 let message = signing_info.to_commitment();
81
82 let secret_key = self
83 .get_key(pub_key)
84 .map_err(|err| {
85 AuthenticationError::other_with_source("failed to load secret key", err)
86 })?
87 .ok_or(AuthenticationError::UnknownPublicKey(pub_key))?;
88
89 let signature = secret_key.sign(message);
90
91 Ok(signature)
92 }
93
94 async fn get_public_key(&self, _pub_key_commitment: PublicKeyCommitment) -> Option<&PublicKey> {
95 None
96 }
97}
98
99fn key_file_path(keys_directory: &Path, pub_key: PublicKeyCommitment) -> PathBuf {
104 let filename = hash_pub_key(pub_key.into());
105 keys_directory.join(filename)
106}
107
108fn read_secret_key_file(file_path: &Path) -> Result<AuthSecretKey, KeyStoreError> {
110 let bytes = fs::read(file_path).map_err(keystore_error("error reading secret key file"))?;
111 AuthSecretKey::read_from_bytes(bytes.as_slice()).map_err(|err| {
112 KeyStoreError::DecodingError(format!("error reading secret key from file: {err:?}"))
113 })
114}
115
116fn write_secret_key_file(file_path: &Path, key: &AuthSecretKey) -> Result<(), KeyStoreError> {
118 fs::write(file_path, key.to_bytes()).map_err(keystore_error("error writing secret key file"))
119}
120
121fn keystore_error(context: &str) -> impl FnOnce(std::io::Error) -> KeyStoreError {
122 move |err| KeyStoreError::StorageError(format!("{context}: {err:?}"))
123}
124
125fn hash_pub_key(pub_key: Word) -> String {
127 let pub_key = pub_key.to_hex();
128 let mut hasher = DefaultHasher::new();
129 pub_key.hash(&mut hasher);
130 hasher.finish().to_string()
131}