# Direct Messaging Zome ## User Stories - As a user, I want to send a message to a contact so that I can deliver textual information to that user - As a user, I want to reply to a specific message so that it would be clear to which message i'm replying to. - As a user, I want to be able to search messages in a chat using a keyword so that I can easily find previously sent messages - As a user, I want to be able to search messages in a chat using a keyword so that I can easily find previously sent messages - As a user, I want to see the status of my messages so that I know if my message has been sent or if my messages has been seen (sending, sent, seen, error) - As a user, I want to search messages by date so that I can pinpoint specific messages - As a user, I want to delete my own message so that I can remove wrong sent messages. - As a user, I want to edit a message I sent so that I can update information. ## Asynchronous node-to-node E2E messaging (with Holo) - This pattern is inspired from [mailbox](https://forum.holochain.org/t/mailbox/1084) and [async node2node messaging](https://forum.holochain.org/t/asynchronous-private-messaging/1085) pattern - We still need to implement E2E scheme for this. Currently, the de facto standard encryption protocol for synchronous/asynchronous messaging is the signal protocol. Simplified diagram can be found [here](https://hackmd.io/@tatssato/rk6ZZ5dNP) - This will be fully functional with a Public Key Infrastructre like [DeepKey](https://developer.holochain.org/docs/guide/dpki/) - TODO: Work on update/delete - can private entries be linked in RSM? (test this) - How to effectively segregate private messages entries in a single source chain? - Is there any risk in leaving encrypted message stay on DHT forever? ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice_Lobby_DNA participant BLD as Bobby_Lobby_DNA participant BUI as Bobby_UI participant LDHT as Lobby_DHT AUI-->>AUI: Signs the message (chaperone) AUI-->>ALD: call send_message ALD-->>ALD: commit the message in source chain (flag: not_received) alt DPKI available ALD-->>LDHT: get PubKey of Bobby through DPKI LDHT-->>ALD: send PubKey else DPKI not available alt online ALD-->>BLD: ask for PubKey BLD-->>ALD: send PubKey else offline ALD-->>AUI: Recipient offline AUI-->>AUI: prompt resend end end ALD-->>ALD: encrypt payload with bobby PubKey ALD-->>BLD: call_remote to call receive_message (check CAP token?) alt online BLD-->>BLD: check the provenance of payload against the author field alt provenance match BLD-->>BLD: commit the payload in the source chain. BLD-->>BUI: Send the encrypted entry BLD-->>ALD: send received flag: true ALD-->>ALD: link received flag to message payload (tentative) BUI-->>BUI: decrypt the payload (chaperone) BUI-->>BUI: render message else provenance mismatch BLD-->>ALD: provenance mismatch ALD-->>AUI: send failed (provenance mismatch) AUI-->>AUI: prompt resend end else offline BLD-->>ALD: recepient offline ALD-->>LDHT: commit encrypted message payload and link to Bobby Agent ID BUI-->>BLD: call fetch_messages when back online BLD-->>LDHT: get messages LDHT-->>BLD: messages BLD-->>BLD: commit messages to source chain BLD-->>LDHT: remove links from agent_id to message (Is this enough?) BLD-->>BUI: messages BUI-->>BUI: render message end ``` ## Entry Structure ```rust // This could be considered as the genesis of a conversation between 2 agents. // This will only be committed synchronously to the receving agent // with call_remote to avoid leaving traces of social connection in DHT. // If the agent's source chain is hosted on Holo, then there is at least a 98.6% // for synchronous remote call to work. // Counter signing this entry might be helpful to ensure authenticity. // QUESTION: Should we have this? Or just have have genesis field // in incoming and outgoing message? #[hdk_entry(id="conversation", visbility="private")] struct Conversation { creator: AgentPubKey, conversant: AgentPubKey, id: String, first_message_address: Option<EntryHash> timestamp: Timestamp, } let conversation = Conversation::new(); let outgoing_message = OugoingMessage::new(); create_link!(conversation, outgoing_message); 1. if conv exist { let link = get_link(hash_entry!(&conversation)); let first_msg = get!(link.target); let first_msg_hash = hash!(first_msg) let new_message = IncomingMessage { previous_message_address: Some(first_msg_hash) } create_link!(first_msg_hash, new_message_hash) create_link!(conversation, new_message, 1); } 2. // This will be an entry that will be committed to the DHT in case the // receiver is offline. // This entry is going to be encrypted and signed with // frequently rotating receiver's signing key and encryption key respectively. #[hdk_entry(id="outgoing_message", visibility="public")] struct OutgoingMessage { conversation_address: EntryHash, receiver: AgentPubKey, // if None, then this is the first message. previous_message_address: Option<EntryHash>, conversation_address: Option<EntryHash> // will be true if call_remote works, // if committed to DHT then it will be false // synchronous: bool, payload: String, timestamp: Timestamp, } Links: { // gets deleted once fetch is done receiving_agent_pubkey->outgoing_message|mailbox| } /* IGNORE // This will be the entry that will be replicated from // OutgoingMessage and committed privately // in the receiver's source chain. // For every IncomingMessage there should be an OutgoingMessage. */ #[hdk_entry(id="incoming_message", visibility="private")] struct IncomingMessage { conversation_address: EntryHash, sender: AgentPubKey, // Will be None if received synchronously // if None then synchronous should be true // outgoing_message_address: Option<EntryHash>, previous_message_address: Option<EntryHash>, conversation_address: Option<EntryHash>, // Should be false if outgoing_message_address is true // synchronous: bool, timestamp: Timestamp, payload: String, } ``` ## Entry Relationship Diagram ```mermaid graph TD subgraph dm zome subgraph agents alice_pubkey bobby_pubkey end subgraph conversation alice_bobby_conversation -.->|creator| alice_pubkey alice_bobby_conversation -.->|conversant| bobby_pubkey end subgraph outgoing_message outgoing_message_to_bobby_#1 -.-> alice_bobby_conversation outgoing_message_to_bobby_#1 -.-> alice_pubkey outgoing_message_to_bobby_#1 -.-> bobby_pubkey bobby_pubkey --> |mailbox - deleted once fetch|outgoing_message_to_bobby_#1 end subgraph incoming_message incoming_message_from_alice_#1 -.-> alice_pubkey incoming_message_from_alice_#1 -.-> bobby_pubkey incoming_message_from_alice_#1 -.-> alice_bobby_conversation incoming_message_from_alice_#1 -.-> |counterpart|outgoing_message_to_bobby_#1 incoming_message_from_bobby_#2 -.-> alice_pubkey incoming_message_from_bobby_#2 -.-> bobby_pubkey incoming_message_from_bobby_#2 -.-> |conversation|alice_bobby_conversation incoming_message_from_bobby_#2 -.-> |previous_message|outgoing_message_to_bobby_#1 incoming_message_from_bobby_#2 -.-> incoming_message_from_alice_#1 end end ``` ## Validation ### Entries ### Links