Phone identity, chats-on-ledger, efficiency, the encryption stack · content/goetica_phone_integration.md
goetica_phone × QALS — identification, chat-on-ledger, efficiency, encryption
Date: 2026-09-11 · Target: ~/projects/goetica_phone (PWA + native shell plan) · Verified against: its live qalnet/gateway.py (:8961) and the frozen chat contract.
1. Can the phone app be identified with the qal-coin ID system? — Yes, already wired
The phone's gateway implements exactly the right pattern (qalnet/gateway.py role 1):
phone user → GET gateway /did/<handle> → qalid :8838 resolve (anchored did:qal:<handle>)
→ gateway /prove challenge → qalid prove/verify (ed25519 challenge sig)
→ app session carries the VERIFIED DID + qoll-code
- The secret never lives in the client bundle (their AGENTS.md hard rule — enforced by the gateway being the only signer); the phone proves through the gateway, which holds nothing but short-lived challenges.
- Identity v2 fits directly: the phone user is a
subject_type: humanDID; the goetica platform service is acontroller-bound DID; companion "agents" (the characters) can be registered as agent-type DIDs under the platform's control — every actor in the chat stack gets a typed, revocable identity. - The gateway binds 127.0.0.1 by default and only exposes the tailnet with
QALNET_TOKEN— same law as qalbridge. Correct.
2. Can the platform handle all AI chats "on the tangle"? — Yes, because chats don't ride the chain — commitments do (by design)
The honest architecture (already implemented as gateway role 2): - Plaintext chats never touch the chain. Transport = the chat backends (hosted Worker / local platform); display truth stays server-side per-participant. - Every session lands on Qalnet as a commitment: server turns fetched → canonicalised → Merkle-rooted → one DataAnchor per session (or per batch). The ledger proves "this conversation looked like this at time T" — tamper-evident, order-provable, cheap. - Capacity is a non-issue at chat scale: an anchor is ~one small object regardless of conversation size. Even 10k sessions/day is trivial object volume for the chain; the constraint is the anchoring service's throughput (one CLI call each, ~5s) — batchable to Merkle-roots-per-minute if ever needed. The efficiency answer: anchoring cost is O(sessions), not O(messages); a thousand-message conversation costs exactly one anchor.
3. Are chats in/out per ID easy to work? — Yes — the contract is already frozen and reconciled
- The hosted Worker speaks the platform contract (
/api/chat+ ndjson streaming,/api/sessions/:sid, free-then-credits metering by qal account) — v0.4 reconciled per their README. - Per-ID flow: sign in with qal ID → sessions keyed to the account → each session's server-turns anchorable → metering bills the account's credit (rate canon: 1 loopd cent = 10 goetica credits, never re-rate).
- In/out operations = HTTP against the frozen contract; the gateway adds the three qalcoin roles without changing the chat path. Multi-device reconcile v2 is the one open item on their board (server-side JSONL history exists; true sync pending).
4. How does the encryption protocol work? — the full stack, precisely
Transport: Tailscale/WireGuard between machines; HTTPS to the hosted tier; nothing plaintext crosses networks unnecessarily.
Message layer (qalchat protocol — the same machinery the phone can adopt for peer DMs):
1. Identity keys every handle holds X25519 (key-agreement) + ed25519 (signing),
derived from the 12/24-word seed (qal-derivation v1) or generated.
2. Session key X25519 ECDH(sender, recipient) → HKDF-SHA256 → root key per pair.
3. Per-message root + counter → HKDF → unique message key (symmetric RATCHET:
key separation per message; full Signal-style FS is the MLS upgrade,
documented honestly).
4. Encrypt AES-256-GCM; AAD binds conversation-id + counter (no cross-convo
replay, no reordering).
5. Sign ed25519 over the envelope (sender authenticity — the relay can't
forge).
6. Chain h_i = sha256(h_{i-1} || ciphertext) — tamper/deletion evidence
even before anchoring.
7. Trust on first contact: relay keyserver + **QR invite PINNING** (key substitution →
refused, exit 4, zero envelope leakage) + server-side
proof-of-possession at registration.
8. Files 256KiB chunks, per-chunk keys (AAD-bound), ciphertext-hash +
plaintext-hash + whole-file sha256 verification, auto-anchored.
9. Anchor conversation Merkle roots / file hashes → DataAnchor on Qalnet.
What the chain adds: public tamper-evidence (anyone verifies a conversation/file was exactly X at time T). What it never sees: plaintext, metadata beyond hashes. The relay is ciphertext-blind by construction — proven by byte-level dump tests.
For goetica chats specifically: companion conversations ride the platform's own auth (account sessions); the ledger layer anchors commitments (role 2) and meters credits (role 3, hold→settle escrow). If peer-DM encryption between users is wanted later, the qalchat protocol drops in unchanged — it's the same identity substrate.
Verdict
The phone project's instincts were right and its v0 gateway already implements the correct three-role pattern. Nothing needs redesign — the next steps are operational: pin the gateway's token strategy, enable tailnet binding, and adopt the identity-v2 types (human/platform/agent DIDs) for the full actor stack.