w3pk 0.9.2 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1290,14 +1290,299 @@ const delegation = await w3pk.requestExternalWalletDelegation({
1290
1290
 
1291
1291
  ---
1292
1292
 
1293
+ ## ML-KEM Encryption
1294
+
1295
+ w3pk supports ML-KEM-1024 encryption for quantum-resistant data protection with multi-recipient support and deterministic key derivation from private keys.
1296
+
1297
+ ### Deterministic Key Derivation
1298
+
1299
+ Derive ML-KEM keypairs deterministically from any private key material (Ethereum keys, random seeds, etc.):
1300
+
1301
+ ```typescript
1302
+ import { deriveMLKemKeypair, mlkemEncryptWithKey, mlkemDecryptWithKey } from 'w3pk';
1303
+
1304
+ // Derive ML-KEM keypair from Ethereum private key
1305
+ const ethPrivateKey = '0x1234...'; // Your wallet private key
1306
+ const keypair = await deriveMLKemKeypair(ethPrivateKey, 'my-app');
1307
+ // Returns: { publicKey: Uint8Array(1568), privateKey: Uint8Array(3168) }
1308
+
1309
+ // Share your public key with others
1310
+ const publicKeyBase64 = Buffer.from(keypair.publicKey).toString('base64');
1311
+
1312
+ // Later: Recreate keypair from same private key (deterministic!)
1313
+ const sameKeypair = await deriveMLKemKeypair(ethPrivateKey, 'my-app');
1314
+ // sameKeypair.publicKey === keypair.publicKey ✅
1315
+ ```
1316
+
1317
+ ### Encrypt/Decrypt with w3pk Instance (Recommended)
1318
+
1319
+ The easiest way to use ML-KEM encryption - no private key exposure:
1320
+
1321
+ ```typescript
1322
+ import { createWeb3Passkey } from 'w3pk';
1323
+
1324
+ const w3pk = createWeb3Passkey();
1325
+ await w3pk.login();
1326
+
1327
+ // 1. Share your public key with others
1328
+ const myPublicKey = await w3pk.deriveMLKemPublicKey();
1329
+ // Send myPublicKey to the server or publish it
1330
+
1331
+ // 2. Get server's public key
1332
+ const serverPubKey = await fetch('/api/mlkem-public-key').then(r => r.text());
1333
+
1334
+ // 3. Encrypt for yourself + server
1335
+ const encrypted = await w3pk.mlkemEncrypt(
1336
+ 'my secret data',
1337
+ [serverPubKey] // Server can decrypt
1338
+ );
1339
+ // You can also decrypt because you're auto-added as recipient
1340
+
1341
+ // 4. Later: Decrypt locally (server never sees plaintext!)
1342
+ const plaintext = await w3pk.mlkemDecrypt(encrypted);
1343
+ ```
1344
+
1345
+ **Security:** Your Ethereum private key never leaves the w3pk instance, matching the security model of `signMessage()` and `sendTransaction()`.
1346
+
1347
+ **Supported Modes:**
1348
+ - ✅ **STANDARD** (default) - Private key derived internally, not exposed to app
1349
+ - ✅ **STRICT** - Same as STANDARD but always requires WebAuthn re-authentication
1350
+ - ✅ **YOLO** - Private key available to app
1351
+ - ❌ **PRIMARY** - Not supported (uses P-256 WebAuthn keys, not Ethereum keys)
1352
+
1353
+ ### Encrypt/Decrypt with Key Derivation (Low-level API)
1354
+
1355
+ For advanced use cases where you manage private keys directly:
1356
+
1357
+ ```typescript
1358
+ // Client encrypts for themselves + server
1359
+ const serverKeypair = await deriveMLKemKeypair(serverEthKey, 'server');
1360
+ const encrypted = await mlkemEncryptWithKey(
1361
+ 'my secret data',
1362
+ myEthPrivateKey, // Sender's private key (auto-added as recipient)
1363
+ [serverKeypair.publicKey], // Additional recipients
1364
+ 'client' // Context for sender's key derivation
1365
+ );
1366
+
1367
+ // Later: Client decrypts locally (no server needed!)
1368
+ const plaintext = await mlkemDecryptWithKey(
1369
+ encrypted,
1370
+ myEthPrivateKey, // Same private key used for encryption
1371
+ 'client' // Same context
1372
+ );
1373
+
1374
+ // Or: Server decrypts for operations
1375
+ const plaintext = await mlkemDecryptWithKey(
1376
+ encrypted,
1377
+ serverEthKey,
1378
+ 'server'
1379
+ );
1380
+ ```
1381
+
1382
+ ### Direct Encryption (Without Key Derivation)
1383
+
1384
+ For maximum flexibility, use raw public keys directly:
1385
+
1386
+ ```typescript
1387
+ import { mlkemEncrypt, mlkemDecrypt } from 'w3pk';
1388
+
1389
+ // Single recipient
1390
+ const publicKey = 'base64_encoded_public_key...'; // 1568 bytes
1391
+ const encrypted = await mlkemEncrypt('my secret data', publicKey);
1392
+
1393
+ // Multiple recipients (encrypt once, multiple people can decrypt)
1394
+ const encrypted = await mlkemEncrypt('my secret data', [
1395
+ publicKey1,
1396
+ publicKey2,
1397
+ publicKey3
1398
+ ]);
1399
+ // Returns: { recipients: [...], encryptedData, iv, authTag }
1400
+
1401
+ // Decrypt with your private key (auto-detects which recipient you are)
1402
+ const privateKey = 'base64_encoded_private_key...'; // 3168 bytes
1403
+ const plaintext = await mlkemDecrypt(encrypted, privateKey);
1404
+ // Returns: 'my secret data'
1405
+
1406
+ // Or specify your public key for faster lookup
1407
+ const plaintext = await mlkemDecrypt(encrypted, privateKey, publicKey);
1408
+ ```
1409
+
1410
+ ### Security Properties
1411
+
1412
+ - ✅ **ML-KEM-1024** (NIST FIPS 203) - Post-quantum secure
1413
+ - ✅ **AES-256-GCM** - 128-bit quantum security (sufficient)
1414
+ - ✅ **Key zeroization** - Shared secrets securely wiped from memory
1415
+ - ✅ **Hybrid encryption** - KEM + symmetric cipher for efficiency
1416
+ - ✅ **Cross-platform** - Works in browser and Node.js
1417
+
1418
+ ### API Reference
1419
+
1420
+ #### Instance Methods (Recommended)
1421
+
1422
+ These methods work with your w3pk wallet instance - no private key exposure:
1423
+
1424
+ #### `w3pk.deriveMLKemPublicKey(options?): Promise<string>`
1425
+
1426
+ Derives your ML-KEM-1024 public key that can be shared for encryption.
1427
+
1428
+ **Parameters:**
1429
+ - `options.context` - Context string for domain separation (default: `'mlkem-v1'`)
1430
+ - `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
1431
+ - `options.tag` - Tag for address derivation (default: `'MAIN'`)
1432
+ - `options.origin` - Origin for derivation (default: current origin)
1433
+ - `options.requireAuth` - Force WebAuthn re-authentication (default: false, always true in STRICT mode)
1434
+
1435
+ **Note:** PRIMARY mode is not supported as it uses P-256 WebAuthn keys instead of Ethereum keys.
1436
+
1437
+ **Returns:** Base64-encoded ML-KEM public key (1568 bytes)
1438
+
1439
+ **Example:**
1440
+ ```typescript
1441
+ const w3pk = createWeb3Passkey();
1442
+ await w3pk.login();
1443
+ const pubKey = await w3pk.deriveMLKemPublicKey();
1444
+ ```
1445
+
1446
+ #### `w3pk.mlkemEncrypt(plaintext: string, recipientPublicKeys: Array<string | Uint8Array>, options?): Promise<EncryptedPayload>`
1447
+
1448
+ Encrypts data for yourself and additional recipients.
1449
+
1450
+ **Parameters:**
1451
+ - `plaintext` - The data to encrypt
1452
+ - `recipientPublicKeys` - Array of recipient ML-KEM public keys (base64 or Uint8Array)
1453
+ - `options.context` - Context string (default: `'mlkem-v1'`)
1454
+ - `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
1455
+ - `options.tag` - Tag for address derivation (default: `'MAIN'`)
1456
+ - `options.origin` - Origin (default: current origin)
1457
+ - `options.requireAuth` - Force re-authentication (default: false, always true in STRICT mode)
1458
+
1459
+ **Returns:** Encrypted payload
1460
+
1461
+ **Example:**
1462
+ ```typescript
1463
+ const encrypted = await w3pk.mlkemEncrypt('secret', [serverPubKey]);
1464
+ ```
1465
+
1466
+ #### `w3pk.mlkemDecrypt(payload: EncryptedPayload, options?): Promise<string>`
1467
+
1468
+ Decrypts data encrypted for your wallet.
1469
+
1470
+ **Parameters:**
1471
+ - `payload` - The encrypted payload
1472
+ - `options.context` - Context string (default: `'mlkem-v1'`, must match encryption)
1473
+ - `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
1474
+ - `options.tag` - Tag (default: `'MAIN'`)
1475
+ - `options.origin` - Origin (default: current origin)
1476
+ - `options.requireAuth` - Force re-authentication (default: false, always true in STRICT mode)
1477
+
1478
+ **Returns:** Decrypted plaintext
1479
+
1480
+ **Example:**
1481
+ ```typescript
1482
+ const plaintext = await w3pk.mlkemDecrypt(encrypted);
1483
+ ```
1484
+
1485
+ ---
1486
+
1487
+ #### Low-Level Functions
1488
+
1489
+ These functions require managing private keys directly:
1490
+
1491
+ #### `deriveMLKemKeypair(privateKey: string | Uint8Array, context?: string): Promise<MLKemKeypair>`
1492
+
1493
+ Derives a deterministic ML-KEM-1024 keypair from any private key material using HKDF-SHA256.
1494
+
1495
+ **Parameters:**
1496
+ - `privateKey` - Private key material (hex string with optional `0x` prefix, or Uint8Array)
1497
+ - `context` - Optional context string for domain separation (default: `'mlkem-v1'`)
1498
+
1499
+ **Returns:** `MLKemKeypair` object containing:
1500
+ - `publicKey` - ML-KEM-1024 public key (Uint8Array, 1568 bytes)
1501
+ - `privateKey` - ML-KEM-1024 private key (Uint8Array, 3168 bytes)
1502
+
1503
+ **Security:** Uses HKDF with salt `"mlkem-keypair-v1"` and the provided context to derive a 64-byte seed, then generates a deterministic ML-KEM keypair. Same input always produces same output. All sensitive material is zeroized after use.
1504
+
1505
+ #### `mlkemEncryptWithKey(plaintext: string, senderPrivateKey: string | Uint8Array, recipientPublicKeys: Array<string | Uint8Array>, senderContext?: string): Promise<EncryptedPayload>`
1506
+
1507
+ Convenience function that derives the sender's ML-KEM keypair, then encrypts for sender + recipients.
1508
+
1509
+ **Parameters:**
1510
+ - `plaintext` - The data to encrypt
1511
+ - `senderPrivateKey` - Sender's private key (hex string or Uint8Array)
1512
+ - `recipientPublicKeys` - Array of recipient ML-KEM public keys
1513
+ - `senderContext` - Optional context for sender's key derivation (default: `'mlkem-v1'`)
1514
+
1515
+ **Returns:** `EncryptedPayload` (sender's public key is automatically included as first recipient)
1516
+
1517
+ **Use case:** Encrypt data so both you and a server can decrypt independently.
1518
+
1519
+ #### `mlkemDecryptWithKey(payload: EncryptedPayload, privateKey: string | Uint8Array, context?: string): Promise<string>`
1520
+
1521
+ Convenience function that derives an ML-KEM keypair, then decrypts the payload.
1522
+
1523
+ **Parameters:**
1524
+ - `payload` - The encrypted payload
1525
+ - `privateKey` - Private key material (hex string or Uint8Array)
1526
+ - `context` - Optional context for key derivation (default: `'mlkem-v1'`)
1527
+
1528
+ **Returns:** Decrypted plaintext string
1529
+
1530
+ **Use case:** Decrypt data using your Ethereum private key without managing separate ML-KEM keys.
1531
+
1532
+ #### `mlkemEncrypt(plaintext: string, publicKeys: (string | Uint8Array) | Array<string | Uint8Array>): Promise<EncryptedPayload>`
1533
+
1534
+ Encrypts data using ML-KEM-1024 + AES-256-GCM for one or more recipients.
1535
+
1536
+ **Parameters:**
1537
+ - `plaintext` - The data to encrypt
1538
+ - `publicKeys` - Single public key or array of ML-KEM-1024 public keys (base64 strings or Uint8Arrays, 1568 bytes each)
1539
+
1540
+ **Returns:** `EncryptedPayload` object containing:
1541
+ - `recipients` - Array of recipient entries, each containing:
1542
+ - `publicKey` - Base64 recipient public key (1568 bytes)
1543
+ - `ciphertext` - Base64 ML-KEM ciphertext for this recipient (1600 bytes: 1568 KEM + 32 encrypted AES key)
1544
+ - `encryptedData` - Base64 AES-encrypted data (shared across all recipients)
1545
+ - `iv` - Base64 initialization vector (12 bytes)
1546
+ - `authTag` - Base64 authentication tag (16 bytes)
1547
+
1548
+ **How it works:**
1549
+ 1. Generates a random AES-256 key
1550
+ 2. Encrypts plaintext with AES-256-GCM using that key
1551
+ 3. For each recipient: encapsulates a shared secret with their ML-KEM public key, then XOR-encrypts the AES key with the shared secret
1552
+ 4. Each recipient can decrypt using their private key to recover the AES key, then decrypt the data
1553
+
1554
+ #### `mlkemDecrypt(payload: EncryptedPayload, privateKey: string | Uint8Array, publicKey?: string | Uint8Array): Promise<string>`
1555
+
1556
+ Decrypts data encrypted with `mlkemEncrypt()`.
1557
+
1558
+ **Parameters:**
1559
+ - `payload` - The encrypted payload from `mlkemEncrypt()`
1560
+ - `privateKey` - ML-KEM-1024 private key (base64 string or Uint8Array, 3168 bytes)
1561
+ - `publicKey` - (Optional) Your ML-KEM-1024 public key for faster recipient lookup (base64 string or Uint8Array, 1568 bytes)
1562
+
1563
+ **Returns:** Decrypted plaintext string
1564
+
1565
+ **Throws:** Error if decryption fails (invalid key, no matching recipient, corrupted data, or tampered auth tag)
1566
+
1567
+ **Note:** If `publicKey` is not provided, the function will try each recipient until it finds a match (slower but convenient)
1568
+
1569
+ ---
1570
+
1293
1571
  ## Document Maintenance
1294
1572
 
1295
- **Version:** 1.1
1296
- **Last Updated:** 2026-02-27
1297
- **Next Review:** 2026-08-27 (6 months)
1573
+ **Version:** 1.2
1574
+ **Last Updated:** 2026-03-21
1575
+ **Next Review:** 2026-09-21 (6 months)
1298
1576
  **Maintained By:** Julien Béranger ([@julienbrg](https://github.com/julienbrg))
1299
1577
 
1300
1578
  **Changelog:**
1579
+ - 2026-03-21: Added ML-KEM encryption utilities with deterministic key derivation
1580
+ - Documented `deriveMLKemKeypair()` function for HKDF-based key derivation from private keys
1581
+ - Documented `mlkemEncryptWithKey()` and `mlkemDecryptWithKey()` convenience functions
1582
+ - Documented `mlkemEncrypt()` function for direct post-quantum encryption
1583
+ - Documented `mlkemDecrypt()` function for decryption
1584
+ - Added security properties and comprehensive API reference
1585
+ - Linked to NIST FIPS 203 (ML-KEM) standard
1301
1586
  - 2026-02-27: **Updated to align with [Ethereum quantum resistance roadmap](https://x.com/VitalikButerin/status/2027075026378543132)** (February 2026)
1302
1587
  - Added Ethereum's four quantum-vulnerable components
1303
1588
  - Integrated [EIP-8141](https://eips.ethereum.org/EIPS/eip-8141) (native AA) as long-term solution
package/docs/RECOVERY.md CHANGED
@@ -264,15 +264,25 @@ await sdk.importFromSync(syncData);
264
264
 
265
265
  ### **3. Social Recovery**
266
266
 
267
- **Purpose**: Distribute encrypted wallet data among trusted guardians using Shamir Secret Sharing (M-of-N)
267
+ **Purpose**: Distribute encrypted backup file fragments among trusted guardians using Shamir Secret Sharing (M-of-N)
268
268
 
269
269
  **Setup (3-of-5 example):**
270
270
  ```typescript
271
+ import { SocialRecoveryManager } from 'w3pk';
272
+
271
273
  const sdk = createWeb3Passkey();
272
274
  await sdk.login();
273
275
 
274
- // Create encrypted backup and split among guardians
275
- const { guardianShares } = await sdk.setupSocialRecovery(
276
+ // Step 1: Create password-encrypted backup file
277
+ const password = 'MySecurePassword123!'; // User must remember this
278
+ const { blob } = await sdk.createBackupFile('password', password);
279
+ const backupFileJson = await blob.text();
280
+
281
+ // Step 2: Split backup file among guardians
282
+ const socialRecovery = new SocialRecoveryManager();
283
+ const guardians = await socialRecovery.setupSocialRecovery(
284
+ backupFileJson, // Encrypted backup file JSON
285
+ sdk.user.ethereumAddress,
276
286
  [
277
287
  { name: 'Alice', email: 'alice@example.com' },
278
288
  { name: 'Bob', email: 'bob@example.com' },
@@ -280,84 +290,61 @@ const { guardianShares } = await sdk.setupSocialRecovery(
280
290
  { name: 'David', email: 'david@example.com' },
281
291
  { name: 'Eve', email: 'eve@example.com' },
282
292
  ],
283
- 3, // threshold: need 3 shares to recover
284
- 'OptionalPassword' // encrypts backup before splitting
293
+ 3 // threshold: need 3 shares to recover
285
294
  );
286
295
 
287
- // Generate invitation for each guardian
288
- for (const share of guardianShares) {
289
- const invitation = await sdk.generateGuardianInvite(share);
290
- // Send invitation.downloadBlob or invitation.qrCodeDataURL to guardian
296
+ // Step 3: Generate invitation for each guardian
297
+ for (const guardian of guardians) {
298
+ const invite = await socialRecovery.generateGuardianInvite(guardian);
299
+ // Send invite.qrCode and invite.explainer to guardian
300
+ // Guardian stores their share safely (password manager, etc.)
291
301
  }
292
302
  ```
293
303
 
294
- **Recover Wallet (Two Options):**
295
-
296
- **Option 1: Recover and register new passkey (recommended for lost devices)**
304
+ **Recover Wallet:**
297
305
  ```typescript
306
+ import { SocialRecoveryManager } from 'w3pk';
307
+
298
308
  const sdk = createWeb3Passkey();
309
+ const socialRecovery = new SocialRecoveryManager();
299
310
 
300
311
  // Step 1: Collect shares from 3+ guardians
301
- const shares = [aliceShare, bobShare, charlieShare];
312
+ const shares = [aliceShareCode, bobShareCode, charlieShareCode];
302
313
 
303
- // Step 2: Combine shares and decrypt to get mnemonic and address
304
- const { mnemonic, ethereumAddress } = await sdk.recoverFromGuardians(
305
- shares,
306
- 'OptionalPassword' // password used during setupSocialRecovery (if any)
307
- );
314
+ // Step 2: Reconstruct encrypted backup file from shares
315
+ const { backupFileJson, ethereumAddress } = await socialRecovery.recoverFromGuardians(shares);
308
316
 
309
- // Step 3: Create a new password-protected backup from recovered mnemonic
310
- const { BackupFileManager } = await import('w3pk/backup/backup-file');
311
- const manager = new BackupFileManager();
312
- const { backupFile } = await manager.createPasswordBackup(
313
- mnemonic,
314
- ethereumAddress,
315
- 'NewPassword123!' // Choose a new password
316
- );
317
+ // Step 3: Decrypt backup file with password
318
+ const password = prompt('Enter your backup password');
317
319
 
318
- // Step 4: Register new passkey using the backup
319
- const backupData = JSON.stringify(backupFile);
320
- await sdk.registerWithBackupFile(backupData, 'NewPassword123!', 'username');
320
+ // Step 4: Register new passkey with recovered backup file
321
+ await sdk.registerWithBackupFile(backupFileJson, password, 'username');
321
322
  // Now logged in with new passkey, credentials stored locally ✅
322
323
  ```
323
324
 
324
- **Option 2: Recover and import to existing passkey**
325
- ```typescript
326
- const sdk = createWeb3Passkey();
327
-
328
- // Step 1: Login with existing WebAuthn credential
329
- await sdk.login();
330
-
331
- // Step 2: Collect shares and recover mnemonic
332
- const shares = [aliceShare, bobShare, charlieShare];
333
- const { mnemonic } = await sdk.recoverFromGuardians(
334
- shares,
335
- 'OptionalPassword'
336
- );
337
-
338
- // Step 3: Import mnemonic to current logged-in user
339
- await sdk.importMnemonic(mnemonic);
340
- // Credentials stored locally under current passkey ✅
341
- ```
342
-
343
325
  **Encryption**:
344
- 1. Create BackupFile (password/passkey encrypted)
345
- 2. Serialize to JSON
346
- 3. Split using Shamir Secret Sharing (M-of-N)
347
- 4. Each guardian gets one share
326
+ 1. User creates password-encrypted BackupFile (AES-256-GCM)
327
+ 2. Serialize BackupFile to JSON
328
+ 3. Split JSON using Shamir Secret Sharing (M-of-N threshold)
329
+ 4. Each guardian gets one encrypted fragment
330
+ 5. Recovery requires: threshold shares + original password
348
331
 
349
332
  **Use Case**: Forgot password, lost all devices, ultimate safety net
350
333
 
351
- **Security**: No single guardian can recover wallet alone
334
+ **Security**: No single guardian can recover wallet alone. Guardians hold fragments of an already-encrypted backup file.
352
335
 
353
336
  **Pros:**
354
337
  - ✅ No single point of failure
338
+ - ✅ Double encryption: backup is encrypted, then fragments are distributed
339
+ - ✅ Guardians never see plaintext wallet data
340
+ - ✅ Password protection even after collecting shares
355
341
  - ✅ Survives your own forgetfulness
356
342
  - ✅ Survives any 2 guardians disappearing
357
343
  - ✅ Highest security and redundancy
358
344
 
359
345
  **Cons:**
360
346
  - ⚠️ Requires trusted friends/family
347
+ - ⚠️ Must remember password (guardians don't have it)
361
348
  - ⚠️ Setup complexity
362
349
  - ⚠️ Recovery takes time (24-48 hours)
363
350
  - ⚠️ Coordination required
@@ -365,9 +352,10 @@ await sdk.importMnemonic(mnemonic);
365
352
  **Recovery scenarios:**
366
353
  | Scenario | Can Recover? | How |
367
354
  |----------|--------------|-----|
368
- | Forgot password + lost devices | ✅ Yes | Contact 3 guardians → combine shares |
369
- | Lost backup + lost passkey | ✅ Yes | Contact 3 guardians |
355
+ | Lost all devices | ✅ Yes | Contact 3 guardians → combine shares → enter password |
356
+ | Lost backup + lost passkey | ✅ Yes | Contact 3 guardians → reconstruct backup file → enter password |
370
357
  | 2 guardians unavailable | ✅ Yes | Still have 3 others |
358
+ | Forgot password + have shares | ❌ No | Backup file is encrypted with password |
371
359
  | All guardians lost shares | ❌ No | Need another recovery layer |
372
360
 
373
361
  ---
@@ -396,45 +384,81 @@ await sdk.importMnemonic(mnemonic);
396
384
  ### **Social Recovery Cryptography**
397
385
 
398
386
  ```typescript
399
- Shamir Secret Sharing Algorithm:
387
+ Backup File-Based Shamir Secret Sharing:
400
388
 
401
389
  Example: 3-of-5 scheme
402
- - Split secret S into 5 shares: s1, s2, s3, s4, s5
403
- - Any 3 shares can reconstruct S
404
- - Any 2 shares reveal ZERO information about S
405
- - Each guardian only has an encrypted piece
390
+
391
+ Step 1: Create Encrypted Backup File
392
+ - User creates password-encrypted backup file
393
+ - Backup contains: { encryptedMnemonic, ethereumAddress, ... }
394
+ - Encryption: PBKDF2(password) → AES-256-GCM
395
+ - Result: BackupFile JSON (already encrypted)
396
+
397
+ Step 2: Split Backup File
398
+ - Serialize BackupFile to JSON string
399
+ - Convert JSON to bytes
400
+ - Split bytes using Shamir Secret Sharing (3-of-5)
401
+ - Result: 5 shares of encrypted data
402
+
403
+ Step 3: Distribute Shares
404
+ - Each guardian gets one share (hex-encoded fragment)
405
+ - Shares are fragments of the encrypted backup file
406
+ - Guardians never see the plaintext mnemonic
407
+ - Guardians don't have the password
408
+
409
+ Recovery Process:
410
+ - Collect 3+ shares from guardians
411
+ - Combine shares → reconstruct BackupFile JSON
412
+ - Decrypt with password → extract mnemonic
413
+ - Register new passkey or import to existing
406
414
 
407
415
  Mathematical basis:
408
- - Polynomial interpolation over finite field
416
+ - Polynomial interpolation over finite field GF(256)
409
417
  - Degree = threshold - 1 (e.g., degree-2 polynomial for 3-of-5)
410
418
  - Points on polynomial = shares
411
- - Reconstruct polynomial with 3 points → get secret
419
+ - Any 3 points → reconstruct polynomial → recover secret
420
+ - Any 2 points → mathematically impossible to recover
421
+ ```
422
+
423
+ **Security Model:**
424
+ ```
425
+ Double Encryption:
426
+ Layer 1: Backup file encrypted with password (AES-256-GCM)
427
+ Layer 2: Encrypted backup split into shares (Shamir)
428
+
429
+ Result:
430
+ - Guardians hold fragments of encrypted data
431
+ - Even with threshold shares, password still required
432
+ - No single guardian has access to wallet
433
+ - No collusion of guardians can bypass password
412
434
 
413
- Share encryption:
414
- Each share is encrypted with guardian's public key:
415
- - Guardian generates key pair (on their device)
416
- - Share encrypted with guardian's public key
417
- - Only guardian can decrypt their share
418
- - Prevents share theft from coordinator
435
+ Attack Scenarios:
436
+ Attacker steals 2 shares Cannot reconstruct
437
+ Attacker steals 3+ shares Still needs password
438
+ Attacker has password Still needs threshold shares
439
+ Attacker has password + threshold shares → Can recover
419
440
  ```
420
441
 
421
442
  **Trust model:**
422
443
  ```
423
444
  Coordinator (You):
445
+ - Must remember password (guardians don't have it)
424
446
  - Cannot recover from less than threshold shares
425
- - Can revoke/replace guardians
426
- - Can update threshold
447
+ - Can revoke/replace guardians (requires new setup)
448
+ - Can verify guardian shares are distributed
427
449
 
428
450
  Guardians:
429
- - Cannot recover alone
430
- - Cannot collude unless threshold met
451
+ - Hold encrypted fragments only
452
+ - Cannot decrypt without password
453
+ - Cannot recover alone (need threshold + password)
431
454
  - Can verify share validity
432
- - Can export/backup their share
455
+ - Can store share safely (it's already encrypted)
433
456
 
434
457
  Attacker:
435
458
  - Cannot recover with < threshold shares
436
- - Cannot brute-force (mathematically secure)
437
- - Must compromise guardians directly
459
+ - Cannot decrypt backup without password
460
+ - Must compromise both guardians AND password
461
+ - Mathematically secure (Shamir + AES-256-GCM)
438
462
  ```
439
463
 
440
464
  ---
@@ -485,24 +509,36 @@ await w3pk.importFromSync(backupData);
485
509
  ### Social Recovery
486
510
 
487
511
  ```typescript
488
- // Setup
489
- const { guardianShares } = await w3pk.setupSocialRecovery(guardians, threshold, password);
512
+ import { SocialRecoveryManager } from 'w3pk';
490
513
 
491
- // Generate invitations
492
- const invitation = await w3pk.generateGuardianInvite(guardianShare);
514
+ // Setup - Create password-encrypted backup and split among guardians
515
+ const { blob } = await w3pk.createBackupFile('password', 'MyPassword123!');
516
+ const backupFileJson = await blob.text();
493
517
 
494
- // Recover (returns mnemonic - then register or import)
495
- const { mnemonic, ethereumAddress } = await w3pk.recoverFromGuardians(shares, password);
518
+ const socialRecovery = new SocialRecoveryManager();
519
+ const guardians = await socialRecovery.setupSocialRecovery(
520
+ backupFileJson,
521
+ w3pk.user.ethereumAddress,
522
+ [
523
+ { name: 'Alice', email: 'alice@example.com' },
524
+ { name: 'Bob', email: 'bob@example.com' },
525
+ { name: 'Charlie', email: 'charlie@example.com' }
526
+ ],
527
+ 2 // threshold
528
+ );
496
529
 
497
- // Option 1: Register new passkey (stores credentials)
498
- const { BackupFileManager } = await import('w3pk/backup/backup-file');
499
- const manager = new BackupFileManager();
500
- const { backupFile } = await manager.createPasswordBackup(mnemonic, ethereumAddress, 'newpass');
501
- await w3pk.registerWithBackupFile(JSON.stringify(backupFile), 'newpass', 'username');
530
+ // Generate invitations for guardians
531
+ for (const guardian of guardians) {
532
+ const invite = await socialRecovery.generateGuardianInvite(guardian);
533
+ // Send invite.qrCode and invite.shareCode to guardian
534
+ }
502
535
 
503
- // Option 2: Import to existing passkey (stores credentials)
504
- await w3pk.login();
505
- await w3pk.importMnemonic(mnemonic);
536
+ // Recover - Collect shares and reconstruct encrypted backup file
537
+ const shares = [aliceShareCode, bobShareCode]; // JSON strings from guardians
538
+ const { backupFileJson, ethereumAddress } = await socialRecovery.recoverFromGuardians(shares);
539
+
540
+ // Decrypt and register with password
541
+ await w3pk.registerWithBackupFile(backupFileJson, 'MyPassword123!', 'username');
506
542
  ```
507
543
 
508
544
  ---
@@ -705,17 +741,20 @@ Platform-specific:
705
741
  3. Contact your guardians (need 3 of 5)
706
742
  4. Each guardian provides their share:
707
743
  - Scan QR code, OR
708
- - Enter share code manually
709
- 5. After 3 shares collected:
710
- - System reconstructs backup file
711
- - Enter password if used during setup
712
- - System extracts mnemonic
713
- 6. Choose recovery path:
714
- - "Register New Passkey" Creates new WebAuthn credential, stores locally ✅
715
- - "Login & Import"Login with existing passkey, import mnemonic
744
+ - Paste share code manually (JSON format)
745
+ 5. After collecting threshold shares (e.g., 3):
746
+ - System reconstructs encrypted backup file
747
+ - Backup file is still encrypted
748
+ 6. Enter your password:
749
+ - This is the password you set during social recovery setup
750
+ - Guardians DO NOT have this password
751
+ - System decrypts backup file extracts mnemonic
752
+ 7. Choose username and register:
753
+ - System creates new passkey on your device
754
+ - Wallet credentials stored locally ✅
716
755
 
717
756
  Timeline: ~24-48 hours (depends on guardian availability)
718
- Note: Either path stores credentials locally after recovery
757
+ Security: Requires both guardian shares AND your password
719
758
  ```
720
759
 
721
760
  #### **Method 4: Manual Mnemonic Import**
@@ -764,7 +803,7 @@ A: You'll need to use another recovery method (passkey sync or social recovery).
764
803
  A: Mathematically secure with Shamir's Secret Sharing. Any 2 guardians cannot recover (need 3 of 5).
765
804
 
766
805
  **Q: Can guardians steal my wallet?**
767
- A: No, each guardian only has an encrypted piece. They need 3 pieces minimum, and even then, the backup may be password-protected.
806
+ A: No. Guardians hold encrypted fragments of an already-encrypted backup file. Even if they collude and combine all shares, they still need your password to decrypt the backup file. The password is never shared with guardians.
768
807
 
769
808
  **Q: What happens if a guardian loses their share?**
770
809
  A: No problem! You only need 3 out of 5. As long as 3 guardians have their shares, you can recover.