951 lines
37 KiB
Markdown
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*
|