- Added a new method to reset the onboarding state, allowing users to re-initiate the onboarding process. - Integrated backup creation functionality, enabling users to create encrypted backups of their node identity. - Updated API endpoints to handle onboarding reset and backup creation requests. - Enhanced UI components to support the new onboarding reset and backup features, including error handling and user feedback. - Introduced new dependencies for cryptographic operations and data encoding.
7.5 KiB
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)
- Format:
did:key:z<base58btc(multicodec + raw-public-key-bytes)>✅ We do this - DID Document: Can be derived from the DID string; no registry. Libraries like
@digitalcredentials/did-method-keyexpand it. - Verification methods:
verificationMethod,authentication,assertionMethod,keyAgreement(X25519 derived),capabilityDelegation,capabilityInvocation - Key ID (kid): Typically
{did}#key-1or 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.
-
Backend: Add
node.signChallengeRPC- Input:
{ challenge: string }(nonce from frontend) - Output:
{ signature: string }(hex-encoded Ed25519 signature) - Uses
NodeIdentity::sign()withchallenge.as_bytes()
- Input:
-
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.
-
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)
-
kid / Key ID:
- Use
#key-1or full{did}#key-1in backup and state - Or follow did:key key IDs
- Use
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
-
Backend: Add
node.createBackupRPC- 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
- Input:
-
Backend: Add
node.restoreBackupRPC (for restore flow)- Input:
{ backupBlob, passphrase } - Decrypt, validate, write to identity dir
- Restart or reload identity
- Input:
-
Frontend (OnboardingBackup.vue):
- Call
node.createBackupinstead of building mock JSON locally - Download the real backup file
- Call
-
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.
-
DID Document endpoint:
GET /.well-known/did.jsonor/did/{did}- Resolve did:key to a full DID Document
- Include
verificationMethod,authentication,keyAgreement(X25519 from Ed25519) - Reference: did:key expansion
-
X25519 derivation: Add
curve25519-dalekor equivalent; derive X25519 pubkey from Ed25519 forkeyAgreement -
Web5/DWN: Ensure
web5-dwnanddid-walletuse 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 |