Update Accounts + service_traits

This commit is contained in:
Jazz Turner-Baggs 2026-04-24 14:21:15 -07:00
parent def297f132
commit 3bf8ecb904
No known key found for this signature in database
14 changed files with 240 additions and 263 deletions

View File

@ -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<String>) -> 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
}
}

View File

@ -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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore + 'static> 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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore + 'static> 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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore + 'static> 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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore + 'static> Cont
pub fn create_group_convo(
&mut self,
participants: &[&str],
participants: &[&AccountId],
) -> Result<Box<dyn GroupConvo<DS, RS, CS>>, ChatError> {
let mut convo = self.pq_inbox.create_group_v1(&mut self.client_ctx)?;
self.client_ctx

View File

@ -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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>: Convo {
pub trait GroupConvo<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>: Convo {
fn add_member(
&mut self,
ctx: &mut ClientCtx<DS, RS, CS>,
members: &[&str],
members: &[&AccountId],
) -> Result<(), ChatError>;
// Default implementation which dispatches envelopes to the DeliveryService

View File

@ -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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
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<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
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<Ctx: MlsCtx> {
pub struct GroupV1Convo<Ctx: MlsContext> {
ctx: Rc<RefCell<Ctx>>,
pub(crate) mls_group: MlsGroup, // TODO: (!) Fix Visibility
convo_id: String,
}
impl<Ctx: MlsCtx> std::fmt::Debug for GroupV1Convo<Ctx> {
impl<Ctx: MlsContext> std::fmt::Debug for GroupV1Convo<Ctx> {
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<Ctx: MlsCtx> std::fmt::Debug for GroupV1Convo<Ctx> {
}
}
impl<Ctx: MlsCtx> GroupV1Convo<Ctx> {
pub fn new<DS: DeliveryService>(ctx: Ctx, ds: &mut DS) -> Self {
impl<Ctx: MlsContext> GroupV1Convo<Ctx> {
pub fn new<DS: DeliveryService>(ctx: Rc<RefCell<Ctx>>, 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<Ctx: MlsCtx> GroupV1Convo<Ctx> {
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<Ctx: MlsCtx> GroupV1Convo<Ctx> {
}
fn subscribe<DS: DeliveryService>(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<Ctx: MlsCtx> GroupV1Convo<Ctx> {
Self::ctrl_delivery_address_from_id(&self.convo_id)
}
fn key_package_for_account<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
fn key_package_for_account<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
ident: &str,
ident: &AccountId,
) -> Result<KeyPackage, ChatError> {
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<Ctx: MlsCtx> GroupV1Convo<Ctx> {
}
}
impl<Ctx: MlsCtx> Id for GroupV1Convo<Ctx> {
impl<Ctx: MlsContext> Id for GroupV1Convo<Ctx> {
fn id(&self) -> ConversationId<'_> {
&self.convo_id
}
}
impl<Ctx: MlsCtx> Convo for GroupV1Convo<Ctx> {
impl<Ctx: MlsContext> Convo for GroupV1Convo<Ctx> {
fn send_message(
&mut self,
content: &[u8],
@ -229,7 +230,7 @@ impl<Ctx: MlsCtx> Convo for GroupV1Convo<Ctx> {
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<Ctx: MlsCtx> Convo for GroupV1Convo<Ctx> {
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<Ctx: MlsCtx> Convo for GroupV1Convo<Ctx> {
})),
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<Ctx: MlsCtx> Convo for GroupV1Convo<Ctx> {
}
}
impl<Ctx: MlsCtx, DS: DeliveryService, RS: RegistrationService, CS: ChatStore>
impl<Ctx: MlsContext, DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>
GroupConvo<DS, RS, CS> for GroupV1Convo<Ctx>
{
fn add_member(
&mut self,
ctx: &mut ClientCtx<DS, RS, CS>,
members: &[&str],
members: &[&AccountId],
) -> Result<(), ChatError> {
// add_members returns:
// commit — the Commit message Alice broadcasts to all members
@ -345,16 +346,14 @@ impl<Ctx: MlsCtx, DS: DeliveryService, RS: RegistrationService, CS: ChatStore>
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 {

View File

@ -6,14 +6,15 @@ use std::{
use storage::ChatStore;
use crate::{DeliveryService, RegistrationService};
use crate::service_traits::KeyPackageProvider;
pub struct ClientCtx<DS: DeliveryService, RS: RegistrationService, CS: ChatStore> {
pub struct ClientCtx<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore> {
ds: DS,
contact_registry: RS,
convo_store: Rc<RefCell<CS>>, // TODO: (P2) Remove Rc/Refcell
}
impl<'a, DS: DeliveryService, RS: RegistrationService, CS: ChatStore> ClientCtx<DS, RS, CS> {
impl<'a, DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore> ClientCtx<DS, RS, CS> {
pub fn new(ds: DS, contact_registry: RS, convo_store: Rc<RefCell<CS>>) -> 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<DS, RS, CS> {
pub fn contact_registry_mut(&'a mut self) -> &'a mut RS {
&mut self.contact_registry
}
}

View File

@ -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<u8>) -> Result<(), Self::Error>;
fn retreive(&self, identity: &str) -> Result<Option<Vec<u8>>, Self::Error>;
}

View File

@ -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<Vec<u8>, 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<Init: MlsInitializer> {
pub ident_provider: LogosAccount,
pub initializer: Init,
provider: Rc<RefCell<LibcruxProvider>>,
}
impl<Init: MlsInitializer + Clone> MlsCtx for MlsContext<Init> {
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<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
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::<hash_size::AccountId>(&["InboxV2|", "delivery_address|", account_id.as_str()])
}
fn conversation_id_for_account_id(account_id: &AccountId) -> String {
blake2b_hex::<hash_size::Testing>(&["InboxV2|", "conversation_id|", account_id.as_str()])
}
}
type ProtocolParams = InboxProtocolParams;
pub struct InboxV2 {
pub account: LogosAccount, // TODO: (!) don't expose account
mls_provider: Rc<RefCell<LibcruxProvider>>,
account_id: AccountId,
ctx: Rc<RefCell<PqMlsContext>>,
}
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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
&mut self,
ctx: &mut ClientCtx<DS, RS, CS>,
@ -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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
pub fn create_group_v1<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
) -> Result<GroupV1Convo<MlsContext<InboxV2>>, ChatError> {
) -> Result<GroupV1Convo<PqMlsContext>, ChatError> {
let convo = GroupV1Convo::new(self.assemble_ctx(), ctx.ds());
Ok(convo)
}
pub fn handle_frame<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
pub fn handle_frame<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
payload_bytes: &[u8],
@ -174,15 +151,11 @@ impl<'a> InboxV2 {
}
}
fn assemble_ctx(&self) -> MlsContext<InboxV2> {
MlsContext {
ident_provider: self.account.clone(),
initializer: self.clone(),
provider: self.mls_provider.clone(),
}
fn assemble_ctx(&self) -> Rc<RefCell<PqMlsContext>> {
self.ctx.clone()
}
fn persist_convo<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
fn persist_convo<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &'a ClientCtx<DS, RS, CS>,
convo: impl GroupConvo<DS, RS, CS>,
@ -199,7 +172,7 @@ impl<'a> InboxV2 {
Ok(())
}
fn handle_heavy_invite<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
fn handle_heavy_invite<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
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<KeyPackage, ChatError> {
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::<hash_size::AccountId>(&["InboxV2|", "delivery_address|", account_id])
}
fn conversation_id_for_account_id(account_id: &str) -> String {
blake2b_hex::<hash_size::Testing>(&["InboxV2|", "conversation_id|", account_id])
}
pub fn load_mls_convo<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
pub fn load_mls_convo<DS: DeliveryService, RS: KeyPackageProvider, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
convo_id: String,
) -> Result<GroupV1Convo<MlsContext<InboxV2>>, ChatError> {
let mls_ctx = self.assemble_ctx();
) -> Result<GroupV1Convo<PqMlsContext>, 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<DS: DeliveryService, RS: RegistrationService, CS: ChatStore>(
&self,
ctx: &mut ClientCtx<DS, RS, CS>,
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")]

View File

@ -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;

View File

@ -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<u8>) -> Result<(), Self::Error>;
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, 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<Option<Vec<u8>>, Self::Error>;
}
impl<T: RegistrationService> KeyPackageProvider for T {
type Error = T::Error;
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, Self::Error> {
RegistrationService::retrieve(self, identity)
}
}

View File

@ -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<u8>) -> Result<(), Self::Error> {
self.registry.lock().unwrap().insert(identity, key_bundle);
Ok(())
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(self.registry.lock().unwrap().get(identity.as_str()).cloned())
}
}
fn retreive(&self, identity: &str) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(self.registry.lock().unwrap().get(identity).cloned())
impl RegistrationService for EphemeralRegistry {
fn register(&mut self, identity: &str, key_bundle: Vec<u8>) -> Result<(), Self::Error> {
self.registry.lock().unwrap().insert(identity.to_string(), key_bundle);
Ok(())
}
}

View File

@ -1,4 +1,4 @@
use std::fmt;
use std::fmt::{self, Debug};
use crate::proto::{self, Message};

View File

@ -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<LocalBroadcaster, EphemeralRegistry, ChatStorage>;
@ -77,7 +77,7 @@ impl DerefMut for Client {
fn pretty_print(prefix: impl Into<String>) -> Box<dyn Fn(ContentData)> {
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();

View File

@ -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<u8>) -> Result<(), Self::Error> {
self.registry.lock().unwrap().insert(identity, key_bundle);
fn register(&mut self, identity: &str, key_bundle: Vec<u8>) -> Result<(), Self::Error> {
self.registry.lock().unwrap().insert(identity.to_string(), key_bundle);
Ok(())
}
fn retreive(&self, identity: &str) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(self.registry.lock().unwrap().get(identity).cloned())
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, Self::Error> {
Ok(self
.registry
.lock()
.unwrap()
.get(identity.as_str())
.cloned())
}
}

View File

@ -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(())
}
}