mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-06-30 21:20:09 +00:00
update de-mls setup
This commit is contained in:
parent
0165612695
commit
d3ff02ea4e
100
Cargo.lock
generated
100
Cargo.lock
generated
@ -774,7 +774,7 @@ dependencies = [
|
|||||||
"objc2-foundation",
|
"objc2-foundation",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.59.0",
|
||||||
"x11rb",
|
"x11rb",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1814,7 +1814,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "de-mls"
|
name = "de-mls"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
source = "git+https://github.com/vacp2p/de-mls?branch=refactor%2Fmls-contract-engine-split#a87b5eba396a36037174e59b28ef561fada179ca"
|
source = "git+https://github.com/vacp2p/de-mls?branch=refactor%2Fmls-contract-engine-split#4269e2faccd0855840492313c201b76660457827"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashgraph-like-consensus",
|
"hashgraph-like-consensus",
|
||||||
"indexmap 2.14.0",
|
"indexmap 2.14.0",
|
||||||
@ -4493,7 +4493,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6427,7 +6427,7 @@ version = "0.52.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6436,16 +6436,7 @@ version = "0.59.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"windows-targets",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-sys"
|
|
||||||
version = "0.60.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
|
|
||||||
dependencies = [
|
|
||||||
"windows-targets 0.53.5",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6463,31 +6454,14 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows_aarch64_gnullvm 0.52.6",
|
"windows_aarch64_gnullvm",
|
||||||
"windows_aarch64_msvc 0.52.6",
|
"windows_aarch64_msvc",
|
||||||
"windows_i686_gnu 0.52.6",
|
"windows_i686_gnu",
|
||||||
"windows_i686_gnullvm 0.52.6",
|
"windows_i686_gnullvm",
|
||||||
"windows_i686_msvc 0.52.6",
|
"windows_i686_msvc",
|
||||||
"windows_x86_64_gnu 0.52.6",
|
"windows_x86_64_gnu",
|
||||||
"windows_x86_64_gnullvm 0.52.6",
|
"windows_x86_64_gnullvm",
|
||||||
"windows_x86_64_msvc 0.52.6",
|
"windows_x86_64_msvc",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows-targets"
|
|
||||||
version = "0.53.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
|
|
||||||
dependencies = [
|
|
||||||
"windows-link",
|
|
||||||
"windows_aarch64_gnullvm 0.53.1",
|
|
||||||
"windows_aarch64_msvc 0.53.1",
|
|
||||||
"windows_i686_gnu 0.53.1",
|
|
||||||
"windows_i686_gnullvm 0.53.1",
|
|
||||||
"windows_i686_msvc 0.53.1",
|
|
||||||
"windows_x86_64_gnu 0.53.1",
|
|
||||||
"windows_x86_64_gnullvm 0.53.1",
|
|
||||||
"windows_x86_64_msvc 0.53.1",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6496,96 +6470,48 @@ version = "0.52.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_aarch64_msvc"
|
name = "windows_aarch64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_aarch64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnu"
|
name = "windows_i686_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_gnullvm"
|
name = "windows_i686_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_i686_msvc"
|
name = "windows_i686_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_i686_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnu"
|
name = "windows_x86_64_gnu"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnu"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_gnullvm"
|
name = "windows_x86_64_gnullvm"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_gnullvm"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows_x86_64_msvc"
|
name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "windows_x86_64_msvc"
|
|
||||||
version = "0.53.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
|||||||
@ -7,33 +7,28 @@ use crate::{Content, WakeupService};
|
|||||||
use alloy::signers::local::PrivateKeySigner;
|
use alloy::signers::local::PrivateKeySigner;
|
||||||
use blake2::{Blake2b, Digest, digest::consts::U6};
|
use blake2::{Blake2b, Digest, digest::consts::U6};
|
||||||
use chat_proto::logoschat::encryption::{EncryptedPayload, Plaintext, encrypted_payload};
|
use chat_proto::logoschat::encryption::{EncryptedPayload, Plaintext, encrypted_payload};
|
||||||
use de_mls::core::{
|
|
||||||
ConsensusPlugin, ConsensusServiceFor, ConversationEvent, ConversationPluginsFactory,
|
|
||||||
DeterministicStewardList, PeerScoringService, ScoringConfig, StewardListConfig,
|
|
||||||
default_score_deltas,
|
|
||||||
};
|
|
||||||
use de_mls::defaults::{
|
use de_mls::defaults::{
|
||||||
DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList, InMemoryPeerScoreStorage,
|
DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList, InMemoryPeerScoreStorage,
|
||||||
};
|
};
|
||||||
use de_mls::member_id::MemberId;
|
|
||||||
use de_mls::mls_crypto::{KeyPackageBytes, MlsError, OpenMlsService};
|
|
||||||
use de_mls::protos::de_mls::messages::v1::{
|
use de_mls::protos::de_mls::messages::v1::{
|
||||||
AppMessage as AppMessageProto, MemberWelcome, app_message,
|
AppMessage as AppMessageProto, MemberWelcome, app_message,
|
||||||
};
|
};
|
||||||
use de_mls::session::{Conversation, ConversationConfig, ConversationDeps};
|
use de_mls::{
|
||||||
|
ConsensusPlugin, ConsensusServiceFor, Conversation, ConversationConfig, ConversationEvent,
|
||||||
|
DeterministicStewardList, PeerScoringService, ScoringConfig, StewardListConfig,
|
||||||
|
default_score_deltas,
|
||||||
|
};
|
||||||
use hashgraph_like_consensus::signing::EthereumConsensusSigner;
|
use hashgraph_like_consensus::signing::EthereumConsensusSigner;
|
||||||
use openmls::key_packages::KeyPackage;
|
use openmls::key_packages::KeyPackage;
|
||||||
use openmls::prelude::tls_codec::Serialize as _;
|
use openmls::prelude::tls_codec::Serialize as _;
|
||||||
use openmls_traits::signatures::Signer;
|
use openmls::prelude::{Capabilities, ExtensionType};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use shared_traits::{IdentId, IdentIdRef};
|
use shared_traits::{IdentId, IdentIdRef};
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tracing::{info, instrument, warn};
|
use tracing::{info, instrument, warn};
|
||||||
|
|
||||||
use crate::inbox_v2::{CIPHER_SUITE, MlsEphemeralPqProvider, MlsIdentityProvider};
|
use crate::inbox_v2::CIPHER_SUITE;
|
||||||
use crypto::{Ed25519Signature, Ed25519SigningKey, Ed25519VerifyingKey};
|
|
||||||
|
|
||||||
use crate::IdentityProvider;
|
use crate::IdentityProvider;
|
||||||
use crate::conversation::{ConversationIdRef, ExternalServices, ServiceContext};
|
use crate::conversation::{ConversationIdRef, ExternalServices, ServiceContext};
|
||||||
@ -46,62 +41,6 @@ use crate::{
|
|||||||
/// with the openmls (GroupV1) keypackage registered under the bare account id.
|
/// with the openmls (GroupV1) keypackage registered under the bare account id.
|
||||||
const DEMLS_KEYPACKAGE_NAMESPACE: &str = "demls";
|
const DEMLS_KEYPACKAGE_NAMESPACE: &str = "demls";
|
||||||
|
|
||||||
/// Owned, `Clone` identity de-mls's `Sig` can hold. A new type only because the
|
|
||||||
/// account identity (`S::IP`) is neither owned nor `Clone` here, and
|
|
||||||
/// `crypto::Identity` implements `IdentityProvider` only under `cfg(test)`.
|
|
||||||
/// Wrapped in [`MlsIdentityProvider`] to reuse its credential + `Signer`.
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct DemlsMember {
|
|
||||||
id: IdentId,
|
|
||||||
signing: Ed25519SigningKey,
|
|
||||||
verifying: Ed25519VerifyingKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DemlsMember {
|
|
||||||
fn new(name: impl Into<String>) -> Self {
|
|
||||||
let signing = Ed25519SigningKey::generate();
|
|
||||||
Self {
|
|
||||||
verifying: signing.verifying_key(),
|
|
||||||
signing,
|
|
||||||
id: IdentId::new(name.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IdentityProvider for DemlsMember {
|
|
||||||
fn id(&self) -> IdentIdRef<'_> {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn display_name(&self) -> String {
|
|
||||||
self.id.as_str().to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sign(&self, payload: &[u8]) -> Ed25519Signature {
|
|
||||||
self.signing.sign(payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn public_key(&self) -> &Ed25519VerifyingKey {
|
|
||||||
&self.verifying
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The de-mls signer: libchat's `MlsIdentityProvider` over a [`DemlsMember`].
|
|
||||||
/// Already a `Signer` + credential source; we also give it de-mls's `MemberId`
|
|
||||||
/// so the protocol-side identity bytes match the MLS credential's serialized
|
|
||||||
/// content (`id().as_str().as_bytes()`).
|
|
||||||
type DemlsSigner = MlsIdentityProvider<DemlsMember>;
|
|
||||||
|
|
||||||
impl MemberId for DemlsSigner {
|
|
||||||
fn member_id_bytes(&self) -> &[u8] {
|
|
||||||
self.id().as_str().as_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn member_id_display(&self) -> &str {
|
|
||||||
self.id().as_str()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Borrows an existing `IdentityProvider` but reports a namespaced `id()`,
|
/// Borrows an existing `IdentityProvider` but reports a namespaced `id()`,
|
||||||
/// so the same identity can register multiple keypackage "flavors"
|
/// so the same identity can register multiple keypackage "flavors"
|
||||||
/// (e.g. openmls vs. de-mls) without colliding in the registry.
|
/// (e.g. openmls vs. de-mls) without colliding in the registry.
|
||||||
@ -116,8 +55,8 @@ impl<'a> NamespacedIdentity<'a> {
|
|||||||
Self { inner, id }
|
Self { inner, id }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prefix(id: &IdentId, namesapce: &str) -> String {
|
fn prefix(id: &IdentId, namespace: &str) -> String {
|
||||||
format!("{namesapce}|{id}")
|
format!("{namespace}|{id}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,160 +78,88 @@ impl IdentityProvider for NamespacedIdentity<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The de-mls MLS service over libchat's PQ provider.
|
/// Local member id bytes — the account identity the protocol matches on,
|
||||||
type DemlsMls = OpenMlsService<MlsEphemeralPqProvider>;
|
/// shared with the MLS credential and the consensus member.
|
||||||
|
fn member_id<S: ExternalServices>(service_ctx: &ServiceContext<S>) -> Vec<u8> {
|
||||||
/// Reference de-mls plug-in factory over libchat's existing PQ provider. Holds
|
service_ctx.mls_identity.id().as_str().as_bytes().to_vec()
|
||||||
/// a clone of the signer (to mint key packages) and stashes the provider that
|
|
||||||
/// minted our key package so the matching `welcome_mls` reuses its private keys
|
|
||||||
/// — replacing the old key-registry namespacing workaround for private keys.
|
|
||||||
struct DemlsFactory {
|
|
||||||
signer: DemlsSigner,
|
|
||||||
pending_provider: RefCell<Option<MlsEphemeralPqProvider>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DemlsFactory {
|
/// `app_id` for outbound packets / echo-dedup — random per conversation.
|
||||||
fn new(signer: DemlsSigner) -> Self {
|
fn rand_app_id() -> Arc<[u8]> {
|
||||||
Self {
|
Arc::from(rand_string(5).as_bytes())
|
||||||
signer,
|
}
|
||||||
pending_provider: RefCell::new(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mint a single-use key package into a fresh provider, stashing that
|
/// Peer-scoring plug-in: the library default over in-memory storage.
|
||||||
/// provider so the matching `welcome_mls` can open the welcome with the key
|
fn make_scoring() -> DefaultPeerScoring {
|
||||||
/// package's private keys.
|
PeerScoringService::new(
|
||||||
fn generate_key_package(&self) -> Result<KeyPackageBytes, ChatError> {
|
InMemoryPeerScoreStorage::new(),
|
||||||
let provider = MlsEphemeralPqProvider::new().map_err(ChatError::generic)?;
|
default_score_deltas(),
|
||||||
let bundle = KeyPackage::builder()
|
ScoringConfig::default(),
|
||||||
.build(
|
)
|
||||||
CIPHER_SUITE,
|
}
|
||||||
&provider,
|
|
||||||
&self.signer,
|
/// Steward-list plug-in: the library default, seedless — the library stamps the
|
||||||
self.signer.get_credential(),
|
/// conversation-id sort salt when it builds the conversation.
|
||||||
)
|
fn make_steward() -> DefaultStewardList {
|
||||||
.map_err(ChatError::generic)?;
|
DeterministicStewardList::empty(StewardListConfig::default())
|
||||||
let bytes = bundle
|
}
|
||||||
.key_package()
|
|
||||||
.tls_serialize_detached()
|
/// Consensus service: the library default over a fresh in-memory store and a
|
||||||
.map_err(ChatError::generic)?;
|
/// random Ethereum consensus signer.
|
||||||
*self.pending_provider.borrow_mut() = Some(provider);
|
fn make_consensus() -> ConsensusServiceFor<DefaultConsensusPlugin> {
|
||||||
Ok(KeyPackageBytes::new(
|
ConsensusServiceFor::<DefaultConsensusPlugin>::new_with_components(
|
||||||
bytes,
|
DefaultConsensusPlugin::new_storage(),
|
||||||
self.signer.member_id_bytes().to_vec(),
|
DefaultConsensusPlugin::new_event_bus(),
|
||||||
))
|
EthereumConsensusSigner::new(PrivateKeySigner::random()),
|
||||||
|
10,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TEST-ONLY millisecond timers. de-mls deadlines are real wall-clock, so the
|
||||||
|
/// default 60s timers never fire under fast virtual time. Production needs a
|
||||||
|
/// real config injected from the caller, not these hardcoded values.
|
||||||
|
fn demls_config() -> ConversationConfig {
|
||||||
|
ConversationConfig {
|
||||||
|
commit_inactivity_duration: Duration::from_millis(50),
|
||||||
|
freeze_duration: Duration::from_millis(20),
|
||||||
|
voting_delay: Duration::from_millis(30),
|
||||||
|
election_voting_delay: Duration::from_millis(30),
|
||||||
|
consensus_timeout: Duration::from_millis(150),
|
||||||
|
proposal_expiration: Duration::from_millis(2000),
|
||||||
|
..ConversationConfig::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConversationPluginsFactory for DemlsFactory {
|
/// Joiner: mint a single-use key package into the user's shared MLS provider
|
||||||
type Mls = DemlsMls;
|
/// (storing its private keys there so the matching welcome opens), and return
|
||||||
type Scoring = DefaultPeerScoring;
|
/// the serialized public key package.
|
||||||
type StewardList = DefaultStewardList;
|
fn mint_key_package<S: ExternalServices>(
|
||||||
|
service_ctx: &ServiceContext<S>,
|
||||||
fn create_mls(
|
) -> Result<Vec<u8>, ChatError> {
|
||||||
&self,
|
let capabilities = Capabilities::builder()
|
||||||
conversation_id: String,
|
.ciphersuites(vec![CIPHER_SUITE])
|
||||||
key_package: &[u8],
|
.extensions(vec![ExtensionType::ApplicationId])
|
||||||
signer: &impl Signer,
|
.build();
|
||||||
) -> Result<Self::Mls, MlsError> {
|
let bundle = KeyPackage::builder()
|
||||||
OpenMlsService::new_as_creator(
|
.leaf_node_capabilities(capabilities)
|
||||||
conversation_id,
|
.build(
|
||||||
MlsEphemeralPqProvider::new()?,
|
CIPHER_SUITE,
|
||||||
key_package,
|
&service_ctx.mls_provider,
|
||||||
signer,
|
&service_ctx.mls_identity,
|
||||||
|
service_ctx.mls_identity.get_credential(),
|
||||||
)
|
)
|
||||||
}
|
.map_err(ChatError::generic)?;
|
||||||
|
bundle
|
||||||
fn welcome_mls(&self, welcome_bytes: &[u8]) -> Result<Option<Self::Mls>, MlsError> {
|
.key_package()
|
||||||
// Each conversation has its own factory and stash, and welcomes are
|
.tls_serialize_detached()
|
||||||
// routed only to the joiner that minted the key package. A missing
|
.map_err(ChatError::generic)
|
||||||
// provider is therefore a logic error here — not a "not for us" case —
|
|
||||||
// so surface it instead of silently yielding `None`.
|
|
||||||
let provider = self.pending_provider.borrow_mut().take().ok_or_else(|| {
|
|
||||||
MlsError::Welcome("no pending key-package provider for this conversation".into())
|
|
||||||
})?;
|
|
||||||
OpenMlsService::new_from_welcome(welcome_bytes, provider)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_scoring(&self, config: &ScoringConfig) -> Self::Scoring {
|
|
||||||
PeerScoringService::new(
|
|
||||||
InMemoryPeerScoreStorage::new(),
|
|
||||||
default_score_deltas(),
|
|
||||||
config.clone(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_steward_list(
|
|
||||||
&self,
|
|
||||||
conversation_id: &[u8],
|
|
||||||
config: StewardListConfig,
|
|
||||||
) -> Self::StewardList {
|
|
||||||
DeterministicStewardList::empty(conversation_id.to_vec(), config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DemlsSetup {
|
|
||||||
signer: DemlsSigner,
|
|
||||||
factory: DemlsFactory,
|
|
||||||
consensus_storage: <DefaultConsensusPlugin as ConsensusPlugin>::ConsensusStorage,
|
|
||||||
consensus_signer: EthereumConsensusSigner,
|
|
||||||
app_id: Vec<u8>, // random bytes; echo-dedup key
|
|
||||||
config: ConversationConfig, // the ms-scale test timers, as before
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DemlsSetup {
|
|
||||||
fn new(identity_name: String) -> Result<Self, ChatError> {
|
|
||||||
let signer = MlsIdentityProvider::new(DemlsMember::new(identity_name));
|
|
||||||
let factory = DemlsFactory::new(signer.clone());
|
|
||||||
// TODO(config): TEST-ONLY millisecond timers. de-mls deadlines are real
|
|
||||||
// wall-clock, so the default 60s timers never fire under fast virtual
|
|
||||||
// time. Production needs a real config injected from the caller, not
|
|
||||||
// these hardcoded values.
|
|
||||||
let config = ConversationConfig {
|
|
||||||
commit_inactivity_duration: Duration::from_millis(50),
|
|
||||||
freeze_duration: Duration::from_millis(20),
|
|
||||||
voting_delay: Duration::from_millis(30),
|
|
||||||
election_voting_delay: Duration::from_millis(30),
|
|
||||||
consensus_timeout: Duration::from_millis(150),
|
|
||||||
proposal_expiration: Duration::from_millis(2000),
|
|
||||||
..ConversationConfig::default()
|
|
||||||
};
|
|
||||||
Ok(DemlsSetup {
|
|
||||||
signer,
|
|
||||||
factory,
|
|
||||||
consensus_storage: DefaultConsensusPlugin::new_storage(),
|
|
||||||
consensus_signer: EthereumConsensusSigner::new(PrivateKeySigner::random()),
|
|
||||||
app_id: rand_string(5).as_bytes().to_vec(),
|
|
||||||
config,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call exactly once per Conversation construction.
|
|
||||||
fn deps(&self) -> ConversationDeps<'_, DefaultConsensusPlugin, DemlsFactory, DemlsSigner> {
|
|
||||||
ConversationDeps {
|
|
||||||
plugins: &self.factory,
|
|
||||||
consensus: ConsensusServiceFor::<DefaultConsensusPlugin>::new_with_components(
|
|
||||||
self.consensus_storage.clone(),
|
|
||||||
DefaultConsensusPlugin::new_event_bus(),
|
|
||||||
self.consensus_signer.clone(),
|
|
||||||
10,
|
|
||||||
),
|
|
||||||
signer: self.signer.clone(),
|
|
||||||
identity: &self.signer,
|
|
||||||
app_id: Arc::from(self.app_id.as_slice()),
|
|
||||||
config: self.config.clone(),
|
|
||||||
scoring_config: ScoringConfig::default(),
|
|
||||||
steward_list_config: StewardListConfig::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GroupV2Convo {
|
pub struct GroupV2Convo {
|
||||||
convo_id: String,
|
convo_id: String,
|
||||||
setup: DemlsSetup,
|
conversation:
|
||||||
conversation: Option<Conversation<DefaultConsensusPlugin, DemlsFactory, DemlsSigner>>,
|
Option<Conversation<DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList>>,
|
||||||
/// Member-ids we proposed via add_member. WelcomeReady now fires on
|
/// Member-ids we proposed via add_member. We forward a welcome only to joiners WE invited.
|
||||||
/// every member; we forward a welcome only to joiners WE invited.
|
|
||||||
pending_invites: Vec<Vec<u8>>,
|
pending_invites: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,13 +180,23 @@ impl GroupV2Convo {
|
|||||||
pub fn new<S: ExternalServices>(
|
pub fn new<S: ExternalServices>(
|
||||||
service_ctx: &mut ServiceContext<S>,
|
service_ctx: &mut ServiceContext<S>,
|
||||||
) -> Result<Self, ChatError> {
|
) -> Result<Self, ChatError> {
|
||||||
let setup = DemlsSetup::new(service_ctx.mls_identity.display_name())?;
|
|
||||||
let convo_id = rand_string(5);
|
let convo_id = rand_string(5);
|
||||||
let key_package = setup.factory.generate_key_package()?;
|
let member = member_id(service_ctx);
|
||||||
let conversation = Conversation::create(&convo_id, key_package.as_bytes(), setup.deps())?;
|
let conversation = Conversation::create(
|
||||||
|
&convo_id,
|
||||||
|
&service_ctx.mls_provider,
|
||||||
|
service_ctx.mls_identity.get_credential(),
|
||||||
|
CIPHER_SUITE,
|
||||||
|
&service_ctx.mls_identity,
|
||||||
|
make_scoring(),
|
||||||
|
make_steward(),
|
||||||
|
make_consensus(),
|
||||||
|
rand_app_id(),
|
||||||
|
demls_config(),
|
||||||
|
&member,
|
||||||
|
)?;
|
||||||
let convo = GroupV2Convo {
|
let convo = GroupV2Convo {
|
||||||
convo_id,
|
convo_id,
|
||||||
setup,
|
|
||||||
conversation: Some(conversation),
|
conversation: Some(conversation),
|
||||||
pending_invites: vec![],
|
pending_invites: vec![],
|
||||||
};
|
};
|
||||||
@ -335,9 +212,7 @@ impl GroupV2Convo {
|
|||||||
pub fn new_pending<S: ExternalServices>(
|
pub fn new_pending<S: ExternalServices>(
|
||||||
service_ctx: &mut ServiceContext<S>,
|
service_ctx: &mut ServiceContext<S>,
|
||||||
) -> Result<Self, ChatError> {
|
) -> Result<Self, ChatError> {
|
||||||
let name = service_ctx.mls_identity.display_name();
|
let kp_bytes = mint_key_package(service_ctx)?;
|
||||||
let setup = DemlsSetup::new(name.clone())?;
|
|
||||||
let kp = setup.factory.generate_key_package()?;
|
|
||||||
|
|
||||||
// Namespace the key package so it doesn't collide with the GroupV1
|
// Namespace the key package so it doesn't collide with the GroupV1
|
||||||
// key package the registry keys under the bare account id.
|
// key package the registry keys under the bare account id.
|
||||||
@ -345,12 +220,11 @@ impl GroupV2Convo {
|
|||||||
NamespacedIdentity::new(&*service_ctx.mls_identity, DEMLS_KEYPACKAGE_NAMESPACE);
|
NamespacedIdentity::new(&*service_ctx.mls_identity, DEMLS_KEYPACKAGE_NAMESPACE);
|
||||||
service_ctx
|
service_ctx
|
||||||
.registry
|
.registry
|
||||||
.register(&namespaced, kp.as_bytes().to_vec())
|
.register(&namespaced, kp_bytes)
|
||||||
.map_err(ChatError::generic)?;
|
.map_err(ChatError::generic)?;
|
||||||
|
|
||||||
Ok(GroupV2Convo {
|
Ok(GroupV2Convo {
|
||||||
convo_id: String::new(),
|
convo_id: String::new(),
|
||||||
setup,
|
|
||||||
conversation: None,
|
conversation: None,
|
||||||
pending_invites: vec![],
|
pending_invites: vec![],
|
||||||
})
|
})
|
||||||
@ -366,8 +240,22 @@ impl GroupV2Convo {
|
|||||||
service_ctx: &mut ServiceContext<S>,
|
service_ctx: &mut ServiceContext<S>,
|
||||||
welcome: &MemberWelcome,
|
welcome: &MemberWelcome,
|
||||||
) -> Result<(), ChatError> {
|
) -> Result<(), ChatError> {
|
||||||
let conv = Conversation::from_welcome(self.setup.deps(), welcome)?
|
let member = member_id(service_ctx);
|
||||||
.ok_or_else(|| ChatError::generic("welcome not addressed to this member"))?;
|
let Some(conv) = Conversation::join(
|
||||||
|
&service_ctx.mls_provider,
|
||||||
|
&welcome.welcome_bytes,
|
||||||
|
&welcome.conversation_sync_bytes,
|
||||||
|
make_scoring(),
|
||||||
|
make_steward(),
|
||||||
|
make_consensus(),
|
||||||
|
rand_app_id(),
|
||||||
|
demls_config(),
|
||||||
|
&member,
|
||||||
|
&service_ctx.mls_identity,
|
||||||
|
)?
|
||||||
|
else {
|
||||||
|
return Err(ChatError::generic("welcome not addressed to this member"));
|
||||||
|
};
|
||||||
self.convo_id = conv.id().to_string();
|
self.convo_id = conv.id().to_string();
|
||||||
self.conversation = Some(conv);
|
self.conversation = Some(conv);
|
||||||
self.init(service_ctx)?; // subscribe
|
self.init(service_ctx)?; // subscribe
|
||||||
@ -414,7 +302,11 @@ where
|
|||||||
.conversation
|
.conversation
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| ChatError::generic("conversation not found"))?;
|
.ok_or_else(|| ChatError::generic("conversation not found"))?;
|
||||||
conv.send_message(content.to_vec())?;
|
conv.send_message(
|
||||||
|
&service_ctx.mls_provider,
|
||||||
|
content.to_vec(),
|
||||||
|
&service_ctx.mls_identity,
|
||||||
|
)?;
|
||||||
self.after_op(service_ctx)?;
|
self.after_op(service_ctx)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -441,8 +333,13 @@ where
|
|||||||
.conversation
|
.conversation
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| ChatError::generic("no conversation"))?;
|
.ok_or_else(|| ChatError::generic("no conversation"))?;
|
||||||
conv.process_inbound(&frame.sender_app_id, &inner)?;
|
conv.process_inbound(
|
||||||
conv.poll();
|
&service_ctx.mls_provider,
|
||||||
|
&frame.sender_app_id,
|
||||||
|
&inner,
|
||||||
|
&service_ctx.mls_identity,
|
||||||
|
)?;
|
||||||
|
conv.poll(&service_ctx.mls_provider, &service_ctx.mls_identity);
|
||||||
let events = self.after_op(service_ctx)?; // route + publish + re-arm, returns events
|
let events = self.after_op(service_ctx)?; // route + publish + re-arm, returns events
|
||||||
|
|
||||||
match self.events_to_content(&events) {
|
match self.events_to_content(&events) {
|
||||||
@ -460,7 +357,7 @@ where
|
|||||||
let Some(conv) = self.conversation.as_mut() else {
|
let Some(conv) = self.conversation.as_mut() else {
|
||||||
return Ok(()); // pending joiner: no deadlines exist yet
|
return Ok(()); // pending joiner: no deadlines exist yet
|
||||||
};
|
};
|
||||||
let outcome = conv.poll();
|
let outcome = conv.poll(&ctx.mls_provider, &ctx.mls_identity);
|
||||||
if outcome.leave_requested {
|
if outcome.leave_requested {
|
||||||
// Commit ejected us (or join expired). Real handling - drops
|
// Commit ejected us (or join expired). Real handling - drops
|
||||||
// this convo from its map;
|
// this convo from its map;
|
||||||
@ -485,8 +382,8 @@ where
|
|||||||
members: &[IdentIdRef],
|
members: &[IdentIdRef],
|
||||||
) -> Result<(), ChatError> {
|
) -> Result<(), ChatError> {
|
||||||
// Record who WE invited before touching the conversation: after_op
|
// Record who WE invited before touching the conversation: after_op
|
||||||
// forwards a welcome only to joiners in pending_invites (member-id
|
// forwards a welcome only to joiners in pending_invites (the de-mls
|
||||||
// bytes == account name bytes for LocalDemlsMember).
|
// member-id is the invitee's account id bytes).
|
||||||
let mut kps = Vec::with_capacity(members.len());
|
let mut kps = Vec::with_capacity(members.len());
|
||||||
for member in members {
|
for member in members {
|
||||||
let device_id = NamespacedIdentity::prefix(member, DEMLS_KEYPACKAGE_NAMESPACE);
|
let device_id = NamespacedIdentity::prefix(member, DEMLS_KEYPACKAGE_NAMESPACE);
|
||||||
@ -505,7 +402,11 @@ where
|
|||||||
.as_mut()
|
.as_mut()
|
||||||
.ok_or_else(|| ChatError::generic("no conversation"))?;
|
.ok_or_else(|| ChatError::generic("no conversation"))?;
|
||||||
for kp_bytes in &kps {
|
for kp_bytes in &kps {
|
||||||
conv.add_member(kp_bytes)?;
|
conv.add_member(
|
||||||
|
&service_ctx.mls_provider,
|
||||||
|
kp_bytes,
|
||||||
|
&service_ctx.mls_identity,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
self.after_op(service_ctx)?;
|
self.after_op(service_ctx)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use de_mls::{mls_crypto::MlsError, session::ConversationError};
|
use de_mls::{ConversationError, mls_crypto::MlsError};
|
||||||
use openmls::{framing::errors::MlsMessageError, prelude::tls_codec};
|
use openmls::{framing::errors::MlsMessageError, prelude::tls_codec};
|
||||||
pub use thiserror::Error;
|
pub use thiserror::Error;
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user