# DID Onboarding Flow: Assessment & Implementation Plan ## Executive Summary The current onboarding DID flow is **partially implemented** and has several significant gaps compared to the W3C DID protocol and Web5 expectations. The core `did:key` format is **correct**, but the user-facing flow includes mock/fake behavior and the backup/verify steps don't actually use the DID infrastructure. --- ## What We Have (Current State) ### ✅ Correct Implementation | Component | Status | Notes | |-----------|--------|-------| | **did:key format** | ✅ Correct | Ed25519 multicodec `0xed 0x01`, base58btc encoding, `z` prefix | | **Key generation** | ✅ Correct | Ed25519 via `ed25519_dalek`, persisted at `/var/lib/archipelago/identity/` | | **node.did RPC** | ✅ Correct | Returns `{ did, pubkey }` from server state | | **Identity persistence** | ✅ Correct | Key survives reboots, 0o600 permissions on Unix | | **Sign/verify primitives** | ✅ Present | `NodeIdentity::sign()`, `NodeIdentity::verify()` exist in Rust | ### ⚠️ Partial / Misleading Implementation | Component | Status | Issue | |-----------|--------|-------| | **OnboardingDid.vue** | ⚠️ Misleading copy | Says "Generate DID" but we *fetch* from server; key is created at first boot, not during onboarding | | **OnboardingVerify.vue** | ❌ Fake | Uses `generateMockSignature()` – random chars, no backend call. Doesn't prove DID control | | **OnboardingBackup.vue** | ❌ Non-functional | Backup is mock JSON with `{ did, kid }`; no encrypted key material; **restore is impossible** | | **kid usage** | ⚠️ Non-standard | We store `pubkey` as `kid`; proper did:key uses fragment like `#key-1` or `did:key:z...#key-1` | ### ❌ Missing | Component | Status | |-----------|--------| | **node.sign RPC** | Not exposed – backend can sign but no API | | **Challenge-sign flow** | No backend support for proof-of-control | | **Encrypted backup** | No real backup with key material or recovery path | | **DID Document endpoint** | Not exposed (optional for did:key – can be derived client-side) | | **keyAgreement / X25519** | Not derived – full DID Document would need Ed25519→X25519 for encryption | --- ## DID Protocol Requirements (W3C / Web5) ### did:key Method (W3C CCG) 1. **Format**: `did:key:z` ✅ We do this 2. **DID Document**: Can be derived from the DID string; no registry. Libraries like `@digitalcredentials/did-method-key` expand it. 3. **Verification methods**: `verificationMethod`, `authentication`, `assertionMethod`, `keyAgreement` (X25519 derived), `capabilityDelegation`, `capabilityInvocation` 4. **Key ID (kid)**: Typically `{did}#key-1` or similar fragment ### Proof of Control To prove control of a DID, you must **sign a challenge** with the private key. The verifier checks the signature against the public key in the DID. Our OnboardingVerify step claims to do this but **does not**. ### Backup / Recovery A proper identity backup for recovery would: - Include the private key (or encrypted key material) - Be encrypted with a user passphrase - Allow restore on a new device Our backup has none of this – it's display-only. --- ## Recommended Implementation Plan ### Phase 1: Fix Verify Step (Proof of Control) **Goal**: Replace the fake "Sign Challenge" with a real cryptographic proof. 1. **Backend**: Add `node.signChallenge` RPC - Input: `{ challenge: string }` (nonce from frontend) - Output: `{ signature: string }` (hex-encoded Ed25519 signature) - Uses `NodeIdentity::sign()` with `challenge.as_bytes()` 2. **Frontend (OnboardingVerify.vue)**: - Generate a random nonce (e.g. 32 bytes, base64) - Call `node.signChallenge({ challenge })` - Verify signature locally using the pubkey from `node.did` (optional – or trust server) - Display the real signature; remove `generateMockSignature()` **Effort**: ~2–4 hours --- ### Phase 2: Improve UX and Terminology **Goal**: Align copy and flow with actual behavior. 1. **OnboardingDid.vue**: - Change "Generate DID" → "Get your node's identity" or "Retrieve DID" - Clarify that the DID is created when the node first starts (not on button click) - Optionally auto-fetch on mount if identity exists (no button needed for returning state) 2. **kid / Key ID**: - Use `#key-1` or full `{did}#key-1` in backup and state - Or follow [did:key key IDs](https://www.w3.org/TR/did-core/#relative-did-urls) **Effort**: ~1–2 hours --- ### Phase 3: Real Backup (Encrypted Export) **Goal**: Backup that can actually be used for recovery. **Design choice**: The private key lives on the **server**. Two options: - **Option A (simpler)**: Backup is a signed, encrypted blob containing the key material. Restore requires: - Upload backup file - Enter passphrase - Server imports key and replaces current identity (or restores to same node) - **Option B (more self-sovereign)**: User can export key to their own wallet. Higher complexity and key-handling risk. **Recommended: Option A** 1. **Backend**: Add `node.createBackup` RPC - Input: `{ passphrase: string }` - Encrypt the raw key bytes (e.g. XChaCha20-Poly1305 or AES-256-GCM) with a key derived from passphrase (Argon2) - Return JSON: `{ version, did, backupBlob (base64), salt, ... }` or trigger download 2. **Backend**: Add `node.restoreBackup` RPC (for restore flow) - Input: `{ backupBlob, passphrase }` - Decrypt, validate, write to identity dir - Restart or reload identity 3. **Frontend (OnboardingBackup.vue)**: - Call `node.createBackup` instead of building mock JSON locally - Download the real backup file 4. **Restore flow**: Add a restore path (e.g. from login or onboarding options) that accepts backup file + passphrase and calls `node.restoreBackup` **Effort**: ~1–2 days (crypto, testing, edge cases) --- ### Phase 4: DID Document & Web5 Interop (Optional) **Goal**: Full compatibility with Web5 resolvers and DWN. 1. **DID Document endpoint**: `GET /.well-known/did.json` or `/did/{did}` - Resolve did:key to a full DID Document - Include `verificationMethod`, `authentication`, `keyAgreement` (X25519 from Ed25519) - Reference: [did:key expansion](https://github.com/digitalbazaar/did-method-key) 2. **X25519 derivation**: Add `curve25519-dalek` or equivalent; derive X25519 pubkey from Ed25519 for `keyAgreement` 3. **Web5/DWN**: Ensure `web5-dwn` and `did-wallet` use our node DID correctly for resolution and operations **Effort**: ~2–3 days --- ### Phase 5: DID as Authentication (Future) **Goal**: Use DID + proof instead of (or in addition to) password. - DID Auth / SIOP flow: prove control of DID via challenge-response - Could reduce or replace password for API access - Larger design and security review required **Effort**: TBD --- ## Priority Recommendation | Priority | Phase | Reason | |----------|-------|--------| | **P0** | Phase 1 (Verify) | Removes fake crypto; proves DID control | | **P1** | Phase 2 (UX) | Quick wins; honest representation of flow | | **P2** | Phase 3 (Backup) | Makes backup/restore actually useful | | **P3** | Phase 4 (DID Doc) | Needed for full Web5 interop | | **P4** | Phase 5 (DID Auth) | Longer-term identity architecture | --- ## Quick Reference: Current vs. Target | Step | Current | Target | |------|---------|--------| | DID fetch | `node.did` ✅ | Same, better UX | | Prove control | Fake random "signature" ❌ | Real `node.signChallenge` | | Backup | Mock JSON, no key ❌ | Encrypted key material + restore | | kid | Raw pubkey | `#key-1` or standard fragment | | Restore | Not possible | `node.restoreBackup` |