w3pk 0.7.0 → 0.7.2

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.
package/docs/SECURITY.md CHANGED
@@ -813,6 +813,283 @@ Before deploying w3pk in production:
813
813
  - [ ] ✅ Security monitoring and alerting
814
814
  - [ ] ✅ User education materials prepared
815
815
 
816
+ ## Credential Scoping and Domain Isolation
817
+
818
+ ### Credentials are Domain-Specific
819
+
820
+ **Important:** Credentials created on one web application **cannot be used on another web application**, even for the same username. This is a fundamental WebAuthn security feature.
821
+
822
+ ### How It Works
823
+
824
+ When you register a credential, it is cryptographically bound to the domain:
825
+
826
+ ```typescript
827
+ // Registration on example.com
828
+ const registrationOptions = {
829
+ challenge,
830
+ rp: {
831
+ name: "w3pk",
832
+ id: window.location.hostname, // "example.com"
833
+ },
834
+ user: {
835
+ id: username,
836
+ name: username,
837
+ displayName: username,
838
+ },
839
+ // ...
840
+ }
841
+
842
+ // Authentication on example.com
843
+ const authOptions = {
844
+ challenge,
845
+ rpId: window.location.hostname, // Must be "example.com"
846
+ userVerification: "required",
847
+ // ...
848
+ }
849
+ ```
850
+
851
+ **Key Points:**
852
+
853
+ 1. **RP ID is auto-detected**: The Relying Party ID (RP ID) is automatically set to `window.location.hostname`
854
+ 2. **Cannot be configured**: Manual RP ID configuration was removed in v0.7.0 to enforce security
855
+ 3. **Cryptographically bound**: The WebAuthn credential private key is tied to the RP ID
856
+ 4. **Browser-enforced**: The browser's WebAuthn API enforces this isolation
857
+
858
+ ### Why Credentials Don't Work Across Domains
859
+
860
+ **Example scenario:**
861
+
862
+ ```typescript
863
+ // Step 1: Register on app1.com
864
+ // User visits: https://app1.com
865
+ await w3pk.register({ username: 'alice' })
866
+ // → RP ID: "app1.com"
867
+ // → Credential created and bound to "app1.com"
868
+ // → Stored in browser with origin: "https://app1.com"
869
+
870
+ // Step 2: Try to login on app2.com
871
+ // User visits: https://app2.com
872
+ await w3pk.login()
873
+ // → RP ID: "app2.com" (different!)
874
+ // → Browser WebAuthn API: "No credential found for RP ID 'app2.com'"
875
+ // → Login fails ❌
876
+
877
+ // Step 3: Must register separately on app2.com
878
+ await w3pk.register({ username: 'alice' })
879
+ // → Creates NEW credential for "app2.com"
880
+ // → This is a completely separate credential
881
+ ```
882
+
883
+ ### Security Guarantees
884
+
885
+ This domain isolation provides critical security guarantees:
886
+
887
+ #### ✅ Protection Against Phishing
888
+
889
+ ```typescript
890
+ // Legitimate site: example.com
891
+ await w3pk.register({ username: 'alice' })
892
+ // RP ID: "example.com"
893
+
894
+ // Phishing site: examp1e.com (note the "1")
895
+ await w3pk.login()
896
+ // RP ID: "examp1e.com" (different!)
897
+ // ❌ Credential not found - phishing attempt blocked
898
+ ```
899
+
900
+ The attacker **cannot** use your `example.com` credential even if they:
901
+ - Copy your localStorage data
902
+ - Copy your IndexedDB data
903
+ - Trick you into visiting their site
904
+ - Use an identical UI
905
+
906
+ The browser enforces that credentials for `example.com` can only be used on `example.com`.
907
+
908
+ #### ✅ Origin-Based Storage Isolation
909
+
910
+ ```typescript
911
+ // Browser storage is automatically scoped by origin
912
+ localStorage // Scoped to "https://example.com"
913
+ IndexedDB // Scoped to "https://example.com"
914
+
915
+ // A different origin cannot access this storage
916
+ // - https://attacker.com → different origin
917
+ // - https://subdomain.example.com → different origin (unless RP ID configured for parent)
918
+ // - http://example.com → different origin (different protocol)
919
+ ```
920
+
921
+ #### ✅ No Cross-Site Credential Replay
922
+
923
+ ```typescript
924
+ // Even if attacker intercepts network traffic
925
+ const stolenSignature = interceptFromNetwork()
926
+
927
+ // They cannot replay it on their site
928
+ await navigator.credentials.get({
929
+ publicKey: {
930
+ challenge: stolenChallenge,
931
+ rpId: "attacker.com", // Different RP ID!
932
+ // ...
933
+ }
934
+ })
935
+ // ❌ Browser rejects: "RP ID mismatch"
936
+ ```
937
+
938
+ ### Subdomain Considerations
939
+
940
+ **By default, credentials are scoped to the exact hostname:**
941
+
942
+ ```typescript
943
+ // Registered on: app.example.com
944
+ // RP ID: "app.example.com"
945
+
946
+ // Cannot use on: api.example.com (different subdomain)
947
+ // Cannot use on: example.com (parent domain)
948
+ ```
949
+
950
+ **Note:** The WebAuthn standard allows setting RP ID to a parent domain, but w3pk uses auto-detection which sets it to the exact hostname for maximum security.
951
+
952
+ ### Localhost and Development
953
+
954
+ During development, credentials are scoped to `localhost`:
955
+
956
+ ```typescript
957
+ // Development environment
958
+ window.location.hostname // "localhost"
959
+ // RP ID: "localhost"
960
+
961
+ // Credentials created during development:
962
+ // ✅ Work on: http://localhost:3000
963
+ // ✅ Work on: http://localhost:8080
964
+ // ✅ Work on: https://localhost:5173
965
+ // ❌ Don't work on: 127.0.0.1 (different hostname!)
966
+ ```
967
+
968
+ **Development tip:** Always use `localhost`, not `127.0.0.1`, for consistent RP ID.
969
+
970
+ ### Migration from v0.6.0 to v0.7.0
971
+
972
+ In v0.6.0, the RP ID could be manually configured:
973
+
974
+ ```typescript
975
+ // v0.6.0 (old)
976
+ const w3pk = createWeb3Passkey({
977
+ rpId: 'example.com', // Manual configuration
978
+ })
979
+ ```
980
+
981
+ In v0.7.0+, this was removed for security:
982
+
983
+ ```typescript
984
+ // v0.7.0+ (current)
985
+ const w3pk = createWeb3Passkey({
986
+ // rpId is auto-detected from window.location.hostname
987
+ // Cannot be overridden
988
+ })
989
+ ```
990
+
991
+ **Why this change?**
992
+ - Prevents misconfiguration
993
+ - Enforces best practices
994
+ - Eliminates cross-origin credential risks
995
+ - Simplifies API
996
+
997
+ ### Credential Storage and Scoping
998
+
999
+ **What's stored and where:**
1000
+
1001
+ ```typescript
1002
+ // localStorage (origin-scoped by browser)
1003
+ // Key: w3pk_credential_<credentialId>
1004
+ {
1005
+ "id": "credential-abc123",
1006
+ "publicKey": "MFkw...", // Public key only
1007
+ "username": "alice",
1008
+ "ethereumAddress": "0x1234...",
1009
+ "createdAt": 1234567890
1010
+ }
1011
+
1012
+ // IndexedDB (origin-scoped by browser)
1013
+ // Store: wallets
1014
+ {
1015
+ "ethereumAddress": "0x1234...",
1016
+ "encryptedMnemonic": "v1kT...", // AES-GCM encrypted
1017
+ "credentialId": "credential-abc123",
1018
+ "createdAt": 1234567890
1019
+ }
1020
+
1021
+ // Authenticator (hardware/platform)
1022
+ // WebAuthn private key (bound to RP ID)
1023
+ // - Cannot be exported
1024
+ // - Cannot be used for different RP ID
1025
+ // - Hardware-protected
1026
+ ```
1027
+
1028
+ **Security properties:**
1029
+
1030
+ 1. **localStorage**: Origin-scoped by browser (cannot access from different origin)
1031
+ 2. **IndexedDB**: Origin-scoped by browser + encrypted with WebAuthn signature
1032
+ 3. **Authenticator**: RP ID-bound + hardware-protected
1033
+
1034
+ ### Common Questions
1035
+
1036
+ **Q: Can I use the same wallet on multiple domains?**
1037
+
1038
+ A: No, each domain requires separate registration. However, you can import the same mnemonic on different domains to access the same wallet addresses:
1039
+
1040
+ ```typescript
1041
+ // On domain1.com
1042
+ const { mnemonic } = await w3pk.register({ username: 'alice' })
1043
+ // Save mnemonic: "word1 word2 ... word12"
1044
+
1045
+ // On domain2.com (later)
1046
+ await w3pk.register({
1047
+ username: 'alice',
1048
+ mnemonic: 'word1 word2 ... word12' // Import same mnemonic
1049
+ })
1050
+ // ✅ Same wallet addresses, different WebAuthn credential
1051
+ ```
1052
+
1053
+ **Q: What if I want to share credentials across subdomains?**
1054
+
1055
+ A: Currently not supported. Each subdomain requires separate registration. This is the most secure approach.
1056
+
1057
+ **Q: Can I migrate credentials between domains?**
1058
+
1059
+ A: WebAuthn credentials cannot be migrated, but wallets can:
1060
+
1061
+ 1. Export mnemonic from old domain
1062
+ 2. Register on new domain with same mnemonic
1063
+ 3. Same wallet addresses, new credential
1064
+
1065
+ **Q: What happens if I switch from `app.example.com` to `example.com`?**
1066
+
1067
+ A: These are different RP IDs. You'll need to re-register. Export your mnemonic first to preserve your wallet.
1068
+
1069
+ ### Security Best Practices
1070
+
1071
+ 1. **Educate users**: Make it clear that credentials are per-domain
1072
+ 2. **Prompt for backup**: Always prompt users to save their mnemonic after registration
1073
+ 3. **Test on production domain**: Don't expect development credentials to work in production
1074
+ 4. **Use consistent domains**: Avoid switching between `www.example.com` and `example.com`
1075
+ 5. **Display current domain**: Show users which domain they're authenticating for
1076
+
1077
+ ### Implementation Example
1078
+
1079
+ ```typescript
1080
+ // Show user which domain they're registering on
1081
+ const currentDomain = window.location.hostname
1082
+
1083
+ console.log(`🔐 Creating credential for: ${currentDomain}`)
1084
+ console.log(`⚠️ This credential will only work on ${currentDomain}`)
1085
+
1086
+ await w3pk.register({ username: 'alice' })
1087
+
1088
+ console.log(`✅ Credential created for ${currentDomain}`)
1089
+ console.log(`💾 Save your recovery phrase - you'll need it to access`)
1090
+ console.log(` this wallet on other domains or devices`)
1091
+ ```
1092
+
816
1093
  ## WebAuthn Security Features
817
1094
 
818
1095
  ### User Verification
@@ -925,6 +1202,360 @@ Modern authenticators (TouchID, Windows Hello, YubiKey) have **built-in secure s
925
1202
  - Mnemonic is the ultimate recovery mechanism
926
1203
  - WebAuthn is for convenience + security, not recovery
927
1204
 
1205
+ ## Backup & Recovery Security
1206
+
1207
+ w3pk implements a **three-layer backup and recovery system** that balances security, usability, and resilience. Each layer uses different cryptographic primitives and trust models.
1208
+
1209
+ ### Layer 1: Passkey Auto-Sync (Platform-Based)
1210
+
1211
+ **How it works:**
1212
+ - WebAuthn credentials automatically sync via platform services (iCloud Keychain, Google Password Manager, Microsoft Account)
1213
+ - Encrypted end-to-end by platform provider
1214
+ - Requires device unlock + cloud account authentication
1215
+
1216
+ **Security properties:**
1217
+ - ✅ **Encrypted in transit** - Platform handles E2E encryption
1218
+ - ✅ **Hardware-backed** - Credentials protected by Secure Enclave/TPM
1219
+ - ✅ **Automatic** - No user action required
1220
+ - ⚠️ **Platform trust** - Relies on Apple/Google/Microsoft security
1221
+ - ⚠️ **Ecosystem lock-in** - Cannot cross platforms (Apple → Android)
1222
+
1223
+ **Threat model:**
1224
+ | Threat | Protected? | Notes |
1225
+ |--------|-----------|-------|
1226
+ | Device loss (same ecosystem) | ✅ Yes | Credentials restore on new device |
1227
+ | Device loss (cross-platform) | ❌ No | Need Layer 2 (mnemonic) |
1228
+ | Platform account compromise | ⚠️ Depends | Platform MFA protects |
1229
+ | State-level attack on cloud | ⚠️ Possible | Platform E2E encryption helps |
1230
+
1231
+ ### Layer 2: Encrypted Backups (User-Controlled)
1232
+
1233
+ **How it works:**
1234
+ - Mnemonic encrypted with user-chosen password
1235
+ - Multiple backup formats: password-protected ZIP, QR code
1236
+ - Encryption: **AES-256-GCM** with **PBKDF2** (310,000 iterations, OWASP 2025 standard)
1237
+
1238
+ **Security properties:**
1239
+ - ✅ **Military-grade encryption** - AES-256-GCM
1240
+ - ✅ **Password-based** - User controls secret
1241
+ - ✅ **Offline storage** - Can be stored on paper/USB/safe
1242
+ - ✅ **Platform-independent** - Works across any device
1243
+ - ⚠️ **Password strength critical** - Weak password = vulnerable
1244
+
1245
+ **Cryptographic details:**
1246
+ ```typescript
1247
+ // Key derivation
1248
+ PBKDF2-SHA256
1249
+ ├─ Iterations: 310,000 (OWASP 2025)
1250
+ ├─ Salt: 32 bytes (random per backup)
1251
+ └─ Output: 256-bit key
1252
+
1253
+ // Encryption
1254
+ AES-256-GCM
1255
+ ├─ Key: From PBKDF2
1256
+ ├─ IV: 12 bytes (random per encryption)
1257
+ ├─ Auth tag: 16 bytes (automatic)
1258
+ └─ Additional data: Ethereum address (for integrity)
1259
+ ```
1260
+
1261
+ **Password validation:**
1262
+ w3pk enforces strong passwords:
1263
+ - Minimum 12 characters
1264
+ - Uppercase + lowercase + numbers + special chars
1265
+ - Not in common password list
1266
+ - Strength score ≥50/100 required
1267
+
1268
+ **Using `isStrongPassword` utility:**
1269
+ ```typescript
1270
+ import { isStrongPassword } from 'w3pk'
1271
+
1272
+ // Validate before creating backups
1273
+ const password = userInput
1274
+ if (!isStrongPassword(password)) {
1275
+ throw new Error('Password must be at least 12 characters with uppercase, lowercase, numbers, and special characters')
1276
+ }
1277
+
1278
+ // Now safe to create backup
1279
+ const blob = await w3pk.createZipBackup(password)
1280
+ ```
1281
+
1282
+ **Examples:**
1283
+ ```typescript
1284
+ isStrongPassword('MyStr0ng!Pass') // ✅ true
1285
+ isStrongPassword('C0mplex#Secure') // ✅ true
1286
+ isStrongPassword('weak') // ❌ false - too short
1287
+ isStrongPassword('NoNumbers!') // ❌ false - missing numbers
1288
+ isStrongPassword('Password123!') // ❌ false - contains "password"
1289
+ ```
1290
+
1291
+ **Threat model:**
1292
+ | Threat | Protected? | Notes |
1293
+ |--------|-----------|-------|
1294
+ | Backup file stolen | ✅ Yes | Requires password to decrypt |
1295
+ | Weak password | ⚠️ Vulnerable | User responsibility |
1296
+ | Password forgotten | ❌ Unrecoverable | Need Layer 3 (social recovery) |
1297
+ | Brute force (strong password) | ✅ Yes | 310k iterations slow down attacks |
1298
+ | Brute force (weak password) | ❌ Vulnerable | Minutes to hours with GPU |
1299
+
1300
+ **Brute force analysis:**
1301
+
1302
+ Assuming attacker has:
1303
+ - Modern GPU (RTX 4090)
1304
+ - ~100,000 PBKDF2-SHA256 hashes/second at 310k iterations
1305
+
1306
+ | Password Type | Entropy | Time to Crack |
1307
+ |--------------|---------|---------------|
1308
+ | `password123` (common) | ~20 bits | Seconds |
1309
+ | `MyPassword123!` (weak) | ~35 bits | Hours |
1310
+ | `MyS3cur3!Pass@2024` (medium) | ~50 bits | Months |
1311
+ | `correct horse battery staple` (strong) | ~80 bits | Centuries |
1312
+ | Truly random 16 chars | ~100 bits | Universe lifetime |
1313
+
1314
+ **Recommendation:** Use password manager to generate strong passwords or use multi-word passphrases (4+ random words).
1315
+
1316
+ ### Layer 3: Social Recovery (Distributed Trust)
1317
+
1318
+ **How it works:**
1319
+ - Mnemonic split into **N shares** using **Shamir Secret Sharing**
1320
+ - Requires **M-of-N** shares to recover (e.g., 3-of-5)
1321
+ - Each guardian receives encrypted share via QR code
1322
+ - Guardians never see the actual mnemonic
1323
+
1324
+ **Cryptographic details:**
1325
+ ```typescript
1326
+ // Shamir Secret Sharing over GF(256)
1327
+ ├─ Threshold: M (minimum shares needed)
1328
+ ├─ Total shares: N (total guardians)
1329
+ ├─ Secret: Mnemonic (67 bytes UTF-8)
1330
+ ├─ Polynomial degree: M-1
1331
+ ├─ Field: Galois Field GF(256)
1332
+ │ ├─ Primitive polynomial: x^8 + x^4 + x^3 + x + 1 (0x11b)
1333
+ │ ├─ Generator: 3
1334
+ │ └─ Lagrange interpolation for reconstruction
1335
+ └─ Share format:
1336
+ ├─ Byte 0: X coordinate (1-255)
1337
+ └─ Bytes 1-67: Y values (polynomial evaluation)
1338
+
1339
+ // Guardian share encryption
1340
+ AES-256-GCM (same as Layer 2)
1341
+ ├─ Optional: Guardian can password-protect their share
1342
+ └─ QR code includes guardian metadata + instructions
1343
+ ```
1344
+
1345
+ **Security properties:**
1346
+ - ✅ **Information-theoretic security** - Cannot learn secret from M-1 shares
1347
+ - ✅ **Distributed trust** - No single point of failure
1348
+ - ✅ **Privacy-preserving** - Guardians never see mnemonic
1349
+ - ✅ **Flexible threshold** - Customize M-of-N based on risk tolerance
1350
+ - ⚠️ **Coordination required** - Must contact M guardians
1351
+ - ⚠️ **Guardian trust** - Guardians could collude (if ≥M)
1352
+
1353
+ **Threat model:**
1354
+ | Threat | Protected? | Notes |
1355
+ |--------|-----------|-------|
1356
+ | M-1 guardians compromised | ✅ Yes | Cannot recover without Mth share |
1357
+ | M guardians collude | ❌ Vulnerable | Can reconstruct mnemonic |
1358
+ | All guardians lost | ❌ Unrecoverable | Need Layer 2 backup |
1359
+ | Guardian share stolen | ✅ Depends | If password-protected, still safe |
1360
+ | User forgets who guardians are | ⚠️ Problem | Keep guardian list separately |
1361
+
1362
+ **Information-theoretic security proof:**
1363
+
1364
+ Shamir Secret Sharing over GF(256) provides perfect secrecy:
1365
+ - Given M-1 shares, **every possible secret is equally likely**
1366
+ - Attacker learns **zero bits** of information about secret
1367
+ - No amount of computation can break this (unlike encryption)
1368
+
1369
+ Mathematical proof:
1370
+ ```
1371
+ For threshold M and secret S:
1372
+ - Polynomial P(x) = a₀ + a₁x + ... + aₘ₋₁x^(M-1)
1373
+ - Secret: S = P(0) = a₀
1374
+ - Share i: Sᵢ = P(i)
1375
+
1376
+ Given M-1 shares {S₁, S₂, ..., Sₘ₋₁}:
1377
+ - Infinite polynomials pass through these points
1378
+ - Each yields different P(0) = a₀
1379
+ - All secrets equally probable
1380
+ - H(S | S₁,...,Sₘ₋₁) = H(S) [Shannon entropy unchanged]
1381
+ ```
1382
+
1383
+ **Example configuration:**
1384
+
1385
+ | Scenario | Threshold | Guardians | Rationale |
1386
+ |----------|-----------|-----------|-----------|
1387
+ | High paranoia | 5-of-7 | 7 close friends | Can lose 2 guardians |
1388
+ | Balanced | 3-of-5 | 5 trusted contacts | Standard recommendation |
1389
+ | Convenience | 2-of-3 | 3 family members | Easy to coordinate |
1390
+ | Multi-sig like | 2-of-2 | 2 co-owners | Both must agree |
1391
+
1392
+ ### Layered Security Strategy
1393
+
1394
+ **Defense in depth:**
1395
+ ```
1396
+ ┌─────────────────────────────────────────────┐
1397
+ │ Recovery Scenario │
1398
+ ├─────────────────────────────────────────────┤
1399
+ │ │
1400
+ │ Lost Device (Same Platform) │
1401
+ │ └─> Layer 1: Passkey Sync ✅ RECOVERED │
1402
+ │ │
1403
+ │ Lost Device (Cross-Platform) │
1404
+ │ └─> Layer 1: Failed ❌ │
1405
+ │ └─> Layer 2: Encrypted Backup ✅ RECOVERED │
1406
+ │ │
1407
+ │ Lost Device + Forgot Password │
1408
+ │ └─> Layer 1: Failed ❌ │
1409
+ │ └─> Layer 2: Failed ❌ │
1410
+ │ └─> Layer 3: Social Recovery ✅ RECOVERED │
1411
+ │ │
1412
+ │ Lost Everything + All Guardians Lost │
1413
+ │ └─> ❌ UNRECOVERABLE │
1414
+ │ │
1415
+ └─────────────────────────────────────────────┘
1416
+ ```
1417
+
1418
+ **Security scoring:**
1419
+
1420
+ w3pk calculates a security score (0-100) based on active backup methods:
1421
+
1422
+ | Configuration | Score | Level |
1423
+ |--------------|-------|-------|
1424
+ | No backups | 0-25 | 🔴 Vulnerable |
1425
+ | Passkey sync only | 30-50 | 🟡 Protected |
1426
+ | Passkey + encrypted backup | 60-80 | 🟢 Secured |
1427
+ | All three layers | 85-100 | 🟦 Fort Knox |
1428
+
1429
+ **Score calculation:**
1430
+ ```typescript
1431
+ score = 0
1432
+ + (passkeySync.enabled ? 30 : 0)
1433
+ + (backups.zip > 0 ? 25 : 0)
1434
+ + (backups.qr > 0 ? 15 : 0)
1435
+ + (socialRecovery.configured ? 30 : 0)
1436
+ ```
1437
+
1438
+ ### Backup Best Practices
1439
+
1440
+ **1. Use multiple layers:**
1441
+ ```typescript
1442
+ // ✅ GOOD: Enable all three layers
1443
+ await w3pk.setupSocialRecovery([...guardians], 3)
1444
+ await w3pk.createZipBackup('MyS3cur3!Password@2024')
1445
+ // Passkey sync enabled by default on platform
1446
+
1447
+ // ❌ BAD: Rely on single layer
1448
+ // (only passkey sync - what if switch platforms?)
1449
+ ```
1450
+
1451
+ **2. Test recovery before trusting:**
1452
+ ```typescript
1453
+ // Simulate recovery scenarios
1454
+ const test1 = await w3pk.simulateRecoveryScenario({
1455
+ type: 'lost-device',
1456
+ hasBackup: true,
1457
+ hasSocialRecovery: true
1458
+ })
1459
+ console.log('Can recover?', test1.canRecover)
1460
+
1461
+ const test2 = await w3pk.simulateRecoveryScenario({
1462
+ type: 'lost-phrase',
1463
+ hasPasskeySync: true
1464
+ })
1465
+ console.log('Can recover?', test2.canRecover)
1466
+ ```
1467
+
1468
+ **3. Store backups securely:**
1469
+ ```typescript
1470
+ // ✅ GOOD: Offline, encrypted, geographically distributed
1471
+ - Physical safe (home)
1472
+ - Safety deposit box (bank)
1473
+ - Encrypted USB drive (office)
1474
+ - Password manager (different password)
1475
+
1476
+ // ❌ BAD: Digital-only, centralized
1477
+ - Cloud storage unencrypted
1478
+ - Email to self
1479
+ - Single location
1480
+ - Shared with others
1481
+ ```
1482
+
1483
+ **4. Choose guardians wisely:**
1484
+ ```typescript
1485
+ // ✅ GOOD guardian criteria:
1486
+ - Trustworthy (won't collude)
1487
+ - Available (can reach when needed)
1488
+ - Technical (understands basic security)
1489
+ - Diverse (different locations/relationships)
1490
+ - Long-term (stable relationship)
1491
+
1492
+ // ❌ BAD guardian choices:
1493
+ - All family members (could collude)
1494
+ - All same location (disaster risk)
1495
+ - Strangers/acquaintances
1496
+ - People who might lose share
1497
+ ```
1498
+
1499
+ **5. Use strong passwords:**
1500
+ ```typescript
1501
+ // ✅ GOOD passwords:
1502
+ 'correct horse battery staple' // Multi-word passphrase
1503
+ 'MyS3cur3!Backup@December2024' // Long with variety
1504
+ (password manager generated) // Truly random
1505
+
1506
+ // ❌ BAD passwords:
1507
+ 'password123' // Common
1508
+ 'MyPassword' // Dictionary word
1509
+ '12345678' // Sequential
1510
+ 'qwerty123' // Keyboard pattern
1511
+ ```
1512
+
1513
+ ### API Security Considerations
1514
+
1515
+ **All backup operations require authentication:**
1516
+ ```typescript
1517
+ // These operations trigger biometric prompt
1518
+ await w3pk.createZipBackup(password) // ✅ Auth required
1519
+ await w3pk.createQRBackup(password) // ✅ Auth required
1520
+ await w3pk.setupSocialRecovery(...) // ✅ Auth required
1521
+ await w3pk.exportMnemonic() // ✅ Auth required
1522
+
1523
+ // Read-only operations don't require auth
1524
+ await w3pk.getBackupStatus() // ✅ No auth needed
1525
+ await w3pk.getSyncStatus() // ✅ No auth needed
1526
+ ```
1527
+
1528
+ **Password validation is client-side:**
1529
+ ⚠️ **Important:** Password strength is checked in the browser. A determined attacker with code execution could bypass validation and create backups with weak passwords.
1530
+
1531
+ **Mitigation:**
1532
+ - Use `requireAuth: true` for backup creation
1533
+ - Short session durations
1534
+ - XSS/injection protection (CSP, input sanitization)
1535
+ - Educate users on password strength
1536
+
1537
+ **Recovery operations don't require authentication:**
1538
+ ```typescript
1539
+ // Recovery from existing backups is public
1540
+ await w3pk.restoreFromBackup(encryptedData, password)
1541
+ await w3pk.recoverFromGuardians([shares...])
1542
+
1543
+ // Rationale: If user has backup data + password/shares,
1544
+ // they own the wallet regardless of authentication
1545
+ ```
1546
+
1547
+ ### Comparison with Other Recovery Systems
1548
+
1549
+ | Recovery Method | w3pk Layer 1 | w3pk Layer 2 | w3pk Layer 3 | Traditional Seed | Hardware Wallet |
1550
+ |----------------|--------------|--------------|--------------|------------------|-----------------|
1551
+ | **Automatic** | ✅ Yes | ❌ Manual | ❌ Manual | ❌ Manual | ❌ Manual |
1552
+ | **Cross-platform** | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
1553
+ | **Offline storage** | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | N/A |
1554
+ | **No single point** | ❌ No | ❌ No | ✅ Yes | ❌ No | ❌ No |
1555
+ | **Cryptographic** | ✅ E2E | ✅ AES-256 | ✅ Shamir | N/A | N/A |
1556
+ | **User effort** | None | Medium | High | Low | None |
1557
+ | **Trust model** | Platform | Self | Distributed | Self | Self |
1558
+
928
1559
  ## Best Practices for Users
929
1560
 
930
1561
  ### 1. **Always Save Your Mnemonic**
@@ -22,23 +22,25 @@ The w3pk SDK supports general-purpose zero-knowledge proofs that enable users to
22
22
  ### Installation
23
23
 
24
24
  ```bash
25
- npm install w3pk
26
- # ZK dependencies (snarkjs, circomlibjs) are included automatically
25
+ npm install w3pk ethers
26
+ # Optional: Install ZK dependencies if you'll use ZK features
27
+ npm install snarkjs circomlibjs
27
28
  ```
28
29
 
30
+ **Note**: ZK dependencies are optional. The SDK automatically loads them only when you access `w3pk.zk`, keeping your bundle small if you don't use ZK features.
31
+
29
32
  ### Quick Start
30
33
 
31
34
  ```typescript
32
35
  import { createWeb3Passkey } from 'w3pk'
33
36
 
34
37
  const w3pk = createWeb3Passkey({
35
- apiBaseUrl: 'https://webauthn.w3hc.org',
36
38
  zkProofs: {
37
39
  enabledProofs: ['membership', 'threshold', 'range']
38
40
  }
39
41
  })
40
42
 
41
- // Access ZK module
43
+ // Access ZK module (loads automatically on first use)
42
44
  const zk = w3pk.zk
43
45
  ```
44
46
 
package/docs/index.html CHANGED
@@ -200,9 +200,10 @@
200
200
  const username = 'user_' + Date.now();
201
201
  const result = await window.w3pk.register({ username });
202
202
  console.log('Registration result:', result);
203
- window.showResult(`User registered: ${username}`, {
204
- username,
205
- mnemonic: result.mnemonic,
203
+ window.showResult(`User registered: ${result.username}`, {
204
+ username: result.username,
205
+ address: result.address,
206
+ isAuthenticated: window.w3pk.isAuthenticated,
206
207
  user: window.w3pk.user
207
208
  }, 'success');
208
209
  } catch (error) {