@docknetwork/wallet-sdk-core 1.7.7-alpha.0 → 1.9.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.
Files changed (76) hide show
  1. package/lib/cloud-wallet.d.ts +79 -3
  2. package/lib/cloud-wallet.d.ts.map +1 -1
  3. package/lib/cloud-wallet.js +147 -14
  4. package/lib/cloud-wallet.js.map +1 -1
  5. package/lib/credential-provider.d.ts.map +1 -1
  6. package/lib/credential-provider.js +10 -4
  7. package/lib/credential-provider.js.map +1 -1
  8. package/lib/delegation/delegation-chain.d.ts +8 -0
  9. package/lib/delegation/delegation-chain.d.ts.map +1 -0
  10. package/lib/delegation/delegation-chain.js +33 -0
  11. package/lib/delegation/delegation-chain.js.map +1 -0
  12. package/lib/delegation/delegation-fixtures.d.ts +69 -0
  13. package/lib/delegation/delegation-fixtures.d.ts.map +1 -0
  14. package/lib/delegation/delegation-fixtures.js +553 -0
  15. package/lib/delegation/delegation-fixtures.js.map +1 -0
  16. package/lib/delegation/delegation-issuance.d.ts +19 -0
  17. package/lib/delegation/delegation-issuance.d.ts.map +1 -0
  18. package/lib/delegation/delegation-issuance.js +60 -0
  19. package/lib/delegation/delegation-issuance.js.map +1 -0
  20. package/lib/delegation/delegation-offer.d.ts +84 -0
  21. package/lib/delegation/delegation-offer.d.ts.map +1 -0
  22. package/lib/delegation/delegation-offer.js +349 -0
  23. package/lib/delegation/delegation-offer.js.map +1 -0
  24. package/lib/delegation/delegation-policy-validation.d.ts +28 -0
  25. package/lib/delegation/delegation-policy-validation.d.ts.map +1 -0
  26. package/lib/delegation/delegation-policy-validation.js +170 -0
  27. package/lib/delegation/delegation-policy-validation.js.map +1 -0
  28. package/lib/delegation/delegation-policy.d.ts +21 -0
  29. package/lib/delegation/delegation-policy.d.ts.map +1 -0
  30. package/lib/delegation/delegation-policy.js +73 -0
  31. package/lib/delegation/delegation-policy.js.map +1 -0
  32. package/lib/delegation/delegation-tree.d.ts +17 -0
  33. package/lib/delegation/delegation-tree.d.ts.map +1 -0
  34. package/lib/delegation/delegation-tree.js +58 -0
  35. package/lib/delegation/delegation-tree.js.map +1 -0
  36. package/lib/delegation/delegation-types.d.ts +56 -0
  37. package/lib/delegation/delegation-types.d.ts.map +1 -0
  38. package/lib/delegation/delegation-types.js +3 -0
  39. package/lib/delegation/delegation-types.js.map +1 -0
  40. package/lib/delegation/delegation-utils.d.ts +3 -0
  41. package/lib/delegation/delegation-utils.d.ts.map +1 -0
  42. package/lib/delegation/delegation-utils.js +10 -0
  43. package/lib/delegation/delegation-utils.js.map +1 -0
  44. package/lib/did-provider.d.ts +2 -1
  45. package/lib/did-provider.d.ts.map +1 -1
  46. package/lib/did-provider.js +11 -7
  47. package/lib/did-provider.js.map +1 -1
  48. package/lib/message-provider.js +1 -1
  49. package/lib/message-provider.js.map +1 -1
  50. package/lib/verification-controller.d.ts +30 -11
  51. package/lib/verification-controller.d.ts.map +1 -1
  52. package/lib/verification-controller.js +372 -68
  53. package/lib/verification-controller.js.map +1 -1
  54. package/package.json +3 -3
  55. package/src/cloud-wallet.test.js +369 -0
  56. package/src/cloud-wallet.ts +206 -18
  57. package/src/credential-provider.ts +13 -4
  58. package/src/delegation/delegation-chain.test.ts +64 -0
  59. package/src/delegation/delegation-chain.ts +34 -0
  60. package/src/delegation/delegation-fixtures.ts +552 -0
  61. package/src/delegation/delegation-issuance.ts +92 -0
  62. package/src/delegation/delegation-offer.ts +488 -0
  63. package/src/delegation/delegation-policy-validation.test.ts +237 -0
  64. package/src/delegation/delegation-policy-validation.ts +281 -0
  65. package/src/delegation/delegation-policy.ts +100 -0
  66. package/src/delegation/delegation-tree.test.ts +110 -0
  67. package/src/delegation/delegation-tree.ts +60 -0
  68. package/src/delegation/delegation-types.ts +65 -0
  69. package/src/delegation/delegation-utils.ts +10 -0
  70. package/src/did-provider.ts +10 -6
  71. package/src/globals.d.ts +6 -0
  72. package/src/message-provider.ts +1 -1
  73. package/src/verification-controller.test.ts +23 -0
  74. package/src/verification-controller.ts +534 -82
  75. package/tsconfig.build.json +2 -1
  76. package/tsconfig.build.tsbuildinfo +1 -1
@@ -0,0 +1,369 @@
1
+ const EventEmitter = require('events');
2
+ const {
3
+ generateCloudWalletMasterKey,
4
+ recoverCloudWalletMasterKey,
5
+ initializeCloudWallet,
6
+ derivePasskeyVaultKeys,
7
+ derivePasskeyEncryptionKey,
8
+ initializePasskeyKeyMappingVault,
9
+ enrollUserWithPasskey,
10
+ authenticateWithPasskey,
11
+ initializeCloudWalletWithPasskey,
12
+ PASSKEY_KEY_MAPPING_TYPE,
13
+ } = require('./cloud-wallet');
14
+
15
+ const mockInitializeFromMasterKey = jest.fn().mockResolvedValue(undefined);
16
+ const mockInitializeFromMnemonic = jest.fn().mockResolvedValue(undefined);
17
+ const mockFind = jest.fn().mockResolvedValue({documents: []});
18
+ const mockDeriveBiometricKey = jest.fn().mockReturnValue(Buffer.alloc(32));
19
+ const mockDeriveKeys = jest.fn().mockResolvedValue({
20
+ hmacKey: 'mock-hmac',
21
+ agreementKey: 'mock-agreement',
22
+ verificationKey: 'mock-verification',
23
+ });
24
+ const mockInitialize = jest.fn().mockResolvedValue(undefined);
25
+ const mockDeriveBiometricEncryptionKey = jest.fn().mockResolvedValue({
26
+ key: Buffer.alloc(32),
27
+ iv: Buffer.alloc(16),
28
+ });
29
+ const mockEncryptMasterKey = jest
30
+ .fn()
31
+ .mockResolvedValue(new Uint8Array([99, 99]));
32
+ const mockDecryptMasterKey = jest
33
+ .fn()
34
+ .mockResolvedValue(new Uint8Array([1, 2, 3]));
35
+ const mockGetController = jest.fn().mockResolvedValue('mock-controller');
36
+ const mockInsert = jest.fn().mockResolvedValue(undefined);
37
+
38
+ jest.mock('@docknetwork/wallet-sdk-wasm/src/services/edv', () => ({
39
+ edvService: {
40
+ initializeFromMasterKey: (...args) => mockInitializeFromMasterKey(...args),
41
+ initializeFromMnemonic: (...args) => mockInitializeFromMnemonic(...args),
42
+ find: (...args) => mockFind(...args),
43
+ deriveBiometricKey: (...args) => mockDeriveBiometricKey(...args),
44
+ deriveKeys: (...args) => mockDeriveKeys(...args),
45
+ initialize: (...args) => mockInitialize(...args),
46
+ deriveBiometricEncryptionKey: (...args) =>
47
+ mockDeriveBiometricEncryptionKey(...args),
48
+ encryptMasterKey: (...args) => mockEncryptMasterKey(...args),
49
+ decryptMasterKey: (...args) => mockDecryptMasterKey(...args),
50
+ getController: (...args) => mockGetController(...args),
51
+ insert: (...args) => mockInsert(...args),
52
+ },
53
+ }));
54
+
55
+ const mockMnemonicGenerate = jest.fn();
56
+ const mockMnemonicToMiniSecret = jest.fn();
57
+
58
+ jest.mock('@docknetwork/wallet-sdk-wasm/src/services/util-crypto', () => ({
59
+ utilCryptoService: {
60
+ mnemonicGenerate: (...args) => mockMnemonicGenerate(...args),
61
+ mnemonicToMiniSecret: (...args) => mockMnemonicToMiniSecret(...args),
62
+ },
63
+ }));
64
+
65
+ function createMockDataStore() {
66
+ return {
67
+ events: new EventEmitter(),
68
+ documents: {
69
+ getDocumentById: jest.fn(),
70
+ addDocument: jest.fn(),
71
+ removeDocument: jest.fn(),
72
+ updateDocument: jest.fn(),
73
+ getAllDocuments: jest.fn().mockResolvedValue([]),
74
+ },
75
+ };
76
+ }
77
+
78
+ describe('cloud-wallet', () => {
79
+ beforeEach(() => {
80
+ jest.clearAllMocks();
81
+ });
82
+
83
+ describe('generateCloudWalletMasterKey', () => {
84
+ it('should generate a mnemonic and master key', async () => {
85
+ const mockMnemonic =
86
+ 'test mnemonic phrase with twelve words one two three four five six';
87
+ const mockMasterKey = new Uint8Array([1, 2, 3, 4]);
88
+
89
+ mockMnemonicGenerate.mockResolvedValue(mockMnemonic);
90
+ mockMnemonicToMiniSecret.mockResolvedValue(mockMasterKey);
91
+
92
+ const result = await generateCloudWalletMasterKey();
93
+
94
+ expect(result.mnemonic).toBe(mockMnemonic);
95
+ expect(result.masterKey).toBeInstanceOf(Uint8Array);
96
+ expect(result.masterKey).toEqual(mockMasterKey);
97
+ });
98
+
99
+ it('should convert plain object to Uint8Array when JSON-RPC serialization occurs', async () => {
100
+ const mockMnemonic = 'test mnemonic';
101
+ const serializedKey = {0: 10, 1: 20, 2: 30};
102
+
103
+ mockMnemonicGenerate.mockResolvedValue(mockMnemonic);
104
+ mockMnemonicToMiniSecret.mockResolvedValue(serializedKey);
105
+
106
+ const result = await generateCloudWalletMasterKey();
107
+
108
+ expect(result.masterKey).toBeInstanceOf(Uint8Array);
109
+ expect(result.masterKey).toEqual(new Uint8Array([10, 20, 30]));
110
+ });
111
+ });
112
+
113
+ describe('recoverCloudWalletMasterKey', () => {
114
+ it('should recover master key from mnemonic', async () => {
115
+ const mockMasterKey = new Uint8Array([5, 6, 7, 8]);
116
+ mockMnemonicToMiniSecret.mockResolvedValue(mockMasterKey);
117
+
118
+ const result = await recoverCloudWalletMasterKey('test mnemonic');
119
+
120
+ expect(result).toBeInstanceOf(Uint8Array);
121
+ expect(result).toEqual(mockMasterKey);
122
+ expect(mockMnemonicToMiniSecret).toHaveBeenCalledWith('test mnemonic');
123
+ });
124
+
125
+ it('should convert plain object to Uint8Array when JSON-RPC serialization occurs', async () => {
126
+ const serializedKey = {0: 100, 1: 200};
127
+ mockMnemonicToMiniSecret.mockResolvedValue(serializedKey);
128
+
129
+ const result = await recoverCloudWalletMasterKey('test mnemonic');
130
+
131
+ expect(result).toBeInstanceOf(Uint8Array);
132
+ expect(result).toEqual(new Uint8Array([100, 200]));
133
+ });
134
+ });
135
+
136
+ describe('initializeCloudWallet', () => {
137
+ const edvUrl = 'https://edv.example.com';
138
+ const authKey = 'test-auth-key';
139
+
140
+ it('should use masterKey when both masterKey and mnemonic are provided', async () => {
141
+ const masterKey = new Uint8Array([1, 2, 3]);
142
+ const dataStore = createMockDataStore();
143
+
144
+ await initializeCloudWallet({
145
+ dataStore,
146
+ edvUrl,
147
+ authKey,
148
+ masterKey,
149
+ mnemonic: 'should be ignored',
150
+ });
151
+
152
+ expect(mockInitializeFromMasterKey).toHaveBeenCalledWith({
153
+ masterKey,
154
+ edvUrl,
155
+ authKey,
156
+ });
157
+ expect(mockInitializeFromMnemonic).not.toHaveBeenCalled();
158
+ });
159
+
160
+ it('should use mnemonic when masterKey is not provided', async () => {
161
+ const mnemonic = 'test mnemonic phrase';
162
+ const dataStore = createMockDataStore();
163
+
164
+ await initializeCloudWallet({
165
+ dataStore,
166
+ edvUrl,
167
+ authKey,
168
+ mnemonic,
169
+ });
170
+
171
+ expect(mockInitializeFromMnemonic).toHaveBeenCalledWith({
172
+ mnemonic,
173
+ edvUrl,
174
+ authKey,
175
+ });
176
+ expect(mockInitializeFromMasterKey).not.toHaveBeenCalled();
177
+ });
178
+
179
+ it('should throw when neither masterKey nor mnemonic is provided', async () => {
180
+ const dataStore = createMockDataStore();
181
+
182
+ await expect(
183
+ initializeCloudWallet({dataStore, edvUrl, authKey}),
184
+ ).rejects.toThrow('Either masterKey or mnemonic is required');
185
+ });
186
+ });
187
+
188
+ describe('passkey functions', () => {
189
+ const edvUrl = 'https://edv.example.com';
190
+ const authKey = 'test-auth-key';
191
+ const prfOutput = new Uint8Array(32).fill(42);
192
+ const identifier = 'user@example.com';
193
+
194
+ describe('derivePasskeyVaultKeys', () => {
195
+ it('should derive vault keys from PRF output and identifier', async () => {
196
+ const result = await derivePasskeyVaultKeys(prfOutput, identifier);
197
+
198
+ expect(mockDeriveBiometricKey).toHaveBeenCalledWith(
199
+ expect.any(Buffer),
200
+ identifier,
201
+ );
202
+ expect(mockDeriveKeys).toHaveBeenCalled();
203
+ expect(result).toEqual({
204
+ hmacKey: 'mock-hmac',
205
+ agreementKey: 'mock-agreement',
206
+ verificationKey: 'mock-verification',
207
+ });
208
+ });
209
+
210
+ it('should convert prfOutput to Buffer before calling deriveBiometricKey', async () => {
211
+ await derivePasskeyVaultKeys(prfOutput, identifier);
212
+
213
+ const bufferArg = mockDeriveBiometricKey.mock.calls[0][0];
214
+ expect(Buffer.isBuffer(bufferArg)).toBe(true);
215
+ expect(bufferArg).toEqual(Buffer.from(prfOutput));
216
+ });
217
+ });
218
+
219
+ describe('derivePasskeyEncryptionKey', () => {
220
+ it('should derive encryption key from PRF output and identifier', async () => {
221
+ const result = await derivePasskeyEncryptionKey(prfOutput, identifier);
222
+
223
+ expect(mockDeriveBiometricEncryptionKey).toHaveBeenCalledWith(
224
+ expect.any(Buffer),
225
+ identifier,
226
+ );
227
+ expect(result).toHaveProperty('key');
228
+ expect(result).toHaveProperty('iv');
229
+ });
230
+ });
231
+
232
+ describe('initializePasskeyKeyMappingVault', () => {
233
+ it('should initialize the EDV with derived vault keys', async () => {
234
+ await initializePasskeyKeyMappingVault(
235
+ edvUrl,
236
+ authKey,
237
+ prfOutput,
238
+ identifier,
239
+ );
240
+
241
+ expect(mockInitialize).toHaveBeenCalledWith({
242
+ hmacKey: 'mock-hmac',
243
+ agreementKey: 'mock-agreement',
244
+ verificationKey: 'mock-verification',
245
+ edvUrl,
246
+ authKey,
247
+ });
248
+ });
249
+ });
250
+
251
+ describe('enrollUserWithPasskey', () => {
252
+ beforeEach(() => {
253
+ mockMnemonicGenerate.mockResolvedValue('mock mnemonic phrase');
254
+ mockMnemonicToMiniSecret.mockResolvedValue(
255
+ new Uint8Array([1, 2, 3, 4]),
256
+ );
257
+ });
258
+
259
+ it('should generate a master key and store it encrypted in the vault', async () => {
260
+ const result = await enrollUserWithPasskey(
261
+ edvUrl,
262
+ authKey,
263
+ prfOutput,
264
+ identifier,
265
+ );
266
+
267
+ expect(result.mnemonic).toBe('mock mnemonic phrase');
268
+ expect(result.masterKey).toEqual(new Uint8Array([1, 2, 3, 4]));
269
+ });
270
+
271
+ it('should encrypt the master key before storing', async () => {
272
+ await enrollUserWithPasskey(edvUrl, authKey, prfOutput, identifier);
273
+
274
+ expect(mockEncryptMasterKey).toHaveBeenCalledWith(
275
+ new Uint8Array([1, 2, 3, 4]),
276
+ expect.any(Buffer),
277
+ expect.any(Buffer),
278
+ );
279
+ });
280
+
281
+ it('should insert document with PASSKEY_KEY_MAPPING_TYPE', async () => {
282
+ await enrollUserWithPasskey(edvUrl, authKey, prfOutput, identifier);
283
+
284
+ expect(mockInsert).toHaveBeenCalledWith({
285
+ document: {
286
+ content: {
287
+ id: 'mock-controller#master-key',
288
+ type: PASSKEY_KEY_MAPPING_TYPE,
289
+ encryptedKey: expect.objectContaining({
290
+ data: expect.any(Array),
291
+ iv: expect.any(Array),
292
+ }),
293
+ },
294
+ },
295
+ });
296
+ });
297
+ });
298
+
299
+ describe('authenticateWithPasskey', () => {
300
+ it('should initialize vault and retrieve decrypted master key', async () => {
301
+ mockFind.mockResolvedValueOnce({
302
+ documents: [
303
+ {
304
+ content: {
305
+ id: 'mock-controller#master-key',
306
+ encryptedKey: {
307
+ data: [99, 99],
308
+ iv: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
309
+ },
310
+ },
311
+ },
312
+ ],
313
+ });
314
+
315
+ const result = await authenticateWithPasskey(
316
+ edvUrl,
317
+ authKey,
318
+ prfOutput,
319
+ identifier,
320
+ );
321
+
322
+ expect(mockInitialize).toHaveBeenCalled();
323
+ expect(mockDecryptMasterKey).toHaveBeenCalled();
324
+ expect(result).toEqual(new Uint8Array([1, 2, 3]));
325
+ });
326
+
327
+ it('should throw when no key mapping document is found', async () => {
328
+ mockFind.mockResolvedValueOnce({documents: []});
329
+
330
+ await expect(
331
+ authenticateWithPasskey(edvUrl, authKey, prfOutput, identifier),
332
+ ).rejects.toThrow('Authentication failed: Invalid identifier');
333
+ });
334
+ });
335
+
336
+ describe('initializeCloudWalletWithPasskey', () => {
337
+ it('should authenticate and initialize cloud wallet', async () => {
338
+ mockFind.mockResolvedValueOnce({
339
+ documents: [
340
+ {
341
+ content: {
342
+ id: 'mock-controller#master-key',
343
+ encryptedKey: {
344
+ data: [99, 99],
345
+ iv: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
346
+ },
347
+ },
348
+ },
349
+ ],
350
+ });
351
+
352
+ const dataStore = createMockDataStore();
353
+ await initializeCloudWalletWithPasskey(
354
+ edvUrl,
355
+ authKey,
356
+ prfOutput,
357
+ identifier,
358
+ dataStore,
359
+ );
360
+
361
+ expect(mockInitializeFromMasterKey).toHaveBeenCalledWith({
362
+ masterKey: new Uint8Array([1, 2, 3]),
363
+ edvUrl,
364
+ authKey,
365
+ });
366
+ });
367
+ });
368
+ });
369
+ });
@@ -15,6 +15,7 @@ import { utilCryptoService } from '@docknetwork/wallet-sdk-wasm/src/services/uti
15
15
  export const SYNC_MARKER_TYPE = 'SyncMarkerDocument';
16
16
  export const MNEMONIC_WORD_COUNT = 12;
17
17
  export const KEY_MAPPING_TYPE = 'KeyMappingDocument';
18
+ export const PASSKEY_KEY_MAPPING_TYPE = 'PasskeyKeyMappingDocument';
18
19
  const MASTER_KEY_SUFFIX = 'master-key';
19
20
 
20
21
  /**
@@ -269,6 +270,167 @@ export async function initializeCloudWalletWithBiometrics(
269
270
  });
270
271
  }
271
272
 
273
+ /**
274
+ * Derives EDV keys from passkey PRF output for the KeyMappingVault
275
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
276
+ * @param identifier User's identifier as additional entropy (email, phone number, etc.)
277
+ * @returns Keys for accessing the KeyMappingVault
278
+ */
279
+ export async function derivePasskeyVaultKeys(
280
+ prfOutput: Uint8Array,
281
+ identifier: string
282
+ ): Promise<{ hmacKey: string; agreementKey: string; verificationKey: string }> {
283
+ const seedBuffer = deriveBiometricKey(Buffer.from(prfOutput), identifier);
284
+ return edvService.deriveKeys(new Uint8Array(seedBuffer));
285
+ }
286
+
287
+ /**
288
+ * Generates a key for encrypting/decrypting the master key from passkey PRF output
289
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
290
+ * @param identifier User's identifier as salt (email, phone number, etc.)
291
+ * @returns Encryption key and IV for AES encryption
292
+ */
293
+ export async function derivePasskeyEncryptionKey(
294
+ prfOutput: Uint8Array,
295
+ identifier: string
296
+ ): Promise<{ key: Buffer; iv: Buffer }> {
297
+ return edvService.deriveBiometricEncryptionKey(Buffer.from(prfOutput), identifier);
298
+ }
299
+
300
+ /**
301
+ * Initializes the KeyMappingVault using passkey PRF output
302
+ * @param edvUrl URL for the edv
303
+ * @param authKey Auth key for the edv
304
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
305
+ * @param identifier User's identifier (email, phone number, etc.)
306
+ * @returns Initialized EDV service
307
+ */
308
+ export async function initializePasskeyKeyMappingVault(
309
+ edvUrl: string,
310
+ authKey: string,
311
+ prfOutput: Uint8Array,
312
+ identifier: string
313
+ ): Promise<typeof edvService> {
314
+ const {
315
+ hmacKey,
316
+ agreementKey,
317
+ verificationKey
318
+ } = await derivePasskeyVaultKeys(prfOutput, identifier);
319
+
320
+ const keyMappingEdvService = edvService;
321
+ await keyMappingEdvService.initialize({
322
+ hmacKey,
323
+ agreementKey,
324
+ verificationKey,
325
+ edvUrl,
326
+ authKey
327
+ });
328
+
329
+ return keyMappingEdvService;
330
+ }
331
+
332
+ /**
333
+ * Enrolls a user by creating a passkey-protected master key in the KeyMappingVault
334
+ * @param edvUrl URL for the edv
335
+ * @param authKey Auth key for the edv
336
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
337
+ * @param identifier User's identifier (email, phone number, etc.)
338
+ * @returns The master key and mnemonic for backup
339
+ */
340
+ export async function enrollUserWithPasskey(
341
+ edvUrl: string,
342
+ authKey: string,
343
+ prfOutput: Uint8Array,
344
+ identifier: string
345
+ ): Promise<{ masterKey: Uint8Array; mnemonic: string }> {
346
+ const keyMappingEdv = await initializePasskeyKeyMappingVault(
347
+ edvUrl,
348
+ authKey,
349
+ prfOutput,
350
+ identifier
351
+ );
352
+ const { mnemonic, masterKey } = await generateCloudWalletMasterKey();
353
+ const { key: encryptionKey, iv } = await derivePasskeyEncryptionKey(prfOutput, identifier);
354
+ const encryptedMasterKey = await encryptMasterKey(masterKey, encryptionKey, iv);
355
+
356
+ const encryptedData = {
357
+ data: Array.from(encryptedMasterKey),
358
+ iv: Array.from(iv)
359
+ };
360
+
361
+ const contentId = `${await keyMappingEdv.getController()}#${MASTER_KEY_SUFFIX}`;
362
+
363
+ await keyMappingEdv.insert({
364
+ document: {
365
+ content: {
366
+ id: contentId,
367
+ type: PASSKEY_KEY_MAPPING_TYPE,
368
+ encryptedKey: encryptedData
369
+ }
370
+ }
371
+ });
372
+
373
+ return { masterKey, mnemonic };
374
+ }
375
+
376
+ /**
377
+ * Authenticates a user with passkey PRF output and identifier
378
+ * @param edvUrl URL for the edv
379
+ * @param authKey Auth key for the edv
380
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
381
+ * @param identifier User's identifier (email, phone number, etc.)
382
+ * @returns The decrypted master key for CloudWalletVault
383
+ */
384
+ export async function authenticateWithPasskey(
385
+ edvUrl: string,
386
+ authKey: string,
387
+ prfOutput: Uint8Array,
388
+ identifier: string
389
+ ): Promise<Uint8Array> {
390
+ const keyMappingEdv = await initializePasskeyKeyMappingVault(
391
+ edvUrl,
392
+ authKey,
393
+ prfOutput,
394
+ identifier
395
+ );
396
+ const { key: decryptionKey } = await derivePasskeyEncryptionKey(prfOutput, identifier);
397
+
398
+ const contentId = `${await keyMappingEdv.getController()}#${MASTER_KEY_SUFFIX}`;
399
+
400
+ return getKeyMappingMasterKey(keyMappingEdv, contentId, decryptionKey);
401
+ }
402
+
403
+ /**
404
+ * Initializes the Cloud Wallet using passkey authentication
405
+ * @param edvUrl Cloud wallet vault URL
406
+ * @param authKey Cloud wallet auth key
407
+ * @param prfOutput 32-byte PRF output from WebAuthn assertion
408
+ * @param identifier User's identifier (email, phone number, etc.)
409
+ * @param dataStore Optional data store for the wallet
410
+ * @returns Initialized cloud wallet
411
+ */
412
+ export async function initializeCloudWalletWithPasskey(
413
+ edvUrl: string,
414
+ authKey: string,
415
+ prfOutput: Uint8Array,
416
+ identifier: string,
417
+ dataStore?: any
418
+ ): Promise<any> {
419
+ const masterKey = await authenticateWithPasskey(
420
+ edvUrl,
421
+ authKey,
422
+ prfOutput,
423
+ identifier
424
+ );
425
+
426
+ return initializeCloudWallet({
427
+ dataStore,
428
+ edvUrl,
429
+ authKey,
430
+ masterKey
431
+ });
432
+ }
433
+
272
434
  interface QueuedOperation {
273
435
  operation: () => Promise<any>;
274
436
  resolve: (value: any) => void;
@@ -283,7 +445,12 @@ interface DocumentQueue {
283
445
  export async function generateCloudWalletMasterKey(): Promise<{ mnemonic: string; masterKey: Uint8Array }> {
284
446
  const mnemonic = await utilCryptoService.mnemonicGenerate(MNEMONIC_WORD_COUNT);
285
447
 
286
- const masterKey = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
448
+ const masterKeyResult = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
449
+
450
+ // Ensure masterKey is a proper Uint8Array (JSON-RPC serialization converts it to a plain object)
451
+ const masterKey = masterKeyResult instanceof Uint8Array
452
+ ? masterKeyResult
453
+ : new Uint8Array(Object.values(masterKeyResult));
287
454
 
288
455
  return {
289
456
  mnemonic,
@@ -292,35 +459,47 @@ export async function generateCloudWalletMasterKey(): Promise<{ mnemonic: string
292
459
  }
293
460
 
294
461
  export async function recoverCloudWalletMasterKey(mnemonic: string): Promise<Uint8Array> {
295
- const masterKey = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
462
+ const masterKeyResult = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
296
463
 
297
- return masterKey;
464
+ // Ensure masterKey is a proper Uint8Array (JSON-RPC serialization converts it to a plain object)
465
+ return masterKeyResult instanceof Uint8Array
466
+ ? masterKeyResult
467
+ : new Uint8Array(Object.values(masterKeyResult));
298
468
  }
299
469
 
470
+ /**
471
+ * Initialize the cloud wallet EDV service.
472
+ *
473
+ * Either `masterKey` or `mnemonic` must be provided. When both are supplied,
474
+ * `masterKey` takes precedence since it is the derived key ready for use.
475
+ *
476
+ * @param {Object} params
477
+ * @param {DataStore} [params.dataStore] - Optional data store
478
+ * @param {string} params.edvUrl - EDV service URL
479
+ * @param {string} params.authKey - Authentication key
480
+ * @param {Uint8Array} [params.masterKey] - Pre-derived master key (takes precedence over mnemonic)
481
+ * @param {string} [params.mnemonic] - BIP-39 mnemonic used to derive the master key
482
+ */
300
483
  export async function initializeCloudWallet({
301
484
  dataStore,
302
485
  edvUrl,
303
486
  authKey,
304
487
  masterKey,
488
+ mnemonic,
305
489
  }: {
306
490
  dataStore?: DataStore;
307
491
  edvUrl: string;
308
492
  authKey: string;
309
- masterKey: Uint8Array;
493
+ masterKey?: Uint8Array;
494
+ mnemonic?: string;
310
495
  }) {
311
- const {
312
- hmacKey,
313
- agreementKey,
314
- verificationKey,
315
- } = await edvService.deriveKeys(masterKey);
316
-
317
- await edvService.initialize({
318
- hmacKey,
319
- agreementKey,
320
- verificationKey,
321
- edvUrl,
322
- authKey
323
- });
496
+ if (masterKey) {
497
+ await edvService.initializeFromMasterKey({ masterKey, edvUrl, authKey });
498
+ } else if (mnemonic) {
499
+ await edvService.initializeFromMnemonic({ mnemonic, edvUrl, authKey });
500
+ } else {
501
+ throw new Error('Either masterKey or mnemonic is required');
502
+ }
324
503
 
325
504
  const documentQueues = new Map<string, DocumentQueue>();
326
505
  const activeOperations = new Set<Promise<any>>();
@@ -498,18 +677,26 @@ export async function initializeCloudWallet({
498
677
  }
499
678
 
500
679
  async function pullDocuments() {
680
+ logger.debug('Pulling documents from EDV');
681
+
501
682
  const allDocs = await edvService.find({});
502
683
 
684
+ logger.debug(`Documents found in EDV: ${allDocs.documents.length}`);
685
+
503
686
  for (const doc of allDocs.documents) {
504
687
  const edvDoc = doc.content;
505
688
  const walletDoc = await dataStore.documents.getDocumentById(edvDoc.id);
506
689
 
507
690
  if (!walletDoc) {
508
- const result = await dataStore.documents.addDocument(edvDoc, {
691
+ logger.debug(
692
+ `Document ${edvDoc.id} not found in data store, adding to data store`,
693
+ );
694
+ await dataStore.documents.addDocument(edvDoc, {
509
695
  stopPropagation: true,
510
696
  });
511
697
  }
512
698
  }
699
+ return allDocs;
513
700
  }
514
701
 
515
702
  async function clearEdvDocuments() {
@@ -517,6 +704,7 @@ export async function initializeCloudWallet({
517
704
 
518
705
  for (const doc of allDocs.documents) {
519
706
  await edvService.delete({ document: doc });
707
+ await new Promise(resolve => setTimeout(resolve, 100));
520
708
  }
521
709
  }
522
710
 
@@ -173,13 +173,16 @@ export const ACUMM_WITNESS_PROP_KEY = '$$accum__witness$$';
173
173
  * @private
174
174
  */
175
175
  export async function addCredential({wallet, credential}) {
176
- // Check if the credential is an SD-JWT (string format)
177
- if (typeof credential === 'string') {
176
+ if (
177
+ credential &&
178
+ (typeof credential === 'string' || typeof credential === 'object')
179
+ ) {
178
180
  try {
179
- const isSDJWT = await credentialServiceRPC.isSDJWTCredential({credential});
181
+ const isSDJWT = await credentialServiceRPC.isSDJWTCredential({
182
+ credential,
183
+ });
180
184
 
181
185
  if (isSDJWT) {
182
- // Convert SD-JWT to W3C format (includes _sd_jwt metadata for unwrapping)
183
186
  credential = await credentialServiceRPC.credentialToW3C({credential});
184
187
  }
185
188
  } catch (error) {
@@ -203,6 +206,12 @@ export async function addCredential({wallet, credential}) {
203
206
  value: acummWitness,
204
207
  initialWitness: acummWitness,
205
208
  });
209
+
210
+ // Pre-fetch and cache the witness blockchain data in the background
211
+ credentialServiceRPC.prefetchWitnessCache({
212
+ credential,
213
+ membershipWitness: acummWitness,
214
+ });
206
215
  }
207
216
 
208
217
  syncCredentialStatus({wallet, credentialIds: [credential.id]});