Skip to main content

miden_client/keystore/
fs_keystore.rs

1use 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/// A filesystem-based keystore that stores keys in separate files and provides transaction
16/// authentication functionality. The public key is hashed and the result is used as the filename
17/// and the contents of the file are the serialized public and secret key.
18#[derive(Debug, Clone)]
19pub struct FilesystemKeyStore {
20    /// The directory where the keys are stored and read from.
21    pub keys_directory: PathBuf,
22}
23
24impl FilesystemKeyStore {
25    /// Creates a [`FilesystemKeyStore`] on a specific directory.
26    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    /// Adds a secret key to the keystore.
36    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    /// Removes a secret key from the keystore, given the commitment of a public key.
43    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    /// Retrieves a secret key from the keystore given the commitment of a public key.
53    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    /// Gets a signature over a message, given a public key.
69    ///
70    /// The public key should correspond to one of the keys tracked by the keystore.
71    ///
72    /// # Errors
73    /// If the public key isn't found in the store, [`AuthenticationError::UnknownPublicKey`] is
74    /// returned.
75    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
99// HELPERS
100// ================================================================================================
101
102/// Returns the file path that belongs to the public key commitment
103fn 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
108/// Reads a file into an [`AuthSecretKey`]
109fn 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
116/// Write an [`AuthSecretKey`] into a file
117fn 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
125/// Hashes a public key to a string representation.
126fn 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}