import type { IdentityKeyPair, SignedPreKey, OneTimePreKey, SessionState } from './types.js'; /** A retired identity kept in history during the rotation grace period */ export interface RetiredIdentity { keyPair: IdentityKeyPair; retiredAt: number; } /** * Persisted stream-transfer resume record. Holds enough state for either * side of a transfer to resume after restart. The `streamSecret` MUST be * encrypted before storage (see `@shade/transfer` for the deviceKey-based * AES-GCM at-rest scheme). */ export interface PersistedStreamState { streamId: string; direction: 'send' | 'receive'; peerAddress: string; status: 'active' | 'paused' | 'finished' | 'aborted'; /** JSON-serialized `StreamMetadata`. */ metadataJson: string; /** JSON-serialized `LaneInitSpec[]`. */ partitionJson: string; /** JSON-serialized per-lane progress array (laneId/nextSeq/bytesProcessed). */ laneStateJson: string; /** JSON-serialized I/O descriptor (file path / file handle reference / buffer). */ ioDescriptorJson: string; /** AES-GCM-encrypted streamSecret (under deviceKey). */ secretEnc: Uint8Array; /** AES-GCM nonce used for `secretEnc`. */ secretNonce: Uint8Array; /** * Reserved for future hasher serialization. Empty in v0.2.0; resume * re-hashes received bytes from disk. */ overallHashState?: string; createdAt: number; updatedAt: number; } /** * Why a peer's fingerprint was deemed verified. * - `user`: human did the side-channel comparison and confirmed. * - `transitive`: trust derived from another verified party (reserved for V3.10). * - `tofu-after-warning`: caller bypassed gate after seeing a warning. */ export type PeerVerificationSource = 'user' | 'transitive' | 'tofu-after-warning'; /** * Persisted broadcast-channel state (V4.6). Holds the sender-key chain that * lets the channel owner encrypt a single ciphertext and fan it out to all * paired members. * * - `ownerRole === 'sender'`: this device created the channel; the record * carries the full chain (chainKey + iteration + signing keypair). * - `ownerRole === 'receiver'`: this device joined a channel that * `ownerAddress` owns; we hold a tracking copy of the chain (chainKey + * iteration + signing public key only — no private signing key). * * `generation` is bumped each time `removeMember` rotates the chain. Stale * broadcasts at lower generations are silently dropped on receive. */ export interface BroadcastChannelRecord { /** Opaque, stable across restarts. UUID-style. */ channelId: string; ownerRole: 'sender' | 'receiver'; /** The address that owns the channel (i.e. the only sender). */ ownerAddress: string; label?: string; /** Sender-key generation — bumped on rotation. Starts at 0. */ generation: number; /** Current chain key (32 bytes). */ chainKey: Uint8Array; /** Counter advanced by `senderKeyEncrypt` / `senderKeyDecrypt`. */ iteration: number; /** Owner's Ed25519 signing public key (32 bytes). */ signingPublicKey: Uint8Array; /** Owner's Ed25519 signing private key. Present iff `ownerRole === 'sender'`. */ signingPrivateKey?: Uint8Array; createdAt: number; updatedAt: number; } /** Membership row for a broadcast channel (sender-side only). */ export interface BroadcastMemberRecord { channelId: string; peerAddress: string; joinedAt: number; /** When this peer was revoked. `null` while still active. */ removedAt: number | null; } /** * Persistent record that a peer's safety number was verified at a point * in time. `identityVersion` is the local counter for that peer's identity: * incrementing it (e.g. via `bumpPeerIdentityVersion` on `acceptIdentityChange`) * invalidates the saved verification because `isPeerVerified` requires the * stored version to equal the current version. */ export interface PeerVerification { peerAddress: string; fingerprint: string; verifiedAt: number; verifiedBy: PeerVerificationSource; identityVersion: number; } /** * StorageProvider — abstract interface for persisting cryptographic state. * * Implementations per platform: * - In-memory (testing) * - IndexedDB (browser) * - SQLite/PostgreSQL (server) * - EncryptedSharedPreferences (Android) */ export interface StorageProvider { // ─── Identity ────────────────────────────────────────────── /** Get our local identity keypair, or null if not yet generated */ getIdentityKeyPair(): Promise; /** Persist our local identity keypair */ saveIdentityKeyPair(keyPair: IdentityKeyPair): Promise; /** Get our local registration ID (unique per installation) */ getLocalRegistrationId(): Promise; /** Save our local registration ID */ saveLocalRegistrationId(id: number): Promise; // ─── Signed Pre-Keys ────────────────────────────────────── /** Get a signed prekey by ID */ getSignedPreKey(keyId: number): Promise; /** Persist a signed prekey */ saveSignedPreKey(key: SignedPreKey): Promise; /** Remove a signed prekey (after rotation grace period) */ removeSignedPreKey(keyId: number): Promise; // ─── One-Time Pre-Keys ──────────────────────────────────── /** Get a one-time prekey by ID */ getOneTimePreKey(keyId: number): Promise; /** Persist a one-time prekey */ saveOneTimePreKey(key: OneTimePreKey): Promise; /** Remove a consumed one-time prekey */ removeOneTimePreKey(keyId: number): Promise; /** Count remaining one-time prekeys */ getOneTimePreKeyCount(): Promise; // ─── Sessions ───────────────────────────────────────────── /** Get session state for a peer address (e.g. "device:abc123") */ getSession(address: string): Promise; /** Persist session state for a peer */ saveSession(address: string, state: SessionState): Promise; /** Remove session for a peer */ removeSession(address: string): Promise; /** Check if we trust a remote identity key (for TOFU or pinned keys) */ isTrustedIdentity(address: string, identityKey: Uint8Array): Promise; /** Save a trusted remote identity key */ saveTrustedIdentity(address: string, identityKey: Uint8Array): Promise; // ─── Identity History (rotation with grace period) ────── /** Add an identity to the retired history */ addRetiredIdentity(identity: RetiredIdentity): Promise; /** Get all retired identities (for grace-period decryption) */ getRetiredIdentities(): Promise; /** Remove retired identities older than the given timestamp */ pruneRetiredIdentities(olderThan: number): Promise; // ─── Peer verifications (V3.3) ──────────────────────────── /** * Persist or replace the verification record for a peer. Idempotent * upsert on `peerAddress`. */ savePeerVerification(verification: PeerVerification): Promise; /** Look up the saved verification for a peer (null if never verified). */ getPeerVerification(address: string): Promise; /** Remove a peer's verification record (e.g. user revoked trust). */ removePeerVerification(address: string): Promise; /** * Returns the current local identity-version counter for a peer. * Defaults to 1 when the peer has never been seen. Bumped by * `bumpPeerIdentityVersion` whenever the peer rotates identity. */ getPeerIdentityVersion(address: string): Promise; /** * Increment the peer's identity-version counter and return the new value. * Called from `acceptIdentityChange` so previous verification rows (which * carry the old version number) become stale. */ bumpPeerIdentityVersion(address: string): Promise; // ─── Stream-transfer resume state (optional, added in v0.2.0) ── /** * Persist or update the stream-state row for a given streamId. Idempotent: * upserts on `streamId`. Optional — providers that don't support resume * can omit this and consumers will fall back to in-memory state. */ saveStreamState?(state: PersistedStreamState): Promise; /** Look up the stream-state row by streamId. Returns null if absent. */ getStreamState?(streamId: string): Promise; /** Remove a stream-state row (e.g. on completion or abort). */ removeStreamState?(streamId: string): Promise; /** List active or paused stream-state rows (for resume on startup). */ listActiveStreamStates?( direction?: 'send' | 'receive', ): Promise; /** Prune stream-state rows in `'finished' | 'aborted'` status older than `olderThan`. */ pruneStreamStates?(olderThan: number): Promise; // ─── Broadcast channels (V4.6) — optional ───────────────── /** * Persist or replace the broadcast-channel record. Idempotent upsert on * `channelId`. Backends that don't implement broadcast-channel support * may omit this; the SDK throws a clear error when the app tries to * call `createBroadcastChannel` on such a backend. */ saveBroadcastChannel?(channel: BroadcastChannelRecord): Promise; /** Look up a broadcast channel by id. */ getBroadcastChannel?(channelId: string): Promise; /** Enumerate all broadcast channels persisted on this device. */ listBroadcastChannels?(): Promise; /** Drop a broadcast channel and all its membership rows. */ removeBroadcastChannel?(channelId: string): Promise; /** Persist or replace a broadcast-membership row (sender-side only). */ saveBroadcastMember?(member: BroadcastMemberRecord): Promise; /** * List membership rows for a channel. Includes revoked members (with * `removedAt !== null`); callers filter as needed. */ getBroadcastMembers?(channelId: string): Promise; /** Hard-delete a single membership row. */ removeBroadcastMember?(channelId: string, peerAddress: string): Promise; }