UC-019: Receive DIDComm Message
Functional Specification
This use case defines intended behavior. DIDComm message reception is not yet integrated in the wallet — messages are currently stored locally only.
Description
The wallet receives a DIDComm v2 message sent by another wallet through the mediator. Messages arrive via two mechanisms: real-time delivery over the WebSocket connection when the wallet is online, or offline pickup using the Message Pickup 3.0 protocol when the wallet reconnects after being offline. The wallet decrypts the received message, validates the sender's signature, stores it in the local conversation history, and displays it in the chat UI.
Actors
- Recipient (End User): Person receiving the message in their wallet
- Wallet (Frontend): Svelte application displaying the incoming message in the chat UI
- Wallet (Rust Backend): Tauri commands performing JWE decryption and JWS signature verification
- Mediator: Go service delivering the encrypted message via WebSocket or holding it for pickup
Preconditions
- The recipient has an identity created in the wallet (UC-001)
- The wallet session is unlocked
- The wallet is registered with the mediator (UC-017)
- The sender has previously sent a message to this wallet (UC-018)
Main Flow (Real-time delivery)
- The wallet maintains an active WebSocket connection to the mediator (
/ws?did=<wallet_DID>) - The mediator receives a forwarded message destined for this wallet (UC-021)
- The mediator detects the recipient has an active WebSocket connection
- The mediator delivers the encrypted inner envelope over the WebSocket
- The wallet receives the encrypted message on the WebSocket handler
- The wallet invokes the Rust backend to unpack the message:
- Decrypts the JWE using the wallet's X25519 private key (derived from Ed25519)
- Verifies the JWS signature against the sender's Ed25519 public key
- Extracts the plaintext DIDComm Basic Message
- The wallet validates the message structure:
- Type is
https://didcomm.org/basicmessage/2.0/message fromcontains a valid DIDbody.contentis present and not emptythid(thread ID) is present
- Type is
- The wallet checks if the sender is an existing contact:
- If yes: The message is added to the existing conversation
- If no: A new contact is created automatically with the sender's DID (no alias) and the message starts a new conversation
- The wallet stores the received message in the Tauri Store (
chat.json) under the sender's conversation - If the user is currently viewing the conversation with this sender:
- The message appears as a received bubble (left-aligned, dark background) with the timestamp
- The view auto-scrolls to the new message
- If the user is on the conversation list or elsewhere:
- The conversation's
unreadCountis incremented - The conversation moves to the top of the list with the new message preview
- A notification badge shows on the chat navigation item
- The conversation's
- The wallet sends a
messages-receivedacknowledgment to the mediator so the message is removed from the queue
Alternative Flow: Offline Message Pickup
AF-PICKUP: Retrieve messages after being offline
- The wallet opens or the WebSocket connection is re-established after being offline
- The wallet sends a
status-requestmessage to the mediator:- Type:
https://didcomm.org/messagepickup/3.0/status-request
- Type:
- The mediator responds with
statusindicating the number of queued messages - If there are queued messages (count > 0):
- The wallet sends a
delivery-request:- Type:
https://didcomm.org/messagepickup/3.0/delivery-request limit: number of messages to retrieve (batch size, default 10)
- Type:
- The mediator responds with a
deliverymessage containing the queued encrypted messages as attachments - The wallet processes each message following steps 6–11 of the main flow
- The wallet sends
messages-receivedwith the IDs of processed messages - The mediator removes the acknowledged messages from the queue
- The wallet repeats until all queued messages are retrieved (status count reaches 0)
- The wallet sends a
Other Alternative Flows
AF-1: Invalid message format
- At step 7, the message structure is invalid (unknown type, missing fields)
- The wallet silently discards the message and logs the error locally
- No message is stored or displayed
AF-2: Signature verification fails
- At step 6, the JWS signature does not match the sender's public key
- The wallet discards the message — it may have been tampered with
- The wallet logs a security warning locally
AF-3: Decryption fails
- At step 6, the JWE decryption fails (wrong key, corrupted message)
- The wallet discards the message and logs the error
- This may indicate the sender encrypted for the wrong recipient
AF-4: WebSocket disconnected during delivery
- The WebSocket connection drops before the message is fully received
- The mediator retains the message in the offline queue
- When the wallet reconnects, it retrieves the message via the pickup flow (AF-PICKUP)
AF-5: Sender blocked
- In a future iteration, the user may block specific DIDs
- Messages from blocked DIDs are decrypted to verify the sender, then silently discarded
- No notification or conversation update occurs
Postconditions
- The received message is decrypted, validated, and stored in the local conversation history
- The chat UI reflects the new message (inline if the conversation is open, or as an unread indicator)
- The mediator has removed the delivered message from its queue (after acknowledgment)
Modules Involved
| Module | Role |
|---|---|
| wallet (frontend) | WebSocket listener, message display in chat UI, unread count badges, notification indicators |
| wallet (Rust backend) | X25519 decryption (JWE), Ed25519 signature verification (JWS), sender DID resolution |
| mediator | Real-time WebSocket delivery, offline message queue, Message Pickup 3.0 protocol, delivery acknowledgment |
Technical Notes
- Two delivery modes: Real-time (WebSocket push) is preferred when the wallet is online. Offline pickup (Message Pickup 3.0 polling) is the fallback. The wallet should always perform a status check on reconnect to retrieve any missed messages
- Message Pickup 3.0: Protocol
https://didcomm.org/messagepickup/3.0/. Three message types:status-request→status,delivery-request→delivery,messages-received. The wallet controls the batch size via thelimitfield indelivery-request - Auto-contact creation: When a message arrives from an unknown DID, the wallet automatically creates a contact entry. The user can later assign an alias or delete the conversation (UC-020)
- Unread tracking: The
unreadCountfield inConversationSummarytracks unread messages per conversation. It resets to 0 when the user opens the conversation. Currently defined in the chat service but not connected to transport - Mediator message TTL: The mediator stores offline messages for up to 72 hours (configurable
message_ttlinmediator.yaml). Messages not picked up within this window are permanently deleted - Max stored messages: The mediator stores up to 1000 messages per wallet DID (configurable
max_stored_messages). If the limit is reached, the oldest messages are discarded - Notification: Currently limited to in-app badge indicators. Future: OS-level push notifications on mobile (via Tauri mobile plugins) and desktop (via system notification APIs)
- Security: The wallet never stores the encrypted envelope — only the decrypted plaintext is persisted locally. The sender's identity is verified via JWS signature before any storage or display