# Group Messaging with Encryption ### Libraries and Functions #### random - used to generate the encrypting/decrypting key ```rust try_from_random ``` #### x_salsa20_poly1305 ##### box (symmetric encryption) - used for encrypting/decrypting messages ```rust x_salsa20_poly1305_encrypt x_salsa20_poly1305_decrypt ``` ##### secretbox (assymetric encryption) - used for encrypting group encryption keys ```rust create_x25519_keypair x_25519_x_salsa20_poly1305_encrypt x_25519_x_salsa20_poly1305_decrypt ``` ### Structures ```rust pub struct IdentityKey { agent: AgentPubKey, key: X25519PubKey, } pub struct MessageEntry { author: AgentPubKey, receiver: Vec<AgentPubKey>, group_uuid: String, payload: String, time_sent: Timestamp, time_received: Option<Timestamp> } pub struct EncryptedMessageEntry { session_id: u32, data: XSalsa20Poly1305EncryptedData } pub struct GroupKey { session_id: u32, key: XSalsa20Poly1305KeyRef } pub struct EncryptedGroupKey { session_id: u32, key: XSalsa20Poly1305EncryptedData } ``` - **session_id** - determines changes in members which necessitates the change of the group encryption key ### Stages 1. Key creation - uses the TryFromRandom function to generate a series of bytes for the KeyRef - needs to be shifted to LAIR according to the docs - done during init() - done when a new key is needed when a member joins or leaves the group 2. Key distribution - group encryption keys are encrypted using `box` encryption - sender: group moderator/creator - recipient: individual members - encrypted key entries are committed to the DHT and linked to individual member's AgentPubKeys - done when a member is added to the group - a new encrypted group key entry (using the new member's pubkey) is committed to the DHT - done when a member is removed from the group - a new key is generated - new encrypted group key entries to correspond to all members - will be moved to an offchain ephemeral storage to minimize the encryption processing of keys per member 3. Encryption and Decryption - messages are encrypted/decrypted by the `secretbox` function - messages are matched to corresponding keys based on a session id ### Functions #### init() - run when cloning and initiating the group DNA - creates an x25519 keypair (public and private) for the agent - commits an `IdentityKey` entry in the DHT (public entry) - links the `IdentityKey` entry to the agent's pubkey ```mermaid sequenceDiagram participant ACC as Alice_Conversation_Cell participant DHT as DHT ACC-->>ACC: call `init` ACC-->>ACC: call create_x25519_keypair ACC-->>DHT: create_entry IdentityKey containing the Public Encrypting Key ACC-->>DHT: create_link from agent's AgentPubKey to IdentityKey ``` #### create_key() -> ExternResult\<()> ```rust= pub struct create_key_input { session_id: u32, agent: Vec<AgentPubKey> } ``` - creates a group encryption key using TryFromRandom - creates a GroupKey structure - use 0 as the session_id for the initial group - add the generated group encryption key - encrypts the GroupKey structure using own x25519 key ```mermaid sequenceDiagram participant ACC as Alice_Conversation_Cell participant DHT as DHT ACC-->>ACC: use TryFromRandom to generate encryption key ACC-->>ACC: get_links for GroupKey with group as base and "latest_group_key" as tag alt no link found ACC-->>ACC: use 0 as session_id else link found ACC-->>ACC: get entry ACC-->>ACC: add 1 to latest session id end ACC-->>ACC: create a GroupKey structure ACC-->>DHT: get member list DHT-->>ACC: return member list loop for member in list ACC-->>ACC: encrypt entry using member's x25519 key ACC-->>DHT: create EncryptedGroupKey entry in DHT ACC-->>DHT: create link from each Agent PubKey to EncryptedGroupKey using their respective Agent PubKey as tags end ACC-->>ACC: encrypt entry using own x25519 key ACC-->>DHT: create EntryptedGroupKey entry in DHT ACC-->>DHT: delete link from previous latest key ACC-->>DHT: create link with Group entry as base, previous EncryptedGroupKey as target, and session_id as tag ACC-->>DHT: create link with Group entry as base, new EncryptedGroupKey as target, and "latest_group_key" as tag ``` #### add_member(agent: AgentPubKey) -> ExternResult\<()> ```mermaid sequenceDiagram participant ACC as Alice_Conversation_Cell participant DHT as DHT participant BCC as Bobby_Conversation_Cell Note over ACC: through what mechanism? ACC-->>BCC: ask new member to clone Group DNA BCC-->>BCC: clone Group DNA BCC-->>BCC: calls `init` to create x25519 key Note over BCC: do we have return values? how can we signal creation success (either remote signal or call_remote) BCC-->>ACC: return Ok ACC-->>ACC: call `create_key` ``` #### remove_member(agent: AgentPubKey) -> ExternResult\<()> ```mermaid sequenceDiagram participant ACC as Alice_Conversation_Cell participant DHT as DHT participant BCC as Bobby_Conversation_Cell ACC-->>ACC: call `remove_member` (updates member list) ACC-->>ACC: call `create_key` ``` #### get_agent_key(agent: AgentPubKey) -> ExternResult\<IdentityKey> ```rust pub struct IdentityKey { agent: AgentPubKey, key: X25519PubKey, } ``` ```mermaid sequenceDiagram participant fn as caller_fn participant ACC as Alice_Conversation_Cell participant DHT as DHT fn-->>ACC: call `get_agent_key` with an agent's PubKey as input ACC-->>DHT: get_links on AgentPubKey with tag "identity_key" DHT-->>ACC: return links ACC-->>DHT: get_details on link target DHT-->>ACC: return details ACC-->>ACC: convert details to IdentityKey entry ACC-->>fn: return IdentityKey ``` #### encrypt_key(input: EncryptInput) -> ExternResult\<XSalsa20Poly1305EncryptedData> ```rust= pub struct EncryptInput { recipient: AgentPubKey, data: XSalsa20Poly1305Data, } ``` ```mermaid sequenceDiagram participant fn as caller participant ACC as Alice_Conversation_Cell participant DHT as DHT fn-->>ACC: call `encrypt_key` ACC-->>DHT: call `get_agent_key` with the recipient AgentPubKey as input DHT-->>ACC: return IdentityKey note over ACC: can we do a query here instead? ACC-->>DHT: call `get_agent_key` with the own AgentPubKey as input DHT-->>ACC: return IdentityKey ACC-->>ACC: call `x_25519_x_salsa20_poly1305_encrypt` with the returned X25519PubKeys ACC-->>fn: return encrypted data ``` #### decrypt_key(input: DecryptInput) -> ExternResult\<XSalsa20Poly1305Data> ```rust= pub struct EncryptInput { sender: AgentPubKey, data: XSalsa20Poly1305Data, } ``` ```mermaid sequenceDiagram participant fn as caller participant ACC as Alice_Conversation_Cell participant DHT as DHT fn-->>ACC: call `decrypt` ACC-->>DHT: call `get_agent_key` with the sender AgentPubKey as input DHT-->>ACC: return IdentityKey note over ACC: can we do a query here instead? ACC-->>DHT: call `get_agent_key` with the own AgentPubKey as input DHT-->>ACC: return IdentityKey ACC-->>ACC: call `x_25519_x_salsa20_poly1305_decrypt` with the returned X25519PubKeys ACC-->>fn: return decrypted data ``` #### send_message ```rust= pub struct EncryptedMessageEntry { session_id: u32, data: XSalsa20Poly1305EncryptedData } ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ACC as Alice_Conversation_Cell participant DHT as DHT AUI-->>ACC: call `send_message` ACC-->>ACC: construct Message Entry ACC-->>ACC: get latest session_id ACC-->>ACC: get latest encryption key ACC-->>ACC: decrypt key using own x25519 key ACC-->>ACC: call x_salsa20_poly1305_encrypt() ACC-->>ACC: construct EncryptedMessage Entry adding the session_id ACC-->>DHT: commit EncryptedMessage Entry to chain ACC-->>AUI: return Message Entry ``` ### get_messages (general) ```rust= pub struct MessageEntry { author: AgentPubKey, receiver: Vec<AgentPubKey>, payload: Payload, time_sent: Timestamp, reply_to: Option<EntryHash>, } pub struct EncryptedMessageEntry { session_id: u32, data: XSalsa20Poly1305EncryptedData } ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ACC as Alice_Conversation_Cell participant DHT as DHT AUI-->>ACC: call `get_fn` ACC-->>DHT: gets encrypted messages DHT-->>ACC: return encrypted messages ACC-->>DHT: get all encryption keys linked to own PubKey loop for encryption_keys in get results ACC-->>ACC: decrypt key ACC-->>ACC: add key to a hashmap with session_id as keys end loop for encrypted_message in get results ACC-->>ACC: check for filtering conditions (author, receiver, hash) alt message should be returned ACC-->>ACC: get session_id from encrypted message entry ACC-->>ACC: get corresponding encryption key from hashmap ACC-->>ACC: call `decrypt()` on encrypted_message using key else message should not be returned ACC-->>ACC: continue with loop end end ``` ### Security Analysis and Comparison to the Signal Protocol ```mermaid sequenceDiagram participant UI as UI participant WASM as WASM participant Lair as Lair participant Chain as Chain participant WASM2 as WASM2 participant Chain2 as Chain2 UI-->>WASM: plaintext WASM-->>Lair: plaintext Lair-->>Lair: encrypt Lair-->>WASM: encrypted WASM-->>Chain: encrypted WASM-->>WASM2: encrypted + encryption via `call_remote` WASM2-->>Chain2: encryped ``` | | | Signal | Holochain | Comment | |----------- |----------------------------------- |-------------------- |-------------------- |------------------------------------------------ | | | Confidentiality | :heavy_check_mark: | :heavy_check_mark: | | | | Integrity | :heavy_check_mark: | :x: | Need MACs or signatures. | | | Authentication | :heavy_check_mark: | partial | | | | Participant Consistency | :heavy_check_mark: | partial | Need MACs or signatures. | | | Destination Validation | :heavy_check_mark: | partial | Need MACs or signatures. | | | Forward Secrecy | :heavy_check_mark: | :x: | Single encrypting key for all messages | | | Backward Secrecy/Self-Healing/PCS | :heavy_check_mark: | :x: | Single encrypting key for all messages | | | Anonymity Preserving | | :x: | Encryption PubKeys are linked to AgentPubKeys | | | Speaker Consistency | :heavy_check_mark: | | | | | Causality Preserving | :heavy_check_mark: | | | | | Global Transcript | | | | | | Message Unlinkability | :heavy_check_mark: | :x: | Author and recipient agent keys are plaintext. | | | Message Repudiation | :heavy_check_mark: | :x: | Author and recipient agent keys are plaintext. | | | Participant Repudiation | :heavy_check_mark: | :x: | Author and recipient agent keys are plaintext. | | Usability | Out-of-Order Resilient | :heavy_check_mark: | :heavy_check_mark: | Single encrypting key for all messages | | Usability | Dropped Message Resilient | :heavy_check_mark: | :heavy_check_mark: | Single encrypting key for all messages | | Usability | Asynchronicity | :heavy_check_mark: | partial | | | Usability | Multi-Device Support | partial | partial | | | Usability | No Additional Service | | TBD | No need for a key server aside from DHT. | | Group | Computational Equality | :heavy_check_mark: | NA | | | Group | Trust Equality | :heavy_check_mark: | NA | | | Group | Subgroup Messaging | :heavy_check_mark: | NA | | | Group | Contractable | :heavy_check_mark: | NA | | | Group | Expandable | :heavy_check_mark: | NA | | ### Things to keep in mind - security is multi-layered - validation rules - capability grants - membrane proof - encryption via call_remote ### Issues - single point of failure (encrypting keys)