import type { OutboxLease, OutboxRequest, OutboxResponse } from "./outboxTypes"; import type { Peer } from "../reducers/roomSlice"; import type { RatchetRootSuite } from "../utils/constants"; export interface UsernamedPeer extends Peer { username: string; } export interface AddressBook extends UsernamedPeer { dateAdded: number; challenge?: string; signature?: string; } export interface BlacklistedPeer extends Peer { dateAdded: number; } export interface UniqueRoom { roomUrl: string; roomId: string; /** Canonical 32-byte public RoomPolicyV1 descriptor; absent on pre-v3 rows. */ roomPolicy?: ArrayBuffer; messageCount: number; lastMessageMerkleRoot: string; createdAt: number; updatedAt: number; } /** * Aggregate history for one saved room, computed inside the database worker. * * Sizes are logical message bytes, not wire bytes: p2party pads everything to * uniform 65,490-byte cells, so what actually crossed the network is always * larger and is a property of the padding policy rather than of the * conversation. Reporting the padded figure would describe the cover traffic, * not what the user sent. */ export interface RoomStats { messageCount: number; messagesSent: number; messagesReceived: number; bytesSent: number; bytesReceived: number; bytesTotal: number; /** Distinct peers this device has received from, ever. */ peerIds: string[]; } export interface MessageData { /** Sender-only random logical-send identity; absent on received/legacy rows. */ transferId?: string; roomId: string; timestamp: number; fromPeerId: string; channelLabel: string; hash: string; merkleRoot: string; filename: string; messageType: number; savedSize: number; totalSize: number; } export interface Chunk { merkleRoot: string; hash: string; chunkIndex: number; data?: ArrayBuffer; mimeType: string; realLen?: number; leafHash?: string; } export interface ReceiveChunk { /** Authenticated wire-metadata schema; protocol v3 accepts exactly v1. */ schemaVersion: number; roomId: string; fromPeerId: string; channelLabel: string; timestamp: number; merkleRoot: string; hash: string; filename: string; messageType: number; chunkIndex: number; mimeType: string; leafHash: string; realLen: number; totalSize: number; data: ArrayBuffer; /** Text stays in IndexedDB; files use the OPFS-first receive path. */ storage: "indexeddb" | "opfs"; } export interface ExpiredReceiveTransfer { merkleRoot: string; roomId: string; fromPeerId: string; } export interface ReceiveDeleteGuard { roomId: string; fromPeerId: string; incompleteOnly: true; } export interface ReceiveChunkStoreResult { /** True only when this call inserted a previously absent chunk index. */ stored: boolean; /** Sum of distinct committed realLen values for this Merkle root. */ savedSize: number; /** Authoritative distinct-byte completion after this transaction. */ complete: boolean; } export interface ChunkLeafHash { chunkIndex: number; leafHash?: string; } export interface NewChunk { /** Random 32-byte lowercase-hex identity of this logical outbound send. */ transferId: string; hash: string; chunkIndex: number; merkleRoot: string; /** Domain-separated Merkle leaf hash SHA-512(0x00 || paddedChunk). */ leafHash: string; /** SHA-512(receipt-domain || root || u64(index) || leafHash), once rooted. */ receiptToken: string; data: ArrayBuffer; metadata: ArrayBuffer; merkleProof: ArrayBuffer; } export interface NewChunkSelector { transferId?: string; merkleRootHex?: string; hashHex?: string; chunkIndex?: number; } export interface SendQueue { position: number; label: string; toPeerId: string; encryptedData: ArrayBuffer; } export interface RatchetSession { /** * Mandatory identity-possession root: interactive 3DH + ML-KEM-768 in every * room, with draft-21 CPace ISK additionally mixed for PIN rooms. */ rootSuite: RatchetRootSuite; roomId: string; peerPublicKey: string; peerId: string; rootKey: ArrayBuffer; sendingChainKey: ArrayBuffer | null; receivingChainKey: ArrayBuffer | null; dhSelfPub: ArrayBuffer; dhSelfSec: ArrayBuffer; dhRemotePub: ArrayBuffer | null; Ns: number; Nr: number; PN: number; skippedMessageKeys: Array<{ dhPub: ArrayBuffer; n: number; messageKey: ArrayBuffer; }>; /** * Protocol-v4 edge checkpoint. The plaintext API value is a bounded, * canonical snapshot containing the sparse-PQ machine, exact sealed control * outbox/replay cache, and epoch-bound active receive keys. ratchetWrap wraps * this whole blob as one variable-length secret field before it reaches disk. * * `null` exists only for store-free/legacy test fixtures. A live v4 edge * always persists a non-null checkpoint in the same row as the Double * Ratchet, so the two ratchets cannot cross a crash boundary independently. */ edgeCryptoState: ArrayBuffer | null; updatedAt: number; } export interface IdentityX25519 { pub: ArrayBuffer; secret: ArrayBuffer; crossSig: ArrayBuffer; } export interface StoredIdentityX25519 { pub: ArrayBuffer; wrappedSecret: ArrayBuffer; crossSig: ArrayBuffer; } /** Ed25519 account key material accepted by the worker write boundary. */ export interface IdentityEd25519KeyMaterial { pub: ArrayBuffer; secret: ArrayBuffer; } /** Ed25519 account identity returned by the worker read boundary. */ export interface IdentityEd25519 extends IdentityEd25519KeyMaterial { /** Persistent, non-reused uint64 revision allocated by IndexedDB. */ recordRevision: bigint; } /** At-rest Ed25519 record (meta key "identityEd25519"). */ export interface StoredIdentityEd25519 { version: 1; pub: ArrayBuffer; wrappedSecret: ArrayBuffer; recordRevision: bigint; } /** Persistent Ed25519 revision high-water/tombstone record in the meta store. */ export interface StoredIdentityEd25519Revision { version: 1; lastRevision: bigint; } /** * How the current Ed25519 account identity came into being. "mnemonic" means it * was derived from a recovery phrase and can therefore be reproduced from one; * "random" means it was generated from a random seed and is unrecoverable. * Non-secret: it says nothing about the phrase or the key, only about whether a * backup is possible at all. */ export type IdentityDerivation = "random" | "mnemonic"; /** At-rest derivation record (meta key "identityDerivation"). */ export interface StoredIdentityDerivation { version: 1; derivation: IdentityDerivation; /** * The Ed25519 public key this claim describes, when one was stored at the * time it was recorded. A flag whose binding no longer matches the stored * account key is reported as unknown rather than carried over: claiming * recoverability for a key that was never derived from a phrase would hand * the user a backup that restores someone else's identity. */ boundPub: ArrayBuffer | null; } /** Non-secret online PIN-guess throttle state for one room/peer-identity edge. */ export interface PinAttemptState { failures: number; retryAfter: number; } export type WorkerMessages = { id: number; method: "outboxRequest"; args: [request: OutboxRequest]; } | { id: number; method: "sweepStaleReceives"; args: []; } | { id: number; method: "sweepStaleReceiveDetails"; args: []; } | { id: number; method: "getDBAddressBookEntry"; args: [peerId?: string, peerPublicKey?: string]; } | { id: number; method: "getAllDBAddressBookEntries"; args: []; } | { id: number; method: "setDBAddressBookEntry"; args: [username: string, peerId: string, peerPublicKey: string]; } | { id: number; method: "deleteDBAddressBookEntry"; args: [username?: string, peerId?: string, peerPublicKey?: string]; } | { id: number; method: "getDBPeerIsBlacklisted"; args: [peerId?: string, peerPublicKey?: string]; } | { id: number; method: "getAllDBBlacklisted"; args: []; } | { id: number; method: "setDBPeerInBlacklist"; args: [peerId: string, peerPublicKey: string]; } | { id: number; method: "getAllDBUniqueRooms"; args: []; } | { id: number; method: "setDBUniqueRoom"; args: [roomUrl: string, roomId: string, roomPolicy: ArrayBuffer]; } | { id: number; method: "deleteDBPeerFromBlacklist"; args: [peerId?: string, peerPublicKey?: string]; } | { id: number; method: "getDBMessageData"; args: [merkleRootHex?: string, hashHex?: string]; } | { id: number; method: "getDBRoomMessageData"; args: [roomId: string]; } | { id: number; method: "getDBRoomStats"; args: [roomId: string]; } | { id: number; method: "setDBRoomMessageData"; args: [ roomId: string, merkleRootHex: string, sha512Hex: string, fromPeerId: string, chunkSize: number, totalSize: number, messageType: number, filename: string, channelLabel: string, timestamp: number, transferId?: string ]; } | { id: number; method: "getDBChunk"; args: [merkleRootHex: string, chunkIndex: number]; } | { id: number; method: "existsDBChunk"; args: [merkleRootHex: string, chunkIndex: number]; } | { id: number; method: "getDBNewChunk"; args: [transferId: string, chunkIndex: number]; } | { id: number; method: "getDBNewChunkByReceipt"; args: [merkleRootHex: string, receiptTokenHex: string]; } | { id: number; method: "existsDBNewChunk"; args: [transferId: string, chunkIndex: number]; } | { id: number; method: "getDBSendQueue"; args: [label: string, toPeerId: string, position?: number]; } | { id: number; method: "getDBAllChunks"; args: [merkleRootHex?: string, hashHex?: string]; } | { id: number; method: "getDBAllChunkLeafHashes"; args: [merkleRootHex: string]; } | { id: number; method: "assembleToOPFS"; args: [ merkleRootHex: string, totalSize: number, filename: string, mimeType: string ]; } | { id: number; method: "getDBAllChunksCount"; args: [merkleRootHex?: string, hashHex?: string]; } | { id: number; method: "setDBChunk"; args: [chunk: Chunk]; } | { id: number; method: "storeReceiveChunk"; args: [chunk: ReceiveChunk]; } | { id: number; method: "getReceiveFile"; args: [ merkleRootHex: string, totalSize: number, filename: string, mimeType: string ]; } | { id: number; method: "closeReceiveFile"; args: [merkleRootHex: string]; } | { id: number; method: "deleteReceiveTransfer"; args: [merkleRootHex: string, guard?: ReceiveDeleteGuard]; } | { id: number; method: "getDBAllNewChunks"; args: [selector: NewChunkSelector]; } | { id: number; method: "getDBAllNewChunksCount"; args: [transferId: string]; } | { id: number; method: "setDBNewChunk"; args: [chunk: NewChunk, lease?: OutboxLease]; } | { id: number; method: "setDBSendQueue"; args: [item: SendQueue]; } | { id: number; method: "countDBSendQueue"; args: [label: string, toPeerId: string]; } | { id: number; method: "deleteDBChunk"; args: [hashHex: string, chunkIndex?: number]; } | { id: number; method: "deleteDBNewChunk"; args: [selector: NewChunkSelector]; } | { id: number; method: "deleteDBMessageData"; args: [merkleRootHex: string]; } | { id: number; method: "deleteDBSendQueue"; args: [label: string, toPeerId: string, position?: number]; } | { id: number; method: "deleteDBUniqueRoom"; args: [roomId: string]; } | { id: number; method: "getRatchetSession"; args: [roomId: string, peerPublicKey: string]; } | { id: number; method: "setRatchetSession"; args: [session: RatchetSession]; } | { id: number; method: "deleteRatchetSession"; args: [roomId: string, peerPublicKey: string]; } | { id: number; method: "getPinAttemptState"; args: [roomId: string, peerIdentityEd25519: string]; } | { id: number; method: "incrementPinAttemptState"; args: [ roomId: string, peerIdentityEd25519: string, now: number, maxImmediateAttempts: number, baseMs: number, maxMs: number ]; } | { id: number; method: "deletePinAttemptState"; args: [roomId: string, peerIdentityEd25519?: string]; } | { id: number; method: "getIdentityX25519"; args: []; } | { id: number; method: "setIdentityX25519"; args: [identity: IdentityX25519]; } | { id: number; method: "deleteIdentityX25519"; args: []; } | { id: number; method: "getIdentityEd25519"; args: []; } | { id: number; method: "setIdentityEd25519"; args: [identity: IdentityEd25519KeyMaterial]; } | { id: number; method: "deleteIdentityEd25519"; args: []; } | { id: number; method: "getIdentityDerivation"; args: []; } | { id: number; method: "setIdentityDerivation"; args: [derivation: IdentityDerivation]; } | { id: number; method: "deleteIdentityDerivation"; args: []; } | { id: number; method: "deleteDB"; args: []; }; export interface WorkerMethodReturnTypes { outboxRequest: OutboxResponse; sweepStaleReceives: string[]; sweepStaleReceiveDetails: ExpiredReceiveTransfer[]; getDBAddressBookEntry: UsernamedPeer | undefined; getAllDBAddressBookEntries: UsernamedPeer[]; setDBAddressBookEntry: undefined; deleteDBAddressBookEntry: string; getDBPeerIsBlacklisted: boolean; getAllDBBlacklisted: BlacklistedPeer[]; setDBPeerInBlacklist: undefined; getAllDBUniqueRooms: UniqueRoom[]; setDBUniqueRoom: undefined; deleteDBPeerFromBlacklist: undefined; getDBMessageData: MessageData | undefined; getDBRoomMessageData: MessageData[]; getDBRoomStats: RoomStats; getDBChunk: ArrayBuffer | undefined; existsDBChunk: boolean; getDBNewChunk: NewChunk | undefined; getDBNewChunkByReceipt: NewChunk | undefined; existsDBNewChunk: boolean; getDBSendQueue: SendQueue[]; getDBAllChunks: Chunk[]; getDBAllChunkLeafHashes: ChunkLeafHash[]; assembleToOPFS: File | null; getDBAllChunksCount: number; setDBChunk: undefined; storeReceiveChunk: ReceiveChunkStoreResult; getReceiveFile: File | null; closeReceiveFile: undefined; deleteReceiveTransfer: boolean | undefined; getDBAllNewChunks: NewChunk[]; getDBAllNewChunksCount: number; setDBNewChunk: undefined; setDBRoomMessageData: undefined; setDBSendQueue: undefined; countDBSendQueue: number; deleteDBChunk: undefined; deleteDBNewChunk: undefined; deleteDBMessageData: undefined; deleteDBSendQueue: undefined; deleteDBUniqueRoom: undefined; getRatchetSession: RatchetSession | undefined; setRatchetSession: undefined; deleteRatchetSession: undefined; getPinAttemptState: PinAttemptState | undefined; incrementPinAttemptState: PinAttemptState; deletePinAttemptState: undefined; getIdentityX25519: IdentityX25519 | undefined; setIdentityX25519: undefined; deleteIdentityX25519: undefined; getIdentityEd25519: IdentityEd25519 | undefined; setIdentityEd25519: bigint; deleteIdentityEd25519: undefined; getIdentityDerivation: IdentityDerivation | null; setIdentityDerivation: undefined; deleteIdentityDerivation: undefined; deleteDB: undefined; }