Skip to main content

Module rekey

Module rekey 

Source
Expand description

Rekey blob primitive (GROUP_PROTOCOL.md).

A rotation (a Private removal, a re-founding, or a scheduled rekey) mints a fresh-random key for the next epoch and delivers it to every member who STAYS, one per-recipient blob each. This module is that atom — the located, wrapped single blob — built and opened in isolation. The 3303 event that carries a set of these blobs, and the apply/recipient-set logic, sit on top (later sub-pieces).

Each blob is:

  • located by recipient_pseudonym(pairwise_secret, scope, epoch): an opaque tag only the sender↔recipient pair can compute, so a recipient jumps straight to their own blob (no trial-decryption) and a removed member can’t even find a slot for a pair they’re not in; and
  • wrapped under the same pairwise secret via NIP-44 v2 (cipher), so only that recipient decrypts it.

The pairwise_secret is the NIP-44 v2 ConversationKey between the two identities — the ECDH-derived secret names. It is symmetric: the sender derives it from (their sk, recipient pk), the recipient recomputes the identical secret from (their sk, sender pk). (How the recipient learns the sender’s pubkey is the 3303-envelope layer’s job, not this atom’s — open_rekey_blob takes it as a parameter.)

The wrapped plaintext binds (scope, epoch) so a blob can’t be spliced into a different coordinate: even though only the authorized rotator can mint blobs, the binding makes a cross-scope/epoch reuse fail closed on open, the same discipline as the message envelope.

Structs§

ParsedRekey
A parsed, inner-signature-verified Rekey — what open_rekey_event yields. Authority (does the rotator’s roster rank permit this rotation?) and blob-opening are SEPARATE later steps: the rotator pubkey here is what the recipient pairs against in open_rekey_blob, and what the roster check is run against.
RekeyBlob
One located, wrapped rekey blob — the unit a 3303 Rekey event carries N of. locator is the recipient-pseudonym hex (where the recipient finds it); wrapped is the base64 NIP-44 ciphertext of scope_id ‖ epoch ‖ new_key.

Functions§

build_channel_rekey_event
Build a signed 3303 channel-Rekey distributing blobs introducing new_epoch for channel_id.
build_rekey_blob
Build one rekey blob: the fresh new_key for (scope, epoch), located + wrapped to recipient_pk. sender_sk is the rotator’s identity secret (the ECDH half the recipient pairs against).
build_server_root_rekey_event
Build a 3303 SERVER-ROOT rekey (a base rotation): the new server root reaches recipients ONLY via per-recipient ECDH blobs (RekeyScope::ServerRoot), while the event itself is enveloped under the PRIOR root and addressed by base_rekey_pseudonym(prior_root, community_id, new_epoch).
epoch_key_commitment
A commitment to the prior epoch’s key (fork detection): a Rekey references it so two managers who both rotate epoch N→N+1 produce a detectable fork (resolved by authority-first→time→id), and so a recipient who holds the prior key can confirm the rotator did too (a legitimacy check). Domain-separated SHA-256 over prev_epoch_be ‖ prev_key; the epoch binds the commitment to the specific link in the chain.
open_rekey_blob
Open a blob addressed to me: recompute the pairwise secret from (my_sk, sender_pk), confirm the blob’s locator is the one THIS pair+scope+epoch produces (rejects a blob handed to us under the wrong coordinate), decrypt, and verify the wrapped plaintext binds the SAME (scope, epoch) before returning the new key. Any mismatch (wrong sender, wrong coordinate, tamper, splice) is Err.
open_rekey_event
Open + verify a 3303 Rekey outer with the server-root key (which every member always holds): version-check, decrypt, parse the inner, verify the rotator’s inner signature, and read the rekey fields. Does NOT check authority (the rotator’s roster rank) or open any blob; the caller does both, pairing against the returned rotator. A wrong key (or non-member) fails the MAC → Err.
rekey_pairwise_secret
The pairwise sender↔recipient secret: the NIP-44 v2 ConversationKey, HKDF-extracted from the ECDH shared point. Symmetric — pairwise_secret(a_sk, b_pk) == pairwise_secret(b_sk, a_pk) — so the recipient recomputes exactly what the sender used, for BOTH the locator and the wrap key.