diff --git a/core/conversations/src/account.rs b/core/conversations/src/account.rs index 06e2914..3ca7dfc 100644 --- a/core/conversations/src/account.rs +++ b/core/conversations/src/account.rs @@ -1,14 +1,15 @@ -use crypto::Ed25519SigningKey; +use crypto::{Ed25519SigningKey, Ed25519VerifyingKey}; use openmls::prelude::SignatureScheme; use openmls_traits::signatures::Signer; -use crate::types::AccountId; +use crate::{conversation::IdentityProvider, types::AccountId}; /// Logos Account represents a single account across /// multiple installations and services. pub struct LogosAccount { id: AccountId, signing_key: Ed25519SigningKey, + verifying_key: Ed25519VerifyingKey, } impl LogosAccount { @@ -17,9 +18,11 @@ impl LogosAccount { /// TODO: (P1) Remove once implementation is ready. pub fn new_test(explicit_id: impl Into) -> Self { let signing_key = Ed25519SigningKey::generate(); + let verifying_key = signing_key.verifying_key() Self { id: AccountId::new(explicit_id.into()), signing_key, + verifying_key } } @@ -38,3 +41,13 @@ impl Signer for LogosAccount { SignatureScheme::ED25519 } } + +impl IdentityProvider for LogosAccount { + fn friendly_name(&self) -> String { + self.id.to_string() + } + + fn public_key(&self) -> &Ed25519VerifyingKey { + &self.verifying_key + } +} diff --git a/core/conversations/src/context.rs b/core/conversations/src/context.rs index ae968e6..e3d6906 100644 --- a/core/conversations/src/context.rs +++ b/core/conversations/src/context.rs @@ -2,6 +2,7 @@ use std::cell::Ref; use std::sync::Arc; use std::{cell::RefCell, rc::Rc}; +use crate::account::LogosAccount; use crate::conversation::{Convo, GroupConvo, IdentityProvider}; use crate::ctx::ClientCtx; @@ -13,7 +14,7 @@ use crate::{ inbox::Inbox, inbox_v2::InboxV2, proto::{EncryptedPayload, EnvelopeV1, Message}, - types::{AddressedEnvelope, ContentData}, + types::{AccountId, AddressedEnvelope, ContentData}, }; use crypto::{Identity, PublicKey}; use storage::{ChatStore, ConversationKind}; @@ -59,11 +60,11 @@ impl Cont let identity = Rc::new(identity); let inbox = Inbox::new(Rc::clone(&store), Rc::clone(&identity)); - let pq_inbox = InboxV2::new(); + let pq_inbox = InboxV2::new_with_account(LogosAccount::new_test(name)); // Subscribe ctx.ds() - .subscribe(pq_inbox.delivery_address()) + .subscribe(&pq_inbox.delivery_address()) .map_err(ChatError::generic)?; Ok(Self { @@ -97,11 +98,11 @@ impl Cont let identity = Rc::new(identity); let inbox = Inbox::new(Rc::clone(&chat_store), Rc::clone(&identity)); - let mut pq_inbox = InboxV2::new(); + let mut pq_inbox = InboxV2::new_with_account(LogosAccount::new_test(name)); pq_inbox.register(&mut ctx)?; ctx.ds() - .subscribe(pq_inbox.delivery_address()) + .subscribe(&pq_inbox.delivery_address()) .map_err(ChatError::generic)?; Ok(Self { @@ -128,8 +129,8 @@ impl Cont } /// Returns the unique identifier associated with the account - pub fn account_id(&self) -> String { - self.pq_inbox.account.friendly_name() + pub fn account_id(&self) -> &AccountId { + self.pq_inbox.account_id() } pub fn installation_name(&self) -> &str { @@ -162,7 +163,7 @@ impl Cont pub fn create_group_convo( &mut self, - participants: &[&str], + participants: &[&AccountId], ) -> Result>, ChatError> { let mut convo = self.pq_inbox.create_group_v1(&mut self.client_ctx)?; self.client_ctx diff --git a/core/conversations/src/conversation.rs b/core/conversations/src/conversation.rs index 6c6685f..4a36bc3 100644 --- a/core/conversations/src/conversation.rs +++ b/core/conversations/src/conversation.rs @@ -2,9 +2,9 @@ pub mod group_v1; mod privatev1; use crate::{ - DeliveryService, RegistrationService, + DeliveryService, service_traits::KeyPackageProvider, ctx::ClientCtx, - types::{AddressedEncryptedPayload, ContentData}, + types::{AccountId, AddressedEncryptedPayload, ContentData}, }; use chat_proto::logoschat::encryption::EncryptedPayload; use std::fmt::Debug; @@ -42,11 +42,11 @@ pub trait Convo: Id + Debug { fn convo_type(&self) -> ConversationKind; } -pub trait GroupConvo: Convo { +pub trait GroupConvo: Convo { fn add_member( &mut self, ctx: &mut ClientCtx, - members: &[&str], + members: &[&AccountId], ) -> Result<(), ChatError>; // Default implementation which dispatches envelopes to the DeliveryService diff --git a/core/conversations/src/conversation/group_v1.rs b/core/conversations/src/conversation/group_v1.rs index fa0662b..12a71da 100644 --- a/core/conversations/src/conversation/group_v1.rs +++ b/core/conversations/src/conversation/group_v1.rs @@ -1,4 +1,4 @@ -use std::cell::{Ref, RefCell}; +use std::cell::RefCell; use std::rc::Rc; use blake2::{Blake2b, Digest, digest::consts::U6}; @@ -10,8 +10,9 @@ use openmls_libcrux_crypto::Provider as LibcruxProvider; use openmls_traits::signatures::Signer as OpenMlsSigner; use storage::{ChatStore, ConversationKind}; +use crate::types::AccountId; use crate::{ - DeliveryService, RegistrationService, + DeliveryService, service_traits::KeyPackageProvider, conversation::{ChatError, ConversationId, Convo, GroupConvo, Id}, ctx::ClientCtx, types::{AddressedEncryptedPayload, ContentData}, @@ -19,37 +20,38 @@ use crate::{ pub trait IdentityProvider: OpenMlsSigner { fn friendly_name(&self) -> String; - fn public_key(&self) -> Ed25519VerifyingKey; + fn public_key(&self) -> &Ed25519VerifyingKey; } -pub trait MlsInitializer { - fn invite_to_group_v1( +pub trait MlsContext { + type IDENT: IdentityProvider; + + fn ident(&self) -> &Self::IDENT; + fn provider(&self) -> &LibcruxProvider; + + // Build an MLS Credential from the supplied IdentityProvider + fn get_credential(&self) -> CredentialWithKey { + CredentialWithKey { + credential: BasicCredential::new(self.ident().friendly_name().into()).into(), + signature_key: self.ident().public_key().as_ref().into(), + } + } + + fn invite_user( &self, ctx: &mut ClientCtx, - account_id: &str, + account_id: &AccountId, welcome: &MlsMessageOut, ) -> Result<(), ChatError>; } -pub trait MlsCtx { - type IDENT: IdentityProvider; - type INIT: MlsInitializer; - - fn ident(&self) -> &Self::IDENT; - fn provider(&self) -> Ref<'_, LibcruxProvider>; - fn init(&self) -> &Self::INIT; - - // Build an MLS Credential from the supplied IdentityProvider - fn get_credential(&self) -> CredentialWithKey; -} - -pub struct GroupV1Convo { +pub struct GroupV1Convo { ctx: Rc>, pub(crate) mls_group: MlsGroup, // TODO: (!) Fix Visibility convo_id: String, } -impl std::fmt::Debug for GroupV1Convo { +impl std::fmt::Debug for GroupV1Convo { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("GroupV1Convo") .field("name", &self.ctx.borrow().ident().friendly_name()) @@ -59,14 +61,13 @@ impl std::fmt::Debug for GroupV1Convo { } } -impl GroupV1Convo { - pub fn new(ctx: Ctx, ds: &mut DS) -> Self { +impl GroupV1Convo { + pub fn new(ctx: Rc>, ds: &mut DS) -> Self { let config = Self::mls_create_config(); - let ctx = Rc::new(RefCell::new(ctx)); let mls_group = { let ctx_ref = ctx.borrow(); MlsGroup::new( - &*ctx_ref.provider(), + ctx_ref.provider(), ctx_ref.ident(), &config, ctx_ref.get_credential(), @@ -99,11 +100,11 @@ impl GroupV1Convo { let ctx_borrow = ctx.borrow(); let provider = ctx_borrow.provider(); - StagedWelcome::build_from_welcome(&*provider, &Self::mls_join_config(), welcome) + StagedWelcome::build_from_welcome(provider, &Self::mls_join_config(), welcome) .unwrap() .build() .unwrap() - .into_group(&*provider) + .into_group(provider) .unwrap() }; @@ -147,9 +148,9 @@ impl GroupV1Convo { } fn subscribe(ds: &mut DS, convo_id: &str) -> Result<(), ChatError> { - ds.subscribe(Self::delivery_address_from_id(&convo_id)) + ds.subscribe(&Self::delivery_address_from_id(&convo_id)) .map_err(ChatError::generic)?; - ds.subscribe(Self::ctrl_delivery_address_from_id(&convo_id)) + ds.subscribe(&Self::ctrl_delivery_address_from_id(&convo_id)) .map_err(ChatError::generic)?; Ok(()) @@ -190,14 +191,14 @@ impl GroupV1Convo { Self::ctrl_delivery_address_from_id(&self.convo_id) } - fn key_package_for_account( + fn key_package_for_account( &self, ctx: &mut ClientCtx, - ident: &str, + ident: &AccountId, ) -> Result { let retrieved_bytes = ctx .contact_registry() - .retreive(ident) + .retrieve(ident) .map_err(|e| ChatError::Generic(e.to_string()))?; // dbg!(ctx.contact_registry()); @@ -214,13 +215,13 @@ impl GroupV1Convo { } } -impl Id for GroupV1Convo { +impl Id for GroupV1Convo { fn id(&self) -> ConversationId<'_> { &self.convo_id } } -impl Convo for GroupV1Convo { +impl Convo for GroupV1Convo { fn send_message( &mut self, content: &[u8], @@ -229,7 +230,7 @@ impl Convo for GroupV1Convo { let provider = ctx_ref.provider(); let mls_message_out = self .mls_group - .create_message(&*provider, ctx_ref.ident(), content) + .create_message(provider, ctx_ref.ident(), content) .unwrap(); let a = AddressedEncryptedPayload { @@ -281,7 +282,7 @@ impl Convo for GroupV1Convo { let processed = self .mls_group - .process_message(&*provider, protocol_message) + .process_message(provider, protocol_message) .map_err(ChatError::generic)?; match processed.into_content() { @@ -292,7 +293,7 @@ impl Convo for GroupV1Convo { })), ProcessedMessageContent::StagedCommitMessage(commit) => { self.mls_group - .merge_staged_commit(&*provider, *commit) + .merge_staged_commit(provider, *commit) .map_err(ChatError::generic)?; Ok(None) } @@ -313,13 +314,13 @@ impl Convo for GroupV1Convo { } } -impl +impl GroupConvo for GroupV1Convo { fn add_member( &mut self, ctx: &mut ClientCtx, - members: &[&str], + members: &[&AccountId], ) -> Result<(), ChatError> { // add_members returns: // commit — the Commit message Alice broadcasts to all members @@ -345,16 +346,14 @@ impl let (commit, welcome, _group_info) = self .mls_group - .add_members(&*provider, ctx_ref.ident(), keypkgs.iter().as_slice()) + .add_members(provider, ctx_ref.ident(), keypkgs.iter().as_slice()) .unwrap(); - self.mls_group.merge_pending_commit(&*provider).unwrap(); + self.mls_group.merge_pending_commit(provider).unwrap(); // TODO: (P3) Evaluate privacy/performance implications of an aggregated Welcome for multiple users for account_id in members { - ctx_ref - .init() - .invite_to_group_v1(ctx, account_id, &welcome)?; + ctx_ref.invite_user(ctx, account_id, &welcome)?; } let encrypted_payload = EncryptedPayload { diff --git a/core/conversations/src/ctx.rs b/core/conversations/src/ctx.rs index 8264ff0..d540a9f 100644 --- a/core/conversations/src/ctx.rs +++ b/core/conversations/src/ctx.rs @@ -6,14 +6,15 @@ use std::{ use storage::ChatStore; use crate::{DeliveryService, RegistrationService}; +use crate::service_traits::KeyPackageProvider; -pub struct ClientCtx { +pub struct ClientCtx { ds: DS, contact_registry: RS, convo_store: Rc>, // TODO: (P2) Remove Rc/Refcell } -impl<'a, DS: DeliveryService, RS: RegistrationService, CS: ChatStore> ClientCtx { +impl<'a, DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore> ClientCtx { pub fn new(ds: DS, contact_registry: RS, convo_store: Rc>) -> Self { Self { ds, @@ -26,11 +27,17 @@ impl<'a, DS: DeliveryService, RS: RegistrationService, CS: ChatStore> ClientCtx< &mut self.ds } - pub fn contact_registry(&'a mut self) -> &'a mut RS { - &mut self.contact_registry + pub fn contact_registry(&'a self) -> &'a RS { + &self.contact_registry } pub fn store(&'a self) -> RefMut<'a, CS> { self.convo_store.borrow_mut() } } + +impl<'a, DS: DeliveryService, RS: RegistrationService, CS: ChatStore> ClientCtx { + pub fn contact_registry_mut(&'a mut self) -> &'a mut RS { + &mut self.contact_registry + } +} diff --git a/core/conversations/src/external_traits.rs b/core/conversations/src/external_traits.rs deleted file mode 100644 index c5422c9..0000000 --- a/core/conversations/src/external_traits.rs +++ /dev/null @@ -1,15 +0,0 @@ -use std::{fmt::Debug, fmt::Display}; - -use crate::types::AddressedEnvelope; - -pub trait DeliveryService { - type Error: Display; - fn publish(&mut self, envelope: AddressedEnvelope) -> Result<(), Self::Error>; - fn subscribe(&mut self, delivery_address: String) -> Result<(), Self::Error>; -} - -pub trait RegistrationService: Debug { - type Error: Display; - fn register(&mut self, identity: String, key_bundle: Vec) -> Result<(), Self::Error>; - fn retreive(&self, identity: &str) -> Result>, Self::Error>; -} diff --git a/core/conversations/src/inbox_v2.rs b/core/conversations/src/inbox_v2.rs index 8bcbf5f..f072726 100644 --- a/core/conversations/src/inbox_v2.rs +++ b/core/conversations/src/inbox_v2.rs @@ -1,131 +1,108 @@ -use std::cell::{Ref, RefCell}; +use std::cell::RefCell; use std::rc::Rc; use chat_proto::logoschat::envelope::EnvelopeV1; -use crypto::Ed25519SigningKey; -use crypto::Ed25519VerifyingKey; use openmls::prelude::tls_codec::Serialize; use openmls::prelude::*; use openmls_libcrux_crypto::Provider as LibcruxProvider; -use openmls_traits::signatures::Signer; use prost::{Message, Oneof}; -use std::sync::atomic::{AtomicUsize, Ordering}; use storage::ChatStore; use storage::ConversationMeta; use crate::AddressedEnvelope; use crate::ChatError; use crate::DeliveryService; -use crate::RegistrationService; +use crate::account::LogosAccount; use crate::conversation::GroupConvo; -use crate::conversation::group_v1::{MlsCtx, MlsInitializer}; +use crate::conversation::group_v1::MlsContext; use crate::conversation::{GroupV1Convo, IdentityProvider}; use crate::ctx::ClientCtx; +use crate::types::AccountId; use crate::utils::{blake2b_hex, hash_size}; - -static ACCOUNT_COUNTER: AtomicUsize = AtomicUsize::new(0); -const ACCOUNT_NAMES: &[&str] = &["Saro", "Raya", "Pax"]; -#[derive(Clone)] -pub struct LogosAccount { - id: String, - signing_key: Ed25519SigningKey, - // x25519_key: crypto::PrivateKey, +use crate::RegistrationService; +use crate::service_traits::KeyPackageProvider; +pub struct PqMlsContext { + ident_provider: LogosAccount, + provider: LibcruxProvider, } -impl LogosAccount { - pub fn new() -> Self { - let idx = ACCOUNT_COUNTER.fetch_add(1, Ordering::Relaxed); - - let id = if idx < ACCOUNT_NAMES.len() { - ACCOUNT_NAMES[idx % ACCOUNT_NAMES.len()].to_string() - } else { - use rand_core::{OsRng, RngCore}; - const CHARSET: &[u8] = - b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - let i: String = (0..8) - .map(|_| { - let idx = (OsRng.next_u32() as usize) % CHARSET.len(); - CHARSET[idx] as char - }) - .collect(); - i - }; - Self { - id, - signing_key: Ed25519SigningKey::generate(), - // x25519_key: crypto::PrivateKey::random(), - } - } -} - -impl Signer for LogosAccount { - 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 friendly_name(&self) -> String { - self.id.clone() - } - - fn public_key(&self) -> Ed25519VerifyingKey { - self.signing_key.verifying_key() - } -} - -#[derive(Clone)] -pub struct MlsContext { - pub ident_provider: LogosAccount, - pub initializer: Init, - provider: Rc>, -} - -impl MlsCtx for MlsContext { +impl MlsContext for PqMlsContext { type IDENT = LogosAccount; - type INIT = Init; fn ident(&self) -> &LogosAccount { &self.ident_provider } - fn provider(&self) -> Ref<'_, LibcruxProvider> { - self.provider.borrow() + fn provider(&self) -> &LibcruxProvider { + &self.provider } - fn init(&self) -> &Init { - &self.initializer - } + fn invite_user( + &self, + ctx: &mut ClientCtx, + account_id: &AccountId, + welcome: &MlsMessageOut, + ) -> Result<(), ChatError> { + let invite = GroupV1HeavyInvite { + welcome_bytes: welcome.to_bytes()?, + }; - // Build an MLS Credential from the supplied IdentityProvider - fn get_credential(&self) -> CredentialWithKey { - CredentialWithKey { - credential: BasicCredential::new(self.ident_provider.friendly_name().into()).into(), - signature_key: self.ident_provider.public_key().as_ref().into(), - } + let frame = InboxV2Frame { + payload: Some(InviteType::GroupV1(invite)), + }; + + let envelope = EnvelopeV1 { + conversation_hint: ProtocolParams::conversation_id_for_account_id(&account_id), + salt: 0, + payload: frame.encode_to_vec().into(), + }; + + let outbound_msg = AddressedEnvelope { + delivery_address: ProtocolParams::delivery_address_for_account_id(&account_id), + data: envelope.encode_to_vec(), + }; + + ctx.ds().publish(outbound_msg).map_err(ChatError::generic)?; + Ok(()) } } -#[derive(Clone)] +struct InboxProtocolParams {} + +impl InboxProtocolParams { + fn delivery_address_for_account_id(account_id: &AccountId) -> String { + blake2b_hex::(&["InboxV2|", "delivery_address|", account_id.as_str()]) + } + + fn conversation_id_for_account_id(account_id: &AccountId) -> String { + blake2b_hex::(&["InboxV2|", "conversation_id|", account_id.as_str()]) + } +} + +type ProtocolParams = InboxProtocolParams; + pub struct InboxV2 { - pub account: LogosAccount, // TODO: (!) don't expose account - mls_provider: Rc>, + account_id: AccountId, + ctx: Rc>, } impl<'a> InboxV2 { - pub fn new() -> Self { - let account = LogosAccount::new(); - let mls_provider = Rc::new(RefCell::new(LibcruxProvider::new().unwrap())); + pub fn new_with_account(account: LogosAccount) -> Self { + let account_id = account.account_id().clone(); + let provider = LibcruxProvider::new().unwrap(); Self { - account, - mls_provider, + account_id, + ctx: Rc::new(RefCell::new(PqMlsContext { + ident_provider: account, + provider, + })), } } + pub fn account_id(&self) -> &AccountId { + &self.account_id + } + pub fn register( &mut self, ctx: &mut ClientCtx, @@ -134,29 +111,29 @@ impl<'a> InboxV2 { let bytes = keypackage.tls_serialize_detached()?; - ctx.contact_registry() - .register(self.account.friendly_name(), bytes) + ctx.contact_registry_mut() + .register(&self.ctx.borrow().ident_provider.friendly_name(), bytes) .map_err(ChatError::generic)?; //TODO: (P1) create an address scheme instead of using names Ok(()) } pub fn delivery_address(&self) -> String { - Self::delivery_address_for_account_id(&self.account.id) + ProtocolParams::delivery_address_for_account_id(&self.account_id) } pub fn id(&self) -> String { - Self::conversation_id_for_account_id(&self.account.id) + ProtocolParams::conversation_id_for_account_id(&self.account_id) } - pub fn create_group_v1( + pub fn create_group_v1( &self, ctx: &mut ClientCtx, - ) -> Result>, ChatError> { + ) -> Result, ChatError> { let convo = GroupV1Convo::new(self.assemble_ctx(), ctx.ds()); Ok(convo) } - pub fn handle_frame( + pub fn handle_frame( &self, ctx: &mut ClientCtx, payload_bytes: &[u8], @@ -174,15 +151,11 @@ impl<'a> InboxV2 { } } - fn assemble_ctx(&self) -> MlsContext { - MlsContext { - ident_provider: self.account.clone(), - initializer: self.clone(), - provider: self.mls_provider.clone(), - } + fn assemble_ctx(&self) -> Rc> { + self.ctx.clone() } - fn persist_convo( + fn persist_convo( &self, ctx: &'a ClientCtx, convo: impl GroupConvo, @@ -199,7 +172,7 @@ impl<'a> InboxV2 { Ok(()) } - fn handle_heavy_invite( + fn handle_heavy_invite( &self, ctx: &mut ClientCtx, invite: GroupV1HeavyInvite, @@ -213,15 +186,12 @@ impl<'a> InboxV2 { )); }; - let mls_ctx = Rc::new(RefCell::new(self.assemble_ctx())); - - let convo = GroupV1Convo::new_from_welcome(mls_ctx, ctx.ds(), welcome); + let convo = GroupV1Convo::new_from_welcome(self.assemble_ctx(), ctx.ds(), welcome); self.persist_convo(ctx, convo) } fn create_keypackage(&self) -> Result { - let mls_ctx = self.assemble_ctx(); - + let ctx_borrow = self.ctx.borrow(); let capabilities = Capabilities::builder() .ciphersuites(vec![ Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519, @@ -232,70 +202,28 @@ impl<'a> InboxV2 { .leaf_node_capabilities(capabilities) .build( Ciphersuite::MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519, - &*mls_ctx.provider(), - &self.account, - mls_ctx.get_credential(), + ctx_borrow.provider(), + ctx_borrow.ident(), + ctx_borrow.get_credential(), ) .expect("Failed to build KeyPackage"); Ok(a.key_package().clone()) } - fn delivery_address_for_account_id(account_id: &str) -> String { - blake2b_hex::(&["InboxV2|", "delivery_address|", account_id]) - } - - fn conversation_id_for_account_id(account_id: &str) -> String { - blake2b_hex::(&["InboxV2|", "conversation_id|", account_id]) - } - - pub fn load_mls_convo( + pub fn load_mls_convo( &self, ctx: &mut ClientCtx, convo_id: String, - ) -> Result>, ChatError> { - let mls_ctx = self.assemble_ctx(); - + ) -> Result, ChatError> { let group_id_bytes = hex::decode(&convo_id).map_err(ChatError::generic)?; let group_id = GroupId::from_slice(&group_id_bytes); - let convo = - GroupV1Convo::load(Rc::new(RefCell::new(mls_ctx)), ctx.ds(), convo_id, group_id)?; + let convo = GroupV1Convo::load(self.assemble_ctx(), ctx.ds(), convo_id, group_id)?; Ok(convo) } } -impl MlsInitializer for InboxV2 { - fn invite_to_group_v1( - &self, - ctx: &mut ClientCtx, - account_id: &str, - welcome: &MlsMessageOut, - ) -> Result<(), ChatError> { - let invite = GroupV1HeavyInvite { - welcome_bytes: welcome.to_bytes()?, - }; - - let frame = InboxV2Frame { - payload: Some(InviteType::GroupV1(invite)), - }; - - let envelope = EnvelopeV1 { - conversation_hint: Self::conversation_id_for_account_id(account_id), - salt: 0, - payload: frame.encode_to_vec().into(), - }; - - let outbound_msg = AddressedEnvelope { - delivery_address: Self::delivery_address_for_account_id(account_id), - data: envelope.encode_to_vec(), - }; - - ctx.ds().publish(outbound_msg).map_err(ChatError::generic)?; - Ok(()) - } -} - #[derive(Clone, PartialEq, Message)] pub struct InboxV2Frame { #[prost(oneof = "InviteType", tags = "1")] diff --git a/core/conversations/src/lib.rs b/core/conversations/src/lib.rs index 63417a4..516edeb 100644 --- a/core/conversations/src/lib.rs +++ b/core/conversations/src/lib.rs @@ -4,7 +4,7 @@ mod conversation; mod crypto; mod ctx; mod errors; -mod external_traits; +mod service_traits; mod inbox; mod inbox_v2; mod proto; @@ -18,7 +18,8 @@ mod test_utils; pub use context::{Context, ConversationId, ConversationIdOwned, Introduction}; pub use conversation::GroupConvo; pub use errors::ChatError; -pub use external_traits::{DeliveryService, RegistrationService}; +pub use service_traits::{DeliveryService, RegistrationService}; pub use sqlite::ChatStorage; pub use sqlite::StorageConfig; -pub use types::{AddressedEnvelope, ContentData}; +pub use types::{AccountId, AddressedEnvelope, ContentData}; +pub use utils::hex_trunc; diff --git a/core/conversations/src/service_traits.rs b/core/conversations/src/service_traits.rs new file mode 100644 index 0000000..028a1c6 --- /dev/null +++ b/core/conversations/src/service_traits.rs @@ -0,0 +1,38 @@ +use std::{fmt::Debug, fmt::Display}; + +use crate::types::{AccountId, AddressedEnvelope}; + +/// A Delivery service is responsible for payload transport. +/// This interface allows Conversations to send payloads on the wire as well as +/// register interest in delivery_addresses. Client implementations are responsible +/// for providing the inbound payloads to Context::handle_payload. +pub trait DeliveryService: Debug { + type Error: Display; + fn publish(&mut self, envelope: AddressedEnvelope) -> Result<(), Self::Error>; + fn subscribe(&mut self, delivery_address: &str) -> Result<(), Self::Error>; +} + +/// Manages key bundle storage for MLS group creation/addition while contacts are +/// offline. +/// +/// Implement this to provide a contact registry — ach participant publishes their key package +/// on registration; others fetch it to initiate a conversation. +pub trait RegistrationService: Debug { + type Error: Display; + fn register(&mut self, identity: &str, key_bundle: Vec) -> Result<(), Self::Error>; + fn retrieve(&self, identity: &AccountId) -> Result>, Self::Error>; +} + +/// Read-only view of a contact registry. Not part of the public API. +/// Satisfied automatically by any `RegistrationService` implementation. +pub trait KeyPackageProvider: Debug { + type Error: Display; + fn retrieve(&self, identity: &AccountId) -> Result>, Self::Error>; +} + +impl KeyPackageProvider for T { + type Error = T::Error; + fn retrieve(&self, identity: &AccountId) -> Result>, Self::Error> { + RegistrationService::retrieve(self, identity) + } +} diff --git a/core/conversations/src/test_utils.rs b/core/conversations/src/test_utils.rs index 2f23da6..cceb668 100644 --- a/core/conversations/src/test_utils.rs +++ b/core/conversations/src/test_utils.rs @@ -10,7 +10,7 @@ use storage::{ConversationMeta, ConversationStore, IdentityStore}; use storage::{EphemeralKeyStore, RatchetStore}; use crate::{ - AddressedEnvelope, DeliveryService, RegistrationService, + AccountId, AddressedEnvelope, DeliveryService, RegistrationService, KeyPackageProvider, utils::{blake2b_hex, hash_size::Testing}, }; @@ -109,17 +109,17 @@ impl DeliveryService for LocalBroadcaster { Ok(()) } - fn subscribe(&mut self, delivery_address: String) -> Result<(), Self::Error> { + fn subscribe(&mut self, delivery_address: &str) -> Result<(), Self::Error> { // Strict temporal ordering of subscriptions is not enforced. // Subscruptions are evaluated on polling, not when the message is published - self.subscriptions.insert(delivery_address); + self.subscriptions.insert(delivery_address.to_string()); Ok(()) } } /// A Contact Registry used for Tests. /// This implementation stores bundle bytes and then returns them when -/// retreived +/// retrieved /// #[derive(Clone)] @@ -159,16 +159,18 @@ impl Debug for EphemeralRegistry { } } -impl RegistrationService for EphemeralRegistry { +impl KeyPackageProvider for EphemeralRegistry { type Error = String; - fn register(&mut self, identity: String, key_bundle: Vec) -> Result<(), Self::Error> { - self.registry.lock().unwrap().insert(identity, key_bundle); - Ok(()) + fn retrieve(&self, identity: &AccountId) -> Result>, Self::Error> { + Ok(self.registry.lock().unwrap().get(identity.as_str()).cloned()) } +} - fn retreive(&self, identity: &str) -> Result>, Self::Error> { - Ok(self.registry.lock().unwrap().get(identity).cloned()) +impl RegistrationService for EphemeralRegistry { + fn register(&mut self, identity: &str, key_bundle: Vec) -> Result<(), Self::Error> { + self.registry.lock().unwrap().insert(identity.to_string(), key_bundle); + Ok(()) } } diff --git a/core/conversations/src/types.rs b/core/conversations/src/types.rs index b5b174b..9564234 100644 --- a/core/conversations/src/types.rs +++ b/core/conversations/src/types.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::fmt::{self, Debug}; use crate::proto::{self, Message}; diff --git a/core/integration_tests_core/tests/mls_integration.rs b/core/integration_tests_core/tests/mls_integration.rs index ff81b30..1769725 100644 --- a/core/integration_tests_core/tests/mls_integration.rs +++ b/core/integration_tests_core/tests/mls_integration.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; use components::{EphemeralRegistry, LocalBroadcaster, MemStore}; -use libchat::{ChatStorage, ContentData, Context, ConversationId, GroupConvo}; +use libchat::{ChatStorage, ContentData, Context, ConversationId, GroupConvo, hex_trunc}; type TestContext = Context; @@ -77,7 +77,7 @@ impl DerefMut for Client { fn pretty_print(prefix: impl Into) -> Box { let prefix = prefix.into(); return Box::new(move |c: ContentData| { - let cid = c.conversation_id.as_bytes(); + let cid = hex_trunc(c.conversation_id.as_bytes()); let content = String::from_utf8(c.data).unwrap(); println!("{} ({:?}) {}", prefix, cid, content) }); @@ -106,10 +106,8 @@ fn create_group() { const SARO: usize = 0; const RAYA: usize = 1; - let raya_id = clients[RAYA].account_id(); - let s_convo = clients[SARO] - .create_group_convo(&[raya_id.as_ref()]) - .unwrap(); + let raya_id = clients[RAYA].account_id().clone(); + let s_convo = clients[SARO].create_group_convo(&[&raya_id]).unwrap(); let convo_id = s_convo.id(); @@ -143,10 +141,10 @@ fn create_group() { clients.push(Client::init(pax_ctx, Some(pretty_print(" Pax")))); const PAX: usize = 2; - let pax_id = clients[PAX].account_id(); + let pax_id = clients[PAX].account_id().clone(); clients[SARO] .convo(convo_id) - .add_member(&mut clients[SARO].client_ctx(), &[pax_id.as_ref()]) + .add_member(&mut clients[SARO].client_ctx(), &[&pax_id]) .unwrap(); // clients[SARO].process_messages(); diff --git a/extensions/components/src/contact_registry.rs b/extensions/components/src/contact_registry.rs index 02f2f26..bd67775 100644 --- a/extensions/components/src/contact_registry.rs +++ b/extensions/components/src/contact_registry.rs @@ -4,11 +4,11 @@ use std::{ sync::{Arc, Mutex}, }; -use libchat::RegistrationService; +use libchat::{AccountId, RegistrationService}; /// A Contact Registry used for Tests. /// This implementation stores bundle bytes and then returns them when -/// retreived +/// retrieved /// #[derive(Clone)] @@ -51,12 +51,17 @@ impl Debug for EphemeralRegistry { impl RegistrationService for EphemeralRegistry { type Error = String; - fn register(&mut self, identity: String, key_bundle: Vec) -> Result<(), Self::Error> { - self.registry.lock().unwrap().insert(identity, key_bundle); + fn register(&mut self, identity: &str, key_bundle: Vec) -> Result<(), Self::Error> { + self.registry.lock().unwrap().insert(identity.to_string(), key_bundle); Ok(()) } - fn retreive(&self, identity: &str) -> Result>, Self::Error> { - Ok(self.registry.lock().unwrap().get(identity).cloned()) + fn retrieve(&self, identity: &AccountId) -> Result>, Self::Error> { + Ok(self + .registry + .lock() + .unwrap() + .get(identity.as_str()) + .cloned()) } } diff --git a/extensions/components/src/delivery/local_broadcaster.rs b/extensions/components/src/delivery/local_broadcaster.rs index 9828b26..a366ad2 100644 --- a/extensions/components/src/delivery/local_broadcaster.rs +++ b/extensions/components/src/delivery/local_broadcaster.rs @@ -107,10 +107,10 @@ impl DeliveryService for LocalBroadcaster { Ok(()) } - fn subscribe(&mut self, delivery_address: String) -> Result<(), Self::Error> { + fn subscribe(&mut self, delivery_address: &str) -> Result<(), Self::Error> { // Strict temporal ordering of subscriptions is not enforced. // Subscriptions are evaluated on polling, not when the message is published - self.subscriptions.insert(delivery_address); + self.subscriptions.insert(delivery_address.to_string()); Ok(()) } }