release(v4.6.0): broadcast channels — Signal sender-keys for one-to-many fan-out
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Some checks failed
Test / test (push) Has been cancelled
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Lands the broadcast-channel primitive Prism asked for in Docs/shade-feature-request-sender-keys.md. The crypto in @shade/core/sender-keys.ts was already in place; this release wires it up as a first-class app-facing API, adds the persistence schema across all six storage backends (memory, sqlite, indexeddb + encrypted variants), introduces wire type 0x21 in @shade/proto, and ships Prism's three acceptance tests verbatim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@shade/core",
|
||||
"version": "4.5.0",
|
||||
"version": "4.6.0",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
|
||||
@@ -46,6 +46,50 @@ export interface PersistedStreamState {
|
||||
*/
|
||||
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:
|
||||
@@ -188,4 +232,35 @@ export interface StorageProvider {
|
||||
|
||||
/** Prune stream-state rows in `'finished' | 'aborted'` status older than `olderThan`. */
|
||||
pruneStreamStates?(olderThan: number): Promise<void>;
|
||||
|
||||
// ─── 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<void>;
|
||||
|
||||
/** Look up a broadcast channel by id. */
|
||||
getBroadcastChannel?(channelId: string): Promise<BroadcastChannelRecord | null>;
|
||||
|
||||
/** Enumerate all broadcast channels persisted on this device. */
|
||||
listBroadcastChannels?(): Promise<BroadcastChannelRecord[]>;
|
||||
|
||||
/** Drop a broadcast channel and all its membership rows. */
|
||||
removeBroadcastChannel?(channelId: string): Promise<void>;
|
||||
|
||||
/** Persist or replace a broadcast-membership row (sender-side only). */
|
||||
saveBroadcastMember?(member: BroadcastMemberRecord): Promise<void>;
|
||||
|
||||
/**
|
||||
* List membership rows for a channel. Includes revoked members (with
|
||||
* `removedAt !== null`); callers filter as needed.
|
||||
*/
|
||||
getBroadcastMembers?(channelId: string): Promise<BroadcastMemberRecord[]>;
|
||||
|
||||
/** Hard-delete a single membership row. */
|
||||
removeBroadcastMember?(channelId: string, peerAddress: string): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user