/** * federation/hmac.ts — HMAC-SHA-256 signing + verification for * federation envelopes. * * F-038 — security-critical. Pinned by AGENTS.md hard constraint: * "MUST use crypto.timingSafeEqual for HMAC comparison * (timing-attack resistant)." * "MUST use crypto.randomUUID() for nonces (128-bit random)." * * Nonce strategy: * - Sender generates a fresh `crypto.randomUUID()` per envelope. * - Receiver tracks every nonce it has seen in a TTL-bounded Set. * - An envelope whose nonce appears twice is a replay → reject. * - An envelope whose timestamp is older than `nonceWindowMs` * (default 5 min) is also a replay → reject. * * No native deps. Pure Node `node:crypto`. */ import { type FederationEnvelope } from "./envelope.js"; /** Default freshness window for HMAC verification (5 minutes). */ export declare const DEFAULT_NONCE_WINDOW_MS: number; /** Compute a deterministic 64-char hex HMAC-SHA-256 over the envelope's * canonical signable payload. */ export declare function signEnvelope(envelope: FederationEnvelope, secret: string): string; export interface VerifyOptions { /** Freshness window. Envelopes older than this are rejected. * Defaults to 5 minutes. */ readonly nonceWindowMs?: number; /** Optional clock-skew offset (ms) to *add* to the freshness * window (e.g. for poorly-synced peers). Defaults to 0. */ readonly clockSkewMs?: number; /** Optional explicit timestamp for deterministic tests. */ readonly now?: number; } export type VerifyOutcome = { ok: true; } | { ok: false; reason: "signature_mismatch" | "nonce_replay" | "envelope_expired" | "envelope_from_future" | "invalid_signature_format"; }; /** Pure verifier — no shared state. The caller owns the seen-nonce * cache (see `NonceCache` below) and passes it in via `seen`. */ export declare function verifySignature(envelope: FederationEnvelope, secret: string, seen: ReadonlySet, opts?: VerifyOptions): VerifyOutcome; /** Mutable nonce cache with TTL eviction + hard cap. The receiver * keeps one per remote node (or a single global one for the skeleton). */ export declare class NonceCache { private readonly seen; private readonly windowMs; constructor(windowMs?: number); /** True if the nonce is fresh (not seen in windowMs). Adds it * to the cache as a side effect. */ check(nonce: string, now?: number): boolean; /** True if the nonce is in the cache (read-only). */ has(nonce: string): boolean; size(): number; clear(): void; private evict; } /** Helper — produce a fresh nonce via `crypto.randomUUID()`. */ export declare function freshNonce(): string; //# sourceMappingURL=hmac.d.ts.map