rspace-online/docs/ENCRYPTID-SPECIFICATION.md

37 KiB

EncryptID: Unified Identity System for the r-Ecosystem

Version 0.1 - February 2026


Executive Summary

EncryptID is a unified, self-sovereign identity system designed to work across all r-ecosystem applications (rspace.online, rwallet, rvote, rmaps, rfiles). It combines the security of hardware-backed authentication with the usability of passkeys, the flexibility of derived cryptographic keys, and the power of account abstraction smart wallets.

Core Principles:

  • No seed phrases - Social recovery replaces mnemonic backup
  • Hardware-backed security - WebAuthn/passkeys as the root of trust
  • Client-side encryption - Keys never leave the user's device
  • Cross-app identity - One login for all r-ecosystem apps
  • Web3 native - Integrated smart wallet for on-chain operations

Architecture Overview

+============================================================================+
||                           ENCRYPTID ARCHITECTURE                          ||
+============================================================================+

+----------------------------------------------------------------------------+
|                        LAYER 5: APPLICATION LAYER                          |
|                                                                            |
|   +-------------+  +-------------+  +-------------+  +-------------+       |
|   |   rspace    |  |   rwallet   |  |   rvote     |  |   rfiles    |       |
|   | (canvas)    |  | (treasury)  |  | (voting)    |  | (storage)   |       |
|   +------+------+  +------+------+  +------+------+  +------+------+       |
|          |                |                |                |              |
+----------|----------------|----------------|----------------|-------------+
           |                |                |                |
           v                v                v                v
+----------------------------------------------------------------------------+
|                      LAYER 4: SESSION & SSO LAYER                          |
|                                                                            |
|   +--------------------------------------------------------------------+   |
|   |                     EncryptID Session Service                      |   |
|   |                                                                    |   |
|   |  - JWT tokens (short-lived, refresh rotation)                      |   |
|   |  - Cross-app SSO via Related Origin Requests                       |   |
|   |  - Per-app capability scoping                                      |   |
|   |  - Session key caching for UX                                      |   |
|   +--------------------------------------------------------------------+   |
|                                                                            |
+--------------------------------+-------------------------------------------+
                                 |
                                 v
+----------------------------------------------------------------------------+
|                   LAYER 3: SMART WALLET LAYER (AA)                         |
|                                                                            |
|   +--------------------------------------------------------------------+   |
|   |              Account Abstraction Smart Wallet (ERC-4337)           |   |
|   |                                                                    |   |
|   |   +------------------+  +------------------+  +------------------+ |   |
|   |   |  Passkey Signer  |  | Session Keys    |  | Social Recovery  | |   |
|   |   |  (WebAuthn P256) |  | (time-limited)  |  | (Guardian Module)| |   |
|   |   +------------------+  +------------------+  +------------------+ |   |
|   |                                                                    |   |
|   |   Features:                                                        |   |
|   |   - Gasless transactions (paymaster)                               |   |
|   |   - Batched operations                                             |   |
|   |   - Spending limits & policies                                     |   |
|   |   - No seed phrase needed                                          |   |
|   +--------------------------------------------------------------------+   |
|                                                                            |
+--------------------------------+-------------------------------------------+
                                 |
                                 v
+----------------------------------------------------------------------------+
|                    LAYER 2: DERIVED KEYS LAYER                             |
|                                                                            |
|   +--------------------------------------------------------------------+   |
|   |                WebCrypto Key Derivation (HKDF)                     |   |
|   |                                                                    |   |
|   |   Source: WebAuthn PRF output OR passphrase-derived master key    |   |
|   |                                                                    |   |
|   |   +---------------+  +---------------+  +---------------+          |   |
|   |   | Encryption    |  | Signing       |  | DID           |          |   |
|   |   | Key (AES-256) |  | Key (P-256)   |  | Key (Ed25519) |          |   |
|   |   +-------+-------+  +-------+-------+  +-------+-------+          |   |
|   |           |                  |                  |                  |   |
|   |           v                  v                  v                  |   |
|   |   +---------------+  +---------------+  +---------------+          |   |
|   |   | rfiles E2E    |  | rvote ballot  |  | did:key:z6Mk  |          |   |
|   |   | rspace boards |  | signatures    |  | identity      |          |   |
|   |   +---------------+  +---------------+  +---------------+          |   |
|   +--------------------------------------------------------------------+   |
|                                                                            |
+--------------------------------+-------------------------------------------+
                                 |
                                 v
+----------------------------------------------------------------------------+
|                  LAYER 1: PRIMARY AUTHENTICATION                           |
|                                                                            |
|   +--------------------------------------------------------------------+   |
|   |                      WebAuthn / Passkeys                           |   |
|   |                                                                    |   |
|   |   +------------------+  +------------------+  +------------------+ |   |
|   |   | Platform Passkey |  | Security Key    |  | Cross-Device     | |   |
|   |   | (iCloud/Google)  |  | (YubiKey/Titan) |  | (QR/Bluetooth)   | |   |
|   |   +------------------+  +------------------+  +------------------+ |   |
|   |                                                                    |   |
|   |   Properties:                                                      |   |
|   |   - Phishing-resistant (origin-bound)                              |   |
|   |   - Hardware-backed (TPM/Secure Enclave)                           |   |
|   |   - Biometric (fingerprint/face)                                   |   |
|   |   - Syncs across devices (platform providers)                      |   |
|   +--------------------------------------------------------------------+   |
|                                                                            |
+============================================================================+

Layer 1: Primary Authentication (WebAuthn/Passkeys)

Why WebAuthn as the Foundation

Property Benefit
Origin-bound Credentials only work on the registered domain - phishing-resistant
Hardware-backed Private key stored in TPM/Secure Enclave - can't be extracted
Biometric Face/fingerprint verification - something you are
Synced Platform passkeys sync via iCloud Keychain, Google Password Manager
No passwords Nothing to remember, nothing to steal

Implementation

// EncryptID Registration Flow
async function registerEncryptID(username: string): Promise<EncryptIDCredential> {
  const challenge = await fetchChallenge('/api/encryptid/register/challenge');

  const credential = await navigator.credentials.create({
    publicKey: {
      challenge: base64ToBuffer(challenge),
      rp: {
        name: "EncryptID",
        id: "encryptid.online"  // Shared RP ID for all r-apps
      },
      user: {
        id: crypto.getRandomValues(new Uint8Array(32)),
        name: username,
        displayName: username
      },
      pubKeyCredParams: [
        { alg: -7, type: "public-key" },    // ES256 (P-256)
        { alg: -257, type: "public-key" }   // RS256 (fallback)
      ],
      authenticatorSelection: {
        residentKey: "required",           // Discoverable credential
        userVerification: "required",      // Biometric/PIN required
        authenticatorAttachment: "platform" // Prefer built-in authenticator
      },
      attestation: "none",                 // Privacy-preserving
      extensions: {
        prf: {                             // PRF extension for key derivation
          eval: {
            first: base64ToBuffer(await generateSalt("encryptid-master"))
          }
        },
        credProps: true                    // Get credential properties
      }
    }
  });

  return processRegistration(credential);
}

PRF Extension Support Status (2026)

Platform Browser PRF Support Notes
Windows Chrome/Edge Full TPM-backed
macOS Chrome/Safari Full Secure Enclave
Android Chrome Full TEE/StrongBox
iOS/iPadOS Safari Partial Works with iCloud Keychain passkeys
Linux Chrome Varies Depends on FIDO2 library

Fallback Strategy: For authenticators without PRF support, derive master key from a user-provided passphrase using Argon2id + HKDF.

Since r-apps span multiple domains, we use Related Origin Requests:

// File: https://encryptid.online/.well-known/webauthn
{
  "origins": [
    "https://rspace.online",
    "https://rwallet.online",
    "https://rvote.online",
    "https://rmaps.online",
    "https://rfiles.online",
    "https://app.rspace.online",
    "https://dev.rspace.online"
  ]
}

This allows a passkey registered with RP ID encryptid.online to authenticate on any of these origins.

Browser Support:

  • Chrome: Full support
  • Safari: Full support
  • Firefox: Under consideration (use iframe fallback)

Layer 2: Derived Keys (WebCrypto API)

Key Derivation Hierarchy

                    +---------------------------+
                    |   WebAuthn PRF Output     |
                    |   (32 bytes from HMAC)    |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    |       Master Key          |
                    |   (HKDF extract step)     |
                    +-------------+-------------+
                                  |
          +-----------------------+-----------------------+
          |                       |                       |
          v                       v                       v
+-------------------+   +-------------------+   +-------------------+
|   Encryption Key  |   |   Signing Key     |   |   DID Key         |
|   (AES-256-GCM)   |   |   (ECDSA P-256)   |   |   (Ed25519)       |
+-------------------+   +-------------------+   +-------------------+
        |                       |                       |
        v                       v                       v
+-------------------+   +-------------------+   +-------------------+
| rfiles encryption |   | rvote signatures  |   | did:key identity  |
| rspace E2E boards |   | rspace authorship |   | verifiable creds  |
+-------------------+   +-------------------+   +-------------------+

Implementation

// Key Derivation from PRF output
class EncryptIDKeyDerivation {
  private masterKey: CryptoKey;

  async initFromPRF(prfOutput: ArrayBuffer): Promise<void> {
    // Import PRF output as HKDF key material
    this.masterKey = await crypto.subtle.importKey(
      "raw",
      prfOutput,
      { name: "HKDF" },
      false,
      ["deriveKey", "deriveBits"]
    );
  }

  async initFromPassphrase(passphrase: string, salt: Uint8Array): Promise<void> {
    // Fallback: derive from passphrase using PBKDF2
    const encoder = new TextEncoder();
    const passphraseKey = await crypto.subtle.importKey(
      "raw",
      encoder.encode(passphrase),
      { name: "PBKDF2" },
      false,
      ["deriveBits", "deriveKey"]
    );

    const masterBits = await crypto.subtle.deriveBits(
      {
        name: "PBKDF2",
        salt: salt,
        iterations: 600000,  // OWASP 2023 recommendation
        hash: "SHA-256"
      },
      passphraseKey,
      256
    );

    this.masterKey = await crypto.subtle.importKey(
      "raw",
      masterBits,
      { name: "HKDF" },
      false,
      ["deriveKey", "deriveBits"]
    );
  }

  async deriveEncryptionKey(): Promise<CryptoKey> {
    return crypto.subtle.deriveKey(
      {
        name: "HKDF",
        hash: "SHA-256",
        salt: new TextEncoder().encode("encryptid-encryption-v1"),
        info: new TextEncoder().encode("AES-256-GCM")
      },
      this.masterKey,
      { name: "AES-GCM", length: 256 },
      false,  // Not extractable
      ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
    );
  }

  async deriveSigningKeyPair(): Promise<CryptoKeyPair> {
    // Derive deterministic seed for signing key
    const seed = await crypto.subtle.deriveBits(
      {
        name: "HKDF",
        hash: "SHA-256",
        salt: new TextEncoder().encode("encryptid-signing-v1"),
        info: new TextEncoder().encode("ECDSA-P256")
      },
      this.masterKey,
      256
    );

    // Use seed to generate deterministic P-256 key
    // (Implementation uses noble-curves or similar)
    return generateP256FromSeed(seed);
  }

  async deriveDIDKey(): Promise<{ did: string; privateKey: Uint8Array }> {
    // Derive Ed25519 key for DID
    const seed = await crypto.subtle.deriveBits(
      {
        name: "HKDF",
        hash: "SHA-256",
        salt: new TextEncoder().encode("encryptid-did-v1"),
        info: new TextEncoder().encode("Ed25519")
      },
      this.masterKey,
      256
    );

    // Generate Ed25519 keypair from seed
    const keyPair = ed25519.generateKeyPairFromSeed(new Uint8Array(seed));

    // Format as did:key
    const did = `did:key:${base58btc.encode(
      new Uint8Array([0xed, 0x01, ...keyPair.publicKey])
    )}`;

    return { did, privateKey: keyPair.privateKey };
  }
}

Key Usage by Application

Application Encryption Key Signing Key DID Key Wallet
rspace Private boards, E2E content Document signatures, authorship Cross-app identity Premium features
rwallet - Transaction signing Identity verification Treasury ops
rvote Encrypted ballot metadata Ballot signatures Voter identity On-chain voting
rmaps Private location data Contribution signatures Contributor identity -
rfiles E2E file encryption File signatures Sharing identity Storage payments

Layer 3: Smart Wallet (Account Abstraction)

Why Account Abstraction?

Traditional EOA (Externally Owned Account) wallets have critical UX problems:

  • Seed phrases are confusing and easily lost
  • No built-in recovery mechanism
  • Gas required for every transaction
  • Single point of failure (one key = full access)

Account Abstraction (ERC-4337) smart wallets solve these:

Feature EOA Wallet AA Smart Wallet
Key management Seed phrase Passkey + social recovery
Gas User pays ETH Paymaster can sponsor
Recovery Seed phrase or nothing Guardian-based recovery
Policies None Spending limits, time locks
Batching One tx at a time Multiple ops in one tx

Provider Comparison

Provider Passkey Support Social Recovery Pricing Best For
ZeroDev Native (Turnkey) Guardian module Usage-based Full control, custom flows
Safe Passkey module Multi-sig native Free (gas only) DAOs, shared treasuries
Privy Native Managed recovery Enterprise pricing Quick integration
Turnkey Native (TEE) Custom Volume-based Infrastructure layer
+============================================================================+
|                    ENCRYPTID SMART WALLET ARCHITECTURE                      |
+============================================================================+

                         +---------------------------+
                         |      User's Passkey       |
                         | (WebAuthn P-256 on device)|
                         +-------------+-------------+
                                       |
                                       v
                         +---------------------------+
                         |     Turnkey TEE Signer    |
                         |  (Secure enclave signing) |
                         +-------------+-------------+
                                       |
                    +------------------+------------------+
                    |                                     |
                    v                                     v
+-------------------------------+       +-------------------------------+
|    Personal Smart Wallet      |       |   Community Treasury (Safe)   |
|         (ZeroDev Kernel)      |       |        Multi-sig Wallet       |
+-------------------------------+       +-------------------------------+
|                               |       |                               |
|  Owner: Passkey Validator     |       |  Signers:                     |
|                               |       |  - User's EncryptID           |
|  Modules:                     |       |  - Other community members    |
|  - Session Key (daily ops)    |       |  - Guardian (recovery)        |
|  - Recovery (guardians)       |       |                               |
|  - Spending Limits            |       |  Threshold: 2 of 3            |
|                               |       |                               |
+-------------------------------+       +-------------------------------+
           |                                       |
           v                                       v
+-------------------------------+       +-------------------------------+
|        Use Cases:             |       |        Use Cases:             |
|  - Personal rwallet           |       |  - Community rwallet          |
|  - rvote governance           |       |  - Shared treasury            |
|  - rfiles payments            |       |  - Grant disbursement         |
+-------------------------------+       +-------------------------------+

Implementation

import { createKernelAccount, createKernelAccountClient } from "@zerodev/sdk";
import { toWebAuthnKey, toPasskeyValidator } from "@zerodev/passkey-validator";
import { toECDSASigner } from "@zerodev/permissions";

class EncryptIDWallet {
  private kernelAccount: KernelAccount;
  private kernelClient: KernelAccountClient;

  async initialize(webAuthnCredential: PublicKeyCredential): Promise<string> {
    // Create WebAuthn-based validator
    const webAuthnKey = await toWebAuthnKey({
      passkeyName: "EncryptID",
      passkeyServerUrl: "https://encryptid.online/passkey",
      credential: webAuthnCredential
    });

    const passkeyValidator = await toPasskeyValidator(publicClient, {
      webAuthnKey,
      entryPoint: ENTRYPOINT_ADDRESS_V07,
      kernelVersion: KERNEL_V3_1
    });

    // Create kernel account with passkey as owner
    this.kernelAccount = await createKernelAccount(publicClient, {
      plugins: {
        sudo: passkeyValidator  // Passkey has full control
      },
      entryPoint: ENTRYPOINT_ADDRESS_V07,
      kernelVersion: KERNEL_V3_1
    });

    // Create client with paymaster for gasless UX
    this.kernelClient = createKernelAccountClient({
      account: this.kernelAccount,
      chain: base,  // or arbitrum, optimism, etc.
      bundlerTransport: http(BUNDLER_URL),
      paymaster: createZeroDevPaymasterClient({
        chain: base,
        transport: http(PAYMASTER_URL)
      })
    });

    return this.kernelAccount.address;
  }

  // Create session key for frictionless UX
  async createSessionKey(permissions: Permission[], duration: number): Promise<SessionKey> {
    const sessionKey = generatePrivateKey();
    const sessionSigner = privateKeyToAccount(sessionKey);

    const sessionKeyValidator = await toPermissionValidator(publicClient, {
      entryPoint: ENTRYPOINT_ADDRESS_V07,
      kernelVersion: KERNEL_V3_1,
      signer: toECDSASigner({ signer: sessionSigner }),
      policies: [
        toCallPolicy({
          permissions: permissions  // e.g., only call specific contracts
        }),
        toTimestampPolicy({
          validUntil: Math.floor(Date.now() / 1000) + duration
        }),
        toSpendingLimitPolicy({
          limits: [{ token: ETH_ADDRESS, limit: parseEther("0.1") }]
        })
      ]
    });

    // User approves session key with passkey (one biometric prompt)
    await this.kernelClient.installModule({
      module: sessionKeyValidator.address,
      data: sessionKeyValidator.initData
    });

    return { privateKey: sessionKey, validator: sessionKeyValidator };
  }
}

Layer 4: Social Recovery (No Seed Phrases!)

Design Philosophy

"The goal of social recovery is to avoid a single point of failure. If you lose your device, you should not lose your assets. If your guardians conspire against you, they should not be able to steal your assets." — Vitalik Buterin

Guardian Configuration

+============================================================================+
|                     ENCRYPTID SOCIAL RECOVERY MODEL                         |
+============================================================================+

                    +---------------------------+
                    |     Recovery Threshold    |
                    |     3 of 5 guardians      |
                    +---------------------------+
                                  |
        +------------+------------+------------+------------+
        |            |            |            |            |
        v            v            v            v            v
+-------------+ +-------------+ +-------------+ +-------------+ +-------------+
|  Guardian 1 | |  Guardian 2 | |  Guardian 3 | |  Guardian 4 | |  Guardian 5 |
+-------------+ +-------------+ +-------------+ +-------------+ +-------------+
|             | |             | |             | |             | |             |
| Secondary   | | Trusted     | | Trusted     | | Hardware    | | Institutional|
| Passkey     | | Friend A    | | Friend B    | | Key         | | Guardian    |
| (YubiKey)   | | (their      | | (their      | | (offline    | | (service)   |
|             | | EncryptID)  | | EncryptID)  | | backup)     | |             |
+-------------+ +-------------+ +-------------+ +-------------+ +-------------+

Recovery Rules:
- Time-lock: 48-hour delay before recovery completes
- Notification: User notified immediately when recovery initiated
- Cancellation: User can cancel with any valid authenticator
- Guardian privacy: Guardians don't know each other's identities

Guardian Types

Type Description Pros Cons
Secondary Passkey Another device you own (YubiKey, second phone) Always available, you control Can lose both devices
Trusted Contact Friend/family with EncryptID Human judgment, diverse access Relationship changes
Hardware Key Offline YubiKey stored securely Immune to remote attacks Physical access needed
Institutional Service provider (e.g., rspace.online) Professional, always available Trust in service
Time-delayed Self Your own auth after N days No external trust needed Attacker has same delay

Implementation

// ZeroDev Recovery Module
class EncryptIDRecovery {
  async setupGuardians(guardians: Guardian[]): Promise<void> {
    const recoveryConfig = {
      threshold: 3,  // 3 of 5 required
      delay: 48 * 60 * 60,  // 48 hours
      guardians: guardians.map(g => ({
        address: g.address,
        weight: g.weight || 1
      }))
    };

    const recoveryPlugin = await createRecoveryPlugin(recoveryConfig);

    await this.kernelClient.installModule({
      module: recoveryPlugin.address,
      data: recoveryPlugin.initData
    });
  }

  async initiateRecovery(
    newOwner: Address,
    guardianSignatures: Signature[]
  ): Promise<RecoveryRequest> {
    // Guardian signs recovery request
    // This starts the time-lock countdown
    const recoveryId = await this.kernelClient.sendUserOperation({
      userOperation: {
        callData: encodeFunctionData({
          abi: recoveryAbi,
          functionName: 'initiateRecovery',
          args: [newOwner, guardianSignatures]
        })
      }
    });

    // Notify user through all channels
    await notifyUserOfRecovery(this.kernelAccount.address, recoveryId);

    return { recoveryId, completesAt: Date.now() + 48 * 60 * 60 * 1000 };
  }

  async completeRecovery(recoveryId: string): Promise<void> {
    // After time-lock, anyone can complete
    await this.kernelClient.sendUserOperation({
      userOperation: {
        callData: encodeFunctionData({
          abi: recoveryAbi,
          functionName: 'completeRecovery',
          args: [recoveryId]
        })
      }
    });
  }

  async cancelRecovery(recoveryId: string): Promise<void> {
    // User can cancel with any valid auth method
    await this.kernelClient.sendUserOperation({
      userOperation: {
        callData: encodeFunctionData({
          abi: recoveryAbi,
          functionName: 'cancelRecovery',
          args: [recoveryId]
        })
      }
    });
  }
}

Guardian Privacy

Guardians don't need to know each other - this prevents collusion:

// Store guardian list as hash on-chain
const guardianListHash = keccak256(
  encodePacked(['address[]'], [guardianAddresses.sort()])
);

// During recovery, reveal full list
function verifyGuardians(addresses: Address[], hash: bytes32): boolean {
  return keccak256(encodePacked(['address[]'], [addresses.sort()])) === hash;
}

Best Practices (per Vitalik)

  1. High guardian count (5-7) for better security
  2. Diverse social circles - family, friends, colleagues, institutions
  3. Privacy-preserving - guardians don't know each other
  4. Time-lock - gives user window to cancel malicious recovery
  5. Regular verification - periodically confirm guardians are reachable

Layer 5: Session & Cross-App SSO

Session Architecture

+============================================================================+
|                        ENCRYPTID SESSION FLOW                               |
+============================================================================+

    User                  encryptid.online              r-app (e.g., rspace)
      |                         |                              |
      |  1. Visit rspace.online |                              |
      |------------------------>|                              |
      |                         |  2. Check for EncryptID      |
      |                         |     session cookie           |
      |                         |----------------------------->|
      |                         |                              |
      |  3. No session, redirect to encryptid.online/auth      |
      |<-------------------------------------------------------|
      |                         |                              |
      |  4. WebAuthn authenticate                              |
      |------------------------>|                              |
      |                         |                              |
      |  5. Derive session keys (if needed)                    |
      |<------------------------|                              |
      |                         |                              |
      |  6. Issue session token |                              |
      |     (JWT + refresh)     |                              |
      |<------------------------|                              |
      |                         |                              |
      |  7. Redirect back with auth code                       |
      |------------------------------------------------------>|
      |                         |                              |
      |                         |  8. Exchange code for tokens |
      |                         |<-----------------------------|
      |                         |                              |
      |  9. Session established, user authenticated            |
      |<-------------------------------------------------------|

Token Structure

interface EncryptIDSession {
  // Standard JWT claims
  iss: "https://encryptid.online";
  sub: string;           // DID (did:key:z6Mk...)
  aud: string[];         // Authorized apps ["rspace.online", "rwallet.online"]
  iat: number;
  exp: number;           // Short-lived (15 min)

  // EncryptID-specific claims
  eid: {
    walletAddress?: string;        // AA wallet if deployed
    credentialId: string;          // WebAuthn credential ID
    authLevel: 1 | 2 | 3 | 4;      // Security level of this session
    capabilities: {
      encrypt: boolean;            // Has derived encryption key
      sign: boolean;               // Has derived signing key
      wallet: boolean;             // Can authorize wallet ops
    };
    recoveryConfigured: boolean;   // Has social recovery set up
  };
}

Security Levels

enum AuthLevel {
  // Level 1: Session token only (cookie)
  // Can: View public content, read-only operations
  // Cannot: Modify data, sign anything
  BASIC = 1,

  // Level 2: Recent WebAuthn (within 15 min)
  // Can: Create/edit content, standard operations
  // Cannot: High-value transactions, key operations
  STANDARD = 2,

  // Level 3: Fresh WebAuthn (just authenticated)
  // Can: Sign votes, approve transactions
  // Cannot: Recovery operations, export keys
  ELEVATED = 3,

  // Level 4: Fresh WebAuthn + explicit consent
  // Can: Everything including recovery, key export
  CRITICAL = 4
}

// Operation requirements
const operationLevels = {
  'rspace:view-public': AuthLevel.BASIC,
  'rspace:edit-board': AuthLevel.STANDARD,
  'rspace:encrypt-board': AuthLevel.ELEVATED,

  'rwallet:view-balance': AuthLevel.BASIC,
  'rwallet:send-small': AuthLevel.STANDARD,
  'rwallet:send-large': AuthLevel.ELEVATED,
  'rwallet:add-guardian': AuthLevel.CRITICAL,

  'rvote:view-proposals': AuthLevel.BASIC,
  'rvote:cast-vote': AuthLevel.ELEVATED,

  'rfiles:download-own': AuthLevel.STANDARD,
  'rfiles:upload': AuthLevel.STANDARD,
  'rfiles:share': AuthLevel.ELEVATED,
  'rfiles:export-keys': AuthLevel.CRITICAL
};

Migration Path from CryptID

Phase 1: Parallel Systems (Months 1-2)

+----------------------------------+
|         Current CryptID          |
|  (WebCrypto keypairs in IDB)     |
+----------------------------------+
              ||
              || Users can link
              || EncryptID passkey
              vv
+----------------------------------+
|         EncryptID                |
|  (WebAuthn + derived keys)       |
+----------------------------------+

- Existing CryptID users continue working
- Optional: Link EncryptID passkey to existing account
- New users default to EncryptID
- Both auth methods valid during transition

Phase 2: Key Migration (Months 2-3)

async function migrateCryptIDToEncryptID(
  cryptidKeyPair: CryptoKeyPair,
  encryptidMasterKey: CryptoKey
): Promise<void> {
  // 1. Export CryptID private key (if exportable)
  //    or re-encrypt data with new keys

  // 2. For each encrypted item:
  //    - Decrypt with CryptID key
  //    - Re-encrypt with EncryptID-derived key

  // 3. Update identity mappings
  //    - Link old CryptID public key to new DID
  //    - Maintain backward compatibility

  // 4. Mark migration complete
}

Phase 3: CryptID Sunset (Months 3-6)

  • New accounts: EncryptID only
  • Existing accounts: Prompted to upgrade
  • CryptID becomes legacy fallback
  • Eventually: Remove CryptID support

Security Considerations

Threat Model

Threat Mitigation
Phishing WebAuthn is origin-bound; passkeys can't be used on fake sites
Device theft Biometric required; derived keys not extractable
Server compromise Keys derived client-side; server never sees private keys
Guardian collusion High threshold (3/5), diverse selection, time-lock
Malicious recovery 48h delay, user notification, cancellation window
Key extraction WebCrypto keys marked non-extractable
PRF unavailable Fallback to passphrase-derived with strong KDF

PRF Key Binding Warning

"Data encrypted with PRF-derived keys is bound exclusively to the specific passkey. Losing the passkey will permanently render the encrypted data inaccessible." — Corbado

Mitigation:

  1. Social recovery enables passkey rotation
  2. Multiple passkeys (backup hardware key)
  3. Encrypted key escrow (wrapped with recovery keys)

Browser/Authenticator Compatibility

Test matrix for WebAuthn features:

Feature Chrome Safari Firefox Edge
Basic WebAuthn Yes Yes Yes Yes
Discoverable Credentials Yes Yes Yes Yes
PRF Extension Yes Yes No Yes
Related Origins Yes Yes No Yes
Cross-device (Hybrid) Yes Yes Partial Yes

Implementation Roadmap

Sprint 1: Foundation (2 weeks)

  • Set up encryptid.online domain
  • Implement WebAuthn registration/authentication
  • Create Related Origins configuration
  • Basic session token issuance

Sprint 2: Key Derivation (2 weeks)

  • Implement PRF-based key derivation
  • Passphrase fallback for non-PRF authenticators
  • Encryption key integration with rfiles
  • Signing key integration with rvote

Sprint 3: Smart Wallet (3 weeks)

  • ZeroDev integration
  • Passkey validator deployment
  • Session key module
  • Paymaster setup for gasless UX

Sprint 4: Social Recovery (2 weeks)

  • Guardian configuration UI
  • Recovery initiation flow
  • Time-lock and notification system
  • Recovery completion and cancellation

Sprint 5: Cross-App Integration (2 weeks)

  • rspace.online integration
  • rwallet.online integration
  • rvote.online integration
  • rfiles.online integration
  • rmaps.online integration

Sprint 6: Migration & Polish (2 weeks)

  • CryptID migration tools
  • User onboarding flow
  • Documentation
  • Security audit

References

WebAuthn & Passkeys

Account Abstraction

Social Recovery

Infrastructure

Decentralized Identity


Document version: 0.1 Last updated: February 5, 2026 Author: Jeff Emmett with Claude