mirror of
https://github.com/logos-messaging/libchat.git
synced 2026-07-01 13:39:46 +00:00
update code after mls service split up
This commit is contained in:
parent
960d0bc119
commit
0165612695
45
Cargo.lock
generated
45
Cargo.lock
generated
@ -1814,13 +1814,11 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "de-mls"
|
||||
version = "3.0.0"
|
||||
source = "git+https://github.com/vacp2p/de-mls?branch=develop#d838e832994fd1d14f624783741bc60b31510fa0"
|
||||
source = "git+https://github.com/vacp2p/de-mls?branch=refactor%2Fmls-contract-engine-split#a87b5eba396a36037174e59b28ef561fada179ca"
|
||||
dependencies = [
|
||||
"hashgraph-like-consensus",
|
||||
"indexmap 2.14.0",
|
||||
"openmls",
|
||||
"openmls_basic_credential",
|
||||
"openmls_rust_crypto 0.5.1",
|
||||
"openmls_traits 0.5.0",
|
||||
"prost",
|
||||
"prost-build",
|
||||
@ -3895,20 +3893,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openmls_basic_credential"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "983e8be1457dd6f316f409292cec334af3b57b49a19deadc925c83c3c35e15b6"
|
||||
dependencies = [
|
||||
"ed25519-dalek",
|
||||
"openmls_traits 0.5.0",
|
||||
"p256",
|
||||
"rand 0.8.6",
|
||||
"serde",
|
||||
"tls_codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openmls_libcrux_crypto"
|
||||
version = "0.2.4"
|
||||
@ -4004,31 +3988,6 @@ dependencies = [
|
||||
"tls_codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openmls_rust_crypto"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fafcc8a3552b10fbb3ab757cccaf1a34081e826ca819f49aa7e6645b1d95c00f"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"chacha20poly1305",
|
||||
"ed25519-dalek",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hpke-rs",
|
||||
"hpke-rs-crypto",
|
||||
"hpke-rs-rust-crypto",
|
||||
"openmls_memory_storage 0.5.0",
|
||||
"openmls_traits 0.5.0",
|
||||
"p256",
|
||||
"rand 0.8.6",
|
||||
"rand_chacha 0.3.1",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"thiserror",
|
||||
"tls_codec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openmls_test"
|
||||
version = "0.2.1"
|
||||
@ -4037,7 +3996,7 @@ checksum = "46c5984361586c8ef56108664ffec909fa78126be8eef1983723f0aed80a9266"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"openmls_libcrux_crypto 0.2.4",
|
||||
"openmls_rust_crypto 0.4.4",
|
||||
"openmls_rust_crypto",
|
||||
"openmls_traits 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@ -18,7 +18,7 @@ storage = { workspace = true }
|
||||
alloy = "2.0"
|
||||
base64 = "0.22"
|
||||
chat-proto = { git = "https://github.com/logos-messaging/chat_proto", rev = "37ec98a151f6d50aab2905802ac0a896477e62ea" }
|
||||
de-mls = { git = "https://github.com/vacp2p/de-mls", branch = "develop" }
|
||||
de-mls = { git = "https://github.com/vacp2p/de-mls", branch = "refactor/mls-contract-engine-split" }
|
||||
double-ratchets = { path = "../double-ratchets" }
|
||||
hashgraph-like-consensus = "0.5.1"
|
||||
hex = "0.4.3"
|
||||
|
||||
@ -9,24 +9,32 @@ use blake2::{Blake2b, Digest, digest::consts::U6};
|
||||
use chat_proto::logoschat::encryption::{EncryptedPayload, Plaintext, encrypted_payload};
|
||||
use de_mls::core::{
|
||||
ConsensusPlugin, ConsensusServiceFor, ConversationEvent, ConversationPluginsFactory,
|
||||
ScoringConfig, StewardListConfig,
|
||||
DeterministicStewardList, PeerScoringService, ScoringConfig, StewardListConfig,
|
||||
default_score_deltas,
|
||||
};
|
||||
use de_mls::defaults::{
|
||||
DefaultConsensusPlugin, DefaultConversationPluginsFactory, MemoryDeMlsStorage,
|
||||
DefaultConsensusPlugin, DefaultPeerScoring, DefaultStewardList, InMemoryPeerScoreStorage,
|
||||
};
|
||||
use de_mls::member_id::MemberId;
|
||||
use de_mls::mls_crypto::MlsCredentials;
|
||||
use de_mls::mls_crypto::{KeyPackageBytes, MlsError, OpenMlsService};
|
||||
use de_mls::protos::de_mls::messages::v1::{
|
||||
AppMessage as AppMessageProto, MemberWelcome, app_message,
|
||||
};
|
||||
use de_mls::session::{Conversation, ConversationConfig, ConversationDeps};
|
||||
use hashgraph_like_consensus::signing::EthereumConsensusSigner;
|
||||
use openmls::key_packages::KeyPackage;
|
||||
use openmls::prelude::tls_codec::Serialize as _;
|
||||
use openmls_traits::signatures::Signer;
|
||||
use prost::Message;
|
||||
use shared_traits::{IdentId, IdentIdRef};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use crate::inbox_v2::{CIPHER_SUITE, MlsEphemeralPqProvider, MlsIdentityProvider};
|
||||
use crypto::{Ed25519Signature, Ed25519SigningKey, Ed25519VerifyingKey};
|
||||
|
||||
use crate::IdentityProvider;
|
||||
use crate::conversation::{ConversationIdRef, ExternalServices, ServiceContext};
|
||||
use crate::{
|
||||
@ -38,25 +46,59 @@ use crate::{
|
||||
/// with the openmls (GroupV1) keypackage registered under the bare account id.
|
||||
const DEMLS_KEYPACKAGE_NAMESPACE: &str = "demls";
|
||||
|
||||
/// This is a Test Wrapper of Demls MemberId Trait
|
||||
/// Libchat has its own trait that will need to be intergrated at somepoint.
|
||||
pub struct LocalDemlsMember {
|
||||
name: String,
|
||||
/// 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 LocalDemlsMember {
|
||||
pub fn new(name: impl Into<String>) -> Self {
|
||||
Self { name: name.into() }
|
||||
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 MemberId for LocalDemlsMember {
|
||||
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.name.as_bytes()
|
||||
self.id().as_str().as_bytes()
|
||||
}
|
||||
|
||||
fn member_id_display(&self) -> &str {
|
||||
&self.name
|
||||
self.id().as_str()
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,9 +139,101 @@ impl IdentityProvider for NamespacedIdentity<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The de-mls MLS service over libchat's PQ provider.
|
||||
type DemlsMls = OpenMlsService<MlsEphemeralPqProvider>;
|
||||
|
||||
/// Reference de-mls plug-in factory over libchat's existing PQ provider. Holds
|
||||
/// 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 {
|
||||
fn new(signer: DemlsSigner) -> Self {
|
||||
Self {
|
||||
signer,
|
||||
pending_provider: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Mint a single-use key package into a fresh provider, stashing that
|
||||
/// provider so the matching `welcome_mls` can open the welcome with the key
|
||||
/// package's private keys.
|
||||
fn generate_key_package(&self) -> Result<KeyPackageBytes, ChatError> {
|
||||
let provider = MlsEphemeralPqProvider::new().map_err(ChatError::generic)?;
|
||||
let bundle = KeyPackage::builder()
|
||||
.build(
|
||||
CIPHER_SUITE,
|
||||
&provider,
|
||||
&self.signer,
|
||||
self.signer.get_credential(),
|
||||
)
|
||||
.map_err(ChatError::generic)?;
|
||||
let bytes = bundle
|
||||
.key_package()
|
||||
.tls_serialize_detached()
|
||||
.map_err(ChatError::generic)?;
|
||||
*self.pending_provider.borrow_mut() = Some(provider);
|
||||
Ok(KeyPackageBytes::new(
|
||||
bytes,
|
||||
self.signer.member_id_bytes().to_vec(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ConversationPluginsFactory for DemlsFactory {
|
||||
type Mls = DemlsMls;
|
||||
type Scoring = DefaultPeerScoring;
|
||||
type StewardList = DefaultStewardList;
|
||||
|
||||
fn create_mls(
|
||||
&self,
|
||||
conversation_id: String,
|
||||
key_package: &[u8],
|
||||
signer: &impl Signer,
|
||||
) -> Result<Self::Mls, MlsError> {
|
||||
OpenMlsService::new_as_creator(
|
||||
conversation_id,
|
||||
MlsEphemeralPqProvider::new()?,
|
||||
key_package,
|
||||
signer,
|
||||
)
|
||||
}
|
||||
|
||||
fn welcome_mls(&self, welcome_bytes: &[u8]) -> Result<Option<Self::Mls>, MlsError> {
|
||||
// Each conversation has its own factory and stash, and welcomes are
|
||||
// routed only to the joiner that minted the key package. A missing
|
||||
// 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 {
|
||||
member: LocalDemlsMember,
|
||||
factory: DefaultConversationPluginsFactory,
|
||||
signer: DemlsSigner,
|
||||
factory: DemlsFactory,
|
||||
consensus_storage: <DefaultConsensusPlugin as ConsensusPlugin>::ConsensusStorage,
|
||||
consensus_signer: EthereumConsensusSigner,
|
||||
app_id: Vec<u8>, // random bytes; echo-dedup key
|
||||
@ -108,12 +242,8 @@ struct DemlsSetup {
|
||||
|
||||
impl DemlsSetup {
|
||||
fn new(identity_name: String) -> Result<Self, ChatError> {
|
||||
let member = LocalDemlsMember::new(identity_name);
|
||||
let credentials = Arc::new(MlsCredentials::from_member_id(&member)?);
|
||||
let factory = DefaultConversationPluginsFactory::new(
|
||||
Arc::new(MemoryDeMlsStorage::new()),
|
||||
credentials,
|
||||
);
|
||||
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
|
||||
@ -128,7 +258,7 @@ impl DemlsSetup {
|
||||
..ConversationConfig::default()
|
||||
};
|
||||
Ok(DemlsSetup {
|
||||
member,
|
||||
signer,
|
||||
factory,
|
||||
consensus_storage: DefaultConsensusPlugin::new_storage(),
|
||||
consensus_signer: EthereumConsensusSigner::new(PrivateKeySigner::random()),
|
||||
@ -138,9 +268,7 @@ impl DemlsSetup {
|
||||
}
|
||||
|
||||
/// Call exactly once per Conversation construction.
|
||||
fn deps(
|
||||
&self,
|
||||
) -> ConversationDeps<'_, DefaultConsensusPlugin, DefaultConversationPluginsFactory> {
|
||||
fn deps(&self) -> ConversationDeps<'_, DefaultConsensusPlugin, DemlsFactory, DemlsSigner> {
|
||||
ConversationDeps {
|
||||
plugins: &self.factory,
|
||||
consensus: ConsensusServiceFor::<DefaultConsensusPlugin>::new_with_components(
|
||||
@ -149,7 +277,8 @@ impl DemlsSetup {
|
||||
self.consensus_signer.clone(),
|
||||
10,
|
||||
),
|
||||
identity: &self.member,
|
||||
signer: self.signer.clone(),
|
||||
identity: &self.signer,
|
||||
app_id: Arc::from(self.app_id.as_slice()),
|
||||
config: self.config.clone(),
|
||||
scoring_config: ScoringConfig::default(),
|
||||
@ -161,7 +290,7 @@ impl DemlsSetup {
|
||||
pub struct GroupV2Convo {
|
||||
convo_id: String,
|
||||
setup: DemlsSetup,
|
||||
conversation: Option<Conversation<DefaultConsensusPlugin, DefaultConversationPluginsFactory>>,
|
||||
conversation: Option<Conversation<DefaultConsensusPlugin, DemlsFactory, DemlsSigner>>,
|
||||
/// Member-ids we proposed via add_member. WelcomeReady now fires on
|
||||
/// every member; we forward a welcome only to joiners WE invited.
|
||||
pending_invites: Vec<Vec<u8>>,
|
||||
@ -186,7 +315,8 @@ impl GroupV2Convo {
|
||||
) -> Result<Self, ChatError> {
|
||||
let setup = DemlsSetup::new(service_ctx.mls_identity.display_name())?;
|
||||
let convo_id = rand_string(5);
|
||||
let conversation = Conversation::create(&convo_id, setup.deps())?;
|
||||
let key_package = setup.factory.generate_key_package()?;
|
||||
let conversation = Conversation::create(&convo_id, key_package.as_bytes(), setup.deps())?;
|
||||
let convo = GroupV2Convo {
|
||||
convo_id,
|
||||
setup,
|
||||
@ -209,10 +339,8 @@ impl GroupV2Convo {
|
||||
let setup = DemlsSetup::new(name.clone())?;
|
||||
let kp = setup.factory.generate_key_package()?;
|
||||
|
||||
// TEMPORARY: Demls creates its own Provider which causes keys to be fragmented in different storage providers.
|
||||
// The key registry does not support a method to namespace keys with the same identity. When the key is pulled down it cannot
|
||||
// guarentee it was the one created with demls owned provider, resulting in failure.
|
||||
// This workaround prefixes the ID used to store the keys, such that they do not conflict.
|
||||
// Namespace the key package so it doesn't collide with the GroupV1
|
||||
// key package the registry keys under the bare account id.
|
||||
let namespaced =
|
||||
NamespacedIdentity::new(&*service_ctx.mls_identity, DEMLS_KEYPACKAGE_NAMESPACE);
|
||||
service_ctx
|
||||
|
||||
@ -30,7 +30,8 @@ use crate::{
|
||||
use crate::{IdentId, IdentIdRef, IdentityProvider};
|
||||
|
||||
// Downgraded from MLS_256_XWING_CHACHA20POLY1305_SHA256_Ed25519 until demls accepts an external provider
|
||||
const CIPHER_SUITE: Ciphersuite = Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
|
||||
pub(crate) const CIPHER_SUITE: Ciphersuite =
|
||||
Ciphersuite::MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519;
|
||||
|
||||
// Define unique Identifiers derivations used in InboxV2
|
||||
fn delivery_address_for(ident_id: IdentIdRef) -> String {
|
||||
|
||||
@ -16,6 +16,7 @@ use crate::IdentityProvider;
|
||||
/// This type stops OpenMLS internal from leaking outside of the crate.
|
||||
/// Developers provider a simple IdentitityProvider, and Signer and Credential generation
|
||||
/// is provided
|
||||
#[derive(Clone)]
|
||||
pub struct MlsIdentityProvider<T: IdentityProvider>(T);
|
||||
|
||||
impl<T: IdentityProvider> MlsIdentityProvider<T> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user