use alloc::{format, string::String, vec::Vec};
use zeroize::Zeroizing;
use crate::{
error::{Error, Result},
keygen::{ClientKeyShare, ServerKeyShare},
params::Params,
};
pub trait SecretStore {
fn put(&self, key: &str, value: &[u8]) -> Result<()>;
fn get(&self, key: &str) -> Result<Option<Zeroizing<Vec<u8>>>>;
fn delete(&self, key: &str) -> Result<()>;
}
fn client_key(wallet_id: &str) -> String {
format!("tecdsa/client-share/{wallet_id}")
}
fn server_key(wallet_id: &str) -> String {
format!("tecdsa/server-share/{wallet_id}")
}
fn put_new(store: &dyn SecretStore, key: &str, value: &[u8]) -> Result<()> {
if store.get(key)?.is_some() {
return Err(Error::AlreadyExists);
}
store.put(key, value)
}
impl ClientKeyShare {
pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
put_new(store, &client_key(wallet_id), &self.export_secret())
}
pub fn replace(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
store.put(&client_key(wallet_id), &self.export_secret())
}
pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
let bytes = store.get(&client_key(wallet_id))?.ok_or(Error::NotFound)?;
Self::import_secret(&bytes, params)
}
}
impl ServerKeyShare {
pub fn save(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
put_new(store, &server_key(wallet_id), &self.export_secret())
}
pub fn replace(&self, store: &dyn SecretStore, wallet_id: &str) -> Result<()> {
store.put(&server_key(wallet_id), &self.export_secret())
}
pub fn load(store: &dyn SecretStore, wallet_id: &str, params: &Params) -> Result<Self> {
let bytes = store.get(&server_key(wallet_id))?.ok_or(Error::NotFound)?;
Self::import_secret(&bytes, params)
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use crate::testutil::{keygen, params};
use core::cell::RefCell;
use std::collections::BTreeMap;
#[derive(Default)]
struct MemStore(RefCell<BTreeMap<String, Vec<u8>>>);
impl SecretStore for MemStore {
fn put(&self, key: &str, value: &[u8]) -> Result<()> {
self.0.borrow_mut().insert(key.into(), value.to_vec());
Ok(())
}
fn get(&self, key: &str) -> Result<Option<Zeroizing<Vec<u8>>>> {
Ok(self.0.borrow().get(key).map(|v| Zeroizing::new(v.clone())))
}
fn delete(&self, key: &str) -> Result<()> {
self.0.borrow_mut().remove(key);
Ok(())
}
}
#[test]
fn shares_roundtrip_through_store() {
let (c, s) = keygen(41);
let store = MemStore::default();
c.save(&store, "w1").unwrap();
s.save(&store, "w1").unwrap();
let c2 = ClientKeyShare::load(&store, "w1", ¶ms()).unwrap();
let s2 = ServerKeyShare::load(&store, "w1", ¶ms()).unwrap();
assert_eq!(c2.key_id(), c.key_id());
assert_eq!(s2.key_id(), s.key_id());
assert_eq!(
ClientKeyShare::load(&store, "missing", ¶ms()).unwrap_err(),
Error::NotFound
);
store.delete(&client_key("w1")).unwrap();
assert!(ClientKeyShare::load(&store, "w1", ¶ms()).is_err());
}
#[test]
fn save_never_overwrites() {
let (c, s) = keygen(41);
let (c_other, s_other) = keygen(42);
let store = MemStore::default();
c.save(&store, "w1").unwrap();
s.save(&store, "w1").unwrap();
assert_eq!(
c_other.save(&store, "w1").unwrap_err(),
Error::AlreadyExists
);
assert_eq!(
s_other.save(&store, "w1").unwrap_err(),
Error::AlreadyExists
);
let kept = ClientKeyShare::load(&store, "w1", ¶ms()).unwrap();
assert_eq!(kept.key_id(), c.key_id());
c_other.replace(&store, "w1").unwrap();
s_other.replace(&store, "w1").unwrap();
let now = ClientKeyShare::load(&store, "w1", ¶ms()).unwrap();
assert_eq!(now.key_id(), c_other.key_id());
}
}