Skip to main content

core_crypto/proteus/
mod.rs

1mod conversation_session;
2mod core_crypto;
3mod message;
4mod prekey;
5mod session;
6mod session_cache;
7
8use std::sync::Arc;
9
10pub use conversation_session::{ProteusConversationSession, SessionIdentifier};
11use core_crypto_keystore::{Database, entities::ProteusIdentity, traits::FetchFromDatabase as _};
12use proteus_wasm::keys::IdentityKeyPair;
13pub(crate) use session_cache::ProteusSessionCache;
14
15use crate::{KeystoreError, ProteusError, Result};
16
17/// Proteus counterpart of [crate::mls::session::Session]
18///
19/// The big difference is that [ProteusCentral] doesn't *own* its own keystore but must borrow it from the outside.
20/// Whether it's exclusively for this struct's purposes or it's shared with our main struct,
21/// [crate::mls::session::Session]
22#[derive(Debug)]
23pub struct ProteusCentral {
24    proteus_identity: Arc<IdentityKeyPair>,
25    proteus_sessions: ProteusSessionCache,
26}
27
28impl ProteusCentral {
29    /// Initializes the [ProteusCentral]
30    pub async fn try_new(keystore: &Database) -> Result<Self> {
31        let proteus_identity: Arc<IdentityKeyPair> = Arc::new(Self::load_or_create_identity(keystore).await?);
32        let proteus_sessions = ProteusSessionCache::new(proteus_identity.clone());
33
34        Ok(Self {
35            proteus_identity,
36            proteus_sessions,
37        })
38    }
39
40    /// This function will try to load a proteus Identity from our keystore; If it cannot, it will create a new one
41    /// This means this function doesn't fail except in cases of deeper errors (such as in the Keystore and other crypto
42    /// errors)
43    async fn load_or_create_identity(keystore: &Database) -> Result<IdentityKeyPair> {
44        let Some(identity) = keystore
45            .get_unique::<ProteusIdentity>()
46            .await
47            .map_err(KeystoreError::wrap("finding proteus identity"))?
48        else {
49            return Self::create_identity(keystore).await;
50        };
51
52        let sk = identity.sk_raw();
53        let pk = identity.pk_raw();
54
55        // SAFETY: Byte lengths are ensured at the keystore level so this function is safe to call, despite being cursed
56        IdentityKeyPair::from_raw_key_pair(*sk, *pk)
57            .map_err(ProteusError::wrap("constructing identity keypair"))
58            .map_err(Into::into)
59    }
60
61    /// Internal function to create and save a new Proteus Identity
62    async fn create_identity(keystore: &Database) -> Result<IdentityKeyPair> {
63        let kp = IdentityKeyPair::new();
64        let pk = kp.public_key.public_key.as_slice().to_vec();
65
66        let ks_identity = ProteusIdentity {
67            sk: kp.secret_key.to_keypair_bytes().into(),
68            pk,
69        };
70        keystore
71            .save(ks_identity)
72            .await
73            .map_err(KeystoreError::wrap("saving new proteus identity"))?;
74
75        Ok(kp)
76    }
77
78    /// Proteus identity keypair
79    pub fn identity(&self) -> &IdentityKeyPair {
80        self.proteus_identity.as_ref()
81    }
82
83    /// Proteus Public key hex-encoded fingerprint
84    pub fn fingerprint(&self) -> String {
85        self.proteus_identity.as_ref().public_key.fingerprint()
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use core_crypto_keystore::{ConnectionType, DatabaseKey};
92
93    use super::*;
94    use crate::test_utils::*;
95
96    #[macro_rules_attribute::apply(smol_macros::test)]
97    async fn can_init() {
98        #[cfg(not(target_os = "unknown"))]
99        let (path, db_file) = tmp_db_file();
100        #[cfg(target_os = "unknown")]
101        let (path, _) = tmp_db_file();
102        let key = DatabaseKey::generate();
103        let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
104            .await
105            .unwrap();
106        keystore.new_transaction().await.unwrap();
107        let central = ProteusCentral::try_new(&keystore).await.unwrap();
108        let identity = (*central.proteus_identity).clone();
109        keystore.commit_transaction().await.unwrap();
110
111        let keystore = core_crypto_keystore::Database::open(ConnectionType::Persistent(&path), &key)
112            .await
113            .unwrap();
114        keystore.new_transaction().await.unwrap();
115        let central = ProteusCentral::try_new(&keystore).await.unwrap();
116        keystore.commit_transaction().await.unwrap();
117        assert_eq!(identity, *central.proteus_identity);
118
119        keystore.wipe().await.unwrap();
120        #[cfg(not(target_os = "unknown"))]
121        drop(db_file);
122    }
123}