feat(server,platform): retain and expose session identity material - #403
Open
johnthecat wants to merge 2 commits into
Open
feat(server,platform): retain and expose session identity material#403johnthecat wants to merge 2 commits into
johnthecat wants to merge 2 commits into
Conversation
The v2 pairing handshake carries three values the core consumed and then discarded: the identity chat X25519 private key, the answering device's encryption public key, and the peer's statement account id. A host adopting core pairing had no way to reach them, so it had to keep a second duplicate pairing alive purely to harvest them. Retain rather than derive. The wallet chooses the chat key and every peer already addresses that identity by the matching public key, so a value derived host-side would name an identity nobody can reach. The pairing host provably cannot recompute it either: the derivation needs raw BIP-39 entropy, which only a signing host holds. Split the read paths by sensitivity. Public material rides SessionUiInfo (chat_public_key, device_enc_public_key, peer_statement_account_id) so a host that only addresses the identity never touches a secret. The secrets sit behind explicit CoreAdmin calls, because SessionUiInfo rides every AuthState broadcast to all registered AuthPresenters and would push them to hosts that never asked. Both secrets are reachable over wasm and UniFFI. Give the responder its own device encryption key. It previously advertised the SSO channel key as device_enc_pub_key, which makes every device sharing an identity indistinguishable. The key is now random and persisted under CoreStorageKey::DeviceEncryptionKey, matching the provider, and the CLI host anchors that slot to its bootstrap directory so a user switch cannot regenerate the identity peers address this install by. Verified field-for-field against the Android provider and the host-papp consumer: the handshake layout, the X25519 basepoint derivation of the chat public key, and the statement-signer origin of the peer account id all match. SessionInfo gains fields mid-struct, so its SCALE layout changes. The blob is unversioned and decode rejects short input, so sessions persisted by an earlier build no longer decode and those users re-pair once. Refs #384 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Move the device-key serialization guard from SigningHost onto RuntimeServices and bundle it with the read. The key is reachable from both roles now, so a guard owned by one of them left the CoreAdmin and pairing-runtime paths free to race a first-time read against pairing, generate a second secret, and overwrite the one peers were told to address. `device_encryption_secret` is the only way to reach the key, so no caller can forget to hold it. Reject instead of resolving with an empty key when the worker host runtime is disposed. Callers encrypt with whatever comes back, so a zero-length secret is the one answer that must never be handed out.
johnthecat
force-pushed
the
feat/session-identity-material
branch
from
August 14, 2026 16:01
9017396 to
8b8d333
Compare
johnthecat
enabled auto-merge
August 14, 2026 16:49
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
The v2 pairing handshake carries three values the core consumed and then discarded: the identity chat X25519 private key, the answering device's encryption public key, and the peer statement account id. A host adopting core pairing could not reach any of them, so the only workaround was keeping a second duplicate pairing alive purely to harvest them — the user pairs twice and two sessions exist for one identity.
Refs #384
Solution
Retain, don't derive. The wallet chooses the chat key and peers already address that identity by its public half, so a host-side derivation would name an identity nobody can reach. The pairing host cannot recompute it regardless: the derivation needs raw BIP-39 entropy that only a signing host holds.
Read paths split by sensitivity:
SessionUiInfo(chat_public_key,device_enc_public_key,peer_statement_account_id), so a host that only addresses the identity never touches a secret.CoreAdmincalls, becauseSessionUiInforides everyAuthStatebroadcast to all registeredAuthPresenters. Both are reachable over wasm and UniFFI.The responder also gets its own random, persisted device encryption key. It previously advertised the SSO channel key, which makes every device sharing an identity indistinguishable.
Verified field-for-field against the Android provider and the
host-pappconsumer: handshake layout, the X25519 basepoint derivation of the chat public key, and the statement-signer origin of the peer account id all match.Why two fields touch 26 files
SessionInfoandSessionUiInfoare contracts, not plain structs:SessionInfois the persistedAuthSessionSCALE blob. Field order is the storage layout.SessionUiInfocrosses three bindings that must agree byte-for-byte — SCALE, generated TS, and the UniFFI Swift converters. Adding a field regenerates the codegen golden, the TS client, and the Swiftread/writesequences; a mismatch there silently corrupts everyAuthState::Connectedcrossing the FFI.CoreAdminmethod is codegen'd into the TS interface, which then needs hand-written plumbing across the worker protocol, the worker runtime, and the main-thread provider, plus a wasm export and a UniFFI export.device_enc_public_keywas meaningless until the responder stopped aliasing it to the SSO key, which pulled in a newCoreStorageKeyand install-scoped storage in the CLI host.Upgrade note
SessionInfogains fields mid-struct and the blob is unversioned, so sessions persisted by an earlier build no longer decode. Existing users re-pair once.