miden_client/keystore/
fs_keystore.rs1use 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#[derive(Debug, Clone)]
32pub struct FilesystemKeyStore<R: Rng> {
33 rng: Arc<RwLock<R>>,
35 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 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 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
112impl FilesystemKeyStore<rand::rngs::StdRng> {
115 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 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
151fn 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}