Part 6 — sharing a graph
With an existing member
Section titled “With an existing member”Membership is your Directory’s answer. Once isGroupMember('acme', 'alex')
is true, Alex’s devices can open g:acme, and the next sync from any key
holder grants them.
// on your serverawait sql`INSERT INTO memberships (team_id, user_id) VALUES ('acme', 'alex')`;// on Alex's deviceawait engine.openGraph('g:acme'); // 'waiting' → 'ok' after a holder syncsWith somebody who is not a member yet — invites
Section titled “With somebody who is not a member yet — invites”The invite string is <doorCode>.<keySecret>. Only the door code reaches your
server; the keyring rides along sealed under PBKDF2 of the half that never
does.
// Samconst stash = await engine.sealKeyringForInvite('g:acme');if (!stash) throw new Error('no key for that graph on this device');const { salt, sealedKeyring, keySecret } = stash;const doorCode = await myApi.createInvite({ graphId: 'g:acme', salt, sealedKeyring });const inviteString = composeInvite(doorCode, keySecret); // from @skm/core
// Alexconst { doorCode, keySecret } = splitInvite(inviteString);const { salt, sealedKeyring } = await myApi.redeemInvite(doorCode);if (keySecret) { await engine.acceptInviteKeyring('g:acme', { salt, sealedKeyring, keySecret });}acceptInviteKeyring returns false on a wrong secret or a malformed keyring —
it never throws, so a mistyped invite is a message, not a crash.
splitInvite on a bare door code returns keySecret: null. That is the
“joined without the key” path: the member is in the group, so an existing
device will grant them normally on its next sync.
Losing the whole invite string to an attacker is a real compromise. Losing your server’s invite table is not: it holds the door code and a blob the server cannot open.
Pairwise channels
Section titled “Pairwise channels”await engine.openGraph('d:alex', { create: true });await engine.put('d:alex', { kind: 'msg', data: { body: 'hey' } });Alex opens d:sam and sees it. Both sides create with {create: true} — the
race is resolved and both end up on one key.
The relay learns that Sam and Alex have a channel; it never learns a word of it. Hiding the existence of a conversation needs sealed sender or a mixnet, neither of which skm claims.