/** Protocol version. Bumped only for a change that is not backward compatible. */ export declare const CLUSTER_ENVELOPE_VERSION = 1; /** * Ceiling on one datagram. * * Larger than an Ethernet MTU on purpose: a roster gossip carrying every member * of a full group does not fit in 1500 bytes, and IP fragmentation on a local * network is a normal, reliable thing. Anything above this is refused rather * than fragmented into dozens of pieces, state that large is a bug, not a big * group. */ export declare const MAX_ENVELOPE_BYTES = 32768; /** A datagram before signing. `body` carries whatever the message type needs. */ export interface ClusterEnvelope { readonly v: number; readonly groupId: string; /** Which group-key generation `sig` was computed under. */ readonly keyGen: number; /** * The surface this message concerns, as a digest, or null for a group-level * message such as the discovery beacon. Populated by the per-surface election * layer; this module only carries and authenticates it. */ readonly surfaceId: string | null; readonly type: string; readonly nodeId: string; readonly nodeVersion: string; readonly seq: number; readonly ts: number; readonly body: Readonly>; } /** An envelope as it appears on the wire. */ export interface SignedClusterEnvelope extends ClusterEnvelope { readonly sig: string; } /** * The group keys this node currently holds, and which of them it will accept. * * Implemented by the key store. Kept as an interface so the envelope codec has * no idea where keys are persisted and tests can hand it a literal. */ export interface ClusterKeyring { readonly groupId: string; /** The generation this node signs with. */ readonly currentGeneration: number; /** The key for a generation, or null when this node does not hold it. */ keyForGeneration(generation: number): string | null; /** * Generations whose signatures are accepted right now, the current one and, * during the cutover window, the one before it. */ acceptedGenerations(): readonly number[]; } /** * The bytes a signature covers. * * A fixed-order array rather than an object, because JSON.stringify of an * object serializes in insertion order: two nodes that built the same logical * message in a different order would produce different bytes and every * signature would fail across builds. `body` is canonicalized by sorting its * keys for the same reason. */ export declare function canonicalizeEnvelope(envelope: ClusterEnvelope): string; /** Hex HMAC-SHA256 of the canonical form under one generation's key. */ export declare function signEnvelope(envelope: ClusterEnvelope, groupKey: string): string; /** Fields the caller supplies; the codec fills in the rest from the keyring. */ export interface EnvelopeDraft { readonly type: string; readonly nodeId: string; readonly nodeVersion: string; readonly seq: number; readonly ts: number; readonly surfaceId?: string | null | undefined; readonly body?: Readonly> | undefined; } /** Build and sign a datagram with the keyring's CURRENT generation. */ export declare function encodeEnvelope(draft: EnvelopeDraft, keyring: ClusterKeyring): string; /** * Build a datagram signed with a key that is NOT a group key. * * Two message classes need this, and only two: * * JOIN / JOIN_ACCEPT / JOIN_REFUSE are authenticated with the JOIN VERIFIER, * because a machine that is trying to get into the group by definition does * not hold a group key yet, and its admitter has no other shared secret with * it. Their `keyGen` is 0 and carries no meaning, the class of the message * determines the key, not the field. * * REJOIN / REJOIN_ACCEPT are authenticated with the sender's long-lived * ed25519 IDENTITY key, checked against the public half in the roster. That * is what lets a machine that has missed every rotation AND a join-key change * still prove it is itself. * * Everything else on the wire is signed with the current group key by * {@link encodeEnvelope}, and nothing here weakens that. */ export declare function encodeEnvelopeWith(draft: EnvelopeDraft, groupId: string, authenticate: (canonical: string) => string): string; /** * Parse a datagram into its envelope and its authenticator WITHOUT checking * anything. * * Only the two message classes above may use this, and each must immediately * check the authenticator with the key its class prescribes. A caller that * reads an envelope here and acts on it without verifying has removed the * entire trust boundary, so every call site is short and does the check on the * next line. */ export declare function readUnverifiedEnvelope(raw: string): { envelope: ClusterEnvelope; sig: string; } | null; /** Why a datagram was not accepted. `null` means it verified. */ export type EnvelopeRejection = 'oversized' | 'not-json' | 'not-an-object' | 'unsupported-version' | 'other-group' | 'malformed-field' | 'generation-not-accepted' | 'generation-not-held' | 'signature-did-not-verify'; export interface EnvelopeDecodeResult { readonly envelope: ClusterEnvelope | null; readonly rejected: EnvelopeRejection | null; /** * The group the datagram claimed, even when it was refused. Lets the beacon * listener enumerate OTHER groups on this network without accepting anything * from them. */ readonly claimedGroupId: string | null; } /** * Parse and authenticate a datagram. * * Order matters here. The group is checked BEFORE the signature so that two * unrelated groups sharing one multicast address spend nothing on each other's * traffic beyond a string compare, and so `claimedGroupId` comes back for the * beacon listener even though the datagram itself is refused. */ export declare function decodeEnvelope(raw: string, keyring: ClusterKeyring): EnvelopeDecodeResult; /** * Read the group a datagram claims without holding any key at all. * * This is how a node with clustering switched on but no membership enumerates * the groups it can see. It authenticates NOTHING, a beacon read this way is * an advertisement, and is treated as one: it can populate a list the operator * chooses from, and it can never cause this node to act. */ export declare function peekEnvelope(raw: string): { groupId: string; type: string; body: Record; } | null; //# sourceMappingURL=protocol-envelope.d.ts.map