From a610117e814c19ea1473dfa88e7ce7b8f758a930 Mon Sep 17 00:00:00 2001 From: Jazz Turner-Baggs <473256+jazzz@users.noreply.github.com> Date: Wed, 10 Jun 2026 06:59:04 -0700 Subject: [PATCH] Update Context to accept External Identity Provider. (#127) * rename .account_id() to .id() * Create logos-traits crate * Remove AccountId references * external IdentityProvider for Context * Fix compile errors from merge * Update logos-traits to shared-traits * format fixes * warnings cleanup * clippy fix * Remove rebase artifact --- Cargo.lock | 11 ++++ Cargo.toml | 4 ++ core/account/Cargo.toml | 1 + core/account/src/account.rs | 9 +-- core/conversations/Cargo.toml | 1 + core/conversations/src/account.rs | 63 ------------------- core/conversations/src/conversation.rs | 7 +-- .../src/conversation/group_v1.rs | 9 +-- core/conversations/src/core.rs | 47 ++++++-------- core/conversations/src/inbox/handler.rs | 46 +++++++++++++- core/conversations/src/inbox_v2.rs | 27 ++++---- core/conversations/src/inbox_v2/identity.rs | 9 +-- .../src/inbox_v2/mls_provider.rs | 9 +-- core/conversations/src/lib.rs | 7 +-- core/conversations/src/service_context.rs | 17 ++--- core/conversations/src/service_traits.rs | 16 +---- core/conversations/src/types.rs | 30 +-------- core/conversations/src/utils.rs | 7 +-- core/integration_tests_core/Cargo.toml | 3 +- .../tests/causal_history.rs | 36 ++++++++--- .../tests/mls_integration.rs | 44 +++++++++---- .../tests/private_integration.rs | 53 ++++++++++++---- core/shared-traits/Cargo.toml | 8 +++ core/shared-traits/src/lib.rs | 39 ++++++++++++ crates/client/Cargo.toml | 1 + crates/client/src/client.rs | 13 ++-- extensions/components/src/contact_registry.rs | 2 +- 27 files changed, 297 insertions(+), 222 deletions(-) delete mode 100644 core/conversations/src/account.rs create mode 100644 core/shared-traits/Cargo.toml create mode 100644 core/shared-traits/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index b358303..f9a1e07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1660,6 +1660,7 @@ dependencies = [ "chat-sqlite", "components", "libchat", + "logos-account", "storage", "tempfile", ] @@ -1787,6 +1788,7 @@ dependencies = [ "prost", "rand_core 0.6.4", "safer-ffi", + "shared-traits", "storage", "tempfile", "thiserror", @@ -2113,6 +2115,7 @@ version = "0.1.0" dependencies = [ "crypto", "libchat", + "shared-traits", ] [[package]] @@ -2122,6 +2125,7 @@ dependencies = [ "chat-sqlite", "components", "libchat", + "logos-account", "tempfile", "thiserror", ] @@ -3381,6 +3385,13 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared-traits" +version = "0.1.0" +dependencies = [ + "crypto", +] + [[package]] name = "shlex" version = "1.3.0" diff --git a/Cargo.toml b/Cargo.toml index 2b8fbd4..f19224c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "core/crypto", "core/double-ratchets", "core/integration_tests_core", + "core/shared-traits", "core/sqlite", "core/storage", "crates/client-ffi", @@ -23,6 +24,7 @@ default-members = [ "core/crypto", "core/double-ratchets", "core/integration_tests_core", + "core/shared-traits", "core/sqlite", "core/storage", "crates/client-ffi", @@ -31,11 +33,13 @@ default-members = [ [workspace.dependencies] # Internal Workspace dependency declarations (sorted) +logos-account = { path = "core/account" } chat-sqlite = { path = "core/sqlite" } components = { path = "extensions/components" } crypto = { path = "core/crypto" } libchat = { path = "core/conversations" } logos-chat = { path = "crates/client" } +shared-traits = { path = "core/shared-traits" } storage = { path = "core/storage" } # External Workspace dependency declarations (sorted) diff --git a/core/account/Cargo.toml b/core/account/Cargo.toml index 5162bf2..dbf71a9 100644 --- a/core/account/Cargo.toml +++ b/core/account/Cargo.toml @@ -10,5 +10,6 @@ dev = [] # Workspace dependencies (sorted) crypto = { workspace = true } libchat = { workspace = true } +shared-traits = { workspace = true } # External dependencies (sorted) diff --git a/core/account/src/account.rs b/core/account/src/account.rs index 016d9bc..db2959b 100644 --- a/core/account/src/account.rs +++ b/core/account/src/account.rs @@ -1,12 +1,13 @@ use crypto::{Ed25519SigningKey, Ed25519VerifyingKey}; +use shared_traits::{IdentId, IdentIdRef}; -use libchat::{AccountId, IdentityProvider}; +use libchat::IdentityProvider; /// A Test Focused LogosAccount using a pre-defined identifier. /// The test account is not persisted, and uses a single user provided id. /// This account type should not be used in a production system. pub struct TestLogosAccount { - id: AccountId, + id: IdentId, signing_key: Ed25519SigningKey, verifying_key: Ed25519VerifyingKey, } @@ -16,7 +17,7 @@ impl TestLogosAccount { let signing_key = Ed25519SigningKey::generate(); let verifying_key = signing_key.verifying_key(); Self { - id: AccountId::new(explicit_id.into()), + id: IdentId::new(explicit_id.into()), signing_key, verifying_key, } @@ -24,7 +25,7 @@ impl TestLogosAccount { } impl IdentityProvider for TestLogosAccount { - fn account_id(&self) -> &AccountId { + fn id(&self) -> IdentIdRef<'_> { &self.id } diff --git a/core/conversations/Cargo.toml b/core/conversations/Cargo.toml index b699369..7e681ac 100644 --- a/core/conversations/Cargo.toml +++ b/core/conversations/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["rlib","staticlib"] blake2 = { workspace = true } chat-sqlite = { workspace = true } crypto = { workspace = true } +shared-traits = { workspace = true } storage = { workspace = true } # External dependencies (sorted) diff --git a/core/conversations/src/account.rs b/core/conversations/src/account.rs deleted file mode 100644 index 676925d..0000000 --- a/core/conversations/src/account.rs +++ /dev/null @@ -1,63 +0,0 @@ -use crypto::{Ed25519Signature, Ed25519SigningKey, Ed25519VerifyingKey}; -use openmls::prelude::SignatureScheme; -use openmls_traits::signatures::Signer; - -use crate::{AccountId, IdentityProvider}; - -/// Logos Account represents a single account across -/// multiple installations and services. -/// -/// Deprecated! -pub struct LogosAccount { - id: AccountId, - signing_key: Ed25519SigningKey, - verifying_key: Ed25519VerifyingKey, -} - -impl LogosAccount { - /// Create a test LogosAccount. The `AccountId` is derived from the - /// generated Ed25519 verifying key (hex-encoded) so signatures over the - /// id can be verified by anyone holding the id alone. - /// The supplied `_display_name` is currently ignored — id is the key. - /// This should only be used during MLS integration. Not suitable for production use. - /// TODO: (P1) Remove once implementation is ready. - pub fn new_test(_display_name: impl Into) -> Self { - let signing_key = Ed25519SigningKey::generate(); - let verifying_key = signing_key.verifying_key(); - let id = AccountId::new(hex::encode(verifying_key.as_ref())); - Self { - id, - signing_key, - verifying_key, - } - } -} - -impl Signer for LogosAccount { - // TODO: (P2) Remove OpenMLS dependency to make accounts more portable - fn sign(&self, payload: &[u8]) -> Result, openmls_traits::signatures::SignerError> { - Ok(self.signing_key.sign(payload).as_ref().to_vec()) - } - - fn signature_scheme(&self) -> SignatureScheme { - SignatureScheme::ED25519 - } -} - -impl IdentityProvider for LogosAccount { - fn account_id(&self) -> &AccountId { - &self.id - } - - fn display_name(&self) -> String { - self.id.to_string() - } - - fn sign(&self, payload: &[u8]) -> Ed25519Signature { - self.signing_key.sign(payload) - } - - fn public_key(&self) -> &Ed25519VerifyingKey { - &self.verifying_key - } -} diff --git a/core/conversations/src/conversation.rs b/core/conversations/src/conversation.rs index a6cd5d0..4724955 100644 --- a/core/conversations/src/conversation.rs +++ b/core/conversations/src/conversation.rs @@ -1,14 +1,13 @@ pub mod group_v1; mod privatev1; +pub use crate::errors::ChatError; use crate::outcomes::ConvoOutcome; use crate::proto::EncryptedPayload; use crate::service_context::{ExternalServices, ServiceContext}; -use crate::types::AccountId; - -pub use crate::errors::ChatError; pub use group_v1::GroupV1Convo; pub use privatev1::PrivateV1Convo; +use shared_traits::IdentIdRef; pub type ConversationId = String; @@ -34,6 +33,6 @@ pub(crate) trait GroupConvo: Convo { fn add_member( &mut self, cx: &mut ServiceContext, - members: &[&AccountId], + members: &[IdentIdRef], ) -> Result<(), ChatError>; } diff --git a/core/conversations/src/conversation/group_v1.rs b/core/conversations/src/conversation/group_v1.rs index 7b06bbb..24cc85d 100644 --- a/core/conversations/src/conversation/group_v1.rs +++ b/core/conversations/src/conversation/group_v1.rs @@ -8,10 +8,11 @@ use chat_proto::logoschat::reliability::ReliablePayload; use openmls::prelude::tls_codec::Deserialize; use openmls::prelude::*; use prost::Message as _; +use shared_traits::IdentIdRef; use crate::inbox_v2::MlsProvider; use crate::service_context::{ExternalServices, ServiceContext}; -use crate::types::AccountId; + use crate::{ DeliveryService, IdentityProvider, conversation::{ChatError, Convo, GroupConvo}, @@ -140,7 +141,7 @@ impl GroupV1Convo { fn key_package_for_account( &self, - ident: &AccountId, + ident: IdentIdRef, provider: &impl MlsProvider, keypkg_provider: &impl KeyPackageProvider, ) -> Result { @@ -171,7 +172,7 @@ impl GroupV1Convo { content: &[u8], cx: &ServiceContext, ) -> Result, ChatError> { - let sender_id = cx.mls_identity.account_id().as_str(); + let sender_id = cx.mls_identity.id().as_str(); let reliable = cx.causal.on_send(&self.convo_id, sender_id, content); let wire = reliable.encode_to_vec(); @@ -274,7 +275,7 @@ impl GroupConvo for GroupV1Convo { fn add_member( &mut self, cx: &mut ServiceContext, - members: &[&AccountId], + members: &[IdentIdRef], ) -> Result<(), ChatError> { if members.len() > 50 { // This is a temporary limit that originates from the the De-MLS epoch time. diff --git a/core/conversations/src/core.rs b/core/conversations/src/core.rs index b3a987e..8dcbf2f 100644 --- a/core/conversations/src/core.rs +++ b/core/conversations/src/core.rs @@ -1,4 +1,3 @@ -use crate::account::LogosAccount; use crate::causal_history::{CausalHistoryStore, MissingMessage}; use crate::service_context::{ExternalServices, ServiceContext}; use crate::{DeliveryService, IdentityProvider, RegistrationService}; @@ -9,10 +8,10 @@ use crate::{ inbox_v2::{InboxV2, MlsEphemeralPqProvider, MlsIdentityProvider}, outcomes::{ConvoOutcome, InboxOutcome, PayloadOutcome}, proto::{EncryptedPayload, EnvelopeV1, Message}, - types::AccountId, }; use crypto::{Identity, PublicKey}; use openmls::prelude::GroupId; +use shared_traits::IdentIdRef; use storage::{ChatStore, ConversationKind, ConversationStore}; pub use crate::conversation::ConversationId; @@ -32,8 +31,9 @@ pub struct Core { // Constructors live on the `(DS, RS, CS)` form: `S` can't be inferred backwards // through `S::DS`, so the bundle is built from the three args here. -impl Core<(DS, RS, CS)> +impl Core<(IP, DS, RS, CS)> where + IP: IdentityProvider + 'static, DS: DeliveryService + 'static, RS: RegistrationService + 'static, CS: ChatStore + 'static, @@ -43,42 +43,34 @@ where /// If an identity exists in storage, it will be restored. /// Otherwise, a new identity will be created with the given name and saved. pub fn new_from_store( - name: impl Into, + ident: IP, delivery: DS, registration: RS, mut store: CS, ) -> Result { - let name = name.into(); - - // Load or create identity let identity = if let Some(identity) = store.load_identity()? { identity } else { - let identity = Identity::new(&name); + let identity = Identity::new(ident.id().as_str().to_string()); store.save_identity(&identity)?; identity }; - Self::assemble(name, identity, delivery, registration, store) + Self::assemble(ident, identity, delivery, registration, store) } /// Creates a new in-memory `Core` (for testing). /// /// Uses in-memory SQLite database. Each call creates a new isolated database. pub fn new_with_name( - name: impl Into, + ident: IP, delivery: DS, registration: RS, - mut store: CS, + store: CS, ) -> Result { - let name = name.into(); - let identity = Identity::new(&name); - store - .save_identity(&identity) - .expect("in-memory storage should not fail"); + let identity = Identity::new(ident.id().as_str().to_string()); + let mut core = Self::assemble(ident, identity, delivery, registration, store)?; - let mut core = Self::assemble(name, identity, delivery, registration, store)?; - // TODO: (P2) Initialize Account in Core or upper client. core.register_keypackage()?; Ok(core) } @@ -86,19 +78,18 @@ where /// Builds the inbox/account/MLS/causal state, subscribes both inbound /// addresses, and assembles the service bundle — shared by both constructors. fn assemble( - name: String, + ident: IP, identity: Identity, mut delivery: DS, registration: RS, store: CS, ) -> Result { let inbox = Inbox::new(&identity); - let account = LogosAccount::new_test(name); - let account_id = account.account_id().clone(); - let mls_identity = MlsIdentityProvider::new(account); + let ident_id = ident.id().clone(); + let mls_identity = MlsIdentityProvider::new(ident); let mls_provider = MlsEphemeralPqProvider::new().map_err(ChatError::generic)?; let causal = CausalHistoryStore::new(); - let pq_inbox = InboxV2::new(account_id); + let pq_inbox = InboxV2::new(ident_id); // Subscribe to inbound addresses for both conversation stacks. delivery @@ -124,7 +115,7 @@ where } } -impl Core { +impl<'a, S: ExternalServices + 'static> Core { pub fn ds(&mut self) -> &mut S::DS { &mut self.services.ds } @@ -138,8 +129,8 @@ impl Core { } /// Returns the unique identifier associated with the account - pub fn account_id(&self) -> &AccountId { - self.pq_inbox.account_id() + pub fn ident_id(&'a self) -> IdentIdRef<'a> { + self.pq_inbox.ident_id() } /// Submit the local account's MLS KeyPackage to the registration service. @@ -179,7 +170,7 @@ impl Core { pub fn create_group_convo( &mut self, - participants: &[&AccountId], + participants: &[IdentIdRef], ) -> Result { // TODO: (P1) Ensure errors are handled properly. This is a high chance for // desynchronized state: MlsGroup persistence, conversation persistence, and @@ -200,7 +191,7 @@ impl Core { pub fn group_add_member( &mut self, convo_id: &str, - members: &[&AccountId], + members: &[IdentIdRef], ) -> Result<(), ChatError> { let mut convo = self.load_group_convo(convo_id)?; convo.add_member(&mut self.services, members) diff --git a/core/conversations/src/inbox/handler.rs b/core/conversations/src/inbox/handler.rs index 359264f..ccbfff7 100644 --- a/core/conversations/src/inbox/handler.rs +++ b/core/conversations/src/inbox/handler.rs @@ -270,18 +270,60 @@ impl Inbox { #[cfg(test)] mod tests { + use super::*; use chat_sqlite::{ChatStorage, StorageConfig}; + use crypto::{Ed25519SigningKey, Ed25519VerifyingKey}; + use shared_traits::{IdentId, IdentityProvider}; + + struct Identity { + name: IdentId, + key: Ed25519SigningKey, + verify: Ed25519VerifyingKey, + } + + impl Identity { + pub fn new(name: impl Into) -> Self { + let key = Ed25519SigningKey::generate(); + let verify = key.verifying_key(); + Identity { + name: IdentId::new(name.into()), + key, + verify, + } + } + } + + impl IdentityProvider for Identity { + fn id(&self) -> shared_traits::IdentIdRef<'_> { + &self.name + } + + fn display_name(&self) -> String { + self.name.to_string() + } + + fn sign(&self, payload: &[u8]) -> crypto::Ed25519Signature { + self.key.sign(payload) + } + + fn public_key(&self) -> &crypto::Ed25519VerifyingKey { + &self.verify + } + } #[test] fn test_invite_privatev1_roundtrip() { let saro_storage = ChatStorage::new(StorageConfig::InMemory).unwrap(); let raya_storage = ChatStorage::new(StorageConfig::InMemory).unwrap(); - let mut saro_cx = ServiceContext::for_test("saro", saro_storage).unwrap(); + let saro_account = Identity::new("saro"); + let raya_account = Identity::new("raya"); + + let mut saro_cx = ServiceContext::for_test(saro_account, saro_storage).unwrap(); let saro_inbox = Inbox::new(&saro_cx.identity); - let mut raya_cx = ServiceContext::for_test("raya", raya_storage).unwrap(); + let mut raya_cx = ServiceContext::for_test(raya_account, raya_storage).unwrap(); let raya_inbox = Inbox::new(&raya_cx.identity); let bundle = raya_inbox.create_intro_bundle(&mut raya_cx).unwrap(); diff --git a/core/conversations/src/inbox_v2.rs b/core/conversations/src/inbox_v2.rs index de63ad9..5f3c40b 100644 --- a/core/conversations/src/inbox_v2.rs +++ b/core/conversations/src/inbox_v2.rs @@ -3,6 +3,8 @@ mod mls_provider; pub use identity::MlsIdentityProvider; pub(crate) use mls_provider::MlsEphemeralPqProvider; +use shared_traits::IdentId; +use shared_traits::IdentIdRef; use chat_proto::logoschat::envelope::EnvelopeV1; use openmls::prelude::tls_codec::Serialize; @@ -18,16 +20,15 @@ use crate::conversation::ConversationId; use crate::conversation::GroupV1Convo; use crate::outcomes::{ConversationClass, InboxOutcome, NewConversation}; use crate::service_context::{ExternalServices, ServiceContext}; -use crate::types::AccountId; use crate::utils::{blake2b_hex, hash_size}; // Define unique Identifiers derivations used in InboxV2 -fn delivery_address_for(account_id: &AccountId) -> String { - blake2b_hex::(&["InboxV2|", "delivery_address|", account_id.as_str()]) +fn delivery_address_for(ident_id: IdentIdRef) -> String { + blake2b_hex::(&["InboxV2|", "delivery_address|", ident_id.as_str()]) } -fn conversation_id_for(account_id: &AccountId) -> String { - blake2b_hex::(&["InboxV2|", "conversation_id|", account_id.as_str()]) +fn conversation_id_for(ident_id: IdentIdRef) -> String { + blake2b_hex::(&["InboxV2|", "conversation_id|", ident_id.as_str()]) } /// An Extension trait which extends OpenMlsProvider to add required functionality @@ -36,7 +37,7 @@ pub trait MlsProvider: OpenMlsProvider { fn invite_user( &self, ds: &mut DS, - account_id: &AccountId, + ident_id: IdentIdRef, welcome: &MlsMessageOut, ) -> Result<(), ChatError>; } @@ -46,16 +47,16 @@ pub trait MlsProvider: OpenMlsProvider { /// such as MLS. pub struct InboxV2 { // Account_id field is an owned value, so it can be returned via reference. - account_id: AccountId, + ident_id: IdentId, } impl InboxV2 { - pub fn new(account_id: AccountId) -> Self { - Self { account_id } + pub fn new(ident_id: IdentId) -> Self { + Self { ident_id } } - pub fn account_id(&self) -> &AccountId { - &self.account_id + pub fn ident_id(&self) -> IdentIdRef<'_> { + &self.ident_id } /// Submit MlsKeypackage to registration service @@ -73,11 +74,11 @@ impl InboxV2 { } pub fn delivery_address(&self) -> String { - delivery_address_for(&self.account_id) + delivery_address_for(&self.ident_id) } pub fn id(&self) -> String { - conversation_id_for(&self.account_id) + conversation_id_for(&self.ident_id) } pub fn handle_frame( diff --git a/core/conversations/src/inbox_v2/identity.rs b/core/conversations/src/inbox_v2/identity.rs index 53ec51a..7691cc4 100644 --- a/core/conversations/src/inbox_v2/identity.rs +++ b/core/conversations/src/inbox_v2/identity.rs @@ -5,8 +5,9 @@ use openmls_traits::{ signatures::{Signer, SignerError}, types::SignatureScheme, }; +use shared_traits::IdentIdRef; -use crate::{AccountId, IdentityProvider}; +use crate::IdentityProvider; /// A Wrapper for an IdentityProvider which provides MLS specific functionality /// @@ -22,7 +23,7 @@ impl MlsIdentityProvider { pub fn get_credential(&self) -> CredentialWithKey { CredentialWithKey { - credential: BasicCredential::new(self.account_id().as_str().as_bytes().to_vec()).into(), + credential: BasicCredential::new(self.id().as_str().as_bytes().to_vec()).into(), signature_key: self.public_key().as_ref().into(), } } @@ -37,8 +38,8 @@ impl Deref for MlsIdentityProvider { } impl IdentityProvider for MlsIdentityProvider { - fn account_id(&self) -> &AccountId { - self.0.account_id() + fn id(&self) -> IdentIdRef<'_> { + self.0.id() } fn display_name(&self) -> String { diff --git a/core/conversations/src/inbox_v2/mls_provider.rs b/core/conversations/src/inbox_v2/mls_provider.rs index 6f617bf..f55f60f 100644 --- a/core/conversations/src/inbox_v2/mls_provider.rs +++ b/core/conversations/src/inbox_v2/mls_provider.rs @@ -4,8 +4,9 @@ use openmls_memory_storage::MemoryStorage; use openmls_traits::OpenMlsProvider; use openmls_traits::types::CryptoError; use prost::Message; +use shared_traits::IdentIdRef; -use crate::{AccountId, ChatError, DeliveryService}; +use crate::{ChatError, DeliveryService}; use super::{ AddressedEnvelope, EnvelopeV1, GroupV1HeavyInvite, InboxV2Frame, InviteType, MlsProvider, @@ -31,7 +32,7 @@ impl MlsProvider for MlsEphemeralPqProvider { fn invite_user( &self, ds: &mut DS, - account_id: &AccountId, + ident_id: IdentIdRef, welcome: &MlsMessageOut, ) -> Result<(), ChatError> { let invite = GroupV1HeavyInvite { @@ -43,13 +44,13 @@ impl MlsProvider for MlsEphemeralPqProvider { }; let envelope = EnvelopeV1 { - conversation_hint: conversation_id_for(account_id), + conversation_hint: conversation_id_for(ident_id), salt: 0, payload: frame.encode_to_vec().into(), }; let outbound_msg = AddressedEnvelope { - delivery_address: delivery_address_for(account_id), + delivery_address: delivery_address_for(ident_id), data: envelope.encode_to_vec(), }; diff --git a/core/conversations/src/lib.rs b/core/conversations/src/lib.rs index 5867558..a6b375d 100644 --- a/core/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -1,4 +1,3 @@ -mod account; mod causal_history; mod conversation; mod core; @@ -13,7 +12,6 @@ mod service_traits; mod types; mod utils; -pub use account::LogosAccount; pub use causal_history::{Frontier, MissingMessage}; pub use chat_sqlite::ChatStorage; pub use chat_sqlite::StorageConfig; @@ -23,7 +21,8 @@ pub use outcomes::{ Content, ConversationClass, ConvoOutcome, InboxOutcome, NewConversation, PayloadOutcome, }; pub use service_context::ExternalServices; -pub use service_traits::{DeliveryService, IdentityProvider, RegistrationService}; +pub use service_traits::{DeliveryService, RegistrationService}; +pub use shared_traits::IdentityProvider; pub use storage::ConversationKind; -pub use types::{AccountId, AddressedEnvelope}; +pub use types::AddressedEnvelope; pub use utils::hex_trunc; diff --git a/core/conversations/src/service_context.rs b/core/conversations/src/service_context.rs index 1bce50c..aad489f 100644 --- a/core/conversations/src/service_context.rs +++ b/core/conversations/src/service_context.rs @@ -3,7 +3,7 @@ use crypto::Identity; use storage::ChatStore; -use crate::account::LogosAccount; +use crate::IdentityProvider; use crate::causal_history::CausalHistoryStore; use crate::inbox_v2::{MlsEphemeralPqProvider, MlsIdentityProvider}; use crate::{DeliveryService, RegistrationService}; @@ -11,17 +11,20 @@ use crate::{DeliveryService, RegistrationService}; /// Bundles the external service types (`DS`, `RS`, `CS`) behind one `S`. The /// `(DS, RS, CS)` tuple impl lets them still be supplied separately. pub trait ExternalServices { + type IP: IdentityProvider; type DS: DeliveryService; type RS: RegistrationService; type CS: ChatStore; } -impl ExternalServices for (DS, RS, CS) +impl ExternalServices for (IP, DS, RS, CS) where + IP: IdentityProvider, DS: DeliveryService, RS: RegistrationService, CS: ChatStore, { + type IP = IP; type DS = DS; type RS = RS; type CS = CS; @@ -32,7 +35,7 @@ pub(crate) struct ServiceContext { pub(crate) ds: S::DS, pub(crate) registry: S::RS, pub(crate) store: S::CS, - pub(crate) mls_identity: MlsIdentityProvider, + pub(crate) mls_identity: MlsIdentityProvider, pub(crate) mls_provider: MlsEphemeralPqProvider, pub(crate) causal: CausalHistoryStore, pub(crate) identity: Identity, @@ -80,15 +83,15 @@ mod test_support { } } - impl ServiceContext<(NoopDelivery, NoopRegistration, CS)> { + impl ServiceContext<(IP, NoopDelivery, NoopRegistration, CS)> { /// Builds a context around a real store, stubbing other services. - pub(crate) fn for_test(name: &str, store: CS) -> Result { - let account = LogosAccount::new_test(name); + pub(crate) fn for_test(ident: IP, store: CS) -> Result { + let name = ident.id().as_str().to_string(); Ok(Self { ds: NoopDelivery, registry: NoopRegistration, store, - mls_identity: MlsIdentityProvider::new(account), + mls_identity: MlsIdentityProvider::new(ident), mls_provider: MlsEphemeralPqProvider::new().map_err(ChatError::generic)?, causal: CausalHistoryStore::new(), identity: Identity::new(name), diff --git a/core/conversations/src/service_traits.rs b/core/conversations/src/service_traits.rs index 1f3093c..d5af222 100644 --- a/core/conversations/src/service_traits.rs +++ b/core/conversations/src/service_traits.rs @@ -1,11 +1,10 @@ /// Service traits define the functionality which must be externally supplied by /// platform clients. Platforms can alter the behaviour of the chat core by supplying /// different implementations. +use shared_traits::IdentityProvider; use std::{fmt::Debug, fmt::Display}; -use crypto::{Ed25519Signature, Ed25519VerifyingKey}; - -use crate::types::{AccountId, AddressedEnvelope}; +use crate::types::AddressedEnvelope; /// A Delivery service is responsible for payload transport. /// This interface allows Conversations to send payloads on the wire as well as @@ -50,14 +49,3 @@ impl KeyPackageProvider for T { RegistrationService::retrieve(self, device_id) } } - -/// Represents an external Identity -/// Implement this to provide an Authentication model for users/installations -pub trait IdentityProvider { - fn account_id(&self) -> &AccountId; - // Display name is not garenteed to be consistent. It should only be used to - // provded a more readable identifier for the account. - fn display_name(&self) -> String; - fn sign(&self, payload: &[u8]) -> Ed25519Signature; - fn public_key(&self) -> &Ed25519VerifyingKey; -} diff --git a/core/conversations/src/types.rs b/core/conversations/src/types.rs index b6c5bfd..892df43 100644 --- a/core/conversations/src/types.rs +++ b/core/conversations/src/types.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Debug}; +use std::fmt::Debug; use crate::proto::{self, Message}; @@ -66,31 +66,3 @@ impl AddressedEncryptedPayload { ) } } - -/// This represents an Identifier for an account. -/// Its a thin wrapper around a string, but providers extra functionality, -/// and ensures type consistency -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct AccountId(String); - -impl AccountId { - pub fn new(id: impl Into) -> Self { - Self(id.into()) - } - - pub fn as_str(&self) -> &str { - &self.0 - } -} - -impl fmt::Display for AccountId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } -} - -impl AsRef for AccountId { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/core/conversations/src/utils.rs b/core/conversations/src/utils.rs index 65ef283..3ed1059 100644 --- a/core/conversations/src/utils.rs +++ b/core/conversations/src/utils.rs @@ -11,7 +11,7 @@ pub fn timestamp_millis() -> i64 { /// Track hash sizes in use across the crate. pub mod hash_size { use blake2::digest::{ - consts::{U32, U64}, + consts::{U4, U6, U32, U64}, generic_array::ArrayLength, typenum::{IsLessOrEqual, NonZero}, }; @@ -34,12 +34,11 @@ pub mod hash_size { }; } - use blake2::digest::consts::{U6, U8}; hash_sizes! { - /// Account ID hash length - AccountId => U8, /// Conversation ID hash length ConvoId => U6, + /// Delivery Address length + DeliveryAddr => U4, /// Causal history message ID hash length (256-bit, collision-resistant) MessageId => U32, } diff --git a/core/integration_tests_core/Cargo.toml b/core/integration_tests_core/Cargo.toml index dec26c4..0f4bde8 100644 --- a/core/integration_tests_core/Cargo.toml +++ b/core/integration_tests_core/Cargo.toml @@ -8,9 +8,10 @@ edition = "2024" [dev-dependencies] # Workspace dependencies (sorted) +chat-sqlite = { workspace = true } components = { workspace = true } libchat = { workspace = true } -chat-sqlite = { workspace = true } +logos-account = { workspace = true , features = ["dev"]} storage = { workspace = true } # External dependencies (sorted) diff --git a/core/integration_tests_core/tests/causal_history.rs b/core/integration_tests_core/tests/causal_history.rs index a6f3b65..5c96339 100644 --- a/core/integration_tests_core/tests/causal_history.rs +++ b/core/integration_tests_core/tests/causal_history.rs @@ -8,13 +8,25 @@ use std::ops::{Deref, DerefMut}; use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; use libchat::{Core, MissingMessage}; - +use logos_account::TestLogosAccount; struct Client { - inner: Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>, + inner: Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>, } impl Client { - fn init(core: Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>) -> Self { + fn init( + core: Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>, + ) -> Self { Client { inner: core } } @@ -38,7 +50,12 @@ impl Client { } impl Deref for Client { - type Target = Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>; + type Target = Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>; fn deref(&self) -> &Self::Target { &self.inner } @@ -55,16 +72,19 @@ fn missing_group_message_is_detected() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); + let saro_account = TestLogosAccount::new("saro"); let saro_ctx = - Core::new_with_name("saro", ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); + Core::new_with_name(saro_account, ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); - let raya_ctx = Core::new_with_name("raya", ds.clone(), rs.clone(), MemStore::new()).unwrap(); + let raya_account = TestLogosAccount::new("raya"); + let raya_ctx = + Core::new_with_name(raya_account, ds.clone(), rs.clone(), MemStore::new()).unwrap(); let mut saro = Client::init(saro_ctx); let mut raya = Client::init(raya_ctx); // Saro creates a group with Raya. - let raya_id = raya.account_id().clone(); + let raya_id = raya.ident_id().clone(); let convo_id = saro.create_group_convo(&[&raya_id]).unwrap().to_string(); // Raya joins (processes the Welcome + commit). @@ -95,7 +115,7 @@ fn missing_group_message_is_detected() { ); assert_eq!( missing[0].frontier.sender_id(), - saro.account_id().as_str(), + saro.ident_id().as_str(), "missing-message sender hint should attribute to Saro" ); diff --git a/core/integration_tests_core/tests/mls_integration.rs b/core/integration_tests_core/tests/mls_integration.rs index 7cecf98..9f578aa 100644 --- a/core/integration_tests_core/tests/mls_integration.rs +++ b/core/integration_tests_core/tests/mls_integration.rs @@ -4,12 +4,18 @@ use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; use libchat::{ Content, ConversationClass, ConvoOutcome, Core, NewConversation, PayloadOutcome, hex_trunc, }; +use logos_account::TestLogosAccount; type ResultCallback = Box; // Simple client Functionality for testing struct Client { - inner: Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>, + inner: Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>, on_result: Option, new_conversations: Vec, received_messages: Vec<(libchat::ConversationId, Content)>, @@ -17,7 +23,12 @@ struct Client { impl Client { fn init( - core: Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>, + core: Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>, cb: Option, ) -> Self { Client { @@ -60,7 +71,12 @@ impl Client { } impl Deref for Client { - type Target = Core<(LocalBroadcaster, EphemeralRegistry, MemStore)>; + type Target = Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + MemStore, + )>; fn deref(&self) -> &Self::Target { &self.inner @@ -111,19 +127,22 @@ fn create_group() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); - let saro_ctx = - Core::new_with_name("saro", ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); - let raya_ctx = Core::new_with_name("raya", ds.clone(), rs.clone(), MemStore::new()).unwrap(); + let saro_ident = TestLogosAccount::new("saro"); + let saro = + Core::new_with_name(saro_ident, ds.new_consumer(), rs.clone(), MemStore::new()).unwrap(); + + let raya_ident = TestLogosAccount::new("raya"); + let raya = Core::new_with_name(raya_ident, ds.clone(), rs.clone(), MemStore::new()).unwrap(); let mut clients = vec![ - Client::init(saro_ctx, Some(pretty_print(" Saro "))), - Client::init(raya_ctx, Some(pretty_print(" Raya "))), + Client::init(saro, Some(pretty_print(" Saro "))), + Client::init(raya, Some(pretty_print(" Raya "))), ]; const SARO: usize = 0; const RAYA: usize = 1; - let raya_id = clients[RAYA].account_id().clone(); + let raya_id = clients[RAYA].ident_id().clone(); let convo_id = clients[SARO] .create_group_convo(&[&raya_id]) .unwrap() @@ -157,11 +176,12 @@ fn create_group() { process(&mut clients); - let pax_ctx = Core::new_with_name("pax", ds, rs, MemStore::new()).unwrap(); - clients.push(Client::init(pax_ctx, Some(pretty_print(" Pax")))); + let pax_ident = TestLogosAccount::new("pax"); + let pax = Core::new_with_name(pax_ident, ds, rs, MemStore::new()).unwrap(); + clients.push(Client::init(pax, Some(pretty_print(" Pax")))); const PAX: usize = 2; - let pax_id = clients[PAX].account_id().clone(); + let pax_id = clients[PAX].ident_id().clone(); clients[SARO] .group_add_member(&convo_id, &[&pax_id]) .unwrap(); diff --git a/core/integration_tests_core/tests/private_integration.rs b/core/integration_tests_core/tests/private_integration.rs index d09f6ca..388afd3 100644 --- a/core/integration_tests_core/tests/private_integration.rs +++ b/core/integration_tests_core/tests/private_integration.rs @@ -1,11 +1,17 @@ use chat_sqlite::{ChatStorage, StorageConfig}; use libchat::{ConversationClass, Core, Introduction, PayloadOutcome}; +use logos_account::TestLogosAccount; use storage::{ConversationStore, IdentityStore}; use tempfile::tempdir; use components::{EphemeralRegistry, LocalBroadcaster}; -type PrivateCore = Core<(LocalBroadcaster, EphemeralRegistry, ChatStorage)>; +type PrivateCore = Core<( + TestLogosAccount, + LocalBroadcaster, + EphemeralRegistry, + ChatStorage, +)>; /// Drains everything published to `receiver`'s delivery service and feeds each /// payload back through `handle_payload`, returning the observed outcomes. @@ -49,9 +55,16 @@ fn ctx_integration() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); - let mut saro = - Core::new_with_name("saro", ds.clone(), rs.clone(), ChatStorage::in_memory()).unwrap(); - let mut raya = Core::new_with_name("raya", ds, rs, ChatStorage::in_memory()).unwrap(); + let saro_account = TestLogosAccount::new("saro"); + let mut saro = Core::new_with_name( + saro_account, + ds.clone(), + rs.clone(), + ChatStorage::in_memory(), + ) + .unwrap(); + let raya_account = TestLogosAccount::new("raya"); + let mut raya = Core::new_with_name(raya_account, ds, rs, ChatStorage::in_memory()).unwrap(); // Raya creates intro bundle and sends to Saro let bundle = raya.create_intro_bundle().unwrap(); @@ -93,7 +106,8 @@ fn identity_persistence() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); let store1 = ChatStorage::new(StorageConfig::InMemory).unwrap(); - let ctx1 = Core::new_with_name("alice", ds, rs, store1).unwrap(); + let alice_account = TestLogosAccount::new("alice"); + let ctx1 = Core::new_with_name(alice_account, ds, rs, store1).unwrap(); let pubkey1 = ctx1.identity().public_key(); let name1 = ctx1.installation_name().to_string(); @@ -112,7 +126,8 @@ fn open_persists_new_identity() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); let store = ChatStorage::new(StorageConfig::File(db_path.clone())).unwrap(); - let core = Core::new_from_store("alice", ds, rs, store).unwrap(); + let alice_account = TestLogosAccount::new("alice"); + let core = Core::new_from_store(alice_account, ds, rs, store).unwrap(); let pubkey = core.identity().public_key(); drop(core); @@ -127,9 +142,16 @@ fn open_persists_new_identity() { fn conversation_metadata_persistence() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); - let mut alice = - Core::new_with_name("alice", ds.clone(), rs.clone(), ChatStorage::in_memory()).unwrap(); - let mut bob = Core::new_with_name("bob", ds, rs, ChatStorage::in_memory()).unwrap(); + let alice_account = TestLogosAccount::new("alice"); + let mut alice = Core::new_with_name( + alice_account, + ds.clone(), + rs.clone(), + ChatStorage::in_memory(), + ) + .unwrap(); + let bob_account = TestLogosAccount::new("bob"); + let mut bob = Core::new_with_name(bob_account, ds, rs, ChatStorage::in_memory()).unwrap(); let bundle = alice.create_intro_bundle().unwrap(); let intro = Introduction::try_from(bundle.as_slice()).unwrap(); @@ -153,9 +175,16 @@ fn conversation_metadata_persistence() { fn conversation_full_flow() { let ds = LocalBroadcaster::new(); let rs = EphemeralRegistry::new(); - let mut alice = - Core::new_with_name("alice", ds.clone(), rs.clone(), ChatStorage::in_memory()).unwrap(); - let mut bob = Core::new_with_name("bob", ds, rs, ChatStorage::in_memory()).unwrap(); + let alice_account = TestLogosAccount::new("alice"); + let mut alice = Core::new_with_name( + alice_account, + ds.clone(), + rs.clone(), + ChatStorage::in_memory(), + ) + .unwrap(); + let bob_account = TestLogosAccount::new("bob"); + let mut bob = Core::new_with_name(bob_account, ds, rs, ChatStorage::in_memory()).unwrap(); let bundle = alice.create_intro_bundle().unwrap(); let intro = Introduction::try_from(bundle.as_slice()).unwrap(); diff --git a/core/shared-traits/Cargo.toml b/core/shared-traits/Cargo.toml new file mode 100644 index 0000000..ada17dc --- /dev/null +++ b/core/shared-traits/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "shared-traits" +description = "Shared traits for the Logos Ecosystem" +version = "0.1.0" +edition = "2024" + +[dependencies] +crypto = { workspace = true } diff --git a/core/shared-traits/src/lib.rs b/core/shared-traits/src/lib.rs new file mode 100644 index 0000000..06316cb --- /dev/null +++ b/core/shared-traits/src/lib.rs @@ -0,0 +1,39 @@ +use crypto::{Ed25519Signature, Ed25519VerifyingKey}; +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct IdentId(String); +pub type IdentIdRef<'a> = &'a IdentId; + +impl IdentId { + pub fn new(id: impl Into) -> Self { + Self(id.into()) + } + + pub fn as_str(&self) -> &str { + &self.0 + } +} + +impl fmt::Display for IdentId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +impl AsRef for IdentId { + fn as_ref(&self) -> &str { + &self.0 + } +} + +/// Represents an external Identity +/// Implement this to provide an Authentication model for users/installations +pub trait IdentityProvider { + fn id(&self) -> IdentIdRef<'_>; + // Display name is not garenteed to be consistent. It should only be used to + // provded a more readable identifier for the account. + fn display_name(&self) -> String; + fn sign(&self, payload: &[u8]) -> Ed25519Signature; + fn public_key(&self) -> &Ed25519VerifyingKey; +} diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 3b61d95..945ac08 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["rlib"] chat-sqlite = { workspace = true } components = { workspace = true} libchat = { workspace = true } +logos-account = { workspace = true, features = ["dev"]} # External dependencies (sorted) thiserror = "2" diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 9cc018d..88ed049 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -6,12 +6,13 @@ use libchat::{ }; use components::EphemeralRegistry; +use logos_account::TestLogosAccount; use crate::errors::ClientError; use crate::event::Event; pub struct ChatClient { - core: Core<(D, R, ChatStorage)>, + core: Core<(TestLogosAccount, D, R, ChatStorage)>, } // ── Default-registry constructors ──────────────────────────────────────────── @@ -21,8 +22,9 @@ impl ChatClient { pub fn new(name: impl Into, delivery: D) -> Self { let registry = EphemeralRegistry::new(); let store = ChatStorage::in_memory(); + let ident = TestLogosAccount::new(name); Self { - core: Core::new_with_name(name, delivery, registry, store).unwrap(), + core: Core::new_with_name(ident, delivery, registry, store).unwrap(), } } @@ -37,7 +39,8 @@ impl ChatClient { ) -> Result { let store = ChatStorage::new(config).map_err(ChatError::from)?; let registry = EphemeralRegistry::new(); - let core = Core::new_from_store(name, delivery, registry, store)?; + let ident = TestLogosAccount::new(name); + let core = Core::new_from_store(ident, delivery, registry, store)?; Ok(Self { core }) } } @@ -64,7 +67,9 @@ where registry: R, ) -> Result { let store = ChatStorage::new(config).map_err(ChatError::from)?; - let mut core = Core::new_from_store(name, delivery, registry, store)?; + + let ident = TestLogosAccount::new(name); + let mut core = Core::new_from_store(ident, delivery, registry, store)?; core.register_keypackage()?; Ok(Self { core }) } diff --git a/extensions/components/src/contact_registry.rs b/extensions/components/src/contact_registry.rs index d4fd627..338a18d 100644 --- a/extensions/components/src/contact_registry.rs +++ b/extensions/components/src/contact_registry.rs @@ -67,7 +67,7 @@ impl RegistrationService for EphemeralRegistry { self.registry .lock() .unwrap() - .insert(identity.account_id().to_string(), key_bundle); + .insert(identity.id().to_string(), key_bundle); Ok(()) }