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