fix: signer-scoped DirectV1 routing (#162)

Core is no longer account-aware: the client resolves an account address
to signer ids via the account directory, and the signer's verifying-key
hex serves as registry key, inbox subscription, and Welcome routing
target end to end. The MLS credential stays the full id().

- GroupV2 reads the de-mls member id from the fetched key package and
  maps it to the signer id the welcome is delivered to.
- All account machinery (directory trait, bundle codec, resolution)
  moves out of core into logos-account; the RegistrationService
  supertrait and Core::account_directory() are gone, and the client
  holds its own directory handle.
- The account exposes functionality, never a signer: add_delegate_signer
  does the lamport upsert and signs internally.
- Every client acts for an account (ChatClientBuilder::new(account)).
  DelegateSigner is a pure keypair; the client composes the wire
  credential from the signer and the account, so the association is
  client state. addr() is the account address.
- resolve_device_ids fails fast (NotAnAccountKey / NoDeviceBundle /
  Directory) instead of falling back to treating an unresolved address
  as a signer id. LogosChatClient::open and chat-cli mint and publish a
  dev account each launch.
- EphemeralRegistry keys key packages by hex pubkey like HttpRegistry.

Supersedes #155 (routing_id).
This commit is contained in:
osmaczko
2026-07-03 23:18:10 +02:00
committed by GitHub
parent d131a69583
commit c09459c0a0
34 changed files with 692 additions and 500 deletions
+2
View File
@@ -1,4 +1,6 @@
mod test_client;
mod test_ident;
mod wakeup;
pub use test_client::TestHarness;
pub use test_ident::TestIdent;
+4 -10
View File
@@ -1,5 +1,5 @@
use crate::test_ident::TestIdent;
use libchat::{ConversationId, Core, IdentityProvider, PayloadOutcome};
use logos_account::TestLogosAccount;
use shared_traits::IdentId;
use std::collections::HashMap;
use std::fmt::Debug;
@@ -21,14 +21,8 @@ const RAYA: usize = 1;
const PAX: usize = 2;
const MIRA: usize = 3;
// type ClientType = CoreClient<TestLogosAccount, LocalBroadcaster, EphemeralRegistry, WP, MemStore>;
type ClientType = Core<(
TestLogosAccount,
LocalBroadcaster,
EphemeralRegistry,
WP,
MemStore,
)>;
// type ClientType = CoreClient<TestIdent, LocalBroadcaster, EphemeralRegistry, WP, MemStore>;
type ClientType = Core<(TestIdent, LocalBroadcaster, EphemeralRegistry, WP, MemStore)>;
#[derive(Debug)]
pub struct ReceivedMessage<T> {
@@ -151,7 +145,7 @@ impl<const N: usize> TestHarness<N> {
for i in 0..N {
let wp = ws.new_provider(i);
let ident = TestLogosAccount::new(Self::names(i));
let ident = TestIdent::new(Self::names(i));
addresses.insert(i, ident.id().clone());
let core_client =
@@ -0,0 +1,41 @@
use crypto::{Ed25519SigningKey, Ed25519VerifyingKey};
use libchat::IdentityProvider;
use shared_traits::{IdentId, IdentIdRef};
/// Test identity with a fixed, human-readable id ("saro"). Stands in for a
/// device signer so core tests can address peers by name.
pub struct TestIdent {
id: IdentId,
signing_key: Ed25519SigningKey,
verifying_key: Ed25519VerifyingKey,
}
impl TestIdent {
pub fn new(explicit_id: impl Into<String>) -> Self {
let signing_key = Ed25519SigningKey::generate();
let verifying_key = signing_key.verifying_key();
Self {
id: IdentId::new(explicit_id.into()),
signing_key,
verifying_key,
}
}
}
impl IdentityProvider for TestIdent {
fn id(&self) -> IdentIdRef<'_> {
&self.id
}
fn display_name(&self) -> String {
self.id.to_string()
}
fn public_key(&self) -> &Ed25519VerifyingKey {
&self.verifying_key
}
fn sign(&self, payload: &[u8]) -> crypto::Ed25519Signature {
self.signing_key.sign(payload)
}
}