release(v4.11.0): streaming Double-Ratchet sub-sessions (ShadeStream)
Some checks failed
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Test / test (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Some checks failed
Cross-platform vectors / TypeScript vectors (bun) (push) Has been cancelled
Cross-platform vectors / Kotlin vectors (gradle) (push) Has been cancelled
Test / test (push) Has been cancelled
Docker build and publish / docker (push) Has been cancelled
Publish / publish (push) Has been cancelled
Answers Vyvern FR shade-ws-streaming-ratchet.md with a first-class
streaming-session API rather than the documented-contract fallback.
The Double-Ratchet crypto was already safe for high-frequency
one-directional use; the send/receive wrapper was not (per-frame
saveSession keystore write; shared per-peer mutex + single stored
session row coupling reuse to the HTTP path).
- @shade/core: stream.ts — identity-bound 3-DH seeding (X3DH-minus-
prekeys, no prekey-server round trip, mutually authenticated against
the parent session's pinned identities), bootstrapStreamSession
reusing init{Sender,Receiver}Session verbatim, in-memory-only
StreamRatchet (own op-mutex, never persisted, zeroized on close).
beginStream/acceptStream on ShadeSessionManager; Stream{Closed,
Handshake}Error; stream.opened/closed events.
- @shade/proto: STREAM_OPEN/OPEN_ACK/FRAME wire (0x31/0x32/0x33),
additive; inspectEnvelopeType extended.
- @shade/sdk: Shade.openStream/acceptStream → ShadeStream
(handshakeFrame/handleHandshake/seal/open/close), transport-
agnostic, independent of encrypt/decrypt queues + parent session,
identical server (sqlite:) and browser (IndexedDB) — touches no
storage.
- Tests: 5000-frame one-directional burst (bounded skipped keys + FS
zeroize), parent-session independence, replay/rewind rejection,
mutual-auth, proto wire round-trips. Full suite green (1159 pass).
- docs/streaming-sessions.md (R1–R7 contract); SECURITY.md matrix rows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
54
CHANGELOG.md
54
CHANGELOG.md
@@ -5,6 +5,60 @@ All notable changes to Shade are documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [4.11.0] — 2026-05-15 — Streaming Double-Ratchet sub-sessions
|
||||
|
||||
Answers Vyvern FR `shade-ws-streaming-ratchet.md` (the last Phase-2
|
||||
blocker) with a first-class streaming-session API rather than the
|
||||
"documented contract" fallback: the Double-Ratchet crypto was already
|
||||
safe for high-frequency one-directional use; the `send`/`receive`
|
||||
*wrapper* was not (a `saveSession` keystore write per frame; a shared
|
||||
per-peer mutex + single stored session row coupling any reuse to the
|
||||
HTTP path). `ShadeStream` keeps the proven ratchet and fixes the
|
||||
wrapper.
|
||||
|
||||
**`@shade/core`**
|
||||
|
||||
- New `stream.ts`: `deriveStreamRootKey` (identity-bound 3-DH —
|
||||
X3DH-minus-prekeys, no prekey-server round trip; mutually
|
||||
authenticated against the parent session's already-pinned
|
||||
identities), `bootstrapStreamSession` (reuses
|
||||
`initSenderSession`/`initReceiverSession` verbatim), and
|
||||
`StreamRatchet` — an in-memory `seal`/`open`/`close` holder on its
|
||||
own op-mutex, **never persisted**, zeroized on close.
|
||||
- `ShadeSessionManager.beginStream` / `acceptStream` custody the
|
||||
identity keys for the handshake without exposing private material;
|
||||
both require an established parent session (no first-contact).
|
||||
- New `StreamClosedError` / `StreamHandshakeError`; `stream.opened` /
|
||||
`stream.closed` events.
|
||||
|
||||
**`@shade/proto`**
|
||||
|
||||
- Wire types `STREAM_OPEN` (0x31), `STREAM_OPEN_ACK` (0x32),
|
||||
`STREAM_FRAME` (0x33) with encode/decode + `inspectEnvelopeType`
|
||||
extension. A `STREAM_FRAME` carries one ratchet message via the exact
|
||||
inner codec the HTTP path uses — one sealed frame ⇒ one WS frame.
|
||||
|
||||
**`@shade/sdk`**
|
||||
|
||||
- `Shade.openStream(peer)` / `Shade.acceptStream(peer, openBytes)`
|
||||
returning `ShadeStream` (`handshakeFrame` / `handleHandshake` /
|
||||
`seal` / `open` / `close`). Transport-agnostic like `send`/`receive`;
|
||||
auto-establishes the parent session if missing. Independent of the
|
||||
per-peer encrypt/decrypt queues and the stored parent session (R5).
|
||||
Identical on the `sqlite:` server build and the IndexedDB browser
|
||||
build (R4) — it touches no storage at all.
|
||||
|
||||
**Security / perf**
|
||||
|
||||
- Per-frame cost is exactly one symmetric KDF + one AES-GCM (no
|
||||
keystore I/O) — strictly better than the budgeted "doubled CPU".
|
||||
In-memory-only is a forward-secrecy property, not a shortcut; a
|
||||
dropped stream is re-opened, never resumed.
|
||||
- New `docs/streaming-sessions.md` (full R1–R7 contract); SECURITY.md
|
||||
threat-matrix rows added with tests
|
||||
(`packages/shade-core/tests/stream.test.ts`,
|
||||
`packages/shade-proto/tests/stream-wire.test.ts`).
|
||||
|
||||
## [Unreleased — 2026-05-09] — Android: V4.9/V4.10 ports + KeystoreStorage adapter
|
||||
|
||||
The Kotlin side of the v4.10 cross-host approval routing FR. With this
|
||||
|
||||
Reference in New Issue
Block a user