{"id":28396297,"url":"https://github.com/ssbc/ssb-meta-feeds-group-spec","last_synced_at":"2026-02-09T10:04:09.440Z","repository":{"id":148991811,"uuid":"516278566","full_name":"ssbc/ssb-meta-feeds-group-spec","owner":"ssbc","description":null,"archived":false,"fork":false,"pushed_at":"2023-10-16T11:04:52.000Z","size":81,"stargazers_count":5,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-07-08T08:21:29.156Z","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":"2022-07-21T07:57:58.000Z","updated_at":"2022-12-15T00:28:28.000Z","dependencies_parsed_at":null,"dependency_job_id":"f5fa523b-52ff-4a77-99d5-12625936e0ed","html_url":"https://github.com/ssbc/ssb-meta-feeds-group-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ssbc/ssb-meta-feeds-group-spec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-meta-feeds-group-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-meta-feeds-group-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-meta-feeds-group-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-meta-feeds-group-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssbc","download_url":"https://codeload.github.com/ssbc/ssb-meta-feeds-group-spec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Fssb-meta-feeds-group-spec/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265568069,"owners_count":23789585,"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:51.292Z","updated_at":"2026-02-09T10:04:09.392Z","avatar_url":"https://github.com/ssbc.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: 2022 Mix Irving\n\nSPDX-License-Identifier: CC-BY-4.0\n--\u003e\n\n# ssb-meta-feeds-group-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## Abstract\n\nThis document specifies how SSB private group content is organized in a metafeed\ntree, what data must be encrypted and to whom, and how peers replicate\ngroup-related portions of the tree.\n\n## 1. Introduction\n\nSSB Private Groups is a symmetric encryption format that allows a large number\nof peers to share a symmetric key and use it to encrypt messages to each other.\nWith the wide adoption of private groups, there would be a large volume of\ncontent that is not readable by the general public, which can cause storage\nproblems as that content is replicated in the network.\n\nIn order to support partial replication, it is desireable to put different group\ncontent in different subfeeds. However, we need to have a clear way to discover\nhow you've been invited to a group, without replicating the whole group's\ncontent. We also need to consider how to ensure our group data is replicated\n_enough_ so it's readily available for anyone who needs access to it. For\nexample, if a group contains only three people, and if only two people have\ncopies of the group data, then there's little or no gossip propagation, and the\nthird member can only get updates when it is directly connected one of the other\ntwo members. Thus we need to enable _sympathetic replication_, such that peers\nwho don't strictly need the group content are incentivized to replicate it\nanyway.\n\nAdditionally, our metafeed structure should not allow other peers to learn\nwhich groups a peer is in, unless they are also members of the group.\n\nUse of metafeeds for groups is described formally in Section 3 and illustrated\nin the examples in Section 4.\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## 3. Functional Specification\n\nImplementations supporting this specification MUST follow the\n[ssb-meta-feeds-spec] version 1.0.\n\nWe define two types of feeds that each peer will have:\n\n1. A `group/additions` feed\n2. A \"group feed\" for each group\n\nWe then define three different flows for how peers can interact with these\nfeeds when:\n\n1. Creating a new group\n2. Adding a peer to a group\n3. Discovering group membership\n\n### 3.1. The group/additions feed\n\nThe purpose of this feed is to hold messages for coordination of joining groups.\nIt represents the record of all peers the user had added to groups.\n\n- 3.1.1 Each peer running this spec MUST have an additions feed\n- 3.1.2 Each peer MUST deterministically place their additions feed as a subfeed\nof a shard feed, such that:\n  - The shard feed is derived from the string \"`group/additions`\" according to\n  the v1 tree structure specified in\n  [ssb-meta-feeds-spec].\n  - The `metafeeds/add/derived` message announcing the additions feed\n    - MUST have `feedpurpose` equal to the string `group/additions`,\n    - MUST be of feed format `classic`\n    - MUST have `metadata` following the [ssb-meta-feeds-dm-spec] Section 1\n    - MUST NOT be encrypted\n- 3.1.3 If a peer A wants to add another peer B in a group, then A MUST replicate\nB's additions feed and B must replicate A's additions feed\n- 3.1.4 All messages published on the additions feed MUST be \"`group/add-member`\"\nmessages encrypted with [envelope-spec] encryption\n  - See details below\n- 3.1.5 There MUST be at most one additions feed per metafeed tree\n\n#### `group/add-member` messages\n\nThis is the only type of message currently expected in the additions feed.\nIt's defined in the [private-group-spec] and MUST have at least the following\nfields in its message `content`:\n\n- `type` property equal to the string \"`group/add-member`\"\n- `version` property equal to the string \"`v2`\"\n- `secret` property equal to the base64-encoded string of the group secret key\n- `root` property equal to the ID of the `group/init` message, as an SSB URI\n- `recps` property containing an array of feed IDs\n  - The first feed ID MUST be the ID of the group feed, as a\n  `ssb:identity/group/` URI\n  - The subsequent feed IDs (at most 15 of them) MUST be the root metafeed ID of\n  the peer(s) being added to the group, all as SSB URIs\n\nThe encryption of this message on the additions feed MUST follow the\n[ssb-meta-feeds-dm-spec].\n\n### 3.2. Group feeds\n\nThe purpose of this feed is to hold the messages the user publishes for a\nspecific group. It represents all the activity this user has in a group. The\nuser has at most one group feed for each group they are a member of.\n\nEach group feed MUST be a direct subfeed of a shard feed, where the shard is\nderived using the base64 encoded *group secret*.\n\n- 3.2.1 Each peer that is a member of a group MAY have a group feed for that\ngroup\n  - It is not compulsory to have a group feed for a group the user is a member\n  of, because the user may not have confirmed their participation in the group\n  after being added to it.\n- 3.2.2 Each peer MUST deterministically place their group feed as a subfeed of\na shard feed, such that:\n  - The shard feed is derived from the base64 encoded string of the group secret\n  according to the v1 tree structure specified in [ssb-meta-feeds-spec].\n  - The `metafeeds/add/derived` message announcing the group feed\n    - MUST have `feedpurpose` equal to the base64 encoded group secret\n    - MUST be encrypted with the group secret, using [envelope-spec] encryption\n    - SHOULD be encrypted as well to your `own_key`, for recovery purposes\n- 3.2.3 Each group member SHOULD replicate the group feeds of all other group\nmembers they know of\n- 3.2.4 All content on the group feed MUST be encrypted with the group secret\nkey, using [envelope-spec] encryption\n\n\u003cdetails\u003e\n  \u003csummary\u003eDetails about the shard feed\u003c/summary\u003e\n  \u003cdiv\u003e\nThe shard feed is derived by the base64 encoded group secret.\n\nWe cannot use the group `id`, as this is publicly known, which would give\nattackers a way to test if people are in the group (breaking membership\nconfidentiality).\n\nWe choose the group `secret` because it is a value known only to those already\nin the group.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n\u003cdetails\u003e\n  \u003csummary\u003eDetails about the group feed\u003c/summary\u003e\n  \u003cdiv\u003e\n\nWe need a `feedpurpose` which is unique to the group, which the group secret is.\n\nWe cannot use the group `id`, because this is derived using the group init\nmessage, which does not exist until our feed exists. We encrypt this announce\nmessage so as not to leak the `secret` and to protect membership\nconfidentiality.\n\nFor sympathetic replication we will therefore need a distinct type of\nannouncement.\n\n  \u003c/div\u003e\n\u003c/details\u003e\n\n### 3.3. Creating a new group\n\nTo create a new group, a peer should perform all of the following steps\nsuccessfully:\n\n- 3.3.1. MUST create a new symmetric group key, also known as the group secret,\nwhich MUST have at least 32 bytes of cryptographically secure random data\n- 3.3.2. MUST create a new group feed using this symmetric group key, as\ndescribed in Section 3.2\n- 3.3.3. MUST publish a `group/init` message on the group feed, as described in\nthe [private-group-spec]\n- 3.3.3. SHOULD create a group/additions feed, as described in Section 3.1,\nunless there is already one for this metafeed\n- 3.3.4. SHOULD publish a `group/add-member` message on the group/additions feed\nto add themselves to the group, as described in Section 3.1\n\n### 3.4. Adding a peer to a group\n\nTo add other peers to a group, a peer MUST publish a `group/add-member` message\non the group/additions feed addressed to the other peer, as described in Section\n3.1. This message must be encrypted following the [ssb-meta-feeds-dm-spec].\n\n### 3.5. Discovering group membership\n\nTo discover the members of a group, a peer MUST perform the following steps:\n\n- 3.5.1. MUST replicate the group/additions feed of other peers, either on the\nbasis of friendship or because they are a known member of a group\n- 3.5.2. MUST decrypt some or all `group/add-member` messages on the\ngroup/additions feed\n- 3.5.3. Each decrypted `group/add-member` message reveals, in the `recps`\nfield, the newly added member(s) and the group identifier, allowing the user to\ninfer that the newly added member(s) are members of the group\n\nUpon a peer discovering that they are a member of a group, they MAY create a\ngroup feed if they do not already have one. This allows them to publish messages\nto the group.\n\n## 4. Examples\n\nThe following diagram illustrates the structure of a metafeed tree for a peer,\n\"Staltz\", who is a member of three groups: \"helsinki\", \"aalborg\", and\n\"wellington\".\n\n```mermaid\ngraph TB\n\nroot(root)\nv1(v1)\n5(5) \u0026 b(b) \u0026 f(f)\nadditions(group/additions):::additionsClass\naalborg(aalborg):::group\nhelsinki(helsinki):::group\nwellington(wellington):::group\n\nroot --\u003e v1 --\u003e 5 --\u003e helsinki\n         v1 --\u003e b --\u003e additions\n         v1 --\u003e f --\u003e aalborg\n                f --\u003e wellington\n\nclassDef default stroke:none;\nclassDef additionsClass fill: #BF2669, stroke:none, color:white;\nclassDef group fill: #702A8C, stroke: none, color:white;\n```\n\n### 4.1. Creating a group\n\nStaltz starts up his application. We assume he has already created his\n`group/additions` feed (following the spec above). In his application he creates\na new \"helsinki\" group, which means he:\n\n1. Creates a new symmetric `groupKey`, also known as \"group secret\"\n2. Creates a content feed under some shard (using the `groupKey` following the\nspec above)\n3. Publishes a box2-encrypted `group/init` message on that new \"helsinki\"\ncontent feed\n4. Publishes a box2-encrypted `group/add-member` message on his\n\"group/additions\" feed\n  \u003cdetails\u003e\n    \u003csummary\u003edetails\u003c/summary\u003e\n    \u003cdiv\u003e\n      This helps new members quickly see he is a member of the group, and also\n      ensures he has a copy of the groupKey persisted in his records (encrypted\n      to him and the group)\n    \u003c/div\u003e\n  \u003c/details\u003e\n\n```mermaid\ngraph TB\n\nroot(root)\nv1(v1)\n4(4)\nd(d)\nadditions(group/additions):::additionsClass\nhelsinki(helsinki):::group\n\nsubgraph Staltz\n  root --\u003e v1 --\u003e 4 --\u003e helsinki\n           v1 --\u003e d --\u003e additions\nend\n\nclassDef default stroke:none;\nclassDef additionsClass fill: #BF2669, stroke:none, color:white;\nclassDef group fill: #702A8C, stroke: none, color:white;\n```\n\n_Diagram showing Staltz feed state from his perspective_\n\n### 4.2. Group creator invites someone\n\nStaltz wants to invite his friend Arj to the group he set up, so he publishes a\n`group/add-member` message (which contains the group `secret`) on his\n\"group/additions\" feed.\n\nWhen Arj next starts up his application and replicates Staltz's feed tree (they\nare friends), he discovers the new `group/add-member` for him on Staltz's\n\"group/additions\" feed (because peers must replicate their friends'\n\"group/additions\" feeds).\n\n```mermaid\ngraph TB\n\nrootA(root)\nv1A(v1)\n4A(4):::unreplicated\ndA(d)\nadditionsA(group/additions):::additionsClass\nhelsinkiA(helsinki):::unreplicated\n\nrootB(root)\nv1B(v1)\n9B(9)\nadditionsB(group/additions):::additionsClass\n\nsubgraph Staltz\n  rootA --\u003e v1A --\u003e 4A --\u003e helsinkiA\n            v1A --\u003e dA --\u003e additionsA\nend\n\nsubgraph Arj\n  rootB --\u003e v1B --\u003e 9B --\u003e additionsB\nend\n\nclassDef default stroke:none;\nclassDef additionsClass fill: #BF2669, stroke:none, color:white;\nclassDef group fill: #702A8C, stroke: none, color:white;\nclassDef unreplicated opacity: 0.4, stroke: none;\n```\n\n_Diagram showing feed state of Arj and Staltz from Arj's perspective. The greyed\nout feeds show feeds that exist for Staltz but which Arj has yet to want to\nreplicate._\n\nAssuming he accepts this invitation, Arj then does the following:\n\n1. Calculates the shard for the \"helsinki\" group for staltz, and starts\nreplicating that shard feed and the \"helsinki\" feed\n2. Creates a \"helsinki\" feed for himself\n\n```mermaid\ngraph TB\n\nrootA(root)\nv1A(v1)\n4A(4) \u0026 dA(d)\nadditionsA(group/additions):::additionsClass\nhelsinkiA(helsinki):::group\n\nrootB(root)\nv1B(v1)\n9B(9) \u0026 cB(c)\nadditionsB(group/additions):::additionsClass\nhelsinkiB(helsinki):::group\n\nsubgraph Staltz\n  rootA --\u003e v1A --\u003e 4A --\u003e helsinkiA\n            v1A --\u003e dA --\u003e additionsA\nend\n\nsubgraph Arj\n  rootB --\u003e v1B --\u003e 9B --\u003e additionsB\n            v1B --\u003e cB --\u003e helsinkiB\nend\n\nclassDef default stroke:none;\nclassDef additionsClass fill: #BF2669, stroke:none, color:white;\nclassDef group fill: #702A8C, stroke: none, color:white;\n```\n\n_Diagram showing the updated state for Arj after he joins the group. Note the\nshards each feed lands in are different for each person (but deterministic if\nyou know the `groupKey`)._\n\nStaltz can see that Arj has accepted the invitation because he is able to\ndecrypt the feed announcement message for Arj's \"helsinki\" feed on the shard\nfeed, and read that the `feedpurpose` is the `groupKey`. Staltz knows which\nshard feed to watch for the announcement, because Arj's shard feed is\ndeterministically derived with information Staltz is aware of.\n\n### 4.3. Non-group creator invites someone\n\nArj now wants to invite Mix to the \"helsinki\" group. He follows the same pattern\nas in (2), but now as the inviter.\n\nMix knows Arj is a part of the group because he was invited by them. Mix also\nknows Staltz is part of the group because all `group/add-member` messages have\n\nStaltz can see Arj has invited Mix because he's replicating Arj's\n\"group/additions\" feed, so Staltz starts replicating Mix's group feed.\n\n## 5. Security Considerations\n\n### 5.1. Degraded confidentiality at scale\n\nAs noted in the [private-groups-original-notes], SSB Private Groups are based on\nsymmetric encryption and static keys (no key rotation), which means it cannot\nguarantee [perfect-forward-secrecy] neither [post-compromise-security]. This\nmakes SSB Private Groups less likely to guarantee confidentiality at scale, when\na group has dozens or hundreds of members. If any of the members leaks the group\nsecret, all past communication in the group is compromised.\n\nThis also means that any group member can add new members to the group. They may\neven do so without publishing a `group/add-member` message, which effectively\nallows any group member to invite a third party that will remain undetected by\nexisting group members. The only signal group members may get is that this third\nparty is requesting replication for group feeds that they should not know about.\n\nSSB Private Groups with several members should thus be used with caution. Our\nsecurity model assumes that the group is as trustworthy as the _least_\ntrustworthy member in the group.\n\nThe choice of symmetric encryption (as opposed to, e.g. hash-ratchet or\ndouble-ratchet) was made because of efficiency. Symmetric encryption means\nadding new members or publishing new content is `O(1)` fast, and the system is\noverall simple to implement.\n\n### 5.2. Membership analysis attacks\n\nA group feed is discoverable only by the group members, because its announcement\nis encrypted. However, an eavesdropper who can replicate a peer's metafeed tree\nwill notice the presence of these encrypted message on shard feeds. If the\neavesdropper can do that for several peers, then they may perform a metadata\nanalysis attack where they try to correlate the encrypted messages on the shard\nfeeds to the group members.\n\nThis can be mitigated by publishing dummy encrypted messages on the shard feed\nat random intervals, with the downside of increasing the size of the shard feed,\nand thus making partial replication heavier.\n\nAnother mitigation, which is covered by this specification and by\n[ssb-meta-feeds-spec], is how shard feeds are derived for each peer. The\nderivation involves a hash function and a piece of metadata unique to the peer,\nwhich means that group members will have their group feeds located in a\ndifferent shard. This makes it harder for an eavesdropper to correlate the\nencrypted messages on the shard feeds of the peers it is eavesdropping on.\n\nAnother way membership information can leak is through replication requests:\nif only Alice, Bob, and Carol request for feeds A, B, C, and if these feeds\ndon't seem to be publicly declared in any metafeed tree, it might be reasonable\nfor an eavesdropper to guess that Alice, Bob, Carol are all in a group and that\nA, B, C are group feeds. We think this can be mitigated through \"sympathetic\nreplication\". Sympathetic replication involves asking friendly peers who aren't\nin our groups to replicate some of our group feeds. This adds more noise in\nmetadata analysis attacks, and makes it harder to guess who is in a group. It\nalso improves replication resilience, because there are more peers replicating\ngroup feeds, making them more available in the network. Sympathetic replication\nneeds further investigation and specification.\n\nIt is currently unknown how much can be learned from metadata analysis attacks,\nas it depends on these mitigations and the network's complexity, such as number\nof peers, number of groups, and number of relationships between peers.\n\n## 6. References\n\n### 6.1. Normative References\n\n- [ssb-meta-feeds-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- [ssb-uri-spec] version 1.3\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-meta-feeds]\n- [perfect-forward-secrecy]\n- [post-compromise-security]\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[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-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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Fssb-meta-feeds-group-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssbc%2Fssb-meta-feeds-group-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Fssb-meta-feeds-group-spec/lists"}