import type { MessageContent, UserId } from './protocol/index.js'; import type { ServerFrame } from './protocol/index.js'; import { type X3DHBundle } from './crypto.js'; /** * Per-user E2E session. Supports two modes: * * LIVE (original): Both parties are online. ECDH P-256 key exchange via the * `pubkey`/`peerkey` frames. Instant but requires both parties to be connected. * * ASYNC (X3DH): The sender encrypts to the recipient's prekey bundle while the * recipient is offline. Uses X3DH (Extended Triple DH) with identity keys, * signed prekeys, and one-time prekeys. The recipient derives the same shared * key from the init message when they come online. * * Both modes produce an AES-GCM 256 shared key for message encryption. */ export declare class E2ESession { private readonly storageKey; private kp?; private shared?; private identityKP; private signedPreKP; private signedPrekeyId; private readonly otpKeys; private x3dhShared?; private pendingX3DH; constructor(storageKey: string); get ready(): boolean; /** Live ECDH mode: Generate/restore our keypair and return our public key to publish. */ begin(): Promise; /** Live ECDH mode: A peer published their key — derive the shared secret. */ onPeerKey(peerKeyB64: string): Promise; /** X3DH: Generate identity key, signed prekey, and OTP prekeys. * Returns the upload frame payload the caller should send to the server. */ initX3DH(): Promise<{ identityKey: string; signedPrekey: string; signedPrekeyId: string; signature: string; oneTimePrekeys: string[]; }>; /** X3DH sender: given a recipient's prekey bundle, derive the shared key and * return the init message fields to embed in the first encrypted message. */ x3dhSendTo(bundle: X3DHBundle): Promise<{ ephemeralKey: string; spkId: string; usedOTP: boolean; senderIK: string; }>; /** X3DH recipient: given an init message's sender IK + EK + SPK ID, derive * the shared key. `usedOTP` MUST reflect whether the SENDER actually * included a one-time prekey in its DH computation (carried on the wire * as `x3dhOTP`, see `X3DHInitFields`) — it must never be inferred from * whether we happen to still have OTP keys locally. Popping one * unconditionally was the bug here: our OTP pool almost always has spare * keys (we upload a batch of 20 and only the sender's own choice consumes * one), so we'd derive dh4 against an OTP the sender never included, * producing a shared key that doesn't match the sender's — every * message would come back "🔒 unable to decrypt" — while also burning a * one-time key that was never actually used. */ x3dhReceiveFrom(senderIKb64: string, ephemeralKeyB64: string, spkId: string, usedOTP: boolean): Promise; /** Flush pending X3DH derivation after initX3DH() completes. */ flushPendingX3DH(): Promise; /** Encrypt outgoing text into a wire content object. For X3DH init messages, * the caller should pass x3dhInit fields to embed in the content. */ sealText(text: string, x3dhInit?: { ephemeralKey: string; spkId: string; senderIK: string; usedOTP: boolean; }): Promise; /** Decrypt one content object if it is encrypted (otherwise pass through). */ private openContent; /** Decrypt any encrypted message content carried by an incoming frame, in place. */ openFrame(frame: ServerFrame): Promise; } /** X3DH init fields embedded in a text MessageContent (as extra properties). * Present only on the very first message from a sender to an offline peer. */ export interface X3DHInitFields { x3dhEK: string; x3dhSPK: string; x3dhIK: string; /** Whether the sender's DH computation included a one-time prekey (dh4). * The receiver MUST honor this exactly — it decides whether to consume * one of its own OTP keys, and doing so when the sender didn't include * one derives a mismatched shared key (see x3dhReceiveFrom). Absent on * messages from a build predating this field: treated as `false`, which * is only correct if that sender also never used an OTP — a fresh E2E * session on both sides (the normal case) is unaffected either way. */ x3dhOTP: boolean; } export declare function extractX3DHInit(content: MessageContent): X3DHInitFields | null; export { type X3DHBundle } from './crypto.js'; export { type UserId };