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.
Related Origin Requests for Cross-App SSO
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 |
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
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)
- High guardian count (5-7) for better security
- Diverse social circles - family, friends, colleagues, institutions
- Privacy-preserving - guardians don't know each other
- Time-lock - gives user window to cancel malicious recovery
- 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:
- Social recovery enables passkey rotation
- Multiple passkeys (backup hardware key)
- 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)
- Passkeys & WebAuthn PRF for E2E Encryption (Corbado)
- Related Origin Requests (passkeys.dev)
- Cross-Domain Passkeys (web.dev)
Account Abstraction
Social Recovery
Infrastructure
Decentralized Identity
Document version: 0.1 Last updated: February 5, 2026 Author: Jeff Emmett with Claude