rspace-online/docs/ENCRYPTID-SPECIFICATION.md

951 lines
37 KiB
Markdown

# 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
```typescript
// 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.
### Related Origin Requests for Cross-App SSO
Since r-apps span multiple domains, we use [Related Origin Requests](https://passkeys.dev/docs/advanced/related-origins/):
```
// 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
```typescript
// 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](https://zerodev.app/)** | Native (Turnkey) | Guardian module | Usage-based | Full control, custom flows |
| **[Safe](https://safe.global/)** | Passkey module | Multi-sig native | Free (gas only) | DAOs, shared treasuries |
| **[Privy](https://privy.io/)** | Native | Managed recovery | Enterprise pricing | Quick integration |
| **[Turnkey](https://turnkey.com/)** | Native (TEE) | Custom | Volume-based | Infrastructure layer |
### Recommended Architecture: ZeroDev + Safe Hybrid
```
+============================================================================+
| 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
```typescript
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](https://vitalik.eth.limo/general/2021/01/11/recovery.html)
### 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
```typescript
// 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:
```typescript
// 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](https://vitalik.eth.limo/general/2021/01/11/recovery.html))
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
```typescript
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
```typescript
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)
```typescript
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](https://www.corbado.com/blog/passkeys-prf-webauthn)
**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
- [WebAuthn PRF Extension Guide (Yubico)](https://developers.yubico.com/WebAuthn/Concepts/PRF_Extension/Developers_Guide_to_PRF.html)
- [Passkeys & WebAuthn PRF for E2E Encryption (Corbado)](https://www.corbado.com/blog/passkeys-prf-webauthn)
- [Related Origin Requests (passkeys.dev)](https://passkeys.dev/docs/advanced/related-origins/)
- [Cross-Domain Passkeys (web.dev)](https://web.dev/articles/webauthn-related-origin-requests)
### Account Abstraction
- [ZeroDev Documentation](https://docs.zerodev.app/)
- [ZeroDev Recovery](https://docs-v4.zerodev.app/use-wallets/recovery)
- [Safe Passkey Module](https://docs.safe.global/advanced/passkeys/passkeys-safe)
- [Safe Passkey Signer](https://docs.safe.global/sdk/signers/passkeys)
### Social Recovery
- [Why We Need Social Recovery Wallets (Vitalik Buterin)](https://vitalik.eth.limo/general/2021/01/11/recovery.html)
- [Social Recovery Best Practices](https://yellow.com/learn/social-recovery-wallets-can-they-solve-the-seed-phrase-problem-complete-2025-guide)
### Infrastructure
- [Turnkey Whitepaper](https://whitepaper.turnkey.com/architecture)
- [Turnkey Embedded Wallets](https://www.turnkey.com/embedded-wallets)
- [Privy Documentation](https://docs.privy.io/)
### Decentralized Identity
- [DID Core Specification (W3C)](https://www.w3.org/TR/did-core/)
- [did:key Method](https://w3c-ccg.github.io/did-method-key/)
- [Decentralized Identity Guide 2025 (Dock)](https://www.dock.io/post/decentralized-identity)
---
*Document version: 0.1*
*Last updated: February 5, 2026*
*Author: Jeff Emmett with Claude*