Home

Awesome

ssb-tribes

A scuttlebutt plugin for managing encrypted groups. Implements the private group spec which uses the envelope spec.

This is the successor to ssb-private1.

Example Usage

const SecretStack = require('secret-stack')
const config = require('ssb-config')
const caps = require('ssb-caps')

const stack = SecretStack({ caps })
  .use(require('ssb-db2/core'))
  .use(require('ssb-classic'))
  .use(require('ssb-db2/compat'))
  .use(require('ssb-db2/compat/feedstate'))
  .use(require('ssb-box2'))
  .use(...)

const ssb = stack({
  ...config,
  box2: {
    ...config.box2,
    legacyMode: true
  }
})
ssb.tribes.create({}, (err, info) => {
  const { groupId } = info

  const content = {
    type: 'post',
    test: 'kia ora, e te whānau',
    recps: [groupId] // <<< you can now put a groupId in the recps
  }
  ssb.tribes.publish(content, (err, msg) => {
    // tada msg is encrypted to group!

    const cookie = '@YXkE3TikkY4GFMX3lzXUllRkNTbj5E+604AkaO1xbz8=.ed25519'
    const staltz = '@QlCTpvY7p9ty2yOFrv1WU1AE88aoQc4Y7wYal7PFc+w=.ed25519'

    ssb.tribes.invite(groupId, [cookie, staltz], {}, (err, invite) => {
      // two friends have been sent an invite which includes the decryption key for the group
      // they can now read the message I just published, and publish their own messages to the group

    })
  })
})

Behaviour

This plugin provides functions for creating groups and administering things about them, but it also provides a bunch of "automatic" behviour.

  1. When you publish a message with recps using ssb.tribes.publish it will auto-encrypt the content when:
    • there are 1-16 FeedIds (direct mesaaging)
    • there is 1 GroupId (private group messaging)
    • there is 1 GroupId followed by 1-15 FeedId
      • NOTE this is currently only recommended for group invite messages as it's easy to leak group info
  2. When you receive an encrypted message with suffix .box2 it will attempt to auto-decrypt the content:
    • on success this value will then be accessible in all database queries/ indexes
    • if it fails because it didn't have the key, the message gets passed to the next auto-decrypter to attempt
    • if it fails because something is clearly horribly wrong in the encyprtion and it should have worked, it throws an error (check this)
  3. When you receive an invite to a new group, you will auto-decrypt all messages
    • we re-index your whole database, which will reveal new messages you can decrypt
      • in the future we will only re-index messages you previously could not decrypt
    • keys for groups are stored in a off-chain key-store
  4. If you've been given the readKey for a particular message, you can use that
    • e.g. ssb.get({ id, private: true, key: readKey }, cb)

NOTES:

Requirements

A Secret-Stack server running the plugins:

The secret stack option config.box2.legacyMode also needs to be true.

API

ssb.tribes.publish(content, cb)

A wrapper around ssb.db.create that makes sure you have correct tangles (if relevant) in your message. Mutates content. You need to put recipients in content.recps.

ssb.tribes.create(opts, cb)

Mint a new private group.

where:

This method calls group.add and group.addAuthors for you (adding you)

ssb.tribes.invite(groupId, [authorId], opts, cb)

Adds an author to a group you belong to. This publishes a message that both this new author AND the group can see, and contains the info needed to get the new person started (the groupKey and root).

where:

This method calls group.addAuthors for you (adding that person to the group register for you)

ssb.tribes.addNewAuthorListener(fn)

Listens for when new authors are added to a tribe, and fires a given function

ssb.tribes.excludeMembers(groupId, [authorId], cb)

Excludes an author from a group you belong to. This publishes a message that both this new author AND the group can see. NOTE :warning: this only politely asks the author to leave the group, we don't rotate keys (yet)

where:

ssb.tribes.list(cb)

Returns a list of all known group IDs. By default this excludes subgroups

Alternatively ssb.tribes.list({ subtribes: true }, cb) will give you all group IDs (including those of subtribes)

ssb.tribes.get(groupId, cb)

Returns group metadata for a given group:

ssb.tribes.listAuthors(groupId, cb)

Lists all the authors (feedIds) who you know are part of the group with id groupId

ssb.tribes.subtribe.create(groupId, opts, cb)

A convenience method which:

where:

This method calls ssb.tribes.create

ssb.tribes.subtribe.get(groupId, cb)

alias of ssb.tribes.get


API (Extras)

These endpoints give you access to additional features, such as:

ssb.tribes.register(groupId, info, cb)

Registers a new group that you have learnt about.

NOTE: mainly used internally

where:

ssb.tribes.poBox.create(opts, cb)

Creates a P.O. Box key-pair, which is like a one-way group messaging setup with a public and private curve25519 keypair.

ssb.tribes.addPOBox(groupId, cb)

Creates a P.O. Box key-pair, and publishes a message announcing those to a group. This will be heard by group members, allowing them to open messages sent to that P.O. Box

ssb.tribes.poBox.get(groupId, cb)

Get the keypair that's attached to a group

ssb.tribes.link.create({ group, name }, cb)

Creates a message of type link/feed-group which links your feedId to a valid group. (i.e. you can only create links between your feedId and profiles at the moment)

Arguments:

Note:

ssb.tribes.link.createSubGroupLink({ group, subGroup, admin }, cb)

Creates a message of tyoe link/group-subGroup which links a group to a subGroup

Arguments:

ssb.tribes.findByFeedId(feedId, cb)

Find groups which have linked with a feedId (see ssb.tribes.link.create).

NOTE: the strange format with states is to leave easy support for multiple editors (of a link to a group) in the future

ssb.tribes.findSubGroupLinks(groupId, cb)

Find subGroups which have linked with a groupId (see ssb.tribes.link.createSubGroupLink).

ssb.tribes.subtribe.findParentGroupLinks(subGroupId, cb)

Find subGroups which have linked with a groupId (see ssb.tribes.link.createSubGroupLink).

TODO