This branch performs a major architectural migration in the Pika messaging app: it removes the external Marmot Development Kit (MDK) dependency and replaces it with an in-repo pika-mls crate for MLS group state management, introduces a new pika-chat-server crate for server-mediated room ordering and delivery, replaces raw npub identifiers with richer 'profile codes' that encode chat-server routing information across all platforms (iOS, Android, Desktop), cleans up the pika-marmot-runtime crate by absorbing its responsibilities into pikachat-sidecar and pika-mls, updates the CLI tooling to use the new MLS layer, and refreshes documentation and diagrams to reflect the new chat-server-centric architecture.
Remove MDK workspace dependencies and add pika-mls and OpenMLS
Intent: Eliminate the external mdk-core, mdk-sqlite-storage, and mdk-storage-traits Git dependencies and replace them with a repo-local pika-mls crate backed by the OpenMLS ecosystem, giving the project full ownership of its MLS layer.
Affected files: Cargo.toml, Cargo.lock
Evidence
@@ -29,19 +30,16 @@ members = [
]
[workspace.dependencies]
-# Pin MDK once for the whole workspace to avoid version skew between members.
-# (MDK is load-bearing for interop: MLS state formats, key package behavior, etc.)
-mdk-core = { git = "https://github.com/marmot-protocol/mdk", rev = "ca0663ee..." }
-mdk-sqlite-storage = { git = "https://github.com/marmot-protocol/mdk", rev = "ca0663ee..." }
-mdk-storage-traits = { git = "https://github.com/marmot-protocol/mdk", rev = "ca0663ee..." }
+pika-mls = { path = "crates/pika-mls" }
The root Cargo.toml workspace undergoes three key changes:
MDK removal — The three mdk-* Git dependencies pinned to a specific MDK revision are deleted entirely. This severs the external dependency on marmot-protocol/mdk.
pika-mls addition — A new path dependency pika-mls = { path = "crates/pika-mls" } is added to the workspace, along with the full OpenMLS stack (openmls, openmls_basic_credential, openmls_memory_storage, openmls_rust_crypto, openmls_traits) and supplementary crates (aes-gcm, tls_codec).
Workspace member swap — crates/pika-marmot-runtime is removed from the members list and replaced by crates/pika-mls and crates/pika-chat-server.
The Cargo.lock reflects the cascade: dozens of transitive MDK dependencies (refinery, postcard, mdk-core, mdk-sqlite-storage, mdk-storage-traits, embedded-io, etc.) are pruned, while OpenMLS and libcrux crate versions are updated (e.g., hpke-rs 0.6.0→0.6.1, libcrux-ml-kem 0.0.7→0.0.8).
Introduce the pika-chat-server crate
Intent: Add a new Axum-based HTTP server crate that handles room-based message ordering, welcome delivery, and membership control — replacing the relay-only transport model with a server-mediated architecture.
A brand-new crate pika-chat-server is introduced with modules for:
nostr_auth — Validates Nostr-signed authentication tokens on incoming requests.
protocol — Defines the wire types for room creation, message submission, welcome leasing, and sync.
routes — Axum route handlers that accept authenticated requests and delegate to the store.
session — Per-connection session state and welcome lease management.
store — Append-only room log storage with welcome slot tracking.
config — Server configuration (bind address, HMAC secret, etc.).
main — Entry point that wires everything together with tokio and tracing-subscriber.
The server uses HMAC-SHA256 for welcome lease tokens and Nostr event authentication for all mutating endpoints. This is the central piece of the 'harden welcome leasing' goal stated in the branch title.
Create the pika-mls crate as the in-repo MLS engine
Intent: Provide a self-contained MLS group state library that replaces the functionality previously imported from mdk-core and mdk-storage-traits, giving pika full control over key packages, welcomes, group message wrapping, and encrypted media.
conversation — process_group_message_event() and wrap_rumor() handle decryption/encryption of group messages.
welcome — Welcome processing and lease validation logic (the hardened path).
membership — Group member add/remove operations.
encrypted_media — AES-GCM media encryption previously handled inside MDK.
storage_traits — Trait definitions for pluggable storage backends.
lib.rs — Re-exports a prelude module containing MessageProcessingResult and other core types.
Platform-specific keyring integration (Apple and Android) is handled via optional dependencies with feature flags, mirroring the pattern previously in pika-nse.
Remove pika-marmot-runtime and absorb into pikachat-sidecar
Intent: Delete the pika-marmot-runtime crate that wrapped MDK and redistribute its responsibilities between pika-mls (for pure MLS operations) and pikachat-sidecar (for transport, media upload, and relay interaction).
The pika-marmot-runtime crate is entirely removed from the workspace. Its former responsibilities are split:
Responsibility
New home
MLS group create/join/message
pika-mls
Key package creation & relay publish
pika-mls + pikachat-sidecar::key_package
Welcome processing & leasing
pika-mls::welcome + pika-chat-server::session
Media encryption/upload
pikachat-sidecar (now depends on pika-mls + nostr-blossom + sha2 directly)
Relay transport helpers
pikachat-sidecar::relay
Call runtime
absorbed into Rust core's call_runtime.rs
pikachat-sidecar/Cargo.toml gains base64, nostr-blossom, sha2, and pika-mls while dropping mdk-core, mdk-sqlite-storage, mdk-storage-traits, and pika-marmot-runtime.
Migrate CLI from mdk_util to mls_util
Intent: Update the pikachat CLI to use the new pika-mls API surface, renaming the utility module and adjusting all call sites for group creation, message wrapping, and key package handling.
Module rename — cli/src/mdk_util.rs becomes cli/src/mls_util.rs. The type PikaMdk becomes PikaMls, and open_mdk() becomes open_mls().
Import updates — All use mdk_core::prelude::* imports are replaced with use pika_mls::prelude::* and specific function imports like pika_mls::conversation::{process_group_message_event, wrap_rumor}.
API call sites — mdk.create_message(group_id, rumor) becomes wrap_rumor(mls, group_id, rumor) returning a struct with a .wrapper field. mdk.process_message(&event) becomes process_group_message_event(mls, &event) returning Option<MessageProcessingResult>.
Key package creation — mdk.create_key_package_for_event(...) becomes the free function pika_mls::key_package::create_key_package_for_event(mls, ...).
Tests updated — All test helpers use the new mls_util types and function signatures.
The CLI README is updated to reference pika-mls.json instead of mdk.sqlite for local state.
Update Rust core to use pika-mls and pika-chat-server
Intent: Rewire the main Rust core (pika_core) to depend on pika-mls and pika-chat-server instead of MDK and pika-marmot-runtime, updating session management, welcome support, membership, and message handling.
The rust/ core crate (pika_core) is the largest consumer of the migration:
Dependency swap — mdk-core, mdk-sqlite-storage, mdk-storage-traits, pika-marmot-runtime, and apple-native-keyring-store are replaced by pika-mls, pika-chat-server, axum, and url.
chat_server.rs — New module integrating with pika-chat-server client-side for room sync, welcome fetching, and message submission.
welcome_support.rs — Welcome processing now uses pika_mls::welcome and the chat server's lease protocol instead of relay-based welcome discovery.
state.rs — MyProfileState gains a profile_code: String field; PeerProfileState gains profile_code: String; AgentKind::Pi variant is removed.
config.rs — Chat server URL is added to the runtime configuration.
session.rs / host_context.rs — Initialization opens pika-mls state instead of MDK SQLite storage.
Add profile codes to replace raw npub sharing
Intent: Introduce 'profile codes' — URIs that encode both the user's public key and their preferred chat server — replacing raw npub strings in the sharing, QR code, and deep link flows across all platforms.
@@ -89,48 +89,57 @@ class NostrConnectIntentTest {
- fun extractChatDeepLinkNpub_returnsNpubForValidChatIntent() {
+ fun extractChatDeepLinkCode_returnsRawCodeForValidChatIntent() {
@@ +0,0 @@
+ fun extractChatDeepLinkCode_preservesServerQuery()
A new profileCode field is threaded through the entire stack:
Data model
MyProfileState and PeerProfileState both gain a profileCode: String field in the UniFFI-generated Kotlin bindings and Swift equivalents.
The profile code is a URI like pika://chat/<hex_pubkey>?server=https%3A%2F%2Fchat.example that bundles identity with chat server routing.
Android
MyProfileScreen — QR code and copy button now use profileCode instead of npub. Section title changes from "Public Key" to "Profile Code".
PeerProfileSheet — Same pattern: displays and copies profileCode, falls back to npub if blank.
NewChatScreen — Input label changes from "Peer npub" to "Profile code". The normalizePeerKey() call is removed; raw input is trimmed and validated directly.
QrScannerDialog — Removes normalizePeerKey() intermediary; validates the scanned string directly with isValidPeerKey().
AppManager — extractChatDeepLinkNpub() is renamed to extractChatDeepLinkCode() and now returns the full URI (preserving query parameters like ?server=...) instead of just the path segment.
Tests — All deep-link tests are renamed and a new test extractChatDeepLinkCode_preservesServerQuery verifies query parameter preservation.
iOS
Parallel changes in SwiftUI views: MyNpubQrSheet, PeerProfileSheet, NewChatView, and QrScannerSheet all switch to profile codes.
Update pika-nse (Notification Service Extension) dependencies
Intent: Migrate the iOS/Android notification service extension from MDK to pika-mls for decrypting incoming push notifications.
The Notification Service Extension (pika-nse) is a lightweight crate used for decrypting message previews in push notifications without launching the full app:
Dependencies mdk-core, mdk-sqlite-storage, mdk-storage-traits, apple-native-keyring-store, and keyring-core are all removed.
A single pika-mls dependency replaces them.
The internal module mdk_support.rs is renamed to mls_support.rs with updated function signatures matching the new pika-mls API surface.
Since pika-mls handles its own keyring integration via feature flags, the NSE no longer needs to directly depend on platform keyring crates.
Update documentation and architecture diagrams
Intent: Refresh the README, architecture docs, and Mermaid diagrams to reflect the new chat-server-centric architecture and removal of MDK.
@@ -27,26 +27,26 @@ End-to-end encrypted messaging
-Pika uses the [Marmot protocol](https://github.com/marmot-protocol/mdk) to layer MLS group encryption on top of Nostr relays.
+Pika uses Nostr keys as identity roots and a Pika chat server for private-chat ordering, membership control, and delivery.
@@ -12,7 +12,6 @@ current remote setup
-| `marmot-protocol/mdk` | Marmot Development Kit. Rust MLS library used by pika. |
The "How it works" section replaces the Marmot/Nostr-relay explanation with a chat-server-centric description.
The ASCII architecture diagram changes: Nostr relays → Pika chat server with ordered room log arrows; MDK (MLS lib) → pika-mls local state.
Bullet points updated: MDK mention removed, pika-mls described as the "repo-local chat state engine".
CLI section updated: "testing the Marmot protocol" → "testing Pika chat flows".
AGENTS.md
The marmot-protocol/mdk row is removed from the repo reference table.
New diagrams
Multiple Mermaid diagrams are added under docs/diagrams/ covering chat-server linking flows: discovery, happy path, many-servers, partial failure, and total loss scenarios.
An HTML visualization for the new-device flow is added at docs/chat-server-new-device-flow.html.
Testing docs
docs/compatibility-testing-spec.md and docs/testing/integration-matrix.md are updated to reflect the new architecture.
Update pikahut integration test framework
Intent: Adjust the pikahut test harness and interop scenarios to work with pika-mls instead of MDK, ensuring compatibility testing covers the new welcome and group creation flows.
The pikahut crate — the integration test framework used for cross-client compatibility testing and OpenClaw interop scenarios — is updated:
pika-marmot-runtime is removed from its dependency list.
Test fixtures that previously constructed MDK state now use pika-mls APIs.
The OpenClaw interop test scenarios are adjusted to use the new welcome flow that goes through the chat server rather than raw relay welcome events.
Guardrail tests (tests/guardrails.rs) are updated to validate the new crate structure.
Clean up Cargo.lock transitive dependency tree
Intent: Remove all orphaned transitive dependencies that were only pulled in by MDK and its sub-crates, reducing the build graph and eliminating unused cryptographic primitives.
Affected files: Cargo.lock
Evidence
@@ removed packages
-name = "block-buffer" version = "0.12.0"
-name = "cobs" version = "0.3.0"
-name = "cpufeatures" version = "0.3.0"
-name = "crypto-common" version = "0.2.1"
-name = "curve25519-dalek" version = "5.0.0-pre.6"
-name = "digest" version = "0.11.2"
-name = "embedded-io" version = "0.4.0"
-name = "embedded-io" version = "0.6.1"
-name = "fiat-crypto" version = "0.3.0"
-name = "hybrid-array" version = "0.4.8"
-name = "keccak" version = "0.2.0-rc.2"
-name = "kem" version = "0.3.0-rc.6"
-name = "ml-kem" version = "0.3.0-rc.0"
-name = "module-lattice" version = "0.1.0"
-name = "postcard" version = "1.1.3"
-name = "refinery" version = "0.9.0"
-name = "sha3" version = "0.11.0-rc.8"
-name = "x-wing" version = "0.1.0-rc.0"
-name = "x25519-dalek" version = "3.0.0-pre.6"
The lock file cleanup removes approximately 20 crate entries that were transitive dependencies exclusively of MDK or its experimental crypto stack:
Duplicate crypto primitives — block-buffer 0.12, crypto-common 0.2, digest 0.11, cpufeatures 0.3, fiat-crypto 0.3, hybrid-array were all pre-release versions pulled by MDK's newer crypto stack. Only the stable 0.10.x versions remain.
Post-quantum/experimental — ml-kem, module-lattice, kem, x-wing, x25519-dalek 3.0.0-pre.6, sha3 0.11.0-rc.8 are all removed. These were experimental PQ key exchange primitives used by hpke-rs-rust-crypto's MDK-era configuration.
Serialization — postcard and cobs (used by mdk-storage-traits) are removed.
Migration framework — refinery, refinery-core, refinery-macros (used by mdk-sqlite-storage) are removed.
Embedded I/O — Both versions of embedded-io are removed.
Several libcrux crates are upgraded to newer patch versions (e.g., libcrux-sha3 0.0.6→0.0.8, libcrux-poly1305 0.0.4→0.0.5) and duplicate libcrux-intrinsics 0.0.5 is eliminated.