Skip to main content

tecdsa_core/
store.rs

1//! Persistence of secrets is the application's job (Keychain / Secure Enclave / HSM); the
2//! crate only describes what it needs (spec §1, §8.4).
3use alloc::{format, string::String, vec::Vec};
4use zeroize::Zeroizing;
5
6use crate::{
7    error::{Error, Result},
8    keygen::{ClientKeyShare, ServerKeyShare},
9    params::Params,
10};
11
12/// Key-value store for secret blobs. Implementations must keep values confidential
13/// (e.g. iOS Keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`).
14pub trait SecretStore {
15    fn put(&self, key: &str, value: &[u8]) -> Result<()>;
16    fn get(&self, key: &str) -> Result<Option<Zeroizing<Vec<u8>>>>;
17    fn delete(&self, key: &str) -> Result<()>;
18}
19
20fn client_key(wallet_id: &str) -> String {
21    format!("tecdsa/client-share/{wallet_id}")
22}
23
24fn server_key(wallet_id: &str) -> String {
25    format!("tecdsa/server-share/{wallet_id}")
26}
27
28impl ClientKeyShare {
29    /// Writes the share through `store` (explicit `export_secret`, spec §8.2).
30    pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
31        store.put(&client_key(wallet_id), &self.export_secret())
32    }
33
34    pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
35        let bytes = store.get(&client_key(wallet_id))?.ok_or(Error::NotFound)?;
36        Self::import_secret(&bytes, params)
37    }
38}
39
40impl ServerKeyShare {
41    pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
42        store.put(&server_key(wallet_id), &self.export_secret())
43    }
44
45    pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
46        let bytes = store.get(&server_key(wallet_id))?.ok_or(Error::NotFound)?;
47        Self::import_secret(&bytes, params)
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    extern crate std;
54    use super::*;
55    use crate::testutil::{keygen, params};
56    use core::cell::RefCell;
57    use std::collections::BTreeMap;
58
59    #[derive(Default)]
60    struct MemStore(RefCell<BTreeMap<String, Vec<u8>>>);
61
62    impl SecretStore for MemStore {
63        fn put(&self, key: &str, value: &[u8]) -> Result<()> {
64            self.0.borrow_mut().insert(key.into(), value.to_vec());
65            Ok(())
66        }
67        fn get(&self, key: &str) -> Result<Option<Zeroizing<Vec<u8>>>> {
68            Ok(self.0.borrow().get(key).map(|v| Zeroizing::new(v.clone())))
69        }
70        fn delete(&self, key: &str) -> Result<()> {
71            self.0.borrow_mut().remove(key);
72            Ok(())
73        }
74    }
75
76    #[test]
77    fn shares_roundtrip_through_store() {
78        let (c, s) = keygen(41);
79        let store = MemStore::default();
80        c.save(&store, "w1").unwrap();
81        s.save(&store, "w1").unwrap();
82        let c2 = ClientKeyShare::load(&store, "w1", &params()).unwrap();
83        let s2 = ServerKeyShare::load(&store, "w1", &params()).unwrap();
84        assert_eq!(c2.key_id(), c.key_id());
85        assert_eq!(s2.key_id(), s.key_id());
86        assert_eq!(
87            ClientKeyShare::load(&store, "missing", &params()).unwrap_err(),
88            Error::NotFound
89        );
90        store.delete(&client_key("w1")).unwrap();
91        assert!(ClientKeyShare::load(&store, "w1", &params()).is_err());
92    }
93}