@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.
- package/lib/cloud-wallet.d.ts +79 -3
- package/lib/cloud-wallet.d.ts.map +1 -1
- package/lib/cloud-wallet.js +147 -14
- package/lib/cloud-wallet.js.map +1 -1
- package/lib/credential-provider.d.ts.map +1 -1
- package/lib/credential-provider.js +10 -4
- package/lib/credential-provider.js.map +1 -1
- package/lib/delegation/delegation-chain.d.ts +8 -0
- package/lib/delegation/delegation-chain.d.ts.map +1 -0
- package/lib/delegation/delegation-chain.js +33 -0
- package/lib/delegation/delegation-chain.js.map +1 -0
- package/lib/delegation/delegation-fixtures.d.ts +69 -0
- package/lib/delegation/delegation-fixtures.d.ts.map +1 -0
- package/lib/delegation/delegation-fixtures.js +553 -0
- package/lib/delegation/delegation-fixtures.js.map +1 -0
- package/lib/delegation/delegation-issuance.d.ts +19 -0
- package/lib/delegation/delegation-issuance.d.ts.map +1 -0
- package/lib/delegation/delegation-issuance.js +60 -0
- package/lib/delegation/delegation-issuance.js.map +1 -0
- package/lib/delegation/delegation-offer.d.ts +84 -0
- package/lib/delegation/delegation-offer.d.ts.map +1 -0
- package/lib/delegation/delegation-offer.js +349 -0
- package/lib/delegation/delegation-offer.js.map +1 -0
- package/lib/delegation/delegation-policy-validation.d.ts +28 -0
- package/lib/delegation/delegation-policy-validation.d.ts.map +1 -0
- package/lib/delegation/delegation-policy-validation.js +170 -0
- package/lib/delegation/delegation-policy-validation.js.map +1 -0
- package/lib/delegation/delegation-policy.d.ts +21 -0
- package/lib/delegation/delegation-policy.d.ts.map +1 -0
- package/lib/delegation/delegation-policy.js +73 -0
- package/lib/delegation/delegation-policy.js.map +1 -0
- package/lib/delegation/delegation-tree.d.ts +17 -0
- package/lib/delegation/delegation-tree.d.ts.map +1 -0
- package/lib/delegation/delegation-tree.js +58 -0
- package/lib/delegation/delegation-tree.js.map +1 -0
- package/lib/delegation/delegation-types.d.ts +56 -0
- package/lib/delegation/delegation-types.d.ts.map +1 -0
- package/lib/delegation/delegation-types.js +3 -0
- package/lib/delegation/delegation-types.js.map +1 -0
- package/lib/delegation/delegation-utils.d.ts +3 -0
- package/lib/delegation/delegation-utils.d.ts.map +1 -0
- package/lib/delegation/delegation-utils.js +10 -0
- package/lib/delegation/delegation-utils.js.map +1 -0
- package/lib/did-provider.d.ts +2 -1
- package/lib/did-provider.d.ts.map +1 -1
- package/lib/did-provider.js +11 -7
- package/lib/did-provider.js.map +1 -1
- package/lib/message-provider.js +1 -1
- package/lib/message-provider.js.map +1 -1
- package/lib/verification-controller.d.ts +30 -11
- package/lib/verification-controller.d.ts.map +1 -1
- package/lib/verification-controller.js +372 -68
- package/lib/verification-controller.js.map +1 -1
- package/package.json +3 -3
- package/src/cloud-wallet.test.js +369 -0
- package/src/cloud-wallet.ts +206 -18
- package/src/credential-provider.ts +13 -4
- package/src/delegation/delegation-chain.test.ts +64 -0
- package/src/delegation/delegation-chain.ts +34 -0
- package/src/delegation/delegation-fixtures.ts +552 -0
- package/src/delegation/delegation-issuance.ts +92 -0
- package/src/delegation/delegation-offer.ts +488 -0
- package/src/delegation/delegation-policy-validation.test.ts +237 -0
- package/src/delegation/delegation-policy-validation.ts +281 -0
- package/src/delegation/delegation-policy.ts +100 -0
- package/src/delegation/delegation-tree.test.ts +110 -0
- package/src/delegation/delegation-tree.ts +60 -0
- package/src/delegation/delegation-types.ts +65 -0
- package/src/delegation/delegation-utils.ts +10 -0
- package/src/did-provider.ts +10 -6
- package/src/globals.d.ts +6 -0
- package/src/message-provider.ts +1 -1
- package/src/verification-controller.test.ts +23 -0
- package/src/verification-controller.ts +534 -82
- package/tsconfig.build.json +2 -1
- 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
|
+
});
|
package/src/cloud-wallet.ts
CHANGED
|
@@ -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
|
|
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
|
|
462
|
+
const masterKeyResult = await utilCryptoService.mnemonicToMiniSecret(mnemonic);
|
|
296
463
|
|
|
297
|
-
|
|
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
|
|
493
|
+
masterKey?: Uint8Array;
|
|
494
|
+
mnemonic?: string;
|
|
310
495
|
}) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
176
|
+
if (
|
|
177
|
+
credential &&
|
|
178
|
+
(typeof credential === 'string' || typeof credential === 'object')
|
|
179
|
+
) {
|
|
178
180
|
try {
|
|
179
|
-
const isSDJWT = await credentialServiceRPC.isSDJWTCredential({
|
|
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]});
|