miden_client/keystore/
fs_keystore.rs

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