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/README.md +76 -19
- package/dist/index.d.mts +650 -5
- package/dist/index.d.ts +650 -5
- package/dist/index.js +776 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +776 -5
- package/dist/index.mjs.map +1 -1
- package/docs/BUNDLE_SIZES.md +7 -4
- package/docs/MIGRATION.md +15 -15
- package/docs/QR_CODE.md +1887 -0
- package/docs/RECOVERY.md +992 -0
- package/docs/SECURITY.md +631 -0
- package/docs/ZK_INTEGRATION_GUIDE.md +6 -4
- package/docs/index.html +4 -3
- package/package.json +9 -2
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
|
|
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
|
-
|
|
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) {
|