{"id":28396299,"url":"https://github.com/ssbc/fusion-identity-spec","last_synced_at":"2026-01-30T04:21:44.390Z","repository":{"id":45053449,"uuid":"361099645","full_name":"ssbc/fusion-identity-spec","owner":"ssbc","description":null,"archived":false,"fork":false,"pushed_at":"2022-07-11T17:14:55.000Z","size":534,"stargazers_count":15,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-10-14T00:26:08.032Z","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}},"created_at":"2021-04-24T07:33:28.000Z","updated_at":"2025-07-14T05:03:07.000Z","dependencies_parsed_at":"2022-08-29T21:51:41.812Z","dependency_job_id":null,"html_url":"https://github.com/ssbc/fusion-identity-spec","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ssbc/fusion-identity-spec","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Ffusion-identity-spec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Ffusion-identity-spec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Ffusion-identity-spec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Ffusion-identity-spec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ssbc","download_url":"https://codeload.github.com/ssbc/fusion-identity-spec/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ssbc%2Ffusion-identity-spec/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28901252,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-30T04:02:34.702Z","status":"ssl_error","status_checked_at":"2026-01-30T04:02:33.562Z","response_time":66,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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.686Z","updated_at":"2026-01-30T04:21:44.385Z","avatar_url":"https://github.com/ssbc.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\nSPDX-FileCopyrightText: 2021 Anders Rune Jensen\n\nSPDX-License-Identifier: CC-BY-4.0\n--\u003e\n\n# Fusion Identity Spec v1 (DRAFT)\n\nThe fusion identity spec is a specification for how to relate multiple\ndevices in such a way that they represent a new combined (fusion)\nidentity. The primary purpose is an individual with multiple devices\nand thus multiple [SSB] identities. Only new members can be added to a\nfusion, there is no removal of members only tombstoning.\n\nIn the case where a fusion identity is tombstoned because a member is\nremoved and another fusion must take its place, the concept of\nredirecting and attestations of these was considered. But instead of\nbaking these concepts into the protocol in an initial version, we have\ndecided to include them in an future version once we get more real\nworld experience.\n\nThe string representation of a fusion id is:\n`ssb:identity/fusion/\u003cPUBLIC_KEY\u003e`\n\nUsages:\n - Can mention a fusion identity and all members can use that as\n   notification\n - Can private message a fusion identity and all members will be able\n   to decrypt the message and respond\n - Acts as a public record of what feeds are part of an identity\n   (discoverability)\n - Following a fusion identity for replication\n - Messages can be shown as coming from the same identity across\n   multiple devices\n\nOut of scope for v1:\n - Redirection\n - Attestation of redirects\n - Private invitations to fusion identity\n - Inviting fusion identities to private groups\n - Use of fusion identity for authorisation logic\n     - this is a priority, but will be addressed in subsequent versions\n - Invite a fusion identity to an existing fusion identity\n\n## Overview\n\nAlice has SSB installed on per laptop and installs a client on her\nphone. She wants one identity that links her two devices. Either\ndevice can do an `init` to create a new fusion identity. Lets assume\nlaptop does. Then laptop `invite` phone to the identity. The phone\n`consent` the invite, after which laptop will `entrust` phone with the\nprivate key. Lastly phone posts a `proof-of-key` message announcing\nthe possession of the key. At this point phone is now a member of the\nfusion identity and can invite other devices.\n\n```\nt   @laptop                                   @phone\n-   ---------------------------------------   -----------------\n1   init(@fuID)\n2   enc(entrust(sk), [@laptop, @fuID])\n3   invite(@phone)\n4                                             %cID = consent(yes)\n5   enc(entrust(sk, %cID), [@phone, @fuID])\n6                                             proof-of-key(%cID, proof)\n```\n\nIf Alice is at a party and looses her rooted phone on the way\nhome. From this point on she `tombstones` the fusion identity to tell\nother peers not to send private messages to the fusion identity any\nlonger as those messages might be read by a third party.\n\n## Fusion Identity Operations:\n\n### init\n\nStart the new identity. This results in two messages: an init message\nand a key to self.\n\n```js\n{\n  type: 'fusion',\n  subtype: 'fusion/init',\n  id: 'ssb:identity/fusion/3h89vDLAcoi68wXZRZ2kVrXs0WT0vthGo9uW1XBPLhU=',\n  members: {\n    '@mmEKdNyVBtxQM3bwAVS8ujJvi/C1PR07tEQZVtyCp1c=.ed25519': 1\n  },\n  tangles: {\n    fusion: { root: null, previous: null }\n  }\n}\n```\n\nDM key to self:\n\n```js\n{\n  type: 'fusion/entrust',\n  secretKey: 'k4eUDLq4rzUvWRxYjVM8PE64DiU6tidLEn+5ERax3OnbO0IXQhe4yQAHom/lnGjrV+hIi3fvSHoG/UuIuuecvA==',\n  rootId: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256',\n  recps: [\n    'ssb:identity/fusion/3h89vDLAcoi68wXZRZ2kVrXs0WT0vthGo9uW1XBPLhU=',\n    '@mmEKdNyVBtxQM3bwAVS8ujJvi/C1PR07tEQZVtyCp1c=.ed25519'\n  ]\n}\n```\n\n`secretKey` is in base64.\n\nTo keep things simple the public encryption key for DMs is the same as\nthe identity.\n\nThe author of the message is now the first member of the fusion\nidentity.\n\nPreconditions:\n - The `id` must not be used in another fusion identity (different\n   root), in that case all fusions with the same id is considered\n   tombstoned.\n\n### invite\n\nInvite one or more feeds to join a fusion.\n\n```js\n{\n  type: 'fusion',\n  subtype: 'fusion/invite',\n  invited: {\n    '@2Cu6gvifd39hHvE/HkT4M7dP5KY5CZ+AsYzM1w2mtT8=.ed25519': 1\n  },\n  tangles: {\n    fusion: {\n      root: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256', // init\n      previous: ['%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256'] // init\n    }\n  }\n}\n```\n\nPreconditions:\n - The feed writing this message message must be a member\n - You can invite multiple feeds\n - Inviting yourself is considered an error\n\n### consent\n\nRespond to an invitation message.\n\nFor an accept:\n\n```js\n{\n  type: 'fusion',\n  subtype: 'fusion/consent',\n  consented: { \n    '@2Cu6gvifd39hHvE/HkT4M7dP5KY5CZ+AsYzM1w2mtT8=.ed25519': 1\n  },\n  tangles: {\n    fusion: {\n      root: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256', // init\n      previous: ['%1UFhwpETE+dR/3IiqXpMIxsh5ga6DsCj6if8Q0JMYAE=.sha256'] // invite\n    }\n  }\n}\n```\n\nFor a decline:\n\n```js\n{\n  type: 'fusion',\n  subtype: 'fusion/consent',\n  consented: { \n    '@2Cu6gvifd39hHvE/HkT4M7dP5KY5CZ+AsYzM1w2mtT8=.ed25519': 0\n  },\n  tangles: {\n    fusion: {\n      root: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256', // init\n      previous: ['%1UFhwpETE+dR/3IiqXpMIxsh5ga6DsCj6if8Q0JMYAE=.sha256'] // invite\n    }\n  }\n}\n```\n\nPreconditions:\n - The author must be invited to the fusion\n - The author must not have consented positivily before\n - The author must not be a member of the fusion\n\n### entrust\n\nGive secret key to someone that has consented an invite. This is\nsimilar to the message to sent self on `fusion/init`.\n\n```js\n{\n  type: 'fusion/entrust',\n  secretKey: 'k4eUDLq4rzUvWRxYjVM8PE64DiU6tidLEn+5ERax3OnbO0IXQhe4yQAHom/lnGjrV+hIi3fvSHoG/UuIuuecvA==',\n  rootId: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256',\n  consentId: '%J0IY7Xxx6OqUr9HXlhIQGJoad+saCEOxkYlkkViY8Oc=.sha256',\n  recps: [\n    'ssb:identity/fusion/3h89vDLAcoi68wXZRZ2kVrXs0WT0vthGo9uW1XBPLhU=',\n    '@2Cu6gvifd39hHvE/HkT4M7dP5KY5CZ+AsYzM1w2mtT8=.ed25519'\n  ]\n}\n```\n\n`secretKey` is in base64.\n\nPreconditions:\n - The recipient must have consented the fusion invite\n\nNOTE:\n - we could have included a private section of the `fusion/invite`\n   which included the key, but decided against this because it makes\n   the flow less clear\n - adding an additional step to send the key after consent does not\n   add significant latency because we are presumed to be in control of\n   all the devices in this fusion identity\n\n### proof-of-key / member\n\nA way to publicly announce that you are in possession of private key\nand thus announce that you are now a member.\n\n```js\n{\n  type: 'fusion',\n  subtype: fusion/proof-of-key,\n  members: { \n    '@2Cu6gvifd39hHvE/HkT4M7dP5KY5CZ+AsYzM1w2mtT8=.ed25519': 1\n  },\n  consentId: '%71lcX2CY306hlZQW1UtoZ0uODm/RurnGlc8mCCjwn7w=.sha256',\n  proofOfKey: 'fqhDYLkijSEKhYD3nQziMcszCFVxBaIAEZuue+1RA/dhm14OgryVHOXK6fhwsdlrFzj58HWBPZUAjVz4zafYCQ==.sig.ed25519',\n  tangles: {\n    fusion: { \n      root: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256', // init\n      previous: ['%71lcX2CY306hlZQW1UtoZ0uODm/RurnGlc8mCCjwn7w=.sha256'] // consent\n  }\n}\n```\n\n`proofOfKey` must be signed by the entrusted fusion key as\nsign(%consentId + 'fusion/proof-of-key').\n\nPreconditions:\n - The author must be have consented an invite in %consentId\n - The author writes this proof-of-key message when it sees the\n   entrust, this ensures that they are in posession of the key\n\n### tombstone\n\nNullify or revoke a fusion identity.\n\nGiven you have a shared private key, there is no easy way to \"remove\"\na device from a fusion identity. Our solution is to \"tombstone\"\nidentities and require that you mint a new fusion identity with the\ndevices you trust.\n\n```js\n{\n  type: 'fusion',\n  tombstone: { \n    set: { \n      date: 1641983852373, \n      reason: 'bye' \n    }\n  },\n  tangles: {\n    fusion: {\n      root: '%ZxAJbfRTwhkhpD9viErN9zBzIEzrm6FTndaH/bEnbfI=.sha256', // init\n      previous: ['%Wo2vAn/LuCCDauDfbZEncCu/5eV/uuk2M1tIbT0DjC8=.sha256'] // proof-of-key\n    }\n  }\n}\n```\n\nPreconditions:\n - Only a member of a fusion identity can tombstone it\n - There is no undo for a tombstone\n - After a tombstone, the only messages which are allowed to extend\n   the tangle are other tombstone messages\n\nNOTE:\n - you MUST NOT DM a tombstoned identity\n - tangles can have divergent state (many tips to the graph). We\n   consider a tangle tombstoned if any of the tips are a tombstone\n   message\n\n## Flow\n\nA flow diagram of what action can follow another. Please note that\nmultiple invites can happen concurrently. The tangle structure keeps\ntrack of the state of the identity. After a tombstone, the only valid\noperation on the identity is attestation.\n\nThe `\u003cstate\u003e -\u003e tombstone` is left out in the list above because any\nstate can lead to the tombstone state. Similarly `\u003cstate\u003e -\u003e invite`\nis also left out.\n\n```\n// identity tangle state changes\ninvite -\u003e consent\ninvite -\u003e attestation (deny)\nconsent -\u003e entrust\nentrust -\u003e proof-of-key\ntombstone -\u003e attestation\n```\n\n## Private messages\n\nPrivate messages uses box2.\n\nFIXME: precise definition. See https://github.com/ssbc/fusion-identity-spec/issues/6\n\n## Operations\n\nBesides the operations operating directly on a fusion identity\ndescribed above: init, invite, consent, entrust, proof-of-key,\ntombstone, the following operations can be useful:\n\n### read\n\nGets the current state of a fusion including the status of feeds such\nas invited, members, consented.\n\n### invitations\n\nWhich fusion identities have I been invited to and not yet responded to.\n\n### all\n\nThe current active fusion identies\n\n### tombstoned\n\nAll tombstoned fusion identies\n\n## Out of scope for v1\n\n### redirect operations\n\nThe purpose of redirects is to make it easy to point from a tombstoned\nrecord to it's replacement.\n\nA redirect is an independant tangle. It is neither part of the old or\nnew fusion identity. This means there is no causality between the old,\nthe new and the redirect. Clearly the old and new needs to exist\nbefore a redirect can be created but there is no need for a redirect\nto be attested before the new identity can start inviting members.\n\n```js\n{\n  type: 'fusion/redirect',\n  old: ssb:identity/fusion/id1,\n  new: ssb:identity/fusion/id2,\n  tangles: {\n    redirect: {\n      root: null,\n      previous: null\n    }\n  }\n}\n```\n\n```js\n{\n  type: 'fusion/redirect',\n  tombstone: {\n    date,\n    reason\n  },\n  tangles: {\n    redirect: {\n      root,      // the root message of a fusion/redirect tangle\n      previous\n    }\n  }\n}\n```\n\nIt might also be useful to have [authenticated redirects].\n\nNOTE:\n- there can be many redirects, which ones you choose to trust are up\n  to you (you might like to consider who authored it, and who's\n  atteseted it - see below below)\n- the only person allowed to tombstone a redirect is the person who\n  published it\n\n### attestation\n\nIs the redirect valid?\n\n```js\n{\n  type: 'fusion/attestation',\n  target: %redirectId,                    // static\n  position: confirm|reject|null,\n  reason: String,                         // optional\n  tombstone: { reason }                   // optional\n  tangles: {\n    attestation: { root: null, previous: null }\n  }\n}\n```\n\nEveryone who agrees with a redirect must attest it publicly because it\nmakes it harder for an adversary to try and hide the fact that a\nidentity have been revoked by withholding messages from a single feed.\n\n### Flow\n\n\n```\n// redirect tangle state changes\nredirect -\u003e attestation\nredirect -\u003e tombstone of redirect?\n```\n\n```\n// attestation tangle state changs\nattestation -\u003e tombstone\n```\n\n\n## Attacks\n\nSomeone trying to add fake identities to a fusion just before posting\na tombstone in order for them to appear to have the majority when it\ncomes to redirecting.\n\n## Related work\n\nSoK: Multi-Device Secure Instant Messaging\n\nhttps://eprint.iacr.org/2021/498.pdf\n\nIdeas from that one:\n - Use 2 keys instead of one to allow differentiation between devices,\n   meaning you could have some devices that you don't trust so much so\n   they are only allowed to read messages to the identity, not to add\n   members or revoke the identity.\n\nBackchannel\n\nhttps://www.inkandswitch.com/backchannel/\n\n\n[SSB]: https://github.com/ssbc/\n[box2]: https://github.com/ssbc/private-group-spec\n[authenticated redirects]: https://ssb-ngi-pointer.github.io/Audit%20Report_%20Secure%20Scuttlebutt%20Partial%20Replication%20and%20Fusion%20Identity.html#Suggestion-A-Explore-Protocol-Extension-for-Authentication-for-Redirects\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Ffusion-identity-spec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fssbc%2Ffusion-identity-spec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fssbc%2Ffusion-identity-spec/lists"}