Skip to main content

Module derive

Module derive 

Source
Expand description

Key-derivation convention (GROUP_PROTOCOL.md) — FROZEN.

Every HKDF use in the Community protocol funnels through here. Changing any byte of the construction shifts every pseudonym and sub-key, orphaning all prior events — a forced migration to avoid. The layout is locked by the golden vectors in the test module; treat those as the spec.

Construction: HKDF-SHA256(IKM, salt=∅, info, L=32), where info = utf8(label) || 0x00 || id32 || epoch_be

  • label : ASCII purpose string, no terminator
  • 0x00 : single separator byte
  • id32 : raw 32-byte id (channel id, or scope id), never hex
  • epoch_be : the epoch as u64 big-endian (8 bytes); omitted where noted

Enums§

RekeyScope
Scope of a per-recipient rekey blob. Disambiguates two blobs a single sender delivers to the same recipient in one epoch (a server-root rotation and a channel rekey), which would otherwise collide on the same tag.

Functions§

banlist_locator
Opaque coordinate for the banlist entity, HKDF-derived from the community id — a STABLE logical id that survives a server-root rotation, so a re-anchored banlist binds the same coordinate at every epoch (re-anchoring). Member-computable (members hold the community id from their invite), outsider-opaque (the id is never on the wire — the relay sees only the rotating control_pseudonym), and the content stays server-root-encrypted, so privacy is unchanged.
base_rekey_pseudonym
The relay-filterable address of a SERVER-ROOT (base) rekey for (community, new_epoch). Keyed by the PRIOR server-root key — the base layer has no stable key above it, so the prior root is the handle every current member holds: a returning member derives this from the root they currently hold, finds the base rekey, learns the rotator from its inner sig, and recovers the next root (a short forward-walk; base rotations are rare). Binds the community id + epoch, and is label-separated from the channel-rekey / channel-message / control pseudonyms.
channel_pseudonym
Channel pseudonym: the value carried in the relay-filterable z tag. Every member derives the same one from the shared channel secret, so it both addresses and (by rotation) unlinks the channel’s traffic.
dissolved_envelope_key
Rotation-stable envelope key for the dissolution tombstone — community-id-derived so the tombstone is openable by any member or joiner at ANY epoch. The control plane is server-root-encrypted (per-epoch), which a post-rotation joiner can’t open for the publish-epoch; the tombstone carries no secret (content is {}), so a community-id key is the right scope — member-computable, outsider-opaque, epoch-free.
dissolved_locator
Opaque coordinate for the owner-dissolution tombstone (vsk=10), HKDF-derived from the community id — STABLE across a server-root rotation, exactly like banlist_locator. This rotation-stability is load-bearing for dissolution: a fresh joiner after a re-founding derives only the NEW epoch root, but can still compute this community-scoped coordinate and discover the tombstone, so a dissolved community can never look “alive” to anyone who can derive the community id. Member-computable, outsider-opaque.
dissolved_pseudonym
Rotation-stable relay #z for the dissolution tombstone — community-id-derived (NOT the per-epoch control_pseudonym), so ANY client that can derive the community id finds the tombstone at the SAME coordinate regardless of which epoch root it holds. This is what closes the post-rotation discoverability split: a fresh joiner who only ever derives a later epoch’s root still probes this fixed coordinate and learns the community is dead. Outsider-opaque (community id is never on the wire).
grant_locator
Opaque coordinate for a member’s Grant entity (vsk=3), HKDF-derived from the community id bound to the member’s x-only pubkey. Community-scoped (not server-root-scoped) so the coordinate is STABLE across a base rotation — the keystone that lets a re-anchored grant fold under the new root (re-anchoring): a new joiner holding only the new root still derives the same entity_id. Member-computable, outsider-opaque (community id never on the wire), content still server-root- encrypted — privacy unchanged. Roles need no locator (their d-tag is the role’s random id).
invite_links_locator
Opaque coordinate for a CREATOR’s own invite-links entity (vsk=8) — the per-creator list of active public-invite-link locators THEY published. Bound to the creator’s x-only pubkey exactly like a per-member grant (grant_locator), so a creator can only publish links at their own coordinate, and members fold every creator’s list into the aggregate active-set (is_public = aggregate non-empty). Community-id-derived (stable across rotation, member-computable, outsider-opaque). There is no shared registry — each creator owns only their own list (per-creator ownership).
public_invite_key
Public-invite sub-keys, all derived from the URL fetch-token. The token is the IKM and there is no channel/epoch context, so the frozen info uses the all-zero id and no epoch — the token alone provides uniqueness. The three labels domain-separate the decryption key, the relay locator (addressable d-tag), and the bundle’s signing key (so the owner can re-post under one coordinate to rotate, and joiners reject an impostor squatting the locator).
public_invite_locator
public_invite_signer
recipient_pseudonym
Per-recipient rekey-blob tag. IKM is the pairwise sender↔recipient ECDH secret (not the channel key), so only that pair can locate the blob and a removed member cannot derive tags for pairs they are not in.
rekey_pseudonym
The relay-filterable address of a channel REKEY event for (channel, epoch). Derived from the server-root key (NOT the channel key) + the channel id + the epoch the rekey introduces. Because the IKM is the server root — which every member always holds and which is stable across a channel rotation — any member can compute this for ANY epoch directly, WITHOUT holding that epoch’s (or the prior epoch’s) channel key. That is what makes epochs independently recoverable: a member fetches the rekey for whichever epoch(s) they choose (latest only, or all, in parallel), rather than chaining forward one key at a time. Distinct from the channel message pseudonym (channel key IKM) and the control pseudonym (community-id binding) by IKM/id, and domain-separated by label.