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
28/// Writes a new secret; an existing one is never replaced (a malicious peer could otherwise
29/// destroy a share by restarting key generation under a known wallet id).
30fn put_new(store: &dyn SecretStore, key: &str, value: &[u8]) -> Result<()> {
31    if store.get(key)?.is_some() {
32        return Err(Error::AlreadyExists);
33    }
34    store.put(key, value)
35}
36
37impl ClientKeyShare {
38    /// Writes the share through `store` (explicit `export_secret`, spec §8.2).
39    /// Fails with `AlreadyExists` if the wallet already has a client share.
40    pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
41        put_new(store, &client_key(wallet_id), &self.export_secret())
42    }
43
44    /// Like `save`, but replaces an existing share. Only for a deliberate restore.
45    pub fn replace(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
46        store.put(&client_key(wallet_id), &self.export_secret())
47    }
48
49    pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
50        let bytes = store.get(&client_key(wallet_id))?.ok_or(Error::NotFound)?;
51        Self::import_secret(&bytes, params)
52    }
53}
54
55impl ServerKeyShare {
56    /// Fails with `AlreadyExists` if the wallet already has a server share.
57    pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
58        put_new(store, &server_key(wallet_id), &self.export_secret())
59    }
60
61    /// Like `save`, but replaces an existing share. Only for a deliberate restore.
62    pub fn replace(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
63        store.put(&server_key(wallet_id), &self.export_secret())
64    }
65
66    pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
67        let bytes = store.get(&server_key(wallet_id))?.ok_or(Error::NotFound)?;
68        Self::import_secret(&bytes, params)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    extern crate std;
75    use super::*;
76    use crate::testutil::{keygen, params};
77    use core::cell::RefCell;
78    use std::collections::BTreeMap;
79
80    #[derive(Default)]
81    struct MemStore(RefCell<BTreeMap<String, Vec<u8>>>);
82
83    impl SecretStore for MemStore {
84        fn put(&self, key: &str, value: &[u8]) -> Result<()> {
85            self.0.borrow_mut().insert(key.into(), value.to_vec());
86            Ok(())
87        }
88        fn get(&self, key: &str) -> Result<Option<Zeroizing<Vec<u8>>>> {
89            Ok(self.0.borrow().get(key).map(|v| Zeroizing::new(v.clone())))
90        }
91        fn delete(&self, key: &str) -> Result<()> {
92            self.0.borrow_mut().remove(key);
93            Ok(())
94        }
95    }
96
97    #[test]
98    fn shares_roundtrip_through_store() {
99        let (c, s) = keygen(41);
100        let store = MemStore::default();
101        c.save(&store, "w1").unwrap();
102        s.save(&store, "w1").unwrap();
103        let c2 = ClientKeyShare::load(&store, "w1", &params()).unwrap();
104        let s2 = ServerKeyShare::load(&store, "w1", &params()).unwrap();
105        assert_eq!(c2.key_id(), c.key_id());
106        assert_eq!(s2.key_id(), s.key_id());
107        assert_eq!(
108            ClientKeyShare::load(&store, "missing", &params()).unwrap_err(),
109            Error::NotFound
110        );
111        store.delete(&client_key("w1")).unwrap();
112        assert!(ClientKeyShare::load(&store, "w1", &params()).is_err());
113    }
114
115    #[test]
116    fn save_never_overwrites() {
117        let (c, s) = keygen(41);
118        let (c_other, s_other) = keygen(42);
119        let store = MemStore::default();
120        c.save(&store, "w1").unwrap();
121        s.save(&store, "w1").unwrap();
122        assert_eq!(
123            c_other.save(&store, "w1").unwrap_err(),
124            Error::AlreadyExists
125        );
126        assert_eq!(
127            s_other.save(&store, "w1").unwrap_err(),
128            Error::AlreadyExists
129        );
130        let kept = ClientKeyShare::load(&store, "w1", &params()).unwrap();
131        assert_eq!(kept.key_id(), c.key_id());
132        c_other.replace(&store, "w1").unwrap();
133        s_other.replace(&store, "w1").unwrap();
134        let now = ClientKeyShare::load(&store, "w1", &params()).unwrap();
135        assert_eq!(now.key_id(), c_other.key_id());
136    }
137}