{"id":28396273,"url":"https://github.com/ssbc/ssb-group-exclusion-spec","last_synced_at":"2026-01-29T10:48:05.837Z","repository":{"id":135634687,"uuid":"599774593","full_name":"ssbc/ssb-group-exclusion-spec","owner":"ssbc","description":null,"archived":false,"fork":false,"pushed_at":"2023-10-16T11:01:05.000Z","size":113,"stargazers_count":12,"open_issues_count":5,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-06-01T08:12:44.978Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ssbc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSES/CC-BY-4.0.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-02-09T21:15:04.000Z","updated_at":"2023-10-06T07:02:42.000Z","dependencies_parsed_at":"2024-03-17T02:14:02.987Z","dependency_job_id":"036b098a-0ccf-4ad1-a51e-6032bda31142","html_url":"https://github.com/ssbc/ssb-group-exclusion-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ssbc/ssb-group-exclusion-spec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-group-exclusion-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-group-exclusion-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-group-exclusion-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-group-exclusion-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssbc","download_url":"https://codeload.github.com/ssbc/ssb-group-exclusion-spec/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-group-exclusion-spec/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259297632,"owners_count":22836431,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-05-31T21:37:48.351Z","updated_at":"2026-01-29T10:48:05.831Z","avatar_url":"https://github.com/ssbc.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: 2023 Andre 'Staltz' Medeiros \u003ccontact@staltz.com\u003e\n\nSPDX-License-Identifier: CC-BY-4.0\n--\u003e\n\n# ssb-group-exclusion-spec\n\nVersion: 1.0\n\nAuthors: Mix Irving \u003cmix@protozoa.nz\u003e, Andre Staltz \u003ccontact@staltz.com\u003e\n\nLicense: This work is licensed under a Creative Commons Attribution 4.0\nInternational License.\n\n\n## Abstract\n\nIn this document, we describe how SSB private groups can create the illusion of\ngroup member removal by cycling the symmetric keys in \"epochs\", thus effectively\nexcluding a peer from participation in the new epochs.  We also address how to\nresolve various cases of diverging epochs, such that group members follow rules\nthat arrive to consensus on which epoch new content must be published on.\n\n\n## 1. Introduction\n\nPrivate groups in SSB are contexts where a set of peers (called \"group members\"\nor \"members\") use the same symmetric encryption key (called \"group key\") to\nshare content with each other privately.  Although adding a peer to the set is a\nsimple matter of forwarding the symmetric encryption key, removing a peer is\nimpossible under the assumptions we operate with.\n\nThe naive solution to this problem is for some group member to create a new\ngroup key (called the \"epoch key\") and to share it with all the existing members\nexcept with the undesired member(s).  From this point onwards, all\nremaining members who have received the epoch key will proceed to encrypt new\ncontent using the epoch key, effectively excluding the undesired member(s) from\ndecrypting that.  This is why we call this procedure \"group member exclusion\",\npreventing them from entering new epochs, not \"group member removal\".  This has\nthe downside of requiring `O(remaining members)` direct messages to be\npublished, but is otherwise good for its simplicity.  We assume that member\nexclusion is not as frequently occurring as member addition, so the downside is\na reasonable cost to pay.\n\nSince SSB is an eventually consistent network where peers may have different\nviews on the current state of data structures, the naive solution can give rise\nto multiple epochs.  This is undesired, because the goal of private groups is to\ncreate a single context, not multiple contexts, where the set of peers can\nparticipate in.  This document describes a few rules to resolve cases of\ndiverging epochs, with the goal of allowing remaining group members to\ndeterministically arrive at a single epoch, given eventual consistency of\npropagated SSB messages.\n\n\n## 2. Terminology\n\nThe key words \"MUST\", \"MUST NOT\", \"REQUIRED\", \"SHALL\", \"SHALL NOT\", \"SHOULD\",\n\"SHOULD NOT\", \"RECOMMENDED\", \"MAY\", and \"OPTIONAL\" in this document are to be\ninterpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).\n\n\n## 3. Definitions and notations\n\nThe following words and phrases are commonly used throughout this document.\nThey are listed here with their intended meaning for the convenience of the\nreader.\n\nPrivate group and membership:\n\n* A set of SSB peers that possess the same [envelope-spec] symmetric encryption\nkey (called \"group key\") is called a \"private group\".  Each peer in a group is\ncalled a \"group member\" or \"member\".  The \"declared members\" of a group is\nthe set of SSB peers who received the group key via `group/add-member` messages.\nWe also denote the declared members of a group `G` as the mathematical set\n`members(G)`.\n\nEpoch:\n\n* When a new group key is created and shared to a subset of members excluding\nsome other members, that group key is called an \"epoch key\", and the private\ngroup is called an \"epoch\".  The act of creating an epoch is called \"group member\nexclusion\" or \"exclusion\".  The group members who receive the \"epoch key\" are\ncalled \"remaining members\".  We can also say that the original private group is\nthe \"epoch zero\", hence we shall use the term \"epoch\" in this document whenever\nwe mean either the original private group or subsequent epochs.\n\nPrecedence:\n\n* We say that `G` \"directly preceded\" epoch `H` if `H` was created by excluding\nsome member from `G`, and `H` \"directly succeeded\" `G`.  There may be a new\nepoch `I` excluding a member from `H`, in which case the `I` directly succeeded\n`H` but `I` \"succeeded\" `G`.  Similarly, `G` \"preceded\" `I`.  A sequence of\nepochs up until epoch zero is called a \"precedence chain\".\n\nPreference:\n\n* Epoch `H` is said to be \"preferred over\" epoch `G` if `H` is where new\ndiscussions should take place among the remaining members.\n\nForked epochs:\n\n* Whenever there are two epochs such that one of them is not preceded by the\nother, and both of them are not succeeded by any epoch, we call this situation\n\"forked epochs\".\n\nCommon predecessors:\n\n* A \"common predecessor\" of two epochs `G` and `H` is any epoch `X` that\nprecedes (or is equal to) `G` and `H`.  The \"nearest common predecessor\" `X` of\nepochs `G` and `H` is the only common predecessor of `G` and `H` such that no\nother common predecessor `Y` (of `G` and `H`) succeeds `X`.  We also denote it\nas `X = nearest(G, H)`.\n\nFork witnesses\n\n* In a situation of forked epochs `G` and `H`, assume that `X` is the nearest\ncommon predecessor.  We say that the \"fork witnesses\" of epochs `G` and `H` is\nthe interestion of the members of `G`, `H`, and `X`:\n\n```\nforkWitness(G,H) = members(G) ∩ members(H) ∩ members(nearest(G,H))\n                 = members(G) ∩ members(H) ∩ members(X)\n```\n\nSome mathematical set relations will be useful throughout this specification.\nFor two sets `A` and `B`, we use the notation:\n\n* `A = B` : `A` is equivalent to `B`\n* `A ⊂ B` : `A` is a [proper subset] of `B` (\"A is a [susbet] of B, AND A is NOT\nequivalent to B\")\n* `A ∩ B` : the [intersection] of `A` and `B` (the set of elements in both `A`\nAND `B`)\n* `A ∪ B` : the [union] of `A` and `B` (the set of elements in `A` AND/OR `B`)\n* `A \\ B` : the [set difference] of `A` and `B` (the set of elements in `A` but\nNOT in `B`)\n* `A △ B` : the [symmetric difference] (the set of elements in the union, but\nNOT in the intersection)\n* `∅` : the \"empty set\" (the set which has no elements)\n\n\n## 4. Functional Specification\n\nExclusion of a group member is a mechanism in which a new epoch is created, such\nthat the excluded member is allowed to continue publishing messages for the\nformer epoch.  However, the excluded member's messages have no guarantee of\nbeing replicated by remaining members, because those remaining members have\ntransitioned to the new epoch and ceased replication of the former epoch.  This\nin effect only excludes the excluded member from participating in *future* group\ndiscussions.\n\nCreation of epochs is not restricted to any specific member and, in the presence\nof network partitions, this allows multiple new epochs created by different\ngroup members.  While epoch zero guaranteed a singleton context, when there are\nmultiple epochs there are multiple contexts where discussions can be held, which\nis an undesirable property, i.e. forked epochs.  In this section we provide some\nrules that remaining members can follow to \"resolve\" forked epochs and\narrive at a common epoch as the new singleton context.\n\n\n### 4.1. Excluding a member\n\nSuppose there is an epoch `G` that  is the \"most preferred epoch\" among all the\nepochs that `a` is a member of, which succeed a certain epoch zero. To exclude\nmember `c` from `G`, some member `a` (who MUST NOT be `c`) publishes to epoch `G`\nthat `c` will be excluded, creates a new epoch `H`, and adds all declared group\nmembers of `G` minus `c` as the members of `H`. See figure 1 as an example, and\nthe following subsections for details.\n\n```mermaid\n---\ntitle: Figure 1\n---\ngraph TB;\n  zero[G: a,b,c,d]\n  zero--\"a excludes c\"--\u003eone[H: a,b,d]\n```\n\nLet `Ga` be `a`'s subfeed dedicated for publishing messages for epoch `G`, and\nsimilarly `Gc` be `c`'s subfeed.  To perform member exclusion, the following\nsteps SHOULD be taken in order:\n\n* 4.1.1. `a` MUST create a new symmetric group key `H` (also known as the epoch\nkey) which MUST have at least 32 bytes of cryptographically secure random data\n* 4.1.2. `a` MUST create a new group feed `Ha` (also known as the \"epoch feed\")\nusing the epoch key as the `feedpurpose`, as described in\n[ssb-meta-feeds-group-spec] Section 3.2.2.\n* 4.1.3. `a` MUST publish a `group/init` message on `Ha`, as described in the\n[private-group-spec]\n* 4.1.4. `a` SHOULD publish an encrypted `group/exclude-member` message on `Ga`\nwith the following fields in the message `content`:\n  * 4.1.4.A. `type` equals the string `group/exclude-member`\n  * 4.1.4.B. `excludes` is an array of objects with the shape\n  `{id, groupFeedId, sequence}` where `id` is the root metafeed ID of `c`,\n  `groupFeedId` is `Gc`'s ID, and `sequence` is the sequence number of the last\n  message `a` possesses from `Gc`.  In this case `c` is the only excluded\n  member, so we only have one `{id, groupFeedId, sequence}` object but Section\n  4.1. also supports excluding multiple members at once.\n  * 4.1.4.C. `recps` is an array containing a single string: the group ID for\n  `G`, signalling that this message should be box2-encrypted for the group `G`\n* 4.1.5. `a` MUST publish a `group/add-member` message on their additions feed,\nto add remaining group members (this includes `a`, for recovery purposes) to the\nepoch `H`, such that the message schema is the same as the one in\n[ssb-meta-feeds-group-spec] Section 3.1 with the following exceptions:\n  * 4.1.5.A. If a single SSB message cannot, due to message size\n  restrictions, contain all remaining members as recipients, then member `a`\n  MUST publish on their additions feed a sequence of `group/add-members`\n  messages according to [ssb-meta-feeds-group-spec] Section 3.1, such that the\n  union of all recipients in that sequence equals all remaining members\n\n\n### 4.2. Preferring the next epoch\n\nWhen a member `b` of an epoch `G` replicates and decrypts a `group/add-member`\nmessage that adds them to the new epoch `H` (in other words, `b` \"detected the\nexistence\" of `H`), then `b` MUST select epoch `H` as \"preferred\" over `G`.\n\nSaid differently, if `H` directly succeeds `G`, then `H` MUST be preferred over\n`G`.\n\nThe implications of determining the \"preferred epochs\" will be addressed in\nsection 4.8.\n\n\n### 4.3. Tie-breaking rule\n\nIn some cases, there may be multiple forked epochs that \"tie\" as the preferred\nepoch.  We define a tie-breaking rule that allows all remaining members to\ndeterministically choose one of those tied epochs as the preferred epoch.  This\nrule will be used in sections 4.4. and 4.6.\n\nThe tie-breaking rule is: among the forked epochs, sort their epoch keys by\nlexicographic order (in hexadecimal encoding) and the epoch with the smallest\nepoch key is considered the winner.\n\n\n### 4.4. Resolving forked epochs with same membership\n\nSuppose there are two forked epochs `L` and `R`, and `L`'s epoch key is the\nwinner according to the tie-breaking rule (section 4.3.).\n\nIf `members(L) = members(R)`, then all peers in `members(L)` who detect the\nexistence of both `L` and `R` MUST select `L` as the preferred epoch (determined\nby the tie-breaking rule) over `R`. See figure 2.\n\n```mermaid\n---\ntitle: Figure 2\n---\ngraph TB;\n  zero[X: a,b,c,d]\n  zero--\"a excludes d\"--\u003eL[L: a,b,c]\n  zero--\"b excludes d\"--\u003eR[R: a,b,c]\n  R-. a,b,c prefer L .-\u003eL\n```\n\nHere we addressed two forked epochs.  In the generalized case where two or more\nforked epochs have the same membership, then the tie-breaking rule (section\n4.3.) is used to select the preferred epoch.  The tie-breaking rule supports\nmultiple inputs.\n\n### 4.5. Resolving forked epochs with subset membership\n\nSuppose there are two forked epochs `L` and `R`.  If `members(L) ⊂ members(R)`,\nthen all peers in `forkWitness(L,R)` who detect the existence of both `L` and\n`R` MUST select `L` as the preferred epoch over `R`.  See figure 3.\n\n```mermaid\n---\ntitle: Figure 3\n---\ngraph TB;\n  zero[X: a,b,c,d]\n  zero--\"a excludes c,d\"--\u003eL[L: a,b]\n  zero--\"b excludes d\"--\u003eR[R: a,b,c]\n  R-. a,b prefer L .-\u003eL\n```\n\n\n### 4.6. Resolving forked epochs with overlapping membership\n\nSuppose there are two forked epochs `L` and `R`, and `L`'s epoch key is the\nwinner according to the tie-breaking rule (section 4.3.).\n\nIf `L` and `R` are neither equivalent, nor subsets, but there is overlapping\nmembership i.e. the intersection is not the empty set\n\n```\nmembers(L) ∩ members(R) ≠ ∅\n```\n\nand the symmetric difference is not the empty set\n\n```\nmembers(L) △ members(R) ≠ ∅\n```\n\nthen any peer in `forkWitness(L,R)` SHOULD create a new epoch directly\nsucceeding `L`, excluding all peers that were excluded in `R`. See figure 4.\n\n```mermaid\n---\ntitle: Figure 4\n---\ngraph TB;\n  zero[X: a,b,c,d]\n  zero--\"a excludes c\"--\u003eL[L: a,b,d]\n  zero--\"b excludes d\"--\u003eR[R: a,b,c]\n  L--\"a excludes d\"--\u003eL2[L2: a,b]\n  R-.-\u003eL2\n```\n\nIt is RECOMMENDED that a peer waits a random amount of time before performing\nthe exclusion, to give opportunity for some other peer to perform the exclusion\nfirst.  If a peer in `witnessFork(L,R)` detects a new epoch `L2`\ndirectly succeeding `L`, then this peer MUST NOT perform the exclusion, but\nSHOULD select `L2` as preferred over `L`.\n\nHowever, it is possible that two or more peers perform exclusions, which leads\nto forked epochs with the same membership, where the rules from section 4.4.\nwould apply in order to resolve that situation.\n\nYou can calculate who should be in the final preferred epoch as:\n```\nL2 = the witnesses to the fork\n   = members(L) ∩ members(R) ∩ members(X)\n   = {a,b}\n```\n\n### 4.7. Forked epochs with disjoint membership\n\nSuppose there are two forked epochs `L` and `R`.  If there are no witnesses of\nthe fork, i.e. `forkWitness(L,R) = ∅`, then nothing needs to be performed in\nthis situation, because the forked epochs represent two disjoint contexts.  From\nthe perspective of peers in `members(L)`, epoch `R` does not exist, and from the\nperspective of peers in `members(R)`, epoch `L` does not exist.  See figure 5.\n\n```mermaid\n---\ntitle: Figure 5\n---\ngraph TB;\n  zero[X: a,b,c,d]\n  zero--\"a excludes c,d\"--\u003eL[L: a,b]\n  zero--\"c excludes a,b\"--\u003eR[R: c,d]\n```\n\nHowever, if a member is added to `L` or `R` such that the `forkWitness(L,R)`\nwould be non-empty, then the rules in sections 4.4. or 4.5. or 4.6. would apply.\nSee figure 6.\n\n```mermaid\n---\ntitle: Figure 6\n---\ngraph TB;\n  zero[X: a,b,c,d]\n  zero--\"a excludes c,d\"--\u003eL[L: a,b]\n  zero--\"c excludes a,b\"--\u003eR[R: c,d]\n  R--\"d adds a,b\"--\u003eR2[R: a,b,c,d]\n  R2-. a,b prefer L .-\u003eL\n```\n\n\n### 4.8. Most preferred epoch\n\nIn sections 4.2.–4.7. we addressed only two forked epochs at a time.  If there\nare more than two simultaneously existing forked epochs, then you can still use\nthose rules to resolve the situation in a pairwise fashion.  Each pairwise\napplication of the rules should reduce the number of forked epochs, and this is\nrepeated until there is only one epoch left.  It shouldn't matter which pairs\nof forked epochs are chosen when applying the rules, because the conclusion\nshould be the same regardless of the order in which the rules are applied.\n\nGiven the rules in sections 4.2.–4.7. defining which epoch is preferred, a\ndirected acyclic graph of preference arises, allowing us to determine the\n\"most preferred epoch\".  Each group member can determine their own most\npreferred epoch, but they are not necessarily the same.  For instance, in the\ncase of forked epochs with disjoint memberships (4.7.), the members of the two\ndisjoint epochs select different most preferred epochs.\n\nWe call \"other epochs\" all the epochs that succeed epoch zero, minus the most\npreferred epoch.\n\n\n### 4.8.1. Publishing messages\n\nOnce a peer has discovered their most preferred epoch `H`, they SHOULD cease\npublishing SSB messages on other epochs, and SHOULD publish new messages only on\nepoch `H`, until the moment when their most preferred epoch changes.\n\n\n### 4.8.2. Replicating other epochs\n\nAssume that replicating an SSB feed involves two operations: \"fetching\" new\nupdates (new messages published) from that feed, and \"serving\" any messages in\nthat feed to an interested peer.\n\nAs soon as a peer `a` has discovered their most preferred epoch `H`:\n\n* 4.8.2.A. (Fetch next epoch) `a` SHOULD fetch messages from group feeds of `H`\nfor all members in `H`. See figure 7 as an example.\n* 4.8.2.B. (Cancel fetching excluded members) `a` SHOULD cease fetching messages\nfrom group feeds of other epochs `X` belonging to any member excluded from `X`,\nbut should continue to fetch messages from group feeds of remaining members of\n`X`. See figure 7.\n* 4.8.2.C. (Out-of-order fetching for tangle integrity) As an exception to\n4.8.2.B., whenever `a` is validating a tangle (see section 4.10) and detects a\nmissing message `Q`, they MAY fetch `Q` in out-of-order fashion (OOO, that\nis, specifically requesting that message from remote peers, disregarding\nthe feed in which it was published), even if `Q` is from an excluded member. See\nfigure 8 as an example.\n* 4.8.2.D. (Serving all feeds) `a` SHOULD continue to serve messages from group\nfeeds of any epoch `X` belonging to **any** member of `X`.\n\n```mermaid\n---\ntitle: Figure 7\n---\ngraph LR;\n  afetch[a fetches]\n  subgraph X\n    Xa[a's group feed in X]\n    Xb[b's group feed in X]\n    Xc[c's group feed in X]\n  end\n  X--\"b excludes c\"--\u003eH\n  subgraph H\n    Ha[a's group feed in H]\n    Hb[b's group feed in H]\n  end\n  afetch -.-\u003e Xa \u0026 Xb \u0026 Ha \u0026 Hb\n\n  style afetch fill:#0000,stroke:#0000;\n```\n\n```mermaid\n---\ntitle: Figure 8\n---\nflowchart RL\n\nA[Msg A]\nB[Msg B]\nC[Msg C]\nsubgraph Fetch[Fetch OOO]\n  Q[Msg Q]\nend\nD[Msg D]\nE[Msg E]\nD--\u003eQ--\u003eB\nD--\u003eC--\u003eB--\u003eA\nE--\u003eC\n\nclassDef default stroke:#ccc,fill:#eee,color:#333;\nclassDef cluster fill:#fff0,stroke:#000,color:#000,stroke-dasharray: 5 5;\nclassDef dead stroke:#ccc,fill:#888,color:#000;\nclass Q dead;\n```\n\n## 4.9. Adding new members\n\nWhen adding a new member to the group, you MUST add them to all the epochs in\nthe history of the group, starting with epoch 0, and ending with the latest\npreferred epoch.\n\nWe add them to all epochs (even non-preferred epochs) because content may have\nbeen published in these forks, and we want everyone to be reading the same\ncontext.\n\nIn figure 9, `b` adds `e` to the group, meaning they add them to all the epochs\nthey can see.\n\n```mermaid\n---\ntitle: Figure 9\n---\ngraph TB;\n  A0[X: a,b,c,d]\n  A0--\"b excludes c\"--\u003e B0[Y: a,b]\n\n  A[X: a,b,c,d,\u003cb\u003ee\u003c/b\u003e]\n  A-.\"b adds e\".-\u003eA\n  A--\"b excludes c\"--\u003eB[Y: a,b,d,\u003cb\u003ee\u003c/b\u003e]\n  B-.\"b adds e\".-\u003eB\n```\n\nLater, if `b` discovers another epoch `Z` (which hasn't had `e` added):\n\n```mermaid\n---\ntitle: Figure 10\n---\ngraph TB;\n  A[X: a,b,c,d,\u003cb\u003ee\u003c/b\u003e]\n  A--\"b excludes c\"--\u003eB[Y: a,b,d,\u003cb\u003ee\u003c/b\u003e]\n  A--\"a excludes c,d\"--\u003eC[Z: a,b]\n```\n\nAny member moving to create a new epoch, MUST ensure epochs have the correct\nmembership before proceeding.\n\nThe \"correct membership\" for a particular epoch is calculated as the union of\nall declared members in all epochs, minus the union of all excluded members in\nany epoch *up till that epoch*.\n\nIn epoch `Z` in figure 10 (above), the correct membership is:\n\n```\n  (all member additions) \\ (all member exclusions leading up to Z)\n= {a,b,c,d,e} \\ {c,d}\n= {a,b,e}\n```\n\nFrom this we can see `e` must be added to epoch `Z` to bring it up to \"correct\nmembership\".\n\n\n## 4.10. Tangles\n\nA \"tangle\" in scuttlebutt is a way to define a directed acyclic graph (DAG) of\nmessages. You can read more about them in the [Tangle SIP].\n\n\n### 4.10.1. Members tangle\n\nThe purpose of the members tangle is to group and partially order all messages\nthat alter the group membership during a single epoch.  A peer can calculate\nthe Set of members in an epoch by [reducing] the messages in this tangle.  Each\nepoch will have its own members tangle.  The human-friendly name in the tangle\ndata is the string `members`.\n\nWe construct the members tangle for some epoch `E` of a group `G` as:\n\n1. Root:\n    - If you publish the `group/init` message for `E` (see 4.1.3.) or the\n    `group/init` message for epoch zero, then it MUST have the tangle data\n    `members: { root: null, previous: null }`. This is the root of the tangle.\n    - If someone else published the `group/init` message for `E`, that message\n    is considered the root of the tangle only if it has the tangle data\n    `members: { root: null, previous: null }`. If it does not, the tangle is\n    invalid and you SHOULD disregard the rules in this section.\n2. Non-roots:\n    - If you publish a `group/add-member` or `group/exclude-member` message for\n    `E`, then it MUST have the tangle data\n    `members: { root: ROOTID, previous: PREVIOUS }`, where `ROOTID` is the ID of\n    the root message, and `PREVIOUS` is an array containing the IDs of the\n    \"tips\" of the tangle, see below.\n3. Determining tips of the tangle at the moment a non-root message is published:\n    - a) Initialize the \"tip set\" with one item: the root message\n    - b) Initialize the \"tangle nodes\" with one item: the root message\n    - c) For each tip in the current \"tip set\":\n        - Find messages which: (1) are valid `group/add-member` on additions\n        feeds or valid `group/exclude-member` published on an epoch feed for\n        `E`, (2) have `ROOTID` in the `root` field of their tangle data, (3)\n        contain this tip in the `previous` field of their tangle data, (4) all\n        messages in the `previous` field are in the \"tangle nodes\"\n        - If there are any such messages, then:\n          - Remove the tip from the \"tip set\"\n          - Add each such message to the \"tip set\"\n          - Add each such message to the \"tangle nodes\"\n    - d) Repeat (c) till you cannot update the tip set further\n    - e) The remaining messages in the \"tip set\" are the tips of the tangle\n\n\nThe diagram below shows an example of the members tangle for a group with only\nepoch zero.\n\n```mermaid\n---\ntitle: Figure 11\n---\nflowchart RL\n\nsubgraph Additions\n  0_add_0[add: Alice]\n  0_add_1[add: Bob, Carol]\nend\n\nsubgraph epoch_0[Epoch 0]\n  0_init[init]\nend\n\n%% members tangle - epoch 0\n0_add_1 --\u003e 0_add_0 --\u003e 0_init\nlinkStyle 0,1 stroke:#118AB2,stroke-width:2;\n\nclassDef default stroke:#ccc,fill:#eee,color:#333;\nclassDef cluster fill:#fff,stroke:#000,color:#333;\n```\n\n\n### 4.10.2. Epoch tangle\n\nThe purpose of the epoch tangle is to group and partially order all messages\nthat create epochs in a group.  A peer can reduce these messages to discover the\npreferred epoch and detect forked epochs.  Its human-friendly name in the tangle\ndata is the string `epoch`.\n\nWe construct the epoch tangle for some group `G` based on its root message `R`\nas:\n\n1. Root:\n    - If you publish the root message `R`, then it MUST have the tangle data\n    `epoch: { root: null, previous: null }`. This is the root of the tangle.\n    - If someone else published the root message `R`, that message is considered\n    the root of the tangle only if it has the tangle data\n    `epoch: { root: null, previous: null }`. If it does not, the tangle is\n    invalid and you SHOULD disregard the rules in this section.\n2. Non-roots:\n    - If you publish a `group/init` message and the first key in `content.recps`\n    is the ID of `G`, then it MUST have the tangle data\n    `epoch: { root: ROOTID, previous: PREVIOUS }`, where `ROOTID` is the ID of\n    the root message, and `PREVIOUS` is an array containing the IDs of the\n    \"tips\" of the tangle, see below.\n3. Determining tips of the tangle at the moment a non-root message is published:\n    - a) Initialize the \"tip set\" with one item: the root message\n    - b) Initialize the \"tangle nodes\" with one item: the root message\n    - c) For each tip in the current \"tip set\":\n        - Find messages which: (1) are valid `group/init` published on an epoch\n        feed with the ID of `G` as the first key in `content.recps`, (2) have\n        `ROOTID` in the `root` field of their tangle data, (3) contain this tip\n        in the `previous` field of their tangle data, (4) all messages in the\n        `previous` field are in the \"tangle nodes\"\n        - If there are any such messages, then:\n          - Remove the tip from the \"tip set\"\n          - Add each such message to the \"tip set\"\n          - Add each such message to the \"tangle nodes\"\n    - d) Repeat (c) till you cannot update the tip set further\n    - e) The remaining messages in the \"tip set\" are the tips of the tangle\n\nThe diagram below shows an example of the epoch tangle for a group with two\nepochs.\n\n```mermaid\n---\ntitle: Figure 12\n---\nflowchart RL\n\nsubgraph epoch_0[Epoch 0]\n  0_init[init]\nend\n\nsubgraph epoch_1[Epoch 1]\n  1_init[init]\nend\n\n%% epoch tangle\n1_init --\u003e 0_init\nlinkStyle 0 stroke:#EF476F,stroke-width:2;\n\n\nclassDef default stroke:#ccc,fill:#eee,color:#333;\nclassDef cluster fill:#fff,stroke:#000,color:#333;\n```\n\n\n### 4.10.3. Group tangle\n\nThe purpose of the group tangle is to group and partially order all messages\nin a given group, across all epochs.  A peer can\n[topologically sort](https://en.wikipedia.org/wiki/Topological_sorting) these\nmessages to create an audit log of actions performed in the group, such as\ndiscovering group discussions that give context to the exclusion of a given\nmember (and thus the creation of a new epoch).  Its human-friendly name in the\ntangle data is the string `group`.\n\nWe construct the group tangle for some group `G` based on its root message `R`\nas:\n\n1. Root:\n    - If you publish the root message `R`, then it MUST have the tangle data\n    `group: { root: null, previous: null }`. This is the root of the tangle.\n    - If someone else published the root message `R`, that message is considered\n    the root of the tangle only if it has the tangle data\n    `group: { root: null, previous: null }`. If it does not, the tangle is\n    invalid and you SHOULD disregard the rules in this section.\n2. Non-roots:\n    - If you publish any message where the first key in `content.recps` is the\n    ID of `G`, then it MUST have the tangle data\n    `group: { root: ROOTID, previous: PREVIOUS }`, where `ROOTID` is the ID of\n    the root message, and `PREVIOUS` is an array containing the IDs of the\n    \"tips\" of the tangle, see below.\n3. Determining tips of the tangle at the moment a non-root message is published:\n    - a) Initialize the \"tip set\" with one item: the root message\n    - b) Initialize the \"tangle nodes\" with one item: the root message\n    - c) For each tip in the current \"tip set\":\n        - Find messages which: (1) are valid messages published with the ID of\n        `G` as the first key in `content.recps`, (2) have `ROOTID` in the `root`\n        field of their tangle data, (3) contain this tip in the `previous` field\n        of their tangle data, (4) all messages in the `previous` field are in\n        the \"tangle nodes\"\n        - If there are any such messages, then:\n          - Remove the tip from the \"tip set\"\n          - Add each such message to the \"tip set\"\n          - Add each such message to the \"tangle nodes\"\n    - d) Repeat (c) till you cannot update the tip set further\n    - e) The remaining messages in the \"tip set\" are the tips of the tangle\n\n\n### 4.10.4. Example: using all the tangles together\n\nIn this section we provide an example that illustrates the presence of all three\ngroup-related tangles.  Suppose peer Z creates a group and performs the\nfollowing actions, in this order:\n\n1. Adds peer A to the group at epoch 0\n2. Adds peers B and C to the group at epoch 0\n3. Excludes peer C, thus creating epoch 1\n4. Re-adds peers A and B to the group, now at epoch 1\n\nThen the following diagram illustrates Z's feeds (additions feed, epoch 0 feed,\nepoch 1 feed) and its messages:\n\n```mermaid\n---\ntitle: Figure 13\n---\nflowchart RL\n\nsubgraph Additions\n  0_add_0[add: A]\n  0_add_1[add: B, C]\n  1_add_0[add: A, B]\nend\n\nsubgraph epoch_0[Epoch 0]\n  0_init[init]\n\n  0_remove_2[exclude: C]\nend\n\nsubgraph epoch_1[Epoch 1]\n  1_init[init]\nend\n\n%% epoch tangle\n1_init --\u003e 0_init\nlinkStyle 0 stroke:#EF476F,stroke-width:2;\n\n%% members tangle - epoch 0\n0_remove_2 --\u003e 0_add_1 --\u003e 0_add_0 --\u003e 0_init\nlinkStyle 1,2,3 stroke:#118AB2,stroke-width:2;\n\n%% members tangle - epoch 1\n1_add_0 --\u003e 1_init\nlinkStyle 4 stroke:#06D6A0,stroke-width:2;\n\n%% group tangle\n1_add_0 -.- 1_init -.- 0_remove_2 -.- 0_add_1 -.- 0_add_0 -.- 0_init\nlinkStyle 5,6,7,8,9 stroke:#FFD166,stroke-width:3;\n\nclassDef default stroke:#ccc,fill:#eee,color:#333;\nclassDef cluster fill:#fff,stroke:#000,color:#333;\n```\n\n```mermaid\nflowchart LR\n\nsubgraph key\n  direction RL\n  members_0[members tangle 0] --\u003eA[ ]\n  members_1[members tangle 1] --\u003eB[ ]\n  epoch[epoch tangle]         --\u003eC[ ]\n  group[group tangle]        -.-\u003eD[ ]\nend\nlinkStyle 0 stroke:#118AB2,stroke-width:2;\nlinkStyle 1 stroke:#06D6A0,stroke-width:2;\nlinkStyle 2 stroke:#EF476F,stroke-width:2;\nlinkStyle 3 stroke:#FFD166,stroke-width:3;\n\nclassDef default fill:#fff,stroke:none,color:#333;\nclassDef cluster fill:#fff,stroke:#000,color:#333;\n```\n\n_NOTE: this diagram shows all nice linear tangles for visual simplicity,\nremember there may be forks and merges because of concurrent publishing._\n\n\nCrucially, the `group/init` messages are involved in 3 tangles, e.g.\n\n```javascript\n{\n  type: `group/init`,\n  // ...\n  tangles: {\n    group: { root: A, previous: [W, X] },\n    epoch: { root: A, previous: [B] },\n    members: { root: null, previous: null }\n  }\n}\n```\n\nThis message says\n\n1. I am part of a group which started with message `A`, and the last messages I\nsaw in the group were `W, X`\n2. The root epoch was `A` and the epoch(s) before this one was `B`\n3. I am the root of a new members tangle for this epoch\n\n\n\n## 5. Security and Privacy Considerations\n\n\n### 5.1. Excluded members can read old messages\n\nAn excluded member can still read and preserve all of the messages up until the\npoint they were excluded, and this could be used maliciously to extract as much\ninformation from other group members as possible, for instance by analysing the\ntimestamps of messages published to the group and deriving suitable timezones\nfor each peer.\n\n\n### 5.2. Excluded member has a time window to publish more messages\n\nDue to eventual consistency in a distributed system, the exclusion of a member\nis not immediate across the whole group, and depending on the network topology\nand uptimes of peers, it is possible that an excluded member can publish more\nmessages that will be replicated to some members, prior to them moving on to the\nnext epoch.\n\nSuppose member `a` excludes member `b` at epoch `G`, and `b` sees this exclusion\nmessage but chooses to publish a message (or many) still in epoch `G`.  A third\nmember `c` may come online and replicate with `b` before it has a chance to\nreplicate with `a`, thus getting messages that `b` published after they had\nknown they were excluded.\n\nThe more frequently-connecting the network topology is, the smaller this time\nwindow will be, and the less severe this issue will be.\n\n\n### 5.3. Collusion\n\nMembers who sympathize and coordinate with recently excluded member(s) can\neffectively nullify the exclusion in various ways.  One way is by re-adding the\nexcluded member, which would be detected by remaining members too.  It is also\npossible to stealthily re-add excluded members, by sharing the \"group key\"\nwithout publishing an accompanying `group/add-member` message.\n\nThis specification does not address collusion, but it is recommended to exclude\nmultiple members at once if it is possible that they may collude.\n\n\n### 5.4. Weak tie-breaking rule\n\nThe tie-breaking rule is simple and easy to implement, but it can be gamed such\nthat a malicious group member can force their forked epoch to be preferred over\nothers.  This can be achieved by brute-forcing the epoch ID until it is\nlexicographically \"small\" (such as starting with the character `0`).  It is\ncurrently unknown how this weakness can be exploited to degrade security or\nprivacy, but at the very least it reduces fairness and randomness in choosing\npreferred epochs.\n\nA better tie-breaking rule would have the property that gives each\nepoch-creating peer no information on how \"strong\" their epoch key in winning\na tie-break.  However, we also require tie-breaking rules to be deterministic,\nand to create a total order between a set of forked epochs, because we need\ngroup members to eventually converge on a single most preferred epoch.  We have\nnot found a tie-breaking rule with all three properties, so this is future work.\n\n\n## 6. References\n\n### 6.1. Normative References\n\n- [ssb-meta-feeds-spec] version 1.0\n- [ssb-meta-feeds-group-spec] version 1.0\n- [envelope-spec] version 1.0.0\n- [private-group-spec] version 2.0.0\n- [ssb-meta-feeds-dm-spec] version 0.1\n\n### 6.2. Informative References\n\n- [private-groups-original-notes]\n- [scuttlebutt-protocol-guide]\n- [ssb-private-group-keys]\n- [ssb-tribes2]\n- [ssb-tribes2-demo]\n- [ssb-meta-feeds]\n- [ssb-ooo]\n- [perfect-forward-secrecy]\n- [post-compromise-security]\n- [reducing]\n\n## Acknowledgements\n\n\u003cimg src=\"./nlnet-logo.svg\" width=\"100\"\u003e \u003cimg src=\"./ngi-assure-logo.png\" width=\"100\"\u003e\n\nThis project has received funding from the European Union’s Horizon 2020 research and innovation programme within the framework of the NGI-Assure Project funded under grant agreement No 957073.\n\n\n\u003c!-- References --\u003e\n\n[private-group-spec]: https://github.com/ssbc/private-group-spec\n[ssb-meta-feeds-spec]: https://github.com/ssbc/ssb-meta-feeds-spec\n[ssb-meta-feeds-group-spec]: https://github.com/ssbc/ssb-meta-feeds-group-spec\n[envelope-spec]: https://github.com/ssbc/envelope-spec/\n[ssb-meta-feeds-dm-spec]: https://github.com/ssbc/ssb-meta-feeds-dm-spec\n[scuttlebutt-protocol-guide]: https://ssbc.github.io/scuttlebutt-protocol-guide/\n[ssb-private-group-keys]: https://github.com/ssbc/ssb-private-group-keys\n[ssb-tribes2]: https://github.com/ssbc/ssb-tribes2\n[ssb-tribes2-demo]: https://github.com/ssbc/ssb-tribes2-demo\n[ssb-meta-feeds]: https://github.com/ssbc/ssb-meta-feeds\n[private-groups-original-notes]: https://github.com/ssbc/envelope-spec/blob/master/original_notes.md\n[perfect-forward-secrecy]: https://en.wikipedia.org/wiki/Forward_secrecy\n[post-compromise-security]: https://ieeexplore.ieee.org/document/7536374\n[ssb-uri-spec]: https://github.com/ssbc/ssb-uri-spec\n[Tangle SIP]: https://github.com/ssbc/sips/blob/master/009.md\n[ssb-ooo]: https://github.com/ssbc/ssb-ooo/\n\n[subset]: https://en.wikipedia.org/wiki/Subset\n[proper subset]: https://en.wikipedia.org/wiki/Subset\n[union]: https://en.wikipedia.org/wiki/Union_(set_theory)\n[intersection]: https://en.wikipedia.org/wiki/Intersection_(set_theory)\n[set difference]: https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement\n[symmetric difference]: https://en.wikipedia.org/wiki/Symmetric_difference\n[reducing]: https://en.wikipedia.org/wiki/Reduction_operator\n[topological-sorting]: https://en.wikipedia.org/wiki/Topological_sorting]\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Fssb-group-exclusion-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssbc%2Fssb-group-exclusion-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Fssb-group-exclusion-spec/lists"}