miden_client/keystore/
fs_keystore.rs1use alloc::string::String;
2use std::fs::OpenOptions;
3use std::hash::{DefaultHasher, Hash, Hasher};
4use std::io::{BufRead, BufReader, BufWriter, Write};
5use std::path::PathBuf;
6use std::string::ToString;
7use std::sync::{Arc, RwLock};
8use std::vec::Vec;
9
10use miden_objects::account::AuthSecretKey;
11use miden_objects::{Felt, Word};
12use miden_tx::AuthenticationError;
13use miden_tx::auth::{SigningInputs, TransactionAuthenticator};
14use miden_tx::utils::{Deserializable, Serializable};
15use rand::{Rng, SeedableRng};
16
17use super::KeyStoreError;
18
19#[derive(Debug, Clone)]
26pub struct FilesystemKeyStore<R: Rng + Send + Sync> {
27 rng: Arc<RwLock<R>>,
29 keys_directory: PathBuf,
31}
32
33impl<R: Rng + Send + Sync> FilesystemKeyStore<R> {
34 pub fn with_rng(keys_directory: PathBuf, rng: R) -> Result<Self, KeyStoreError> {
35 if !keys_directory.exists() {
36 std::fs::create_dir_all(&keys_directory).map_err(|err| {
37 KeyStoreError::StorageError(format!("error creating keys directory: {err:?}"))
38 })?;
39 }
40
41 Ok(FilesystemKeyStore {
42 keys_directory,
43 rng: Arc::new(RwLock::new(rng)),
44 })
45 }
46
47 pub fn add_key(&self, key: &AuthSecretKey) -> Result<(), KeyStoreError> {
49 let pub_key = match key {
50 AuthSecretKey::RpoFalcon512(k) => Word::from(k.public_key()),
51 };
52
53 let filename = hash_pub_key(pub_key);
54
55 let file_path = self.keys_directory.join(filename);
56 let file = OpenOptions::new()
57 .write(true)
58 .create(true)
59 .truncate(true)
60 .open(file_path)
61 .map_err(|err| {
62 KeyStoreError::StorageError(format!("error opening secret key file: {err:?}"))
63 })?;
64
65 let mut writer = BufWriter::new(file);
66 let key_pair_hex = hex::encode(key.to_bytes());
67 writer.write_all(key_pair_hex.as_bytes()).map_err(|err| {
68 KeyStoreError::StorageError(format!("error writing secret key file: {err:?}"))
69 })?;
70
71 Ok(())
72 }
73
74 pub fn get_key(&self, pub_key: Word) -> Result<Option<AuthSecretKey>, KeyStoreError> {
76 let filename = hash_pub_key(pub_key);
77
78 let file_path = self.keys_directory.join(filename);
79 if !file_path.exists() {
80 return Ok(None);
81 }
82
83 let file = OpenOptions::new().read(true).open(file_path).map_err(|err| {
84 KeyStoreError::StorageError(format!("error opening secret key file: {err:?}"))
85 })?;
86 let mut reader = BufReader::new(file);
87 let mut key_pair_hex = String::new();
88 reader.read_line(&mut key_pair_hex).map_err(|err| {
89 KeyStoreError::StorageError(format!("error reading secret key file: {err:?}"))
90 })?;
91
92 let secret_key_bytes = hex::decode(key_pair_hex.trim()).map_err(|err| {
93 KeyStoreError::DecodingError(format!("error decoding secret key hex: {err:?}"))
94 })?;
95 let secret_key =
96 AuthSecretKey::read_from_bytes(secret_key_bytes.as_slice()).map_err(|err| {
97 KeyStoreError::DecodingError(format!(
98 "error reading secret key from bytes: {err:?}"
99 ))
100 })?;
101
102 Ok(Some(secret_key))
103 }
104}
105
106impl FilesystemKeyStore<rand::rngs::StdRng> {
109 pub fn new(keys_directory: PathBuf) -> Result<Self, KeyStoreError> {
111 use rand::rngs::StdRng;
112 let rng = StdRng::from_os_rng();
113
114 FilesystemKeyStore::with_rng(keys_directory, rng)
115 }
116}
117
118impl<R: Rng + Send + Sync> TransactionAuthenticator for FilesystemKeyStore<R> {
119 async fn get_signature(
127 &self,
128 pub_key: Word,
129 signing_info: &SigningInputs,
130 ) -> Result<Vec<Felt>, AuthenticationError> {
131 let mut rng = self.rng.write().expect("poisoned lock");
132
133 let message = signing_info.to_commitment();
134
135 let secret_key = self
136 .get_key(pub_key)
137 .map_err(|err| AuthenticationError::other(err.to_string()))?;
138
139 let AuthSecretKey::RpoFalcon512(k) =
140 secret_key.ok_or(AuthenticationError::UnknownPublicKey(pub_key.to_hex()))?;
141
142 miden_tx::auth::signatures::get_falcon_signature(&k, message, &mut *rng)
143 }
144}
145
146fn hash_pub_key(pub_key: Word) -> String {
148 let pub_key = pub_key.to_hex();
149 let mut hasher = DefaultHasher::new();
150 pub_key.hash(&mut hasher);
151 hasher.finish().to_string()
152}