Skip to main content

walletkit_core/storage/
traits.rs

1//! Platform interfaces for credential storage.
2//!
3//! These traits are the platform integration boundary. The host selects the storage
4//! root and provides a [`DeviceKeystore`] and [`AtomicBlobStore`]; core storage code
5//! is root-agnostic and consumes a provider-supplied [`StoragePaths`].
6//!
7//! # Expected platform components
8//!
9//! - **iOS (Swift):** [`DeviceKeystore`] backed by Keychain / Secure Enclave;
10//!   [`AtomicBlobStore`] over the app container filesystem (atomic replace).
11//! - **Android (Kotlin):** [`DeviceKeystore`] backed by the Android Keystore;
12//!   [`AtomicBlobStore`] over app internal storage (atomic replace).
13//! - **Node.js:** file-backed [`DeviceKeystore`] (development; production can use an
14//!   OS keystore); [`AtomicBlobStore`] over app internal storage.
15//! - **Browser (WASM):** `WebCrypto`-backed [`DeviceKeystore`]; [`AtomicBlobStore`]
16//!   over an origin-private storage namespace.
17
18use std::sync::Arc;
19
20use super::error::StorageResult;
21use super::paths::StoragePaths;
22
23/// Device keystore interface used to seal and open account keys.
24#[uniffi::export(with_foreign)]
25pub trait DeviceKeystore: Send + Sync {
26    /// Seals plaintext under the device-bound key, authenticating `associated_data`.
27    ///
28    /// The associated data is not encrypted, but it is integrity-protected as part
29    /// of the seal operation. Any mismatch when opening must fail.
30    ///
31    /// # Errors
32    ///
33    /// Returns an error if the keystore refuses the operation or the seal fails.
34    fn seal(
35        &self,
36        associated_data: Vec<u8>,
37        plaintext: Vec<u8>,
38    ) -> StorageResult<Vec<u8>>;
39
40    /// Opens ciphertext under the device-bound key, verifying `associated_data`.
41    ///
42    /// The same associated data used during sealing must be supplied or the open
43    /// operation must fail.
44    ///
45    /// # Errors
46    ///
47    /// Returns an error if authentication fails or the keystore cannot open.
48    fn open_sealed(
49        &self,
50        associated_data: Vec<u8>,
51        ciphertext: Vec<u8>,
52    ) -> StorageResult<Vec<u8>>;
53}
54
55/// Atomic blob store for small binary files (e.g., `account_keys.bin`).
56#[uniffi::export(with_foreign)]
57pub trait AtomicBlobStore: Send + Sync {
58    /// Reads the blob at `path`, if present.
59    ///
60    /// # Errors
61    ///
62    /// Returns an error if the read fails.
63    fn read(&self, path: String) -> StorageResult<Option<Vec<u8>>>;
64
65    /// Writes bytes atomically to `path`.
66    ///
67    /// # Errors
68    ///
69    /// Returns an error if the write fails.
70    fn write_atomic(&self, path: String, bytes: Vec<u8>) -> StorageResult<()>;
71
72    /// Deletes the blob at `path`.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if the delete fails.
77    fn delete(&self, path: String) -> StorageResult<()>;
78}
79
80/// Provider responsible for platform-specific storage components and paths.
81#[uniffi::export(with_foreign)]
82pub trait StorageProvider: Send + Sync {
83    /// Returns the device keystore implementation.
84    fn keystore(&self) -> Arc<dyn DeviceKeystore>;
85
86    /// Returns the blob store implementation.
87    fn blob_store(&self) -> Arc<dyn AtomicBlobStore>;
88
89    /// Returns the storage paths selected by the platform.
90    fn paths(&self) -> Arc<StoragePaths>;
91}
92
93/// Listener notified when the credential vault contents change and a new
94/// backup is needed.
95///
96/// Register via [`super::CredentialStore::set_vault_changed_listener`]. The
97/// callback is delivered on a dedicated background thread to avoid re-entering
98/// the `UniFFI` call stack (see `logger.rs` for rationale).
99///
100/// This is only called when individual credentials are added or removed.
101///
102/// # Expected usage
103///
104/// The host app should treat this as a trigger to schedule a backup of the
105/// vault. It should contain synchronous actions only.
106///
107/// # Safety
108///
109/// **Warning:** implementors **must not** call back into
110/// [`super::CredentialStore`] from
111/// [`on_vault_changed`](VaultChangedListener::on_vault_changed) — doing so
112/// will deadlock.
113#[cfg_attr(not(target_arch = "wasm32"), uniffi::export(with_foreign))]
114pub trait VaultChangedListener: Send + Sync {
115    /// Called after a credential is added or removed.
116    fn on_vault_changed(&self);
117}