miden_client/keystore/
fs_keystore.rs

1use alloc::string::String;
2use std::{
3    fs::OpenOptions,
4    hash::{DefaultHasher, Hash, Hasher},
5    io::{BufRead, BufReader, BufWriter, Write},
6    path::PathBuf,
7    string::ToString,
8    sync::Arc,
9    vec::Vec,
10};
11
12use miden_objects::{
13    Digest, Felt, Word,
14    account::{AccountDelta, AuthSecretKey},
15};
16use miden_tx::{
17    AuthenticationError,
18    auth::TransactionAuthenticator,
19    utils::{Deserializable, Serializable, sync::RwLock},
20};
21use rand::{Rng, SeedableRng};
22
23use super::KeyStoreError;
24
25/// A filesystem-based keystore that stores keys in separate files and provides transaction
26/// authentication functionality. The public key is hashed and the result is used as the filename
27/// and the contents of the file are the serialized public and secret key.
28///
29/// The keystore requires an RNG component for generating Falcon signatures at the moment of
30/// transaction signing.
31#[derive(Debug, Clone)]
32pub struct FilesystemKeyStore<R: Rng> {
33    /// The random number generator used to generate signatures.
34    rng: Arc<RwLock<R>>,
35    /// The directory where the keys are stored and read from.
36    keys_directory: PathBuf,
37}
38
39impl<R: Rng> FilesystemKeyStore<R> {
40    pub fn with_rng(keys_directory: PathBuf, rng: R) -> Result<Self, KeyStoreError> {
41        if !keys_directory.exists() {
42            std::fs::create_dir_all(&keys_directory).map_err(|err| {
43                KeyStoreError::StorageError(format!("error creating keys directory: {err:?}"))
44            })?;
45        }
46
47        Ok(FilesystemKeyStore {
48            keys_directory,
49            rng: Arc::new(RwLock::new(rng)),
50        })
51    }
52
53    /// Adds a secret key to the keystore.
54    pub fn add_key(&self, key: &AuthSecretKey) -> Result<(), KeyStoreError> {
55        let pub_key = match key {
56            AuthSecretKey::RpoFalcon512(k) => Word::from(k.public_key()),
57        };
58
59        let filename = hash_pub_key(pub_key);
60
61        let file_path = self.keys_directory.join(filename);
62        let file = OpenOptions::new()
63            .write(true)
64            .create(true)
65            .truncate(true)
66            .open(file_path)
67            .map_err(|err| {
68                KeyStoreError::StorageError(format!("error opening secret key file: {err:?}"))
69            })?;
70
71        let mut writer = BufWriter::new(file);
72        let key_pair_hex = hex::encode(key.to_bytes());
73        writer.write_all(key_pair_hex.as_bytes()).map_err(|err| {
74            KeyStoreError::StorageError(format!("error writing secret key file: {err:?}"))
75        })?;
76
77        Ok(())
78    }
79
80    /// Retrieves a secret key from the keystore given its public key.
81    pub fn get_key(&self, pub_key: Word) -> Result<Option<AuthSecretKey>, KeyStoreError> {
82        let filename = hash_pub_key(pub_key);
83
84        let file_path = self.keys_directory.join(filename);
85        if !file_path.exists() {
86            return Ok(None);
87        }
88
89        let file = OpenOptions::new().read(true).open(file_path).map_err(|err| {
90            KeyStoreError::StorageError(format!("error opening secret key file: {err:?}"))
91        })?;
92        let mut reader = BufReader::new(file);
93        let mut key_pair_hex = String::new();
94        reader.read_line(&mut key_pair_hex).map_err(|err| {
95            KeyStoreError::StorageError(format!("error reading secret key file: {err:?}"))
96        })?;
97
98        let secret_key_bytes = hex::decode(key_pair_hex.trim()).map_err(|err| {
99            KeyStoreError::DecodingError(format!("error decoding secret key hex: {err:?}"))
100        })?;
101        let secret_key =
102            AuthSecretKey::read_from_bytes(secret_key_bytes.as_slice()).map_err(|err| {
103                KeyStoreError::DecodingError(format!(
104                    "error reading secret key from bytes: {err:?}"
105                ))
106            })?;
107
108        Ok(Some(secret_key))
109    }
110}
111
112// Provide a default implementation for `StdRng` so you can call FilesystemKeyStore::new() without
113// type annotations.
114impl FilesystemKeyStore<rand::rngs::StdRng> {
115    /// Creates a new [`FilesystemKeyStore`] using [`rand::rngs::StdRng`] as the RNG.
116    pub fn new(keys_directory: PathBuf) -> Result<Self, KeyStoreError> {
117        use rand::rngs::StdRng;
118        let rng = StdRng::from_os_rng();
119
120        FilesystemKeyStore::with_rng(keys_directory, rng)
121    }
122}
123
124impl<R: Rng> TransactionAuthenticator for FilesystemKeyStore<R> {
125    /// Gets a signature over a message, given a public key.
126    ///
127    /// The public key should correspond to one of the keys tracked by the keystore.
128    ///
129    /// # Errors
130    /// If the public key isn't found in the store, [`AuthenticationError::UnknownPublicKey`] is
131    /// returned.
132    fn get_signature(
133        &self,
134        pub_key: Word,
135        message: Word,
136        _account_delta: &AccountDelta,
137    ) -> Result<Vec<Felt>, AuthenticationError> {
138        let mut rng = self.rng.write();
139
140        let secret_key = self
141            .get_key(pub_key)
142            .map_err(|err| AuthenticationError::other(err.to_string()))?;
143
144        let AuthSecretKey::RpoFalcon512(k) = secret_key
145            .ok_or(AuthenticationError::UnknownPublicKey(Digest::from(pub_key).into()))?;
146
147        miden_tx::auth::signatures::get_falcon_signature(&k, message, &mut *rng)
148    }
149}
150
151/// Hashes a public key to a string representation.
152fn hash_pub_key(pub_key: Word) -> String {
153    let pub_key = Digest::from(pub_key).to_hex();
154    let mut hasher = DefaultHasher::new();
155    pub_key.hash(&mut hasher);
156    hasher.finish().to_string()
157}