1use crate::{AgentKeyShare, Error, PartyRole, Result};
31use async_trait::async_trait;
32use chacha20poly1305::{
33 ChaCha20Poly1305, Nonce,
34 aead::{Aead, KeyInit},
35};
36use serde::{Deserialize, Serialize};
37use std::collections::HashMap;
38use std::path::PathBuf;
39use std::sync::Arc;
40use tokio::sync::RwLock;
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct EncryptedKeyShare {
45 pub ciphertext: Vec<u8>,
47 pub nonce: [u8; 12],
49 pub salt: [u8; 32],
51 pub role: PartyRole,
53 pub public_key: Vec<u8>,
55 pub eth_address: String,
57 pub created_at: i64,
59 pub version: u32,
61}
62
63impl EncryptedKeyShare {
64 pub const CURRENT_VERSION: u32 = 1;
66
67 pub fn encrypt(share: &AgentKeyShare, encryption_key: &[u8; 32]) -> Result<Self> {
69 let cipher = ChaCha20Poly1305::new(encryption_key.into());
70
71 let nonce_bytes: [u8; 12] = rand::random();
73 let salt: [u8; 32] = rand::random();
74 let nonce = Nonce::from_slice(&nonce_bytes);
75
76 let plaintext =
78 serde_json::to_vec(share).map_err(|e| Error::Serialization(e.to_string()))?;
79
80 let ciphertext = cipher
81 .encrypt(nonce, plaintext.as_ref())
82 .map_err(|e| Error::Encryption(e.to_string()))?;
83
84 let eth_address = share.eth_address().unwrap_or_default();
85
86 Ok(Self {
87 ciphertext,
88 nonce: nonce_bytes,
89 salt,
90 role: share.role,
91 public_key: share.public_key.clone(),
92 eth_address,
93 created_at: chrono::Utc::now().timestamp(),
94 version: Self::CURRENT_VERSION,
95 })
96 }
97
98 pub fn decrypt(&self, encryption_key: &[u8; 32]) -> Result<AgentKeyShare> {
100 let cipher = ChaCha20Poly1305::new(encryption_key.into());
101 let nonce = Nonce::from_slice(&self.nonce);
102
103 let plaintext = cipher
104 .decrypt(nonce, self.ciphertext.as_ref())
105 .map_err(|_| {
106 Error::Encryption("Decryption failed - invalid key or corrupted data".into())
107 })?;
108
109 let share: AgentKeyShare = serde_json::from_slice(&plaintext)
110 .map_err(|e| Error::Deserialization(e.to_string()))?;
111
112 Ok(share)
113 }
114}
115
116#[async_trait]
118pub trait KeyShareStore: Send + Sync {
119 async fn store(&self, id: &str, share: &EncryptedKeyShare) -> Result<()>;
121
122 async fn load(&self, id: &str) -> Result<EncryptedKeyShare>;
124
125 async fn delete(&self, id: &str) -> Result<()>;
127
128 async fn exists(&self, id: &str) -> Result<bool>;
130
131 async fn list(&self) -> Result<Vec<String>>;
133
134 async fn get_metadata(&self, id: &str) -> Result<ShareMetadata>;
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct ShareMetadata {
141 pub id: String,
143 pub role: PartyRole,
145 pub public_key: Vec<u8>,
147 pub eth_address: String,
149 pub created_at: i64,
151 pub version: u32,
153}
154
155#[derive(Debug)]
157pub struct EncryptedMemoryStore {
158 shares: Arc<RwLock<HashMap<String, EncryptedKeyShare>>>,
159}
160
161impl EncryptedMemoryStore {
162 pub fn new() -> Self {
164 Self {
165 shares: Arc::new(RwLock::new(HashMap::new())),
166 }
167 }
168}
169
170impl Default for EncryptedMemoryStore {
171 fn default() -> Self {
172 Self::new()
173 }
174}
175
176#[async_trait]
177impl KeyShareStore for EncryptedMemoryStore {
178 async fn store(&self, id: &str, share: &EncryptedKeyShare) -> Result<()> {
179 let mut shares = self.shares.write().await;
180 shares.insert(id.to_string(), share.clone());
181 Ok(())
182 }
183
184 async fn load(&self, id: &str) -> Result<EncryptedKeyShare> {
185 let shares = self.shares.read().await;
186 shares
187 .get(id)
188 .cloned()
189 .ok_or_else(|| Error::KeyShareNotFound(id.to_string()))
190 }
191
192 async fn delete(&self, id: &str) -> Result<()> {
193 let mut shares = self.shares.write().await;
194 shares.remove(id);
195 Ok(())
196 }
197
198 async fn exists(&self, id: &str) -> Result<bool> {
199 let shares = self.shares.read().await;
200 Ok(shares.contains_key(id))
201 }
202
203 async fn list(&self) -> Result<Vec<String>> {
204 let shares = self.shares.read().await;
205 Ok(shares.keys().cloned().collect())
206 }
207
208 async fn get_metadata(&self, id: &str) -> Result<ShareMetadata> {
209 let shares = self.shares.read().await;
210 let share = shares
211 .get(id)
212 .ok_or_else(|| Error::KeyShareNotFound(id.to_string()))?;
213
214 Ok(ShareMetadata {
215 id: id.to_string(),
216 role: share.role,
217 public_key: share.public_key.clone(),
218 eth_address: share.eth_address.clone(),
219 created_at: share.created_at,
220 version: share.version,
221 })
222 }
223}
224
225#[derive(Debug)]
227pub struct FileSystemStore {
228 base_path: PathBuf,
230}
231
232impl FileSystemStore {
233 pub fn new(base_path: impl Into<PathBuf>) -> Result<Self> {
235 let base_path = base_path.into();
236
237 if !base_path.exists() {
239 std::fs::create_dir_all(&base_path)?;
240 }
241
242 Ok(Self { base_path })
243 }
244
245 fn share_path(&self, id: &str) -> PathBuf {
247 let safe_id = id.replace(['/', '\\', '.', '~'], "_");
249 self.base_path.join(format!("{}.share", safe_id))
250 }
251}
252
253#[async_trait]
254impl KeyShareStore for FileSystemStore {
255 async fn store(&self, id: &str, share: &EncryptedKeyShare) -> Result<()> {
256 let path = self.share_path(id);
257 let data = serde_json::to_vec_pretty(share)?;
258
259 tokio::fs::write(&path, data).await?;
260
261 #[cfg(unix)]
263 {
264 use std::os::unix::fs::PermissionsExt;
265 let perms = std::fs::Permissions::from_mode(0o600);
266 std::fs::set_permissions(&path, perms)?;
267 }
268
269 Ok(())
270 }
271
272 async fn load(&self, id: &str) -> Result<EncryptedKeyShare> {
273 let path = self.share_path(id);
274
275 if !path.exists() {
276 return Err(Error::KeyShareNotFound(id.to_string()));
277 }
278
279 let data = tokio::fs::read(&path).await?;
280 let share: EncryptedKeyShare =
281 serde_json::from_slice(&data).map_err(|e| Error::Deserialization(e.to_string()))?;
282
283 Ok(share)
284 }
285
286 async fn delete(&self, id: &str) -> Result<()> {
287 let path = self.share_path(id);
288
289 if path.exists() {
290 let size = tokio::fs::metadata(&path).await?.len() as usize;
292 let zeros = vec![0u8; size];
293 tokio::fs::write(&path, zeros).await?;
294 tokio::fs::remove_file(&path).await?;
295 }
296
297 Ok(())
298 }
299
300 async fn exists(&self, id: &str) -> Result<bool> {
301 let path = self.share_path(id);
302 Ok(path.exists())
303 }
304
305 async fn list(&self) -> Result<Vec<String>> {
306 let mut ids = Vec::new();
307 let mut entries = tokio::fs::read_dir(&self.base_path).await?;
308
309 while let Some(entry) = entries.next_entry().await? {
310 let path = entry.path();
311 if path.extension().and_then(|s| s.to_str()) == Some("share") {
312 if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
313 ids.push(stem.to_string());
314 }
315 }
316 }
317
318 Ok(ids)
319 }
320
321 async fn get_metadata(&self, id: &str) -> Result<ShareMetadata> {
322 let share = self.load(id).await?;
323
324 Ok(ShareMetadata {
325 id: id.to_string(),
326 role: share.role,
327 public_key: share.public_key,
328 eth_address: share.eth_address,
329 created_at: share.created_at,
330 version: share.version,
331 })
332 }
333}
334
335pub fn derive_key_from_password(password: &str, salt: &[u8; 32]) -> Result<[u8; 32]> {
337 use sha2::{Digest, Sha256};
338
339 let mut hasher = Sha256::new();
341 hasher.update(password.as_bytes());
342 hasher.update(salt);
343
344 let mut result = hasher.finalize();
346 for _ in 0..10000 {
347 let mut hasher = Sha256::new();
348 hasher.update(&result);
349 hasher.update(salt);
350 result = hasher.finalize();
351 }
352
353 let mut key = [0u8; 32];
354 key.copy_from_slice(&result);
355 Ok(key)
356}
357
358pub fn generate_encryption_key() -> [u8; 32] {
360 rand::random()
361}
362
363#[cfg(test)]
364mod tests {
365 use super::*;
366 use crate::types::KeyShareMetadata;
367 use k256::Scalar;
368 use std::collections::HashMap;
369
370 fn create_test_share() -> AgentKeyShare {
371 AgentKeyShare {
372 party_id: 0,
373 role: PartyRole::Agent,
374 secret_share: Scalar::ONE,
375 public_key: vec![0x02; 33],
376 public_shares: vec![vec![0x02; 33]; 3],
377 chain_code: [0u8; 32],
378 metadata: KeyShareMetadata {
379 share_id: "test".to_string(),
380 role: PartyRole::Agent,
381 created_at: 0,
382 last_refreshed_at: None,
383 addresses: HashMap::new(),
384 label: None,
385 },
386 }
387 }
388
389 #[test]
390 fn test_encrypt_decrypt() {
391 let share = create_test_share();
392 let key = generate_encryption_key();
393
394 let encrypted = EncryptedKeyShare::encrypt(&share, &key).unwrap();
395 assert!(!encrypted.ciphertext.is_empty());
396 assert_eq!(encrypted.role, PartyRole::Agent);
397
398 let decrypted = encrypted.decrypt(&key).unwrap();
399 assert_eq!(decrypted.party_id, share.party_id);
400 assert_eq!(decrypted.role, share.role);
401 }
402
403 #[test]
404 fn test_decrypt_wrong_key() {
405 let share = create_test_share();
406 let key1 = generate_encryption_key();
407 let key2 = generate_encryption_key();
408
409 let encrypted = EncryptedKeyShare::encrypt(&share, &key1).unwrap();
410 let result = encrypted.decrypt(&key2);
411 assert!(result.is_err());
412 }
413
414 #[tokio::test]
415 async fn test_memory_store() {
416 let store = EncryptedMemoryStore::new();
417 let share = create_test_share();
418 let key = generate_encryption_key();
419
420 let encrypted = EncryptedKeyShare::encrypt(&share, &key).unwrap();
421
422 store.store("test-id", &encrypted).await.unwrap();
424
425 assert!(store.exists("test-id").await.unwrap());
427 assert!(!store.exists("nonexistent").await.unwrap());
428
429 let loaded = store.load("test-id").await.unwrap();
431 assert_eq!(loaded.role, encrypted.role);
432
433 let list = store.list().await.unwrap();
435 assert_eq!(list, vec!["test-id"]);
436
437 let metadata = store.get_metadata("test-id").await.unwrap();
439 assert_eq!(metadata.role, PartyRole::Agent);
440
441 store.delete("test-id").await.unwrap();
443 assert!(!store.exists("test-id").await.unwrap());
444 }
445
446 #[test]
447 fn test_derive_key_from_password() {
448 let password = "test-password";
449 let salt: [u8; 32] = rand::random();
450
451 let key1 = derive_key_from_password(password, &salt).unwrap();
452 let key2 = derive_key_from_password(password, &salt).unwrap();
453
454 assert_eq!(key1, key2);
456
457 let key3 = derive_key_from_password("different", &salt).unwrap();
459 assert_ne!(key1, key3);
460 }
461
462 #[tokio::test]
463 async fn test_file_system_store() {
464 let temp_dir = std::env::temp_dir().join(format!("mpc-test-{}", rand::random::<u64>()));
465 let store = FileSystemStore::new(&temp_dir).unwrap();
466 let share = create_test_share();
467 let key = generate_encryption_key();
468
469 let encrypted = EncryptedKeyShare::encrypt(&share, &key).unwrap();
470
471 store.store("test-file", &encrypted).await.unwrap();
473
474 let loaded = store.load("test-file").await.unwrap();
476 assert_eq!(loaded.role, encrypted.role);
477
478 let list = store.list().await.unwrap();
480 assert!(list.contains(&"test-file".to_string()));
481
482 store.delete("test-file").await.unwrap();
484 std::fs::remove_dir_all(&temp_dir).ok();
485 }
486}