# Group (Same DHT) - Without Encryption ###### tags: `Kizuna-architecture` <style> .ui-infobar, #doc.markdown-body { max-width: 1000px; } </style> #### comments - This document is a fork of this more complete [document](https://hackmd.io/4s2hZn0-ToeNt22_J57FpQ) - This document was created for ease of implementation of group WITHOUT encryption. The other document is going to be updated more constantly. - When HoloPort allowed cloneable DNA, this will be refactored to fit the cloneable DNA pattern - Messages have to be push based if we want instant update for all members (but then is taxing for the sender) - Spam protection for creating meaningless groups so many times - limiting the number of groups that can be created by an agent within a timeframe through the usage of local timestamp + the signature of the Group entry + counter - `leave_group` is architected minimally as there will be drastic change when this pattern shifts to cloneable DNA. - `block_group` is not yet architected and will be with cloned DNA in mind - right now, there is no check whether the same agent is being added twice in a same group. should we check this at input level as well as in the validation? ## Entry Structure ``` rust // verify the signature of this entry with Group entry "creator" #[hdk_entry(id = "group", visibility = "public")] struct Group { name: String, created: Timestamp, creator: AgentPubKey, members: Vec<AgentPubKey>, } Links: { agent_pubkey->group|member| } // unix_timestamp is denominated in days Path::from("<group_entry_hash>.<unix_timestamp>") Links: { Path(group_entry_hash)->Path(group_entry_hash.unix_timestamp) Path(group_entry_hash.unix_timestamp)->group_message|(day)'s message| } // ENCRYPTED #[hdk_entry(id = "group_message", visibility = "public")] struct GroupMessage { group_hash: EntryHash, key_ref: SecretBoxKeyRef, // counter: u8, // nonce: Vec<u8>, // needs to be unique per message. If we could set the nonce's length, we can do DNA hash + agentPubKey + counter // set one nonce and counter per agent. encrypted_payload: XSalsa20Poly1305EncryptedData, // Contains payload, craeted, sender, reply_to } // NOT ENCRYPTED #[hdk_entry(id = "group_message", visibility = "public")] struct GroupMessage { // EntryHash of first ver of Group group_hash: EntryHash, payload: Payload, created: Timestamp, sender: AgentPubKey, reply_to: Option<EntryHash> } ``` ## Entry Relationship Diagram - group_v3's links are ommitted but works the same way with group_v2. Only shown to signify that update pattern. ```mermaid graph TD subgraph group_zome subgraph group_entry group_a==>|update by add/remove_members/update_name|group_a_v2 group_a_v2 group_a==>|update by add/remove_members/update_name|group_a_v3 end subgraph agents alice-->|member|group_a bobby-->|member|group_a clark-->|member|group_a diego-->|member|group_a group_a.->alice group_a.->bobby group_a.->clark group_a_v2.->alice group_a_v2.->bobby group_a_v2.->clark group_a_v2.->diego end subgraph group_message_entry group_message_1.->group_a group_message_2.->group_a group_message_3.->group_a group_a-->|pinned|group_message_1 end subgraph path group_entry_hash-->gorup_message group_entry_hash-->unix_timestamp_2 group_entry_hash-->unix_timestamp_3 gorup_message-->|tag 'text'|group_message_1 gorup_message-->|tag 'text'|group_message_2 gorup_message-->|tag 'file'|group_message_3 end end ``` ## Zome Functions ### `create_group` ```rust pub struct CreateGroupInput { name: String, // cannot be empty and must at least include 2 pubkeys // creator AgentPubKey is not included here members: Vec<AgentPubKey> } pub struct CreateGroupOutput { content: Group, group_id: EntryHash, group_revision_id: HeaderHash } fn create_group(create_group_input: CreateGroupInput) -> ExternResult<CreateGroupOutput> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice_Lobby_CELL participant LDHT as Lobby_DHT participant IM as Initial_Members AUI-->>ALD: call `create_group` rect rgba(255, 0, 0, .5) ALD-->>ALD: call list_blocked() to contacts zome ALD-->>ALD: check if any members are blocked alt if even one agent is blocked ALD-->>AUI: return error("cannot create group with blocked agents") note right of ALD: dep->contacts zome end end ALD-->>LDHT: commit group entry LDHT-->>ALD: return header address loop for all members ALD-->>LDHT: link from each member to Group entry tagged with the EntryHash of Group as String end loop for all added agents ALD-->>IM: send remote signal with Group EntryHash note right of ALD: check forum for not getting the entry just committed to DHT end ALD-->>ALD: construct CreateGroupOutput ALD-->>AUI: return CreateGroupOutput ``` ### `add_members` - can only be called by the admin of the group ```rust= pub struct UpdateMembersIO { members: Vec<AgentPubKey>, // assume that original HeaderHash is given here group_id: EntryHash group_revision_id: HeaderHash } pub fn add_members(add_member_input: UpdateMembersIO) -> ExternResult<UpdateMembersIO> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice(Admin)_Lobby_CELL participant LDHT as Lobby_DHT participant OM as Other Members AUI-->>ALD: AddMembersInput ALD-->>ALD: check whether members field is empty alt if empty ALD-->>AUI: return Err(members field is empty) end note right of ALD: this is also checked in the UI rect rgba(255, 0, 0, .5) ALD-->>ALD: call list_blocked() to contacts zome ALD-->>ALD: check if any invitees are blocked and dont add blocked members to the group alt if anyone is blocked ALD-->>AUI: return error("blocked agents cannot be added to the group") note right of ALD: dep->contacts zome end end ALD-->>LDHT: get_deatils the Group entry with EntryHash given note right of ALD: get should have the latest() option. LDHT-->>ALD: return Group EntryDetails ALD-->>ALD: filter the latest Header (should be element) note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change. ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash note right of ALD: getOption is contents() LDHT-->>ALD: most recent Group Entry ALD-->>ALD: add member to members field of Group ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash loop for all newly added agents ALD-->>LDHT: link from each member to original Group entry tag with the EntryHash of Group as String end ALD-->>OM: send remote signal with GroupMembers EntryHash to all members ALD-->>AUI: HeaderHash ``` ### `remove_members` - can only be called by the admin of the group - this will be drastically changed once the pattern moves to cloneable DNA ```rust= // same with add_members input pub struct UpdateMembersIO { members: Vec<AgentPubKey>, // assume that original HeaderHash is given here group_id: EntryHash group_revision_id: HeaderHash } pub fn remove_members(member: UpdateMembersIO) -> ExternResult<UpdateMembersIO> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice(Admin)_Lobby_CELL participant LDHT as Lobby_DHT participant OM as Other Members AUI-->>ALD: AddMembersInput ALD-->>ALD: check whether members field is empty alt if empty ALD-->>AUI: return Err(members field is empty) end note right of ALD: this is also checked in the UI ALD-->>LDHT: get_deatils the Group entry with EntryHash given note right of ALD: get should have the latest() option. LDHT-->>ALD: return Group EntryDetails ALD-->>ALD: filter the latest Header (should be element) note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change. ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash note right of ALD: getOption is contents() LDHT-->>ALD: most recent Group Entry ALD-->>ALD: remove member from members field of Group ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash loop for all removed agents ALD-->>LDHT: remove link from removed members to original Group entry tag "member" note right of ALD: see if looping through all links here is too much of an overload and change implementation if necessary. end ALD-->>OM: send remote signal with GroupMembers EntryHash to all remaining members ``` ### `get_all_my_groups` - TODO: invited checking if the invitor is blocked (warning on ui) - probably need to fetch the messages with the group as well ```rust pub struct GroupOutput { // taken from the latest update of Group entry latest_name: String, // taken from the latest update of Group entry members: Vec<AgentPubKey>, // This should be the EntryHash of the very first Group Entry group_id: EntryHash, // the HeaderHash of the very first Group entry group_revision_id: HeaderHash, // If this will only be used to get the secrets then we can probably only return the relevant info for that // for now, no need to implement this field. // group_versions: Vec<Group>, created: Timestamp, creator: AgentPubKey, } pub struct GroupOutputsWrapper(Vec<GroupOutput>) pub fn get_all_groups() -> ExternResult<Groups> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice(Admin)_Lobby_CELL participant LDHT as Lobby_DHT AUI-->>ALD: call get_all_my_groups ALD-->>LDHT: get_links agent_pubkey->group|member| LDHT-->>ALD: return Links note right of ALD: can get group_id loop for each link ALD-->>LDHT: get_details on Target(Group EntryHash) note right of ALD: GetOption latest() LDHT-->>ALD: Group EntryDetails note right of ALD: can get group_revision_id , creator, created from here ALD-->>ALD: check if there is update or not alt there is an update for group Entry ALD-->>ALD: check which update header is the latest ALD-->>LDHT: get entry of the EntryHash in latest update header note right of ALD: GetOption contents() note right of ALD: may change once get_details() API returns elements in update field and not just header LDHT-->>ALD: latest Group entry note right of ALD: can get members and latest_name note right of ALD: need to get all versions of GroupMembers if need to know the secrets (id?) end ALD-->>ALD: create GroupOutput end ALD-->>ALD: collect GroupOutputs into Groups ALD-->>AUI: return Groups ``` ### `update_group_name` * Admin only * updating group entry may change in multi-admin pattern. * store header hashes in ui. * check whether the new name is the same with old name and return error if so (also checked in ui) ```rust pub struct UpdateGroupNameIO { name: String, group_id: EntryHash, group_revision_id: HeaderHash } pub fn update_group_name(group_input: UpdateGroupNameIO) -> ExternResult<UpdateGroupNameIO> ``` ```mermaid sequenceDiagram participant AUI as Alice_UI participant ALD as Alice(Admin)_Lobby_CELL participant LDHT as Lobby_DHT participant OM as Other Members AUI-->>ALD: UpdateGroupNameInput ALD-->>LDHT: get_deatils the Group entry with EntryHash given note right of ALD: get should have the latest() option. LDHT-->>ALD: return Group EntryDetails ALD-->>ALD: filter the latest Header (should be element) note right of ALD: the current API only returns Vec<SignedHeaderHashed> so we need to call get on the EntryHash found in the latest update header. This architecture should be changed once get_details() API change. ALD-->>LDHT: call get() on most recent update of Group Entry with EntryHash note right of ALD: getOption is contents() LDHT-->>ALD: most recent Group Entry ALD-->>ALD: check whether the new name and the old name are the same alt same name ALD-->>AUI: return Err("cannot update group name with same group name") end ALD-->>ALD: update the name field ALD-->>LDHT: update_entry the Group with new members field with original HeaderHash LDHT-->>ALD: Header ALD-->>AUI: Header ``` ### `leave_group` - this function will be architected once moved to cloneable DNA pattern - this will change current validation of group_members ### `block_group` - this function will be architected once moved to cloneable DNA pattern - this will change current validation of group_members ### Validations #### Entries * `group` * create is valid if creator pubkey matches the signature * create is valid if group name is not more than 50 characters * create is valid if group name is at least one character long * craete is valid only if members field do not contain the id of creator * delete is valid if creator pubkey matches the signature (not implemented) * update is valid if creator pubkey matches the signature * update is only valid if the old_entry's header is Create * update is valid only if members > 2 * update is valid only if members field do not contain the id of creator <!--- update is only valid if old_group_name != new_group_name | old_members != new_members * `group_message` ---> * <!--- This has been disabled due to not having get_details available in validation. The cost of ensuring this is much more expensive than updating the Group entry with same content ---> * create is only valid when the author is part of the group members given GroupMembers entry hash field in GroupMessage #### Links * `agent_pubkey->group|member|` * `Path(all_groups)->group` * `Path(CREATOR_PUB_KEY)->group` * `Path(unix_timestamp)->group_message` * create is only valid if the author of the link is part of the group members given group members being the target of the link ## [Redux](https://hackmd.io/Ga6gwkLAR3KWqstFwjJjGg#Redux)