mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-05-12 04:59:27 +00:00
Update Accounts + service_traits
This commit is contained in:
parent
def297f132
commit
3bf8ecb904
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>;
|
||||
}
|
||||
@ -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")]
|
||||
|
||||
@ -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;
|
||||
|
||||
38
core/conversations/src/service_traits.rs
Normal file
38
core/conversations/src/service_traits.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{self, Debug};
|
||||
|
||||
use crate::proto::{self, Message};
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user