32B from initiateX3DH/completeX3DH.
Application-provided salt (e.g. chatId). MUST be identical on both sides. Does not need to be secret.
Direction role: 'initiator' or 'recipient'. Defaults to 'initiator'. Affects nothing cryptographically (X3DH is symmetric), but useful for application-side audit.
A Channel with epoch=0 and hidden internals. The object is brand-validated (D1) and frozen (D2) — all fields readonly.
Establishes a channel from an X3DH shared secret.
───────────────────────────────────────────────────────────── Key hierarchy ─────────────────────────────────────────────────────────────
X3DH sharedSecret (32B, raw) └─ HKDF(sharedSecret, salt=channelSalt, info='…/channel-root/v1') ├─ rootKeyMaterial (32B) — channel-local secret └─ channelId = SHA-256(sharedSecret ‖ salt) — stable across rotations
Why derive (not use X3DH secret directly)?
Key representation: