@cryptforge/auth 0.1.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.
- package/README.md +914 -0
- package/dist/index.d.mts +372 -0
- package/dist/index.d.ts +372 -0
- package/dist/index.js +1198 -0
- package/dist/index.mjs +1174 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,914 @@
|
|
|
1
|
+
# @cryptforge/auth
|
|
2
|
+
|
|
3
|
+
Browser-compatible authentication and key management for cryptocurrency wallets. Built on industry-standard BIP39/BIP44 with secure keystore encryption.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Secure Key Management** - BIP39 mnemonics encrypted with PBKDF2 + AES-256-CBC
|
|
8
|
+
- 🌐 **Browser-First** - Zero configuration, works everywhere (browser, Node.js, Electron, React Native)
|
|
9
|
+
- 🔗 **Multi-Blockchain** - Pluggable adapter system for any blockchain
|
|
10
|
+
- 👤 **Multi-Identity** - Manage multiple wallets with separate keystores
|
|
11
|
+
- 🔒 **Auto-Lock** - Configurable session timeouts for security
|
|
12
|
+
- ⏱️ **Key Expiration** - Automatic tracking with live countdowns
|
|
13
|
+
- 💾 **IndexedDB Storage** - Encrypted keystores persisted locally
|
|
14
|
+
- 🎯 **Type-Safe** - Full TypeScript support
|
|
15
|
+
- 📦 **Lightweight** - ~26 KB minified
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @cryptforge/auth @cryptforge/blockchain-evm @cryptforge/blockchain-btc
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { createAuthClient } from "@cryptforge/auth";
|
|
27
|
+
import { EVMAdapter } from "@cryptforge/blockchain-evm";
|
|
28
|
+
import { BitcoinAdapter } from "@cryptforge/blockchain-btc";
|
|
29
|
+
|
|
30
|
+
// Create auth client
|
|
31
|
+
const auth = createAuthClient();
|
|
32
|
+
|
|
33
|
+
// Register blockchain adapters
|
|
34
|
+
auth.registerAdapter(
|
|
35
|
+
"ethereum",
|
|
36
|
+
new EVMAdapter({
|
|
37
|
+
chainData: { name: "Ethereum", symbol: "ETH", cmc_id: 1027 },
|
|
38
|
+
coinType: 60,
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
auth.registerAdapter("bitcoin", new BitcoinAdapter());
|
|
43
|
+
|
|
44
|
+
// Generate mnemonic
|
|
45
|
+
const mnemonic = auth.generateMnemonic({ wordCount: 12 });
|
|
46
|
+
|
|
47
|
+
// Create identity
|
|
48
|
+
const { identity, keys } = await auth.createIdentity({
|
|
49
|
+
mnemonic,
|
|
50
|
+
password: "secure-password",
|
|
51
|
+
label: "Personal Wallet",
|
|
52
|
+
chainId: "ethereum",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
console.log("Address:", keys.address);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Usage Guide
|
|
59
|
+
|
|
60
|
+
### First Time Setup
|
|
61
|
+
|
|
62
|
+
#### 1. Generate and Backup Mnemonic
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Generate a new mnemonic
|
|
66
|
+
const mnemonic = auth.generateMnemonic({ wordCount: 12 });
|
|
67
|
+
// Example: "abandon ability able about above absent absorb abstract absurd abuse access accident"
|
|
68
|
+
|
|
69
|
+
// ⚠️ CRITICAL: Display to user and require backup confirmation
|
|
70
|
+
// User must write down or securely store the mnemonic
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### 2. Create Identity
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const { identity, keys } = await auth.createIdentity({
|
|
77
|
+
mnemonic: mnemonic,
|
|
78
|
+
password: "user-chosen-password",
|
|
79
|
+
label: "Personal Wallet",
|
|
80
|
+
metadata: {
|
|
81
|
+
createdBy: "MyApp",
|
|
82
|
+
version: "1.0.0",
|
|
83
|
+
},
|
|
84
|
+
chainId: "ethereum", // Optional: unlock with specific chain
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Identity created and encrypted in IndexedDB
|
|
88
|
+
// If chainId provided, wallet is unlocked and ready to use
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Returning User Flow
|
|
92
|
+
|
|
93
|
+
#### 1. List Available Identities
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
const identities = await auth.listIdentities();
|
|
97
|
+
// [
|
|
98
|
+
// {
|
|
99
|
+
// id: 'identity_A1B2C3D4',
|
|
100
|
+
// label: 'Personal Wallet',
|
|
101
|
+
// fingerprint: 'A1B2C3D4',
|
|
102
|
+
// createdAt: Date,
|
|
103
|
+
// lastAccess: Date
|
|
104
|
+
// }
|
|
105
|
+
// ]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 2. Select and Unlock
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Select an identity
|
|
112
|
+
await auth.switchIdentity(identities[0].id);
|
|
113
|
+
|
|
114
|
+
// Unlock with password
|
|
115
|
+
const { keys } = await auth.unlock({
|
|
116
|
+
password: "user-chosen-password",
|
|
117
|
+
chainId: "ethereum",
|
|
118
|
+
duration: 10 * 60 * 1000, // Auto-lock after 10 minutes
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Wallet is now unlocked and ready to sign
|
|
122
|
+
console.log("Address:", keys.address);
|
|
123
|
+
console.log("Expires in:", auth.currentExpiresIn, "seconds");
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Core Features
|
|
127
|
+
|
|
128
|
+
### Identity Management
|
|
129
|
+
|
|
130
|
+
#### Create Identity
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const { identity, keys } = await auth.createIdentity({
|
|
134
|
+
mnemonic: "word1 word2 ... word12",
|
|
135
|
+
password: "secure-password",
|
|
136
|
+
label: "Trading Wallet",
|
|
137
|
+
metadata: { purpose: "trading" },
|
|
138
|
+
chainId: "ethereum", // Optional
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### Import Identity
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// From mnemonic backup
|
|
146
|
+
const { identity } = await auth.importIdentity(
|
|
147
|
+
"word1 word2 ... word12",
|
|
148
|
+
"new-password",
|
|
149
|
+
"mnemonic"
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// From keystore JSON
|
|
153
|
+
const { identity } = await auth.importIdentity(
|
|
154
|
+
keystoreJsonString,
|
|
155
|
+
"password",
|
|
156
|
+
"keystore"
|
|
157
|
+
);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Export Identity
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// Export as mnemonic (for backup)
|
|
164
|
+
const { data: mnemonic } = await auth.exportIdentity(identity.id, {
|
|
165
|
+
password: "password",
|
|
166
|
+
format: "mnemonic",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Export as keystore JSON
|
|
170
|
+
const { data: keystore } = await auth.exportIdentity(identity.id, {
|
|
171
|
+
password: "password",
|
|
172
|
+
format: "keystore",
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Delete Identity
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Permanently delete (requires password)
|
|
180
|
+
await auth.deleteIdentity(identity.id, "password");
|
|
181
|
+
// ⚠️ Make sure mnemonic is backed up first!
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### Update Identity
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
await auth.updateIdentity(identity.id, {
|
|
188
|
+
label: "Updated Wallet Name",
|
|
189
|
+
metadata: { theme: "dark" },
|
|
190
|
+
});
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### Change Password
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
await auth.changePassword(identity.id, "old-password", "new-password");
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Session Management
|
|
200
|
+
|
|
201
|
+
#### Unlock
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const { keys } = await auth.unlock({
|
|
205
|
+
password: "password",
|
|
206
|
+
chainId: "ethereum",
|
|
207
|
+
duration: 15 * 60 * 1000, // Optional: auto-lock after 15 min
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Access derived keys
|
|
211
|
+
console.log("Address:", keys.address);
|
|
212
|
+
console.log("Public Key:", keys.publicKeyHex);
|
|
213
|
+
console.log("Derivation Path:", keys.derivationPath);
|
|
214
|
+
console.log("Expires At:", keys.expiresAt);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Lock
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
await auth.lock();
|
|
221
|
+
// Clears keys from memory
|
|
222
|
+
// Keystore remains encrypted in IndexedDB
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Blockchain Operations
|
|
226
|
+
|
|
227
|
+
#### Switch Chain
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Switch to different blockchain (if already unlocked)
|
|
231
|
+
await auth.switchChain("bitcoin");
|
|
232
|
+
|
|
233
|
+
// Switch when locked (requires password)
|
|
234
|
+
await auth.switchChain("bitcoin", "password");
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Get Addresses
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// Get multiple addresses for a chain
|
|
241
|
+
const addresses = await auth.getAddresses("ethereum", 0, 5);
|
|
242
|
+
// [
|
|
243
|
+
// { address: '0x...', path: "m/44'/60'/0'/0/0", index: 0 },
|
|
244
|
+
// { address: '0x...', path: "m/44'/60'/0'/0/1", index: 1 },
|
|
245
|
+
// ...
|
|
246
|
+
// ]
|
|
247
|
+
|
|
248
|
+
// Get specific address by index
|
|
249
|
+
const { address, publicKey, derivationPath } = await auth.getAddressForChain(
|
|
250
|
+
"bitcoin",
|
|
251
|
+
0
|
|
252
|
+
);
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Find Used Addresses (Account Discovery)
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
// Find all addresses with balance
|
|
259
|
+
const usedAddresses = await auth.findUsedAddresses(
|
|
260
|
+
"ethereum",
|
|
261
|
+
async (address) => {
|
|
262
|
+
// Your balance checking logic
|
|
263
|
+
const balance = await checkBalance(address);
|
|
264
|
+
return balance > 0;
|
|
265
|
+
}
|
|
266
|
+
);
|
|
267
|
+
// Scans with BIP44 gap limit of 20
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Cryptographic Operations
|
|
271
|
+
|
|
272
|
+
#### Sign Message
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const { signature, address, publicKey } = await auth.signMessage({
|
|
276
|
+
message: "Hello CryptForge!",
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Sign with different derivation path
|
|
280
|
+
const result = await auth.signMessage({
|
|
281
|
+
message: "Custom path",
|
|
282
|
+
derivationPath: "m/44'/60'/0'/0/1",
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### Sign Transaction
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
const { signedTransaction, signature } = await auth.signTransaction({
|
|
290
|
+
transaction: {
|
|
291
|
+
to: "0x...",
|
|
292
|
+
value: "1000000000000000000", // 1 ETH in wei
|
|
293
|
+
gasLimit: 21000,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// Sign with different key
|
|
298
|
+
const result = await auth.signTransaction({
|
|
299
|
+
transaction: tx,
|
|
300
|
+
derivationPath: "m/44'/60'/0'/0/5",
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
#### Verify Signature
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const isValid = await auth.verifySignature(
|
|
308
|
+
"Hello CryptForge!",
|
|
309
|
+
signature,
|
|
310
|
+
publicKey
|
|
311
|
+
);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Key Management
|
|
315
|
+
|
|
316
|
+
#### Rotate Keys
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// Rotate to next address index
|
|
320
|
+
const { keys } = await auth.rotateKeys();
|
|
321
|
+
console.log("New address:", keys.address);
|
|
322
|
+
|
|
323
|
+
// Rotate to custom derivation path
|
|
324
|
+
const { keys } = await auth.rotateKeys("m/44'/60'/0'/0/5");
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### Derive Custom Key
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
// One-time key derivation (not stored in session)
|
|
331
|
+
const { privateKey, publicKey, address, path } = await auth.deriveKey({
|
|
332
|
+
path: "m/44'/60'/1'/0/0", // Different account
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## State Management
|
|
337
|
+
|
|
338
|
+
### Getters
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
// Identity state
|
|
342
|
+
auth.currentIdentity; // Identity | null
|
|
343
|
+
auth.hasIdentity; // boolean
|
|
344
|
+
|
|
345
|
+
// Keys state
|
|
346
|
+
auth.currentKeys; // Keys | null
|
|
347
|
+
auth.currentAddress; // string | null
|
|
348
|
+
auth.currentPublicKey; // string | null (hex)
|
|
349
|
+
|
|
350
|
+
// Chain state
|
|
351
|
+
auth.currentChain; // Chain | null
|
|
352
|
+
|
|
353
|
+
// Lock state
|
|
354
|
+
auth.isLocked; // boolean
|
|
355
|
+
auth.isUnlocked; // boolean
|
|
356
|
+
|
|
357
|
+
// Expiration state
|
|
358
|
+
auth.currentExpiresAt; // Date | null
|
|
359
|
+
auth.currentExpiresIn; // number | null (seconds remaining)
|
|
360
|
+
|
|
361
|
+
// Available chains
|
|
362
|
+
auth.getRegisteredChains(); // string[]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Event Subscription
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
const unsubscribe = auth.onAuthStateChange((event, keys) => {
|
|
369
|
+
console.log("Event:", event);
|
|
370
|
+
|
|
371
|
+
switch (event) {
|
|
372
|
+
case "IDENTITY_CREATED":
|
|
373
|
+
console.log("New identity created");
|
|
374
|
+
break;
|
|
375
|
+
case "UNLOCKED":
|
|
376
|
+
console.log("Wallet unlocked:", keys?.address);
|
|
377
|
+
break;
|
|
378
|
+
case "LOCKED":
|
|
379
|
+
console.log("Wallet locked");
|
|
380
|
+
break;
|
|
381
|
+
case "CHAIN_SWITCHED":
|
|
382
|
+
console.log("Switched to:", keys?.chain.name);
|
|
383
|
+
break;
|
|
384
|
+
case "KEYS_ROTATED":
|
|
385
|
+
console.log("Keys rotated to:", keys?.address);
|
|
386
|
+
break;
|
|
387
|
+
case "KEYS_EXPIRED":
|
|
388
|
+
console.log("Keys expired, please unlock again");
|
|
389
|
+
break;
|
|
390
|
+
// ... other events
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
// Unsubscribe when done
|
|
395
|
+
unsubscribe();
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Available Events
|
|
399
|
+
|
|
400
|
+
- `IDENTITY_CREATED` - New identity created
|
|
401
|
+
- `IDENTITY_RESTORED` - Identity imported/restored
|
|
402
|
+
- `IDENTITY_SWITCHED` - Switched to different identity
|
|
403
|
+
- `IDENTITY_UPDATED` - Identity metadata updated
|
|
404
|
+
- `IDENTITY_DELETED` - Identity removed
|
|
405
|
+
- `PASSWORD_CHANGED` - Password updated
|
|
406
|
+
- `UNLOCKED` - Wallet unlocked with keys
|
|
407
|
+
- `LOCKED` - Wallet locked
|
|
408
|
+
- `KEYS_ROTATED` - Keys rotated to new address
|
|
409
|
+
- `KEYS_EXPIRED` - Keys passed expiration time
|
|
410
|
+
- `KEY_DERIVED` - Custom key derived
|
|
411
|
+
- `CHAIN_SWITCHED` - Switched to different blockchain
|
|
412
|
+
|
|
413
|
+
## Blockchain Adapters
|
|
414
|
+
|
|
415
|
+
### Registering Adapters
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
import { EVMAdapter } from "@cryptforge/blockchain-evm";
|
|
419
|
+
import { BitcoinAdapter } from "@cryptforge/blockchain-btc";
|
|
420
|
+
|
|
421
|
+
// Ethereum
|
|
422
|
+
auth.registerAdapter(
|
|
423
|
+
"ethereum",
|
|
424
|
+
new EVMAdapter({
|
|
425
|
+
chainData: { name: "Ethereum", symbol: "ETH", cmc_id: 1027 },
|
|
426
|
+
coinType: 60,
|
|
427
|
+
networks: {
|
|
428
|
+
mainnet: {
|
|
429
|
+
name: "Ethereum Mainnet",
|
|
430
|
+
rpcUrl: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
|
|
431
|
+
chainId: 1,
|
|
432
|
+
},
|
|
433
|
+
},
|
|
434
|
+
})
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// Bitcoin
|
|
438
|
+
auth.registerAdapter("bitcoin", new BitcoinAdapter());
|
|
439
|
+
|
|
440
|
+
// Polygon (EVM-compatible)
|
|
441
|
+
auth.registerAdapter(
|
|
442
|
+
"polygon",
|
|
443
|
+
new EVMAdapter({
|
|
444
|
+
chainData: { name: "Polygon", symbol: "MATIC", cmc_id: 3890 },
|
|
445
|
+
coinType: 60,
|
|
446
|
+
networks: {
|
|
447
|
+
mainnet: {
|
|
448
|
+
name: "Polygon Mainnet",
|
|
449
|
+
rpcUrl: "https://polygon-rpc.com",
|
|
450
|
+
chainId: 137,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
})
|
|
454
|
+
);
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
### Creating Custom Adapters
|
|
458
|
+
|
|
459
|
+
Implement the `BlockchainAdapter` interface to add support for new blockchains:
|
|
460
|
+
|
|
461
|
+
```typescript
|
|
462
|
+
import type { BlockchainAdapter, KeyData, ChainData } from "@cryptforge/core";
|
|
463
|
+
|
|
464
|
+
class SolanaAdapter implements BlockchainAdapter {
|
|
465
|
+
readonly chainData: ChainData = {
|
|
466
|
+
name: "Solana",
|
|
467
|
+
symbol: "SOL",
|
|
468
|
+
cmc_id: 5426,
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
async deriveKeys(mnemonic: string): Promise<KeyData> {
|
|
472
|
+
// Implement Solana key derivation
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
async deriveKeysAtIndex(mnemonic: string, index: number): Promise<KeyData> {
|
|
476
|
+
// Implement indexed derivation
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ... implement all required methods
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Register your custom adapter
|
|
483
|
+
auth.registerAdapter("solana", new SolanaAdapter());
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
## API Reference
|
|
487
|
+
|
|
488
|
+
### Methods
|
|
489
|
+
|
|
490
|
+
#### Identity Management
|
|
491
|
+
|
|
492
|
+
| Method | Parameters | Returns | Description |
|
|
493
|
+
| ------------------ | ------------------------------------------------------------------- | ----------------------------- | ----------------------- |
|
|
494
|
+
| `generateMnemonic` | `options?: { wordCount?: 12 \| 24 }` | `string` | Generate BIP39 mnemonic |
|
|
495
|
+
| `createIdentity` | `CreateIdentityOptions & { chainId?: string }` | `Promise<{ identity, keys }>` | Create new identity |
|
|
496
|
+
| `importIdentity` | `data: string, password: string, format?: 'mnemonic' \| 'keystore'` | `Promise<{ identity }>` | Import from backup |
|
|
497
|
+
| `exportIdentity` | `identityId: string, options: ExportOptions` | `Promise<{ format, data }>` | Export identity |
|
|
498
|
+
| `listIdentities` | - | `Promise<Identity[]>` | Get all identities |
|
|
499
|
+
| `switchIdentity` | `identityId: string` | `Promise<{ identity }>` | Switch active identity |
|
|
500
|
+
| `updateIdentity` | `identityId: string, updates: { label?, metadata? }` | `Promise<{ identity }>` | Update metadata |
|
|
501
|
+
| `deleteIdentity` | `identityId: string, password: string` | `Promise<void>` | Delete identity |
|
|
502
|
+
| `changePassword` | `identityId: string, oldPassword: string, newPassword: string` | `Promise<void>` | Change password |
|
|
503
|
+
|
|
504
|
+
#### Session Management
|
|
505
|
+
|
|
506
|
+
| Method | Parameters | Returns | Description |
|
|
507
|
+
| -------- | --------------- | ------------------- | ---------------------- |
|
|
508
|
+
| `unlock` | `UnlockOptions` | `Promise<{ keys }>` | Decrypt and load keys |
|
|
509
|
+
| `lock` | - | `Promise<void>` | Clear keys from memory |
|
|
510
|
+
|
|
511
|
+
#### Chain Management
|
|
512
|
+
|
|
513
|
+
| Method | Parameters | Returns | Description |
|
|
514
|
+
| --------------------- | --------------------------------------------- | ------------------- | ------------------------ |
|
|
515
|
+
| `registerAdapter` | `chainId: string, adapter: BlockchainAdapter` | `void` | Register blockchain |
|
|
516
|
+
| `switchChain` | `chainId: string, password?: string` | `Promise<{ keys }>` | Switch blockchain |
|
|
517
|
+
| `getRegisteredChains` | - | `string[]` | Get registered chain IDs |
|
|
518
|
+
|
|
519
|
+
#### Key Operations
|
|
520
|
+
|
|
521
|
+
| Method | Parameters | Returns | Description |
|
|
522
|
+
| ------------ | ---------------------------- | --------------------------------------------------- | ---------------------- |
|
|
523
|
+
| `rotateKeys` | `newDerivationPath?: string` | `Promise<{ keys }>` | Rotate to next address |
|
|
524
|
+
| `deriveKey` | `DeriveKeyOptions` | `Promise<{ privateKey, publicKey, address, path }>` | One-time derivation |
|
|
525
|
+
|
|
526
|
+
#### Cryptographic Operations
|
|
527
|
+
|
|
528
|
+
| Method | Parameters | Returns | Description |
|
|
529
|
+
| ----------------- | ------------------------------- | -------------------------------------------- | ---------------- |
|
|
530
|
+
| `signMessage` | `SignMessageOptions` | `Promise<{ signature, address, publicKey }>` | Sign message |
|
|
531
|
+
| `signTransaction` | `SignTransactionOptions` | `Promise<{ signedTransaction, signature }>` | Sign transaction |
|
|
532
|
+
| `verifySignature` | `message, signature, publicKey` | `Promise<boolean>` | Verify signature |
|
|
533
|
+
|
|
534
|
+
#### Address Management
|
|
535
|
+
|
|
536
|
+
| Method | Parameters | Returns | Description |
|
|
537
|
+
| -------------------- | ------------------------------------------------- | ------------------------------------------------- | ---------------------- |
|
|
538
|
+
| `getAddressForChain` | `chainId: string, index?: number` | `Promise<{ address, publicKey, derivationPath }>` | Get single address |
|
|
539
|
+
| `getAddresses` | `chainId: string, start?: number, count?: number` | `Promise<Array<{ address, path, index }>>` | Get multiple addresses |
|
|
540
|
+
| `findUsedAddresses` | `chainId: string, checkBalance: Function` | `Promise<Array<{ address, path, index }>>` | Account discovery |
|
|
541
|
+
|
|
542
|
+
#### Event Management
|
|
543
|
+
|
|
544
|
+
| Method | Parameters | Returns | Description |
|
|
545
|
+
| ------------------- | ------------------------------ | ------------ | ----------------------------------------- |
|
|
546
|
+
| `onAuthStateChange` | `callback: AuthChangeCallback` | `() => void` | Subscribe to events (returns unsubscribe) |
|
|
547
|
+
|
|
548
|
+
### Types
|
|
549
|
+
|
|
550
|
+
#### CreateIdentityOptions
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
interface CreateIdentityOptions {
|
|
554
|
+
mnemonic: string; // BIP39 mnemonic (12 or 24 words)
|
|
555
|
+
password: string; // Password to encrypt keystore
|
|
556
|
+
label?: string; // Human-readable label
|
|
557
|
+
metadata?: Record<string, any>; // Custom metadata
|
|
558
|
+
chainId?: string; // Optional: unlock with specific chain
|
|
559
|
+
}
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
#### UnlockOptions
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
interface UnlockOptions {
|
|
566
|
+
password: string; // Keystore decryption password
|
|
567
|
+
identityId?: string; // Which identity to unlock (defaults to current)
|
|
568
|
+
chainId?: string; // Which blockchain to use (required)
|
|
569
|
+
derivationPath?: string; // Custom derivation path (advanced)
|
|
570
|
+
duration?: number; // Auto-lock duration in ms
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
#### Identity
|
|
575
|
+
|
|
576
|
+
```typescript
|
|
577
|
+
interface Identity {
|
|
578
|
+
id: string; // Unique identifier
|
|
579
|
+
publicKey: string; // Master public key (xpub)
|
|
580
|
+
fingerprint: string; // Master key fingerprint
|
|
581
|
+
label?: string; // User-defined label
|
|
582
|
+
metadata: Record<string, any>;
|
|
583
|
+
createdAt: Date;
|
|
584
|
+
lastAccess?: Date;
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
#### Keys
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
interface Keys {
|
|
592
|
+
privateKey: Uint8Array; // Derived private key (binary)
|
|
593
|
+
privateKeyHex: string; // Derived private key (hex)
|
|
594
|
+
publicKey: Uint8Array; // Derived public key (binary)
|
|
595
|
+
publicKeyHex: string; // Derived public key (hex)
|
|
596
|
+
address: string; // Blockchain address
|
|
597
|
+
derivationPath: string; // BIP44 path (e.g., "m/44'/60'/0'/0/0")
|
|
598
|
+
chain: Chain; // Current blockchain
|
|
599
|
+
expiresAt: Date; // When keys expire
|
|
600
|
+
expiresIn: number; // Seconds until expiration (at creation)
|
|
601
|
+
identity: Identity; // Associated identity
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
#### Chain
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
interface Chain {
|
|
609
|
+
id: string; // Chain identifier (e.g., 'ethereum')
|
|
610
|
+
name: string; // Display name (e.g., 'Ethereum')
|
|
611
|
+
symbol: string; // Token symbol (e.g., 'ETH')
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## Security Best Practices
|
|
616
|
+
|
|
617
|
+
### 1. Mnemonic Backup
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
// Always require user confirmation before creating identity
|
|
621
|
+
const mnemonic = auth.generateMnemonic({ wordCount: 12 });
|
|
622
|
+
|
|
623
|
+
// Show to user with clear warnings:
|
|
624
|
+
// ⚠️ Write down these words in order
|
|
625
|
+
// ⚠️ Never share with anyone
|
|
626
|
+
// ⚠️ Store in a secure location
|
|
627
|
+
// ⚠️ Losing these words = losing access to funds
|
|
628
|
+
|
|
629
|
+
// Only proceed after confirmation
|
|
630
|
+
await auth.createIdentity({ mnemonic, password, ... });
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### 2. Password Requirements
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
// Enforce strong passwords
|
|
637
|
+
function isStrongPassword(password: string): boolean {
|
|
638
|
+
return (
|
|
639
|
+
password.length >= 12 &&
|
|
640
|
+
/[A-Z]/.test(password) &&
|
|
641
|
+
/[a-z]/.test(password) &&
|
|
642
|
+
/[0-9]/.test(password) &&
|
|
643
|
+
/[^A-Za-z0-9]/.test(password)
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
### 3. Auto-Lock
|
|
649
|
+
|
|
650
|
+
```typescript
|
|
651
|
+
// Always use auto-lock for security
|
|
652
|
+
await auth.unlock({
|
|
653
|
+
password,
|
|
654
|
+
chainId: "ethereum",
|
|
655
|
+
duration: 5 * 60 * 1000, // Lock after 5 minutes of inactivity
|
|
656
|
+
});
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
### 4. Key Expiration Monitoring
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
// Monitor expiration in your UI
|
|
663
|
+
setInterval(() => {
|
|
664
|
+
const remaining = auth.currentExpiresIn;
|
|
665
|
+
if (remaining !== null && remaining < 60) {
|
|
666
|
+
console.warn("Keys expiring soon!");
|
|
667
|
+
// Show UI warning
|
|
668
|
+
}
|
|
669
|
+
}, 1000);
|
|
670
|
+
|
|
671
|
+
// Listen for expiration event
|
|
672
|
+
auth.onAuthStateChange((event) => {
|
|
673
|
+
if (event === "KEYS_EXPIRED") {
|
|
674
|
+
// Prompt user to unlock again
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### 5. Secure Key Handling
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
// Never log private keys in production
|
|
683
|
+
if (process.env.NODE_ENV !== "production") {
|
|
684
|
+
console.log("Private Key:", keys.privateKeyHex);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Use public key for verification
|
|
688
|
+
console.log("Public Key:", keys.publicKeyHex); // Safe to log
|
|
689
|
+
|
|
690
|
+
// Lock when not in use
|
|
691
|
+
window.addEventListener("beforeunload", () => {
|
|
692
|
+
auth.lock();
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
## Advanced Usage
|
|
697
|
+
|
|
698
|
+
### Multiple Identities
|
|
699
|
+
|
|
700
|
+
```typescript
|
|
701
|
+
// Create multiple wallets
|
|
702
|
+
const personal = await auth.createIdentity({
|
|
703
|
+
mnemonic: generateMnemonic(),
|
|
704
|
+
password: "password1",
|
|
705
|
+
label: "Personal",
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
const trading = await auth.createIdentity({
|
|
709
|
+
mnemonic: generateMnemonic(),
|
|
710
|
+
password: "password2",
|
|
711
|
+
label: "Trading",
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
// Switch between them
|
|
715
|
+
await auth.switchIdentity(trading.identity.id);
|
|
716
|
+
await auth.unlock({ password: "password2", chainId: "ethereum" });
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### Custom Derivation Paths
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
// Derive keys at custom path
|
|
723
|
+
const customKey = await auth.deriveKey({
|
|
724
|
+
path: "m/44'/60'/1'/0/0", // Account 1 instead of 0
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// Sign with custom path
|
|
728
|
+
const { signature } = await auth.signMessage({
|
|
729
|
+
message: "Hello",
|
|
730
|
+
derivationPath: "m/44'/60'/1'/0/0",
|
|
731
|
+
});
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
### React/Vue Integration
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
import { ref, onMounted, onUnmounted } from "vue";
|
|
738
|
+
|
|
739
|
+
const currentAddress = ref(auth.currentAddress);
|
|
740
|
+
const expiresIn = ref(auth.currentExpiresIn);
|
|
741
|
+
|
|
742
|
+
// Subscribe to changes
|
|
743
|
+
const unsubscribe = auth.onAuthStateChange((event, keys) => {
|
|
744
|
+
currentAddress.value = auth.currentAddress;
|
|
745
|
+
expiresIn.value = auth.currentExpiresIn;
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// Live expiration countdown
|
|
749
|
+
const interval = setInterval(() => {
|
|
750
|
+
expiresIn.value = auth.currentExpiresIn;
|
|
751
|
+
}, 1000);
|
|
752
|
+
|
|
753
|
+
// Cleanup
|
|
754
|
+
onUnmounted(() => {
|
|
755
|
+
unsubscribe();
|
|
756
|
+
clearInterval(interval);
|
|
757
|
+
});
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
## Master Public Key
|
|
761
|
+
|
|
762
|
+
Get the master public key derived from the mnemonic.
|
|
763
|
+
|
|
764
|
+
### Security Note
|
|
765
|
+
|
|
766
|
+
This returns ONLY the public key bytes (33 bytes, compressed secp256k1), NOT the extended public key (xpub). This is safe to expose publicly because:
|
|
767
|
+
|
|
768
|
+
1. ✅ Cannot derive child keys (no chaincode included)
|
|
769
|
+
2. ✅ Cannot compute private keys
|
|
770
|
+
3. ✅ Standard public key cryptography - public keys are meant to be public
|
|
771
|
+
4. ✅ Chain-independent - same key regardless of blockchain
|
|
772
|
+
|
|
773
|
+
### Use Cases
|
|
774
|
+
|
|
775
|
+
- Document ownership verification
|
|
776
|
+
- Identity verification across different blockchains
|
|
777
|
+
- Signature verification
|
|
778
|
+
- Access control (e.g., "owner" field in documents)
|
|
779
|
+
|
|
780
|
+
### What This Is NOT
|
|
781
|
+
|
|
782
|
+
- ❌ NOT an extended public key (xpub) - does not include chaincode
|
|
783
|
+
- ❌ NOT a blockchain address - this is the raw master public key
|
|
784
|
+
- ❌ NOT blockchain-specific - same for all chains
|
|
785
|
+
|
|
786
|
+
### Usage
|
|
787
|
+
|
|
788
|
+
```typescript
|
|
789
|
+
await auth.unlock({ password: 'my-password' });
|
|
790
|
+
|
|
791
|
+
const masterPubKey = auth.masterPublicKey;
|
|
792
|
+
// "02a1b2c3d4e5f6..." (33 bytes hex, compressed secp256k1)
|
|
793
|
+
|
|
794
|
+
// Use for document ownership
|
|
795
|
+
const document = {
|
|
796
|
+
id: 'doc_123',
|
|
797
|
+
owner: masterPubKey, // ← Chain-independent identity!
|
|
798
|
+
data: { ... }
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// Sign with current blockchain key
|
|
802
|
+
const signature = await auth.signMessage({
|
|
803
|
+
message: `${document.id}:update:${Date.now()}`
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Server can verify ownership regardless of chain
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
## BIP44 Document ID
|
|
810
|
+
|
|
811
|
+
Derives a deterministic document ID using BIP44-style hierarchical derivation.
|
|
812
|
+
|
|
813
|
+
This creates chain-independent, deterministic document IDs that can be used with Automerge or any other document system. The ID is derived from your mnemonic using BIP44 HD key derivation, ensuring the same inputs always produce the same ID.
|
|
814
|
+
|
|
815
|
+
**How it works:** `deriveBIP44DocumentID()` generates a 32-byte seed from your mnemonic. When passed to `store.create()` as the second parameter, Automerge uses this seed with UUID v5 to create a deterministic document URL. Same seed = same document every time!
|
|
816
|
+
|
|
817
|
+
### Path Structure
|
|
818
|
+
|
|
819
|
+
`m/44'/[appId]'/[account]'/[purpose]/[index]`
|
|
820
|
+
|
|
821
|
+
### Purpose Parameter Guidance
|
|
822
|
+
|
|
823
|
+
The `purpose` parameter (4th level) can represent document types or categories. This is flexible and application-specific. Examples:
|
|
824
|
+
|
|
825
|
+
- 0: Profile/Settings
|
|
826
|
+
- 1: Notes
|
|
827
|
+
- 2: Tasks
|
|
828
|
+
- 3: Documents
|
|
829
|
+
- 100+: Custom types
|
|
830
|
+
|
|
831
|
+
### Parameters
|
|
832
|
+
|
|
833
|
+
- `options.appId` - Application identifier (1000000+ recommended, will be hardened)
|
|
834
|
+
- `options.account` - Account/workspace number (default: 0, hardened)
|
|
835
|
+
- `options.purpose` - Document type/category (default: 0, non-hardened)
|
|
836
|
+
- `options.index` - Document instance number (default: 0, non-hardened)
|
|
837
|
+
|
|
838
|
+
### Returns
|
|
839
|
+
|
|
840
|
+
Hex-encoded document ID (32 bytes / 64 hex chars)
|
|
841
|
+
|
|
842
|
+
### Usage
|
|
843
|
+
|
|
844
|
+
```typescript
|
|
845
|
+
// Derive document ID
|
|
846
|
+
const docId = await auth.deriveBIP44DocumentID({
|
|
847
|
+
appId: 1000000,
|
|
848
|
+
purpose: 0, // Settings type
|
|
849
|
+
index: 0, // First settings doc
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
// Create document with deterministic ID
|
|
853
|
+
await store.create({ type: "settings", theme: "dark" }, docId);
|
|
854
|
+
|
|
855
|
+
// Later, re-derive the same ID
|
|
856
|
+
const sameId = await auth.deriveBIP44DocumentID({
|
|
857
|
+
appId: 1000000,
|
|
858
|
+
purpose: 0,
|
|
859
|
+
index: 0,
|
|
860
|
+
});
|
|
861
|
+
const settings = await store.get(sameId); // Gets existing doc!
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
See `BIP44_DOCUMENT_ID.md` for detailed documentation and examples.
|
|
865
|
+
|
|
866
|
+
## Browser Compatibility
|
|
867
|
+
|
|
868
|
+
This package is **100% browser-compatible** with zero configuration:
|
|
869
|
+
|
|
870
|
+
- ✅ Chrome, Firefox, Safari, Edge
|
|
871
|
+
- ✅ Node.js (v16+)
|
|
872
|
+
- ✅ Electron (main and renderer)
|
|
873
|
+
- ✅ React Native
|
|
874
|
+
- ✅ Web Workers
|
|
875
|
+
- ✅ Service Workers
|
|
876
|
+
|
|
877
|
+
**No polyfills required.** Uses native browser APIs:
|
|
878
|
+
|
|
879
|
+
- `crypto.subtle` for encryption
|
|
880
|
+
- `indexedDB` for storage
|
|
881
|
+
- `Uint8Array` for binary data
|
|
882
|
+
|
|
883
|
+
## Dependencies
|
|
884
|
+
|
|
885
|
+
```json
|
|
886
|
+
{
|
|
887
|
+
"@scure/bip39": "^1.2.1",
|
|
888
|
+
"@scure/bip32": "^1.3.2",
|
|
889
|
+
"@cryptforge/core": "workspace:*"
|
|
890
|
+
}
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
All dependencies are browser-safe, audited, and actively maintained.
|
|
894
|
+
|
|
895
|
+
## Examples
|
|
896
|
+
|
|
897
|
+
See the complete working example in `examples/vue-electron-example/src/AuthTest.vue`.
|
|
898
|
+
|
|
899
|
+
## License
|
|
900
|
+
|
|
901
|
+
MIT
|
|
902
|
+
|
|
903
|
+
## Contributing
|
|
904
|
+
|
|
905
|
+
Contributions welcome! Please ensure:
|
|
906
|
+
|
|
907
|
+
- All code is browser-compatible (use `@scure` and `@noble` libraries)
|
|
908
|
+
- TypeScript types are properly defined
|
|
909
|
+
- Examples are updated
|
|
910
|
+
- Tests pass (when implemented)
|
|
911
|
+
|
|
912
|
+
## Support
|
|
913
|
+
|
|
914
|
+
For issues, questions, or feature requests, please open an issue on GitHub.
|