host_sso/traits.rs
1use crate::product_key_cache::ProductKeyCacheEntry;
2use crate::state::SsoState;
3
4/// Transport adapter: topic-keyed publish/subscribe.
5///
6/// Hosts implement this to connect the SSO manager to a Statement Store
7/// or equivalent message bus (e.g. test stub, WebSocket relay).
8pub trait SsoTransport: Send + Sync {
9 /// Subscribe to messages on `topic_hex`.
10 ///
11 /// Returns `(subscription_id, receiver)` where each message is `(key, value)`.
12 /// The subscription lifetime is tied to the returned ID; call `unsubscribe` to cancel.
13 fn subscribe(
14 &self,
15 topic_hex: &str,
16 ) -> Result<(u64, std::sync::mpsc::Receiver<(String, String)>), String>;
17
18 /// Cancel a subscription by its ID.
19 fn unsubscribe(&self, id: u64);
20
21 /// Publish `value` to `topic_hex`.
22 fn write(&self, topic_hex: &str, value: &str) -> Result<(), String>;
23}
24
25/// Re-export of [`host_wallet::HostSigner`] — the unified signing trait.
26///
27/// SSO callers should provide an `SsoPathSigner` (from `host-wallet`) or
28/// any other `HostSigner` implementation that derives at `//wallet//sso`.
29pub use host_wallet::HostSigner as SsoSigner;
30
31/// Persistence adapter: session metadata storage.
32///
33/// Hosts implement this to persist a paired session across restarts (e.g.
34/// platform keychain, encrypted local file, in-memory for tests).
35pub trait SsoSessionStore: Send + Sync {
36 /// Persist `session` metadata, overwriting any existing entry.
37 fn save(&self, session: &PersistedSessionMeta) -> Result<(), String>;
38
39 /// Load the previously persisted session, or `None` if absent.
40 fn load(&self) -> Result<Option<PersistedSessionMeta>, String>;
41
42 /// Remove any persisted session.
43 fn clear(&self) -> Result<(), String>;
44}
45
46/// Event-sink adapter: push state changes to the UI layer.
47///
48/// Hosts implement this to forward `SsoState` updates to the UI (e.g. via a
49/// channel, UniFFI callback, or test recorder).
50pub trait SsoEventSink: Send + Sync {
51 /// Called whenever the manager transitions to a new `SsoState`.
52 fn on_state_changed(&self, state: &SsoState);
53
54 /// Called when the phone revokes a product key authorization.
55 ///
56 /// The default implementation is a no-op so existing `SsoEventSink`
57 /// implementors do not need to be updated.
58 fn on_product_key_revoked(&self, _product_id: &str, _index: u32) {}
59}
60
61/// The return type of [`SsoProductKeyStore::load`].
62///
63/// Contains the bound identity and its cached key entries, or `None` if the
64/// store is empty.
65pub type ProductKeyStoreLoadResult = Result<Option<([u8; 32], Vec<ProductKeyCacheEntry>)>, String>;
66
67/// Persistence adapter for the product key cache.
68///
69/// Separate from [`SsoSessionStore`] — the cache has its own keychain entry.
70/// Hosts implement this to persist the product key cache across restarts
71/// (e.g. platform keychain, encrypted local file, in-memory for tests).
72pub trait SsoProductKeyStore: Send + Sync {
73 /// Persist `entries` bound to `identity`. Overwrites any existing entry.
74 fn save(&self, identity: &[u8; 32], entries: &[ProductKeyCacheEntry]) -> Result<(), String>;
75
76 /// Load the previously persisted cache, or `None` if absent.
77 ///
78 /// Returns `(identity, entries)` so callers can detect staleness.
79 fn load(&self) -> ProductKeyStoreLoadResult;
80
81 /// Remove any persisted cache entry.
82 fn delete(&self) -> Result<(), String>;
83}
84
85/// No-op product key store for tests and WASM targets.
86#[derive(Debug, Default)]
87pub struct NoopProductKeyStore;
88
89impl SsoProductKeyStore for NoopProductKeyStore {
90 fn save(&self, _: &[u8; 32], _: &[ProductKeyCacheEntry]) -> Result<(), String> {
91 Ok(())
92 }
93
94 fn load(&self) -> ProductKeyStoreLoadResult {
95 Ok(None)
96 }
97
98 fn delete(&self) -> Result<(), String> {
99 Ok(())
100 }
101}
102
103/// Minimal session metadata that survives across process restarts.
104///
105/// The AES session key is NOT persisted here — it must be re-derived during
106/// the next pairing or kept in a platform-native secure enclave by the host.
107#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
108pub struct PersistedSessionMeta {
109 /// Opaque session identifier shared with the mobile wallet.
110 pub session_id: String,
111 /// SS58 address of the paired mobile account.
112 pub address: String,
113 /// Human-readable name shown during pairing (e.g. device name).
114 pub display_name: String,
115 /// Hex-encoded uncompressed P-256 public key (65 bytes, 0x04-prefixed).
116 pub p256_pubkey_hex: String,
117 /// Hex-encoded 32-byte sr25519 public key of the phone's wallet account.
118 ///
119 /// `None` for sessions established with older phones that do not send their
120 /// wallet pubkey during the pairing handshake.
121 #[serde(default)]
122 pub phone_wallet_pubkey_hex: Option<String>,
123 /// Capability tokens advertised by the phone during pairing.
124 ///
125 /// Currently defined token: `"product_key"` — phone supports
126 /// `ProductKeyRequest`/`ProductKeyResponse` over the encrypted channel.
127 #[serde(default)]
128 pub capabilities: Vec<String>,
129}