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:
@@ -39,6 +39,7 @@ export const COL = {
|
||||
trustedIdentity: 'trusted_identity',
|
||||
retiredIdentity: 'retired_identity',
|
||||
streamSensitive: 'stream_sensitive',
|
||||
broadcastChannelSensitive: 'broadcast_channel_sensitive',
|
||||
} as const;
|
||||
|
||||
/** Logical table identifiers — used for fieldKey + AAD binding. */
|
||||
@@ -51,6 +52,7 @@ export const TBL = {
|
||||
trustedIdentities: 'trusted_identities',
|
||||
retiredIdentities: 'retired_identities',
|
||||
streamState: 'stream_state',
|
||||
broadcastChannels: 'broadcast_channels',
|
||||
} as const;
|
||||
|
||||
/** Encrypt an arbitrary string payload bound to (table, column, pk). */
|
||||
@@ -226,3 +228,76 @@ export async function openStreamSensitive(
|
||||
if (b.overallHashState !== undefined) (out as { overallHashState?: string }).overallHashState = b.overallHashState;
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast-channel sensitive bundle (V4.6). Routing fields (channelId,
|
||||
* ownerRole, ownerAddress, label, generation, createdAt, updatedAt) live
|
||||
* in plaintext columns so backends can list/query without unsealing every
|
||||
* row; the chain key, iteration, and signing keys all live in this sealed
|
||||
* blob.
|
||||
*/
|
||||
interface BroadcastChannelSensitiveBundle {
|
||||
chainKey: string; // base64(32B)
|
||||
iteration: number;
|
||||
signingPublicKey: string; // base64(32B)
|
||||
signingPrivateKey?: string; // base64; only when ownerRole === 'sender'
|
||||
}
|
||||
|
||||
export async function sealBroadcastChannelSensitive(
|
||||
km: KeyManager,
|
||||
channelId: string,
|
||||
s: {
|
||||
chainKey: Uint8Array;
|
||||
iteration: number;
|
||||
signingPublicKey: Uint8Array;
|
||||
signingPrivateKey?: Uint8Array;
|
||||
},
|
||||
): Promise<Uint8Array> {
|
||||
const bundle: BroadcastChannelSensitiveBundle = {
|
||||
chainKey: toBase64(s.chainKey),
|
||||
iteration: s.iteration,
|
||||
signingPublicKey: toBase64(s.signingPublicKey),
|
||||
};
|
||||
if (s.signingPrivateKey !== undefined) {
|
||||
bundle.signingPrivateKey = toBase64(s.signingPrivateKey);
|
||||
}
|
||||
return sealString(
|
||||
km,
|
||||
TBL.broadcastChannels,
|
||||
COL.broadcastChannelSensitive,
|
||||
channelId,
|
||||
JSON.stringify(bundle),
|
||||
);
|
||||
}
|
||||
|
||||
export async function openBroadcastChannelSensitive(
|
||||
km: KeyManager,
|
||||
channelId: string,
|
||||
blob: Uint8Array,
|
||||
): Promise<{
|
||||
chainKey: Uint8Array;
|
||||
iteration: number;
|
||||
signingPublicKey: Uint8Array;
|
||||
signingPrivateKey?: Uint8Array;
|
||||
}> {
|
||||
const json = await openString(
|
||||
km,
|
||||
TBL.broadcastChannels,
|
||||
COL.broadcastChannelSensitive,
|
||||
channelId,
|
||||
blob,
|
||||
);
|
||||
const b = JSON.parse(json) as BroadcastChannelSensitiveBundle;
|
||||
const out: {
|
||||
chainKey: Uint8Array;
|
||||
iteration: number;
|
||||
signingPublicKey: Uint8Array;
|
||||
signingPrivateKey?: Uint8Array;
|
||||
} = {
|
||||
chainKey: fromBase64(b.chainKey),
|
||||
iteration: b.iteration,
|
||||
signingPublicKey: fromBase64(b.signingPublicKey),
|
||||
};
|
||||
if (b.signingPrivateKey !== undefined) out.signingPrivateKey = fromBase64(b.signingPrivateKey);
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user