A scuttlebutt plugin for managing encrypted groups. Implements the private group spec which uses the envelope spec.
This is the successor to ssb-private1
.
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
})
})
})
This plugin provides functions for creating groups and administering things about them, but it also provides a bunch of "automatic" behviour.
recps
using ssb.tribes.publish
it will auto-encrypt the content when:
.box2
it will attempt to auto-decrypt the content:
ssb.get({ id, private: true, key: readKey }, cb)
NOTES:
<GroupId>
(a unique identifier) which can be mapped to that tribe's <GroupKey>
(a shared encryption key). The <GroupId>
is related to the initialisation message for the tribe, but is safe to share (leaks no info about who started the group). The reason we can't use the "public" part of the <GroupKey>
as an id (like <FeedId>
) is that there isn't a public part - it's a symmetric key!<FeedId>
is synonymous with the public key of a particular device (@...sha256
). The private group spec details how we map this to a shared key between you (the author) and that recipient.A Secret-Stack server running the plugins:
ssb-db2/core
>= 7.1.1ssb-classic
ssb-tribes
ssb-db2/compat
ssb-db2/compat/feedstate
ssb-box2
>= 7.4.0ssb-replicate
- (optional) used to auto-replicate people who you're in groups withThe secret stack option config.box2.legacyMode
also needs to be true
.
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:
opts
Object
opts.addPOBox
Boolean attaches a P.O. Box to the group and publish the keys into the group
false
cb
Function is a callback with signature cb(err, data)
where data
is an Object with properties:
groupId
String - a cipherlink that's safe to use publicly to name the group, and is used in recps
to trigger enveloping messages to that groupgroupKey
Buffer - the symmetric key used for encryption by the groupgroupInitMsg
Object - a copy of the (enveloped) message used to initialise the groupThis 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:
groupId
String - is a cloaked id for a group you're a part of[authorId]
Array - is a collection of the feed ids of authors you're going to invite
opts
Object - is of form { text }
which allows you to (optionally) post some welcoming or intruducing message along with the invte.cb
Function - is a callback with signature cb(err, invite)
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
fn
Function - a function to call when a new author is added to the tribe. The function receives:
groupId
String - the id of the tribenewAuthors
Array - array of new authors added to the tribessb.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:
groupId
String - is a cloaked id for a group you're a part of[authorId]
Array - is a collection of the feed ids of authors you're going to excludecb
Function - is a callback with signature cb(err, exclusion)
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:
key
- the decryption key for the groupscheme
- the scheme the key is associated with (e.g. DM, group)root
- the initial message which started the groupssb.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:
link
in the parent group which advertises the existence of the subGrouppoBoxId
for that group so that the parent group member can send messages to the subGrouplink
inside the existing group (linking group + subGroup)where:
groupId
String - the id of the parent group this subGroup will be linked toopts
Object
opts.addPOBox
Boolean attaches a P.O. Box to the group and publish the keys into the group
false
opts.admin
Booelan adds meta-data to the link flagging it as the admin subgroupcb
Function is a callback with signature cb(err, data)
where data
is an Object with properties:
groupId
String - a cipherlink that's safe to use publicly to name the subGroup, and is used in recps
to trigger enveloping messages to that groupgroupKey
Buffer - the symmetric key used for encryption by the subGrouppoBoxId
Buffer - the asymetric key used to encrypt messages sent from outside of the subGroupgroupInitMsg
Object - a copy of the (enveloped) message used to initialise the subGroupThis method calls ssb.tribes.create
ssb.tribes.subtribe.get(groupId, cb)
alias of ssb.tribes.get
These endpoints give you access to additional features, such as:
ssb.tribes.register(groupId, info, cb)
ssb.tribes.link.create({ group, name }, cb)
ssb.tribes.link.createSubGroupLink({ group, subGroup, admin }, cb)
ssb.tribes.findByFeedId(feedId, cb)
ssb.tribes.findSubGroupLinks(groupId, cb)
ssb.tribes.subtribe.findParentGroupLinks(subGroupId, cb)
ssb.tribes.poBox.create(opts, cb)
ssb.tribes.addPOBox(groupId, cb)
ssb.tribes.poBox.get(groupId, cb)
ssb.tribes.ownKeys.list(cb)
: returns a list of self-DM keyinfo. Always a length of 1 (only a list for historical reasons). The keyinfo has the format { key: Buffer, scheme: String }
.ssb.tribes.ownKeys.register(key)
: sets the self-DM key (buffer).ssb.tribes.register(groupId, info, cb)
Registers a new group that you have learnt about.
NOTE: mainly used internally
where:
groupId
String - is a cloaked group id (see private-group-spec/group-id/README.md
)info
Object - contains data of form { key, scheme }
where:
key
String - a 32 byte symmetric key for the group (as a base64
encoded string)scheme
String (optional) - a description of the key management scheme this key is part ofcb
[ Function - a callback with signature cb(err: Error, success: Boolean)
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.
opts
Object - currently unused but still requiredcb
Function is a callback with signature cb(err, data)
where data
is an Object with properties:
poBoxId
String - a cipherlink that can be used in recps
by anyone, to send messages only those with the secret key can openpublic
Buffer - the public part of the keypairsecret
Buffer - the secret part of the keypairssb.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
groupId
String - group to add P.O. Box tocb
Function is a callback with signature cb(err, poBoxId)
ssb.tribes.poBox.get(groupId, cb)
Get the keypair that's attached to a group
groupId
String - group that has a P.O. Boxcb
Function is a callback with signature cb(err, { poBoxId, key })
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:
group
GroupId - the id of the private group you're creating a link with (linking your scuttlebutt feed with that group)name
String (optional) - this adds your nickname for the groupcb
Function - callback with signature (err, link)
where link
is the link messageNote:
recps: [groupId]
)ssb.tribes.link.createSubGroupLink({ group, subGroup, admin }, cb)
Creates a message of tyoe link/group-subGroup
which links a group to a subGroup
Arguments:
group
GroupId - the id of the parent private groupsubGroup
GroupId - the id of the subGroup you're linking to group
admin
Boolean - when set to true, this flag is used to tell when a subgroup is the admin-only subgroup for the groupcb
- Function - call with signature (err, link)
where link
is the link message
Note:recps: [group]
)ssb.tribes.findByFeedId(feedId, cb)
Find groups which have linked with a feedId (see ssb.tribes.link.create
).
feedId
FeedId is a stringcb
function is a callback with signature cb(err, data)
where data
is an Array of items of form:
{
groupId: GroupId,
recps: Recps, // an array of recipients who know about this link (should just be the group)
states: [
{
head: MsgId,
state: {
name: null|String
}
}
]
}
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
).
groupId
GroupId is a string representing the groupId
of the parent groupcb
function is a callback with signature cb(err, data)
where data
is an Array of items of form:
[{
linkId: MsgId,
groupId: GroupId,
subGroupId: GroupId,
admin: Boolean,
recps: Recps, // an array of recipients who know about this link (should just be the group)
}]
ssb.tribes.subtribe.findParentGroupLinks(subGroupId, cb)
Find subGroups which have linked with a groupId (see ssb.tribes.link.createSubGroupLink
).
subGroupId
GroupId is a string representing the groupId
of the subGroupcb
function is a callback with signature cb(err, data)
where data
is an Array of items of form:
[{
linkId: MsgId,
groupId: GroupId, // the parent group
subGroupId: GroupId,
recps: Recps, // an array of recipients who know about this link (should just be the group)
}]