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