Sign in to any app with a chain-verified DID — the SSO showcase · content/guides/digital-identity-login.md
Digital Identity Login — sign in to any app with QALS
The showcase: one identity you own, proving who you are to any app — no password shared, nothing to breach. Verified live.
✓ verified against the live stack, 2026-09-12 — the full challenge flow below was executed against the running qalid service (:8838): challenge issued → wallet-signed → verified: true → replay refused. DID did:qal:gsg created and resolved with chain anchor PASS (tx Bjb5YN2fjCZywsCL…).
The problem with logins today
Every app wants its own password. You reuse them (bad) or manage a vault (work). The app stores something that, if leaked, is you. And for AI agents — which increasingly do the logging-in — there is no login form at all.
How QALS login works (the qalid challenge flow)
A DID (decentralised identifier — a self-owned ID card whose proof is cryptography, not a password) looks like did:qal:alice. Its document is signed with your identity key and anchored on the Qalnet chain — a tampered copy is caught in one command, because resolution re-checks the anchor.
Login is one challenge-response round:
┌──────────┐ 1. "prove you're did:qal:gsg" ┌──────────┐
│ app │ ────────────────────────────────► │ wallet │
│ (tradez) │ (a random nonce, 5-min TTL) │ (yours) │
│ │ ◄──────────────────────────────── └──────────┘
│ │ 2. signature over qalid-prove-v1|<did>|<nonce>
│ │ 3. app resolves the DID: signature valid AND
│ chain anchor PASS → logged in
└──────────┘
The app never receives a secret. It receives a proof tied to that one nonce — worthless for replay, worthless anywhere else.
Do it yourself (real commands, real output)
# 1. the app asks for a challenge
CH=$(curl -s -X POST http://127.0.0.1:8838/prove/gsg \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["challenge"])')
# challenge=fcbc8dbe61fc7315b5fa25f2ff4da547
# 2. your wallet signs it
SIG=$(python3 qalid/qalid.py prove gsg "$CH" 2>/dev/null \
| python3 -c 'import json,sys;print(json.load(sys.stdin)["sig"])')
# 3. the app verifies — against the CHAIN-verified key, with no keys of its own
curl -s -X POST http://127.0.0.1:8838/prove/gsg/verify \
-d "{\"challenge\":\"$CH\",\"sig\":\"$SIG\"}"
# → {"verified": true, "did": "did:qal:gsg"}
# replay the same proof a second later:
# → {"verified": false, "error": "challenge unknown/expired"}
Setting up your DID first is one command: python3 qalid/qalid.py create <handle> (signs + anchors; exit codes: 0 verified · 2 tampered · 3 bad signature · 4 not found). Your DID doc even carries your qollCode, so your identity and your chat handle are provably the same actor.
What apps get (and what you keep)
| The app gets | You keep |
|---|---|
| A verified identity — signature valid and chain anchor PASS, every time, no password DB to defend | Your secret. It never leaves your device; the app only ever saw one nonce-bound signature |
| Spend-cap scoping — the login can carry a per-agent ledger key with a hard daily cap, so "logged in" ≠ "can drain the wallet" | A kill switch: qalid.py revoke marks the DID REVOKED in ~2 s; every resolver refuses its proofs |
| Revocable, attributable access — agent logins are controller-signed DIDs; every action is attributable to an anchored identity | Your email address. Nothing to phish, nothing to reset, no cross-app tracking via a shared identifier |
Worked example — tradez.au and goetica.ai
Both sites keep the logins they already have for accounts and billing; the Qal layer binds value and agents:
- tradez.au (dispute resolution): a customer's workspace binds to their Qal handle (qalclaim binding, per the identity architecture §5). When tradez's mediation agent needs to move escrowed funds or sign a settlement receipt, it authenticates as its own
did:qal:tradez-bot— challenge → sign → verify — with its ledger key capped per day. A compromised bot hits the cap; a fired bot is revoked in ~2 seconds. Customers who pay get a claim link and pull credit into their own wallet — tradez never holds customer balances. - goetica.ai (AI character rigs): rigs are compute providers. Each rig is a device DID (
did:qal:goetica--rig7, controller = the goetica org DID, optionally tailscale-bound so "the machine on the network" and "the identity in the ledger" must agree). Jobs pay through escrowed holds; the rig signs its metered log with the same challenge machinery. One rig misbehaving → dispute → stake slashed, reputation drops, and at rep < 40 it is delisted — the marketplace polices itself.
The pattern, one line: apps keep their logins; the value layer binds to a Qal handle; every non-human actor has a controller; every key is derived, bounded, revocable.
Honest scope & upgrade paths
- v0 today:
did:qaldocuments, challenge auth, HTTP resolver, human/agent/device types with controller signatures. Not yet: key rotation (re-anchor today), revocation registries as status lists, capability VCs. - Revocation upgrade: BitstringStatusList objects anchored the same way everything else is — flip a bit, verifiable by anyone resolving the DID.
- Selective disclosure (KYC tiers): SD-JWT credentials so you prove "over 18" without showing your birthdate — planned upgrade, design doc §5.6.
- Government interoperability (EUDI): the documented Phase-C item — when the EU Digital Identity Wallet lands, Qal DIDs are positioned to interop rather than replace it; identity aggregation with government eID is deliberately not in the stack yet.
Where to see it for real
bash qalid/test_qalid.sh # 17-assertion live-chain suite
python3 qalid/qalid.py list # every DID on this machine
curl http://127.0.0.1:8838/did/<handle> # resolve incl. chain check
Companions: login-and-accounts (the three layers) · qalid reference · identity architecture · agent pay loop.