dig_keystore/backend/mod.rs
1//! Storage backend abstraction.
2//!
3//! A `KeychainBackend` is any byte-blob KV store. The shipped `FileBackend`
4//! persists to the local filesystem with atomic (tmp + rename) writes. Planned
5//! future backends: `OsKeyringBackend` (macOS Keychain / Windows Credential
6//! Store / Secret Service), and hardware-signer backends (`LedgerBackend`,
7//! `YubiHsmBackend`) that proxy `sign` to an external device.
8
9use crate::error::Result;
10
11#[cfg(feature = "file-backend")]
12mod file;
13mod memory;
14
15#[cfg(feature = "file-backend")]
16pub use file::FileBackend;
17/// In-memory backend — always available. Originally feature-gated, now
18/// unconditional because production adapters (e.g., `dig-l1-wallet`'s
19/// encrypt/decrypt-bytes helpers) wrap it in scratch backends to reuse the
20/// full keystore format without touching the filesystem.
21pub use memory::MemoryBackend;
22
23/// An opaque key identifying a single encrypted blob within a backend.
24///
25/// For `FileBackend`, the key maps to `<root>/<key>.dks`; for an OS-keyring
26/// backend it maps to a service / account pair; for a hardware signer it maps
27/// to a slot identifier.
28#[derive(Clone, Debug, PartialEq, Eq, Hash)]
29pub struct BackendKey(pub String);
30
31impl BackendKey {
32 /// Construct from any string-like value.
33 pub fn new(name: impl Into<String>) -> Self {
34 Self(name.into())
35 }
36
37 /// Borrow as `&str`.
38 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41}
42
43impl<T: Into<String>> From<T> for BackendKey {
44 fn from(v: T) -> Self {
45 Self(v.into())
46 }
47}
48
49impl std::fmt::Display for BackendKey {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.write_str(&self.0)
52 }
53}
54
55/// Storage backend trait. Implementations must be `Send + Sync + 'static` so
56/// they can be held behind `Arc<dyn KeychainBackend>`.
57pub trait KeychainBackend: Send + Sync + 'static {
58 /// Read the full contents of the blob at `key`.
59 ///
60 /// Returns a backend I/O error if the blob does not exist.
61 fn read(&self, key: &BackendKey) -> Result<Vec<u8>>;
62
63 /// Write `data` to `key`. Implementations should be atomic — a reader
64 /// seeing the key after this call must see either the old bytes or the
65 /// new bytes in full, never a torn mix.
66 fn write(&self, key: &BackendKey, data: &[u8]) -> Result<()>;
67
68 /// Remove the blob at `key`. Implementations should best-effort overwrite
69 /// the storage before removing so residual disk sectors do not retain the
70 /// ciphertext.
71 fn delete(&self, key: &BackendKey) -> Result<()>;
72
73 /// List keys that start with `prefix`. Order is unspecified.
74 fn list(&self, prefix: &str) -> Result<Vec<BackendKey>>;
75
76 /// Whether a blob exists at `key`. Default impl delegates to `read`;
77 /// backends with cheaper existence checks should override.
78 fn exists(&self, key: &BackendKey) -> Result<bool> {
79 match self.read(key) {
80 Ok(_) => Ok(true),
81 Err(crate::error::KeystoreError::Backend(e))
82 if e.kind() == std::io::ErrorKind::NotFound =>
83 {
84 Ok(false)
85 }
86 Err(e) => Err(e),
87 }
88 }
89}