feat: http server based key package registry (#124)

* feat: http server based key package registry

* chore: instructions on running the registration service

* chore: remove duplicate post param

* chore: revert out sourced account id for multi devices support

* feat: signature on account id and key packages

* chore: include http registry in contact registry module

* refactor: use device id for retrieve key package

* chore: use string for device id

* feat: server verification on the register

* chore: doc the smoke test

* chore: fix data folder non exist

* chore: use payload for register and retrieve

* chore: fix clippy
This commit is contained in:
kaichao
2026-06-04 10:09:29 +08:00
committed by GitHub
parent 6f5838af51
commit cd7dd6a330
23 changed files with 1825 additions and 53 deletions
+7 -3
View File
@@ -15,14 +15,18 @@ pub struct LogosAccount {
}
impl LogosAccount {
/// Create a test LogosAccount using a pre-defined identifier.
/// Create a test LogosAccount. The `AccountId` is derived from the
/// generated Ed25519 verifying key (hex-encoded) so signatures over the
/// id can be verified by anyone holding the id alone.
/// The supplied `_display_name` is currently ignored — id is the key.
/// This should only be used during MLS integration. Not suitable for production use.
/// TODO: (P1) Remove once implementation is ready.
pub fn new_test(explicit_id: impl Into<String>) -> Self {
pub fn new_test(_display_name: impl Into<String>) -> Self {
let signing_key = Ed25519SigningKey::generate();
let verifying_key = signing_key.verifying_key();
let id = AccountId::new(hex::encode(verifying_key.as_ref()));
Self {
id: AccountId::new(explicit_id.into()),
id,
signing_key,
verifying_key,
}
+7
View File
@@ -164,6 +164,13 @@ where
self.identity.public_key()
}
/// Submit the local account's MLS KeyPackage to the registration service.
/// Idempotent on the server side (registries that retain history will keep
/// the most recent N submissions; older entries are pruned).
pub fn register_keypackage(&mut self) -> Result<(), ChatError> {
self.pq_inbox.register()
}
pub fn create_private_convo(
&mut self,
remote_bundle: &Introduction,
@@ -193,10 +193,15 @@ where
}
fn key_package_for_account(&self, ident: &AccountId) -> Result<KeyPackage, ChatError> {
// INTERIM: the key package registry is keyed by `DeviceId`, but resolving an
// `AccountId` to its device(s) is a future task. For now (single device
// per account) we use the account-id string directly as the device id.
// When account->device resolution lands, only this conversion changes.
let device_id = ident.to_string();
let retrieved_bytes = self
.keypkg_provider
.borrow()
.retrieve(ident)
.retrieve(&device_id)
.map_err(|e: KP::Error| ChatError::Generic(e.to_string()))?;
// dbg!(ctx.contact_registry());
+1 -1
View File
@@ -105,7 +105,7 @@ where
// "LastResort" package or publish multiple
self.reg_service
.borrow_mut()
.register(self.account_id().as_str(), keypackage_bytes)
.register(&*self.account.borrow(), keypackage_bytes)
.map_err(ChatError::generic)
}
+14 -5
View File
@@ -22,23 +22,32 @@ pub trait DeliveryService: Debug {
///
/// Implement this to provide a contact registry — ach participant publishes their key package
/// on registration; others fetch it to initiate a conversation.
///
/// `register` receives an [`IdentityProvider`] (not just a name) so
/// implementations that need to authenticate the submission — e.g. a network
/// service that verifies the bundle is signed by the correct account — can
/// sign or attest with the caller's key material.
pub trait RegistrationService: Debug {
type Error: Display + Debug;
fn register(&mut self, identity: &str, key_bundle: Vec<u8>) -> Result<(), Self::Error>;
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, Self::Error>;
fn register(
&mut self,
identity: &dyn IdentityProvider,
key_bundle: Vec<u8>,
) -> Result<(), Self::Error>;
fn retrieve(&self, device_id: &str) -> 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 + Debug;
fn retrieve(&self, identity: &AccountId) -> Result<Option<Vec<u8>>, Self::Error>;
fn retrieve(&self, device_id: &str) -> 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)
fn retrieve(&self, device_id: &str) -> Result<Option<Vec<u8>>, Self::Error> {
RegistrationService::retrieve(self, device_id)
}
}
+6
View File
@@ -68,6 +68,12 @@ impl Ed25519VerifyingKey {
.verify_strict(msg, &ed25519_dalek::Signature::from_bytes(&inner_signature))
.map_err(|_| SignatureVerificationError {})
}
pub fn from_bytes(bytes: &[u8; 32]) -> Result<Self, SignatureVerificationError> {
ed25519_dalek::VerifyingKey::from_bytes(bytes)
.map(Self)
.map_err(|_| SignatureVerificationError {})
}
}
impl From<ed25519_dalek::VerifyingKey> for Ed25519VerifyingKey {