archy/docs/DID_ONBOARDING_ASSESSMENT.md
Dorian 62d6c13764 Implement onboarding reset functionality and enhance backup features
- 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.
2026-03-02 08:34:13 +00:00

7.5 KiB
Raw Blame History

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<base58btc(multicodec + raw-public-key-bytes)> 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.


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: ~24 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

Effort: ~12 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: ~12 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
  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: ~23 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